diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index de8bb80..de6cfbe 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -1062,15 +1062,16 @@ novirtual class AssignGenMixin: return (ast.Name*)(expr).id if expr.kind() == ast.ASTKind.UnaryOp: uo_b: ast.UnaryOp* = ast.UnaryOp*(expr) - if uo_b.op == ast.OpKind.AddrOf: + if uo_b.op == ast.OpKind.AddrOf or uo_b.op == ast.OpKind.AddrOfMut: return self._BorrowOwnerName(uo_b.operand) if uo_b.op == ast.OpKind.Deref: return self._BorrowOwnerName(uo_b.operand) return None def _move_out_assigned_source(self, trans: HT.Translator*, rhs: ast.AST*, - pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*) -> int: - """OBS move: after `field = ownVar` / `arr[i] = ownVar`, the source no longer owns the value.""" + pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, + target_own: int = 0) -> int: + """OBS move: `ownField = ownVar` transfers ownership; any other slot rejects the move.""" if trans is None or rhs is None or trans.StrictOBS == 0: return 0 if rhs.kind() != ast.ASTKind.Name: return 0 src_nm: ast.Name* = ast.Name*(rhs) @@ -1078,6 +1079,9 @@ novirtual class AssignGenMixin: src_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, src_nm.id) if src_slot is None: return 0 if self.Gen.VarGen._SlifeOwned(src_slot) == 0: return 0 + if target_own == 0: + self.Gen.TypeGenErr.fatal_error(rhs, f"cannot move `{src_nm.id}` into a slot that is not `own`: it would never be released. Declare the target as `own T`, or keep the owned binding and borrow it instead", "LifetimeError") + return 0 # Cancel the source's scope-exit release (the field/element now holds the pointer) and # reject later uses of the moved binding. ssh: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(src_slot) @@ -1416,14 +1420,25 @@ novirtual class AssignGenMixin: return res if k == ast.ASTKind.UnaryOp: uo: ast.UnaryOp* = ast.UnaryOp*(node) - if uo.op == ast.OpKind.AddrOf and uo.operand is not None and uo.operand.kind() == ast.ASTKind.Name: + if (uo.op == ast.OpKind.AddrOf or uo.op == ast.OpKind.AddrOfMut) \ + and uo.operand is not None and uo.operand.kind() == ast.ASTKind.Name: onm: ast.Name* = ast.Name*(uo.operand) if onm.id is not None: if GenApi.is_global_name(trans, onm.id) != 0: return GenTypes.LIFE_STATIC # 模块级(根 scope)变量:静态存储,同 Name 分支判据(`&<模块级数组>` 不悬垂) if self._is_module_scope_var(trans, onm.id) != 0: return GenTypes.LIFE_STATIC if GenApi.is_nonlocal_name(trans, onm.id) != 0: return GenTypes.LIFE_UNKNOWN - if self.Gen.VarGen.lookup_var(trans.SymTab, onm.id) is not None: return GenTypes.LIFE_FRAME + slot_lo: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, onm.id) + if slot_lo is not None: + # `&ownedPtr` yields the heap object pointer (one load), not the frame slot. + if trans.StrictOBS != 0 and slot_lo.Ty is not None: + match slot_lo.Ty: + case mlirlite.LLVMType.Ptr(lo1): + match lo1: + case mlirlite.LLVMType.Ptr(_): return GenTypes.LIFE_HEAP + case _: pass + case _: pass + return GenTypes.LIFE_FRAME return GenTypes.LIFE_STATIC return GenTypes.LIFE_UNKNOWN if k == ast.ASTKind.Call: @@ -1628,6 +1643,10 @@ novirtual class AssignGenMixin: if trans is None or value is None: return 0 fd: ast.AST* = self.Gen.StructGen.NllCurFunc if fd is None: return 0 + # OBS has no `@life` contracts: a returned borrow must come from a reference parameter or + # `self`, which is the elided lifetime rule for reference return types. + if GenApi.strict_obs() != 0: + return self._check_return_borrow_obs(trans, ast.FunctionDef*(fd), value) rv_ty: mlirlite.LLVMType* = None if trans._cur_func is not None: rv_ty = mlirlite.function_get_ret_ty(trans._cur_func) # target_ty 必须传返回类型:裸本帧数组名在**指针**返回类型下是数组退让(交出栈地址), @@ -1686,6 +1705,64 @@ novirtual class AssignGenMixin: # _SlifeDropOwned - 该槽已被登记为 slife 绑定时交还所有权:释放它当前拥有的对象、影子清 0。 # 用于所有"槽里不再是编译器拥有的对象"的赋值(覆盖赋值 / 别名拷贝 / 外来指针 / None)。 # 未登记 → no-op(该槽从未被 `new heap slife` 绑定过,编译器不负责它)。 + # _check_return_borrow_obs - OBS return check: reference returns borrow a parameter or `self`. + def _check_return_borrow_obs(self, trans: HT.Translator*, fd: ast.FunctionDef*, value: ast.AST*) -> int: + """Reject a frame-local return in an OBS file; borrow elision replaces `@life`.""" + if trans is None or fd is None or value is None: return 0 + rv_ty: mlirlite.LLVMType* = None + if trans._cur_func is not None: rv_ty = mlirlite.function_get_ret_ty(trans._cur_func) + life_r: int = self.life_of_expr(trans, value, rv_ty) + if life_r == GenTypes.LIFE_PURE_OWNED: return 0 + if life_r == GenTypes.LIFE_FRAME and trans._in_pure != 0 and value.kind() == ast.ASTKind.Name: + n_p: ast.Name* = ast.Name*(value) + if n_p is not None and n_p.id is not None: + slot_p: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, n_p.id) + if slot_p is not None and self._pure_str_slot(trans.Pool, slot_p) != 0 \ + and self.Gen.VarGen._SlifeOwned(slot_p) != 0: return 0 + if life_r != GenTypes.LIFE_FRAME: return 0 + if trans._in_pure != 0: + nm_pure: str = "expression" + if value.kind() == ast.ASTKind.Name: + n_pure: ast.Name* = ast.Name*(value) + if n_pure is not None and n_pure.id is not None: nm_pure = n_pure.id + self.Gen.TypeGenErr.fatal_error(value, f"pure value escapes: `{nm_pure}` is borrowed from the caller; pure results must be owned value copies. Return a newly allocated value instead", "LifetimeError") + return 0 + ret_rk: int = self.Gen.TypeGenResolve.ref_kind(fd.returns) + pi: int = self.Gen.StructGen._ParamIndex(fd, value) + nm_txt: str = "expression" + nm_pre: str = "" + if value.kind() == ast.ASTKind.Name: + n2: ast.Name* = ast.Name*(value) + if n2.id is not None: nm_txt = n2.id + elif value.kind() == ast.ASTKind.UnaryOp: + u2: ast.UnaryOp* = ast.UnaryOp*(value) + if u2.operand is not None and u2.operand.kind() == ast.ASTKind.Name: + n3: ast.Name* = ast.Name*(u2.operand) + if n3.id is not None: + nm_txt = n3.id + nm_pre = "&" + if ret_rk != 0 and pi >= 0: + # Borrow elision: returning a reference parameter borrows the caller's value. + par: ast.Arg* = self._ParamAt(fd, pi) + if par is not None and self.Gen.TypeGenResolve.ref_kind(par.annotation) != 0: return 0 + self.Gen.TypeGenErr.fatal_error(value, f"cannot return `{nm_txt}` from a reference-returning function: parameter '{nm_txt}' is stored by value in this frame, so the result would dangle. Declare the parameter as `&T` / `&mut T`", "LifetimeError") + return 0 + if ret_rk != 0: + self.Gen.TypeGenErr.fatal_error(value, f"cannot return `{nm_pre}{nm_txt}`: it lives in this frame, and a reference result may only borrow a reference parameter or `self` (borrow elision). Allocate the value with `new` and return an owned binding instead", "LifetimeError") + return 0 + self.Gen.TypeGenErr.fatal_error(value, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local and OBS files have no `@life(ret(...))` contracts. Return a heap allocation (`new ...`), a reference parameter, or a value copy", "LifetimeError") + return 0 + + # _ParamAt - parameter node at a 0-based position (None when out of range). + def _ParamAt(self, fd: ast.FunctionDef*, idx: int) -> ast.Arg*: + if fd is None or idx < 0: return None + agr: ast.Arguments* = ast.Arguments*(fd.args) + if agr is None or agr.args is None: return None + if idx >= int(len(agr.args)): return None + pa: ast.AST* = agr.args.get(sizet(idx)) + if pa is None: return None + return ast.Arg*(pa) + 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 @@ -1801,7 +1878,7 @@ novirtual class AssignGenMixin: # take the module-global "object with static storage -> copy the object" branch. if node.kind() == ast.ASTKind.UnaryOp: uo_al: ast.UnaryOp* = ast.UnaryOp*(node) - if uo_al.op == ast.OpKind.AddrOf: return 1 + if uo_al.op == ast.OpKind.AddrOf or uo_al.op == ast.OpKind.AddrOfMut: return 1 if uo_al.op == ast.OpKind.Deref: return 1 return 0 return 0 @@ -1816,7 +1893,7 @@ novirtual class AssignGenMixin: if node is None: return 0 if node.kind() == ast.ASTKind.UnaryOp: uo_ao: ast.UnaryOp* = ast.UnaryOp*(node) - if uo_ao.op == ast.OpKind.AddrOf: return 1 + if uo_ao.op == ast.OpKind.AddrOf or uo_ao.op == ast.OpKind.AddrOfMut: return 1 return 0 return 0 @@ -2717,19 +2794,29 @@ novirtual class AssignGenMixin: rk_decl: int = self._RefKindOf(annot) if rk_decl != 0: self.Gen.VarGen.set_var_ref_kind(trans.SymTab, nm.id, rk_decl) - # A reference must be created from an explicit borrow (`&value` / `&*ptr`) or - # another reference; `b: &U = a` must not silently take the address. + # A reference must be created from an explicit borrow (`&value` / `&mut value` + # / `&*ptr`), another reference, or a call that hands out a reference; + # `b: &U = a` must not silently take the address. ok_ref: int = 0 + src_rk: int = 0 if aa.value is not None: - if aa.value.kind() == ast.ASTKind.UnaryOp: - uo_i: ast.UnaryOp* = ast.UnaryOp*(aa.value) - if uo_i.op == ast.OpKind.AddrOf: ok_ref = 1 + src_rk = self.Gen.TypeGenResolve.ref_expr_kind(aa.value) + if src_rk != 0: + ok_ref = 1 elif aa.value.kind() == ast.ASTKind.Name: nm_i: ast.Name* = ast.Name*(aa.value) - if nm_i.id is not None and self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_i.id) != 0: - ok_ref = 1 + if nm_i.id is not None: + src_rk = self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_i.id) + if src_rk != 0: ok_ref = 1 + elif aa.value.kind() == ast.ASTKind.Call: + # `find()` / `get()` / iterator APIs hand out a borrow of something the + # callee owns; the call result is a reference value. + ok_ref = 1 + src_rk = rk_decl if ok_ref == 0: - self.Gen.TypeGenErr.fatal_error(aa.value, f"a reference must be initialized from a borrow: write `{nm.id}: &T = &value` (or `&ownedPtr`)", "TypeError") + self.Gen.TypeGenErr.fatal_error(aa.value, f"a reference must be initialized from a borrow: write `{nm.id}: &T = &value`, `&mut value`, or call a function returning `&T`", "TypeError") + if rk_decl == 2 and src_rk == 1: + self.Gen.TypeGenErr.fatal_error(aa.value, f"`{nm.id}` is a mutable reference (`&mut T`): create it with `&mut value`, not `&value`", "TypeError") owner_b: str = self._BorrowOwnerName(aa.value) if aa.value is not None and aa.value.kind() == ast.ASTKind.Name: # Copying a reference extends the original borrow to the new binding. @@ -2737,8 +2824,8 @@ novirtual class AssignGenMixin: if nm_r.id is not None: o_ref: str = self.Gen.StmtGen._BorrowOwnerOf(trans, nm_r.id) if o_ref is not None: owner_b = o_ref - if owner_b is not None: - self.Gen.StmtGen._BorrowAdd(trans, owner_b, nm.id) + if owner_b is not None and src_rk != 0: + self.Gen.StmtGen._BorrowAdd(trans, owner_b, nm.id, src_rk, aa.value) # A call returning `own T` transfers ownership to this binding. if GenApi.annotation_is_own(annot) != 0 and aa.value is not None \ and aa.value.kind() == ast.ASTKind.Call: @@ -3624,10 +3711,21 @@ novirtual class AssignGenMixin: self.const_assign_fail(tgt_c, "assignment", nm_c.id, self.Gen.VarGen.var_annot_class_name(ve_c)) # Reassigning a reference drops its old borrow (a fresh borrow is re-registered). if self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_c.id) != 0: + rk_tgt: int = self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_c.id) + src_rk2: int = self.Gen.TypeGenResolve.ref_expr_kind(asgn.value) + if src_rk2 == 0 and asgn.value is not None and asgn.value.kind() == ast.ASTKind.Name: + nm_s2: ast.Name* = ast.Name*(asgn.value) + if nm_s2.id is not None: src_rk2 = self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_s2.id) + if asgn.value is not None and GenApi.is_none_literal(asgn.value) == 0 \ + and self.Gen.VarGen.var_is_nullable(trans.SymTab, nm_c.id) == 0 and src_rk2 == 0 \ + and asgn.value.kind() == ast.ASTKind.Constant: + self.Gen.TypeGenErr.fatal_error(asgn.value, f"`{nm_c.id}` is a reference: assign a borrow (`&value` / `&mut value`), not a value", "TypeError") + if rk_tgt == 2 and src_rk2 == 1: + self.Gen.TypeGenErr.fatal_error(asgn.value, f"`{nm_c.id}` is a mutable reference (`&mut T`): assign `&mut value`, not `&value`", "TypeError") self.Gen.StmtGen._BorrowDrop(trans, nm_c.id) owner_rb: str = self._BorrowOwnerName(asgn.value) - if owner_rb is not None: - self.Gen.StmtGen._BorrowAdd(trans, owner_rb, nm_c.id) + if owner_rb is not None and src_rk2 != 0: + self.Gen.StmtGen._BorrowAdd(trans, owner_rb, nm_c.id, src_rk2, asgn.value) elif tgt_c.kind() == ast.ASTKind.Attribute: at_c: ast.Attribute* = ast.Attribute*(tgt_c) if at_c.attr is None: continue @@ -4036,26 +4134,38 @@ novirtual class AssignGenMixin: fe_rf: GenTypes.FieldEntry* = self.Gen.ExprGenAttr._resolve_attr_field_entry(pool, trans, target) if fe_rf is not None and fe_rf.RefKind != 0 and asgn.value is not None: ref_ok: int = 0 - if GenApi.is_none_literal(asgn.value) != 0: ref_ok = 1 - elif asgn.value.kind() == ast.ASTKind.UnaryOp: - uo_rf: ast.UnaryOp* = ast.UnaryOp*(asgn.value) - if uo_rf.op == ast.OpKind.AddrOf: ref_ok = 1 - elif asgn.value.kind() == ast.ASTKind.Name: - nm_rf: ast.Name* = ast.Name*(asgn.value) - if nm_rf.id is not None and self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_rf.id) != 0: ref_ok = 1 + src_rf: int = 0 + if GenApi.is_none_literal(asgn.value) != 0: + ref_ok = 1 + else: + src_rf = self.Gen.TypeGenResolve.ref_expr_kind(asgn.value) + if src_rf == 0 and asgn.value.kind() == ast.ASTKind.Name: + nm_rf: ast.Name* = ast.Name*(asgn.value) + if nm_rf.id is not None: + src_rf = self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_rf.id) + if src_rf != 0: ref_ok = 1 if ref_ok == 0: self.Gen.TypeGenErr.fatal_error(asgn.value, f"field '.{at_tg.attr}' is a reference: assign a borrow (`&value`), not an owner", "TypeError") + elif fe_rf.RefKind == 2 and src_rf == 1: + self.Gen.TypeGenErr.fatal_error(asgn.value, f"field '.{at_tg.attr}' is a mutable reference (`&mut T`): assign `&mut value`, not `&value`", "TypeError") else: # Reference field: remember the borrow so the owner cannot be del-ed - # while the owning object is in scope. + # while the field path still refers to it (`B.n` borrows `B`). own_bf: str = self._BorrowOwnerName(asgn.value) - brw_bf: str = None - if at_tg.value is not None and at_tg.value.kind() == ast.ASTKind.Name: - brw_bf = (ast.Name*)(at_tg.value).id - if own_bf is not None and brw_bf is not None: - self.Gen.StmtGen._BorrowAdd(trans, own_bf, brw_bf) + if own_bf is None and asgn.value.kind() == ast.ASTKind.Name: + nm_bf: ast.Name* = ast.Name*(asgn.value) + if nm_bf.id is not None: + own_bf = self.Gen.StmtGen._BorrowOwnerOf(trans, nm_bf.id) + if own_bf is not None: + brw_bf: str = self.Gen.StmtGen._BorrowPathName(pool, target) + if brw_bf is None: + self.Gen.TypeGenErr.fatal_error(target, "assign a reference field through a named object (`obj.field = &value`); a reference held by a temporary cannot be tracked", "TypeError") + else: + self.Gen.StmtGen._BorrowAdd(trans, own_bf, brw_bf, src_rf, asgn.value) mlirlite.build_store(builder, store_val, field_ptr, vol_fw) - self._move_out_assigned_source(trans, asgn.value, pool, builder) + own_tgt: int = 0 + if fe_rf is not None and fe_rf.IsOwn != 0: own_tgt = 1 + self._move_out_assigned_source(trans, asgn.value, pool, builder, own_tgt) continue # Guard: field_ptr is None -> build a detailed error message attr_node: ast.Attribute* = ast.Attribute*(target) diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index d825e8c..185a674 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -110,6 +110,10 @@ novirtual class CallGenBase: # -> byte-per-parameter '0'/'1' (1 = the callee takes ownership and releases it). OwnParams: hashtable.HashTable* = None + # --- reference parameter kinds (OBS `&T` / `&mut T`): key = callee symbol name -> + # byte-per-parameter '0' (none) / '1' (read-only) / '2' (mutable). + RefParams: hashtable.HashTable* = None + def InitCallGen(self) -> int: """Initialize the call mixin fields (inlined by subclass constructors since NoVTable does not forward inherited symbols)""" if self is None: return 1 @@ -185,6 +189,42 @@ novirtual class CallGenBase: if i != idx: return 0 return 1 if int(flags[idx]) == 49 else 0 + # _register_ref_params - store per-parameter reference kinds ('0' none / '1' &T / '2' &mut T). + def _register_ref_params(self, name: char*, kinds: char*, count: int) -> int: + """Store per-parameter reference kinds (PermPool-persistent) keyed by callee symbol name.""" + if name is None or kinds is None or count <= 0: return 0 + perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool + if perm is None: return 0 + if self.RefParams is None: + self.RefParams = hashtable.HashTable(perm) + if self.RefParams is None: return 0 + key: char* = perm.alloc(string.strlen(name) + 1) + if key is None: return 0 + string.strcpy(key, name) + val: char* = perm.alloc(sizet(count) + 1) + if val is None: return 0 + string.memcpy(val, kinds, sizet(count)) + val[count] = char(0) + self.RefParams[key] = val + return 1 + + # _param_ref_known - 1 when a reference-kind mask was registered for the callee symbol. + def _param_ref_known(self, name: char*) -> int: + if self.RefParams is None or name is None: return 0 + v: rawptr = self.RefParams.__getitem__(name) + return 0 if v is None else 1 + + # _param_ref_kind - 0 / 1 (`&T`) / 2 (`&mut T`) for parameter idx of the callee symbol. + def _param_ref_kind(self, name: char*, idx: int) -> int: + if self.RefParams is None or name is None or idx < 0: return 0 + v: rawptr = self.RefParams.__getitem__(name) + if v is None: return 0 + kinds: char* = rawptr(v) + i: int = 0 + while i < idx and kinds[i] != char(0): i += 1 + if i != idx: return 0 + return int(kinds[idx]) - 48 + # _register_nullable_params - store per-parameter nullability flags for a callee symbol name. def _register_nullable_params(self, name: char*, flags: char*, count: int) -> int: """Store per-parameter '0'/'1' nullable flags (PermPool-persistent) keyed by callee symbol name.""" diff --git a/App/lib/core/Gen/Call/CallGenLeafKit.vp b/App/lib/core/Gen/Call/CallGenLeafKit.vp index c880ccd..05a2c46 100644 --- a/App/lib/core/Gen/Call/CallGenLeafKit.vp +++ b/App/lib/core/Gen/Call/CallGenLeafKit.vp @@ -1122,7 +1122,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if uo is None or uo.operand is None: return None inner_u: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, uo.operand) if inner_u is None: return None - if uo.op == ast.OpKind.AddrOf: + if uo.op == ast.OpKind.AddrOf or uo.op == ast.OpKind.AddrOfMut: # &x -> 指针(PtrLevel+1);数组取地址退化为元素指针(与实参翻译的数组衰变一致) if inner_u.Kind == TSpec.TS_ARRAY: if inner_u.Elem is None: return None diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index c652156..46647f6 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -399,6 +399,11 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): _dummy_ptr: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) for ai in range(int(can)): if ((esc_mask >> int(ai)) & 1) != 0: + # OBS references are safe to pass: the callee borrows the value, and the + # `&T` return rule keeps the borrow inside the caller's frame. + if self.Gen.CallGenBase._param_ref_known(func_name) != 0 \ + and self.Gen.CallGenBase._param_ref_kind(func_name, int(ai)) != 0: + continue # 泄漏点:把"被调者 + 形参位"交给检查方,确认真要报错后才去采集(同模块才有行号) GenApi.fail_if_stack_addr_escape(cargs.get(ai), "an escaping callee parameter", trans, _dummy_ptr, func_name, int(ai)) # P2 willFree:查被调者的形参失效标注(本文件表 → 跨模块 .vpi 回退)。 diff --git a/App/lib/core/Gen/Class/ClassGenGeneric.vp b/App/lib/core/Gen/Class/ClassGenGeneric.vp index b7e159a..1c31b36 100644 --- a/App/lib/core/Gen/Class/ClassGenGeneric.vp +++ b/App/lib/core/Gen/Class/ClassGenGeneric.vp @@ -577,6 +577,22 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase): oc_g += 1 own_buf_g_[oc_g] = char(0) self.Gen.CallGenBase._register_own_params(spec_name, own_buf_g_, oc_g) + ref_buf_g_: str = pool.alloc(an + 1) + if ref_buf_g_ is not None: + rc_g2: int = 0 + for ai_rg in range(int(an)): + pa_rg: ast.AST* = alist.get(ai_rg) + f_rg: char = char(48) + if pa_rg is not None and pa_rg.kind() == ast.ASTKind.Arg: + arg_rg: ast.Arg* = ast.Arg*(pa_rg) + if arg_rg.annotation is not None: + rk_rg: int = self.Gen.TypeGenResolve.ref_kind(arg_rg.annotation) + if rk_rg == 1: f_rg = char(49) + elif rk_rg == 2: f_rg = char(50) + ref_buf_g_[rc_g2] = f_rg + rc_g2 += 1 + ref_buf_g_[rc_g2] = char(0) + self.Gen.CallGenBase._register_ref_params(spec_name, ref_buf_g_, rc_g2) if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(saved_cur) trans.ModuleSha1 = saved_ms trans._imported_modules = saved_imp diff --git a/App/lib/core/Gen/Class/ClassGenVTable.vp b/App/lib/core/Gen/Class/ClassGenVTable.vp index a8fd423..41bfca0 100644 --- a/App/lib/core/Gen/Class/ClassGenVTable.vp +++ b/App/lib/core/Gen/Class/ClassGenVTable.vp @@ -758,6 +758,24 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): oc_m += 1 own_buf_m[oc_m] = char(0) self.Gen.CallGenBase._register_own_params(mangled_name, own_buf_m, oc_m) + # Per-parameter reference kinds (`&T` / `&mut T`), same indexing. + ref_buf_m: str = pool.alloc(an + 1) + if ref_buf_m is not None: + rc_m: int = 0 + if is_static_m == 0: + ref_buf_m[rc_m] = char(48) + rc_m += 1 + for ai_r in range(int(start_idx), int(an)): + arg_r: ast.Arg* = ast.Arg*(alist.get(ai_r)) + f_r: char = char(48) + if arg_r is not None and arg_r.annotation is not None: + rk_r: int = self.Gen.TypeGenResolve.ref_kind(arg_r.annotation) + if rk_r == 1: f_r = char(49) + elif rk_r == 2: f_r = char(50) + ref_buf_m[rc_m] = f_r + rc_m += 1 + ref_buf_m[rc_m] = char(0) + self.Gen.CallGenBase._register_ref_params(mangled_name, ref_buf_m, rc_m) # *args variadic: set IsVarArg=1 so the signature carries the ... suffix (the body # pipeline allocates the va_list buffer, same as a module-level function). diff --git a/App/lib/core/Gen/Expr/ExprGenOps.vp b/App/lib/core/Gen/Expr/ExprGenOps.vp index 5222a33..eea2249 100644 --- a/App/lib/core/Gen/Expr/ExprGenOps.vp +++ b/App/lib/core/Gen/Expr/ExprGenOps.vp @@ -910,7 +910,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase): # Non-pointer means type inference degraded: fail-fast, never fall back to reading as i8 self.Gen.TypeGenErr.fatal_error(node, "the operand of unary '*' dereference must be a pointer type (type inference degraded)", "TypeError") return None - elif uo.op == ast.OpKind.AddrOf: + elif uo.op == ast.OpKind.AddrOf or uo.op == ast.OpKind.AddrOfMut: # &x -> lvalue address (no load): Name alloca / module global / module function / Attribute field GEP / Subscript element address. if trans is None: return None if uo.operand.kind() == ast.ASTKind.Name: diff --git a/App/lib/core/Gen/Func/FuncGenBase.vp b/App/lib/core/Gen/Func/FuncGenBase.vp index f78cb70..4a203f6 100644 --- a/App/lib/core/Gen/Func/FuncGenBase.vp +++ b/App/lib/core/Gen/Func/FuncGenBase.vp @@ -415,9 +415,8 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): self.Gen.VarGen.define_var(trans.SymTab, arg2.arg, alloca) if GenApi.annotation_is_own(arg2.annotation) != 0: # An `own` parameter takes ownership: the callee releases it at exit. + # The release registration is re-armed after the slife range reset below. self.Gen.VarGen.set_var_own(trans.SymTab, arg2.arg, 1) - sh_own_p: mlirlite.Value* = self.Gen.VarGen._SlifeEnsureOwned(pool, builder, alloca) - if sh_own_p is not None: self.Gen.VarGen._SlifeNoteStore(pool, builder, sh_own_p, 1) if GenApi.strict_obs() != 0 and arg2.annotation is not None: rk_p: int = self.Gen.TypeGenResolve.ref_kind(arg2.annotation) if rk_p != 0: self.Gen.VarGen.set_var_ref_kind(trans.SymTab, arg2.arg, rk_p) @@ -459,6 +458,25 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): trans._vararg_name = vararg_node.arg return 0 + # _arm_own_params - register owned parameters inside the function's slife release range. + def _arm_own_params(self, trans: HT.Translator*, fd: ast.FunctionDef*, pool: memhub.MemBuddy*, + builder: mlirlite.IRBuilder*) -> int: + if trans is None or fd is None or pool is None or builder is None: return 0 + agr: ast.Arguments* = ast.Arguments*(fd.args) + if agr is None or agr.args is None: return 0 + i: int = 0 + while i < int(len(agr.args)): + pa: ast.AST* = agr.args.get(sizet(i)) + i += 1 + if pa is None or pa.kind() != ast.ASTKind.Arg: continue + ar: ast.Arg* = ast.Arg*(pa) + if ar.arg is None or GenApi.annotation_is_own(ar.annotation) == 0: continue + slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, ar.arg) + if slot is None: continue + sh: mlirlite.Value* = self.Gen.VarGen._SlifeEnsureOwned(pool, builder, slot) + if sh is not None: self.Gen.VarGen._SlifeNoteStore(pool, builder, sh, 1) + return 1 + # add_eh_params - append the try/except output params (__eh_msg_out__/__eh_code_out__). # Returns 1 when they were added; the body pipeline then wires them onto trans. def add_eh_params(self, trans: HT.Translator*, fd: ast.FunctionDef*, func: mlirlite.Function*) -> int: @@ -730,6 +748,7 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): trans.CurFuncRetAnnot = fd.returns trans._stop_iter_flag_ptr = stop_iter_flag self.Gen.VarGen.freed_clear() + self.Gen.StmtGen._BorrowClearFunc(trans) trans._break_bb = None trans._continue_bb = None trans._in_switch_case = 0 @@ -761,6 +780,9 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): # M3 owned slife: the registration range starts at this stack depth. old_slife_base: int = self.Gen.VarGen._SlifeReset(pool) trans._in_pure = pure_mode + # `own` parameters are owned by this frame: register them inside the new range so the + # epilogue releases them (with their `own` fields) like any other owned binding. + self._arm_own_params(trans, fd, pool, builder) # The body itself. if body is not None: diff --git a/App/lib/core/Gen/Func/FuncGenDef.vp b/App/lib/core/Gen/Func/FuncGenDef.vp index 3d84850..a5b4be7 100644 --- a/App/lib/core/Gen/Func/FuncGenDef.vp +++ b/App/lib/core/Gen/Func/FuncGenDef.vp @@ -86,6 +86,24 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): own_buf[oc] = char(0) self.Gen.CallGenBase._register_own_params(name, own_buf, oc) stdlib.free(own_buf) + # Per-parameter reference kinds (`&T` / `&mut T`), same emitted-parameter indexing. + ref_buf: bytes = stdlib.malloc(an + 1) + if ref_buf is not None: + rc_n: int = 0 + for i4 in range(int(an)): + arg4: ast.Arg* = ast.Arg*(alist.get(i4)) + if arg4 is None or arg4.arg is None: continue + if arg4.annotation is not None and self.Gen.TypeGenResolve._is_bare_void(arg4.annotation) != 0: continue + f4: char = char(48) + if arg4.annotation is not None: + rk4: int = self.Gen.TypeGenResolve.ref_kind(arg4.annotation) + if rk4 == 1: f4 = char(49) + elif rk4 == 2: f4 = char(50) + ref_buf[rc_n] = f4 + rc_n += 1 + ref_buf[rc_n] = char(0) + self.Gen.CallGenBase._register_ref_params(name, ref_buf, rc_n) + stdlib.free(ref_buf) return 1 # ============================================================ diff --git a/App/lib/core/Gen/Func/FuncGenMixin.vp b/App/lib/core/Gen/Func/FuncGenMixin.vp index 4948803..7c43357 100644 --- a/App/lib/core/Gen/Func/FuncGenMixin.vp +++ b/App/lib/core/Gen/Func/FuncGenMixin.vp @@ -203,6 +203,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase): trans._cur_func_name = func_name # P2 willFree 失效表:函数作用域复位(栈槽地址会被后续函数复用,跨函数保留会误报) self.Gen.VarGen.freed_clear() + self.Gen.StmtGen._BorrowClearFunc(trans) GenApi.clear_scope_names(trans) trans._break_bb = None trans._continue_bb = None diff --git a/App/lib/core/Gen/GenApi.vp b/App/lib/core/Gen/GenApi.vp index 5587668..323d31c 100644 --- a/App/lib/core/Gen/GenApi.vp +++ b/App/lib/core/Gen/GenApi.vp @@ -149,8 +149,19 @@ def annotation_is_nullable(ann: ast.AST*) -> int: return 0 -# annotation_is_own - 1 when the annotation carries the `own` prefix (`OwnT[T]`, also -# under an optional wrapper such as `own T?`). +# _is_own_marker_name - 1 for the ownership spellings `OwnT`, and `Box` / `BoxT` when the +# program has not declared a generic class with that name (a user `Box[T]` keeps its meaning). +def _is_own_marker_name(name: str) -> int: + if name is None: return 0 + if string.strcmp(name, "OwnT") == 0: return 1 + if string.strcmp(name, "Box") != 0 and string.strcmp(name, "BoxT") != 0: return 0 + if _g_gen is None: return 1 + if _g_gen.ClassGenGeneric._find_generic_template(name) is not None: return 0 + return 1 + + +# annotation_is_own - 1 when the annotation carries the `own` prefix (`OwnT[T]`, `Box[T]`, +# also under an optional wrapper such as `own T?`). def annotation_is_own(ann: ast.AST*) -> int: node: ast.AST* = ann depth: int = 0 @@ -160,7 +171,7 @@ def annotation_is_own(ann: ast.AST*) -> int: if sb.value is None or sb.value.kind() != ast.ASTKind.Name: return 0 nm: ast.Name* = ast.Name*(sb.value) if nm.id is None: return 0 - if string.strcmp(nm.id, "OwnT") == 0: return 1 + if _is_own_marker_name(nm.id) != 0: return 1 if string.strcmp(nm.id, "OptionT") == 0 or string.strcmp(nm.id, "NullableT") == 0: node = sb.slice depth += 1 diff --git a/App/lib/core/Gen/MatchGenMixin.vp b/App/lib/core/Gen/MatchGenMixin.vp index 561b337..8ea1e20 100644 --- a/App/lib/core/Gen/MatchGenMixin.vp +++ b/App/lib/core/Gen/MatchGenMixin.vp @@ -428,7 +428,7 @@ novirtual class MatchGenMixin: self.Gen.VarGen.set_var_ref_kind(trans.SymTab, nm_s.id, ref_kind_pat) own_r: str = self._borrow_owner_name(node.subject) if own_r is not None: - self.Gen.StmtGen._BorrowAdd(trans, own_r, nm_s.id) + self.Gen.StmtGen._BorrowAdd(trans, own_r, nm_s.id, ref_kind_pat, sub) if some_body is not None and some_body.children is not None: bi: int = 0 while bi < int(len(some_body.children)): diff --git a/App/lib/core/Gen/Stmt/StmtGenMixin.vp b/App/lib/core/Gen/Stmt/StmtGenMixin.vp index e221b8b..6f4de75 100644 --- a/App/lib/core/Gen/Stmt/StmtGenMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenMixin.vp @@ -12,53 +12,111 @@ import viperlib import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HT import lib.core.CompilerFactory as CompilerFactory +import lib.core.Gen.GenApi as GenApi import lib.Projectrans.Config as Config -# OBS borrow table: (function, owner, borrower) triples for `r: &T = x` / `r: &T = &x`. -# Entries are function-scoped; a borrow counts as alive while its reference variable is still -# declared in the current symbol table. -define BORROW_MAX = 128 +# OBS borrow table: (function, scope, owner, borrower, kind) rows for reference bindings and +# reference fields. `borrower` is a path (`r`, `B.n`). `kind` is 1 for `&T` and 2 for `&mut T`. +# A row is alive while its registration scope is still open and its root binding is declared. +define BORROW_MAX = 1024 _g_borrow_funcs: sizet* = None _g_borrow_owners: str* = None _g_borrow_borrowers: str* = None +_g_borrow_kinds: sizet* = None +_g_borrow_scopes: sizet* = None _g_borrow_count: int = 0 -def _BorrowTableAdd(func: mlirlite.Function*, owner: str, borrower: str) -> int: - global _g_borrow_funcs, _g_borrow_owners, _g_borrow_borrowers, _g_borrow_count + +def _BorrowRoot(name: str) -> str: + """Root binding of a borrower path: `B.n.m` -> `B`.""" + if name is None: return None + n: sizet = string.strlen(name) + i: sizet = 0 + while i < n: + if name[i] == '.': + root: str = CompilerFactory.factory.PermPool.alloc(i + 1) + if root is None: return name + string.strncpy(root, name, i) + root[i] = '\0' + return root + i += 1 + return name + + +def _BorrowTableAdd(func: mlirlite.Function*, scope: GenTypes.Scope*, owner: str, + borrower: str, kind: int) -> int: + """Add/replace a row; 1 = added, 0 = duplicate refreshed, -1 = table full.""" + global _g_borrow_funcs, _g_borrow_owners, _g_borrow_borrowers, _g_borrow_kinds, _g_borrow_scopes, _g_borrow_count if func is None or owner is None or borrower is None: return 0 if _g_borrow_funcs is None: _g_borrow_funcs = sizet*(stdlib.malloc(BORROW_MAX * 8)) _g_borrow_owners = str*(stdlib.malloc(BORROW_MAX * 8)) _g_borrow_borrowers = str*(stdlib.malloc(BORROW_MAX * 8)) + _g_borrow_kinds = sizet*(stdlib.malloc(BORROW_MAX * 8)) + _g_borrow_scopes = sizet*(stdlib.malloc(BORROW_MAX * 8)) if _g_borrow_funcs is None or _g_borrow_owners is None or _g_borrow_borrowers is None: return 0 - # Refresh an existing (func, owner, borrower) entry instead of growing the table. + if _g_borrow_kinds is None or _g_borrow_scopes is None: return 0 + # Refresh an existing (func, borrower) row instead of growing the table. i: int = 0 while i < _g_borrow_count: if _g_borrow_funcs[i] == sizet(func) and _g_borrow_borrowers[i] is not None \ and string.strcmp(_g_borrow_borrowers[i], borrower) == 0: _g_borrow_owners[i] = owner - return 1 + _g_borrow_kinds[i] = sizet(kind) + _g_borrow_scopes[i] = sizet(scope) + return 0 i += 1 - if _g_borrow_count >= BORROW_MAX: return 0 + if _g_borrow_count >= BORROW_MAX: return -1 _g_borrow_funcs[_g_borrow_count] = sizet(func) _g_borrow_owners[_g_borrow_count] = owner _g_borrow_borrowers[_g_borrow_count] = borrower + _g_borrow_kinds[_g_borrow_count] = sizet(kind) + _g_borrow_scopes[_g_borrow_count] = sizet(scope) _g_borrow_count += 1 return 1 -def _BorrowTableDrop(func: mlirlite.Function*, borrower: str) -> int: - """Forget every borrow held by `borrower` (the reference was reassigned).""" + +def _BorrowTableDrop(func: mlirlite.Function*, name: str) -> int: + """Forget every borrow held by `name` or by a path rooted at `name`.""" global _g_borrow_count - if func is None or borrower is None or _g_borrow_funcs is None: return 0 + if func is None or name is None or _g_borrow_funcs is None: return 0 i: int = 0 while i < _g_borrow_count: - if _g_borrow_funcs[i] == sizet(func) and _g_borrow_borrowers[i] is not None \ - and string.strcmp(_g_borrow_borrowers[i], borrower) == 0: + drop: int = 0 + if _g_borrow_funcs[i] == sizet(func) and _g_borrow_borrowers[i] is not None: + if string.strcmp(_g_borrow_borrowers[i], name) == 0: drop = 1 + else: + root: str = _BorrowRoot(_g_borrow_borrowers[i]) + if root is not None and string.strcmp(root, name) == 0: drop = 1 + if drop != 0: j: int = i while j + 1 < _g_borrow_count: _g_borrow_funcs[j] = _g_borrow_funcs[j + 1] _g_borrow_owners[j] = _g_borrow_owners[j + 1] _g_borrow_borrowers[j] = _g_borrow_borrowers[j + 1] + _g_borrow_kinds[j] = _g_borrow_kinds[j + 1] + _g_borrow_scopes[j] = _g_borrow_scopes[j + 1] + j += 1 + _g_borrow_count -= 1 + continue + i += 1 + return 1 + + +def _BorrowClearFunc(func: mlirlite.Function*) -> int: + """Drop every row of a function (its frame is gone, so are its borrows).""" + global _g_borrow_count + if func is None or _g_borrow_funcs is None: return 0 + i: int = 0 + while i < _g_borrow_count: + if _g_borrow_funcs[i] == sizet(func): + j: int = i + while j + 1 < _g_borrow_count: + _g_borrow_funcs[j] = _g_borrow_funcs[j + 1] + _g_borrow_owners[j] = _g_borrow_owners[j + 1] + _g_borrow_borrowers[j] = _g_borrow_borrowers[j + 1] + _g_borrow_kinds[j] = _g_borrow_kinds[j + 1] + _g_borrow_scopes[j] = _g_borrow_scopes[j + 1] j += 1 _g_borrow_count -= 1 continue @@ -75,16 +133,64 @@ import lib.core.Gen.DebugInfoGen as DebugInfoGen novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): """Statement/flow translation Mixin (top level, extends Decl)""" - def _BorrowAdd(self, trans: HT.Translator*, owner: str, borrower: str) -> int: - """Record that reference `borrower` refers to `owner` inside the current function.""" - if trans is None: return 0 - return _BorrowTableAdd(trans._cur_func, owner, borrower) + def _BorrowScopeActive(self, trans: HT.Translator*, scope: GenTypes.Scope*) -> int: + """1 when `scope` is the current scope or one of its ancestors (still open).""" + if trans is None or trans.SymTab is None or scope is None: return 0 + s: GenTypes.Scope* = trans.SymTab.Current + while s is not None: + if uint64(s) == uint64(scope): return 1 + s = s.Parent + return 0 + + def _BorrowEntryLive(self, trans: HT.Translator*, i: int) -> int: + """1 when row i is still alive: its scope is open and its root binding is declared.""" + if trans is None or _g_borrow_funcs is None or i < 0 or i >= _g_borrow_count: return 0 + if _g_borrow_funcs[i] != sizet(trans._cur_func): return 0 + if self._BorrowScopeActive(trans, GenTypes.Scope*(_g_borrow_scopes[i])) == 0: return 0 + b_n: str = _g_borrow_borrowers[i] + if b_n is None: return 0 + root: str = _BorrowRoot(b_n) + if root is None: return 0 + return 1 if self.Gen.VarGen.lookup_var_entry(trans.SymTab, root) is not None else 0 + + def _BorrowAdd(self, trans: HT.Translator*, owner: str, borrower: str, + kind: int = 1, node: ast.AST* = None) -> int: + """Record that reference `borrower` refers to `owner`; reject `&mut` conflicts. + + Minimal exclusivity rule: while an owner is mutably borrowed, no new borrow is allowed; + a mutable borrow is rejected when any read-only borrow is still alive. + """ + if trans is None or owner is None or borrower is None: return 0 + if trans._cur_func is None: return 0 + i: int = 0 + while i < _g_borrow_count: + if _g_borrow_funcs[i] == sizet(trans._cur_func) and _g_borrow_owners[i] is not None \ + and string.strcmp(_g_borrow_owners[i], owner) == 0 \ + and _g_borrow_borrowers[i] is not None \ + and string.strcmp(_g_borrow_borrowers[i], borrower) != 0 \ + and self._BorrowEntryLive(trans, i) != 0: + have: int = int(_g_borrow_kinds[i]) + other: str = _g_borrow_borrowers[i] + if have == 2: + self.Gen.TypeGenErr.fatal_error(node, f"cannot borrow '{owner}': it is already mutably borrowed by '{other}'", "LifetimeError") + elif kind == 2: + self.Gen.TypeGenErr.fatal_error(node, f"cannot borrow '{owner}' as mutable: it is also borrowed as read-only by '{other}'", "LifetimeError") + i += 1 + rc: int = _BorrowTableAdd(trans._cur_func, trans.SymTab.Current, owner, borrower, kind) + if rc < 0: + self.Gen.TypeGenErr.fatal_error(node, f"too many live references in this function (limit {BORROW_MAX}); split it into smaller functions", "LifetimeError") + return 0 def _BorrowDrop(self, trans: HT.Translator*, borrower: str) -> int: - """Forget the borrows held by `borrower` (it was reassigned).""" - if trans is None: return 0 + """Forget the borrows held by `borrower` (or by a path rooted at it).""" + if trans is None or trans._cur_func is None: return 0 return _BorrowTableDrop(trans._cur_func, borrower) + def _BorrowClearFunc(self, trans: HT.Translator*) -> int: + """Drop every borrow row of the current function (called when its frame ends).""" + if trans is None or trans._cur_func is None: return 0 + return _BorrowClearFunc(trans._cur_func) + def _BorrowOwnerOf(self, trans: HT.Translator*, borrower: str) -> str: """Owner borrowed by a reference variable (None when it holds no registered borrow).""" global _g_borrow_count @@ -92,26 +198,44 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): i: int = 0 while i < _g_borrow_count: if _g_borrow_funcs[i] == sizet(trans._cur_func) and _g_borrow_borrowers[i] is not None \ - and string.strcmp(_g_borrow_borrowers[i], borrower) == 0: + and string.strcmp(_g_borrow_borrowers[i], borrower) == 0 \ + and self._BorrowEntryLive(trans, i) != 0: return _g_borrow_owners[i] i += 1 return None def _BorrowLiveBorrower(self, trans: HT.Translator*, owner: str) -> str: - """Name of a still-declared reference that borrows `owner` (None when none).""" + """Name of a still-live reference (or reference field path) that borrows `owner`.""" global _g_borrow_count if trans is None or owner is None or _g_borrow_funcs is None: return None i: int = 0 while i < _g_borrow_count: if _g_borrow_funcs[i] == sizet(trans._cur_func): own_n: str = _g_borrow_owners[i] - if own_n is not None and string.strcmp(own_n, owner) == 0: - b_n: str = _g_borrow_borrowers[i] - if b_n is not None and self.Gen.VarGen.lookup_var_entry(trans.SymTab, b_n) is not None: - return b_n + if own_n is not None and string.strcmp(own_n, owner) == 0 \ + and self._BorrowEntryLive(trans, i) != 0: + return _g_borrow_borrowers[i] i += 1 return None + def _BorrowPathName(self, pool: memhub.MemBuddy*, expr: ast.AST*, depth: int = 0) -> str: + """Dotted name of a variable/field chain (`B`, `B.n`, `o.h`); None for other expressions.""" + if pool is None or expr is None or depth > 8: return None + if expr.kind() == ast.ASTKind.Name: + nm_bp: ast.Name* = ast.Name*(expr) + return nm_bp.id + if expr.kind() == ast.ASTKind.Attribute: + at_bp: ast.Attribute* = ast.Attribute*(expr) + if at_bp.attr is None: return None + base_bp: str = self._BorrowPathName(pool, at_bp.value, depth + 1) + if base_bp is None: return None + n_bp: sizet = string.strlen(base_bp) + string.strlen(at_bp.attr) + 2 + out_bp: str = pool.alloc(n_bp) + if out_bp is None: return None + viperlib.snprintf(out_bp, n_bp, "%s.%s", base_bp, at_bp.attr) + return out_bp + return None + def translate_stmt(self, trans: HT.Translator*, node: ast.AST*) -> int: """Translate a single statement with a depth guard; returns the number of new variables""" if trans is not None: @@ -259,6 +383,8 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): 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) + # The frame slot is gone: the borrows it held (including reference fields) die too. + self._BorrowDrop(trans, nm_del.id) # del obj.attr -> property deleter; falls back to __del__ (matches TPC) elif tgt.kind() == ast.ASTKind.Attribute: del_at: ast.Attribute* = ast.Attribute*(tgt) @@ -332,12 +458,55 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): # 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). + # _has_own_fields - 1 when the struct type has at least one `own T` field. + def _has_own_fields(self, struct_ty: mlirlite.LLVMType*) -> int: + if struct_ty is None: return 0 + entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(struct_ty) + if entry is None: return 0 + fi: int = 0 + while fi < entry.FieldCount: + fe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fi) + fi += 1 + if fe is not None and fe.IsOwn != 0: return 1 + return 0 + # _release_own_fields - free the `own` fields of a heap object before the object itself is freed. def _release_own_fields(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, cur: mlirlite.Value*, struct_ty: mlirlite.LLVMType*, depth: int = 0) -> int: + """Release the `own` fields of a non-null heap object.""" if pool is None or builder is None or mod is None or cur is None or struct_ty is None: return 0 if depth > 8: return 0 + # Null guard: a null pointer owns no fields (see the recursion below). + func: mlirlite.Function* = builder.Func + if func is None: return 0 + ptr_ty: mlirlite.LLVMType* = cur.Ty + if ptr_ty is None: ptr_ty = mlirlite.Ptr(pool, struct_ty) + nulp: mlirlite.Value* = mlirlite.ConstNull(pool, ptr_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 + 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, "ownrel.body.%d", cnt) + viperlib.snprintf(b2, 32, "ownrel.ret.%d", cnt) + body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, b1) + ret_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, b2) + if body_bb is None or ret_bb is None: return 0 + mlirlite.build_cond_br(builder, cond, body_bb, ret_bb) + mlirlite.position_at_end(builder, body_bb) + self._release_own_fields_body(pool, builder, mod, trans, cur, struct_ty, depth) + mlirlite.build_br(builder, ret_bb) + mlirlite.position_at_end(builder, ret_bb) + return 1 + + def _release_own_fields_body(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, + mod: mlirlite.LLVMModule*, trans: HT.Translator*, + cur: mlirlite.Value*, struct_ty: mlirlite.LLVMType*, depth: int) -> int: + if depth > 8: return 0 entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(struct_ty) if entry is None: return 0 if self.Gen.CallGenBase.find_func_in_module(mod, "free") is None: return 0 @@ -347,15 +516,31 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): fe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fi) fi += 1 if fe is None or fe.IsOwn == 0: continue - if fe.Ty is None or fe.Ty.Pointee is None: continue + if fe.Ty is None: continue if self.Gen.ExprGenBase.is_ptr_type(fe.Ty) == 0: continue + # Resolve the pointee class: the field's LLVM type when it is a real struct, else the + # annotation's class name (a forward-referenced `own B?` degrades to an opaque pointer). + inner: mlirlite.LLVMType* = None + if fe.Ty.Pointee is not None and fe.Ty.Pointee.kind() == mlirlite.LLVMType.Struct: + inner = fe.Ty.Pointee + else: + cn_ow: str = None + if fe.Spec is not None: cn_ow = fe.Spec.SourceName + if cn_ow is not None: + ent_ow: GenTypes.StructEntry* = GenApi.find_struct(cn_ow) + if ent_ow is not None and ent_ow.Ty is not None: inner = ent_ow.Ty + if inner is None: continue fp: mlirlite.Value* = mlirlite.build_gep_struct(builder, struct_ty, fe.Ty, cur, fe.Index) if fp is None: continue fv: mlirlite.Value* = mlirlite.build_load(builder, fe.Ty, fp) if fv is None: continue - inner: mlirlite.LLVMType* = fe.Ty.Pointee - self._emit_del_dtor(pool, builder, mod, trans, fv, fe.Ty, inner) - self._release_own_fields(pool, builder, mod, trans, fv, inner, depth + 1) + want_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, inner) + pv: mlirlite.Value* = fv + if fv.Ty is not None and uint64(fv.Ty) != uint64(want_ty): + pv = mlirlite.build_bitcast(builder, fv, want_ty) + if pv is None: continue + self._emit_del_dtor(pool, builder, mod, trans, pv, want_ty, inner) + self._release_own_fields(pool, builder, mod, trans, pv, inner, depth + 1) arg_f: mlirlite.Value* = fv if fv.Ty is not None and uint64(fv.Ty) != uint64(i8p): arg_f = mlirlite.build_bitcast(builder, fv, i8p) diff --git a/App/lib/core/Gen/TranslatorGenMixin.vp b/App/lib/core/Gen/TranslatorGenMixin.vp index ad65aff..aa0c73b 100644 --- a/App/lib/core/Gen/TranslatorGenMixin.vp +++ b/App/lib/core/Gen/TranslatorGenMixin.vp @@ -632,7 +632,7 @@ novirtual class TranslatorGenMixin: # (UnaryOp(AddrOf, Name)) is a symbol-address initializer instead and must not be # fed to the integer constant extractor. uop_g: ast.UnaryOp* = ast.UnaryOp*(aa.value) - if uop_g is not None and uop_g.op == ast.OpKind.AddrOf: + if uop_g is not None and (uop_g.op == ast.OpKind.AddrOf or uop_g.op == ast.OpKind.AddrOfMut): init_sym_node = uop_g.operand init_kind = 5 else: @@ -686,7 +686,7 @@ novirtual class TranslatorGenMixin: # Negative literal (same as the AnnAssign branch above): UnaryOp(USub, Constant). # `= &global` is a symbol-address initializer instead (see the AnnAssign branch). uop_g2: ast.UnaryOp* = ast.UnaryOp*(asgn.value) - if uop_g2 is not None and uop_g2.op == ast.OpKind.AddrOf: + if uop_g2 is not None and (uop_g2.op == ast.OpKind.AddrOf or uop_g2.op == ast.OpKind.AddrOfMut): init_sym_node = uop_g2.operand init_kind = 5 else: diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index 3f2dbe5..4394548 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -1397,6 +1397,13 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): continue return 0 return 0 + def ref_expr_kind(self, node: ast.AST*) -> int: + """Kind of an explicit borrow expression: 0=none / 1=`&x` / 2=`&mut x`.""" + if node is None or node.kind() != ast.ASTKind.UnaryOp: return 0 + uo_re: ast.UnaryOp* = ast.UnaryOp*(node) + if uo_re.op == ast.OpKind.AddrOf: return 1 + if uo_re.op == ast.OpKind.AddrOfMut: return 2 + return 0 def reject_ref_marker_position(self, node: ast.AST*, imported_modules: str, where: str) -> int: """Fail fast when a reference marker is used where it is not allowed.""" mode: int = self._ref_marker_name_mode(node) diff --git a/App/lib/core/Gen/VarGenMixin.vp b/App/lib/core/Gen/VarGenMixin.vp index 8444e86..e580454 100644 --- a/App/lib/core/Gen/VarGenMixin.vp +++ b/App/lib/core/Gen/VarGenMixin.vp @@ -779,6 +779,47 @@ novirtual class VarGenMixin: mlirlite.position_at_end(builder, merge_bb) return 1 + def _SlifeEmitOwnFieldRelease(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, + mod: mlirlite.LLVMModule*, trans: HT.Translator*, + alloca: mlirlite.Value*, sh: mlirlite.Value*) -> int: + """Release the `own` fields of a compiler-managed object before the object is freed. + + Guarded by the slot's ownership shadow, exactly like the `__del__`/free path. + """ + 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 + if self.Gen.ExprGenBase.is_ptr_type(elem) == 0: return 0 + struct_ty: mlirlite.LLVMType* = elem.Pointee + if struct_ty is None or struct_ty.kind() != mlirlite.LLVMType.Struct: return 0 + if self.Gen.StmtGen._has_own_fields(struct_ty) == 0: 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, elem, alloca) + if cur is None: return 0 + 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.ownrel.%d", cnt) + rel_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf) + viperlib.snprintf(name_buf, 32, "slife.ownrel.end.%d", cnt) + merge_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf) + if rel_bb is None or merge_bb is None: return 0 + mlirlite.build_cond_br(builder, cond, rel_bb, merge_bb) + mlirlite.position_at_end(builder, rel_bb) + self.Gen.StmtGen._release_own_fields(pool, builder, mod, trans, cur, struct_ty) + mlirlite.build_br(builder, merge_bb) + mlirlite.position_at_end(builder, merge_bb) + return 1 + def _SlifeEmitPureStrFree(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, alloca: mlirlite.Value*, sh: mlirlite.Value*) -> int: """Free a pure_str slot's owned payload through _pure_free, guarded by its shadow.""" if pool is None or builder is None or alloca is None or sh is None: return 0 @@ -832,6 +873,8 @@ novirtual class VarGenMixin: return self._SlifeEmitPureStrFree(pool, builder, mod, trans, alloca, sh) # §3.6:先析构,再 free self._SlifeEmitDtorBranch(pool, builder, mod, trans, alloca, sh) + # `own` fields belong to this object: release them before the block itself is freed. + self._SlifeEmitOwnFieldRelease(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)