from __future__ import annotations from typing import Any import vast as ast import os import re import sys import llvmlite.ir as ir from lib.core.Handles.HandlesBase import CTypeInfo from lib.core.VLogger import get_logger as _vlog from lib.constants.config import mode as _config_mode class FailFastError(Exception): """专用 fail-fast 异常:编译器主动抛出的编译错误(不可隐式转换/不可恢复)。 刻意与 llvmlite 自身抛出的 TypeError/ValueError 区分开 —— 后者属于"此处可以尝试 简化回退"的类型调整失败,本异常则是必须上抛、禁止被兜底 `except` 吞掉的信号 (例如 `_adjust_args` 的 L120/L181 两条 fail-fast)。 """ class MemberLookupFailFast(KeyError): """成员偏移查找失败的 fail-fast 异常(**继承 KeyError** 以保持既有兜底点可用)。 为什么继承 KeyError:本仓有两处 `except KeyError` 把 `_get_member_offset` 的 失败当作"该字段类型未知"继续回查(HandlesExprAttr 的字段类型三级回退),改成 其它异常类型会把这些兜底点击穿。 为什么重写 `__str__`:KeyError 的默认 `__str__` 是 `repr(消息)` —— 会在报文两端 加引号、并把换行/制表转义成字面 `\\n`,多行 fail-fast 报文(含"源代码 (文件, 行号): 行号 | 源码")因此彻底不可读。这里原样返回裸消息,与 FailFastError 的观感一致。 """ def __str__(self) -> str: if self.args and isinstance(self.args[0], str): return self.args[0] return super().__str__() class FuncGenMixin: def setup_from_symbol_table(self, SymbolTable: Any) -> None: self.SymbolTable: Any = SymbolTable for name, info in SymbolTable.items(): if isinstance(info, CTypeInfo): # CTypeInfo 对象:struct/union 由 StructGen 处理,此处只处理 dict 格式 continue if not isinstance(info, dict): if hasattr(info, 'get'): info = dict(info) if hasattr(info, '__iter__') else {} else: continue TypeKind: str = info.get('type', '') if TypeKind == 'struct': ClassName: str = name self.class_methods[ClassName] = [] self.class_members[ClassName] = [] members: dict[str, Any] = info.get('members', {}) if members: for MemberName, MemberInfo in members.items(): if isinstance(MemberInfo, dict): MemberType: ir.Type = self._type_str_to_llvm(MemberInfo.get('type', 'int'), MemberInfo.get('IsPtr', False)) self.class_members[ClassName].append((MemberName, MemberType)) elif isinstance(MemberInfo, CTypeInfo): MemberType: ir.Type = self._ctype_to_llvm(MemberInfo) if MemberType is None: MemberType = self._type_str_to_llvm(MemberInfo.ToString(), MemberInfo.IsPtr) self.class_members[ClassName].append((MemberName, MemberType)) self._generate_structs() self._create_Vtable_globals() def register_method(self, ClassName: str, MethodName: str, IsVtableSlot: bool = True) -> None: """登记类方法。 IsVtableSlot:该方法是否占用虚表槽(class_methods 即 vtable 槽序唯一真源)。 默认 True 与既有语义逐字一致;False 用于方法级 `novirtual def`(⇒ 方法装饰器 @t.NoVTable)——此时只保证类的方法容器存在,**不**把它排进槽序,使 TPC 与 TPV(ClassGenBase._should_method_be_virtual 排除该项)的虚表布局逐槽相同。 """ if ClassName not in self.class_methods: self.class_methods[ClassName] = [] if ClassName not in self.class_members: self.class_members[ClassName] = [] self._generate_structs() self._create_Vtable_globals() # 构造函数(__new__/__init__/__before_init__)不放入 vtable, # 避免子类与基类 vtable 大小不一致导致虚分派索引偏移 _short_name: str = MethodName.split('.')[-1] if '.' in MethodName else MethodName if _short_name in ('__new__', '__init__', '__before_init__'): return if not IsVtableSlot: return if MethodName not in self.class_methods[ClassName]: self.class_methods[ClassName].append(MethodName) def _apply_auto_addr(self, func_name: str, args: list[ir.Value]) -> list[ir.Value]: """对函数参数应用自动取址逻辑(ref / temp / always 三种模式)。 在调用前对标记为 auto-addr 的实参做地址适配(模式由 HandlesFunctions 从形参注解 读出并写入 Gen._auto_addr_params[func_name]): - 'ref'(&T):实参已是指针 → 原样传;非指针左值 → 传该左值地址; 非指针且非左值(字面量/表达式结果)→ fail-fast(禁止静默降级)。 - 'temp'(&temp T):实参已是指针 → 原样传;否则在 entry 块建同型临时 + store + 传地址。 - 'always'(t.CAutoPtr):无条件取址(保持既有语义)。 非指针左值的地址取自实参 load 指令的指针操作数(`load(p)` ⇒ p 即左值地址), 因为调用点实参已按 rvalue 求值(标量变量的 load 结果)。 """ modes: list[str | None] | None = self._auto_addr_params.get(func_name) if not modes: return args new_args: list[ir.Value] = list(args) for i, mode in enumerate(modes): if i >= len(new_args) or mode is None: continue arg: ir.Value = new_args[i] is_ptr: bool = isinstance(arg.type, ir.PointerType) if mode == 'ref': if is_ptr: continue if isinstance(arg, ir.LoadInstr) and len(arg.operands) == 1: # load(p) 的 p 即该左值的地址(该实参是变量/字段/下标的取值) new_args[i] = arg.operands[0] continue raise FailFastError( f"&T 形参只接受左值,右值请用具名临时变量或改用 &temp T" f"(调用 {func_name} 第 {i} 个实参类型 {arg.type})") if mode == 'temp': if is_ptr: continue slot: ir.Value = self._allocaEntry(arg.type, name=f"autoaddr_tmp_{i}") self.builder.store(arg, slot) new_args[i] = slot elif mode == 'always': slot: ir.Value = self.builder.alloca(arg.type) self.builder.store(arg, slot) new_args[i] = slot return new_args def _adjust_args(self, args: list[ir.Value], func: ir.Function) -> list[ir.Value]: adjusted: list[ir.Value] = [] for i, (arg, param) in enumerate(zip(args, func.args)): if arg.type != param.type: if isinstance(arg.type, ir.PointerType) and isinstance(param.type, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)): if arg.type.pointee == param.type or (hasattr(arg.type.pointee, 'name') and hasattr(param.type, 'name') and arg.type.pointee.name == param.type.name): # C 语言中数组参数退化为指针,不做 load 而是退化为元素指针 if isinstance(param.type, ir.ArrayType): zero: ir.Constant = ir.Constant(ir.IntType(32), 0) elem_ptr: ir.Value = self.builder.gep(arg, [zero, zero], name=f"array_decay_arg_{i}") adjusted.append(elem_ptr) else: adjusted.append(self._load(arg, name=f"Load_arg_{i}")) continue try: if isinstance(arg.type, ir.IntType) and isinstance(param.type, ir.PointerType): if isinstance(param.type.pointee, ir.IntType) and param.type.pointee.width == 8 and arg.type.width == 8: # 当参数是 i8(字符值)而函数期望 i8*(字符串指针)时 # 为字符值分配内存,创建 null-terminated 字符串 tmp: ir.AllocaInstr = self._alloca(ir.ArrayType(ir.IntType(8), 2), name=f"char2str_arg_{i}") zero: ir.Constant = ir.Constant(ir.IntType(32), 0) char_ptr: ir.Value = self.builder.gep(tmp, [zero, zero], name=f"char2str_ptr_{i}") self._store(arg, char_ptr) null_ptr: ir.Value = self.builder.gep(tmp, [zero, ir.Constant(ir.IntType(32), 1)], name=f"char2str_null_{i}") self._store(ir.Constant(ir.IntType(8), 0), null_ptr) adjusted.append(char_ptr) else: if isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.IntType): # 当参数是指针而函数期望整数时,使用 ptrtoint adjusted.append(self.builder.ptrtoint(arg, param.type, name=f"ptrtoint_arg_{i}")) else: if arg.type.width < 32: arg = self.builder.zext(arg, ir.IntType(32), name=f"zext_arg_{i}") adjusted.append(self.builder.inttoptr(arg, param.type, name=f"inttoptr_arg_{i}")) elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.IntType): # fail-fast:指针→整数隐式转换(跨类型层级,有损)禁止, # 须显式转换(用户隐式转换规则:同层指针可转,指针→整数不允许)。 # 诊断增强(只在即将终止时拼串,零行为影响):把 callee 名、 # 实参/形参个数与**逐项类型**一并报出——只给单个类型对无法判断 # 「形参表整体错位一位」(如 self/pool 约定不一致)这类根因。 _diag: str = "; ".join( f"[{k}] arg={a.type} param={p.type}" for k, (a, p) in enumerate(zip(args, func.args))) raise FailFastError( f"调用参数类型不匹配: 指针类型 {arg.type} 不能隐式转换为" f"整数参数 {param.type};如需转换请显式 cast" f"(callee={func.name} 实参{len(args)}个/形参{len(func.args)}个;" f"逐项: {_diag})") elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.PointerType): if isinstance(arg.type.pointee, ir.PointerType) and isinstance(param.type.pointee, ir.IntType): Loaded: ir.Value = self._load(arg, name=f"Load_arg_{i}") adjusted.append(Loaded) elif isinstance(arg.type.pointee, ir.ArrayType) and isinstance(param.type.pointee, (ir.IntType, ir.FloatType, ir.DoubleType)): zero: ir.Constant = ir.Constant(ir.IntType(32), 0) elem_ptr: ir.Value = self.builder.gep(arg, [zero, zero], name=f"array_decay_arg_{i}") if elem_ptr.type != param.type: adjusted.append(self.builder.bitcast(elem_ptr, param.type, name=f"cast_arg_{i}")) else: adjusted.append(elem_ptr) elif isinstance(arg.type.pointee, ir.ArrayType) and isinstance(param.type.pointee, ir.PointerType): zero: ir.Constant = ir.Constant(ir.IntType(32), 0) elem_ptr: ir.Value = self.builder.gep(arg, [zero, zero], name=f"array_decay_arg_{i}") if elem_ptr.type != param.type: adjusted.append(self.builder.bitcast(elem_ptr, param.type, name=f"cast_arg_{i}")) else: adjusted.append(elem_ptr) else: # 新增指针隐式转换规则①/②:通用指针通配放行;非通用 # T*→U* 仅在无损时放行,非基本/有损/层数不同 fail-fast。 Allowed: bool Reason: str Allowed, Reason = self._ClassifyPtrImplicitConvert(arg.type, param.type) if not Allowed: raise FailFastError( f"指针隐式转换已禁止({Reason}):调用 {func.name} 第 {i} 个实参" f" {arg.type} 不能隐式转为形参 {param.type};如需转换请显式 cast" f"(如 t.CPtr(...)){self._DescribeValueNode()}") adjusted.append(self.builder.bitcast(arg, param.type, name=f"cast_arg_{i}")) else: if isinstance(arg.type, ir.PointerType) and isinstance(param.type, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)): if arg.type.pointee == param.type or (hasattr(arg.type.pointee, 'name') and hasattr(param.type, 'name') and arg.type.pointee.name == param.type.name): adjusted.append(self._load(arg, name=f"Load_arg_{i}")) elif isinstance(arg.type.pointee, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)): tmp: ir.AllocaInstr = self._alloca(param.type, name=f"temp_arg_{i}") Loaded: ir.Value = self.builder.bitcast(arg, ir.PointerType(param.type), name=f"cast_arg_{i}") Loaded_val: ir.Value = self._load(Loaded, name=f"Load_arg_{i}") self._store(Loaded_val, tmp) adjusted.append(self._load(tmp, name=f"reLoad_arg_{i}")) else: tmp: ir.AllocaInstr = self._alloca(param.type, name=f"temp_arg_{i}") cast_ptr: ir.Value = self.builder.bitcast(arg, ir.PointerType(param.type), name=f"cast_arg_{i}") Loaded_val: ir.Value = self._load(cast_ptr, name=f"Load_arg_{i}") self._store(Loaded_val, tmp) adjusted.append(self._load(tmp, name=f"reLoad_arg_{i}")) elif isinstance(arg.type, ir.PointerType) and arg.type.pointee == param.type: adjusted.append(self._load(arg, name=f"Load_arg_{i}")) elif isinstance(param.type, ir.PointerType) and param.type.pointee == arg.type: tmp: ir.AllocaInstr = self._alloca(arg.type, name=f"temp_arg_{i}") self._store(arg, tmp) adjusted.append(tmp) elif isinstance(arg.type, ir.IntType) and isinstance(param.type, ir.IntType): if arg.type.width < param.type.width: # 整数扩展:使用 sext(符号扩展)以正确处理负数 adjusted.append(self.builder.sext(arg, param.type, name=f"sext_arg_{i}")) else: adjusted.append(self.builder.trunc(arg, param.type, name=f"trunc_arg_{i}")) elif isinstance(arg.type, (ir.FloatType, ir.DoubleType)) and isinstance(param.type, (ir.FloatType, ir.DoubleType)): if isinstance(arg.type, ir.DoubleType) and isinstance(param.type, ir.FloatType): adjusted.append(self.builder.fptrunc(arg, param.type, name=f"fptrunc_arg_{i}")) elif isinstance(arg.type, ir.FloatType) and isinstance(param.type, ir.DoubleType): adjusted.append(self.builder.fpext(arg, param.type, name=f"fpext_arg_{i}")) else: adjusted.append(arg) elif isinstance(arg.type, (ir.FloatType, ir.DoubleType)) and isinstance(param.type, ir.IntType): # fail-fast:浮点→整数隐式转换(有损)禁止,须显式转换 # (用户隐式转换规则:浮点→整数不允许)。 raise FailFastError( f"调用参数类型不匹配: 浮点类型 {arg.type} 不能隐式转换为" f"整数参数 {param.type};如需转换请显式 cast") elif isinstance(arg.type, ir.IntType) and isinstance(param.type, (ir.FloatType, ir.DoubleType)): adjusted.append(self.builder.sitofp(arg, param.type, name=f"sitofp_arg_{i}")) elif isinstance(arg.type, ir.IntType) and isinstance(param.type, ir.PointerType): # 整数转指针:inttoptr if arg.type.width < 64: i64_val: ir.Value = self.builder.sext(arg, ir.IntType(64), name=f"sext_arg_{i}") adjusted.append(self.builder.inttoptr(i64_val, param.type, name=f"inttoptr_arg_{i}")) else: adjusted.append(self.builder.inttoptr(arg, param.type, name=f"inttoptr_arg_{i}")) elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.IntType): # 指针转整数:ptrtoint i64_val: ir.Value = self.builder.ptrtoint(arg, ir.IntType(64), name=f"ptrtoint_arg_{i}") if i64_val.type.width > param.type.width: adjusted.append(self.builder.trunc(i64_val, param.type, name=f"trunc_arg_{i}")) elif i64_val.type.width < param.type.width: adjusted.append(self.builder.sext(i64_val, param.type, name=f"sext_arg_{i}")) else: adjusted.append(i64_val) else: adjusted.append(self.builder.bitcast(arg, param.type, name=f"cast_arg_{i}")) # 只收口 llvmlite 自身"可回退"的类型调整失败(builder/IR 构造抛出的 # TypeError/ValueError);FailFastError 与其余异常一律上抛, # 避免编译器主动抛出的真因被伪装成 llvmlite 的下游报文。 except (TypeError, ValueError): # 参数类型调整失败时使用简化回退逻辑 if isinstance(arg.type, ir.PointerType) and isinstance(param.type, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)): if arg.type.pointee == param.type or (hasattr(arg.type.pointee, 'name') and hasattr(param.type, 'name') and arg.type.pointee.name == param.type.name): adjusted.append(self._load(arg, name=f"Load_arg_{i}")) else: adjusted.append(self.builder.bitcast(arg, param.type, name=f"cast_arg_{i}")) elif isinstance(arg.type, ir.PointerType) and arg.type.pointee == param.type: adjusted.append(self._load(arg, name=f"Load_arg_{i}")) elif isinstance(param.type, ir.PointerType) and param.type.pointee == arg.type: tmp: ir.AllocaInstr = self._alloca(arg.type, name=f"temp_arg_{i}") self._store(arg, tmp) adjusted.append(tmp) elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.PointerType): # 任意指针到指针的类型转换(如 struct* -> i8*) adjusted.append(self.builder.bitcast(arg, param.type, name=f"cast_arg_{i}")) else: adjusted.append(arg) else: adjusted.append(arg) while len(adjusted) < len(func.args): missing_idx: int = len(adjusted) param: ir.Argument = func.args[missing_idx] param_type: ir.Type = param.type if hasattr(param, 'type') else param if isinstance(param_type, ir.PointerType): adjusted.append(ir.Constant(param_type, None)) elif isinstance(param_type, ir.IntType): adjusted.append(ir.Constant(param_type, 0)) elif isinstance(param_type, (ir.FloatType, ir.DoubleType)): adjusted.append(ir.Constant(param_type, 0.0)) else: adjusted.append(ir.Constant(ir.IntType(32), 0)) if func.type.pointee.var_arg: for i in range(len(func.args), len(args)): arg: ir.Value = args[i] adjusted.append(arg) return adjusted def _class_has_vtable(self, ClassName: str | None) -> bool: """类(或其任一祖先)是否带 vtable 槽。 与 StructGen.py:442-450 的父链传播同源——vtable 沿继承链携带,子类 struct 布局为 [vtable?] + [基类字段…] + [自身字段…]。只认 class_vtable / _cross_module_vtable_classes(装饰器集合)会漏掉源码未写 @t.CVTable 的跨模块 AST 节点类(ast.Try / ast.Match / ast.ExceptHandler…):它们有方法、继承自 @t.CVTable 的 AST,struct 首元素确实是 vtable 指针,但 StructGen.py:415 在 "struct body 已从 stub 完整加载"时直接 continue,永不把它们登记进集合 (实测 TransPyV/temp/_shared_sym.json 的 class_vtable/cross_module_vtable 仅有 {AST, MemManager, Module})。此时 base 静默取 0 → 该类字段偏移整体前移 1 (ast.ExceptHandler.children 落到 end_col_offset,i32)。 """ if not ClassName: return False _Seen: set[str] = set() _Cur: str | None = ClassName while _Cur and _Cur not in _Seen: _Seen.add(_Cur) if _Cur in self.class_vtable or _Cur in self._cross_module_vtable_classes: return True _Cur = self.class_parent.get(_Cur) return False def _NormalizeMemberClass(self, ClassName: str | None) -> str | None: """成员查询用的类键归一(短名 + 指针包装展开)。 - 统一短名解析(治本修复 2026-08-10):struct/class_members/class_vtable/ class_parent 均以短名注册(跨模块加载 HandlesImports L1604),而调用方 可能传入带 sha1 前缀的全名(如 "f47f88ae6545965a.Import")。 - rawptr[X] / t.CPtr[X] 指针包装归一:rawptr[X] 是内建裸指针类型 (X*),非真实类——接收者注解/类型名为 'rawptr[AST]' 时字段访问 (child.parent)应作用于内层 struct X,否则查 class_members 全 miss → fast-fail "属性 'parent' 在类 'rawptr[AST]' 中找不到(struct=opaque)" (includes/ast/base.vp _set_parent_list bootstrap 根因)。 """ if not ClassName: return ClassName _Cls: str = self._extract_short_name(ClassName) if _Cls.startswith('rawptr[') or _Cls.startswith('t.CPtr['): _lb: int = _Cls.find('[') if _Cls.endswith(']') and _lb > 0: _inner: str = _Cls[_lb + 1:-1].strip() _short_inner: str = _inner.rsplit('.', 1)[-1] if '.' in _inner else _inner _hit: str | None = None if _short_inner in self.structs: _hit = _short_inner else: for _k in self.structs: if self._extract_short_name(_k) == _short_inner: _hit = _k break if _hit: _Cls = _hit return _Cls def _member_base(self, ClassName: str | None, StructHint: Any = None) -> int: """类 struct 的成员偏移基址(vtable 占位槽数)——**全仓唯一真源**。 `StructHint`(可选):调用方手里**实际要用于 GEP 的那个 struct 实例** (通常就是 `ObjVal.type.pointee`)。同一类键上可能同时存在两个实例 (同名类 sha1 冲突的双注册,或一个已从 stub 加载完整、另一个仍 opaque), 而偏移基址必须与"实际 GEP 用的那份布局"一致 —— 用键上那份算、用另一份读, 会让整类字段偏移平移(现场:`Constant.lineno` 偏移 2 落到 `pool`(MemManager*), 实参第 6 项被报"指针不能隐式转整数 i32")。传 hint 时以 hint 为准并**报出** 两侧差异(每类一次),不传时行为与改前逐字一致。 `_get_member_offset` 与所有"自算 base 再 GEP 字段"的消费点(构造/初始化、 字段默认值、继承方法 this 调整等)都必须走本方法,禁止各自直查 `class_vtable`/`_cross_module_vtable_classes`——旧实现只认这两个装饰器集合, 跨模块 AST 节点类(ast.Try/ast.Match/ast.ExceptHandler…)源码未写 @t.CVTable 且 StructGen 在"struct body 已从 stub 加载"时提前 continue, 永不把它们登记进集合,于是 base 只能靠"成员表长度 == struct 元素数 - 1" 这个长度启发式救回;成员表未展平/尚未加载时启发式失效 ⇒ base 静默取 0 ⇒ 该类所有字段偏移整体前移 1(ast.ExceptHandler.children 落到 end_col_offset(i32),len() 分派拿到 i32 ⇒ Type of #1 arg mismatch)。 判定规则(与 `StructGen._generate_structs` 生成 struct 的规则同源): - vtable 归属沿 class_parent 链判定(见 `_class_has_vtable`); - 再用 struct 实际元素数校准: delta(=struct 元素数 - 成员表长度) == 0 ⇒ 该 struct 无 vtable 槽 → base=0; delta == 1 ⇒ 恰多一个槽:首元素为指针 / 无父类 / 继承链归属成立 → base=1; delta >= 2 ⇒ 长度启发式失效,保留"继承链 vtable 归属"结果(不再静默取 0)。 查询前会调用 `HandlesImports.EnsureClassMembersFlattened`(幂等)把"停在自有 字段"的未展平表补齐——未展平表会让下列 delta 校准失真,也会让按表内下标 取字段的消费点漏掉继承前缀。 """ _Cls: str | None = self._NormalizeMemberClass(ClassName) if not _Cls: return 0 # 查询前确保成员表已按父类展平(幂等、顺序无关)。 # 未展平的表(停在自有字段)会让本方法的 delta 校准失真,也会让所有 # "按成员表下标 base+i 取字段"的消费点漏掉继承前缀 —— 见 # HandlesImports.EnsureClassMembersFlattened(内部有纯内存前置判断与 # 重入/重复尝试保护,对已展平类零额外开销)。 _EnsureIH: Any = getattr(self, '_import_handler_ref', None) if _EnsureIH is not None and hasattr(_EnsureIH, 'EnsureClassMembersFlattened'): try: _EnsureIH.EnsureClassMembersFlattened(_Cls, self) except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"补齐类成员展平失败: {_e}", "Exception") _ByInheritVtable: bool = self._class_has_vtable(_Cls) base: int = 1 if _ByInheritVtable else 0 # 布局来源:优先调用方**实际 GEP 用的那个实例**(StructHint)——偏移必须与 # 真正被索引的那份 body 同源。只在 hint 不可用时才回落到类键上的实例。 _RegBase: ir.IdentifiedStructType | None = self.structs.get(_Cls) _StructBase: ir.IdentifiedStructType | None = _RegBase if isinstance(StructHint, ir.IdentifiedStructType): # 仅接受**同类**的 hint:接收者静态类型是基类、实际指向派生类实例时, # hint 的布局属于派生类,拿它配 `class_members[_Cls]` 会算错 delta。 _HintShort: str = self._extract_short_name(getattr(StructHint, 'name', '') or '') if _HintShort == _Cls: _StructBase = StructHint if _RegBase is not None and _RegBase is not StructHint: # 同一类键上两个 struct 实例:报出事实(每类一次),避免"用一份算、 # 用另一份读"的静默偏移平移(本函数此处即该平移的判定源)。 _Rep: set = getattr(self, '_MemberBaseInstMismatch', None) if _Rep is None: _Rep = set() self._MemberBaseInstMismatch = _Rep if _Cls not in _Rep: _Rep.add(_Cls) _RegN: str = (f"{len(_RegBase.elements)} 元素" if _RegBase.elements is not None else 'opaque') _HintN: str = (f"{len(StructHint.elements)} 元素" if StructHint.elements is not None else 'opaque') _vlog().warning( f"[成员偏移] 类 '{_Cls}' 存在两个 struct 实例:类键上 " f"{_RegBase.name}({_RegN}) vs 取值处 {StructHint.name}({_HintN})" f";偏移基址已按取值处实例计算(两者布局不同会让字段偏移平移)", "MemberBase") if (isinstance(_StructBase, ir.IdentifiedStructType) and _StructBase.elements is not None and len(_StructBase.elements) > 0): _NumElems: int = len(_StructBase.elements) _MembersRaw: list = self.class_members.get(_Cls, []) _NumMembers: int = len(_MembersRaw) # 【重名条目不是布局槽位 ⇒ 长度校准按**去重名数**算(2026-09-30)】 # 与同文件 `_get_member_offset` 的配对段(重名条目跳过、不占槽位)以及 # StructGen 的"重名条目"分支同口径。依据:源码层面的字段名遮蔽已由 # HandlesClassDef 的"字段名遮蔽 fail-fast"(:1083)拦下 ⇒ 成员表里的重名 # 只可能是**产物**(展平残留 / 同一字段被两条收集路径各登记一次), # 不是合法槽位。 # 不这样算的后果(现场):`ast.MatchCase` 表 10 项(9 个字段 + 1 条重复) # 而 body 恰 10 槽 ⇒ `_Delta == 0` ⇒ base 静默取 0 ⇒ 该类所有字段偏移整体 # 前移 1:`MatchCase.children` 表内下标 6 被当作槽 6(`end_col_offset`,i32) # ⇒ `len(mc_c.children)` 分派 `list[AST*].__len__` 时收到 i32: # "Type of #1 arg mismatch: %"…list[…]"* != i32"。 _UniqMembers: set = {n for n, _ in _MembersRaw} if len(_UniqMembers) < _NumMembers: _NumMembers = len(_UniqMembers) _Delta: int = _NumElems - _NumMembers _FirstElem: ir.Type = _StructBase.elements[0] _IsVtablePtr: bool = (isinstance(_FirstElem, ir.PointerType) and isinstance(_FirstElem.pointee, ir.IntType) and _FirstElem.pointee.width == 8) if _Delta == 0: base = 0 elif _Delta == 1: _HasParent: bool = self.class_parent.get(_Cls) is not None base = 1 if (_IsVtablePtr or not _HasParent or _ByInheritVtable) else 0 # ---- 类型一致性校准(治本)---- # 上面的 delta 长度启发式只在"表与布局恰好差 vtable 一项"时成立:一旦成员表 # 比布局多/少**非 vtable** 项(跨模块类展平不全、同名类复用等),base 会被判错, # 结果是**整个类所有字段偏移整体平移**。实测:`ast.Constant` 的 # struct = {i8*(vtable), AST*(parent), MemManager*(pool), i32(lineno), …} # 而查询 `cnst.lineno` 得到的是 pool 那槽(`%MemManager*`)⇒ 调用点报 # "形参 lineno: i32 收到指针",掩盖了真因。 # 校准办法:对 b∈{0,1,2} 逐项比较"成员声明类型 vs elements[b+i] 类型", # 取一致度最高者(完全相同 +2;同为指针但类型不同 +0)。正确对齐时 # 指针槽几乎全中,错位对齐只能靠 i32 撞运气,分数明显更低;平手保留启发式。 # # 【只在长度不齐(_Delta != 0)时校准】_Delta == 0 表示"成员表项数 == body # 槽数":定义端 body 由成员表生成、跨模块端 body(.stub.ll)与 .vpi 同源 # (同一声明顺序),长度相等即**逐位对齐**,base 必为 0。此时再用严格类型 # 比较挑 b 反而会误判——声明侧的联合类型(如 `list[str] | t.CPtr`)可能被记成 # 折叠后的 i8*,而 body 槽仍是具体指针(`%list[str]*`):b=0 丢掉这些匹配, # 某个平移 b 却能靠指针槽互相撞分拿到更高分。 # 现场(2026-09-30):TypeGenMixin(41 成员 / 41 槽 / @t.NoVTable,_Delta=0) # base 被抬到 2 ⇒ `SubTypeGen.Gen = self`(Gen 声明 Generator*、正确槽 0) # 落到槽 40(TypeSpecArena*)⇒ "指针隐式转换已禁止(非基本类型(结构体/类/联合))"。 # _Delta != 0 的既有现场(ast.Constant 的 `cnst.lineno` 落到 pool 槽等)不受影响, # 仍走校准。 _MembersCal: list = self.class_members.get(_Cls, []) if _MembersCal and _NumElems > 0 and _Delta != 0: _BestB: int = base _BestScore: int = -1 _HeurScore: int = -1 _b: int for _b in range(0, 3): _Score: int = 0 _i2: int = 0 for _nm2, _mt2 in _MembersCal: _j2: int = _b + _i2 if _j2 >= _NumElems: break _et2: ir.Type = _StructBase.elements[_j2] if _mt2 is _et2 or str(_mt2) == str(_et2): _Score += 2 _i2 += 1 if _b == base: _HeurScore = _Score if _Score > _BestScore: _BestScore = _Score _BestB = _b # 仅当"明显更优"(至少多对齐 1 个成员)才覆盖启发式结果,避免抖动。 if _HeurScore >= 0 and _BestScore >= _HeurScore + 2: base = _BestB return base def _RenumVariantFieldOwner(self, ClassName: str | None, field_name: str) -> str | None: """REnum **变体字段**的归属类键:主 REnum 不声明该字段时,返回**唯一**声明它的变体键。 判据(逐条可查,不是猜): ① 类短名按 `_IsRenumLayoutClass`(全仓唯一判据源)判为 REnum —— 只有 REnum 才有变体层; ② 变体类键固定为 `<主类短名>_<变体名>`,且成员表**首项恒为 `__tag`** (唯一产源:HandlesClassDef._EmitREnumLlvm:1659-1673 与 HandlesImports 的 REnum 跨模块注册 :1975-2015); ③ 恰有**一个**变体声明该字段(多个变体同名字段 ⇒ 无法判定 ⇒ None,由调用方 照常 fail-fast); ④ 该变体键的 struct 已注册(布局可用)。 用变体键算偏移为什么正确:变体 struct 的 body 是**主 REnum 镜像** `{i32 __tag, }`,而变体成员表 `[__tag] + 载荷` 与主 REnum 槽位一一对应 ⇒ `base + 表内下标` 即真实槽位。 现场(2026-09-30):CallGenDispatch.vp:787 `len_val.Ty.Pointee` —— 'Pointee' 只属 变体键 'LLVMType_Ptr',主类 'LLVMType' 的成员表为空(HandlesClassDef:716)。 """ if not ClassName or not field_name: return None _Short: str | None = self._extract_short_name(ClassName) if not _Short: return None if not self._IsRenumLayoutClass(_Short): return None _Owner: str | None = None _Prefix: str = f"{_Short}_" for _Key, _Members in self.class_members.items(): if not _Key.startswith(_Prefix): continue _List: list = _Members or [] if not _List or _List[0][0] != '__tag': continue if not any(_n == field_name for _n, _ in _List): continue if _Key not in self.structs: continue if _Owner is not None: return None _Owner = _Key return _Owner def _MemberSlotIndex(self, Members: list, FieldName: str) -> int: """字段在成员表里的**槽位下标**(重名条目**不占槽位**;找不到返回 -1)。 与 `_get_member_offset` 的配对路径同口径:重名条目是产物,不是布局槽位 —— 源码层面的字段名遮蔽已由 HandlesClassDef 的"字段名遮蔽 fail-fast"拦下, 其余重名只可能来自"展平残留 / 同一字段被两条收集路径各登记一次"。 所有"按表内下标 + base 求偏移"的站点都必须走本方法,否则含重名条目的表会让 偏移整体偏斜: · `ast.ExceptHandler` 表 10 项、'children' 出现两次 ⇒ 'name' 原始下标 9 ⇒ 偏移 10(越界); · `ast.MatchCase` 表 10 项 / 去重 9(body 10 槽)⇒ `_Delta == 0` ⇒ base 变 0 ⇒ `children` 偏移 6 落到 `end_col_offset`(i32)。 重名条目在**首个同名(正本)之后**才出现,故按"首个同名的去重下标"取值。 """ _Slot: int = 0 _Seen: set = set() for _n, _ in (Members or []): if _n in _Seen: continue _Seen.add(_n) if _n == FieldName: return _Slot _Slot += 1 return -1 def _get_member_offset(self, field_name: str, ClassName: str | None = None, StructHint: Any = None) -> int | None: # base 判定用未归一的原始类名:_member_base 内部自行做同一套归一 # (_NormalizeMemberClass),二者结果逐字一致(避免二次归一在 # "rawptr[X] 展开命中 sha1 前缀 struct 键"这一角落改变取值)。 _MemberBaseKey: str | None = ClassName # 治本修复:同名类 sha1 冲突检测。 # 当 class_sha1_map[ClassName] 的 sha1 与 Gen.structs[ClassName] 的 sha1 不一致时, # class_members[ClassName] 可能存储了错误模块的同名类成员。 # 此处检测冲突,加载正确模块的 class_members 并以 sha1 前缀全名缓存。 _ResolvedClass: str = ClassName if ClassName else '' if ClassName: # 类键归一(短名 + rawptr[X]/t.CPtr[X] 展开)统一走 _NormalizeMemberClass, # 与 _member_base 的 base 判定共用同一归一,禁止各写一份。 ClassName = self._NormalizeMemberClass(ClassName) _ResolvedClass = ClassName _ClassSha1Map: dict[str, str] = getattr(self, 'class_sha1_map', {}) _ExpectedSha1: str | None = _ClassSha1Map.get(ClassName) if _ExpectedSha1: _ExistingStruct = self.structs.get(ClassName) if isinstance(_ExistingStruct, ir.IdentifiedStructType): _ExistingSha1: str = self._extract_struct_sha1(_ExistingStruct) if _ExistingSha1 and _ExistingSha1 != _ExpectedSha1: # sha1 冲突:使用全名查找 class_members _FullKey: str = f"{_ExpectedSha1}.{ClassName}" if _FullKey in self.class_members and self.class_members[_FullKey]: _ResolvedClass = _FullKey else: # 从正确 stub 加载 class_members if self._Trans and hasattr(self._Trans, 'ImportHandler'): _StubName: str = f"{_ExpectedSha1}.stub.ll" # 临时保存旧值,清除短名条目以绕过 guard,加载后恢复 _OldMembers = self.class_members.get(ClassName) self.class_members[ClassName] = [] try: self._Trans.ImportHandler._TryLoadClassMembersFromPyi(ClassName, _StubName, self) except Exception: pass _NewMembers = self.class_members.get(ClassName) if _NewMembers: self.class_members[_FullKey] = _NewMembers _ResolvedClass = _FullKey # 恢复旧值(避免影响其他代码对短名的依赖) if _OldMembers: self.class_members[ClassName] = _OldMembers else: self.class_members.pop(ClassName, None) # base(vtable 占位槽数):唯一真源 = _member_base(规则与文档见该方法)。 # 本函数在同一次调用内还会在下方多处重载成员表(懒加载 425-436、模块重载 # 482-508、字段预注册 558-578、force 重建 587-643),每次都可能改写 # _member_base 的三个输入(class_parent 父链、structs 布局、class_members # 长度)。因此 base 与"即将返回的下标 i"必须取自**同一份最新已加载状态**: # 所有 return 一律用 _BaseNow() 现算,禁止缓存早先算出的 base——缓存会让 # 偏移整体差 1(ast.ExceptHandler.children 落到 end_col_offset(i32), # len 分派拿到 i32)。 def _BaseNow() -> int: return self._member_base(_MemberBaseKey, StructHint) # 预热(与原行为逐字一致):先触发一次 EnsureClassMembersFlattened, # 再决定下面的懒加载。 _BaseNow() if ClassName and ClassName not in self.class_members and self._Trans and hasattr(self._Trans, 'ImportHandler'): # 跨模块 class_members 懒加载:struct 已从 stub 加载但 class_members # 可能尚未从 .vpi 填充(vpi/stub 就绪时序不一致),主动按类 sha1 加载。 _Sha1sForLoad: dict[str, str] = getattr(self, 'class_sha1_map', {}) for _mod_name_lz, _mod_sha1_lz in _Sha1sForLoad.items(): try: self._Trans.ImportHandler._TryLoadClassMembersFromPyi( ClassName, f"{_mod_sha1_lz}.stub.ll", self) except Exception: continue if ClassName in self.class_members and len(self.class_members[ClassName]) > 0: break # 依赖补齐(上面的懒加载)后再确认一次展平(幂等);偏移基址由 _BaseNow() # 在真正 return 处现算,不在此缓存。 _BaseNow() # ---- REnum 变体字段:主 REnum 不声明该字段时,改用它**唯一**的变体键继续 ---- # 现场(2026-09-30):CallGenDispatch.vp:787 `len_val.Ty.Pointee` —— 'Pointee' 属 # LLVMType 的 REnum 变体键 'LLVMType_Ptr',主类 'LLVMType' 成员表为空 ⇒ 旧路径 # 直接 fast-fail "属性 'Pointee' 在类 'LLVMType' 中找不到" 终止整个编译。 # 判据与槽位正确性见 _RenumVariantFieldOwner(变体 struct body = 主 REnum 镜像)。 if field_name and ClassName: _MainMembers: list = (self.class_members.get(_ResolvedClass) or self.class_members.get(ClassName) or []) if not any(_n == field_name for _n, _ in _MainMembers): _VariantOwner: str | None = None try: _VariantOwner = self._RenumVariantFieldOwner(ClassName, field_name) except Exception: _VariantOwner = None if _VariantOwner: _RepRK: set = getattr(self, '_RenumVariantFieldReported', None) if _RepRK is None: _RepRK = set() self._RenumVariantFieldReported = _RepRK _RKey: tuple = (_VariantOwner, field_name) if _RKey not in _RepRK: _RepRK.add(_RKey) _vlog().warning( f"[REnum 变体字段] '{field_name}' 不在主 REnum 类 " f"{_ResolvedClass!r} 中,按唯一变体类 '{_VariantOwner}' 取偏移" f"(变体 struct body = 主 REnum 镜像,槽位一一对应)", "MemberBase") ClassName = _VariantOwner _ResolvedClass = _VariantOwner _MemberBaseKey = _VariantOwner _BaseNow() # ---- 布局真源优先(配对式偏移):偏移只认"运行时真正被 GEP 的那份 struct body" ---- # class_members 是**可变**表(继承展平会在父类就绪后前置前缀、跨模块可按需重载), # 而 struct body 一旦 set_body 即**永久冻结** ⇒ "base + 成员表下标"会随表漂移 # 整体平移(现场:Converter 模块读 `Alias.name` 取到槽 9(= asname), # `import t` 无 as 子句 ⇒ asname=NULL ⇒ vpi 生成误报"import 别名 name 为空"; # 同族旧现场:ast.Constant.lineno 取到 pool 槽)。改为两条"只看布局"的路径: # ① 建 body 时冻结下来的"成员名 → 槽位"映射(StructGen._generate_structs 挂载); # ② 用**类型**把成员表逐项配对到 elements:类型不匹配的条目视为方法/占位/漂移项, # 不占槽位;配对中类型相等的那个槽位即该字段的真实槽位。 # 表与布局自洽时两条路径给出与旧下标法**逐字相同**的结果(body 本按该表建); # 都覆盖不到时落回旧下标法(对既有正确行为零变化)。 if field_name: _OffStruct: Any = self.structs.get(ClassName) if isinstance(StructHint, ir.IdentifiedStructType) and ClassName: # 与 _member_base 同口径:只接受**同类**的 hint(接收者静态类型可能是基类, # 实际指向派生类实例,其布局不属于本类)。 if self._extract_short_name(getattr(StructHint, 'name', '') or '') == ClassName: _OffStruct = StructHint if isinstance(_OffStruct, ir.IdentifiedStructType): _OffEls: Any = _OffStruct.elements _Frozen: Any = getattr(_OffStruct, '_tpc_member_slots', None) # 冻结表**有效性**判定:表挂在 struct 实例上,而 body 会被"不完整即清除" # (StructGen 预检 `existing_st.elements = None`)与 stub 加载 # (HandlesImports._TryLoadStructFromStub 的 set_body)**清空后重设**, # 重设路径不更新本表 ⇒ 陈旧表把字段指到别的槽。现场:AnnAssign 在 # "自有 4 字段 + vtable"(5 槽:i8*,AST*,AST*,AST*,i32)时冻结 # {'value': 3},随后 body 被重设为 12 槽正确布局(value 在 10), # 查询仍读槽 3(lineno, i32)⇒ `.kind()` 虚分派 GEP 的操作数是 i32。 # 判据 = 建表时记下的 **body 对象身份**(set_body 每次赋新 tuple): # 与当前 elements 非同一对象即视为失效,落到按布局求槽的路径。 _FrozenFresh: bool = (isinstance(_Frozen, dict) and getattr(_OffStruct, '_tpc_member_body', None) is _OffEls) if _FrozenFresh and field_name in _Frozen: _FrozenSlot: Any = _Frozen[field_name] if isinstance(_FrozenSlot, int) and (not _OffEls or 0 <= _FrozenSlot < len(_OffEls)): return _FrozenSlot _OffMembers: list = self.class_members.get(_ResolvedClass) or [] _BitFields: Any = getattr(self, 'class_member_bitfields', None) if _OffEls and _OffMembers and not (_BitFields and _BitFields.get(ClassName)): # ---- 布局真源之二:两种对齐候选按"逐项类型可写入"**打分**取高者 ---- # 成员表可能是①展平表(含父类前缀)②仅自有字段(父类前缀未补齐 ⇒ 真身是 # body **尾部**)③残缺前缀(如仅 ['Gen'] 一项 ⇒ 真身在 body **头部**)。 # 只用"长度关系/父类槽数"来断言①③是猜,已被两轮现场打脸 # (Generator.vp:136 TypeGenMixin 被锚到槽 40;:164 TypeGenErr 因恰好 # 只有 1 个自有字段、`body-len(表) == 父类槽数` 巧合成立而被锚到槽 41)。 # 改为**比较**两个候选锚(前缀锚 `_BaseNow()` vs 尾部锚 `len(body)-len(表)`) # 的逐项得分(判据与建 body/存储侧同一套 `_MemberSlotAccepts`): # 只有尾部锚**严格更高分**才采用它,同分/更低一律保持原行为 # (配对 → 下标法),因此对既有正确布局零风险。 _ElemN: int = len(_OffEls) _AnchorA: int = _BaseNow() _AnchorB: int = _ElemN - len(_OffMembers) _ScoreA: int = 0 _aA: int = 0 while _aA < len(_OffMembers): if _AnchorA + _aA >= _ElemN: break _nA, _tA = _OffMembers[_aA] if self._MemberSlotAccepts(_tA, _OffEls[_AnchorA + _aA]): _ScoreA += 1 _aA += 1 _ScoreB: int = 0 if _AnchorB >= 0 and _AnchorB != _AnchorA and not self._IsRenumLayoutClass(_ResolvedClass): _aB: int = 0 while _aB < len(_OffMembers): if _AnchorB + _aB >= _ElemN: break _nB, _tB = _OffMembers[_aB] if self._MemberSlotAccepts(_tB, _OffEls[_AnchorB + _aB]): _ScoreB += 1 _aB += 1 if _ScoreB > _ScoreA and _ScoreB > 0: _TIdx: int = -1 for _i5, (_n5, _) in enumerate(_OffMembers): if _n5 == field_name: _TIdx = _i5 break if _TIdx >= 0: # 每类只留一次痕(大类的每个字段都会命中本路径) _TailKey: str = _ResolvedClass _TailRep: set = getattr(self, '_MemberTailAlignedReported', None) if _TailRep is None: _TailRep = set() self._MemberTailAlignedReported = _TailRep if _TailKey not in _TailRep: _TailRep.add(_TailKey) _vlog().warning( f"[成员偏移] 类 '{_ResolvedClass}' 的成员表({len(_OffMembers)} 项)" f"在 body({_ElemN} 槽)上按**尾部对齐**得分更高" f"(尾部锚 {_AnchorB} 得 {_ScoreB} vs 前缀锚 {_AnchorA} 得 {_ScoreA})" f"⇒ 判定该表缺的是**父类前缀**:字段 '{field_name}' = " f"{_AnchorB + _TIdx}", "MemberBase") return _AnchorB + _TIdx _Cursor: int = _BaseNow() _PairedNames: set = set() for _nmP, _mtP in _OffMembers: if _Cursor >= _ElemN: break # 重名条目不是布局槽位(表构造/重载产生的重复项)⇒ 不占槽位 if _nmP in _PairedNames: continue # None 成员在 body 里被物化为 i32(见 StructGen 建 body 分支), # 配对类型必须同口径,否则该类字段会被整条跳过。 _mtEff: Any = _mtP if _mtP is not None else ir.IntType(32) _etP: Any = _OffEls[_Cursor] _PairOk: bool = (_mtEff is _etP or str(_mtEff) == str(_etP)) if (not _PairOk and isinstance(_mtEff, ir.PointerType) and isinstance(_etP, ir.PointerType)): # 联合类型字段在成员表里存成通用指针(如 AST.children = # `list[rawptr[AST]] | t.CPtr` ⇒ i8*),而 body 槽是具体指针 # (`%"...list[...]"*`):严格串比较会在这条**断链**,其后的 # 所有条目都不再消费槽位(cursor 不动)⇒ 目标字段全部退化成 # "base + 表内下标"(现场:ExceptHandler.name 表内下标 9 ⇒ # 偏移 10,越界 fail-fast,而正确槽是 9)。判据与存储/建 body # 侧同一套(_MemberSlotAccepts:两侧都是指针时按指针转换规则, # i8* 通用指针放行);标量仍走严格串比较,不引入新的过配对。 _PairOk = self._MemberSlotAccepts(_mtEff, _etP) if _PairOk: _PairedNames.add(_nmP) if _nmP == field_name: # 与旧下标法不一致时留痕:这正是"成员表与 struct 布局 # 不自洽"的现场事实(每类每字段一次,便于定位漂移来源)。 _OldIdx: int = -1 for _i3, (_n3, _) in enumerate(_OffMembers): if _n3 == field_name: _OldIdx = _i3 break if _OldIdx >= 0 and _BaseNow() + _OldIdx != _Cursor: _RepKey: tuple = (_ResolvedClass, field_name) _RepSet: set = getattr(self, '_MemberSlotDriftReported', None) if _RepSet is None: _RepSet = set() self._MemberSlotDriftReported = _RepSet if _RepKey not in _RepSet: _RepSet.add(_RepKey) _vlog().warning( f"[成员偏移] 类 '{_ResolvedClass}' 的成员表与 struct " f"布局不自洽:字段 '{field_name}' 下标法={_BaseNow() + _OldIdx}" f"(成员表 {len(_OffMembers)} 项) vs 布局配对比={_Cursor}" f"(struct {_ElemN} 槽)——已采用布局配对结果", "MemberBase") return _Cursor _Cursor += 1 if _ResolvedClass and _ResolvedClass in self.class_members: # 只有走到这里(布局配对未命中)才做一次重名检查:重名条目**不占布局槽**, # 却会让 `base + 表内下标` 整体后移(现场:ExceptHandler 表 10 项、其中 # 'children' 出现两次 ⇒ 'name' 下标 9 ⇒ 偏移 10 越界)。此处把事实报出来 # (每类一次),并标注该返回值取自回退路径、可能失准。 _MemList: list = self.class_members[_ResolvedClass] _SeenNm: set = set() _DupNm: list[str] = [] for _nmC, _ in _MemList: if _nmC in _SeenNm: if _nmC not in _DupNm: _DupNm.append(_nmC) else: _SeenNm.add(_nmC) if _DupNm: _DupRep: set = getattr(self, '_MemberDupNamesReported', None) if _DupRep is None: _DupRep = set() self._MemberDupNamesReported = _DupRep if _ResolvedClass not in _DupRep: _DupRep.add(_ResolvedClass) _vlog().warning( f"[成员偏移] 类 '{_ResolvedClass}' 的成员表含**重名条目** " f"{_DupNm!r}(表 {len(_MemList)} 项 / 去重 {len(_SeenNm)} 项)——" f"重名项不是布局槽位,`base + 表内下标` 会因此整体后移;" f"本次字段 '{field_name}' 走的是回退路径(未被 body 配对命中)," f"返回值可能失准。重名来源通常是「同一字段被两条收集路径各登记一次" f"(类内 AnnAssign 声明 + 方法体 self.x 赋值)」或继承前缀重复前置。", "MemberBase") # 回退取值也按"重名条目**不占槽位**"算(与上方配对路径同口径):直接取 # `base + 原始下标` 会把重名条目也算进槽位,整体后移(现场:ExceptHandler # 表 10 项、'children' 出现两次 ⇒ 'name' 原始下标 9 ⇒ 偏移 10 越界)。 _SlotIdx: int = 0 _Counted: set = set() for name, _ in _MemList: if name in _Counted: continue _Counted.add(name) if name == field_name: return _BaseNow() + _SlotIdx _SlotIdx += 1 # 继承链回退:当字段不在当前类的 class_members 中时(常见于跨模块导入 # 父类未加载,字段被填充为 __inherited_N 占位名),沿 class_parent 链向上查找。 # 子类 struct 布局为 [vtable?] + [基类字段...] + [子类字段...], # 基类字段在子类 struct 中的起始偏移即为 base(子类), # 因此祖先 class_members 中的索引 i 对应子类 struct 中的偏移 base + i。 if ClassName and field_name: _parent: str | None = self.class_parent.get(ClassName) _visited: set = {ClassName} if ClassName else set() _cls_short: str = ClassName.split('.')[-1] if ClassName else '' while _parent and _parent not in _visited: _visited.add(_parent) # 父类候选键:class_parent 只登记**短名**(含泛型基类时是模板名, # 如 'GSListNode'),而 class_members 里真实键可能是**实例化名** # ('GSListNode[GlobalVariable]',见 includes/llvmlite/__module.vp # `class GlobalVariable(GSListNode[GlobalVariable])`)或模板名。 # 少这一步 ⇒ 泛型基类字段(Next/Prev 等)沿链永远查不到 → fast-fail # ("属性 'Next' 在类 'GlobalVariable' 中找不到")。 _p_tmpl: str = _parent.split('[')[0] _cand_keys: list = [] for _k in (_parent, _p_tmpl, f"{_p_tmpl}[{_cls_short}]" if _cls_short else _p_tmpl): if _k and _k not in _cand_keys: _cand_keys.append(_k) # 跳过 __inherited_N 占位条目,只在真实字段名中查找 _hit_idx: int = -1 for _k in _cand_keys: if _k in self.class_members: _members: list = self.class_members[_k] _has_placeholder: bool = any(n.startswith('__inherited_') for n, _ in _members) if not _has_placeholder: _hit_idx = self._MemberSlotIndex(_members, field_name) if _hit_idx >= 0: break if _hit_idx >= 0: # 基类字段位于子类 struct 的 base 起始处,索引 i 即基类内偏移 return _BaseNow() + _hit_idx # 父类 class_members 未加载或含占位条目,尝试从 stub 加载真实字段 if self._Trans and hasattr(self._Trans, 'ImportHandler'): sha1_map: dict[str, str] = getattr(self, 'ModuleSha1Map', {}) for _k in _cand_keys: for mod_name, mod_sha1 in sha1_map.items(): stub_name: str = f"{mod_sha1}.stub.ll" self._Trans.ImportHandler._TryLoadClassMembersFromPyi(_k, stub_name, self) if _k in self.class_members and not any(n.startswith('__inherited_') for n, _ in self.class_members[_k]): _pIdx: int = self._MemberSlotIndex(self.class_members[_k], field_name) if _pIdx >= 0: return _BaseNow() + _pIdx break if _k in self.class_members and any(n == field_name for n, _ in self.class_members.get(_k, [])): break _parent = self.class_parent.get(_parent) short_name: str | None = ClassName.split('.')[-1] if ClassName and '.' in ClassName else None if short_name and short_name in self.class_members: _sIdx: int = self._MemberSlotIndex(self.class_members[short_name], field_name) if _sIdx >= 0: return _BaseNow() + _sIdx if ClassName and field_name: if ClassName not in self.class_members or field_name not in [n for n, _ in self.class_members.get(ClassName, [])]: if self._Trans and hasattr(self._Trans, 'ImportHandler'): sha1_map: dict[str, str] = getattr(self, 'ModuleSha1Map', {}) for mod_name, mod_sha1 in sha1_map.items(): stub_name: str = f"{mod_sha1}.stub.ll" self._Trans.ImportHandler._TryLoadClassMembersFromPyi(ClassName, stub_name, self) if ClassName in self.class_members and len(self.class_members[ClassName]) > 0: _rIdx: int = self._MemberSlotIndex(self.class_members[ClassName], field_name) if _rIdx >= 0: return _BaseNow() + _rIdx break if short_name: for mod_name, mod_sha1 in sha1_map.items(): stub_name: str = f"{mod_sha1}.stub.ll" self._Trans.ImportHandler._TryLoadClassMembersFromPyi(short_name, stub_name, self) if short_name in self.class_members and len(self.class_members[short_name]) > 0: _rIdx2: int = self._MemberSlotIndex(self.class_members[short_name], field_name) if _rIdx2 >= 0: return _BaseNow() + _rIdx2 break if ClassName and field_name: for cn_key in self.class_members: if cn_key == ClassName or cn_key.endswith(f'.{ClassName}') or (short_name and (cn_key == short_name or cn_key.endswith(f'.{short_name}'))): _cIdx: int = self._MemberSlotIndex(self.class_members[cn_key], field_name) if _cIdx >= 0: return _BaseNow() + _cIdx # REnum 变体搜索: ClassName 是 REnum(如 MetaType),成员注册在变体结构 # (MetaType_Var, MetaType_Concrete, MetaType_App)中。变体与 REnum 主结构 # 共享 max_variant_struct 布局,偏移一致(__tag 在偏移 0,字段从偏移 1 开始) if ClassName and field_name: variant_prefix: str = f"{ClassName}_" for cn_key in self.class_members: if cn_key.startswith(variant_prefix): _vIdx: int = self._MemberSlotIndex(self.class_members[cn_key], field_name) if _vIdx >= 0: return _vIdx if short_name: variant_prefix = f"{short_name}_" for cn_key in self.class_members: if cn_key.startswith(variant_prefix): _vIdx2: int = self._MemberSlotIndex(self.class_members[cn_key], field_name) if _vIdx2 >= 0: return _vIdx2 if ClassName and ClassName in self.structs: struct_type: ir.IdentifiedStructType = self.structs[ClassName] if isinstance(struct_type, ir.IdentifiedStructType) and struct_type.elements is not None and len(struct_type.elements) > 0: for cn_key, members in self.class_members.items(): if cn_key == short_name or (short_name and cn_key.endswith(f'.{short_name}')): _kIdx: int = self._MemberSlotIndex(members, field_name) if 0 <= _kIdx < len(struct_type.elements): return _kIdx break else: if short_name: for cn_key, members in self.class_members.items(): sn: str = cn_key.split('.')[-1] if '.' in cn_key else cn_key if sn == short_name: _kIdx2: int = self._MemberSlotIndex(members, field_name) if 0 <= _kIdx2 < len(struct_type.elements): return _kIdx2 break if ClassName and field_name and ClassName not in self.class_members: if self._Trans and hasattr(self._Trans, 'ImportHandler'): for temp_dir_candidate in [getattr(self, '_temp_dir', None)]: if temp_dir_candidate and os.path.isdir(temp_dir_candidate): for pyi_file in os.listdir(temp_dir_candidate): if pyi_file.endswith('.vpi') or pyi_file.endswith('.pyi'): stub_name: str = pyi_file.replace('.vpi', '.stub.ll').replace('.pyi', '.stub.ll') short_cn: str = short_name if short_name else ClassName self._Trans.ImportHandler._TryLoadClassMembersFromPyi(short_cn, stub_name, self) if short_cn in self.class_members and len(self.class_members[short_cn]) > 0: _scIdx: int = self._MemberSlotIndex(self.class_members[short_cn], field_name) if _scIdx >= 0: return _BaseNow() + _scIdx break if ClassName and ClassName in self.structs and (ClassName not in self.class_members or len(self.class_members.get(ClassName, [])) == 0): struct_type: ir.IdentifiedStructType = self.structs[ClassName] if isinstance(struct_type, ir.IdentifiedStructType) and struct_type.elements is not None: if self._Trans and hasattr(self._Trans, 'ClassHandler'): class_handler: Any = self._Trans.ClassHandler if self._current_tree: for node in ast.iter_child_nodes(self._current_tree): if isinstance(node, ast.ClassDef) and (node.name == ClassName or node.name == short_name): if node.name not in self.class_members: self.class_members[node.name] = [] if len(self.class_members[node.name]) == 0: class_handler._PreRegisterClassMembers(node, self) if ClassName in self.class_members: _prIdx: int = self._MemberSlotIndex(self.class_members[ClassName], field_name) if _prIdx >= 0: return _BaseNow() + _prIdx if short_name and short_name in self.class_members: _prIdx2: int = self._MemberSlotIndex(self.class_members[short_name], field_name) if _prIdx2 >= 0: return _BaseNow() + _prIdx2 break # 最后回退:当继承链回退失败但 struct body 已加载时, # 从 struct elements 数和 class_members 数推断继承字段偏移。 # 继承字段在 struct 开头(base 之后),此 fallback 仅适用于单继承字段的情况 # (如 GSListNode[T] 的 Next 字段)。子类 struct 布局: # [vtable?] + [基类字段...] + [子类字段...] # 当 class_members 只含子类自身字段、基类 class_members 未加载时, # 继承字段数 = struct_elements - class_members_count, # 若该值 == 1,则所查字段(如 Next)的偏移 = base + 0。 if ClassName and field_name: _st_final = self.structs.get(ClassName) if isinstance(_st_final, ir.IdentifiedStructType) and _st_final.elements is not None: _num_elements_final: int = len(_st_final.elements) _members_final: list = self.class_members.get(ClassName, []) # 与 _member_base 的长度校准同口径:重名条目**不是布局槽位** ⇒ 按去重名数算 _num_members_final: int = len({_Rn for _Rn, _ in _members_final}) if _num_elements_final > _num_members_final: # base 是 vtable 占位(struct[0] = i8*),不是继承字段,必须扣除: # 不扣 base 时,凡"struct 元素数恰好多 1"的类,差值其实只是 vtable # 本身(class_members 不含 vtable),却会对任意字段名返回 base+0。 # 实例:ast.ImportFrom 的 level/module → 静默返回偏移 1(=parent, # %"…AST"*)→ 调用点把 AST* 当 i32 传入 _resolve_relative_module # → llvmlite "Type of #4 arg mismatch: i32 != %"…AST"*"。 # 成员表与 struct 不一致(少一个非 vtable 槽)时,先强制按定义模块 # 的 .vpi 重载一次成员表再按名查:_TryLoadClassMembersFromPyi 的 guard # 会永久跳过"已存在但不完整"的表,此处即该冻结的解除点。重载后仍缺 # 才走下方推断/fast-fail。每类只重载一次(避免递归与重复 IO)。 _ReloadKey: tuple = (id(self), ClassName) _ReloadTried: set = getattr(self, '_MemberReloadTried', None) if _ReloadTried is None: _ReloadTried = set() self._MemberReloadTried = _ReloadTried if _ReloadKey not in _ReloadTried: _ReloadTried.add(_ReloadKey) _Sha1Final: str | None = getattr(self, 'class_sha1_map', {}).get(ClassName) if _Sha1Final and self._Trans and hasattr(self._Trans, 'ImportHandler'): _OldMembers = _members_final try: self._Trans.ImportHandler._TryLoadClassMembersFromPyi( ClassName, f"{_Sha1Final}.stub.ll", self, force=True) except Exception: pass _ReloadedMembers: list = self.class_members.get(ClassName, []) if len(_ReloadedMembers) < len(_OldMembers): # 重载反而变少(.vpi 缺该类):回滚,避免丢字段 self.class_members[ClassName] = _OldMembers _ReloadedMembers = _OldMembers _members_final = _ReloadedMembers _num_members_final = len({_Rn for _Rn, _ in _members_final}) _rfIdx: int = self._MemberSlotIndex(_members_final, field_name) if _rfIdx >= 0: return _BaseNow() + _rfIdx _num_inherited_final: int = _num_elements_final - _num_members_final - _BaseNow() if _num_inherited_final == 1: # 推断(缺失槽位 = 位于 base+0 的未展平继承字段)仅在成员表 # 尚未含展平继承字段时成立:若成员表首项本身就是任一已加载 # 祖先的字段名(如 ImportFrom 'parent' ← Stmt),说明继承字段 # 已展平、缺的是类自身字段,推断 base+0 必然读错槽位。 _ParentNamesFinal: set = set() for _ParentF in (getattr(self, 'class_parents', {}).get(ClassName) or []): for _Np, _Tp in self.class_members.get(_ParentF, []) or []: _ParentNamesFinal.add(_Np) if _members_final and _members_final[0][0] in _ParentNamesFinal: pass # 已含展平继承字段 → 落入下方 fast-fail 暴露真实缺失 else: # 单继承字段,偏移 = base(vtable 之后第一个位置) return _BaseNow() + 0 # fast-fail(治本修复 2026-08-10):字段未找到不再静默返回 None。 # 静默 None 会让调用方(_gep_and_Load_member L26-27)直接 return None, # AnnAssign InitValue 变 None → 变量 alloca 未初始化 → 运行期读垃圾指针 # (如 HandlesImports.HandleImport 的 imp.names → 0xffffffffffffffff 崩溃)。 # 抛出编译错误,暴露 class_members/struct 的真实状态便于定位根因。 _members_diag: list = self.class_members.get(ClassName, []) if ClassName else [] _struct_diag: str = 'not-registered' if ClassName and ClassName in self.structs: _st_diag = self.structs[ClassName] if isinstance(_st_diag, ir.IdentifiedStructType): _struct_diag = f"{len(_st_diag.elements)} elems" if _st_diag.elements is not None else 'opaque' _member_names_diag: list = [n for n, _ in _members_diag][:16] # ---- 取证(只读,不改变行为,2026-09-30)---- # 现场:CallGenDispatch.vp:787 `len_val.Ty.Pointee` 里 `.Pointee` 被判成在类 # 'ParamNode'(2 字段 Next/Ty)里找 —— 而 'Pointee' 本属 LLVMType 的 REnum # 变体镜像字段。要一次定位"是谁把接收者记成了 ParamNode",报文需给出: # ① 本类键 → struct 实例 IR 名(键/实例名不一致即"同名两份实例"); # ② **真正声明了该字段**的类键(唯一者即"本该用的类"); # ③ var_struct_class 里被登记为**本类**的变量(封顶 6 个)及其实际 pointee # IR 名 —— 两者不一致即"记录类名与实际 IR 类型不符",直接圈定记录点。 _extra_diag: str = '' try: _st_key = self.structs.get(ClassName) if ClassName else None if isinstance(_st_key, ir.IdentifiedStructType): _extra_diag += (f"\n 本类键 '{ClassName}' → struct 实例 IR 名 " f"'{getattr(_st_key, 'name', '?')}'") _owners: list = [] for _ck2, _cm2 in self.class_members.items(): if _ck2 == ClassName: continue if any(_n2 == field_name for _n2, _ in (_cm2 or [])): _owners.append(_ck2) _extra_diag += (f"\n 声明了 '{field_name}' 的其它类键({len(_owners)} 个): " f"{_owners[:8]}{' …' if len(_owners) > 8 else ''}") _vsc: dict = getattr(self, 'var_struct_class', {}) or {} _vars_here: list = [v for v, cn in _vsc.items() if cn == ClassName] if _vars_here: _vdesc: list = [] for _v2 in _vars_here[:6]: _pointee_desc: str = '?' for _src3 in (getattr(self, 'variables', {}) or {}, getattr(self, '_reg_values', {}) or {}, getattr(self, '_direct_values', {}) or {}): _val3 = _src3.get(_v2) if _val3 is not None and isinstance(getattr(_val3, 'type', None), ir.PointerType): _pointee_desc = str(_val3.type.pointee) break _vdesc.append(f"{_v2}(pointee={_pointee_desc})") _extra_diag += (f"\n var_struct_class 登记为 '{ClassName}' 的变量(" f"{len(_vars_here)} 个,示前 6): {_vdesc}") except Exception as _e_diag: _extra_diag += f"\n (取证附加信息生成失败: {type(_e_diag).__name__}: {_e_diag})" # 异常类型:MemberLookupFailFast(KeyError 子类 + 裸消息 __str__)—— # 既保留 `except KeyError` 兜底点,又让报文不再被 repr 成"带引号 + \n 转义" # 的不可读形式(原有 traceback.print_stack 调试转储一并移除,保持输出干净)。 raise MemberLookupFailFast( f"[fast-fail] 属性 '{field_name}' 在类 '{ClassName}' 中找不到" f"(class_members={len(_members_diag)} 个: {_member_names_diag}, struct={_struct_diag})" f"{_extra_diag}" ) def _resolve_class_for_var(self, var_name: str) -> str | None: for ClassName in self.class_methods: if var_name == ClassName or var_name.startswith(f"__with_{ClassName}_"): return ClassName if var_name in self.variables: var: ir.Value = self.variables[var_name] if isinstance(var.type, ir.PointerType): for ClassName, struct_type in self.structs.items(): if var.type.pointee == struct_type: return ClassName if isinstance(var.type.pointee, ir.PointerType) and var.type.pointee.pointee == struct_type: return ClassName return None def _infer_return_type(self, FuncName: str) -> ir.Type: if FuncName in self.functions: return self.functions[FuncName].type.pointee.return_type # 也尝试混淆后的名称 mangled: str = self._mangle_func_name(FuncName) if mangled != FuncName and mangled in self.functions: return self.functions[mangled].type.pointee.return_type if FuncName in self.known_return_types: return self.known_return_types[FuncName] if mangled != FuncName and mangled in self.known_return_types: return self.known_return_types[mangled] for ClassName, methods in self.class_methods.items(): for method in methods: if FuncName == method: return ir.VoidType() if self._import_handler_ref: try: # 先用原始名称查找,再用混淆名称查找 stub_func_type: Any = self._import_handler_ref._LookupStubFuncType(FuncName, self) if stub_func_type is None and mangled != FuncName: stub_func_type = self._import_handler_ref._LookupStubFuncType(mangled, self) if stub_func_type and hasattr(stub_func_type, 'return_type'): return stub_func_type.return_type except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"查找桩函数返回类型失败: {_e}", "Exception") return ir.IntType(32) def _get_or_declare_func(self, name: str, func_type: ir.FunctionType) -> ir.Function: for fname, fobj in self.functions.items(): if fname == name: return fobj if re.match(r'^[0-9a-f]{16,}\.' + re.escape(name) + r'$', fname): return fobj # module.globals 里可能已有同名函数而 Gen.functions 未登记(如 # BaseGen._get_or_declare_function 的历史路径):直接新建必抛 llvmlite # DuplicatedNameError(NameScope 按名去重)⇒ 统一走幂等入口。 return self._CreateOrReuseFuncDecl(name, func_type) def get_or_declare_c_func(self, name: str, fallback_type: ir.FunctionType | None = None) -> ir.Function | None: if name in self.functions: return self.functions[name] for fname, fobj in self.functions.items(): if re.match(r'^[0-9a-f]{16,}\.' + re.escape(name) + r'$', fname): return fobj stub_func_type: ir.FunctionType | None = None if self._import_handler_ref: try: stub_func_type = self._import_handler_ref._LookupStubFuncType(name, self) except Exception as _e: if _config_mode == "strict": raise _vlog().warning(f"查找桩函数类型失败: {_e}", "Exception") if stub_func_type: return self._CreateOrReuseFuncDecl(name, stub_func_type) if fallback_type is not None: return self._CreateOrReuseFuncDecl(name, fallback_type) return None