From fc797da542f4f69e3a3dc7f1ec88e4a6ae0d9e13 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 9 Oct 2026 18:44:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(lifetime):=20@life(escape/noescape)=20?= =?UTF-8?q?=E5=A4=9A=E5=BD=A2=E5=8F=82=E3=80=81=E7=A1=AC=E9=80=83=E9=80=B8?= =?UTF-8?q?=E4=BD=8D=E5=90=8C=E5=91=BD=E7=B2=BE=E5=8C=96=E3=80=81=E5=BD=92?= =?UTF-8?q?=E7=BA=B3=E5=8F=98=E9=87=8F=E5=8C=BA=E9=97=B4=E4=B8=8E=E8=B7=A8?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=B8=B8=E9=87=8F=E7=89=B9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GenApi:escape/noescape 标签支持多实参;新增 compute_escape_directives 与若干委托 - StructGenMixin:硬逃逸位(box[2])、常量与归纳变量区间折叠、_PackEscapeClear/compute_escape_clears - Call 系:调用点同命裁剪(_FrameArgsEscapeClears)+ 跨模块 escg= 查询;写侧 Converter、读侧 VpiReader - AssignGenMixin:裸 T()(无 new、无 __new__)判为帧生命期 --- App/lib/Stub/StubGen/Converter.vp | 19 +- App/lib/Stub/StubGen/VpiReader.vp | 128 ++++++ App/lib/core/Gen/AssignGenMixin.vp | 10 + App/lib/core/Gen/Call/CallGenBase.vp | 11 + App/lib/core/Gen/Call/CallGenCall.vp | 6 +- App/lib/core/Gen/Call/CallGenMixin.vp | 43 ++ App/lib/core/Gen/GenApi.vp | 123 ++++- App/lib/core/Gen/StructGenMixin.vp | 633 ++++++++++++++++++++++++-- 8 files changed, 937 insertions(+), 36 deletions(-) diff --git a/App/lib/Stub/StubGen/Converter.vp b/App/lib/Stub/StubGen/Converter.vp index 14d56d0..9d56e0c 100644 --- a/App/lib/Stub/StubGen/Converter.vp +++ b/App/lib/Stub/StubGen/Converter.vp @@ -527,7 +527,15 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*, if GenApi.compute_escape_summary(None, fn, _g_pyi_esc_scratch, esc_box_c) == 0: 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: + # 跨模块常量特化:算"哪些常量绑定能清掉哪些逃逸位"(每条 = 打包的 p|k|m),随同行走 escg= 落盘。 + # 只在真有逃逸位时才有意义;算出来 0 条就不写(保持 vpi 干净)。 + clears_c: sizet* = None + n_clears_c: int = 0 + if esc_v_c != 0: + clears_c = sizet*(stdlib.malloc(4 * 8)) + if clears_c is not None: + n_clears_c = GenApi.compute_escape_clears(fn, clears_c, 4) + if esc_v_c != 0 or ret_v_c != 0 or wf_mask_c != 0 or n_clears_c > 0: tmp_c: bytes = stdlib.malloc(96) if tmp_c is not None: if esc_v_c != 0 or ret_v_c != 0: @@ -535,7 +543,16 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*, 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) + # 跨模块常量特化:每条"常量守卫"落一个 escg= 字段(打包 p|k|m,见 + # StructGenMixin._PackEscapeClear)。同一函数多条 → 重复该字段,读侧按同键全部收集。 + if clears_c is not None: + ci_c: int = 0 + while ci_c < n_clears_c: + viperlib.snprintf(tmp_c, 96, " escg=%d", int(clears_c[ci_c])) + pos = _PyiEmit(buf, buf_size, pos, tmp_c) + ci_c += 1 stdlib.free(tmp_c) + if clears_c is not None: stdlib.free(clears_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 1c7fda0..fb33371 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -124,6 +124,15 @@ define VPI_WF_KEY_LEN = 224 _g_vpi_wf_keys: bytes = None _g_vpi_wf_vals: sizet* = None _g_vpi_wf_count: int = 0 +# 跨模块常量特化(P3 收尾):同一函数可有多条 `escg=`(打包 p|k|m,见 StructGenMixin._PackEscapeClear), +# 所以是"一键多行"表:键列与 wf 表同构,值列是打包整数。 +define VPI_ESG_MAX = 8192 +define VPI_ESG_KEY_LEN = 224 +# 常量环境的形参位上限:必须与 StructGenMixin.EC_ENV_MAX 一致(越界位一律不信) +define VPI_ESG_ENV_PARAM_MAX = 64 +_g_vpi_esg_keys: bytes = None +_g_vpi_esg_vals: sizet* = None +_g_vpi_esg_count: int = 0 _g_vpi_esc_keys: bytes = None _g_vpi_esc_vals: sizet* = None @@ -880,6 +889,124 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int: return 1 +# _cache_vpi_esc_clears_text - 抽取行尾**所有** `escg=` 标记(跨模块常量特化)。 +# 与 wf 表同构,但同一行可有多条(一条一个打包值),故是"一键多行"。值 = p|k|m 打包整数 +# (见 StructGenMixin._PackEscapeClear:p=守卫形参位、k=常量、m=可清掉的位掩码)。 +def _cache_vpi_esc_clears_text(vpi_buf: bytes, src_sha1: str) -> int: + """抽取 vpi 行尾的 escg= 标记进缓存;1=处理完成 / 0=缓冲区或分配失败""" + global _g_vpi_esg_keys, _g_vpi_esg_vals, _g_vpi_esg_count + if vpi_buf is None or src_sha1 is None: return 0 + if _g_vpi_esg_keys is None: + _g_vpi_esg_keys = stdlib.malloc(VPI_ESG_MAX * VPI_ESG_KEY_LEN) + _g_vpi_esg_vals = sizet*(stdlib.malloc(VPI_ESG_MAX * 8)) + if _g_vpi_esg_keys is None or _g_vpi_esg_vals is None: return 0 + string.memset(_g_vpi_esg_keys, 0, VPI_ESG_MAX * VPI_ESG_KEY_LEN) + string.memset(_g_vpi_esg_vals, 0, VPI_ESG_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: + 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' + 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 + # 只在注释段('#' 之后)里找 escg=:避开参数名 + hm: sizet = le + j: sizet = i + while j < le: + if vpi_buf[j] == '#': + hm = j + break + j += 1 + dstart: sizet = i + 4 + dend: sizet = dstart + while dend < le and vpi_buf[dend] != '(': dend += 1 + if hm < le and dend > dstart: + mname_len: sizet = dend - dstart + cls_len_e: sizet = 0 + if cur_cls is not None: cls_len_e = string.strlen(cur_cls) + key_len_e: sizet = sha1_len + 1 + cls_len_e + 1 + mname_len + if key_len_e < VPI_ESG_KEY_LEN: + mname_buf_e: bytes = stdlib.malloc(mname_len + 1) + key_buf_e: bytes = stdlib.malloc(key_len_e + 1) + if mname_buf_e is not None and key_buf_e is not None: + string.strncpy(mname_buf_e, vpi_buf + dstart, mname_len) + mname_buf_e[mname_len] = '\0' + if cur_cls is not None: viperlib.snprintf(key_buf_e, key_len_e + 1, "%s.%s.%s", src_sha1, cur_cls, mname_buf_e) + else: viperlib.snprintf(key_buf_e, key_len_e + 1, "%s.%s", src_sha1, mname_buf_e) + j3: sizet = hm + 1 + while j3 + 5 <= le: + if vpi_buf[j3] == 'e' and vpi_buf[j3 + 1] == 's' and vpi_buf[j3 + 2] == 'c' and vpi_buf[j3 + 3] == 'g' and vpi_buf[j3 + 4] == '=': + kq: sizet = j3 + 5 + eg_v: int = 0 + while kq < le and vpi_buf[kq] >= '0' and vpi_buf[kq] <= '9': + eg_v = eg_v * 10 + (int(vpi_buf[kq]) - 48) + kq += 1 + if eg_v > 0 and _g_vpi_esg_count < VPI_ESG_MAX: + string.strcpy(_g_vpi_esg_keys + sizet(_g_vpi_esg_count) * VPI_ESG_KEY_LEN, key_buf_e) + _g_vpi_esg_vals[_g_vpi_esg_count] = sizet(eg_v) + _g_vpi_esg_count += 1 + j3 = kq + else: + j3 += 1 + if mname_buf_e is not None: stdlib.free(mname_buf_e) + if key_buf_e is not None: stdlib.free(key_buf_e) + pos = le + 1 + if cur_cls is not None: stdlib.free(cur_cls) + return 1 + + +# _lookup_vpi_escape_clears - 跨模块常量守卫查询:按调用点常量环境算出"该清掉的位";0 = 无命中 +def _lookup_vpi_escape_clears(src_sha1: str, cls_name: str, method_name: str, env: sizet*) -> int: + """env = 常量环境(2 槽/形参:[0]=是否已知、[1]=值,下标 = 形参位);返回该清掉的位掩码 OR 值""" + if _g_vpi_esg_keys is None or _g_vpi_esg_count == 0: return 0 + if src_sha1 is None or method_name is None or env 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) + cleared: int = 0 + idx: int = 0 + while idx < _g_vpi_esg_count: + ent: bytes = _g_vpi_esg_keys + sizet(idx) * VPI_ESG_KEY_LEN + # 键以模块 sha1 打头:先比前 2 个十六进制字符,避免逐项 strcmp + if ent[0] == kb[0] and ent[1] == kb[1] and string.strcmp(ent, kb) == 0: + v: int = int(_g_vpi_esg_vals[idx]) + if v > 0: + p: int = v & 255 + kv: int = (v >> 8) & 255 + if kv >= 128: kv -= 256 + m: int = v >> 16 + if p >= 0 and p < VPI_ESG_ENV_PARAM_MAX and env[p * 2] != 0: + if int64(env[p * 2 + 1]) == int64(kv): cleared = cleared | m + idx += 1 + stdlib.free(kb) + return cleared + + # _cache_vpi_annots_text - 所有"从 vpi 文本解析"的缓存**单一入口**。 # 任何新增的文本缓存都加在这里,不要再散落到各加载点——漏喂一处就会**静默退化** # (现场:逃逸摘要只在 _ensure_vpi_ret_annot_loaded 里喂,而 stub 注入加载点没喂, @@ -892,6 +1019,7 @@ def _cache_vpi_annots_text(vpi_buf: bytes, src_sha1: str) -> int: _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) + _cache_vpi_esc_clears_text(vpi_buf, src_sha1) return 1 diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 9bca80d..9376ab3 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -636,6 +636,16 @@ novirtual class AssignGenMixin: 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 + # 裸 `T(...)` 构造:**没走 `new`、且该类没有 `__new__`** 时,存储就是入口块的 alloca + # (CallGenCtor._translate_struct_ctor:`tmp = _alloca_at_entry(...)`,只有 __new__ 才会 + # 改写存储指针)→ 帧生命期。这是"写进本帧对象字段不算逃逸"(调用点同命判定)成立的前提。 + # 有 `__new__` 的类仍返回未知(存储由 __new__ 决定,可能是池/堆)。 + ctor_nm: str = self.Gen.ExprGenBase.get_func_name(cl.func) + if ctor_nm is not None: + ct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(ctor_nm) + if ct_ty is not None: + ct_en: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(ct_ty) + if ct_en is None or ct_en.HasNew == 0: return GenTypes.LIFE_FRAME return GenTypes.LIFE_UNKNOWN if k == ast.ASTKind.New: # M2.4 `new`:生命期由描述符决定(§2.3 组合矩阵) diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 644dc70..26bbd50 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -840,6 +840,17 @@ novirtual class CallGenBase: VpiReader._vpi_escape_mark_module(sha1) return VpiReader._lookup_vpi_willfree(sha1, cls_name, method_name) + def lookup_escape_clears_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, class_name: str, func_name: str, cargs: list[ast.AST*]*, can: sizet, param_base: int) -> int: + """跨模块常量特化:把实参字面量装进常量环境,再向 vpi 缓存要"该清掉的位掩码";0 = 无命中 + + 跨模块拿不到被调者函数体(只有 .vpi),所以用落盘的守卫表(`escg=`)而不是重算摘要。 + 没有常量实参、或该函数没落过守卫记录 → 返回 0(调用方原样保留掩码,保守)。""" + if cls_sha1 is None or func_name is None or cargs is None or can == 0: return 0 + if GenApi.build_const_env(cargs, can, param_base) == 0: return 0 + fenv: sizet* = GenApi.const_env_ptr() + if fenv is None: return 0 + return VpiReader._lookup_vpi_escape_clears(cls_sha1, class_name, func_name, fenv) + 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 diff --git a/App/lib/core/Gen/Call/CallGenCall.vp b/App/lib/core/Gen/Call/CallGenCall.vp index a6ab6ab..0ee4d06 100644 --- a/App/lib/core/Gen/Call/CallGenCall.vp +++ b/App/lib/core/Gen/Call/CallGenCall.vp @@ -556,7 +556,11 @@ novirtual class CallGenCall(CallGenBase.CallGenBase): # 跨模块回退(v3):本文件摘要表按文件重置,跨模块只能查 .vpi 落盘的摘要 sha_mc: str = self.Gen.StructGen.get_struct_sha1(class_name) pk_mc: int = self.Gen.CallGenBase.lookup_escape_via_vpi(pool, sha_mc, class_name, method_name) - if pk_mc != 0: esc_mask_mc = int(pk_mc & 4294967295) + if pk_mc != 0: esc_mask_mc = int(pk_mc & 4294967295) + # 跨模块常量特化:用实参常量套用对方模块 .vpi 落盘的守卫记录(形参位含 self 偏移) + if esc_mask_mc != 0: + cl_mc: int = self.Gen.CallGenBase.lookup_escape_clears_via_vpi(pool, sha_mc, class_name, method_name, cargs_mc, len(cargs_mc), 1) + if cl_mc != 0: esc_mask_mc = esc_mask_mc & (~cl_mc) # 跨过程常量特化(P3 收尾):同文件能拿到方法体时,用常量实参重算摘要(形参位含 self 偏移) if esc_mask_mc != 0: can_rf: sizet = len(cargs_mc) diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index 99b30b6..b4ea965 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -60,6 +60,32 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): return 0 + # _FrameArgsEscapeClears - "同命"精化(P3 同命规则在**调用点**的对应物): + # 候选位 only_fld 只由"写进某个**形参对象**的字段"造成;若调用点交出的其它实参也都在本帧 + # (LIFE_FRAME)或纯值语义(LIFE_VALUE),则对象与本帧同命、并不真的逃逸 → 可清。 + # 判据保守:任何一个其它实参的生命期为 HEAP/STATIC/未知,就**保留**该位(宁可多报)。 + def _FrameArgsEscapeClears(self, trans: HT.Translator*, cargs: list[ast.AST*]*, can: sizet, only_fld: int) -> int: + """返回可安全清掉的位掩码(0 = 一位都清不掉)""" + if trans is None or cargs is None or only_fld == 0: return 0 + cleared: int = 0 + ai: sizet = 0 + while ai < can: + if ((only_fld >> int(ai)) & 1) != 0: + all_frame: int = 1 + j: sizet = 0 + while j < can: + if j != ai: + a_j: ast.AST* = cargs.get(j) + if a_j is not None: + lj: int = self.Gen.AssignGen.life_of_expr(trans, a_j, None) + if lj != GenTypes.LIFE_FRAME and lj != GenTypes.LIFE_VALUE: + all_frame = 0 + break + j += 1 + if all_frame != 0: cleared = cleared | (1 << int(ai)) + ai += 1 + return cleared + # translate_call - translate Call(func, args) (module-level version). def _MarkWillFreeKwargs(self, trans: HT.Translator*, wf_mask: int, cargs: list[ast.AST*]*, can: sizet, keywords: list[ast.AST*]*, kw_count: sizet, found_func: mlirlite.Function*) -> int: """P2 willFree:关键字(或"位置+关键字"混合)实参路径的失效标记。 @@ -123,7 +149,11 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): # 类型——只用于让 life_of_expr 判定"裸局部数组名处于指针位置 = 数组退让 = 栈地址"。 if cargs is not None and func_name is not None: esc_mask: int = GenApi.get_param_escapes(func_name) + # loc_mask:1=掩码来自**本文件**摘要表(只有这时才有"硬逃逸位"记录可用); + # 走下面的跨模块回退时置 0 —— 跨模块的掩码是合并值,不能再按"字段位"裁剪(会变成漏报)。 + loc_mask: int = 1 if esc_mask == 0: + loc_mask = 0 # 跨模块回退(v3):本文件摘要表按文件重置,跨模块的被调者只能靠 .vpi 传递。 # 解析被调者所属模块 sha1:带模块前缀(mod.f)用限定名,否则用 from-import 别名。 mod_sha_ec: str = None @@ -144,11 +174,24 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): if mod_sha_ec is not None: pk_ec: int = self.Gen.CallGenBase.lookup_escape_via_vpi(pool, mod_sha_ec, None, func_name) if pk_ec != 0: esc_mask = int(pk_ec & 4294967295) + # 跨模块常量特化:本文件摘要表查不到(= 被调者在别的模块)时,用实参常量套用 + # 该模块 .vpi 里落盘的守卫记录(无记录/无常量 → 原样保留,保守) + if esc_mask != 0: + cl_ec: int = self.Gen.CallGenBase.lookup_escape_clears_via_vpi(pool, mod_sha_ec, None, func_name, cargs, can, 0) + if cl_ec != 0: esc_mask = esc_mask & (~cl_ec) # 跨过程常量特化(P3 收尾):本文件能拿到被调者函数体时,用常量实参把摘要重算一遍—— # `if y: a = x` 在 `f(l, False)` 这一调用点上是死分支,不该按"逃逸"拦。 # 跨模块拿不到 AST → 原样返回(保守,与不启用特化时完全一致)。 if esc_mask != 0: esc_mask = GenApi.refine_escape_mask(pool, func_name, 0, cargs, can, esc_mask) + # 同命精化:只写进**形参对象**字段的位(字段位 = 逃逸位 & ~硬位),若调用点交出的其它实参 + # 也都在本帧,则两者同命、并不真的逃逸(`g(l, &local_s)`)。跨模块(loc_mask=0)不裁剪。 + if esc_mask != 0 and loc_mask != 0: + hard_m: int = GenApi.get_param_escapes_hard(func_name) + fld_only: int = esc_mask & (~hard_m) + if fld_only != 0: + cl_sf: int = self._FrameArgsEscapeClears(trans, cargs, can, fld_only) + if cl_sf != 0: esc_mask = esc_mask & (~cl_sf) if esc_mask != 0: _dummy_ptr: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) for ai in range(can): diff --git a/App/lib/core/Gen/GenApi.vp b/App/lib/core/Gen/GenApi.vp index 56c86c6..74e0fd1 100644 --- a/App/lib/core/Gen/GenApi.vp +++ b/App/lib/core/Gen/GenApi.vp @@ -361,15 +361,88 @@ def _WillFreeMergeTag(fn: ast.FunctionDef*, tag: ast.AST*, mask: int) -> int: 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") + if tnm is None: + fatal_error(tag, "@life(...) accepts only `willFree()` / `escape()` / `noescape()` tags", "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") + is_wf: int = string.strcmp(tnm, "willFree") + is_esc: int = string.strcmp(tnm, "escape") + is_nesc: int = string.strcmp(tnm, "noescape") + if is_wf != 0 and is_esc != 0 and is_nesc != 0: + fatal_error(tag, "@life(...) accepts only `willFree()` / `escape()` / `noescape()` tags", "LifetimeError") return mask - idx: int = _WillFreeArgIndex(fn, tc.args.get(0)) - if idx < 0: return mask - return mask | (1 << idx) + if tc.args is None or len(tc.args) == 0: + fatal_error(tag, f"@life tag `{tnm}(...)` requires at least one argument: parameter names or 0-based positions", "LifetimeError") + return mask + # 一个标签可以带多个形参:`@life(noescape(x, s))`(逐个并入) + an_w: sizet = len(tc.args) + k_w: sizet = 0 + while k_w < an_w: + idx: int = _WillFreeArgIndex(fn, tc.args.get(k_w)) + k_w += 1 + if idx < 0: continue + # escape/noescape 的位由 compute_escape_directives 收,这里只负责**校验**合法性 + if is_wf != 0: mask = mask | (1 << idx) + return mask + + +# compute_escape_directives - 收 `@life(escape(...), noescape(...))` 的方向性契约。 +# out[0] = 强制**逃逸**位(无条件,不受数据流/常量折叠影响)、out[1] = 强制**不逃逸**位。 +# 只认 `@life(...)` 里的标签(独立的 `@willFree(...)` 叠加形式里不放这两个)。 +# 同一形参同时标 escape 与 noescape → fail-fast(矛盾契约,绝不猜一边)。 +def compute_escape_directives(fd: ast.AST*, out: sizet*) -> int: + """解析 @life(...) 里的 escape/noescape 标签到 out[0]/out[1];0=完成 / 1=入参非法""" + if out is None: return 1 + out[0] = 0 + out[1] = 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) + i: sizet = 0 + while i < dn: + deco: ast.AST* = dl.get(i) + i += 1 + 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 or string.strcmp(fname.id, "life") != 0: continue + if dc.args is None: continue + an: sizet = len(dc.args) + j: sizet = 0 + while j < an: + tg: ast.AST* = dc.args.get(j) + j += 1 + if tg is None or tg.kind() != ast.ASTKind.Call: continue + tcl: ast.Call* = ast.Call*(tg) + tnm2: str = None + if tcl.func is not None and tcl.func.kind() == ast.ASTKind.Name: + tn2: ast.Name* = ast.Name*(tcl.func) + tnm2 = tn2.id + if tnm2 is None: continue + is_esc2: int = string.strcmp(tnm2, "escape") + is_nesc2: int = string.strcmp(tnm2, "noescape") + if is_esc2 != 0 and is_nesc2 != 0: continue + if tcl.args is None or len(tcl.args) == 0: + fatal_error(tg, f"@life tag `{tnm2}(...)` requires at least one argument: parameter names or 0-based positions", "LifetimeError") + continue + an4: sizet = len(tcl.args) + k4: sizet = 0 + while k4 < an4: + idx2: int = _WillFreeArgIndex(fn, tcl.args.get(k4)) + k4 += 1 + if idx2 < 0: continue + if is_esc2 == 0: + if ((int(out[1]) >> idx2) & 1) != 0: + fatal_error(tg, f"contradictory lifetime tags: parameter #{idx2} is marked both escape(...) and noescape(...)", "LifetimeError") + continue + out[0] = out[0] | (sizet(1) << idx2) + else: + if ((int(out[0]) >> idx2) & 1) != 0: + fatal_error(tg, f"contradictory lifetime tags: parameter #{idx2} is marked both escape(...) and noescape(...)", "LifetimeError") + continue + out[1] = out[1] | (sizet(1) << idx2) + return 0 def _WillFreeArgIndex(fn: ast.FunctionDef*, arg: ast.AST*) -> int: @@ -412,6 +485,18 @@ def _WillFreeArgIndex(fn: ast.FunctionDef*, arg: ast.AST*) -> int: return idx +def get_param_escapes_hard(name: str) -> int: + """本文件函数"硬逃逸位"掩码(全局写 / 返回形参 / 传递闭包 / base 未定的字段写);0 = 无记录""" + if _g_gen is not None: return _g_gen.StructGen.get_param_escapes_hard(name) + return 0 + + +def get_param_escapes_hard_method(pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int: + """方法版硬逃逸位:查 "Class.method" 并沿 MRO 回退""" + if _g_gen is not None: return _g_gen.StructGen.get_param_escapes_hard_method(pool, class_name, method_name) + return 0 + + 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) @@ -437,6 +522,30 @@ def refine_escape_mask_method(pool: memhub.MemBuddy*, class_name: str, method_na return mask +def compute_escape_clears(fd: ast.FunctionDef*, out_packed: sizet*, max: int) -> int: + """算出可落 .vpi 的"常量守卫"记录条数(打包值写进 out_packed);0 = 没有""" + if _g_gen is not None: return _g_gen.StructGen.compute_escape_clears(None, fd, out_packed, max) + return 0 + + +def build_const_env(cargs: list[ast.AST*]*, can: sizet, param_base: int) -> int: + """把实参字面量装进常量环境;1 = 装了至少一个(供跨模块守卫套用)""" + if _g_gen is not None: return _g_gen.StructGen.build_const_env(None, cargs, can, param_base) + return 0 + + +def apply_escape_clears(packed: sizet*, n: int, mask: int) -> int: + """按常量环境套用守卫记录(清位);返回精化后的掩码""" + if _g_gen is not None: return _g_gen.StructGen.apply_escape_clears(packed, n, mask) + return mask + + +def const_env_ptr() -> sizet*: + """取常量环境区指针(`build_const_env` 装填的那块,2 槽/形参);None = 不可用""" + if _g_gen is not None: return _g_gen.StructGen.const_env_ptr() + return None + + # ---------------- 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/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 45bd49f..8b2723f 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -29,12 +29,25 @@ define STRUCT_MAX = 512 # [EC_BOX_SLOT] / [EC_BOX_SLOT+1] box[0]=逃逸掩码、box[1]=返回借用位(compute_escape_summary 约定 box = gset+65 槽) # [EC_EVAL_SLOT] 表达式常量求值的临时槽(递归求值每层算完立刻拷出,见 _EvalConstInt) # [EC_ENV_SLOT..] 常量绑定环境:2 槽/形参([0]=是否已知、[1]=值),下标 = 形参位(self/cls 计入) +# [EC_CAND_SLOT..] 守卫候选:2 槽/候选(形参位 + 常量值),供 _CollectGuardCandidates 收 define EC_GLOBAL_MAX = 64 define EC_BOX_SLOT = 65 -define EC_EVAL_SLOT = 67 +define EC_EVAL_SLOT = 246 define EC_ENV_SLOT = 68 define EC_ENV_MAX = 64 -define EC_SCRATCH_SLOTS = 196 +define EC_CAND_SLOT = 196 +define EC_CAND_MAX = 8 +# [EC_IND_SLOT..] 循环归纳变量区间:4 槽/条(名哈希, lo, hi, 保留),按 EscIndCount 当栈用 +# [EC_IND_LO]/[EC_IND_HI] 区间查询的临时输出槽 +define EC_IND_SLOT = 212 +define EC_IND_MAX = 8 +define EC_IND_LO = 244 +define EC_IND_HI = 245 +define EC_SCRATCH_SLOTS = 256 +# 跨模块守卫记录的打包范围:p(8 位) | k(8 位,有符号) | m(其余位,被清掉的位掩码)。 +# 超出范围(形参 > 255 个、常量超出 int8)就不记——只影响精度,不影响正确性。 +define EC_PACK_K_MIN = -128 +define EC_PACK_K_MAX = 127 # Name | type index slot count (power of two and > 2*STRUCT_MAX so load factor < 0.5 and probing terminates). define STRUCT_IDX_CAP = 1024 # Field-slot upper limit (kept in sync with HandlesStruct): Generator flattens ~120 fields across 43 mixins, so a limit of 64 would drop 9th+ parent fields and break __init__ attribute assignment. @@ -149,7 +162,13 @@ novirtual class StructGenMixin: # 把登记/.vpi 落盘过程中 EscScratch 的内容踩掉。 EscRefineScratch: sizet* # 常量绑定环境(2 槽/形参,位于 EscRefineScratch 的 EC_ENV_SLOT 起)。None = 不启用常量特化。 - EscConstEnv: sizet* + EscConstEnv: sizet* + # 循环归纳变量区间的条数(在 EscRefineScratch 的 EC_IND_SLOT 起,当栈用:进循环体前 +1、出后 -1) + EscIndCount: int + # "硬逃逸位"表(键口径同 EscTable):该位由**全局写 / 返回形参 / 传递闭包 / base 未定的字段写**造成。 + # 与"只写进形参对象字段"(调用点可判同命 → 放行)区分开:字段位 = 逃逸位 & ~硬位。每文件重置。 + EscFieldTable: sizet* + EscFieldCount: int def InitStructGen(self) -> int: """Initialize struct-mixin fields; called inline from subclass constructors.""" @@ -174,7 +193,10 @@ novirtual class StructGenMixin: self.EscFuncTable = None self.EscFuncCount = 0 self.EscRefineScratch = None - self.EscConstEnv = None + self.EscConstEnv = None + self.EscIndCount = 0 + self.EscFieldTable = None + self.EscFieldCount = 0 self.StructNameIdx = None self.StructTypeIdx = None self.StructIdxCap = 0 @@ -324,8 +346,14 @@ novirtual class StructGenMixin: if self.EscRefineScratch is None: self.EscRefineScratch = sizet*(esc_pool.alloc(sizet(EC_SCRATCH_SLOTS) * 8)) if self.EscRefineScratch is not None: string.memset(self.EscRefineScratch, 0, sizet(EC_SCRATCH_SLOTS) * 8) + # "字段逃逸位"表:同样只分配一次,每文件重置计数 + if self.EscFieldTable is None: + self.EscFieldTable = sizet*(esc_pool.alloc(esc_cap * 16)) + if self.EscFieldTable is not None: string.memset(self.EscFieldTable, 0, esc_cap * 16) + self.EscFieldCount = 0 self.EscFuncCount = 0 self.EscConstEnv = None + self.EscIndCount = 0 cn: sizet = len(ch) ci: sizet for ci in range(cn): @@ -442,6 +470,35 @@ novirtual class StructGenMixin: i += 1 return h + def get_param_escapes_hard(self, key: str) -> int: + """形参"硬逃逸位"掩码(全局写 / 返回形参 / 传递闭包 / base 未定的字段写);0 = 无记录""" + if key is None or self.EscFieldTable is None: return 0 + h: sizet = self._NameHash(key) + i: int = 0 + while i < self.EscFieldCount: + if self.EscFieldTable[i * 2] == h: return int(self.EscFieldTable[i * 2 + 1]) + i += 1 + return 0 + + def get_param_escapes_hard_method(self, pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int: + """方法版:查 "Class.method" 并沿 MRO 回退(口径同 get_param_escapes_method)""" + if method_name is None or class_name is None: return 0 + key_q: str = self._EscKey2(pool, class_name, method_name) + if key_q is not None: + m_q: int = self.get_param_escapes_hard(key_q) + if m_q != 0: return m_q + mro: sizet* = self.get_mro_chain(pool, class_name) + mi2: int = 1 + while mro is not None and mro[mi2] != 0: + anc_m: str = rawptr(mro[mi2]) + if anc_m is not None and string.strcmp(anc_m, class_name) != 0: + key_a: str = self._EscKey2(pool, anc_m, method_name) + if key_a is not None: + m_a: int = self.get_param_escapes_hard(key_a) + if m_a != 0: return m_a + mi2 += 1 + return 0 + def get_param_escapes(self, name: str) -> int: """形参逃逸掩码:第 k 位=第 k 个形参逃逸;无记录返回 0""" idx: int = self._EscLookup(name) @@ -517,6 +574,7 @@ novirtual class StructGenMixin: if fd is None or box is None: return 1 box[0] = 0 box[1] = 0 + box[2] = 0 g: sizet* = gset if g is None: g = self.EscScratch if g is None: @@ -527,8 +585,22 @@ novirtual class StructGenMixin: self._CollectGlobalNames(fd.children, g, 0) prev_env: sizet* = self.EscConstEnv self.EscConstEnv = const_env + self.EscIndCount = 0 self._ScanEscapes(fd, fd.children, g, box, 0) self.EscConstEnv = prev_env + self.EscIndCount = 0 + # 显式标注(声明式契约)**在扫描之后**叠加:escape 无条件置位、noescape 无条件清位。 + # 顺序是关键:常量折叠发生在扫描里,所以 `escape(p)` 不会被"死分支"折掉 —— 契约的本意 + # 正是"编译器看不见的路径";`noescape(p)` 也不会被任何推导重新置位。 + # 借 g 的空槽(68/69,此刻 env 已用完)当输出缓冲,不依赖任何额外 scratch。 + ann_out: sizet* = sizet*(uint64(g) + 68 * 8) + if GenApi.compute_escape_directives(fd, ann_out) == 0: + ef_m: int = int(ann_out[0]) + ne_m: int = int(ann_out[1]) + if ef_m != 0 or ne_m != 0: + box[0] = (box[0] | sizet(ef_m)) & (~sizet(ne_m)) + # 强制逃逸同样算**硬**位:调用点不能再按"同命"把它裁掉 + box[2] = (box[2] | sizet(ef_m)) & (~sizet(ne_m)) return 0 # ============================================================ @@ -623,6 +695,12 @@ novirtual class StructGenMixin: if ops_list is None or comps is None: return -1 # 只处理单比较器:`a < b < c` 的短路语义这里不复刻(折不动更安全) if len(ops_list) != 1 or len(comps) != 1: return -1 + # 循环归纳变量区间:`i == k` / `i < k` 且 k 落在区间外(或区间退化成单值)→ 直接定论 + op_v0: int = ops_list.get(0) + ic0: int = self._IndCompare(fd, cmp.left, comps.get(0), op_v0) + if ic0 >= 0: return ic0 + ic1: int = self._IndCompare(fd, comps.get(0), cmp.left, self._FlipCmpOp(op_v0)) + if ic1 >= 0: return ic1 if self._EvalConstInt(fd, cmp.left, depth + 1) == 0: return -1 lv: int64 = int64(self.EscRefineScratch[EC_EVAL_SLOT]) if self._EvalConstInt(fd, comps.get(0), depth + 1) == 0: return -1 @@ -658,28 +736,10 @@ novirtual class StructGenMixin: def _RefineWithConsts(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, param_base: int, cargs: list[ast.AST*]*, can: sizet, mask: int) -> int: """把常量实参装进 EscConstEnv 后重算一次摘要;返回精化后的掩码(无可用常量时原样返回)""" if self.EscRefineScratch is None or pool is None: return mask + if self.build_const_env(pool, cargs, can, param_base) == 0: return mask env: sizet* = sizet*(uint64(self.EscRefineScratch) + EC_ENV_SLOT * 8) - string.memset(env, 0, sizet(EC_ENV_MAX) * 2 * 8) - prev_env: sizet* = self.EscConstEnv - # 顶层实参只认字面量(及其字面量折叠):先把环境置空,_EvalConstInt 遇形参名即放弃 - self.EscConstEnv = None - found: int = 0 - ai: sizet = 0 - while ai < can: - pi: int = int(ai) + param_base - arg: ast.AST* = cargs.get(ai) - if arg is not None and pi >= 0 and pi < EC_ENV_MAX: - if self._EvalConstInt(fd, arg, 0) != 0: - env[pi * 2] = 1 - env[pi * 2 + 1] = self.EscRefineScratch[EC_EVAL_SLOT] - found = 1 - ai += 1 - if found == 0: - self.EscConstEnv = prev_env - return mask box: sizet* = sizet*(uint64(self.EscRefineScratch) + EC_BOX_SLOT * 8) self.compute_escape_summary(pool, fd, self.EscRefineScratch, box, env) - self.EscConstEnv = prev_env return int(box[0]) def refine_escape_mask(self, pool: memhub.MemBuddy*, name: str, param_base: int, cargs: list[ast.AST*]*, can: sizet, mask: int) -> int: @@ -712,6 +772,482 @@ novirtual class StructGenMixin: if fd is None: return mask return self._RefineWithConsts(pool, fd, 1, cargs, can, mask) + def _EnsureRefineScratch(self) -> int: + """确保重算 scratch 就绪(**登记路径与 .vpi 落盘路径都会用到**——后者不走 register_file_funcs, + 那时 CompilerFactory.factory / PermPool 可能还没就绪);1=可用 / 0=不可用。 + + 用 stdlib.malloc:与文件头 _g_freed_slots 同口径,跨文件存活且不依赖任何池。""" + if self.EscRefineScratch is not None: return 1 + self.EscRefineScratch = sizet*(stdlib.malloc(sizet(EC_SCRATCH_SLOTS) * 8)) + if self.EscRefineScratch is None: return 0 + string.memset(self.EscRefineScratch, 0, sizet(EC_SCRATCH_SLOTS) * 8) + return 1 + + def build_const_env(self, pool: memhub.MemBuddy*, cargs: list[ast.AST*]*, can: sizet, param_base: int) -> int: + """把实参里的**字面量**常量装进 EscConstEnv 区(EC_ENV_SLOT 起);返回 1=装了至少一个常量 + + 顶层实参只认字面量(及其字面量折叠):装之前把 EscConstEnv 置空,_EvalConstInt 遇形参名即放弃, + 所以这里不需要调用方的函数体(跨模块调用点同样可用)。""" + if cargs is None or self._EnsureRefineScratch() == 0: return 0 + env: sizet* = sizet*(uint64(self.EscRefineScratch) + EC_ENV_SLOT * 8) + string.memset(env, 0, sizet(EC_ENV_MAX) * 2 * 8) + prev_env: sizet* = self.EscConstEnv + self.EscConstEnv = None + found: int = 0 + ai: sizet = 0 + while ai < can: + pi: int = int(ai) + param_base + arg: ast.AST* = cargs.get(ai) + if arg is not None and pi >= 0 and pi < EC_ENV_MAX: + if self._ConstOfNode(None, arg) != 0: + env[pi * 2] = 1 + env[pi * 2 + 1] = self.EscRefineScratch[EC_EVAL_SLOT] + found = 1 + ai += 1 + self.EscConstEnv = prev_env + return found + + def _ConstOfNode(self, fd: ast.FunctionDef*, node: ast.AST*) -> int: + """字面量常量求值(**不使用**形参绑定):1=成功(值在 EC_EVAL_SLOT)/ 0=不是常量""" + if node is None or self.EscRefineScratch is None: return 0 + prev_e: sizet* = self.EscConstEnv + self.EscConstEnv = None + ok: int = self._EvalConstInt(fd, node, 0) + self.EscConstEnv = prev_e + return ok + + def _AddGuardCand(self, out: sizet*, n: int, max: int, p: int, kv: int) -> int: + """加一条守卫候选并按 (形参位, 常量) 去重;返回新条数""" + if out is None or n >= max: return n + i: int = 0 + while i < n: + if int(out[i * 2]) == p and int64(out[i * 2 + 1]) == int64(kv): return n + i += 1 + out[n * 2] = sizet(p) + out[n * 2 + 1] = sizet(kv) + return n + 1 + + def _HarvestGuardTest(self, fd: ast.FunctionDef*, node: ast.AST*, out: sizet*, n: int, max: int) -> int: + """从一个条件表达式收守卫候选:BoolOp 递归 values;单条件只认 `p` / `not p` / `p == k` / `k == p`""" + if node is None or out is None or n >= max: return n + k: int = node.kind() + if k == ast.ASTKind.BoolOp: + bo: ast.BoolOp* = ast.BoolOp*(node) + if bo.values is None: return n + cnt: sizet = len(bo.values) + i: sizet = 0 + while i < cnt and n < max: + n = self._HarvestGuardTest(fd, bo.values.get(i), out, n, max) + i += 1 + return n + p: int = -1 + add_false: int = 0 + kv: int = 1 + if k == ast.ASTKind.Name: + p = self._ParamIndex(fd, node) + add_false = 1 + elif k == ast.ASTKind.UnaryOp: + uo: ast.UnaryOp* = ast.UnaryOp*(node) + if uo is not None and uo.op == ast.OpKind.Not: + p = self._ParamIndex(fd, uo.operand) + add_false = 1 + elif k == ast.ASTKind.Compare: + cmp: ast.Compare* = ast.Compare*(node) + if cmp is not None: + ops_l: list[int]* = (list[int]*)(cmp.ops) + comps_l: list[ast.AST*]* = cmp.comparators + if ops_l is not None and comps_l is not None and len(ops_l) == 1 and len(comps_l) == 1: + op_v: int = ops_l.get(0) + if op_v == ast.OpKind.Eq or op_v == ast.OpKind.Ne: + p = self._ParamIndex(fd, cmp.left) + if p >= 0: + if self._ConstOfNode(fd, comps_l.get(0)) != 0: kv = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + else: p = -1 + else: + p = self._ParamIndex(fd, comps_l.get(0)) + if p >= 0: + if self._ConstOfNode(fd, cmp.left) != 0: kv = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + else: p = -1 + if p < 0: return n + n = self._AddGuardCand(out, n, max, p, kv) + if add_false != 0: n = self._AddGuardCand(out, n, max, p, 0) + return n + + def _CollectGuardCandidates(self, fd: ast.FunctionDef*, stmts: list[ast.AST*]*, out: sizet*, max: int, depth: int) -> int: + """收 `if`/`while` 条件里的守卫候选,递归进体与 else;返回条数""" + if fd is None or stmts is None or out is None or depth > 64: return 0 + n: int = 0 + cnt: sizet = len(stmts) + i: sizet = 0 + while i < cnt and n < max: + st: ast.AST* = stmts.get(i) + i += 1 + if st is None: continue + k: int = st.kind() + if k == ast.ASTKind.If: + ifn: ast.If* = ast.If*(st) + n = self._HarvestGuardTest(fd, ifn.test, out, n, max) + n = self._CollectGuardCandidates(fd, ifn.children, out, max, depth + 1) + n = self._CollectGuardCandidates(fd, ifn.orelse, out, max, depth + 1) + elif k == ast.ASTKind.While: + whn: ast.While* = ast.While*(st) + n = self._HarvestGuardTest(fd, whn.test, out, n, max) + n = self._CollectGuardCandidates(fd, whn.children, out, max, depth + 1) + n = self._CollectGuardCandidates(fd, whn.orelse, out, max, depth + 1) + return n + + def _PackEscapeClear(self, p: int, kv: int, m: int) -> int: + """打包 (守卫形参位, 常量, 被清掉的掩码) 成一个整数;超出打包范围返回 -1(只影响精度)""" + if p < 0 or p > 255: return -1 + if kv < EC_PACK_K_MIN or kv > EC_PACK_K_MAX: return -1 + if m <= 0: return -1 + return p + (kv & 255) * 256 + m * 65536 + + def compute_escape_clears(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, out_packed: sizet*, max: int) -> int: + """算出可落 .vpi 的"常量守卫"记录条数(0=没有)。每条 = 打包的 (守卫形参位, 常量, 被清掉的位掩码)。 + + 含义:"若调用点把形参 p 传成常量 k,则掩码 m 里的位可以清掉"。 + 做法:先从函数体收守卫候选(`if

