fix(tpv): 泛型注解预特化下沉到 AnnAssign Handle 路径,修 .get() 误分派崩溃

translate_annassign 解析注解前未先注册泛型特化,`list[ast.AST | t.CPtr]
| t.CPtr` 会退化为裸 list*,随后 body_list.get(i) 被分派到默认
list[int].get(返回 i32)→ inttoptr 截断指针 → 读垃圾 AST 节点,
StmtGenMixin._translate_stmt_impl 崩溃(寄存器指纹与既有潜伏崩溃一致,
IR 实证见 Debugger/ViperOS3.md 第十九节)。

抽出 _prespecialize_generic_annotation 供 pre_scan_allocas 与
translate_annassign 共用(该补救原先只存在于预扫描路径),使两处解析一致。

同批(P1-A):赋值/返回路径把目标类型作为 target_ty 传入 translate_value,
使 `x: uint64 = 12345` 与 `u64(12345)` 物化出同一条 i64 常量、省去 coerce
的 zext。站点:translate_annassign(global/nonlocal/模块级/局部四处)、
translate_assign(单 Name 目标)、translate_return。target_ty 仅被
Constant 节点消费,不影响调用/二元/属性路径。
This commit is contained in:
TermiNexus committed 2026-09-11 18:10:19 +08:00
1 parent d3690f9143
commit 4ce55e3ea3
2 files changed
+124 -40

No files matched your search

