fix(gen): TPC 治本——struct opaque 具化、多返回值 fail-fast、with MRO 修复

1. HandlesExprCall: vtable 分派前 _EnsureReceiverStructConcrete 主动具化 struct
   body(stub + class_members 双路径),消除元数据已注册、本体仍 opaque 的
   撕裂状态,失败即 fail-fast 暴露上游 stub 加载时序问题;
2. HandlesImports: stub 加载后仍 opaque 时不保留成功缓存(可重试),
   避免 opaque 状态被固化导致 vtable 分派 GEP 崩溃;
3. HandlesAssign: 跨模块多返回值 CReturnTypes 恢复(LiteralStructType 回退)
   + 解析失败 fail-fast,不再静默跳过致目标变量未初始化触发堆损坏;
4. HandlesWith: MRO 继承方法解析跳过无函数体的 stub declare(Phase1 继承
   包装声明),bitcast 目标改用函数签名参数类型避免 opaque 占位对象。
This commit is contained in:
TermiNexus committed 2026-08-13 07:51:27 +08:00
1 parent a99f126cfa
commit 2f0b741d55
4 files changed
+132 -10

No files matched your search

+34 -1
View File
@@ -533,7 +533,28 @@ class AssignHandle(BaseHandle):
ret_type: ir.Type = func_ft.return_type
if isinstance(ret_type, ir.LiteralStructType):
CReturnTypes = [None] * len(ret_type.elements)
if not CReturnTypes:
# 跨模块多返回值兜底:短名未注册时(函数以 ModulePath.名 /
# sha1.名 全名注册),从其 LiteralStructType 返回类型恢复 CReturnTypes。
# 否则 CReturnTypes 为空会走下方静默 return,目标变量未初始化,
# 运行期 free(垃圾指针) 触发堆损坏(HashLibTest 0xC0000374)。
func = None
if ModulePath:
func = Gen.functions.get(f"{ModulePath}.{FuncName}")
if func is None:
for key in Gen.functions:
if key.endswith(f".{FuncName}"):
func = Gen.functions[key]
break
if func:
func_ft: ir.FunctionType | None = getattr(func, 'ftype', None)
if func_ft:
ret_type: ir.Type = func_ft.return_type
if isinstance(ret_type, ir.LiteralStructType):
CReturnTypes = [None] * len(ret_type.elements)
if CReturnTypes and len(Target.elts) == len(CReturnTypes):
if func is None:
func = Gen.functions.get(FuncName)
CallArgs: list[ir.Value | None] = []
for arg in Node.value.args:
ArgVal: ir.Value | None = self.HandleExprLlvm(arg)
@@ -593,7 +614,7 @@ class AssignHandle(BaseHandle):
var: ir.AllocaInstr = Gen._alloca(VarType, name=VarName)
Gen.variables[VarName] = var
if CReturnTypes[j] is not None:
Gen._record_var_signedness(VarName, ReturnTypeStr)
Gen._record_var_signedness(VarName, str(ReturnTypeInfo) if ReturnTypeInfo else '')
elif ret_type and isinstance(ret_type, ir.LiteralStructType) and j < len(ret_type.elements):
elem_type: ir.Type = ret_type.elements[j]
if isinstance(elem_type, ir.IntType):
@@ -604,6 +625,18 @@ class AssignHandle(BaseHandle):
elif elem_type.width == 64:
Gen._record_var_signedness(VarName, 'unsigned long long')
Gen._store(field_val, var)
elif isinstance(Node.value, ast.Call):
# fail-fast:多返回值赋值但 CReturnTypes 解析失败(跨模块签名丢失),
# 绝不能静默跳过——目标变量不会被初始化,运行期 free(垃圾指针)
# 触发堆损坏(HashLibTest 0xC0000374)。
target_names: list[str] = [
e.id if isinstance(e, ast.Name) else '<expr>' for e in Target.elts
]
raise Exception(
f"多返回值赋值解析失败: '{FuncName}' 的返回类型无法解析为"
f"元组/CReturn(ModulePath={ModulePath}),目标 {target_names} 未被初始化"
f"{Gen._get_node_info()}"
)
return
def _InitStructAtPtr(self, call_node: ast.Call, ClassName: str, VarPtr: ir.Value) -> None:
+71
View File
@@ -2309,6 +2309,10 @@ class ExprCallHandle(BaseHandle):
Gen.class_vtable.add(ClassName)
# 设置方法列表(与 prescan 逻辑一致:基类方法在前)
Gen.class_methods[ClassName] = methods
# 根本修复:注册 vtable 元数据后同步具化 struct body,从源头消除
# "元数据已注册、本体仍 opaque" 的撕裂状态(配合 _HandleMethodCallLlvm
# 中 _EnsureReceiverStructConcrete 的使用前校验,双保险)。
import_handler._TryLoadStructFromStub(ClassName, Gen, source_sha1=class_sha1)
# 设置 class_parent / class_parents(用于虚分派时的函数查找回退与 MRO)
if parent_name and ClassName not in Gen.class_parent:
Gen.class_parent[ClassName] = parent_name
@@ -2943,6 +2947,16 @@ class ExprCallHandle(BaseHandle):
self._EnsureClassParentsLoaded(ClassName, Gen)
if (ClassName in Gen.class_vtable or ClassName in Gen._cross_module_vtable_classes) and ClassName in Gen.class_methods:
# 根本修复:vtable 分派要求接收者 struct 已具化。元数据(vtable 集合 /
# class_methods)与 struct body 可能因并行 include 翻译时序撕裂——
# _try_register_vtable_ondemand 只注册元数据不具化 body,若此时 struct
# 仍 opaque,下方 GEP [0,0] 取 vtable 指针会抛 llvmlite 裸 TypeError
# (典型:list[AST|t.CPtr].append 中 self.__pool__.alloc(...),
# __pool__ 类型 memhub.MemManager|t.CPtr,MemManager 已进 vtable 集合
# 但 struct body 未从 stub 具化)。
# 此处先主动具化(stub + class_members 双路径),仍失败则 fail-fast
# 暴露上游 stub 加载时序问题,而非静默崩溃。
ObjVal = self._EnsureReceiverStructConcrete(ClassName, class_sha1, ObjVal, Gen)
methods = Gen.class_methods[ClassName]
method_idx = -1
for mi, mn in enumerate(methods):
@@ -3100,6 +3114,63 @@ class ExprCallHandle(BaseHandle):
return result
raise Exception(f"Undefined method '{MethodName}' in class '{ClassName}' (no function declaration found for '{FullMethodName}')")
def _EnsureReceiverStructConcrete(self, ClassName: str, class_sha1: str | None, ObjVal: Any, Gen: "LlvmGeneratorMixin") -> Any:
"""根本修复:vtable 分派前保证接收者 struct 已具化(非 opaque)。
背景:跨模块类的 vtable 元数据(class_vtable / _cross_module_vtable_classes /
class_methods)由 _precollect_vtable_state 预扫描或 _try_register_vtable_ondemand
按需注册,而 struct body 依赖 _TryLoadStructFromStub 从 stub.ll 加载。
二者时序可能撕裂:body 仍 opaque 就对 struct 做 GEP [0,0] 取 vtable 指针,
llvmlite 在 types.py 的 elements[i] 处抛裸 TypeError('NoneType' object is
not subscriptable)。
本方法:
1. 先走 stub.ll 补 body(source_sha1 精确匹配优先)
2. 再补 class_members(供 _generate_structs 用成员构造 body 的备选路径)
3. 若 Gen.structs 中已具化版本与 ObjVal 指向的 opaque 占位对象不同,
bitcast 替换 ObjVal 到具体版本
4. 仍 opaque → fail-fast 报带源码上下文的编译错误(遵循"不兜底"原则,
暴露上游 stub 加载/时序问题而非让 llvmlite 裸崩)
Returns:
ObjVal 本身或 bitcast 到具体 struct 版本后的新值。
"""
if not isinstance(ObjVal.type, ir.PointerType):
return ObjVal
pointee: ir.Type = ObjVal.type.pointee
if not isinstance(pointee, ir.IdentifiedStructType) or not pointee.is_opaque:
return ObjVal
import_handler: Any = self.Trans.ImportHandler
# 1) stub.ll 补 body(source_sha1 精确匹配优先)
if class_sha1:
import_handler._TryLoadStructFromStub(ClassName, Gen, source_sha1=class_sha1)
else:
import_handler._TryLoadStructFromStub(ClassName, Gen)
# 2) 补 class_members(备选具化路径)
stub_basename: str = f'{class_sha1}.stub.ll' if class_sha1 else f'{ClassName}.stub.ll'
import_handler._TryLoadClassMembersFromPyi(ClassName, stub_basename, Gen)
# 3) 从 Gen.structs 找已具化版本(短名优先,再按 sha1 全名)
concrete_st: ir.IdentifiedStructType | None = None
_st_short: Any = Gen.structs.get(ClassName)
if isinstance(_st_short, ir.IdentifiedStructType) and not _st_short.is_opaque:
concrete_st = _st_short
if concrete_st is None and class_sha1:
_st_full: Any = Gen.structs.get(f'{class_sha1}.{ClassName}')
if isinstance(_st_full, ir.IdentifiedStructType) and not _st_full.is_opaque:
concrete_st = _st_full
if concrete_st is None:
# 4) 仍 opaque → fail-fast(暴露上游 stub 加载/时序问题)
raise Exception(
f"vtable 分派失败: 类 '{ClassName}' 已注册虚表(class_vtable/class_methods)"
f"但 struct 类型无法从 stub 具化(仍为 opaque){Gen._get_node_info()}"
)
if concrete_st is pointee:
return ObjVal
# ObjVal 指向旧 opaque 占位对象:bitcast 到具体版本
return Gen.builder.bitcast(ObjVal, ir.PointerType(concrete_st), name=f"concrete_self_{ClassName}")
_C_LIB_FUNCS = {
'malloc': lambda: (ir.PointerType(ir.IntType(8)), [ir.IntType(64)]),
'calloc': lambda: (ir.PointerType(ir.IntType(8)), [ir.IntType(64), ir.IntType(64)]),
+10
View File
@@ -2145,6 +2145,16 @@ class ImportHandle(BaseHandle):
_vlog().warning(f"处理导入失败: {_e}", "Exception")
stub_filename = actual_sha1 + '.stub.ll'
self._TryLoadClassMembersFromPyi(class_name, stub_filename, Gen)
# 根本修复:加载后 struct 仍为 opaque 时(如选中 'type opaque' 占位条目、
# 或 set_body 未生效),不保留成功缓存。保留会导致同 Gen 内后续调用永久
# 跳过补载,opaque 状态被固化,vtable 分派 GEP 直接崩溃。
# 失败状态可重试,待 stub 索引刷新 / 定义文件就绪后再加载。
_final_st = Gen.structs.get(class_name)
if isinstance(_final_st, ir.IdentifiedStructType) and _final_st.is_opaque:
try:
del self._struct_Load_cache[cache_key]
except KeyError:
pass
def _TryLoadClassMembersFromPyi(self, class_name: str, stub_filename: str, Gen: LlvmGeneratorMixin) -> None:
ProjectRoot = self._find_project_root()
+17 -9
View File
@@ -15,12 +15,17 @@ class WithHandle(BaseHandle):
_generate_inherited_method_wrappers 只为 class_methods[ClassName] 中的方法生成包装,
不包括父类独有方法,因此 with 语句需要单独处理继承查找。
"""
# 先检查子类自身
# 先检查子类自身:必须有函数体(define)才算真方法。
# 子类继承方法的 stub declare(如 MemPool.__enter__,Phase1 生成的继承
# 包装声明)无函数体且 TPC 不生成对应定义,直接调用会链接 undefined,
# 必须跳过并沿 MRO 找父类实现。
FullName: str = f'{ClassName}.{MethodName}'
func = Gen._find_function(FullName) if hasattr(Gen, '_find_function') else Gen.functions.get(FullName)
if func:
if func and len(list(func.blocks)) > 0:
return (ClassName, func)
# 沿 MRO 继承链查找(多继承:先声明父类优先)
# 沿 MRO 继承链查找(多继承:先声明父类优先)。
# 祖先的 stub declare(无函数体)是真实定义的声明(定义在其模块 obj 中),
# 可直接调用——与 _HandleMethodCallLlvm 的 MRO 回退语义一致。
for anc in Gen._get_mro_chain(ClassName)[1:]:
parent_full: str = f'{anc}.{MethodName}'
parent_func = Gen._find_function(parent_full) if hasattr(Gen, '_find_function') else Gen.functions.get(parent_full)
@@ -84,9 +89,12 @@ class WithHandle(BaseHandle):
enter_class, enter_func = self._ResolveInheritedMethod(ClassName, '__enter__', Gen)
if enter_func is not None:
ctx_Loaded: ir.Value = Gen._load(ctx_alloca, name="__with_ctx")
# 父类方法需要 self 为父类指针类型,bitcast 子类指针
if enter_class != ClassName and enter_class in Gen.structs:
parent_ptr_type = ir.PointerType(Gen.structs[enter_class])
# 父类方法需要 self 为父类指针类型,bitcast 子类指针。
# bitcast 目标用 enter_func 签名参数类型(args[0] 本身即
# MemManager* 指针),避免 Gen.structs 查找返回 opaque/异
# sha1 占位对象导致 llvmlite 类型不匹配。
if enter_class != ClassName:
parent_ptr_type: ir.Type = enter_func.function_type.args[0]
if ctx_Loaded.type != parent_ptr_type:
ctx_Loaded = Gen.builder.bitcast(ctx_Loaded, parent_ptr_type, name="cast_enter_self")
enter_call: ir.CallInstr = Gen.builder.call(enter_func, [ctx_Loaded], name="call_enter")
@@ -169,9 +177,9 @@ class WithHandle(BaseHandle):
exit_class, exit_func = self._ResolveInheritedMethod(ClassName, '__exit__', Gen)
if exit_func is not None:
ctx_Loaded: ir.Value = Gen._load(ctx_alloca, name="__with_ctx")
# 父类方法需要 self 为父类指针类型,bitcast 子类指针
if exit_class != ClassName and exit_class in Gen.structs:
parent_ptr_type = ir.PointerType(Gen.structs[exit_class])
# 父类方法需要 self 为父类指针类型,bitcast 子类指针(同上)
if exit_class != ClassName:
parent_ptr_type: ir.Type = exit_func.function_type.args[0]
if ctx_Loaded.type != parent_ptr_type:
ctx_Loaded = Gen.builder.bitcast(ctx_Loaded, parent_ptr_type, name="cast_exit_self")
Gen.builder.call(exit_func, [ctx_Loaded], name="call_exit")