feat(lifetime): @life(escape/noescape) 多形参、硬逃逸位同命精化、归纳变量区间与跨模块常量特化

- GenApi:escape/noescape 标签支持多实参;新增 compute_escape_directives 与若干委托

- StructGenMixin:硬逃逸位(box[2])、常量与归纳变量区间折叠、_PackEscapeClear/compute_escape_clears

- Call 系:调用点同命裁剪(_FrameArgsEscapeClears)+ 跨模块 escg= 查询;写侧 Converter、读侧 VpiReader

- AssignGenMixin:裸 T()(无 new、无 __new__)判为帧生命期
This commit is contained in:
TermiNexus committed 2026-10-09 18:44:51 +08:00
1 parent 14a8dcb98b
commit fc797da542
8 files changed
+937 -36

No files matched your search

+18 -1
View File
@@ -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
+128
View File
@@ -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=<packed>` 标记(跨模块常量特化)。
# 与 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
+10
View File
@@ -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 组合矩阵)
+11
View File
@@ -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
+5 -1
View File
@@ -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)
+43
View File
@@ -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):
+116 -7
View File
@@ -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(<param>)` is defined for @life(...)", "LifetimeError")
if tnm is None:
fatal_error(tag, "@life(...) accepts only `willFree(<param>)` / `escape(<param>)` / `noescape(<param>)` 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(<param>)` / `escape(<param>)` / `noescape(<param>)` 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"""
+606 -27
View File
@@ -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 <p>` / `if <p> == k` / `if not <p>` 及其 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:
"""`<归纳变量> <op> <常量>` 按区间判定: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 <op> i` 等价于 `i <flip(op)> 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 <name> 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 覆盖面扩展:类方法与嵌套函数的摘要 ----