` / `if

== k` / `if not

` 及其 and/or 组合), + 再对每个候选**真的用该绑定把摘要重算一遍**并与原始掩码求差——只记确实清掉的位,绝不猜。""" + if fd is None or out_packed is None or max <= 0: return 0 + if self._EnsureRefineScratch() == 0: return 0 + box_b: sizet* = sizet*(uint64(self.EscRefineScratch) + EC_BOX_SLOT * 8) + if self.compute_escape_summary(pool, fd, self.EscRefineScratch, box_b, None) != 0: return 0 + base_mask: int = int(box_b[0]) + if base_mask == 0: return 0 + cand: sizet* = sizet*(uint64(self.EscRefineScratch) + EC_CAND_SLOT * 8) + ncand: int = self._CollectGuardCandidates(fd, fd.children, cand, EC_CAND_MAX, 0) + if ncand <= 0: return 0 + env: sizet* = sizet*(uint64(self.EscRefineScratch) + EC_ENV_SLOT * 8) + n_out: int = 0 + ci: int = 0 + while ci < ncand and n_out < max: + p: int = int(cand[ci * 2]) + kv: int = int64(cand[ci * 2 + 1]) + ci += 1 + if p < 0 or p >= EC_ENV_MAX: continue + string.memset(env, 0, sizet(EC_ENV_MAX) * 2 * 8) + env[p * 2] = 1 + env[p * 2 + 1] = sizet(kv) + if self.compute_escape_summary(pool, fd, self.EscRefineScratch, box_b, env) == 0: + cleared: int = base_mask & (~int(box_b[0])) + pk: int = self._PackEscapeClear(p, kv, cleared) + if pk >= 0: + out_packed[n_out] = sizet(pk) + n_out += 1 + return n_out + + def const_env_ptr(self) -> sizet*: + """常量环境区指针(`build_const_env` 装填的那块,2 槽/形参);None = 不可用""" + if self.EscRefineScratch is None: return None + return sizet*(uint64(self.EscRefineScratch) + EC_ENV_SLOT * 8) + + def apply_escape_clears(self, packed: sizet*, n: int, mask: int) -> int: + """按调用点常量环境(EscConstEnv 区)套用跨模块守卫记录:命中即清位;返回精化后的掩码""" + if packed is None or n <= 0 or mask == 0 or self.EscRefineScratch is None: return mask + env: sizet* = sizet*(uint64(self.EscRefineScratch) + EC_ENV_SLOT * 8) + res: int = mask + i: int = 0 + while i < n: + v: int = int(packed[i]) + i += 1 + if v <= 0: continue + p: int = v & 255 + kv: int = (v >> 8) & 255 + if kv >= 128: kv -= 256 + m: int = v >> 16 + if p >= 0 and p < EC_ENV_MAX and env[p * 2] != 0: + if int64(env[p * 2 + 1]) == int64(kv): res = res & (~m) + return res + + # ---- 循环归纳变量区间:`for i in range(<常量>)` 让 `if i == k` 这类比较能按区间判定 ---- + def _IsNameNode(self, node: ast.AST*, name: str) -> int: + """node 是名字且等于 name;1=是 / 0=否""" + if node is None or name is None or node.kind() != ast.ASTKind.Name: return 0 + nm: ast.Name* = ast.Name*(node) + if nm.id is None: return 0 + return 1 if string.strcmp(nm.id, name) == 0 else 0 + + def _IndBodyReassigns(self, name: str, stmts: list[ast.AST*]*, depth: int) -> int: + """这些语句(含嵌套块)里是否给 name 赋过值;1=赋过 → 归纳变量的区间不可信。 + + Python 允许在循环里重绑循环变量,所以必须查这个;碰到看不透的块语句(with/try/match) + **一律按"赋过"处理**(宁可放弃精度,也不能把会执行到的写当成死代码)。""" + if name is None or stmts is None or depth > 64: return 1 + cnt: sizet = len(stmts) + i: sizet = 0 + while i < cnt: + if self._StmtAssignsName(name, stmts.get(i), depth) != 0: return 1 + i += 1 + return 0 + + def _StmtAssignsName(self, name: str, st: ast.AST*, depth: int) -> int: + """单条语句(含其嵌套块)是否给 name 赋过值;1=赋过 / 0=没有(看不透的块语句按 1 处理)""" + if name is None or st is None or depth > 64: return 1 + k: int = st.kind() + if k == ast.ASTKind.Assign: + tg: list[ast.AST*]* = ast.Assign*(st).targets + if tg is not None: + nt: sizet = len(tg) + j: sizet = 0 + while j < nt: + if self._IsNameNode(tg.get(j), name) != 0: return 1 + j += 1 + elif k == ast.ASTKind.AnnAssign: + if self._IsNameNode(ast.AnnAssign*(st).target, name) != 0: return 1 + elif k == ast.ASTKind.AugAssign: + if self._IsNameNode(ast.AugAssign*(st).target, name) != 0: return 1 + elif k == ast.ASTKind.If: + ifn: ast.If* = ast.If*(st) + if self._IndBodyReassigns(name, ifn.children, depth + 1) != 0: return 1 + if self._IndBodyReassigns(name, ifn.orelse, depth + 1) != 0: return 1 + elif k == ast.ASTKind.While: + whn: ast.While* = ast.While*(st) + if self._IndBodyReassigns(name, whn.children, depth + 1) != 0: return 1 + if self._IndBodyReassigns(name, whn.orelse, depth + 1) != 0: return 1 + elif k == ast.ASTKind.For: + frn: ast.For* = ast.For*(st) + if self._IndBodyReassigns(name, frn.children, depth + 1) != 0: return 1 + if self._IndBodyReassigns(name, frn.orelse, depth + 1) != 0: return 1 + elif k == ast.ASTKind.With or k == ast.ASTKind.Try or k == ast.ASTKind.Match: + return 1 + return 0 + + def _IndRangeOf(self, node: ast.AST*) -> int: + """node 是**在册归纳变量**时返回 1 并把区间 [lo, hi] 写进 EC_IND_LO/HI;否则 0""" + if node is None or self.EscRefineScratch is None or self.EscIndCount <= 0: return 0 + if node.kind() != ast.ASTKind.Name: return 0 + nm: ast.Name* = ast.Name*(node) + if nm.id is None: return 0 + h: sizet = self._NameHash(nm.id) + i: int = 0 + while i < self.EscIndCount: + base: int = EC_IND_SLOT + i * 4 + if self.EscRefineScratch[base] == h: + self.EscRefineScratch[EC_IND_LO] = self.EscRefineScratch[base + 1] + self.EscRefineScratch[EC_IND_HI] = self.EscRefineScratch[base + 2] + return 1 + i += 1 + return 0 + + def _IndCompare(self, fd: ast.FunctionDef*, a_side: ast.AST*, b_side: ast.AST*, op: int) -> int: + """`<归纳变量> <常量>` 按区间判定:1=恒真 / 0=恒假 / -1=判不定""" + if self._IndRangeOf(a_side) == 0: return -1 + # 先拷出区间,再做常量侧求值(求值会用到 EC_EVAL_SLOT,且其递归理论上有机会覆写区间槽) + lo: int = int64(self.EscRefineScratch[EC_IND_LO]) + hi: int = int64(self.EscRefineScratch[EC_IND_HI]) + # 常量侧用当前环境求值(`i == n` 里的 n 也可能被调用点常量绑定) + if self._EvalConstInt(fd, b_side, 0) == 0: return -1 + k: int = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + if op == ast.OpKind.Eq: + if lo == k and hi == k: return 1 + if k < lo or k > hi: return 0 + return -1 + if op == ast.OpKind.Ne: + if lo == k and hi == k: return 0 + if k < lo or k > hi: return 1 + return -1 + if op == ast.OpKind.Lt: + if hi < k: return 1 + if lo >= k: return 0 + return -1 + if op == ast.OpKind.Le: + if hi <= k: return 1 + if lo > k: return 0 + return -1 + if op == ast.OpKind.Gt: + if lo > k: return 1 + if hi <= k: return 0 + return -1 + if op == ast.OpKind.Ge: + if lo >= k: return 1 + if hi < k: return 0 + return -1 + return -1 + + def _FlipCmpOp(self, op: int) -> int: + """`k i` 等价于 `i k`(等式类保持不变)""" + if op == ast.OpKind.Lt: return ast.OpKind.Gt + if op == ast.OpKind.Le: return ast.OpKind.Ge + if op == ast.OpKind.Gt: return ast.OpKind.Lt + if op == ast.OpKind.Ge: return ast.OpKind.Le + return op + + def _PushInductionEntry(self, fd: ast.FunctionDef*, frn: ast.For*) -> int: + """`for in range(<常量>)`:把归纳变量区间压进表。 + + 返回 2=区间可证为空(循环体一次都不执行)/ 1=已压入区间 / 0=判不定(调用方照旧扫)。 + 只认内建 `range`(本文件自己定义过 range 就放弃)、只认 step 缺省(正负步长的边界细节不猜)、 + 且循环体不得重绑该变量。""" + if fd is None or frn is None or self.EscRefineScratch is None: return 0 + if self.EscIndCount >= EC_IND_MAX: return 0 + tgt: ast.AST* = frn.target + if tgt is None or tgt.kind() != ast.ASTKind.Name: return 0 + tnm: ast.Name* = ast.Name*(tgt) + if tnm.id is None: return 0 + if self._IndBodyReassigns(tnm.id, frn.children, 0) != 0: return 0 + it: ast.AST* = frn.iter + if it is None or it.kind() != ast.ASTKind.Call: return 0 + cl: ast.Call* = ast.Call*(it) + if cl.func is None or cl.func.kind() != ast.ASTKind.Name: return 0 + fnm: ast.Name* = ast.Name*(cl.func) + if fnm.id is None or string.strcmp(fnm.id, "range") != 0: return 0 + if GenApi.is_file_func("range") != 0: return 0 + args_i: list[ast.AST*]* = cl.args + na: sizet = 0 + if args_i is not None: na = len(args_i) + if na < 1 or na > 3: return 0 + start: int64 = 0 + stop: int64 = 0 + step: int64 = 1 + if na == 1: + if self._EvalConstInt(fd, args_i.get(0), 0) == 0: return 0 + stop = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + else: + if self._EvalConstInt(fd, args_i.get(0), 0) == 0: return 0 + start = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + if self._EvalConstInt(fd, args_i.get(1), 0) == 0: return 0 + stop = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + if na == 3: + if self._EvalConstInt(fd, args_i.get(2), 0) == 0: return 0 + step = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + # step==0 是非法 range(Python 也报错)→ 不猜;正负步长各自算首尾(除法的分子都保证为正) + lo_v: int64 = 0 + hi_v: int64 = 0 + if step > 0: + if stop <= start: return 2 + cnt_p: int64 = (stop - start + step - 1) // step + if cnt_p <= 0: return 2 + lo_v = start + hi_v = start + (cnt_p - 1) * step + elif step < 0: + if start <= stop: return 2 + nstep: int64 = 0 - step + cnt_n: int64 = (start - stop + nstep - 1) // nstep + if cnt_n <= 0: return 2 + hi_v = start + lo_v = start + (cnt_n - 1) * step + else: + return 0 + base: int = EC_IND_SLOT + self.EscIndCount * 4 + self.EscRefineScratch[base] = self._NameHash(tnm.id) + self.EscRefineScratch[base + 1] = sizet(lo_v) + self.EscRefineScratch[base + 2] = sizet(hi_v) + self.EscIndCount = self.EscIndCount + 1 + return 1 + + def _PopInductionEntry(self) -> int: + """弹出最近压入的归纳变量区间(栈式)""" + if self.EscIndCount > 0: self.EscIndCount = self.EscIndCount - 1 + return 0 + + def _IsSelfIncrement(self, name: str, value: ast.AST*, is_aug: int) -> int: + """`i = i + <非负常量>`(is_aug=0)或 `i += <非负常量>`(is_aug=1);1=是 + + 只接受**非负**增量:负增量会让计数器掉到区间下界之外,区间结论就不成立了。 + 增量为 0 时 i 不动、仍在原区间内,故允许。""" + if name is None or value is None: return 0 + if is_aug != 0: + if self._ConstOfNode(None, value) == 0: return 0 + return 1 if int64(self.EscRefineScratch[EC_EVAL_SLOT]) >= 0 else 0 + if value.kind() != ast.ASTKind.BinOp: return 0 + bo: ast.BinOp* = ast.BinOp*(value) + if bo is None or bo.op != ast.OpKind.Add: return 0 + if self._IsNameNode(bo.left, name) == 0: return 0 + if self._ConstOfNode(None, bo.right) == 0: return 0 + return 1 if int64(self.EscRefineScratch[EC_EVAL_SLOT]) >= 0 else 0 + + def _BodyCounterIncrementLast(self, name: str, stmts: list[ast.AST*]*) -> int: + """计数循环体的形态检查:**只有最后一条**顶层语句可以自增计数器,之前语句一律不得给它赋值。 + + 为什么必须"自增在最后":区间只在自增**之前**成立(自增后 i 可能已越过上界), + 而体内语句是按顺序执行的 —— 自增排在中间,它后面的读就不能再用区间判定。1=形态满足。""" + if name is None or stmts is None: return 0 + cnt: sizet = len(stmts) + if cnt == 0: return 0 + i: sizet = 0 + while i + 1 < cnt: + if self._StmtAssignsName(name, stmts.get(i), 0) != 0: return 0 + i += 1 + last: ast.AST* = stmts.get(cnt - 1) + if last is None: return 0 + lk: int = last.kind() + if lk == ast.ASTKind.Assign: + asg_l: ast.Assign* = ast.Assign*(last) + if asg_l.targets is None or len(asg_l.targets) != 1: return 0 + if self._IsNameNode(asg_l.targets.get(0), name) == 0: return 0 + return self._IsSelfIncrement(name, asg_l.value, 0) + if lk == ast.ASTKind.AugAssign: + au_l: ast.AugAssign* = ast.AugAssign*(last) + if self._IsNameNode(au_l.target, name) == 0: return 0 + return self._IsSelfIncrement(name, au_l.value, 1) + return 0 + + def _TryWhileCounter(self, fd: ast.FunctionDef*, prev_stmt: ast.AST*, whn: ast.While*) -> int: + """`i = <常量>; while i < B: …; i = i + <常量>` 计数循环:把 i 的取值区间压表。 + + 返回 2=区间可证为空 / 1=已压入 / 0=判不定。 + 只认把计数器**往上界推**的 `i < B` / `i <= B`(镜像写法也认);`i != B` 这类不折。""" + if fd is None or prev_stmt is None or whn is None or self.EscRefineScratch is None: return 0 + if self.EscIndCount >= EC_IND_MAX: return 0 + # 初始化语句:`i = <常量>` 或 `i: int = <常量>`(带注解的写法同样常见,两种都认) + init_tgt: ast.AST* = None + init_val: ast.AST* = None + pk: int = prev_stmt.kind() + if pk == ast.ASTKind.Assign: + pn: ast.Assign* = ast.Assign*(prev_stmt) + if pn.targets is None or len(pn.targets) != 1: return 0 + init_tgt = pn.targets.get(0) + init_val = pn.value + elif pk == ast.ASTKind.AnnAssign: + ini: ast.AnnAssign* = ast.AnnAssign*(prev_stmt) + init_tgt = ini.target + init_val = ini.value + else: + return 0 + if init_tgt is None or init_tgt.kind() != ast.ASTKind.Name: return 0 + inm: ast.Name* = ast.Name*(init_tgt) + if inm.id is None: return 0 + if self._EvalConstInt(fd, init_val, 0) == 0: return 0 + init_v: int64 = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + tst: ast.AST* = whn.test + if tst is None or tst.kind() != ast.ASTKind.Compare: return 0 + cmpw: ast.Compare* = ast.Compare*(tst) + if cmpw.left is None: return 0 + ops_l: list[int]* = (list[int]*)(cmpw.ops) + comps_l: list[ast.AST*]* = cmpw.comparators + if ops_l is None or comps_l is None or len(ops_l) != 1 or len(comps_l) != 1: return 0 + op_w: int = ops_l.get(0) + hi_v: int64 = 0 + have_hi: int = 0 + if self._IsNameNode(cmpw.left, inm.id) != 0: + if self._EvalConstInt(fd, comps_l.get(0), 0) != 0: + b_v: int64 = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + if op_w == ast.OpKind.Lt: + hi_v = b_v - 1 + have_hi = 1 + elif op_w == ast.OpKind.Le: + hi_v = b_v + have_hi = 1 + elif self._IsNameNode(comps_l.get(0), inm.id) != 0: + if self._EvalConstInt(fd, cmpw.left, 0) != 0: + b_v2: int64 = int64(self.EscRefineScratch[EC_EVAL_SLOT]) + if op_w == ast.OpKind.Gt: + hi_v = b_v2 - 1 + have_hi = 1 + elif op_w == ast.OpKind.Ge: + hi_v = b_v2 + have_hi = 1 + if have_hi == 0: return 0 + if hi_v < init_v: return 2 + if self._BodyCounterIncrementLast(inm.id, whn.children) == 0: return 0 + base: int = EC_IND_SLOT + self.EscIndCount * 4 + self.EscRefineScratch[base] = self._NameHash(inm.id) + self.EscRefineScratch[base + 1] = sizet(init_v) + self.EscRefineScratch[base + 2] = sizet(hi_v) + self.EscIndCount = self.EscIndCount + 1 + return 1 + def _RegisterFuncEscapeSummary(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, key: str) -> int: """扫函数体登记摘要;key 必须是与 EscTable 同池的字符串""" if fd is None or key is None or self.EscTable is None or self.EscScratch is None: return 1 @@ -725,6 +1261,12 @@ novirtual class StructGenMixin: self.EscTable[slot * 3 + 1] = box[0] self.EscTable[slot * 3 + 2] = box[1] self.EscCount = slot + 1 + # 硬逃逸位表(同键口径):仅在非空时登记,"查不到" = "没有硬逃逸位" + if self.EscFieldTable is not None and box[2] != 0 and self.EscFieldCount < 1024: + fld_s: int = self.EscFieldCount + self.EscFieldTable[fld_s * 2 + 0] = sizet(self._NameHash(key)) + self.EscFieldTable[fld_s * 2 + 1] = box[2] + self.EscFieldCount = fld_s + 1 # 记录 FunctionDef 指针:调用点用常量实参重算摘要时要把常量代进这份函数体。 if self.EscFuncTable is not None and self.EscFuncCount < 1024: fs: int = self.EscFuncCount @@ -798,10 +1340,19 @@ novirtual class StructGenMixin: tk: int = target.kind() if tk == ast.ASTKind.Attribute: box[0] = box[0] | (sizet(1) << pi) + # box[2] = **硬**逃逸位:不含"写进**形参对象**字段"这一类 —— 那类是否真逃逸取决于 + # 调用点交出的对象是否长命(`g(l, &local_s)` 两都在本帧 → 同命,不报),留给调用点精化。 + # 只有 base 不是形参(全局对象 / 未定)时才无条件算硬逃逸。 + hard_f: int = 1 + at_m: ast.Attribute* = ast.Attribute*(target) + if at_m.value is not None: + if self._ParamIndex(fd, at_m.value) >= 0: hard_f = 0 + if hard_f != 0: box[2] = box[2] | (sizet(1) << pi) elif tk == ast.ASTKind.Name: tnm: ast.Name* = ast.Name*(target) if tnm.id is not None and gset is not None and self._IsGlobalNm(gset, tnm.id) != 0: box[0] = box[0] | (sizet(1) << pi) + box[2] = box[2] | (sizet(1) << pi) return 0 def _IsGlobalNm(self, garr: sizet*, name: str) -> int: @@ -841,6 +1392,8 @@ novirtual class StructGenMixin: if pi >= 0: box[0] = box[0] | (sizet(1) << pi) box[1] = 1 + # 返回形参 = 硬逃逸(调用点无法再判定去向) + box[2] = box[2] | (sizet(1) << pi) elif k == ast.ASTKind.Assign: asg: ast.Assign* = ast.Assign*(st) if asg.targets is not None: @@ -865,11 +1418,34 @@ novirtual class StructGenMixin: self._ScanEscapes(fd, ifn.orelse, gset, box, depth + 1) elif k == ast.ASTKind.While: whn: ast.While* = ast.While*(st) - self._ScanEscapes(fd, whn.children, gset, box, depth + 1) - self._ScanEscapes(fd, whn.orelse, gset, box, depth + 1) + # 常量特化:`while <常量假>:` 的循环体一次都不执行 → 不扫(`while <常量真>` 体必执行, + # 与不启用特化时一致)。else 支一律照扫:能否到达还取决于循环体里有没有 break, + # 那个不折(保守,宁可多扫)。 + wcond_v: int = self._EvalConstCond(fd, whn.test, 0) + if wcond_v == 0: + self._ScanEscapes(fd, whn.orelse, gset, box, depth + 1) + else: + # 计数循环 `i = <常量>; while i < B: …; i = i + k`:把 i 的区间压表供体内判定 + prev_st: ast.AST* = None + if i > 0: prev_st = stmts.get(i - 1) + ind_w: int = self._TryWhileCounter(fd, prev_st, whn) + if ind_w != 2: + self._ScanEscapes(fd, whn.children, gset, box, depth + 1) + if ind_w == 1: self._PopInductionEntry() + self._ScanEscapes(fd, whn.orelse, gset, box, depth + 1) elif k == ast.ASTKind.For: frn: ast.For* = ast.For*(st) - self._ScanEscapes(fd, frn.children, gset, box, depth + 1) + # 零次迭代 → 不扫循环体:① 字面量空列表;② `range(<常量>)` 的区间可证为空。 + empty_iter: int = 0 + if frn.iter is not None and frn.iter.kind() == ast.ASTKind.List: + it_elts: list[ast.AST*]* = ast.List*(frn.iter).elts + if it_elts is None or len(it_elts) == 0: empty_iter = 1 + # 归纳变量区间:`for i in range(<常量>)` 把 i 的取值区间压表,供体内 `if i == k` 按区间判定 + ind: int = 0 + if empty_iter == 0: ind = self._PushInductionEntry(fd, frn) + if empty_iter == 0 and ind != 2: + self._ScanEscapes(fd, frn.children, gset, box, depth + 1) + if ind == 1: self._PopInductionEntry() self._ScanEscapes(fd, frn.orelse, gset, box, depth + 1) return 0 @@ -886,7 +1462,10 @@ novirtual class StructGenMixin: for j in range(na): if ((gmask >> int(j)) & 1) == 0: continue pi: int = self._ParamIndex(fd, cl.args.get(j)) - if pi >= 0: box[0] = box[0] | (sizet(1) << pi) + if pi >= 0: + box[0] = box[0] | (sizet(1) << pi) + # 传递闭包推上来的位同样算**硬**逃逸(被调者可能把它写进全局,这里看不到具体去向) + box[2] = box[2] | (sizet(1) << pi) return 0 # ---- M2.3 覆盖面扩展:类方法与嵌套函数的摘要 ----