Files
TransPyC/lib/core/LLVMCG/IRMetadataFix.py
T
TermiNexus 43e7cdd2cc feat(tpc): 函数级 DWARF 调试元数据 + 合并 IR 元数据规整
DWARF: 新增 LLVMCG/DebugInfo.py(DebugInfoEmitter)——每模块一个 DIFile/DICompileUnit、每个 define 一个 DISubprogram 并在函数入口首条指令挂 DILocation,模块落盘前补 !llvm.dbg.cu 与 !llvm.module.flags(Debug Info Version 3)。开关:新增 --dwarf(默认关闭),dwarf_enabled = args.dwarf or args.asan,沿 Phase2Translator → _translate_file → Translator.DwarfEnabled → LlvmCodeGenerator.dwarf_enabled 下传。define 之外的 declare 不挂。include 库刻意不给 DI(其 .obj 进跨项目共享的 includes.binary 缓存,注入会污染缓存)

关键约束(踩过的坑,已在探针中实测):DICompileUnit 与 DISubprogram 必须 is_distinct=True;emissionKind 用 FullDebug 而非 Full;spFlags 用 DISPFlagDefinition;DISubprogram 必带 type=DISubroutineType,缺了 llc 会崩 0xC0000005;模块级必须有 !llvm.dbg.cu + Debug Info Version 3

IRMetadataFix: 新增 SanitizeIRMetadata,在 _llc_compile 入口统一规整 IR——BuildCombinedIR 是多模块 IR 文本直接拼接,--dwarf 后每个片段自带 !0..!N 与两份命名元数据,22383 行样本里 !0/!1 各重复 67 次导致 opt/llc 报 Metadata id is already used。实现:按块重编号 !N 并改写 !dbg/命名元数据引用、!llvm.dbg.cu 全量聚合、其它命名元数据全同去重;无元数据文件逐字节 no-op、幂等(1.7MB/0.30s)

顺带修 _AddWeakComdat:函数头正则跨行吞换行,--dwarf 把 !dbg 放到 define 行尾后 comdat 被插到 ! 附着之后,llc 报 expected { in function body(现场 52 个 define 中 36 个中招);改为插在附着之前,无 !dbg 时产物逐字节不变

已自证:llc/opt RC=0、行表含 .vp 文件名与行号、opt -passes=forceattrs,asan 与 llvm-link/always-inline 均不丢 !dbg。已知待办:llc 现报 inlinable function call ... must have a !dbg location ⇒ 需补语句/调用点级 !dbg(否则警告 ignoring invalid debug info 会丢弃整份 DI)
2026-09-16 19:29:51 +08:00

249 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""合并 IR 的元数据规整(sanitize)。
场景:多个模块的 .ll 文本被直接拼接成一个文件(每个模块都自带一小段
DWARF 元数据:!llvm.dbg.cu / !llvm.module.flags / !0 = !DIFile / ...
以及内联在 `define ... !dbg !N`、指令行上的 !N 引用)时,各模块的编号
元数据都从 !0 重新开始 —— 合并文件里同一个 !N 被定义多次、命名元数据
也被定义多次,LLVM 解析即报:
error: Metadata id is already used
error: ... !llvm.dbg.cu ... defined more than once
本模块提供 SanitizeIRMetadata(text) -> text:把全文的 `!N = ` 定义按
「元数据块」为单位重新编号成全局唯一 id,并同步改写全文引用;命名元数据
按名字聚合/去重成一份。无元数据的文本逐字节返回原样(严格 no-op),对
已规整的文本再跑一次结果一致(幂等)。
"""
from __future__ import annotations
import re
# `!N = ` 形式的编号元数据定义行
_Definition = re.compile(r'^!(\d+) = ')
# 编号元数据引用
_Reference = re.compile(r'!(\d+)')
# `!name = ...` 形式的命名元数据(首字符为字母,故不会命中 `!N = `)
_NamedDefinition = re.compile(r'^!([A-Za-z][\w.]*)\s*=\s*(.*)$')
# `!{ ... }` 列表形式
_ListForm = re.compile(r'^!\{(.*)\}$', re.DOTALL)
# 纯 id 引用 `!N`
_PureId = re.compile(r'^!(\d+)$')
def _SplitTopLevel(Content: str) -> list[str]:
"""按顶层逗号切分 !{ ... } 的条目(跳过字符串字面量与嵌套括号内的逗号)。"""
Items: list[str] = []
Depth: int = 0
Buf: list[str] = []
i: int = 0
n: int = len(Content)
while i < n:
Ch: str = Content[i]
if Ch == '"':
j: int = i + 1
while j < n:
if Content[j] == '\\':
j += 2
continue
if Content[j] == '"':
j += 1
break
j += 1
Buf.append(Content[i:j])
i = j
continue
if Ch in '{[(':
Depth += 1
elif Ch in '}])':
Depth -= 1
elif Ch == ',' and Depth == 0:
Items.append(''.join(Buf).strip())
Buf = []
i += 1
continue
Buf.append(Ch)
i += 1
Tail: str = ''.join(Buf).strip()
if Tail:
Items.append(Tail)
return Items
def _ListItems(Rhs: str) -> list[str] | None:
"""`!{ a, b }` -> ['a', 'b'];非列表形式返回 None。"""
m = _ListForm.match(Rhs.strip())
if not m:
return None
return _SplitTopLevel(m.group(1))
def _RewriteReferences(Line: str, Mapping: dict[int, int]) -> str:
"""改写一行里对编号元数据的引用,跳过字符串字面量与 `;` 注释。"""
if '!' not in Line:
return Line
if '"' not in Line and ';' not in Line:
# 元数据行绝大多数不含字符串/注释:整体正则替换,避免逐字符扫描
return _Reference.sub(
lambda m: '!' + str(Mapping.get(int(m.group(1)), int(m.group(1)))), Line)
Out: list[str] = []
i: int = 0
n: int = len(Line)
while i < n:
Ch: str = Line[i]
if Ch == '"':
j: int = i + 1
while j < n:
if Line[j] == '\\':
j += 2
continue
if Line[j] == '"':
j += 1
break
j += 1
Out.append(Line[i:j])
i = j
continue
if Ch == ';':
Out.append(Line[i:])
break
if Ch == '!':
m = _Reference.match(Line, i)
if m:
New: int | None = Mapping.get(int(m.group(1)))
Out.append('!' + str(New) if New is not None else m.group(0))
i = m.end()
continue
Out.append(Ch)
i += 1
continue
Out.append(Ch)
i += 1
return ''.join(Out)
def SanitizeIRMetadata(text: str) -> str:
"""让拼接后的 IR 元数据合法:编号全局唯一 + 命名元数据只留一份。
无元数据(没有任何以 `!` 开头的行)时原样返回 —— 严格 no-op。
"""
if '!' not in text:
return text
Segments: list[str] = text.split('\n')
LineCount: int = len(Segments)
# 1) 元数据块 = 连续的以 '!' 开头的行。
# 每个模块的元数据(命名行 + 编号定义)是连续的一段;IR 行把它断开,
# 所以块边界即模块边界,块内共享同一套局部编号空间。
Blocks: list[tuple[int, int]] = [] # [Start, End)
i: int = 0
while i < LineCount:
if Segments[i].startswith('!'):
j: int = i
while j < LineCount and Segments[j].startswith('!'):
j += 1
Blocks.append((i, j))
i = j
else:
i += 1
if not Blocks:
return text
# 2) 每行归属的块:块内行归本块;块之间的行(IR、命名行)归「其后第一个
# 块」—— 因为每个模块的 IR 之后紧跟它自己的元数据块(含被引用的 !N)。
Owner: list[int] = [-1] * LineCount
PrevEnd: int = 0
for Index, (Start, End) in enumerate(Blocks):
for k in range(PrevEnd, End):
Owner[k] = Index
PrevEnd = End
# 3) 块内按定义出现顺序重新编号(局部 id -> 全局 id)
Lines: list[str] = list(Segments)
BlockMaps: list[dict[int, int]] = []
BlockIsIdentity: list[bool] = []
Counter: int = 0
for Start, End in Blocks:
Mapping: dict[int, int] = {}
Identity: bool = True
for k in range(Start, End):
m = _Definition.match(Lines[k])
if not m:
continue
Local: int = int(m.group(1))
Mapping[Local] = Counter
if Local != Counter:
Identity = False
Lines[k] = '!' + str(Counter) + ' = ' + Lines[k][m.end():]
Counter += 1
BlockMaps.append(Mapping)
BlockIsIdentity.append(Identity)
# 4) 改写全文引用(定义体右值、define/指令行的 !dbg、命名元数据列表)
for k in range(LineCount):
Index: int = Owner[k]
if Index < 0 or BlockIsIdentity[Index] or not BlockMaps[Index]:
continue
Fixed: str = _RewriteReferences(Lines[k], BlockMaps[Index])
if Fixed != Lines[k]:
Lines[k] = Fixed
# 5) 定义体索引(新 id -> 右值文本),供命名元数据判重
BodyOf: dict[int, str] = {}
for k in range(LineCount):
m = _Definition.match(Lines[k])
if m:
BodyOf[int(m.group(1))] = Lines[k][m.end():].strip()
# 6) 命名元数据:同名多份合并成一份。
# - !llvm.dbg.cu:全部条目聚合(每个 DICompileUnit 都必须被列出,不能去重)
# - 其它(!llvm.module.flags、!llvm.ident ...):条目内容(id 引用解析成
# 节点正文)完全相同的多份只留一份,不同则聚合。
NamedLines: dict[str, list[int]] = {}
for k in range(LineCount):
m = _NamedDefinition.match(Lines[k])
if m:
NamedLines.setdefault(m.group(1), []).append(k)
Dropped: set[int] = set()
for Name, Indices in NamedLines.items():
if len(Indices) == 1 and Name != 'llvm.dbg.cu':
continue
First: int = Indices[0]
Cr: str = Segments[First][len(Segments[First].rstrip('\r')):]
Items: list[str] = []
SeenItems: set[str] = set()
SeenBodies: set[tuple[str, ...]] = set()
Single: bool = True
for k in Indices:
Rhs: str = _NamedDefinition.match(Lines[k]).group(2).strip()
ListItems: list[str] | None = _ListItems(Rhs)
if ListItems is None:
Single = False
break
Bodies: tuple[str, ...] = tuple(
BodyOf.get(int(_PureId.match(Item).group(1)), Item) if _PureId.match(Item) else Item
for Item in ListItems
)
if Name != 'llvm.dbg.cu':
if Bodies in SeenBodies:
continue
SeenBodies.add(Bodies)
for Item in ListItems:
if Item not in SeenItems:
SeenItems.add(Item)
Items.append(Item)
if not Single:
# 非 `!{ ... }` 列表形式(实际 IR 里不出现):保留首份,其余丢弃,
# 避免出现同名命名元数据被定义多次。
Dropped.update(Indices[1:])
continue
Rebuilt: str = '!%s = !{ %s }%s' % (Name, ', '.join(Items), Cr)
if Rebuilt != Segments[First]:
Lines[First] = Rebuilt
Dropped.update(Indices[1:])
if not Dropped and Lines == Segments:
return text
return '\n'.join(Line for k, Line in enumerate(Lines) if k not in Dropped)