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:
1 parent
65f0e87193
commit
07ba7a4fe5
5 files changed
+141
-11
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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 解引用空指针崩溃)
|
||||
|
||||
@@ -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 语义)。
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user