+116 -39
View File
@@ -1227,33 +1227,11 @@ class AssignGenMixin:
# 泛型特化补救(在解析之前无条件执行,_specialize_generic_class 幂等):
# 注解可能是 list[X](Subscript)或 list[X] | t.CPtr(含 CPtr 的 BinOp)。
# 若泛型类尚未特化,resolve_annotation_type 会把 list[X] 退化为不透明
# 基类 list*(丢掉元素类型)——PreScan 预建的 alloca 因此变成 list*,
# 之后 body_list.get(i) 会分派到默认 list[int] 特化(返回 i32)→
# inttoptr i32→AST* 截断指针 → StmtGenMixin._translate_stmt_impl
# 虚调用读 vtable 崩溃。故先提取 Subscript 一侧主动特化再解析,保证
# PreScan 与 Handle 两处解析结果一致。
spec_annot: ast.Subscript | t.CPtr = None
if annot.kind() == ast.ASTKind.Subscript:
spec_annot = (ast.Subscript | t.CPtr)(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ps: ast.BinOp | t.CPtr = (ast.BinOp | t.CPtr)(annot)
if bop_ps.op == ast.OpKind.BitOr:
if bop_ps.left is not None and bop_ps.left.kind() == ast.ASTKind.Subscript:
spec_annot = (ast.Subscript | t.CPtr)(bop_ps.left)
elif bop_ps.right is not None and bop_ps.right.kind() == ast.ASTKind.Subscript:
spec_annot = (ast.Subscript | t.CPtr)(bop_ps.right)
if spec_annot is not None:
sv_ps: ast.AST | t.CPtr = spec_annot.value
if sv_ps is not None and sv_ps.kind() == ast.ASTKind.Name:
sub_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(sv_ps)
if sub_nm.id is not None:
template_cd: ast.ClassDef | t.CPtr = self.Gen.ClassGenGeneric._find_generic_template(sub_nm.id)
if template_cd is not None:
type_args_ps: list[str] | t.CPtr = self.Gen.CallGenCtor._extract_type_args_from_slice(
pool, spec_annot.slice, trans._from_imports)
if type_args_ps is not None and len(type_args_ps) > 0:
self.Gen.ClassGenGeneric._specialize_generic_class(
trans, sub_nm.id, type_args_ps)
# 基类 list*(丢掉元素类型)——预建的 alloca 因此变成 list*,之后
# body_list.get(i) 会分派到默认 list[int] 特化(返回 i32)→ inttoptr
# i32→AST* 截断指针 → StmtGenMixin._translate_stmt_impl 读垃圾节点崩溃。
# 与 Handle 路径共用 _prespecialize_generic_annotation,保证两处解析一致。
self._prespecialize_generic_annotation(trans, pool, annot)
resolved: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type(
pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None:
@@ -1299,6 +1277,64 @@ class AssignGenMixin:
return 0
return 0
# ============================================================
# _prespecialize_generic_annotation - 注解为泛型 Subscript(含 | t.CPtr 联合)
# 时先注册特化,保证随后的 resolve_annotation_type 得到「带类型实参」的特化
# 结构体(如 {sha1}.list[ast.ASTptr]),而不是退化为不透明基类(裸 list*)。
#
# 背景(崩溃根因,IR 实证):`body_list: list[ast.AST | t.CPtr] | t.CPtr =
# mc.children` 若在 resolve 时该特化尚未注册,注解会退化成裸 list* →
# 之后 body_list.get(i) 被分派到默认 list[int].get(返回 i32)→
# inttoptr i32→AST* 截断指针 → 读垃圾 AST 节点,
# StmtGenMixin._translate_stmt_impl 崩溃。gen1 IR 实证(MatchGenMixin 模块):
# %"body_list.1" = alloca %"….list"* ← 裸 list(丢类型实参)
# %cast_arg_0.9 = bitcast %"….list"* … to %"….list[int]"*
# %call = call i32 @…list[int].get(…) ; 返回 i32
# %int2ptr_stmt = inttoptr i32 %call to %"….AST"* ← 截断
# PreScan 路径(pre_scan_allocas)此前已有此补救,但 Handle 路径
# (translate_annassign)缺失 ⇒ 在任何「未复用预扫描 alloca」的场景
# (如循环体内换作用域重新声明同名变量)又会建出裸 list* alloca。
# 故抽成公共 helper,两条路径共用,保证两处解析结果一致。
#
# _specialize_generic_class 幂等,重复调用安全。
# 返回 1=执行过特化尝试;0=注解形态不适用(非泛型 Subscript)。
# ============================================================
def _prespecialize_generic_annotation(self, trans: HT.Translator | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
annot: ast.AST | t.CPtr) -> int:
"""泛型注解预特化(见上方注释),返回 1=已尝试 / 0=不适用"""
if annot is None or trans is None:
return 0
# 取注解中的泛型 Subscript 一侧:Subscript 本身,或 A | t.CPtr 的 Subscript 分支
spec_annot: ast.Subscript | t.CPtr = None
if annot.kind() == ast.ASTKind.Subscript:
spec_annot = (ast.Subscript | t.CPtr)(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ps: ast.BinOp | t.CPtr = (ast.BinOp | t.CPtr)(annot)
if bop_ps.op == ast.OpKind.BitOr:
if bop_ps.left is not None and bop_ps.left.kind() == ast.ASTKind.Subscript:
spec_annot = (ast.Subscript | t.CPtr)(bop_ps.left)
elif bop_ps.right is not None and bop_ps.right.kind() == ast.ASTKind.Subscript:
spec_annot = (ast.Subscript | t.CPtr)(bop_ps.right)
if spec_annot is None:
return 0
sv_ps: ast.AST | t.CPtr = spec_annot.value
if sv_ps is None or sv_ps.kind() != ast.ASTKind.Name:
return 0
sub_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(sv_ps)
if sub_nm.id is None:
return 0
template_cd: ast.ClassDef | t.CPtr = self.Gen.ClassGenGeneric._find_generic_template(sub_nm.id)
if template_cd is None:
return 0
type_args_ps: list[str] | t.CPtr = self.Gen.CallGenCtor._extract_type_args_from_slice(
pool, spec_annot.slice, trans._from_imports)
if type_args_ps is None or len(type_args_ps) == 0:
return 0
self.Gen.ClassGenGeneric._specialize_generic_class(
trans, sub_nm.id, type_args_ps)
return 1
# ============================================================
# translate_annassign - 翻译 AnnAssign 语句(迁移自
# HandlesAnnAssign.AnnAssignHandle.Handle)
@@ -1349,6 +1385,12 @@ class AssignGenMixin:
var_ty: llvmlite.LLVMType | t.CPtr = None
annot: ast.AST | t.CPtr = aa.annotation
if annot is not None:
# 与 PreScan 同源:先注册泛型特化,避免 list[X](含 | t.CPtr 联合)
# 在 resolve 时退化为裸 list* → 之后 .get() 误分派到默认
# list[int](返回 i32)→ inttoptr 截断指针 → 读垃圾 AST 节点崩溃。
# 此处是崩溃根因路径:循环体内重新声明同名变量时不会复用预扫描
# alloca,必须在本路径同样先特化(详见 _prespecialize_generic_annotation)。
self._prespecialize_generic_annotation(trans, pool, annot)
resolved: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type(
pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None:
@@ -1377,9 +1419,15 @@ class AssignGenMixin:
mod_alloca: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_module_var(
trans.SymTab, nm.id)
if mod_alloca is not None and aa.value is not None:
# P1-A(上下文位宽传播):把目标变量的类型作为 target_ty 传入,
# 使整数字面量按目标位宽直接物化(如 uint64 目标 → i64 const),
# 与显式 u64(...) 强转一致,且省去后续 coerce 生成的 zext。
aa_glob_ty: llvmlite.LLVMType | t.CPtr = None
if mod_alloca.Ty is not None:
aa_glob_ty = mod_alloca.Ty.Pointee
rhs_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, aa.value,
None, 0, trans)
None, 0, trans, aa_glob_ty)
if rhs_val is not None:
# 对象类全局(有静态 storage):对象构造/赋值 → 绑定 storage + 复制对象
stg_ref_aa: llvmlite.Value | t.CPtr = None
@@ -1417,18 +1465,21 @@ class AssignGenMixin:
# nonlocal 变量:通过闭包 env 写入
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
if aa.value is not None:
# P1-A:先把 nonlocal 槽位指针取出(原先在 RHS 翻译之后),以便把
# 槽位类型作为 target_ty 传入;槽位/类型缺失时直接返回,不再发射
# 无用的 RHS 死值。
nl_ptr: llvmlite.Value | t.CPtr = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(
trans, nm.id)
# 从 nl_ptr 的类型推断 nonlocal 变量实际类型,
# 不再强制 i32(会截断指针/结构体/i64 导致闭包数据损坏)
nl_target_ty: llvmlite.LLVMType | t.CPtr = None
if nl_ptr is not None and nl_ptr.Ty is not None:
nl_target_ty = nl_ptr.Ty.Pointee
rhs_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, aa.value,
None, 0, trans)
None, 0, trans, nl_target_ty)
if rhs_val is not None:
nl_ptr: llvmlite.Value | t.CPtr = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(
trans, nm.id)
if nl_ptr is not None:
# 从 nl_ptr 的类型推断 nonlocal 变量实际类型,
# 不再强制 i32(会截断指针/结构体/i64 导致闭包数据损坏)
nl_target_ty: llvmlite.LLVMType | t.CPtr = None
if nl_ptr.Ty is not None:
nl_target_ty = nl_ptr.Ty.Pointee
if nl_target_ty is None:
# fast-fail: nl_ptr 类型信息丢失,无法安全 store
VLogger.error(
@@ -1455,9 +1506,13 @@ class AssignGenMixin:
self._init_global_array_from_list(
builder, pool, mod, mod_glob, aa.value, trans)
else:
# P1-A:模块级全局目标类型作为 target_ty(同上方 global 分支)
mg_val_ty: llvmlite.LLVMType | t.CPtr = None
if mod_glob.Ty is not None:
mg_val_ty = mod_glob.Ty.Pointee
rhs_val_mg: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, aa.value,
None, 0, trans)
None, 0, trans, mg_val_ty)
if rhs_val_mg is not None:
# 对象类全局(有静态 storage):对象构造/赋值 → 绑定 storage + 复制对象
stg_ref_mg: llvmlite.Value | t.CPtr = None
@@ -1604,9 +1659,14 @@ class AssignGenMixin:
# 是否为赋值直接 RHS(对齐 TPC _infer_deref_target_type)。
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
# P1-A:局部变量 alloca 的 pointee 类型作为 target_ty(同全局分支),
# 使 `x: uint64 = 12345` 直接物化 i64 常量,与 `x: uint64 = u64(12345)` 一致。
lv_target_ty: llvmlite.LLVMType | t.CPtr = None
if alloca.Ty is not None:
lv_target_ty = alloca.Ty.Pointee
rhs_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, aa.value,
None, 0, trans)
None, 0, trans, lv_target_ty)
if rhs_val is not None:
# 注解退化修复(对齐 TPC 从构造结果推断类型):
# 无实参泛型模板类注解(如 vi: vector.Vector | CPtr,模板
@@ -1719,8 +1779,25 @@ class AssignGenMixin:
# (防残留误判 c.Deref 宽度)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
# P1-A(上下文位宽传播):单 Name 目标且其存储类型已知时,把存储类型作为
# target_ty 传入,使 `x = 12345`(x: uint64)与 `x = u64(12345)` 一致
# (直接 i64 常量,省去 coerce 生成的 zext)。多目标/非 Name 目标无法给出
# 唯一目标类型(且 RHS 只翻译一次供全部目标共用)⇒ 维持原行为(None)。
asg_target_ty: llvmlite.LLVMType | t.CPtr = None
if len(targets) == 1:
asg_t0: ast.AST | t.CPtr = targets.get(0)
if asg_t0 is not None and asg_t0.kind() == ast.ASTKind.Name:
asg_nm0: ast.Name | t.CPtr = (ast.Name | t.CPtr)(asg_t0)
if asg_nm0.id is not None:
asg_slot: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(
trans.SymTab, asg_nm0.id)
if asg_slot is None:
asg_slot = self.Gen.VarGen.lookup_module_var(
trans.SymTab, asg_nm0.id)
if asg_slot is not None and asg_slot.Ty is not None:
asg_target_ty = asg_slot.Ty.Pointee
rhs_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, asgn.value, None, 0, trans)
builder, pool, mod, asgn.value, None, 0, trans, asg_target_ty)
if rhs_val is None:
# Phase A 依赖未就绪(_deps_pending=1):跨模块函数/方法因 text.ll
# 未生成返回类型退化为 void/None(CallGenMixin L4493