diff --git a/App/lib/core/Gen/Expr/ExprGenAttr.vp b/App/lib/core/Gen/Expr/ExprGenAttr.vp index cc1b35b..cd003d3 100644 --- a/App/lib/core/Gen/Expr/ExprGenAttr.vp +++ b/App/lib/core/Gen/Expr/ExprGenAttr.vp @@ -524,7 +524,11 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase): case _: nl_val_ptr_a: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl_a, mlirlite.Ptr(pool, nl_ty_a)) if nl_val_ptr_a is not None: obj_ptr = mlirlite.build_load(builder, nl_ty_a, nl_val_ptr_a) - else: obj_ptr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id) + else: + obj_ptr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id) + if self.Gen.VarGen._FreedCheck(obj_ptr) != 0: + self.Gen.TypeGenErr.fatal_error(at.value, f"cannot use '{nm.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError") + return None # non-Name path: translate the object value (which loads) if obj_ptr is None: @@ -938,7 +942,11 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase): case _: nl_val_ptr_a: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl_a, mlirlite.Ptr(pool, nl_ty_a)) if nl_val_ptr_a is not None: obj_ptr = mlirlite.build_load(builder, nl_ty_a, nl_val_ptr_a) - else: obj_ptr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id) + else: + obj_ptr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id) + if self.Gen.VarGen._FreedCheck(obj_ptr) != 0: + self.Gen.TypeGenErr.fatal_error(at.value, f"cannot use '{nm.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError") + return None # module attribute access (write path): sys._mbuddy = mb / os._win32._mbuddy = v → store to the module # global. When obj is an imported module name (Name or nested Attribute path), find/create the global. diff --git a/App/lib/core/Gen/Expr/ExprGenBase.vp b/App/lib/core/Gen/Expr/ExprGenBase.vp index 6c77031..da5b611 100644 --- a/App/lib/core/Gen/Expr/ExprGenBase.vp +++ b/App/lib/core/Gen/Expr/ExprGenBase.vp @@ -891,7 +891,7 @@ novirtual class ExprGenBase: if self._same_agg_ty(builder.Pool, slot_p2v, target_ty) != 0: return mlirlite.build_load(builder, target_ty, val) if self._same_agg_ty(builder.Pool, ctor_p2v, target_ty) == 0: - m_p2v: str = "implicit value/pointer conversion for non-basic types is forbidden (except construction materialization): a pointer type cannot be implicitly converted to a non-basic value (Foo* -> Foo); to dereference use c.deref(p) / *p explicitly, or cast explicitly" + m_p2v: str = "implicit value/pointer conversion for non-basic types is forbidden (except construction materialization): a pointer type cannot be implicitly converted to a non-basic value (Foo* -> Foo); to dereference use *p explicitly, or cast explicitly" p2v_txt: str = GenApi.escape_fstring_copy(f"; source type {self._type_text(builder.Pool, val.Ty)} -> target value type {self._type_text(builder.Pool, target_ty)}") self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), f"{m_p2v}{p2v_txt}", "CastError") return val diff --git a/App/lib/core/Gen/Expr/ExprGenName.vp b/App/lib/core/Gen/Expr/ExprGenName.vp index 3d5777f..7a10d7a 100644 --- a/App/lib/core/Gen/Expr/ExprGenName.vp +++ b/App/lib/core/Gen/Expr/ExprGenName.vp @@ -42,7 +42,7 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase): # 只在**读**路径检查;对同名变量重新赋值会清除失效态(见 AssignGenMixin)。 if trans is not None: if self.Gen.VarGen._FreedCheck(self.Gen.VarGen.lookup_var(trans.SymTab, nm_id)) != 0: - self.Gen.TypeGenErr.fatal_error(node, f"cannot use '{nm_id}': already released by a @life(willFree(...)) call", "LifetimeError") + self.Gen.TypeGenErr.fatal_error(node, f"cannot use '{nm_id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError") # DefineT compile-time constant: return the constant (float -> double, int -> i32). # Existing variables win over DefineT table entries injected from vpi stubs (lookup_var guard). cdef_val_d: double = self.Gen.TypeGenCDef.lookup_cdefine_double(nm_id) @@ -535,6 +535,9 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase): if nl_elem_ty is not None: return mlirlite.build_load(builder, nl_elem_ty, nl_ptr_r) return None alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id) + if self.Gen.VarGen._FreedCheck(alloca) != 0: + self.Gen.TypeGenErr.fatal_error(sub.value, f"cannot use '{nm.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError") + return None if alloca is not None and alloca.Ty is not None: if self.is_ptr_type(alloca.Ty) != 0: pointee: mlirlite.LLVMType* = alloca.Ty.Pointee @@ -844,6 +847,9 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase): # cannot be used because its SSA number belongs to another function). if GenApi.is_nonlocal_name(trans, nm.id) != 0: return self._nonlocal_subscript_ptr(builder, pool, trans, nm.id, idx_val) alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id) + if self.Gen.VarGen._FreedCheck(alloca) != 0: + self.Gen.TypeGenErr.fatal_error(sub.value, f"cannot use '{nm.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError") + return None if alloca is not None and alloca.Ty is not None: if self.is_ptr_type(alloca.Ty) != 0: pointee: mlirlite.LLVMType* = alloca.Ty.Pointee diff --git a/App/lib/core/Gen/Expr/ExprGenOps.vp b/App/lib/core/Gen/Expr/ExprGenOps.vp index 0ea2d69..7aa3bc9 100644 --- a/App/lib/core/Gen/Expr/ExprGenOps.vp +++ b/App/lib/core/Gen/Expr/ExprGenOps.vp @@ -918,7 +918,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase): if nm_ao.id is not None: # P2 willFree:对已失效变量取地址 → fail-fast(与读取路径同一口径) if self.Gen.VarGen._FreedCheck(self.Gen.VarGen.lookup_var(trans.SymTab, nm_ao.id)) != 0: - self.Gen.TypeGenErr.fatal_error(node, f"cannot take the address of '{nm_ao.id}': already released by a @life(willFree(...)) call", "LifetimeError") + self.Gen.TypeGenErr.fatal_error(node, f"cannot take the address of '{nm_ao.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError") # self/cls object reference: return the object pointer itself (Ptr(Struct)), not the pointer variable's address (Ptr(Ptr(Struct))). if string.strcmp(nm_ao.id, "self") == 0 or string.strcmp(nm_ao.id, "cls") == 0: se_ao: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_ao.id) diff --git a/App/lib/core/Gen/Stmt/StmtGenMixin.vp b/App/lib/core/Gen/Stmt/StmtGenMixin.vp index 238df34..51abc54 100644 --- a/App/lib/core/Gen/Stmt/StmtGenMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenMixin.vp @@ -121,20 +121,45 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): for ti in range(int(tn)): tgt: ast.AST* = dnode.targets.get(ti) if tgt is None: continue - # del obj -> __delete__ (matches TPC HandlesDelete.py; no-op if undefined) + # del obj -> reclaim a heap object: `__del__` first, then free; later uses fail 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 compiler-owned `slife` object (freed automatically at scope exit); a manual del would double-free it", "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) - if del_st_nm is not None: - del_cls_nm: str = self.Gen.StructGen.get_class_name_by_type(pool, del_st_nm) - if del_cls_nm is not None: self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, del_obj, "__delete__", trans) + if nm_del is None or nm_del.id is None: continue + del_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_del.id) + if del_slot is None or del_slot.Ty is None or del_slot.Ty.Pointee == 0: + self.Gen.TypeGenErr.fatal_error(tgt, f"cannot `del {nm_del.id}`: unknown variable", "TypeError") + continue + if self.Gen.VarGen._FreedCheck(del_slot) != 0: + self.Gen.TypeGenErr.fatal_error(tgt, f"cannot `del {nm_del.id}`: it was already released", "LifetimeError") + continue + del_var_ty: mlirlite.LLVMType* = del_slot.Ty.Pointee + del_struct_ty: mlirlite.LLVMType* = None + if self.Gen.ExprGenBase.is_ptr_type(del_var_ty) != 0: del_struct_ty = del_var_ty.Pointee + if del_struct_ty is None or del_struct_ty.kind() != mlirlite.LLVMType.Struct: + self.Gen.TypeGenErr.fatal_error(tgt, f"cannot `del {nm_del.id}`: del reclaims a heap object, so the variable must be a pointer (T*) to a class/struct", "TypeError") + continue + del_owned: int = self.Gen.VarGen._SlifeOwned(del_slot) + if self.Gen.VarGen.get_var_life(trans.SymTab, nm_del.id) == GenTypes.LIFE_FRAME and del_owned == 0: + self.Gen.TypeGenErr.fatal_error(tgt, f"cannot `del {nm_del.id}`: it points into the frame (`new stack` or a borrow), so there is no heap block to free", "LifetimeError") + continue + del_cur: mlirlite.Value* = mlirlite.build_load(builder, del_var_ty, del_slot) + if del_cur is None: + self.Gen.TypeGenErr.fatal_error(tgt, f"cannot `del {nm_del.id}`: the pointer value could not be loaded", "TypeError") + continue + self._emit_del_dtor(pool, builder, mod, trans, del_cur, del_var_ty, del_struct_ty) + if self.Gen.CallGenBase.find_func_in_module(mod, "free") is None: + self.Gen.TypeGenErr.fatal_error(tgt, "cannot `del`: the module has no declaration of the C library function 'free'", "TypeError") + continue + i8_ptr_del: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) + del_arg: mlirlite.Value* = del_cur + if del_cur.Ty is not None and uint64(del_cur.Ty) != uint64(i8_ptr_del): + del_arg = mlirlite.build_bitcast(builder, del_cur, i8_ptr_del) + if del_arg is not None: mlirlite.build_call(builder, "free", del_arg, 1, mlirlite.Void(pool), 0) + # cancel the automatic scope-exit release of a compiler-owned binding + if del_owned != 0: + del_sh: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(del_slot) + if del_sh is not None: self.Gen.VarGen._SlifeNoteStore(pool, builder, del_sh, 0) + self.Gen.VarGen._FreedMark(del_slot) # del obj.attr -> property deleter; falls back to __del__ (matches TPC) elif tgt.kind() == ast.ASTKind.Attribute: del_at: ast.Attribute* = ast.Attribute*(tgt) @@ -176,6 +201,35 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): mlirlite.build_call(builder, dl_fn_name, dl_self, 2, dl_ret, 0) return 0 + def _emit_del_dtor(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, cur: mlirlite.Value*, var_ty: mlirlite.LLVMType*, struct_ty: mlirlite.LLVMType*) -> int: + """Call `__del__` on a heap object before freeing it; null pointers are skipped.""" + if pool is None or builder is None or mod is None or cur is None or struct_ty is None: 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 + nulp: mlirlite.Value* = mlirlite.ConstNull(pool, var_ty, "null") + if nulp is None: return 0 + cond: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, cur, nulp) + if cond is None: return 0 + func: mlirlite.Function* = builder.Func + if func is None: return 0 + cnt: int = trans._label_counter + trans._label_counter = cnt + 1 + b1: str = pool.alloc(32) + b2: str = pool.alloc(32) + if b1 is None or b2 is None: return 0 + viperlib.snprintf(b1, 32, "del.dtor.%d", cnt) + viperlib.snprintf(b2, 32, "del.dtor.end.%d", cnt) + dtor_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, b1) + end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, b2) + if dtor_bb is None or end_bb is None: return 0 + mlirlite.build_cond_br(builder, cond, dtor_bb, end_bb) + mlirlite.position_at_end(builder, dtor_bb) + self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, cur, "__del__", trans, None, 0) + mlirlite.build_br(builder, end_bb) + mlirlite.position_at_end(builder, end_bb) + return 1 + # translate_assert - translate `assert cond[, msg]` into a single testcheck.check(...) call: # a true condition is silent (ok_msg=None), a false one reports FAIL via the standard path # (no printf/exit/exception; the checker prints nothing when the condition holds).