diff --git a/lib/Projectrans/Phase2Translator.py b/lib/Projectrans/Phase2Translator.py index d44484a..41a6850 100644 --- a/lib/Projectrans/Phase2Translator.py +++ b/lib/Projectrans/Phase2Translator.py @@ -1089,6 +1089,33 @@ def _count_braces_outside_strings(text: str) -> int: _LLVM_UNQUOTED_NAME_RE: re.Pattern[str] = re.compile( r'[-a-zA-Z$._][-a-zA-Z$._0-9]*\Z') +# comdat 声明行:`$name = comdat any` / `$"name" = comdat any`(允许行首缩进) +_RE_COMDAT_DECL: re.Pattern[str] = re.compile(r'^\s*\$("?)([^"=\s]+)\1\s*=\s*comdat\b') + + +def _DedupComdatDecls(ll_content: str) -> str: + """删除重复的 `$name = comdat any` 声明(保留首次出现)。 + + 落盘的 `_dso.ll` 是「本模块 + 各依赖 .stub.ll/.text.ll 片段」拼接而成, + 每个片段各自带一份同名 comdat 声明(片段生成时也会各自补 comdat)。同名 + 声明出现两次即 llc 报 `redefinition of comdat '$…'`(现场:泛型特化 + `list[.AST*].__before_init__` 在同一文件里出现 4 次)。comdat 声明 + 在顶层可位于任意位置,保留首次出现即可,语义不变。 + """ + if '= comdat' not in ll_content: + return ll_content + seen: set[str] = set() + kept: list[str] = [] + for line in ll_content.splitlines(True): + m = _RE_COMDAT_DECL.match(line) + if m is not None: + name: str = m.group(2) + if name in seen: + continue + seen.add(name) + kept.append(line) + return ''.join(kept) + def _AddWeakComdat(ll_content: str) -> str: """为 weak/linkonce 系列 函数/全局 定义补 COMDAT(COFF/PE 必需)。 @@ -1096,13 +1123,15 @@ def _AddWeakComdat(ll_content: str) -> str: COFF 下无 COMDAT 的 weak/linkonce 定义会被 llc 展开成强符号 `.weak..default`(weak external 的实现副本)。同一弱定义出现在 多个模块时(泛型特化方法如 `e94ef9e139461915.list[rawptr[AST]]. - __before_init__`、跨模块共享常量如 PYI_BUF_SIZE),副本同名 → + __before_init__`、`linkonce`/`weak` 标注的跨模块共享常量),副本同名 → ld.lld 报 duplicate symbol。补 COMDAT(any) 后 llc 输出单一 COMDAT 成员,链接器自动去重(llc/ld.lld 端到端已实验验证)。 """ if (' linkonce ' not in ll_content and ' linkonce_odr ' not in ll_content and ' weak_odr ' not in ll_content and ' weak ' not in ll_content): - return ll_content + return _DedupComdatDecls(ll_content) + # 拼接而来的片段可能各自已带同名 comdat 声明 ⇒ 先去重,再做本次补全 + ll_content = _DedupComdatDecls(ll_content) weak_links = 'linkonce|linkonce_odr|weak|weak_odr|extern_weak' comdat_names: list[str] = [] @@ -2968,6 +2997,9 @@ class Phase2Translator: # 都会报 "Metadata id is already used"。规整后盘上产物本身即合法 IR, # _llc_compile 内的规整随之成为幂等 no-op。 ll_content = SanitizeIRMetadata(ll_content) + # 拼接片段各自带一份同名 comdat 声明 ⇒ 落盘前统一去重: + # llc 对同一 comdat 声明出现两次即报 redefinition of comdat + ll_content = _DedupComdatDecls(ll_content) dso_ll_path = ll_path[:-3] + '_dso.ll' with open(dso_ll_path, 'w', encoding='utf-8') as f: f.write(ll_content) @@ -3878,6 +3910,8 @@ class Phase2Translator: # 同 App 路径:写盘前规整元数据(拼接片段各自带 !0..!N 的 DI 元数据), # 让盘上产物本身即合法 IR,不依赖 _llc_compile 里那次事后规整。 ll_content = SanitizeIRMetadata(ll_content) + # 同 App 路径:拼接片段各自带一份同名 comdat 声明 ⇒ 落盘前统一去重 + ll_content = _DedupComdatDecls(ll_content) dso_ll_path = ll_path[:-3] + '_dso.ll' with open(dso_ll_path, 'w', encoding='utf-8') as f: f.write(ll_content) diff --git a/lib/core/Handles/HandlesExpr.py b/lib/core/Handles/HandlesExpr.py index 581fa04..e5e6888 100644 --- a/lib/core/Handles/HandlesExpr.py +++ b/lib/core/Handles/HandlesExpr.py @@ -326,12 +326,21 @@ class ExprHandle(BaseHandle): # AST 的 lineno 可能不准确(常量节点可能指向下一行), # 因此在 lineno 和 lineno-1 两行中查找引号 for try_lineno in (node_lineno, node_lineno - 1): - source_line: str | None = Gen._get_source_line(try_lineno) - if source_line and col_offset < len(source_line): - ch: str = source_line[col_offset] + # AST 行号含 _inject_auto_imports 注入的 N 行,须经 _DiskLineno + # 还原成磁盘行号,否则取到 N 行之后的错行:引号判定只靠"错行同列 + # 恰好也是引号"的巧合命中,一旦源码行数变动(单行化/增删行)就会 + # 把 'x' 误判成字符串(i8*)→ 传给 int 形参时 fail-fast。 + source_line: str | None = Gen._get_source_line(Gen._DiskLineno(try_lineno)) + if not source_line: + continue + # col_offset 是 UTF-8 字节偏移(vast Tokenizer 口径)→ 转字符偏移; + # 前缀含多字节字符时二者不等,直接按字节偏移取字符会取错列。 + char_col: int = len(source_line.encode('utf-8')[:col_offset].decode('utf-8', 'ignore')) + if char_col < len(source_line): + ch: str = source_line[char_col] if ch in ("'", '"'): quote_char = ch - if col_offset + 2 < len(source_line) and source_line[col_offset:col_offset + 3] in ("'''", '"""'): + if char_col + 2 < len(source_line) and source_line[char_col:char_col + 3] in ("'''", '"""'): is_triple = True break # 空字符串 → 创建指向 \0 的全局变量 (而非 null, 避免 strcmp 解引用空指针崩溃) diff --git a/lib/core/Handles/HandlesExprCall.py b/lib/core/Handles/HandlesExprCall.py index 09be28f..2e30612 100644 --- a/lib/core/Handles/HandlesExprCall.py +++ b/lib/core/Handles/HandlesExprCall.py @@ -723,13 +723,35 @@ class ExprCallHandle(BaseHandle): # 因此改走权威 typedef 值:'t.CUInt64T' → CTypeRegistry.NameToLLVM # → 'i64'(与 DeclarationGenerator 的 stub 兜底同机制)。 _bv_td: str | None = _TAR_TD.builtins_typedef_value(FuncName) + _lls_td: str | None = None if _bv_td: _nm_td: str = _bv_td[2:] if _bv_td.startswith('t.') else _bv_td from lib.includes.t import CTypeRegistry as _CR_TD - _lls_td: str | None = _CR_TD.NameToLLVM(_nm_td) - if _lls_td: - _tg_td: ir.Type = Gen._ctype_to_llvm(_lls_td) - if isinstance(_tg_td, (ir.IntType, ir.FloatType, ir.DoubleType, ir.PointerType)): + _lls_td = _CR_TD.NameToLLVM(_nm_td) + if not _lls_td: + # 声明值本身也是短别名(新语法:`typedef short = int16`、 + # `typedef longlong = int64`)⇒ 单步 NameToLLVM(不认短别名) + # 落空。改走权威链式解析(builtins.vp 声明 → 逐级展开到 t.C*), + # 与注解位置 LLVMTypeMapper._resolve_name 同一来源。 + _td_info = _TAR_TD._lookup_builtins_typedef(FuncName, self.Trans.SymbolTable) + if _td_info is not None and _td_info.BaseType: + _tg_td2: ir.Type = Gen._ctype_to_llvm(_td_info) + if isinstance(_tg_td2, (ir.IntType, ir.FloatType, ir.DoubleType, ir.PointerType)): + if len(Node.args) >= 2: + _p2b: ast.AST = Node.args[1] + _p2b_cptr: bool = ( + (isinstance(_p2b, ast.Attribute) and _p2b.attr == 'CPtr' + and isinstance(_p2b.value, ast.Name) and _p2b.value.id in ('t', 'c')) or + (isinstance(_p2b, ast.Name) and _p2b.id == 'CPtr') + ) + if _p2b_cptr: + if isinstance(_tg_td2, ir.PointerType): + return self._emit_rawptr_cast(Node, Gen, _tg_td2) + return self._emit_rawptr_cast(Node, Gen, ir.PointerType(_tg_td2)) + return self._emit_rawptr_cast(Node, Gen, _tg_td2) + if _lls_td: + _tg_td: ir.Type = Gen._ctype_to_llvm(_lls_td) + if isinstance(_tg_td, (ir.IntType, ir.FloatType, ir.DoubleType, ir.PointerType)): # 双参数形式 uint8(s, t.CPtr) / int64(v, CPtr):第二参是 # CPtr 标记 → 指针包装(等价旧写法 t.CUInt8T(s, t.CPtr), # 对齐 Attribute 形式 t.CXXX(v, t.CPtr) 的 IsPtr_cast 语义)。 diff --git a/lib/core/LLVMTypeMapper.py b/lib/core/LLVMTypeMapper.py index 9143c1a..d96b093 100644 --- a/lib/core/LLVMTypeMapper.py +++ b/lib/core/LLVMTypeMapper.py @@ -183,6 +183,25 @@ class LLVMTypeMapper: Entry: CTypeInfo = self._symtab[node.id] if Entry.IsTypedef and Entry.OriginalType: return Entry.OriginalType + # 短别名 / 内建速记(char/int/int64/uchar/str…):与注解位置 + # `_resolve_name` 同源。缺此分支时 typedef 值里的裸名解析为空, + # 整条 typedef 随之解析失败(现场:w32/win32base.vp 的 + # `LPCSTR: t.CTypedef = t.CConst | char | t.CPtr` ⇒ LPCSTR 不登记, + # 用到处报「裸名 LPCSTR 无法解析为类型」)。 + _binfo: CTypeInfo | None = self._lookup_builtins_typedef(node.id) + if _binfo is None: + _e: tuple[type, int] | None = BuiltinTypeMap.Get(node.id) + if _e: + _binfo = CTypeInfo() + _binfo.BaseType = _e[0] + _binfo.PtrCount = _e[1] + if _binfo is not None and _binfo.BaseType: + _bt: object = _binfo.BaseType + _nm: str = (_bt if isinstance(_bt, str) + else CTypeHelper.GetCName(_bt) if isinstance(_bt, type) + else type(_bt).__name__) + if _nm and _nm != 'CType': + return _nm + (' *' * (_binfo.PtrCount or 0)) return '' if isinstance(node, ast.Attribute): if isinstance(node.value, ast.Name) and IsTModule(node.value.id): @@ -344,6 +363,21 @@ class LLVMTypeMapper: builtin_llvm: str = self._ctype_to_llvm_str(builtin_info) if builtin_llvm: return builtin_llvm + # 存储类 / 类型限定符标记(CExport/CExtern/CStatic/CInline/CConst/ + # CVolatile/State 等,均以 `from t import ...` 裸名出现):自身不是类型, + # 只作标记 ⇒ 贡献空串,由 _resolve_bitor 取另一侧真实类型 + # (`CInt | CExport` → i32)。与 _resolve_attribute 的 marker 分支、 + # TypeAnnotationResolver._ResolveNameNode 的 STORAGE_CLASS/TYPE_QUALIFIER + # 判定同口径。缺此分支时裸名标记会落到下方 fail-fast —— 而带前缀写作 + # `t.CExport` 时却能通过(走 Attribute 路径),同一语义两种写法结果不同。 + # 现场:Test/AstTest `def main() -> CInt | CExport`(第 108 行)。 + _marker_cls: object = getattr(_t, node.id, None) + if (isinstance(_marker_cls, type) and _marker_cls is not _t.CType + and issubclass(_marker_cls, _t.CType)): + _pos_tags: object = getattr(_marker_cls, 'position', None) or () + if _t.CType.BASE not in _pos_tags and ( + _t.CType.STORAGE_CLASS in _pos_tags or _t.CType.TYPE_QUALIFIER in _pos_tags): + return '' # fail-fast:类型信息缺失不再静默退化成 i32 _node_line: object = getattr(node, 'lineno', None) _node_col: object = getattr(node, 'col_offset', None) diff --git a/lib/core/SymbolTypeResolver.py b/lib/core/SymbolTypeResolver.py index cf4630f..519cf62 100644 --- a/lib/core/SymbolTypeResolver.py +++ b/lib/core/SymbolTypeResolver.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: from lib.core.SymbolData import ( ModuleSymbols, TypedefSymbolData, FuncSymbolData, DefineSymbolData ) -from lib.core.Handles.HandlesBase import CTypeInfo, CTypeHelper +from lib.core.Handles.HandlesBase import CTypeInfo, CTypeHelper, BuiltinTypeMap from lib.core.LLVMTypeMapper import LLVMTypeMapper, TypeNameUnresolvedError from lib.core.SymbolUtils import IsTModule from lib.includes import t @@ -221,6 +221,37 @@ class TypeResolver: td.original_type_kind = 'typedef' return + # 最后兜底:builtins.vp 的短别名(int8/uint64/uchar/ushort/sizet…) + # 与内建速记(int/char/float/bool/str…)。 + # `_find_base_type` 只认 `t.X`(ast.Attribute)形态,裸名根本走不到上面 + # 的 GetCName 分支 ⇒ `X: t.CTypedef = int64` 这类声明此前整条解析失败, + # X 不登记进符号表,用到处报「裸名 X 无法解析为类型」 + # (现场:includes/posix.vp 的 off_t、stdint.vp 的 INT)。 + # 此分支与注解位置 LLVMTypeMapper._resolve_name 的来源一致,产物形态也 + # 与 `t.CInt64T` 分支逐字相同(original_class 存 t.C 类名)。 + builtin_info: CTypeInfo | None = self._type_mapper._lookup_builtins_typedef(ref_name) + if builtin_info is None: + _entry: tuple[type, int] | None = BuiltinTypeMap.Get(ref_name) + if _entry: + builtin_info = CTypeInfo() + builtin_info.BaseType = _entry[0] + builtin_info.PtrCount = _entry[1] + if builtin_info is not None and builtin_info.BaseType: + _bt: object = builtin_info.BaseType + # BaseType 可能是 t.C 类名(str)、CType 子类,或 CType **实例** + # (_lookup_builtins_typedef 走的是 t.CInt64T() 实例); + # CTypeHelper.GetCName 只认前两种 ⇒ 实例取 type(x).__name__。 + if isinstance(_bt, str): + _cname: str = _bt + elif isinstance(_bt, type): + _cname = CTypeHelper.GetCName(_bt) + else: + _cname = type(_bt).__name__ + if _cname and _cname != 'CType': + td.original_class = _cname + (' *' * (builtin_info.PtrCount or 0)) + td.original_type_kind = 'typedef' + return + td.original_type_kind = None td.original_class = None