feat(gen): 函数生命期摘要表 + 逃逸 walker(M2.3 v1b 步骤1-3)

- StructGenMixin:EscTable/EscCount(追加在类末尾,避免偏移漂移);register_file_funcs 每文件重置并逐函数算摘要。 - 逃逸 walker:return p / global 赋值 / obj.f 赋值,递归 if/while/for;不进嵌套 def/class;形参访问逐级显式重转(AST 退化)。 - 表内只存 u64 名哈希(djb2-64),不解引用字符串指针,消除槽内坏指针导致的 strcmp 崩溃。 - 表从 PermPool 分配并 memset。 - GenApi:get_param_escapes/get_ret_borrow 查询入口。调用点检查(步骤4)待接。
This commit is contained in:
TermiNexus committed 2026-10-08 13:29:28 +08:00
1 parent a6e1957768
commit 4ce577d8fb
2 files changed
+202 -3

No files matched your search

+13
View File
@@ -285,6 +285,19 @@ def is_file_func(name: str) -> int:
return 0
# ---------------- 生命期摘要查询(M2.3 v1b, delegates to StructGenMixin) ----------------
def get_param_escapes(name: str) -> int:
"""形参逃逸掩码(第 k 位=第 k 个形参逃逸);无记录返回 0"""
if _g_gen is not None: return _g_gen.StructGen.get_param_escapes(name)
return 0
def get_ret_borrow(name: str) -> int:
"""返回借用标记(1=该函数直接返回某形参);无记录返回 0"""
if _g_gen is not None: return _g_gen.StructGen.get_ret_borrow(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"""
+189 -3
View File
@@ -101,13 +101,20 @@ novirtual class StructGenMixin:
# Top-level FunctionDef name table for this file (see register_file_funcs).
# A stub-injected bare-name declaration (extern/state/export, no SHA1 prefix) that matches a local declaration would take the name first and make the local declaration silently dropped in forward_declare_functions; the injection side (VpiReader._inject_function_declare) skips these names.
FileFuncNames: hashtable.HashTable*
FileFuncNames: hashtable.HashTable*
# --- Name | type index (fast lookup path; falls back to a linear scan when the index is untrusted) ---
StructNameIdx: sizet* # name -> first registration index (u64 slot: 0 = empty, else index+1)
StructTypeIdx: sizet* # Ty pointer value -> first registration index (same layout)
StructIdxCap: int # index slot count (power of two, > 2*STRUCT_MAX)
StructIdxBuilt: int # indexed entry count (-1 = untrusted; trusted only when == StructCount)
StructIdxBuilt: int # indexed entry count (-1 = untrusted; trusted only when == StructCount)
# 生命期摘要(M2.3 v1b):本文件函数 → 形参逃逸掩码 / 返回借用标记。
# **必须追加在类字段末尾**——新增字段插在中间会改变其后字段的偏移(本仓约定,见
# VarEntry/FieldEntry 的 "Appended at the end so existing field offsets are unchanged")。
# 池数组 + 线性查找;每项 3 槽:name(池上 str 地址) | mask(第 k 位=第 k 形参逃逸) | ret_borrow
EscTable: sizet*
EscCount: int
def InitStructGen(self) -> int:
"""Initialize struct-mixin fields; called inline from subclass constructors."""
@@ -123,6 +130,8 @@ novirtual class StructGenMixin:
self.FileClassNames = None
self.FileModuleSha1 = None
self.FileFuncNames = None
self.EscTable = None
self.EscCount = 0
self.StructNameIdx = None
self.StructTypeIdx = None
self.StructIdxCap = 0
@@ -244,6 +253,17 @@ novirtual class StructGenMixin:
if ch is None: return
if self.FileFuncNames is None: self.FileFuncNames = hashtable.HashTable(pool)
if self.FileFuncNames is None: return
# 生命期摘要表:每次进入本文件时重置(v1b 只覆盖同文件;跨文件属 v3)
# 两个要点(对照 init_struct_table):
# 1) 必须从 **PermPool** 分配——临时池在 Phase1 会被 reset_to(mark) 回收,同块内存
# 被复用后我们会握着已变脏的旧指针(ASan 抓不到,表现为槽里读出垃圾 → strcmp 崩)。
# 2) 必须显式清零:未写入的槽若不是 0,_EscLookup 的 strcmp 会对脏指针解引用。
esc_cap: sizet = 1024
esc_pool: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if esc_pool is None: esc_pool = pool
self.EscTable = sizet*(esc_pool.alloc(esc_cap * 24))
if self.EscTable is not None: string.memset(self.EscTable, 0, esc_cap * 24)
self.EscCount = 0
cn: sizet = len(ch)
ci: sizet
for ci in range(cn):
@@ -259,12 +279,178 @@ novirtual class StructGenMixin:
if nm_buf is None: continue
string.strcpy(nm_buf, fd.name)
self.FileFuncNames.set_int(nm_buf, 1)
# 生命期摘要(M2.3 v1b):表内只存 u64 名哈希——不存字符串指针,
# 从根上消除"槽里是坏指针 → strcmp 崩在 0xffff…"这一整类问题。
self._RegisterFuncEscapeSummary(esc_pool, fd, fd.name)
def is_file_func(self, name: str) -> int:
"""Return 1 when the bare name is a top-level function declared in this file."""
if name is None or self.FileFuncNames is None: return 0
return self.FileFuncNames.__contains__(name)
# ============================================================
# 生命期摘要(M2.3 v1b):本文件函数 → 形参逃逸掩码 / 返回借用标记
# 覆盖:`return p`、`global G` 下的 `G = p`、`obj.f = p`(含 AnnAssign);
# 递归进 if/while/for 的体与 else;**不**进嵌套 def/class。
# 不做传递闭包(v2)、不做跨模块(v3)。
# ============================================================
def _EscLookup(self, name: str) -> int:
"""线性查找摘要项下标;未找到返回 -1"""
if name is None or self.EscTable is None: return -1
h: u64 = self._NameHash(name)
cap: int = 1024
i: int = 0
while i < self.EscCount and i < cap:
# 槽 0 是名哈希(u64);未写入的槽为 0,与任何非零哈希都不相等,无需判空
if u64(self.EscTable[i * 3 + 0]) == h: return i
i += 1
return -1
def _NameHash(self, name: str) -> u64:
"""djb2-64(小立即数,溢出按 u64 回绕):把函数名压成表键"""
h: u64 = 5381
if name is None: return h
n: sizet = string.strlen(name)
i: sizet = 0
while i < n:
h = h * 33 + u64(uint8(name[i]))
i += 1
return h
def get_param_escapes(self, name: str) -> int:
"""形参逃逸掩码:第 k 位=第 k 个形参逃逸;无记录返回 0"""
idx: int = self._EscLookup(name)
if idx < 0: return 0
return int(self.EscTable[idx * 3 + 1])
def get_ret_borrow(self, name: str) -> int:
"""返回借用标记:1=该函数直接返回某形参;无记录返回 0"""
idx: int = self._EscLookup(name)
if idx < 0: return 0
return int(self.EscTable[idx * 3 + 2])
def _RegisterFuncEscapeSummary(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, key: str) -> int:
"""扫函数体登记摘要;key 必须是与 EscTable 同池的字符串"""
if pool is None or fd is None or key is None or self.EscTable is None: return 1
if self._EscLookup(key) >= 0: return 0
if self.EscCount >= 1024: return 1
gset: hashtable.HashTable* = hashtable.HashTable(pool)
if gset is not None: self._CollectGlobalNames(fd.children, gset, 0)
box: sizet* = sizet*(pool.alloc(16))
if box is None: return 1
box[0] = 0
box[1] = 0
self._ScanEscapes(fd, fd.children, gset, box, 0)
slot: int = self.EscCount
self.EscTable[slot * 3 + 0] = sizet(self._NameHash(key))
self.EscTable[slot * 3 + 1] = box[0]
self.EscTable[slot * 3 + 2] = box[1]
self.EscCount = slot + 1
return 0
def _CollectGlobalNames(self, stmts: list[ast.AST*]*, gset: hashtable.HashTable*, depth: int) -> int:
"""收集本函数(含嵌套块)里 `global` 声明的名字"""
if stmts is None or gset 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.Global:
gn: ast.Global* = ast.Global*(st)
if gn is not None and gn.names is not None:
cg: sizet = len(gn.names)
for j in range(cg):
nnd: ast.AST* = gn.names.get(j)
if nnd is not None and nnd.kind() == ast.ASTKind.Name:
nnm: ast.Name* = ast.Name*(nnd)
if nnm.id is not None: gset.set_int(nnm.id, 1)
elif k == ast.ASTKind.If:
ifn: ast.If* = ast.If*(st)
self._CollectGlobalNames(ifn.children, gset, depth + 1)
self._CollectGlobalNames(ifn.orelse, gset, depth + 1)
elif k == ast.ASTKind.While:
whn: ast.While* = ast.While*(st)
self._CollectGlobalNames(whn.children, gset, depth + 1)
self._CollectGlobalNames(whn.orelse, gset, depth + 1)
elif k == ast.ASTKind.For:
frn: ast.For* = ast.For*(st)
self._CollectGlobalNames(frn.children, gset, depth + 1)
self._CollectGlobalNames(frn.orelse, gset, depth + 1)
return 0
def _ParamIndex(self, fd: ast.FunctionDef*, value: ast.AST*) -> int:
"""值节点是形参名时返回其下标,否则 -1"""
if value is None or value.kind() != ast.ASTKind.Name: return -1
vnm: ast.Name* = ast.Name*(value)
if vnm.id is None: return -1
# 形参注解可能退化为 ast.AST*(AST 只有 children,没有 args)→ 使用点逐级显式重转
fdef: ast.FunctionDef* = ast.FunctionDef*(fd)
if fdef is None: return -1
# FunctionDef.args 的成员类型在 TPC 侧只登记到 AST*,二级 .args 需再转一次
agr: ast.Arguments* = ast.Arguments*(fdef.args)
if agr is None or agr.args is None: return -1
pargs: list[ast.AST*]* = agr.args
if pargs is None: return -1
cnt: sizet = len(pargs)
for i in range(cnt):
an: ast.AST* = pargs.get(i)
if an is None: continue
pa: ast.Arg* = ast.Arg*(an)
if pa.arg is not None and string.strcmp(pa.arg, vnm.id) == 0: return int(i)
return -1
def _MarkEscapingStore(self, fd: ast.FunctionDef*, target: ast.AST*, value: ast.AST*, gset: hashtable.HashTable*, box: sizet*) -> int:
"""`target = 形参`:若 target 是对象字段或已声明的全局,则该形参逃逸"""
pi: int = self._ParamIndex(fd, value)
if pi < 0 or target is None or box is None: return 0
tk: int = target.kind()
if tk == ast.ASTKind.Attribute:
box[0] = box[0] | (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 gset.__contains__(tnm.id) != 0:
box[0] = box[0] | (sizet(1) << pi)
return 0
def _ScanEscapes(self, fd: ast.FunctionDef*, stmts: list[ast.AST*]*, gset: hashtable.HashTable*, box: sizet*, depth: int) -> int:
"""递归扫描:return p / G = p / obj.f = p;只进 if/while/for,不进嵌套 def/class"""
if fd is None or stmts is None or box 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.Return:
rt: ast.Return* = ast.Return*(st)
pi: int = self._ParamIndex(fd, rt.value)
if pi >= 0:
box[0] = box[0] | (sizet(1) << pi)
box[1] = 1
elif k == ast.ASTKind.Assign:
asg: ast.Assign* = ast.Assign*(st)
if asg.targets is not None:
nt: sizet = len(asg.targets)
for t in range(nt):
self._MarkEscapingStore(fd, asg.targets.get(t), asg.value, gset, box)
elif k == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(st)
self._MarkEscapingStore(fd, aa.target, aa.value, gset, box)
elif k == ast.ASTKind.If:
ifn: ast.If* = ast.If*(st)
self._ScanEscapes(fd, ifn.children, gset, box, depth + 1)
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)
elif k == ast.ASTKind.For:
frn: ast.For* = ast.For*(st)
self._ScanEscapes(fd, frn.children, gset, box, depth + 1)
self._ScanEscapes(fd, frn.orelse, gset, box, depth + 1)
return 0
def get_file_module_sha1(self) -> str:
"""Return the module SHA1 of the file being translated (None if unregistered)."""
return self.FileModuleSha1