From 4ce577d8fb7f4ee70c128125a0325440ea3ca938 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 8 Oct 2026 13:29:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(gen):=20=E5=87=BD=E6=95=B0=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E6=9C=9F=E6=91=98=E8=A6=81=E8=A1=A8=20+=20=E9=80=83?= =?UTF-8?q?=E9=80=B8=20walker=EF=BC=88M2.3=20v1b=20=E6=AD=A5=E9=AA=A41-3?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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)待接。 --- App/lib/core/Gen/GenApi.vp | 13 ++ App/lib/core/Gen/StructGenMixin.vp | 192 ++++++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 3 deletions(-) diff --git a/App/lib/core/Gen/GenApi.vp b/App/lib/core/Gen/GenApi.vp index a0910cc..0b4fe13 100644 --- a/App/lib/core/Gen/GenApi.vp +++ b/App/lib/core/Gen/GenApi.vp @@ -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""" diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 94c328f..8734308 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -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