Files
TransPyC/lib/core/LLVMCG/FuncGen.py
T
TermiNexus 2514cee4c9 feat(lang): 同步 cif/celif/celse、值引用注解与 assert 改译 testcheck.check
vast 双路径(Python Parser/ExprParser 与 C 扩展 vast_c.c)新增 cif-celif-celse 语句族与 &T / &temp T 参数注解解析,_parser.pyd 已同步重编。HandlesIf 改认 @CIf 标记并做编译期常量求值,含运行时变量一律 fail-fast,同时删除 c.CIf 家族(c.py)。HandlesAssert 合成 testcheck.check(cond, None, msg) 调用,非字符串字面量消息报 TypeError。FuncGen 调用点按形参模式自动取址(ref 仅接受左值,右值 fail-fast;temp 物化临时栈),SymbolUtils/TypeAnnotationResolver 提供模式识别;t.py 删除 CNeedPtr。
2026-10-07 19:03:29 +08:00

1240 lines
84 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.
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, <max_variant_payload>}`,而变体成员表 `[__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