feat(gen): 生命期标签(LifeTag)与借用传播(M2.1)
- GenTypes:新增 define LIFE_VALUE/FRAME/HEAP/STATIC/UNKNOWN 与 VarEntry.Life。 - VarGenMixin:set_var_life/get_var_life/life_meet;add_var 初始化 Life=UNKNOWN。 - AssignGenMixin:life_of_expr(&local、数组退让、变量 Life 读取)、propagate_life(赋值传播)、fail_if_life_escapes(汇点检查,LifetimeError);在 AnnAssign 与单 Name Assign 处挂传播。 - GenApi.fail_if_stack_addr_escape 改为薄壳并委托 fail_if_life_escapes;原 (a)(b) 语法判据并入 life_of_expr。于是 s: str = l; return s 被拦截。
This commit is contained in:
1 parent
7070bc1608
commit
ec47401151
4 files changed
+149
-29
No files matched your search
@@ -583,6 +583,97 @@ novirtual class AssignGenMixin:
|
||||
if buf_a[0] == 0 or buf_b[0] == 0: return 0
|
||||
return 1 if string.strcmp(buf_a, buf_b) == 0 else 0
|
||||
|
||||
# ============================================================
|
||||
# 生命期(LifeTag)判定 / 传播 / 汇点检查(架构见 Debugger/ownership-and-new.md §11)
|
||||
# 标签常量见 GenTypes define LIFE_*;有序,越小越"短命",汇合取 min。
|
||||
# ============================================================
|
||||
|
||||
# life_of_expr - 求表达式当前值的生命期标签。
|
||||
# target_ty 参与判定:裸本帧数组名在**指针**目标位置 = 数组退让(交出本帧栈地址),
|
||||
# 在同型聚合目标位置 = 按值拷贝(LIFE_VALUE)。
|
||||
def life_of_expr(self, trans: HT.Translator*, node: ast.AST*, target_ty: mlirlite.LLVMType*) -> int:
|
||||
"""表达式的 LifeTag;无法判定时 LIFE_UNKNOWN(保守放行)"""
|
||||
if node is None or trans is None: return GenTypes.LIFE_VALUE
|
||||
k: int = node.kind()
|
||||
if k == ast.ASTKind.Constant:
|
||||
cn: ast.Constant* = ast.Constant*(node)
|
||||
if cn is None: return GenTypes.LIFE_VALUE
|
||||
if cn.const_kind == ast.CONST_STR: return GenTypes.LIFE_STATIC
|
||||
if cn.const_kind == ast.CONST_NONE: return GenTypes.LIFE_UNKNOWN
|
||||
return GenTypes.LIFE_VALUE
|
||||
if k == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(node)
|
||||
if nm.id is None: return GenTypes.LIFE_UNKNOWN
|
||||
if GenApi.is_global_name(trans, nm.id) != 0: return GenTypes.LIFE_STATIC
|
||||
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return GenTypes.LIFE_UNKNOWN
|
||||
v: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||||
if v is None: return GenTypes.LIFE_STATIC
|
||||
if v.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(v.Ty) != 0:
|
||||
inner: mlirlite.LLVMType* = v.Ty.Pointee
|
||||
if inner is not None and inner.kind() == mlirlite.LLVMType.Array:
|
||||
# 本帧局部数组名:退让到指针目标交出栈地址;同型聚合目标是值拷贝
|
||||
if target_ty is not None and self.Gen.ExprGenBase.is_ptr_type(target_ty) != 0: return GenTypes.LIFE_FRAME
|
||||
return GenTypes.LIFE_VALUE
|
||||
# 指针/标量变量:读登记的生命期(默认 UNKNOWN)
|
||||
return self.Gen.VarGen.get_var_life(trans.SymTab, nm.id)
|
||||
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:
|
||||
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
|
||||
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
|
||||
return GenTypes.LIFE_STATIC
|
||||
return GenTypes.LIFE_UNKNOWN
|
||||
if k == ast.ASTKind.Call:
|
||||
# 分配类调用 → 堆;其余调用 → 未知(函数摘要落地前保守放行)
|
||||
cl: ast.Call* = ast.Call*(node)
|
||||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||||
if at.attr is not None:
|
||||
if (string.strcmp(at.attr, "alloc") == 0 or string.strcmp(at.attr, "alloc_buf") == 0 or string.strcmp(at.attr, "calloc") == 0 or string.strcmp(at.attr, "realloc") == 0): return GenTypes.LIFE_HEAP
|
||||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
|
||||
fnm: ast.Name* = ast.Name*(cl.func)
|
||||
if fnm.id is not None and string.strcmp(fnm.id, "malloc") == 0: return GenTypes.LIFE_HEAP
|
||||
return GenTypes.LIFE_UNKNOWN
|
||||
return GenTypes.LIFE_UNKNOWN
|
||||
|
||||
# propagate_life - 把 RHS 的生命期写到赋值目标(仅 Name 目标;全局目标走汇点检查)。
|
||||
def propagate_life(self, trans: HT.Translator*, target_node: ast.AST*, rhs_node: ast.AST*, target_ty: mlirlite.LLVMType*) -> int:
|
||||
"""Propagate the RHS life tag onto a Name target; 0=done / 1=skipped"""
|
||||
if trans is None or target_node is None: return 1
|
||||
if target_node.kind() != ast.ASTKind.Name: return 1
|
||||
tnm: ast.Name* = ast.Name*(target_node)
|
||||
if tnm.id is None: return 1
|
||||
if GenApi.is_global_name(trans, tnm.id) != 0: return 1
|
||||
if GenApi.is_nonlocal_name(trans, tnm.id) != 0: return 1
|
||||
life: int = self.life_of_expr(trans, rhs_node, target_ty)
|
||||
self.Gen.VarGen.set_var_life(trans.SymTab, tnm.id, life)
|
||||
return 0
|
||||
|
||||
# fail_if_life_escapes - 汇点检查:被写出表达式的生命期为 LIFE_FRAME → LifetimeError。
|
||||
# 这是 GenApi.fail_if_stack_addr_escape 的唯一实现(语法判据已并入 life_of_expr)。
|
||||
def fail_if_life_escapes(self, node: ast.AST*, where: str, trans: HT.Translator*, target_ty: mlirlite.LLVMType*) -> int:
|
||||
"""Fail fast (LifetimeError) when the written-out value is frame-lifetime storage"""
|
||||
if node is None or trans is None: return 0
|
||||
if self.life_of_expr(trans, node, target_ty) != GenTypes.LIFE_FRAME: return 0
|
||||
# 报文里给出名字(&x / x),便于定位;前缀单独存放,避免 str 相加(str 是 i8*,无 '+' 语义)
|
||||
nm_txt: str = "expression"
|
||||
nm_pre: str = ""
|
||||
if node.kind() == ast.ASTKind.Name:
|
||||
n2: ast.Name* = ast.Name*(node)
|
||||
if n2.id is not None: nm_txt = n2.id
|
||||
elif node.kind() == ast.ASTKind.UnaryOp:
|
||||
u2: ast.UnaryOp* = ast.UnaryOp*(node)
|
||||
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 = "&"
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"stack address escapes: `{nm_pre}{nm_txt}` refers to storage of the current frame (local variable/array or parameter); using it at {where} hands out a stack address, which becomes invalid once the function returns (dangling pointer). Return the value itself (a value copy) or allocate on the heap (`new heap hlife ...`).", "LifetimeError")
|
||||
return 0
|
||||
|
||||
def _is_addrlike_call(self, node: ast.AST*) -> int:
|
||||
"""Return 1 if the rhs is an explicit pointer expression (c.Addr/c.Deref/c.Cast/c.Ptr call, or new-syntax `&x` / `*p`)."""
|
||||
if node is None: return 0
|
||||
@@ -1787,6 +1878,12 @@ novirtual class AssignGenMixin:
|
||||
# FakeDuck: auto-inject requires fields (e.g. __mbuddy__) for the with context
|
||||
self.Gen.CallGenFd._inject_requires_fields(trans, pool, builder, mod, fd_meta_alloca, "_str")
|
||||
|
||||
# 生命期传播(本地变量):`s: str = l` → Life(s) = Life(l)(数组退让即借用)
|
||||
if aa.value is not None and nm.id is not None and alloca is not None:
|
||||
tgt_life_ty: mlirlite.LLVMType* = None
|
||||
if alloca.Ty is not None: tgt_life_ty = alloca.Ty.Pointee
|
||||
self.propagate_life(trans, target, aa.value, tgt_life_ty)
|
||||
|
||||
return 0
|
||||
|
||||
# translate_assign - handle an Assign statement; returns the number of new variables
|
||||
@@ -1919,6 +2016,11 @@ novirtual class AssignGenMixin:
|
||||
else: VLogger.error("assignment rhs is None (asgn.value is empty)", "ASGN")
|
||||
return 0
|
||||
|
||||
# 生命期传播(单 Name 目标):`s = l` → Life(s) = Life(l)
|
||||
if len(targets) == 1:
|
||||
t0_sl: ast.AST* = targets.get(0)
|
||||
self.propagate_life(trans, t0_sl, asgn.value, asg_target_ty)
|
||||
|
||||
new_vars: int = 0
|
||||
tn: sizet = len(targets)
|
||||
for ti in range(tn):
|
||||
|
||||
@@ -458,37 +458,16 @@ def escape_fstring_copy(src: str) -> str:
|
||||
|
||||
|
||||
# Stack-escape rule: the address of a stack object must not outlive the current frame.
|
||||
# Two forms hand out this frame's stack address:
|
||||
# (a) `&name` — the explicit address of a local variable / parameter;
|
||||
# (b) `name` — a bare local ARRAY used where a pointer is expected (array decay,
|
||||
# e.g. `def f() -> char*: ...; return arr`).
|
||||
# The judgement is now the unified **life-tag** mechanism (GenTypes define LIFE_*); the
|
||||
# implementation lives in AssignGen.fail_if_life_escapes. It covers:
|
||||
# (a) `&name` — explicit address of a local variable / parameter;
|
||||
# (b) `name` — a bare local ARRAY used where a pointer is expected (array decay);
|
||||
# (c) propagated borrows — `s: str = l` gives Life(s)=FRAME, so `return s` is caught.
|
||||
# A bare name with a matching aggregate target is a value copy, never an escape.
|
||||
def fail_if_stack_addr_escape(node: ast.AST*, where: str, trans: HT.Translator*, target_ty: mlirlite.LLVMType* = None) -> int:
|
||||
"""Fail fast when the address of a stack object escapes the current frame."""
|
||||
"""Fail fast (LifetimeError) when the address of a stack object escapes the frame."""
|
||||
if node is None or trans is None or _g_gen is None: return 0
|
||||
nm_sa: str = None
|
||||
if node.kind() == ast.ASTKind.UnaryOp:
|
||||
uo_sa: ast.UnaryOp* = ast.UnaryOp*(node)
|
||||
if uo_sa.op == ast.OpKind.AddrOf and uo_sa.operand is not None and uo_sa.operand.kind() == ast.ASTKind.Name:
|
||||
nm0_sa: ast.Name* = ast.Name*(uo_sa.operand)
|
||||
if nm0_sa.id is not None: nm_sa = nm0_sa.id
|
||||
elif node.kind() == ast.ASTKind.Name and target_ty is not None:
|
||||
# 数组退让(array decay):本帧局部数组名出现在指针目标位置 ⇒ 交出首元素地址(栈地址)
|
||||
if _g_gen.ExprGenBase.is_ptr_type(target_ty) != 0:
|
||||
nm1_sa: ast.Name* = ast.Name*(node)
|
||||
if nm1_sa.id is not None:
|
||||
v1_sa: mlirlite.Value* = _g_gen.VarGen.lookup_var(trans.SymTab, nm1_sa.id)
|
||||
if v1_sa is not None and v1_sa.Ty is not None and _g_gen.ExprGenBase.is_ptr_type(v1_sa.Ty) != 0:
|
||||
inner1_sa: mlirlite.LLVMType* = v1_sa.Ty.Pointee
|
||||
if inner1_sa is not None and inner1_sa.kind() == mlirlite.LLVMType.Array: nm_sa = nm1_sa.id
|
||||
if nm_sa is None: return 0
|
||||
# Nonlocal captures are heap-promoted and may outlive the frame.
|
||||
if is_nonlocal_name(trans, nm_sa) != 0: return 0
|
||||
# Only a local alloca of this frame is stack storage; module globals / function names
|
||||
# do not resolve in the local scope and are allowed to escape.
|
||||
if _g_gen.VarGen.lookup_var(trans.SymTab, nm_sa) is None: return 0
|
||||
_g_gen.TypeGenErr.fatal_error(node, f"stack address escapes: `{nm_sa}` refers to storage of the current frame (local variable/array or parameter); using it at {where} hands out a stack address, which becomes invalid once the function returns (dangling pointer). Return the value itself (a value copy) or allocate on the heap (`new heap hlife ...`).", "LifetimeError")
|
||||
return 0
|
||||
return _g_gen.AssignGen.fail_if_life_escapes(node, where, trans, target_ty)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -48,6 +48,15 @@ class TraceFrame:
|
||||
col_end: int
|
||||
|
||||
|
||||
# ---- 生命期标签(LifeTag):有序,越小越"短命" ----
|
||||
# 逃逸检查只在 LIFE_FRAME 上触发;控制流汇合取更短者(Meet = min)。
|
||||
# 详见 Debugger/ownership-and-new.md §11。
|
||||
define LIFE_VALUE = 0 # 值语义(标量/聚合按值拷贝),不参与逃逸
|
||||
define LIFE_FRAME = 1 # 本帧栈存储:局部变量地址、数组退让、形参副本地址
|
||||
define LIFE_HEAP = 2 # 堆,hlife(释放靠手动/区域)
|
||||
define LIFE_STATIC = 3 # 模块级全局 / 字符串常量 / 函数地址
|
||||
define LIFE_UNKNOWN = 4 # 未知(extern 返回等),保守放行
|
||||
|
||||
# ============================================================
|
||||
# VarEntry - variable table entry (formerly HandlesVar.VarEntry)
|
||||
class VarEntry:
|
||||
@@ -67,7 +76,9 @@ class VarEntry:
|
||||
# 1=this variable is a const object (annotation has t.CConst and the type is not a
|
||||
# pointer): assignment to it anywhere in scope fails fast. Pointer CConst only limits the pointee.
|
||||
IsConst: int
|
||||
|
||||
# 生命期标签(LifeTag,见文件头 define LIFE_*):该变量**当前值**的生命期。
|
||||
# 追加在末尾以保持既有字段偏移不变。默认 LIFE_UNKNOWN(未登记 = 保守放行)。
|
||||
Life: int
|
||||
|
||||
# ============================================================
|
||||
# Scope - scope node (formerly HandlesVar.Scope)
|
||||
|
||||
@@ -178,6 +178,32 @@ novirtual class VarGenMixin:
|
||||
if entry is None: return 0
|
||||
return entry.IsConst
|
||||
|
||||
# ============================================================
|
||||
# 生命期标签读写(LifeTag,见 GenTypes define LIFE_*;架构见 ownership-and-new.md §11)
|
||||
# ============================================================
|
||||
|
||||
# set_var_life - write a variable's current-value life tag.
|
||||
def set_var_life(self, symtab: GenTypes.SymbolTable*, name: str, life: int) -> int:
|
||||
"""Set a variable's LifeTag (VarEntry.Life), return 0=success / 1=failure"""
|
||||
if symtab is None or name is None: return 1
|
||||
entry: GenTypes.VarEntry* = self.lookup_var_entry(symtab, name)
|
||||
if entry is None: return 1
|
||||
entry.Life = life
|
||||
return 0
|
||||
|
||||
# get_var_life - read a variable's life tag; LIFE_UNKNOWN when unregistered (conservative).
|
||||
def get_var_life(self, symtab: GenTypes.SymbolTable*, name: str) -> int:
|
||||
"""A variable's LifeTag; LIFE_UNKNOWN when the variable is unknown"""
|
||||
if symtab is None or name is None: return GenTypes.LIFE_UNKNOWN
|
||||
entry: GenTypes.VarEntry* = self.lookup_var_entry(symtab, name)
|
||||
if entry is None: return GenTypes.LIFE_UNKNOWN
|
||||
return entry.Life
|
||||
|
||||
# life_meet - control-flow merge: the shorter life wins (smaller tag = shorter).
|
||||
def life_meet(self, a: int, b: int) -> int:
|
||||
"""Meet of two life tags (min); used where branches join"""
|
||||
return a if a < b else b
|
||||
|
||||
# add_var - add a variable to the variable table
|
||||
# ============================================================
|
||||
def add_var(self, vars_ptr: GenTypes.VarEntry*, var_count: int, name: str, alloca: mlirlite.Value*) -> int:
|
||||
@@ -199,6 +225,8 @@ novirtual class VarGenMixin:
|
||||
# Explicitly zero the const flag (do not rely on the entry's zero initialization): set_var_const
|
||||
# sets it to 1 only for real const objects, so zeroing avoids false positives.
|
||||
entry.IsConst = 0
|
||||
# 生命期标签显式初始化:未登记 = LIFE_UNKNOWN(保守放行)。
|
||||
entry.Life = GenTypes.LIFE_UNKNOWN
|
||||
# [S3 dual write] Value-type Spec (alloca.Ty is Ptr(value type), so take its pointee)
|
||||
entry.Spec = self._spec_of_var_alloca(alloca)
|
||||
return 0
|
||||
|
||||
Reference in new issue
Block a user