feat(gen): P2 生命期标注 @life(willFree(...)) 驱动释放后使用与二次释放检查
标注面:@life(willFree(<形参名|0起序号>)),兼容叠加式 @willFree(...),可混用累加;序号按形参表下标、self/cls 计入。GenApi.compute_willfree_mask 解析标注并对非法标注 fail-fast。本文件表 StructGenMixin.WfTable + 跨模块 .vpi 行尾 wf=<mask>(Converter 写 / VpiReader 读 / CallGenBase 查)。调用点在实参全部翻译之后把对应实参变量槽置 VarEntry.Freed;其后的读取路径报 LifetimeError。重新赋值清除失效态。
This commit is contained in:
1 parent
9ff5a3a03e
commit
97313e9e55
10 files changed
+374
-3
No files matched your search
@@ -517,13 +517,23 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*,
|
||||
# v3 跨模块逃逸摘要:把形参逃逸掩码与返回借用位写成**同一行**的行尾注释,供 VpiReader
|
||||
# 解析后在别的模块的调用点查询(跨模块摘要表按文件重置,只能靠 vpi 传递)。
|
||||
# 只在真有逃逸/返回借用时写,保持 vpi 干净:无标记 = 无摘要 = 调用点保守放行。
|
||||
# P2 willFree:同行走 `# ... wf=<mask>`(第 k 位=第 k 个形参在本调用后失效),
|
||||
# 纯声明式、不依赖逃逸扫描的 scratch,所以单独算、独立决定是否落盘。
|
||||
wf_mask_c: int = GenApi.compute_willfree_mask(fn)
|
||||
esc_v_c: int = 0
|
||||
ret_v_c: int = 0
|
||||
if _PyiEnsureEscScratch() != 0:
|
||||
esc_box_c: sizet* = sizet*(uint64(_g_pyi_esc_scratch) + 520)
|
||||
if GenApi.compute_escape_summary(None, fn, _g_pyi_esc_scratch, esc_box_c) == 0:
|
||||
if esc_box_c[0] != 0 or esc_box_c[1] != 0:
|
||||
tmp_c: bytes = stdlib.malloc(64)
|
||||
esc_v_c = int(esc_box_c[0])
|
||||
ret_v_c = int(esc_box_c[1])
|
||||
if esc_v_c != 0 or ret_v_c != 0 or wf_mask_c != 0:
|
||||
tmp_c: bytes = stdlib.malloc(96)
|
||||
if tmp_c is not None:
|
||||
viperlib.snprintf(tmp_c, 64, " # esc=%d ret=%d", int(esc_box_c[0]), int(esc_box_c[1]))
|
||||
if esc_v_c != 0 or ret_v_c != 0:
|
||||
if wf_mask_c != 0: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d wf=%d", esc_v_c, ret_v_c, wf_mask_c)
|
||||
else: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d", esc_v_c, ret_v_c)
|
||||
else: viperlib.snprintf(tmp_c, 96, " # wf=%d", wf_mask_c)
|
||||
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
|
||||
stdlib.free(tmp_c)
|
||||
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
||||
|
||||
@@ -116,6 +116,15 @@ define VPI_IDX_FNV_MASK = 4294967295
|
||||
define VPI_ESC_MAX = 8192
|
||||
define VPI_ESC_KEY_LEN = 224
|
||||
|
||||
# P2 跨模块 willFree 标注缓存(.vpi 行尾 `# ... wf=<mask>`,由 StubGen.Converter._PyiFunction 写出)。
|
||||
# 键格式与逃逸摘要完全一致("{sha1}.{Class}.{method}" / "{sha1}.{func}");值 = 形参失效掩码
|
||||
# (第 k 位 = 第 k 个形参在本调用后失效)。无标注的 def 不写条目,"查不到" 即 "无标注"。
|
||||
define VPI_WF_MAX = 8192
|
||||
define VPI_WF_KEY_LEN = 224
|
||||
_g_vpi_wf_keys: bytes = None
|
||||
_g_vpi_wf_vals: sizet* = None
|
||||
_g_vpi_wf_count: int = 0
|
||||
|
||||
_g_vpi_esc_keys: bytes = None
|
||||
_g_vpi_esc_vals: sizet* = None
|
||||
_g_vpi_esc_count: int = 0
|
||||
@@ -882,6 +891,7 @@ def _cache_vpi_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
|
||||
_cache_vpi_return_annots_text(vpi_buf, src_sha1)
|
||||
_cache_vpi_param_annots_text(vpi_buf, src_sha1)
|
||||
_cache_vpi_escape_annots_text(vpi_buf, src_sha1)
|
||||
_cache_vpi_willfree_annots_text(vpi_buf, src_sha1)
|
||||
return 1
|
||||
|
||||
|
||||
@@ -1000,6 +1010,122 @@ def _lookup_vpi_escape_packed(src_sha1: str, cls_name: str, method_name: str) ->
|
||||
return res
|
||||
|
||||
|
||||
# _cache_vpi_willfree_annots_text - extract the trailing `wf=<mask>` marker (P2 willFree).
|
||||
# 与逃逸摘要同构:不依赖 AST,按行扫描;只在有 wf 标记的 def 上建条目。
|
||||
def _cache_vpi_willfree_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
|
||||
"""抽取 vpi 行尾 P2 willFree 标记(`# ... wf=<mask>`)进缓存;1=处理完成 / 0=缓冲区或分配失败"""
|
||||
global _g_vpi_wf_keys, _g_vpi_wf_vals, _g_vpi_wf_count
|
||||
if vpi_buf is None or src_sha1 is None: return 0
|
||||
if _g_vpi_wf_keys is None:
|
||||
_g_vpi_wf_keys = stdlib.malloc(VPI_WF_MAX * VPI_WF_KEY_LEN)
|
||||
_g_vpi_wf_vals = sizet*(stdlib.malloc(VPI_WF_MAX * 8))
|
||||
if _g_vpi_wf_keys is None or _g_vpi_wf_vals is None: return 0
|
||||
string.memset(_g_vpi_wf_keys, 0, VPI_WF_MAX * VPI_WF_KEY_LEN)
|
||||
string.memset(_g_vpi_wf_vals, 0, VPI_WF_MAX * 8)
|
||||
sha1_len: sizet = string.strlen(src_sha1)
|
||||
buf_len: sizet = string.strlen(vpi_buf)
|
||||
cur_cls: bytes = None
|
||||
pos: sizet = 0
|
||||
while pos < buf_len:
|
||||
ls: sizet = pos
|
||||
le: sizet = pos
|
||||
while le < buf_len and vpi_buf[le] != '\n': le += 1
|
||||
i: sizet = ls
|
||||
while i < le and vpi_buf[i] == ' ': i += 1
|
||||
if i < le:
|
||||
# class X: -> 记录当前类上下文(与方法键一致)
|
||||
if le - i >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0:
|
||||
cstart: sizet = i + 6
|
||||
cend: sizet = cstart
|
||||
while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': cend += 1
|
||||
if cend > cstart:
|
||||
if cur_cls is not None: stdlib.free(cur_cls)
|
||||
cur_cls = stdlib.malloc(cend - cstart + 1)
|
||||
if cur_cls is not None:
|
||||
string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart)
|
||||
cur_cls[cend - cstart] = '\0'
|
||||
# def name(...) ... # [esc=<mask> ret=<ret>] wf=<mask>
|
||||
elif le - i >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0:
|
||||
# 模块级函数(无缩进)清掉类上下文,避免上一类泄漏
|
||||
if i == ls and cur_cls is not None:
|
||||
stdlib.free(cur_cls)
|
||||
cur_cls = None
|
||||
# 只在注释段里找 "wf=":避开参数名(如 def f(wf: int))
|
||||
mark: sizet = le
|
||||
hm: sizet = le
|
||||
j: sizet = i
|
||||
while j < le:
|
||||
if vpi_buf[j] == '#':
|
||||
hm = j
|
||||
break
|
||||
j += 1
|
||||
if hm < le:
|
||||
j2: sizet = hm + 1
|
||||
while j2 + 3 <= le:
|
||||
if vpi_buf[j2] == 'w' and vpi_buf[j2 + 1] == 'f' and vpi_buf[j2 + 2] == '=':
|
||||
mark = j2
|
||||
break
|
||||
j2 += 1
|
||||
if mark < le:
|
||||
kp: sizet = mark + 3
|
||||
wf_v: int = 0
|
||||
while kp < le and vpi_buf[kp] >= '0' and vpi_buf[kp] <= '9':
|
||||
wf_v = wf_v * 10 + (int(vpi_buf[kp]) - 48)
|
||||
kp += 1
|
||||
if wf_v != 0 and _g_vpi_wf_count < VPI_WF_MAX:
|
||||
dstart: sizet = i + 4
|
||||
dend: sizet = dstart
|
||||
while dend < le and vpi_buf[dend] != '(': dend += 1
|
||||
if dend > dstart:
|
||||
mname_len: sizet = dend - dstart
|
||||
cls_len_w: sizet = 0
|
||||
if cur_cls is not None: cls_len_w = string.strlen(cur_cls)
|
||||
key_len_w: sizet = sha1_len + 1 + cls_len_w + 1 + mname_len
|
||||
if key_len_w < VPI_WF_KEY_LEN:
|
||||
mname_buf_w: bytes = stdlib.malloc(mname_len + 1)
|
||||
key_buf_w: bytes = stdlib.malloc(key_len_w + 1)
|
||||
if mname_buf_w is not None and key_buf_w is not None:
|
||||
string.strncpy(mname_buf_w, vpi_buf + dstart, mname_len)
|
||||
mname_buf_w[mname_len] = '\0'
|
||||
if cur_cls is not None: viperlib.snprintf(key_buf_w, key_len_w + 1, "%s.%s.%s", src_sha1, cur_cls, mname_buf_w)
|
||||
else: viperlib.snprintf(key_buf_w, key_len_w + 1, "%s.%s", src_sha1, mname_buf_w)
|
||||
string.strcpy(_g_vpi_wf_keys + sizet(_g_vpi_wf_count) * VPI_WF_KEY_LEN, key_buf_w)
|
||||
_g_vpi_wf_vals[_g_vpi_wf_count] = sizet(wf_v)
|
||||
_g_vpi_wf_count += 1
|
||||
if mname_buf_w is not None: stdlib.free(mname_buf_w)
|
||||
if key_buf_w is not None: stdlib.free(key_buf_w)
|
||||
pos = le + 1
|
||||
if cur_cls is not None: stdlib.free(cur_cls)
|
||||
return 1
|
||||
|
||||
|
||||
# _lookup_vpi_willfree - query the cross-module P2 willFree cache (key shape = escape summary).
|
||||
def _lookup_vpi_willfree(src_sha1: str, cls_name: str, method_name: str) -> int:
|
||||
"""查跨模块 willFree 形参掩码;0 = 无标注"""
|
||||
if _g_vpi_wf_keys is None or _g_vpi_wf_count == 0: return 0
|
||||
if src_sha1 is None or method_name is None: return 0
|
||||
sha1_len: sizet = string.strlen(src_sha1)
|
||||
cl_len: sizet = 0
|
||||
if cls_name is not None: cl_len = string.strlen(cls_name)
|
||||
me_len: sizet = string.strlen(method_name)
|
||||
key_len: sizet = sha1_len + 1 + cl_len + 1 + me_len
|
||||
kb: bytes = stdlib.malloc(key_len + 1)
|
||||
if kb is None: return 0
|
||||
if cls_name is not None: viperlib.snprintf(kb, key_len + 1, "%s.%s.%s", src_sha1, cls_name, method_name)
|
||||
else: viperlib.snprintf(kb, key_len + 1, "%s.%s", src_sha1, method_name)
|
||||
res: int = 0
|
||||
idx: int = 0
|
||||
while idx < _g_vpi_wf_count:
|
||||
ent: bytes = _g_vpi_wf_keys + sizet(idx) * VPI_WF_KEY_LEN
|
||||
# 键以模块 sha1 打头:先比前 2 个十六进制字符,避免逐项 strcmp
|
||||
if ent[0] == kb[0] and ent[1] == kb[1] and string.strcmp(ent, kb) == 0:
|
||||
res = int(_g_vpi_wf_vals[idx])
|
||||
break
|
||||
idx += 1
|
||||
stdlib.free(kb)
|
||||
return res
|
||||
|
||||
|
||||
# _vpi_param_annot_seg_is - equality check for a param-annotation segment (for the "str/bytes is not a generic pointer" rule).
|
||||
def _vpi_param_annot_seg_is(src_sha1: str, cls_name: str, method_name: str, seg_idx: int, want: str) -> int:
|
||||
"""Check whether the definition-side param-annotation segment equals want exactly; 1=yes / 0=no."""
|
||||
|
||||
@@ -1914,6 +1914,8 @@ novirtual class AssignGenMixin:
|
||||
# (`p: T* = new heap slife …` → 释放旧对象 + 影子置 1;`q: T* = p` → 交还所有权)
|
||||
self._slife_note_assign(pool, builder, mod, trans, alloca, aa.value)
|
||||
mlirlite.build_store(builder, rhs_val, alloca, vol_laa)
|
||||
# P2 willFree:带注解声明/赋值后清除失效态(新变量本就为 0;复用槽兜底)
|
||||
self.Gen.VarGen.clear_var_freed(trans.SymTab, nm.id)
|
||||
# FakeDuck: initialize the shadow struct __data__ field with the initial string pointer
|
||||
if fd_str_ty is not None and fd_meta_alloca is not None:
|
||||
fd_data_fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_str_ty, "__data__")
|
||||
@@ -2399,6 +2401,9 @@ novirtual class AssignGenMixin:
|
||||
# `p = new heap slife …` → 释放旧对象 + 影子置 1;`q = p` / 外来指针 → 交还所有权
|
||||
self._slife_note_assign(pool, builder, mod, trans, alloca, asgn.value)
|
||||
mlirlite.build_store(builder, store_val, alloca, vol_las)
|
||||
# P2 willFree:对同名变量重新赋值即重新获得有效值,清除失效态
|
||||
# (新变量由 add_var 置 0;已存在变量走这里,见 clear_var_freed 注释)
|
||||
self.Gen.VarGen.clear_var_freed(trans.SymTab, nm.id)
|
||||
# A lambda assigned to a variable must be registered as a closure name
|
||||
if asgn.value is not None and asgn.value.kind() == ast.ASTKind.Lambda: self.Gen.CallGenLeafKit.register_closure_name(nm.id)
|
||||
existing: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nm.id)
|
||||
|
||||
@@ -828,6 +828,37 @@ novirtual class CallGenBase:
|
||||
mi += 1
|
||||
return 0
|
||||
|
||||
def _vpi_willfree(self, sha1: str, cls_name: str, method_name: str) -> int:
|
||||
"""跨模块 willFree 标注查询(P2):按 (定义模块 sha1, Class?, method) 查 .vpi 缓存;0=无标注。
|
||||
|
||||
与逃逸摘要共用"本模块已加载"备忘:_ensure_vpi_ret_annot_loaded 的单一入口会把
|
||||
ret/param/esc/wf 一起喂齐,所以这里只需保证模块被加载过一次。"""
|
||||
if sha1 is None or method_name is None: return 0
|
||||
if VpiReader._vpi_escape_module_loaded(sha1) == 0:
|
||||
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
|
||||
VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir)
|
||||
VpiReader._vpi_escape_mark_module(sha1)
|
||||
return VpiReader._lookup_vpi_willfree(sha1, cls_name, method_name)
|
||||
|
||||
def lookup_willfree_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> int:
|
||||
"""跨模块 willFree 掩码(P2):本类 + MRO 祖先各自的模块 sha1 依次查 .vpi;0=无标注"""
|
||||
if method_name is None: return 0
|
||||
if cls_sha1 is not None:
|
||||
p0: int = self._vpi_willfree(cls_sha1, cls_name, method_name)
|
||||
if p0 != 0: return p0
|
||||
if cls_name is not None:
|
||||
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, cls_name)
|
||||
mi: int = 1
|
||||
while mro is not None and mro[mi] != 0:
|
||||
anc: str = rawptr(mro[mi])
|
||||
if anc is not None and string.strcmp(anc, cls_name) != 0:
|
||||
sha_a: str = self.Gen.StructGen.get_struct_sha1(anc)
|
||||
if sha_a is not None:
|
||||
pa: int = self._vpi_willfree(sha_a, anc, method_name)
|
||||
if pa != 0: return pa
|
||||
mi += 1
|
||||
return 0
|
||||
|
||||
# _vpi_method_param_annot: on-demand lookup of the vpi param annotation string
|
||||
def _vpi_method_param_annot(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> str:
|
||||
"""Load and query the vpi parameter annotation string on demand; None on miss"""
|
||||
|
||||
@@ -119,6 +119,30 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
for ai in range(can):
|
||||
if ((esc_mask >> int(ai)) & 1) != 0:
|
||||
GenApi.fail_if_stack_addr_escape(cargs.get(ai), "an argument bound to a parameter that the callee escapes or returns", trans, _dummy_ptr)
|
||||
# P2 willFree:查被调者的形参失效标注(本文件表 → 跨模块 .vpi 回退)。
|
||||
# 标记动作在**实参全部翻译完成之后**(见下方 "# Default-argument filling" 之前),
|
||||
# 否则 `stdlib.free(p)` 自己那次读取 p 会被误判为"使用已失效变量"。
|
||||
wf_mask: int = 0
|
||||
if cargs is not None and func_name is not None:
|
||||
wf_mask = GenApi.get_param_willfree(func_name)
|
||||
if wf_mask == 0:
|
||||
mod_sha_wf: str = None
|
||||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||||
at_wf: ast.Attribute* = ast.Attribute*(cl.func)
|
||||
mod_p_wf: str = None
|
||||
if at_wf.value is not None and at_wf.value.kind() == ast.ASTKind.Name:
|
||||
mn_wf: ast.Name* = ast.Name*(at_wf.value)
|
||||
if mn_wf.id is not None: mod_p_wf = mn_wf.id
|
||||
elif at_wf.value is not None and at_wf.value.kind() == ast.ASTKind.Attribute:
|
||||
mod_p_wf = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_wf.value)
|
||||
if mod_p_wf is not None:
|
||||
fi_wf: str = None
|
||||
if trans is not None: fi_wf = trans._from_imports
|
||||
mod_sha_wf = self.Gen.StructGen._resolve_module_sha1(mod_p_wf, fi_wf)
|
||||
if mod_sha_wf is None and trans is not None and trans._from_imports is not None:
|
||||
mod_sha_wf = self._lookup_mod_sha1(pool, trans._from_imports, func_name, 0)
|
||||
if mod_sha_wf is not None:
|
||||
wf_mask = self.Gen.CallGenBase.lookup_willfree_via_vpi(pool, mod_sha_wf, None, func_name)
|
||||
# Generic function calls (PEP 695 def f[T]) are intercepted first: infer type args and
|
||||
# monomorphize here (templates emit no plain function); hit_box=1 means handled-with-void.
|
||||
h0_hit: t.CPtr = stdlib.malloc(4)
|
||||
@@ -887,6 +911,19 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
elif ai == 1: mc_src2 = arg_val
|
||||
elif ai == 2: mc_num2 = arg_val
|
||||
|
||||
# P2 willFree:实参已全部翻译完,此刻把被标注形参位上的实参变量标为"已失效"。
|
||||
# 只处理位置实参且实参是简单变量名(与编译器"标记实参变量槽"的口径一致);
|
||||
# 该槽其后被读/取址/再传(含再传 free)时由变量读取路径报 LifetimeError。
|
||||
# 关键字实参路径(_translate_call_with_kwargs)不支持该标记:见文档记录的限制。
|
||||
if wf_mask != 0 and cargs is not None and trans is not None:
|
||||
for ai_wf in range(can):
|
||||
if ((wf_mask >> int(ai_wf)) & 1) == 0: continue
|
||||
arg_wf: ast.AST* = cargs.get(ai_wf)
|
||||
if arg_wf is None or arg_wf.kind() != ast.ASTKind.Name: continue
|
||||
nm_wf: ast.Name* = ast.Name*(arg_wf)
|
||||
if nm_wf.id is None: continue
|
||||
ent_wf: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_wf.id)
|
||||
if ent_wf is not None: ent_wf.Freed = 1
|
||||
# Default-argument filling: when fewer args than params, fill the gaps; the defaults array is
|
||||
# right-aligned to args (defaults[0] maps to args[offset], offset = param_count - default_count).
|
||||
if can > 0 or (can == 0 and cargs is not None):
|
||||
|
||||
@@ -39,6 +39,12 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
|
||||
if nm is None: return None
|
||||
nm_id: str = nm.id
|
||||
if nm_id is None: return None
|
||||
# P2 willFree:读取已被"消费"的变量(其所有权被 @life(willFree(...)) 标注的调用夺取)→ fail-fast。
|
||||
# 只在**读**路径检查;对同名变量重新赋值会清除失效态(见 AssignGenMixin)。
|
||||
if trans is not None:
|
||||
ent_rd: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_id)
|
||||
if ent_rd is not None and ent_rd.Freed != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"cannot use '{nm_id}': its value was released by a call annotated with @life(willFree(...)) and is no longer valid", "LifetimeError")
|
||||
# CDefine compile-time constant: return the constant (float -> double, int -> i32).
|
||||
# Existing variables win over CDefine table entries injected from vpi stubs (lookup_var guard).
|
||||
cdef_val_d: double = self.Gen.TypeGenCDef.lookup_cdefine_double(nm_id)
|
||||
|
||||
@@ -316,6 +316,102 @@ def compute_escape_summary(pool: memhub.MemBuddy*, fd: ast.AST*, gset: sizet*, b
|
||||
return 1
|
||||
|
||||
|
||||
# ---------------- 生命期标注 @life(willFree(...)) / @willFree(...)(P2) ----------------
|
||||
# 返回值 = 形参位掩码:第 k 位 = 第 k 个形参所指对象在**本调用返回后失效**(被调用者释放/接管)。
|
||||
# 语义见 includes/stdlib.vp 的 free 与 Debugger/ownership-and-new.md §12-P2。
|
||||
# 非法标注一律 fail-fast(LifetimeError),不静默忽略。
|
||||
def compute_willfree_mask(fd: ast.AST*) -> int:
|
||||
"""解析函数定义的 @life(willFree(...)) / @willFree(...) 标注并返回形参位掩码(0=无标注)"""
|
||||
fn: ast.FunctionDef* = ast.FunctionDef*(fd)
|
||||
if fn is None or fn.decorator_list is None: return 0
|
||||
dl: list[ast.AST*]* = fn.decorator_list
|
||||
dn: sizet = len(dl)
|
||||
if dn == 0: return 0
|
||||
mask: int = 0
|
||||
for i in range(dn):
|
||||
deco: ast.AST* = dl.get(i)
|
||||
if deco is None or deco.kind() != ast.ASTKind.Call: continue
|
||||
dc: ast.Call* = ast.Call*(deco)
|
||||
if dc.func is None or dc.func.kind() != ast.ASTKind.Name: continue
|
||||
fname: ast.Name* = ast.Name*(dc.func)
|
||||
if fname.id is None: continue
|
||||
if string.strcmp(fname.id, "life") == 0:
|
||||
# @life(<tag>, <tag>, ...):逐个标签并入
|
||||
if dc.args is not None:
|
||||
an: sizet = len(dc.args)
|
||||
for j in range(an):
|
||||
mask = _WillFreeMergeTag(fn, dc.args.get(j), mask)
|
||||
elif string.strcmp(fname.id, "willFree") == 0:
|
||||
# 兼容形式:@willFree(0) 直接叠加(与 @life 等价、可混用)
|
||||
mask = _WillFreeMergeTag(fn, deco, mask)
|
||||
return mask
|
||||
|
||||
|
||||
def _WillFreeMergeTag(fn: ast.FunctionDef*, tag: ast.AST*, mask: int) -> int:
|
||||
"""校验一个标签调用 willFree(<int>|<name>) 并并入掩码;未知标签/非法实参 → fail-fast"""
|
||||
if tag is None or tag.kind() != ast.ASTKind.Call:
|
||||
fatal_error(tag, "@life(...) accepts only tag calls such as willFree(0) or willFree(p)", "LifetimeError")
|
||||
return mask
|
||||
tc: ast.Call* = ast.Call*(tag)
|
||||
tnm: str = None
|
||||
if tc.func is not None:
|
||||
if tc.func.kind() == ast.ASTKind.Name:
|
||||
tn: ast.Name* = ast.Name*(tc.func)
|
||||
tnm = tn.id
|
||||
elif tc.func.kind() == ast.ASTKind.Attribute:
|
||||
ta: ast.Attribute* = ast.Attribute*(tc.func)
|
||||
tnm = ta.attr
|
||||
if tnm is None or string.strcmp(tnm, "willFree") != 0:
|
||||
fatal_error(tag, "unknown lifetime tag: only `willFree(<param>)` is defined for @life(...)", "LifetimeError")
|
||||
return mask
|
||||
if tc.args is None or len(tc.args) != 1:
|
||||
fatal_error(tag, "@willFree(...) requires exactly one argument: a parameter name or its 0-based position", "LifetimeError")
|
||||
return mask
|
||||
idx: int = _WillFreeArgIndex(fn, tc.args.get(0))
|
||||
if idx < 0: return mask
|
||||
return mask | (1 << idx)
|
||||
|
||||
|
||||
def _WillFreeArgIndex(fn: ast.FunctionDef*, arg: ast.AST*) -> int:
|
||||
"""标签实参 → 形参下标(0 起,self/cls 计入);非法即报错并返回 -1"""
|
||||
if arg is None:
|
||||
fatal_error(arg, "@willFree(...) requires a parameter name or its 0-based position", "LifetimeError")
|
||||
return -1
|
||||
if fn.args is None or fn.args.args is None:
|
||||
fatal_error(arg, "@willFree(...) cannot be used here: the definition has no parameter list", "LifetimeError")
|
||||
return -1
|
||||
pl: list[ast.AST*]* = fn.args.args
|
||||
pn: sizet = len(pl)
|
||||
if arg.kind() == ast.ASTKind.Name:
|
||||
nma: ast.Name* = ast.Name*(arg)
|
||||
if nma.id is None: return -1
|
||||
for k in range(pn):
|
||||
pa: ast.AST* = pl.get(k)
|
||||
if pa is None or pa.kind() != ast.ASTKind.Arg: continue
|
||||
aa: ast.Arg* = ast.Arg*(pa)
|
||||
if aa.arg is not None and string.strcmp(aa.arg, nma.id) == 0: return int(k)
|
||||
fatal_error(arg, f"@willFree({nma.id}): '{nma.id}' is not a parameter of this function", "LifetimeError")
|
||||
return -1
|
||||
if arg.kind() != ast.ASTKind.Constant:
|
||||
fatal_error(arg, "@willFree(...) accepts only a parameter name or an integer position", "LifetimeError")
|
||||
return -1
|
||||
ca: ast.Constant* = ast.Constant*(arg)
|
||||
if ca.const_kind != ast.CONST_INT:
|
||||
fatal_error(arg, "@willFree(...) accepts only a parameter name or an integer position", "LifetimeError")
|
||||
return -1
|
||||
idx: int = int(ca.int_val)
|
||||
if idx < 0 or idx >= int(pn):
|
||||
fatal_error(arg, f"@willFree({idx}): parameter position out of range (the function has {int(pn)} parameter(s))", "LifetimeError")
|
||||
return -1
|
||||
return idx
|
||||
|
||||
|
||||
def get_param_willfree(name: str) -> int:
|
||||
"""本文件函数的 willFree 形参掩码(第 k 位=第 k 个形参在本调用后失效);无记录返回 0"""
|
||||
if _g_gen is not None: return _g_gen.StructGen.get_param_willfree(name)
|
||||
return 0
|
||||
|
||||
|
||||
# ---------------- find_struct_by_type (delegates to StructGenMixin.find_struct_by_type) ----------------
|
||||
def find_struct_by_type(struct_ty: mlirlite.LLVMType*) -> GenTypes.StructEntry*:
|
||||
"""Delegate to gen.find_struct_by_type"""
|
||||
|
||||
@@ -79,6 +79,9 @@ class VarEntry:
|
||||
# 生命期标签(LifeTag,见文件头 define LIFE_*):该变量**当前值**的生命期。
|
||||
# 追加在末尾以保持既有字段偏移不变。默认 LIFE_UNKNOWN(未登记 = 保守放行)。
|
||||
Life: int
|
||||
# P2 willFree:1=该变量已被某个带 @life(willFree(...)) 标注的调用"消费",其后再读/取址/传参
|
||||
# 即 LifetimeError(fail-fast)。追加在末尾以保持既有字段偏移不变;add_var 显式置 0。
|
||||
Freed: int
|
||||
|
||||
# ============================================================
|
||||
# Scope - scope node (formerly HandlesVar.Scope)
|
||||
|
||||
@@ -122,6 +122,11 @@ novirtual class StructGenMixin:
|
||||
# 扫描临时区(也只分配一次、每函数复用),消除每函数每轮的 gset/box 分配:
|
||||
# [0]=global 名计数 | [1..64]=global 名指针 | [65],[66]=box(mask, ret_borrow)
|
||||
EscScratch: sizet*
|
||||
# P2 willFree 标注表:本文件函数 → 形参失效掩码(第 k 位=第 k 个形参在本调用后失效)。
|
||||
# 与 EscTable 同款:池数组 + 线性查找,每项 2 槽:name_hash | mask;只存 u64 哈希。
|
||||
# **必须追加在类字段末尾**(见上方的字段偏移约定)。
|
||||
WfTable: sizet*
|
||||
WfCount: int
|
||||
|
||||
def InitStructGen(self) -> int:
|
||||
"""Initialize struct-mixin fields; called inline from subclass constructors."""
|
||||
@@ -141,6 +146,8 @@ novirtual class StructGenMixin:
|
||||
self.EscTable = None
|
||||
self.EscCount = 0
|
||||
self.EscScratch = None
|
||||
self.WfTable = None
|
||||
self.WfCount = 0
|
||||
self.StructNameIdx = None
|
||||
self.StructTypeIdx = None
|
||||
self.StructIdxCap = 0
|
||||
@@ -278,6 +285,11 @@ novirtual class StructGenMixin:
|
||||
self.EscScratch = sizet*(esc_pool.alloc(72 * 8))
|
||||
if self.EscScratch is not None: string.memset(self.EscScratch, 0, 72 * 8)
|
||||
self.EscCount = 0
|
||||
# P2 willFree 表:同样只分配一次,每文件仅重置 WfCount(表内只有 u64 哈希,无指针)
|
||||
if self.WfTable is None:
|
||||
self.WfTable = sizet*(esc_pool.alloc(esc_cap * 16))
|
||||
if self.WfTable is not None: string.memset(self.WfTable, 0, esc_cap * 16)
|
||||
self.WfCount = 0
|
||||
cn: sizet = len(ch)
|
||||
ci: sizet
|
||||
for ci in range(cn):
|
||||
@@ -296,6 +308,8 @@ novirtual class StructGenMixin:
|
||||
# 生命期摘要(M2.3 v1b):表内只存 u64 名哈希——不存字符串指针,
|
||||
# 从根上消除"槽里是坏指针 → strcmp 崩在 0xffff…"这一整类问题。
|
||||
self._RegisterFuncEscapeSummary(esc_pool, fd, fd.name)
|
||||
# P2:同一处登记 willFree 标注(声明式,直接来自 @life/@willFree 装饰器)
|
||||
self._RegisterFuncWillFree(fd, fd.name)
|
||||
# 嵌套函数用裸名键(与调用点 func_name 一致)
|
||||
self._RegisterNestedFuncs(esc_pool, fd.children, 0)
|
||||
|
||||
@@ -404,6 +418,35 @@ novirtual class StructGenMixin:
|
||||
if idx < 0: return 0
|
||||
return int(self.EscTable[idx * 3 + 2])
|
||||
|
||||
# ---- P2 willFree:声明式标注表(直接来自 @life(willFree(...)) / @willFree(...),不做数据流) ----
|
||||
def _WillFreeLookup(self, name: str) -> int:
|
||||
"""线性查找 willFree 表项下标;未找到返回 -1"""
|
||||
if name is None or self.WfTable is None: return -1
|
||||
h: sizet = self._NameHash(name)
|
||||
i: int = 0
|
||||
while i < self.WfCount:
|
||||
if self.WfTable[i * 2] == h: return i
|
||||
i += 1
|
||||
return -1
|
||||
|
||||
def get_param_willfree(self, name: str) -> int:
|
||||
"""willFree 形参掩码:第 k 位=第 k 个形参在本调用后失效;无记录返回 0"""
|
||||
idx: int = self._WillFreeLookup(name)
|
||||
if idx < 0: return 0
|
||||
return int(self.WfTable[idx * 2 + 1])
|
||||
|
||||
def _RegisterFuncWillFree(self, fd: ast.FunctionDef*, key: str) -> int:
|
||||
"""登记函数的 willFree 掩码(无标注也登记 0,避免同名重复计算);1=失败 / 0=成功"""
|
||||
if fd is None or key is None or self.WfTable is None: return 1
|
||||
if self._WillFreeLookup(key) >= 0: return 0
|
||||
if self.WfCount >= 1024: return 1
|
||||
mask: int = GenApi.compute_willfree_mask(fd)
|
||||
slot: int = self.WfCount
|
||||
self.WfTable[slot * 2 + 0] = sizet(self._NameHash(key))
|
||||
self.WfTable[slot * 2 + 1] = sizet(mask)
|
||||
self.WfCount = slot + 1
|
||||
return 0
|
||||
|
||||
def compute_escape_summary(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, gset: sizet*, box: sizet*) -> int:
|
||||
"""计算 fd 的摘要:box[0]=形参逃逸掩码、box[1]=返回借用位(写前清零)。
|
||||
|
||||
@@ -615,6 +658,7 @@ novirtual class StructGenMixin:
|
||||
if mfd is None or mfd.name is None: continue
|
||||
key_m: str = self._EscKey2(pool, cd.name, mfd.name)
|
||||
if key_m is not None: self._RegisterFuncEscapeSummary(pool, mfd, key_m)
|
||||
if key_m is not None: self._RegisterFuncWillFree(mfd, key_m)
|
||||
self._RegisterNestedFuncs(pool, mfd.children, 0)
|
||||
return 0
|
||||
|
||||
@@ -630,6 +674,7 @@ novirtual class StructGenMixin:
|
||||
nfd: ast.FunctionDef* = ast.FunctionDef*(st)
|
||||
if nfd is not None and nfd.name is not None:
|
||||
self._RegisterFuncEscapeSummary(pool, nfd, nfd.name)
|
||||
self._RegisterFuncWillFree(nfd, nfd.name)
|
||||
self._RegisterNestedFuncs(pool, nfd.children, depth + 1)
|
||||
elif k == ast.ASTKind.If:
|
||||
ifn: ast.If* = ast.If*(st)
|
||||
|
||||
@@ -304,10 +304,22 @@ novirtual class VarGenMixin:
|
||||
entry.IsConst = 0
|
||||
# 生命期标签显式初始化:未登记 = LIFE_UNKNOWN(保守放行)。
|
||||
entry.Life = GenTypes.LIFE_UNKNOWN
|
||||
# P2 willFree:新变量未失效。显式清零,避免复用槽位残留上一变量的失效态。
|
||||
entry.Freed = 0
|
||||
# [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
|
||||
|
||||
def clear_var_freed(self, symtab: GenTypes.SymbolTable*, name: str) -> int:
|
||||
"""P2 willFree:对同名变量重新赋值后清除失效态;1=清除过 / 0=无该项
|
||||
|
||||
变量已存在时赋值走 get_or_create_sym(不重建 VarEntry),所以失效态必须显式清除——
|
||||
否则 `p = malloc(8); free(p); p = malloc(16); Use(p)` 会被误报。"""
|
||||
e: GenTypes.VarEntry* = self.lookup_var_entry(symtab, name)
|
||||
if e is None: return 0
|
||||
e.Freed = 0
|
||||
return 1
|
||||
|
||||
# _spec_of_var_alloca - build a TypeSpec from the alloca's value type (S3 dual write).
|
||||
# alloca.Ty is Ptr(var_ty), so the pointee is the value type; None means failure or an unsupported type.
|
||||
def _spec_of_var_alloca(self, alloca: mlirlite.Value*) -> TSpec.TypeSpec*:
|
||||
|
||||
Reference in new issue
Block a user