feat(gen): M2.3 覆盖面扩展——类方法与嵌套函数的逃逸摘要

摘要键约定:模块级/嵌套函数用裸名(与调用点 func_name 一致),类方法用 "Class.method",查询沿 MRO 回退;刻意不回退裸名,避免方法名与同名模块函数(error/init 等)串味产生误报。

register_file_funcs 登记类方法与嵌套函数摘要,v2 传递闭包相应覆盖(_RescanEscapes 拆为 _RescanOne/_RescanNested/_RescanClassMethods)。

调用点新增:_translate_method_call 的方法路径检查(形参下标含 self,用户第 ai 个实参对应 bit ai+1)、_call_method_on_ptr 的 __init__ 构造器检查(self.f = p 的逃逸;__new__ 形参可能与 __init__ 共享并裁剪,不查)。

已知边界:摘要表按文件重置,跨模块查询待 v3;_PropagateCallEscapes 只处理裸名被调者,方法调用链的传递待补。
This commit is contained in:
TermiNexus committed 2026-10-08 16:55:34 +08:00
1 parent b03e55bb7e
commit 9f98e683a9
3 files changed
+192 -16

No files matched your search

+26
View File
@@ -146,6 +146,19 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
if pool is None or builder is None or class_name is None or method_name is None: return None
if self_ptr is None: return None
# 调用点生命期检查(M2.3 覆盖面扩展,构造器):`__init__` 里最常见的逃逸就是
# `self.f = p`(_MarkEscapingStore 已把该形参置位)。实参链 = (self, 用户实参…),
# 故第 ai 个用户实参对应 bit (ai+1);默认值是**右对齐追加**在末尾,不影响前缀对应。
# `__new__` 不查:其形参与 __init__ 共享且可能被裁剪,位置对应不可靠。
if arg_nodes is not None and string.strcmp(method_name, "__init__") == 0:
esc_mask_ci: int = GenApi.get_param_escapes_method(pool, class_name, method_name)
if esc_mask_ci != 0:
dummy_ptr_ci: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
can_ci: sizet = len(arg_nodes)
for ai_ci in range(can_ci):
if ((esc_mask_ci >> int(ai_ci + 1)) & 1) != 0:
GenApi.fail_if_stack_addr_escape(arg_nodes.get(ai_ci), "an argument bound to a constructor parameter that __init__ stores into a field (escapes)", trans, dummy_ptr_ci)
# Fetch the struct entry early (find_struct_by_type avoids cross-module
# same-name collisions) for SHA1 lookup and vtable dispatch.
cmop_entry: GenTypes.StructEntry* = None
@@ -517,6 +530,19 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
if pool is None or builder is None or class_name is None or method_name is None: return None
if obj_ptr is None or cl is None: return None
# 调用点生命期检查(M2.3 覆盖面扩展):方法摘要键为 "Class.method"(含 MRO 回退)。
# 摘要的形参下标含 self(bit 0),故用户第 ai 个实参对应 bit (ai+1)。语义与模块级
# 函数那条检查一致:被调者"会逃逸或直接返回"的形参位上,实参不得交出本帧栈地址。
cargs_mc: list[ast.AST*]* = cl.args
if cargs_mc is not None:
esc_mask_mc: int = GenApi.get_param_escapes_method(pool, class_name, method_name)
if esc_mask_mc != 0:
dummy_ptr_mc: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
can_mc: sizet = len(cargs_mc)
for ai_mc in range(can_mc):
if ((esc_mask_mc >> int(ai_mc + 1)) & 1) != 0:
GenApi.fail_if_stack_addr_escape(cargs_mc.get(ai_mc), "an argument bound to a method parameter that the callee escapes or returns", trans, dummy_ptr_mc)
# list generic container method: dispatch to _translate_list_builtin_call inline
# when the method is a list builtin; use the receiver's actual struct name.
lb_src: str = class_name
+6
View File
@@ -298,6 +298,12 @@ def get_ret_borrow(name: str) -> int:
return 0
def get_param_escapes_method(pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
"""方法形参逃逸掩码:查 "Class.method" 并沿 MRO 回退,最后退裸名键;无记录返回 0"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escapes_method(pool, class_name, method_name)
return 0
# ---------------- find_struct_by_type (delegates to StructGenMixin.find_struct_by_type) ----------------
def find_struct_by_type(struct_ty: mlirlite.LLVMType*) -> GenTypes.StructEntry*:
"""Delegate to gen.find_struct_by_type"""
+160 -16
View File
@@ -290,6 +290,16 @@ novirtual class StructGenMixin:
# 生命期摘要(M2.3 v1b):表内只存 u64 名哈希——不存字符串指针,
# 从根上消除"槽里是坏指针 → strcmp 崩在 0xffff…"这一整类问题。
self._RegisterFuncEscapeSummary(esc_pool, fd, fd.name)
# 嵌套函数用裸名键(与调用点 func_name 一致)
self._RegisterNestedFuncs(esc_pool, fd.children, 0)
# M2.3 覆盖面扩展:类方法用 "Class.method" 键——此前方法与嵌套函数没有摘要,
# 它们的"存对象字段 / 返回形参"在调用点不会被拦截。
for ci2 in range(cn):
child2: ast.AST* = ch.get(ci2)
if child2 is None: continue
if child2.kind() != ast.ASTKind.ClassDef: continue
self._RegisterClassMethods(esc_pool, ast.ClassDef*(child2))
# v2 传递闭包:反复重扫直到掩码不再变化(单调 OR-in 必收敛),上限 8 轮兜底。
# 原来固定 2 轮是近似,>2 层的链会漏;这里改为扫到不动点。
@@ -519,8 +529,148 @@ novirtual class StructGenMixin:
if pi >= 0: box[0] = box[0] | (sizet(1) << pi)
return 0
# ---- M2.3 覆盖面扩展:类方法与嵌套函数的摘要 ----
# 键约定(注册与查询必须一致):
# 模块级函数 / 嵌套函数 → **裸名**(与调用点的 func_name 一致);
# 类方法 → "Class.method"(调用点拿得到 class_name,沿 MRO 回退)。
def _EscKey2(self, pool: memhub.MemBuddy*, a: str, b: str) -> str:
"""构造 "a.b" 键(池上字符串,仅供立即哈希,不常驻)"""
if pool is None or a is None or b is None: return None
klen: sizet = string.strlen(a) + string.strlen(b) + 2
kb: str = pool.alloc(klen)
if kb is None: return None
viperlib.snprintf(kb, klen, "%s.%s", a, b)
return kb
def _RegisterClassMethods(self, pool: memhub.MemBuddy*, cd: ast.ClassDef*) -> int:
"""登记一个类里各方法的摘要(键 "Class.method"),并登记方法体内的嵌套函数"""
if cd is None or cd.name is None: return 0
mch: list[ast.AST*]* = cd.children
if mch is None: return 0
mn: sizet = len(mch)
for mi in range(mn):
m_c: ast.AST* = mch.get(mi)
if m_c is None: continue
if m_c.kind() != ast.ASTKind.FunctionDef: continue
mfd: ast.FunctionDef* = ast.FunctionDef*(m_c)
if mfd is None or mfd.name is None: continue
key_m: str = self._EscKey2(pool, cd.name, mfd.name)
if key_m is not None: self._RegisterFuncEscapeSummary(pool, mfd, key_m)
self._RegisterNestedFuncs(pool, mfd.children, 0)
return 0
def _RegisterNestedFuncs(self, pool: memhub.MemBuddy*, stmts: list[ast.AST*]*, depth: int) -> int:
"""登记 stmts 中的嵌套 def(裸名键,与调用点一致),递归进其体与 if/while/for 块"""
if stmts is None or depth > 64: return 0
n: sizet = len(stmts)
for i in range(n):
st: ast.AST* = stmts.get(i)
if st is None: continue
k: int = st.kind()
if k == ast.ASTKind.FunctionDef:
nfd: ast.FunctionDef* = ast.FunctionDef*(st)
if nfd is not None and nfd.name is not None:
self._RegisterFuncEscapeSummary(pool, nfd, nfd.name)
self._RegisterNestedFuncs(pool, nfd.children, depth + 1)
elif k == ast.ASTKind.If:
ifn: ast.If* = ast.If*(st)
self._RegisterNestedFuncs(pool, ifn.children, depth + 1)
self._RegisterNestedFuncs(pool, ifn.orelse, depth + 1)
elif k == ast.ASTKind.While:
whn: ast.While* = ast.While*(st)
self._RegisterNestedFuncs(pool, whn.children, depth + 1)
self._RegisterNestedFuncs(pool, whn.orelse, depth + 1)
elif k == ast.ASTKind.For:
frn: ast.For* = ast.For*(st)
self._RegisterNestedFuncs(pool, frn.children, depth + 1)
self._RegisterNestedFuncs(pool, frn.orelse, depth + 1)
return 0
def get_param_escapes_method(self, pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
"""方法形参逃逸掩码:查 "Class.method",再沿 MRO 祖先查;无记录返回 0。
刻意**不**回退裸名键:方法名与模块级函数名可能重名(如 error/init),回退会把
模块函数的掩码错用到方法调用上,产生误报。查不到即视为无摘要(保守放行)。"""
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(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(key_a)
if m_a != 0: return m_a
mi2 += 1
return 0
def _RescanOne(self, fd: ast.FunctionDef*, key: str) -> int:
"""一次迭代中重扫单个函数:以既有掩码为起点做单调 OR-in"""
if fd is None or key is None or self.EscTable is None or self.EscScratch is None: return 1
idx: int = self._EscLookup(key)
if idx < 0: return 0
gset: sizet* = self.EscScratch
gset[0] = 0
self._CollectGlobalNames(fd.children, gset, 0)
box: sizet* = sizet*(uint64(self.EscScratch) + 520)
box[0] = self.EscTable[idx * 3 + 1]
box[1] = self.EscTable[idx * 3 + 2]
self._ScanEscapes(fd, fd.children, gset, box, 0)
self.EscTable[idx * 3 + 1] = box[0]
self.EscTable[idx * 3 + 2] = box[1]
return 0
def _RescanNested(self, pool: memhub.MemBuddy*, stmts: list[ast.AST*]*, depth: int) -> int:
"""重扫 stmts 里的嵌套 def(裸名键),递归进其体与 if/while/for"""
if stmts is None or depth > 64: return 0
n: sizet = len(stmts)
for i in range(n):
st: ast.AST* = stmts.get(i)
if st is None: continue
k: int = st.kind()
if k == ast.ASTKind.FunctionDef:
nfd: ast.FunctionDef* = ast.FunctionDef*(st)
if nfd is not None and nfd.name is not None:
self._RescanOne(nfd, nfd.name)
self._RescanNested(pool, nfd.children, depth + 1)
elif k == ast.ASTKind.If:
ifn: ast.If* = ast.If*(st)
self._RescanNested(pool, ifn.children, depth + 1)
self._RescanNested(pool, ifn.orelse, depth + 1)
elif k == ast.ASTKind.While:
whn: ast.While* = ast.While*(st)
self._RescanNested(pool, whn.children, depth + 1)
self._RescanNested(pool, whn.orelse, depth + 1)
elif k == ast.ASTKind.For:
frn: ast.For* = ast.For*(st)
self._RescanNested(pool, frn.children, depth + 1)
self._RescanNested(pool, frn.orelse, depth + 1)
return 0
def _RescanClassMethods(self, pool: memhub.MemBuddy*, cd: ast.ClassDef*) -> int:
"""重扫一个类的方法(键 "Class.method")与其方法体内的嵌套函数"""
if cd is None or cd.name is None: return 0
mch: list[ast.AST*]* = cd.children
if mch is None: return 0
mn: sizet = len(mch)
for mi in range(mn):
m_c: ast.AST* = mch.get(mi)
if m_c is None: continue
if m_c.kind() != ast.ASTKind.FunctionDef: continue
mfd: ast.FunctionDef* = ast.FunctionDef*(m_c)
if mfd is None or mfd.name is None: continue
key_m: str = self._EscKey2(pool, cd.name, mfd.name)
if key_m is not None: self._RescanOne(mfd, key_m)
self._RescanNested(pool, mfd.children, 0)
return 0
def _RescanEscapes(self, pool: memhub.MemBuddy*, tree: ast.AST*) -> int:
"""v2 传递闭包的一次迭代:以既有掩码为起点重扫本文件全部顶层函数(单调 OR-in)"""
"""v2 传递闭包的一次迭代:重扫本文件全部函数(模块级 + 类方法 + 嵌套),单调 OR-in"""
if pool is None or tree is None or self.EscTable is None: return 1
ch: list[ast.AST*]* = tree.children
if ch is None: return 1
@@ -528,21 +678,15 @@ novirtual class StructGenMixin:
for ci in range(cn):
child: ast.AST* = ch.get(ci)
if child is None: continue
if child.kind() != ast.ASTKind.FunctionDef: continue
fd: ast.FunctionDef* = ast.FunctionDef*(child)
if fd is None or fd.name is None: continue
idx: int = self._EscLookup(fd.name)
if idx < 0: continue
if self.EscScratch is None: continue
gset: sizet* = self.EscScratch
gset[0] = 0
self._CollectGlobalNames(fd.children, gset, 0)
box: sizet* = sizet*(uint64(self.EscScratch) + 520)
box[0] = self.EscTable[idx * 3 + 1]
box[1] = self.EscTable[idx * 3 + 2]
self._ScanEscapes(fd, fd.children, gset, box, 0)
self.EscTable[idx * 3 + 1] = box[0]
self.EscTable[idx * 3 + 2] = box[1]
kc: int = child.kind()
if kc == ast.ASTKind.FunctionDef:
fd: ast.FunctionDef* = ast.FunctionDef*(child)
if fd is None or fd.name is None: continue
self._RescanOne(fd, fd.name)
self._RescanNested(pool, fd.children, 0)
elif kc == ast.ASTKind.ClassDef:
cd_r: ast.ClassDef* = ast.ClassDef*(child)
self._RescanClassMethods(pool, cd_r)
return 0
def _EscMaskSum(self) -> sizet: