Files
TransPyC/lib/core/LLVMCG/IRMetadataFix.py
T
TermiNexus c83fc752c6 fix(sizeof,parse): 尺寸链去静默兜底,解析报错可定位,补 C 解析器缺口
StructGen: _get_struct_size/_get_align 去 opaque→8/ptr_size、未知→4 兜底并补 VectorType;LiteralStructType.fields→.elements(llvmlite 无 fields,必然 AttributeError)

HandlesClassDef: union 载荷尺寸经不存在的 ir.DataLayout 静默退化成 8 字节;HandlesExprCall/ExprBuiltin: sizeof 各静默 0 分支改 fail-fast,数组元素 sizeof 统一走 _get_struct_size

HandlesFor: nonlocal struct/array 堆分配不再退化成 malloc(8)

vast/vast_c: 补一元 * / & 解析,装饰器后 extern/export 分派修复,C 侧报错带 token 与行号;StubGen.Converter: 宏展开丢尾随空行修 .pyi 缩进非法

其余为 rawptr 迁移 / fail-fast 口径等历史改动一并入库
2026-09-26 19:27:22 +08:00

303 lines
12 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+) = ')
# `!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+)$')
# Decimal digit set for the no-regex fast rewrite path.
_DIGITS: frozenset[str] = frozenset('0123456789')
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 _RewritePlain(Line: str, StrMap: dict[str, str]) -> str:
"""Rewrite `!N` references in a line that has no `"` and no `;`.
Equivalent to a regex substitution of `!` + digits but without the regex
engine callback: split on '!' (C-level), map each digit run by full token,
keep unknown ids verbatim.
"""
Parts: list[str] = Line.split('!')
if len(Parts) == 1:
return Line
Out: list[str] = [Parts[0]]
Ap = Out.append
Get = StrMap.get
for Part in Parts[1:]:
d: int = 0
n: int = len(Part)
while d < n and Part[d] in _DIGITS:
d += 1
if not d:
Ap('!')
Ap(Part)
continue
Digits: str = Part[:d]
# Leading zeros normalise the way int()/str() did in the old path.
Tok: str = '!' + Digits if (d == 1 or Digits[0] != '0') else '!' + str(int(Digits))
New: str | None = Get(Tok)
Ap(Tok if New is None else New)
Ap(Part[d:])
return ''.join(Out)
def _RewriteQuoted(Line: str, StrMap: dict[str, str]) -> str:
"""Rewrite `!N` refs in a line that contains `"` and/or `;`.
String literals and the trailing `;` comment are copied verbatim; the
remaining (quote/comment free) chunks go through `_RewritePlain`. Uses
C-level str.find instead of the old per-character Python scan.
"""
Out: list[str] = []; Ap = Out.append
i: int = 0
n: int = len(Line)
while i < n:
q: int = Line.find('"', i)
s: int = Line.find(';', i, q if q >= 0 else n)
if s >= 0:
Ap(_RewritePlain(Line[i:s], StrMap))
Ap(Line[s:]) # ';' comment: copy verbatim to end of line
break
if q < 0:
Ap(_RewritePlain(Line[i:], StrMap))
break
Ap(_RewritePlain(Line[i:q], StrMap))
j: int = q + 1
while j < n:
if Line[j] == '\\':
j += 2
continue
if Line[j] == '"':
j += 1
break
j += 1
Ap(Line[q:j]) # string literal: copy verbatim
i = j
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 + 1
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]] = []
BlockStrMaps: list[dict[str, str]] = []
BlockIsIdentity: list[bool] = []
# Definition line index -> (new global id, length of its rewritten "!N = " prefix).
# Lets step 4/5 skip a per-line regex to re-detect definition lines.
DefInfo: dict[int, tuple[int, int]] = {}
# Named metadata lines are collected here (only block lines can start with '!').
NamedLines: dict[str, list[int]] = {}
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:
nm = _NamedDefinition.match(Lines[k])
if nm is not None:
NamedLines.setdefault(nm.group(1), []).append(k)
continue
Local: int = int(m.group(1))
Mapping[Local] = Counter
if Local != Counter:
Identity = False
Prefix: str = '!' + str(Counter) + ' = '
DefInfo[k] = (Counter, len(Prefix))
Lines[k] = Prefix + Lines[k][m.end():]
Counter += 1
BlockMaps.append(Mapping)
BlockStrMaps.append({'!' + str(LocalId): '!' + str(GlobId)
for LocalId, GlobId in Mapping.items()})
BlockIsIdentity.append(Identity)
# 4) 改写全文引用(定义体右值、define/指令行的 !dbg、命名元数据列表)
# 定义行左侧的 id 是上一步刚分配的新编号,绝不能再当引用改写:本块的
# 新编号区间可能与它自己的旧局部编号区间重叠(例如新编号恰好也是本块
# 旧 id 843 → Mapping[843]=1686),一旦改写左侧就会出现两个定义共用同一
# id,opt/llc 随即报 "Metadata id is already used"(本 bug 实测残留 276 个
# 重复 id)。故定义行只改写 '=' 之后的右值。
GetDef = DefInfo.get
for k in range(LineCount):
Index: int = Owner[k]
if Index < 0 or BlockIsIdentity[Index] or not BlockMaps[Index]:
continue
Line: str = Lines[k]
if '!' not in Line:
continue
StrMap: dict[str, str] = BlockStrMaps[Index]
Def: tuple[int, int] | None = GetDef(k)
if Def is not None:
Split: int = Def[1]
Tail: str = Line[Split:]
Fixed: str = Line[:Split] + (_RewritePlain(Tail, StrMap)
if ('"' not in Tail and ';' not in Tail)
else _RewriteQuoted(Tail, StrMap))
else:
Fixed = (_RewritePlain(Line, StrMap)
if ('"' not in Line and ';' not in Line)
else _RewriteQuoted(Line, StrMap))
if Fixed != Line:
Lines[k] = Fixed
# 5) 定义体索引(新 id -> 右值文本),供命名元数据判重。
# 只遍历定义行(步骤 3 已登记),不再逐行做正则匹配。
BodyOf: dict[int, str] = {}
for k, (NewId, Split) in DefInfo.items():
BodyOf[NewId] = Lines[k][Split:].strip()
# 6) 命名元数据:同名多份合并成一份。
# - !llvm.dbg.cu:全部条目聚合(每个 DICompileUnit 都必须被列出,不能去重)
# - 其它(!llvm.module.flags、!llvm.ident ...):条目内容(id 引用解析成
# 节点正文)完全相同的多份只留一份,不同则聚合。
# NamedLines 已在步骤 3 随块扫描一并收集(命名行的名字不受引用改写影响)。
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
IsDbgCu: bool = Name == 'llvm.dbg.cu'
for k in Indices:
NamedMatch = _NamedDefinition.match(Lines[k])
Rhs: str = (NamedMatch.group(2) if NamedMatch is not None else '').strip()
ListItems: list[str] | None = _ListItems(Rhs)
if ListItems is None:
Single = False
break
if not IsDbgCu:
# !llvm.dbg.cu never de-dupes by body, so skip resolving bodies for it.
Bodies: list[str] = []
for Item in ListItems:
PureMatch = _PureId.match(Item)
Bodies.append(BodyOf.get(int(PureMatch.group(1)), Item)
if PureMatch else Item)
Key: tuple[str, ...] = tuple(Bodies)
if Key in SeenBodies:
continue
SeenBodies.add(Key)
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)