fix(codegen): 新规范迁移暴露的 4 类解析/代码生成缺陷

1) HandlesExpr._HandleConstantLlvm 引号判定用 AST lineno 直查磁盘行,漏了 _inject_auto_imports 注入行的偏移 N,取到错行;改用 _DiskLineno 还原,并把 col_offset(UTF-8 字节偏移)转字符偏移后再切片。2) Phase2Translator 新增 _DedupComdatDecls:_dso.ll 由各依赖片段拼接,每片段各带同名 comdat 声明,llc 报 redefinition of comdat;在 _AddWeakComdat 入口与两处落盘点去重。3) LLVMTypeMapper._resolve_name 补存储类/类型限定符标记分支(CExport/CExtern/CConst… 返回空串,由 _resolve_bitor 取另一侧真实类型),与 _resolve_attribute 同口径。4) 短别名(int/int64/sizet/char…)在 typedef 右值与类型转换调用处解析不到:SymbolTypeResolver._resolve_typedef_from_value 与 LLVMTypeMapper.resolve_typedef_value_type 的 ast.Name 分支补 builtins typedef / BuiltinTypeMap 兜底(BaseType 可能是 CType 实例,须取 type(x).__name__),HandlesExprCall 的类型转换求值补 _lookup_builtins_typedef 回退并保留双参数 CPtr 指针包装。验证:TransPyV 自举 includes 52/52 + App 76/76 全通过;TPV 顺序回归 77/77。
This commit is contained in:
TermiNexus committed 2026-10-05 09:25:02 +08:00
1 parent 65f0e87193
commit 07ba7a4fe5
5 files changed
+141 -11

No files matched your search

+36 -2
View File
@@ -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[<sha1>.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.<name>.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)
+13 -4
View File
@@ -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 解引用空指针崩溃)
+26 -4
View File
@@ -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 语义)。
+34
View File
@@ -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)
+32 -1
View File
@@ -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