feat(gen): slife 释放前调度析构 __del__,并禁止手动 del(§3.6 落地)

- 释放统一入口 _SlifeEmitGuardedFree 改为「先析构、再 free」;析构名定案 __del__(对齐 TPC LLVMCG/MemoryOps._emit_local_heap_frees 的释放路径;del obj 那条语句走的仍是 __delete__)。

- 查表沿 MRO / 跨模块 stub 回退(复用 ExprGenOps._resolve_dunder_by_class);类未定义 __del__ 时**不加任何分支**,常见路径零开销。

- 析构用条件分支而不是 select:未拥有的路径必须完全不进析构体(析构体里会解引用 self,传 null 进去会踩空);块标签走 trans._label_counter,避免同函数内重名。

- §2.4 判据5 扩展:对 slife 对象手动 del obj → LifetimeError(否则析构跑两次)。hlife 路径不受影响。

- 为把 trans 传进析构查找,_SlifeEmitFrees / _SlifeEmitGuardedFree / _SlifeDropOwned 加 trans 形参,9 处调用点同步。
This commit is contained in:
TermiNexus committed 2026-10-08 20:34:11 +08:00
1 parent 78b2052445
commit 2178970ff7
7 files changed
+75 -21

No files matched your search

+5 -5
View File
@@ -687,12 +687,12 @@ novirtual class AssignGenMixin:
# _SlifeDropOwned - 该槽已被登记为 slife 绑定时交还所有权:释放它当前拥有的对象、影子清 0。
# 用于所有"槽里不再是编译器拥有的对象"的赋值(覆盖赋值 / 别名拷贝 / 外来指针 / None)。
# 未登记 → no-op(该槽从未被 `new heap slife` 绑定过,编译器不负责它)。
def _SlifeDropOwned(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, target_slot: mlirlite.Value*) -> int:
"""释放该槽当前拥有的对象并置"不拥有";1=处理过 / 0=未登记"""
def _SlifeDropOwned(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, target_slot: mlirlite.Value*) -> int:
"""释放该槽当前拥有的对象并置"不拥有"(先按 §3.6 调度 `__del__`);1=处理过 / 0=未登记"""
if target_slot is None: return 0
sh: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(target_slot)
if sh is None: return 0
self.Gen.VarGen._SlifeEmitGuardedFree(pool, builder, mod, target_slot, sh)
self.Gen.VarGen._SlifeEmitGuardedFree(pool, builder, mod, trans, target_slot, sh)
self.Gen.VarGen._SlifeNoteStore(pool, builder, sh, 0)
return 1
@@ -710,7 +710,7 @@ novirtual class AssignGenMixin:
if self.Gen.CallGenCtor._new_is_owned_heap(nn) != 0:
sh: mlirlite.Value* = self.Gen.VarGen._SlifeEnsureOwned(pool, builder, target_slot)
if sh is None: return 0
self._SlifeDropOwned(pool, builder, mod, target_slot)
self._SlifeDropOwned(pool, builder, mod, trans, target_slot)
self.Gen.VarGen._SlifeNoteStore(pool, builder, sh, 1)
return 1
# 自赋值(p = p):保持原状;否则会"先释放再放弃所有权",把对象漏掉
@@ -719,7 +719,7 @@ novirtual class AssignGenMixin:
if rn.id is not None:
src_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, rn.id)
if src_slot is not None and uint64(src_slot) == uint64(target_slot): return 0
self._SlifeDropOwned(pool, builder, mod, target_slot)
self._SlifeDropOwned(pool, builder, mod, trans, target_slot)
return 0
def _is_addrlike_call(self, node: ast.AST*) -> int:
+2 -2
View File
@@ -832,7 +832,7 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if ret_none_cn_m.const_kind == ast.CONST_NONE: _is_void = 1
if _is_void != 0:
# M3:隐式收尾也是函数出口,先释放本方法登记的 slife
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
mlirlite.build_ret_void(func_builder)
elif last_is_ctrl != 0 and trans._ctrl_merge_reachable == 0:
# Non-void with a control-flow last statement and a structurally unreachable merge ->
@@ -844,7 +844,7 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
_m_zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty)
if _m_zero_val is not None:
# M3:隐式收尾出口,先释放 slife 再返回
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
mlirlite.build_ret(func_builder, _m_zero_val)
else: self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
else: self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
+2 -2
View File
@@ -820,7 +820,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if ret_none_cn.const_kind == ast.CONST_NONE: _is_void = 1
if _is_void != 0:
# M3:隐式收尾也是一个函数出口,先释放本函数登记的 slife 再返回
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
mlirlite.build_ret_void(func_builder)
elif last_is_ctrl != 0 and trans._ctrl_merge_reachable == 0:
# A non-void function whose last statement is control flow with a structurally unreachable merge (all
@@ -832,7 +832,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty)
if zero_val is not None:
# M3:隐式收尾出口,先释放 slife 再返回
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
mlirlite.build_ret(func_builder, zero_val)
else: self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
else: self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
+1 -1
View File
@@ -224,7 +224,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
if last_stmt is not None and last_stmt.kind() == ast.ASTKind.Return: last_is_return = 1
if last_is_return == 0:
# M3:隐式收尾也是函数出口,先释放本嵌套函数登记的 slife
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
zero_val: mlirlite.Value* = mlirlite.const_int32(pool, 0)
mlirlite.build_ret(func_builder, zero_val)
+5 -5
View File
@@ -535,17 +535,17 @@ novirtual class StmtGenCifMixin:
is_float_ret = 1
if is_void_ret != 0:
# M3 owned slife(§4.2):每个 return 都是函数出口,先释放本函数登记的 slife
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
mlirlite.build_ret_void(builder)
return 0
if is_ptr_ret != 0:
null_val: mlirlite.Value* = mlirlite.ConstNull(pool, ret_ty, "null")
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
mlirlite.build_ret(builder, null_val)
return 0
if is_float_ret != 0:
zero_f: mlirlite.Value* = mlirlite.ConstFloat(pool, ret_ty, 0.0)
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
mlirlite.build_ret(builder, zero_f)
return 0
if ret_ty is None:
@@ -569,7 +569,7 @@ novirtual class StmtGenCifMixin:
case mlirlite.LLVMType.Void():
rv_void = 1
if rv_void != 0:
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
mlirlite.build_ret_void(builder)
return 0
if ret_ty_rt is not None:
@@ -633,7 +633,7 @@ novirtual class StmtGenCifMixin:
val = self.Gen.ExprGenBase.coerce_to_type(builder, val, ret_ty_rt, node, trans)
# M3 owned slife(§4.2):返回值已算完,此处是常规出口——先释放 slife 再 ret
# (必须在 val 求值之后:`return p.x` 之类若先释放 p 再读就成了 use-after-free)。
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod)
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
mlirlite.build_ret(builder, val)
return 0
+6
View File
@@ -100,6 +100,12 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
if tgt is None: continue
# del obj -> __delete__ (matches TPC HandlesDelete.py; no-op if undefined)
if tgt.kind() == ast.ASTKind.Name:
# §2.4 判据5:slife 对象由编译器独占(出口自动 __del__ + free),手动 del 会让析构跑两次
nm_del: ast.Name* = ast.Name*(tgt)
if nm_del is not None and nm_del.id is not None:
del_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_del.id)
if self.Gen.VarGen._SlifeOwned(del_slot) != 0:
self.Gen.TypeGenErr.fatal_error(tgt, f"cannot `del {nm_del.id}`: it is a `slife` object owned by the compiler (its destructor `__del__` runs and its storage is freed automatically at the end of the scope); a manual del would destroy it twice", "LifetimeError")
del_obj: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, tgt, None, 0, trans)
if del_obj is not None:
del_st_nm: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(del_obj)
+54 -6
View File
@@ -10,6 +10,7 @@ import mlirlite
import memhub
import string
import stdlib
import viperlib
# VarGenMixin - variable/scope symbol table Mixin
@@ -493,13 +494,60 @@ novirtual class VarGenMixin:
mlirlite.build_store(builder, v, sh)
return 1
def _SlifeEmitGuardedFree(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, alloca: mlirlite.Value*, sh: mlirlite.Value*) -> int:
"""`free(select(sh != 0, load 槽, null))`:只释放该槽**确实拥有**的对象。
def _SlifeEmitDtorBranch(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, alloca: mlirlite.Value*, sh: mlirlite.Value*) -> int:
"""§3.6 析构调度:拥有该对象时先调 `__del__`(对齐 TPC MemoryOps._emit_local_heap_frees:先析构、再 free)。
用 icmp + select 而不是分支块:释放点可能插在任意位置(每个 return、覆盖赋值处),
建块要处理支配关系与合流,select 无分支、无合流,安全得多。sh=0 时等价 free(NULL)。"""
用**条件分支**而不是 select:未拥有的路径上必须完全不进析构体(析构体里会解引用 self)。
类(含 MRO / 跨模块 stub)没定义 `__del__` 时**不加任何分支**,直接返回——常见路径零开销。
析构名的选择:与 TPC 的**释放**路径一致用 `__del__`(`del obj` 那条语句才是 `__delete__`)。"""
if pool is None or builder is None or mod is None or trans is None: return 0
if alloca is None or sh is None: return 0
if alloca.Ty is None or alloca.Ty.Pointee == 0: return 0
elem: mlirlite.LLVMType* = alloca.Ty.Pointee # 变量类型(T*)
if elem is None or self.Gen.ExprGenBase.is_ptr_type(elem) == 0: return 0
struct_ty: mlirlite.LLVMType* = elem.Pointee # T
if struct_ty is None or struct_ty.kind() != mlirlite.LLVMType.Struct: return 0
cls_nm: str = self.Gen.StructGen.get_class_name_by_type(pool, struct_ty)
if cls_nm is None: return 0
if self.Gen.ExprGenOps._resolve_dunder_by_class(pool, mod, cls_nm, "__del__", trans, struct_ty) is None: return 0
func: mlirlite.Function* = builder.Func
if func is None: return 0
zero8: mlirlite.Value* = mlirlite.const_int(pool, 8, 0)
if zero8 is None: return 0
shv: mlirlite.Value* = mlirlite.build_load(builder, mlirlite.Int8(pool), sh)
if shv is None: return 0
cond: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, shv, zero8)
if cond is None: return 0
cur: mlirlite.Value* = mlirlite.build_load(builder, alloca.Ty.Pointee, alloca)
if cur is None: return 0
# 唯一标签:用 trans._label_counter(与 translate_if 同源,避免同函数内重名标签)
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
name_buf: str = pool.alloc(32)
if name_buf is None: return 0
viperlib.snprintf(name_buf, 32, "slife.dtor.%d", cnt)
dtor_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
viperlib.snprintf(name_buf, 32, "slife.dtor.end.%d", cnt)
merge_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
if dtor_bb is None or merge_bb is None: return 0
mlirlite.build_cond_br(builder, cond, dtor_bb, merge_bb)
mlirlite.position_at_end(builder, dtor_bb)
# `%cur` 定义在分支块之前,支配 dtor_bb 与 merge_bb 两边 ✓
self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, cur, "__del__", trans, None, 0)
mlirlite.build_br(builder, merge_bb)
mlirlite.position_at_end(builder, merge_bb)
return 1
def _SlifeEmitGuardedFree(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, alloca: mlirlite.Value*, sh: mlirlite.Value*) -> int:
"""释放该槽**确实拥有**的对象:先按 §3.6 调度析构 `__del__`(若类定义了),再 `free`。
释放本体用 icmp + select 而不是分支块:释放点可能插在任意位置(每个 return、覆盖赋值处),
select 无分支、无合流,安全得多;sh=0 时取 null,等价 free(NULL),槽里的外来指针/未初始化
值绝不参与释放。析构那一步另有条件分支(见 _SlifeEmitDtorBranch)。"""
if pool is None or builder is None or alloca is None or sh is None: return 0
if alloca.Ty is None or alloca.Ty.Pointee == 0: return 0
# §3.6:先析构,再 free
self._SlifeEmitDtorBranch(pool, builder, mod, trans, alloca, sh)
zero8: mlirlite.Value* = mlirlite.const_int(pool, 8, 0)
if zero8 is None: return 0
shv: mlirlite.Value* = mlirlite.build_load(builder, mlirlite.Int8(pool), sh)
@@ -536,7 +584,7 @@ novirtual class VarGenMixin:
j += 1
return 1
def _SlifeEmitFrees(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*) -> int:
def _SlifeEmitFrees(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*) -> int:
"""函数出口(所有 return / 隐式收尾):逆序释放**本函数**登记且仍拥有的 slife 对象。
防御性去重:同一变量槽若在表里出现多次(登记路径异常 / 旧条目残留),只发一次 free——
@@ -547,7 +595,7 @@ novirtual class VarGenMixin:
slot: uint64 = uint64(_g_slife_owned[i * 2])
sh: mlirlite.Value* = mlirlite.Value*(_g_slife_owned[i * 2 + 1])
if sh is not None and self._SlifeSlotFirstSeen(i, slot) != 0:
self._SlifeEmitGuardedFree(pool, builder, mod, mlirlite.Value*(_g_slife_owned[i * 2]), sh)
self._SlifeEmitGuardedFree(pool, builder, mod, trans, mlirlite.Value*(_g_slife_owned[i * 2]), sh)
i -= 1
return 0