diff --git a/App/lib/Stub/StubGen/Converter.vp b/App/lib/Stub/StubGen/Converter.vp index d7bd266..79962c7 100644 --- a/App/lib/Stub/StubGen/Converter.vp +++ b/App/lib/Stub/StubGen/Converter.vp @@ -517,15 +517,25 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*, # v3 跨模块逃逸摘要:把形参逃逸掩码与返回借用位写成**同一行**的行尾注释,供 VpiReader # 解析后在别的模块的调用点查询(跨模块摘要表按文件重置,只能靠 vpi 传递)。 # 只在真有逃逸/返回借用时写,保持 vpi 干净:无标记 = 无摘要 = 调用点保守放行。 + # P2 willFree:同行走 `# ... wf=`(第 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) - 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])) - pos = _PyiEmit(buf, buf_size, pos, tmp_c) - stdlib.free(tmp_c) + 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: + 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") return pos diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 3b45fdc..1c7fda0 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -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=`,由 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=` marker (P2 willFree). +# 与逃逸摘要同构:不依赖 AST,按行扫描;只在有 wf 标记的 def 上建条目。 +def _cache_vpi_willfree_annots_text(vpi_buf: bytes, src_sha1: str) -> int: + """抽取 vpi 行尾 P2 willFree 标记(`# ... wf=`)进缓存;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= ret=] wf= + 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.""" diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 1caa760..771ca2d 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -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) diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index d1c35d7..644dc70 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -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""" diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index 8032db2..366cf83 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -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): diff --git a/App/lib/core/Gen/Expr/ExprGenName.vp b/App/lib/core/Gen/Expr/ExprGenName.vp index c74e3b7..8a9898f 100644 --- a/App/lib/core/Gen/Expr/ExprGenName.vp +++ b/App/lib/core/Gen/Expr/ExprGenName.vp @@ -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) diff --git a/App/lib/core/Gen/GenApi.vp b/App/lib/core/Gen/GenApi.vp index 71a1626..b645464 100644 --- a/App/lib/core/Gen/GenApi.vp +++ b/App/lib/core/Gen/GenApi.vp @@ -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(, , ...):逐个标签并入 + 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(|) 并并入掩码;未知标签/非法实参 → 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()` 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""" diff --git a/App/lib/core/Gen/GenTypes.vp b/App/lib/core/Gen/GenTypes.vp index ffd6a25..a4422e4 100644 --- a/App/lib/core/Gen/GenTypes.vp +++ b/App/lib/core/Gen/GenTypes.vp @@ -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) diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index d0733d2..32cbd6b 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -121,7 +121,12 @@ novirtual class StructGenMixin: EscCount: int # 扫描临时区(也只分配一次、每函数复用),消除每函数每轮的 gset/box 分配: # [0]=global 名计数 | [1..64]=global 名指针 | [65],[66]=box(mask, ret_borrow) - EscScratch: sizet* + 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.""" @@ -140,7 +145,9 @@ novirtual class StructGenMixin: self.FileVarNames = None self.EscTable = None self.EscCount = 0 - self.EscScratch = None + 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) diff --git a/App/lib/core/Gen/VarGenMixin.vp b/App/lib/core/Gen/VarGenMixin.vp index 7696170..5cb9135 100644 --- a/App/lib/core/Gen/VarGenMixin.vp +++ b/App/lib/core/Gen/VarGenMixin.vp @@ -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*: