feat(obs): borrow exclusivity, Box ownership and full own release
Borrows carry a kind: while a value is mutably borrowed no new borrow starts, a mutable borrow is rejected while read-only borrows are alive, and a `&mut T` slot only accepts `&mut value`. Reference fields record their path (`B.n`) as the borrower, so `del` is blocked by nested and self borrows and released on `del`/reassign of the root. The table grows to 1024 rows and fails fast instead of silently dropping rows, and each function clears its rows at entry. `Box[T]`/`BoxT[T]` count as ownership spellings; own fields are released at scope exit (not only on `del`), for forward-declared and self-referential types too, behind a null guard. A move is only allowed into an `own` slot. Reference results use borrow elision (a reference parameter or `self`) instead of the `@life` advice, reference parameters no longer trip the stack-escape check, and `&` on an owned pointer yields the heap pointer (LIFE_HEAP).
This commit is contained in:
1 parent
88b3724303
commit
3764afbaa6
16 files changed
+549
-73
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 回退)。
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
# ============================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user