From dd24dc670fa74761a387716c0659226870491548 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 9 Oct 2026 11:51:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(gen):=20P2=20=E5=A4=B1=E6=95=88=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=94=B9=E5=AD=98=E7=8B=AC=E7=AB=8B=E6=A7=BD=E8=A1=A8?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E7=BB=99=20VarEntry=20=E5=8A=A0?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现象:任何工程编译都在函数形参注册刚结束处 access-violation(pc=0,逐字节可复现)。定位:逐层探针把崩点压到 FuncGenDef 的 pre_scan 段,而该段对 Check(body 仅 if+return)应为空操作,唯一变量是 VarEntry 这个热结构被加了 Freed 字段。修法:撤销 VarEntry.Freed,改用 VarGenMixin 的独立失效槽表 _g_freed_slots(键 = 变量槽 alloca,整次翻译内唯一,故全局累积、无需按函数 reset/restore);_FreedMark/_FreedCheck/_FreedClearSlot 三个 API 替换字段读写,P2 语义不变。同时清除全部 TEMP 探针。 --- App/lib/Phase/Phase2/__init__.vp | 2 - App/lib/Stub/StubGen/Converter.vp | 2 - App/lib/core/Gen/Call/CallGenCall.vp | 5 +- App/lib/core/Gen/Expr/ExprGenName.vp | 3 +- App/lib/core/Gen/Expr/ExprGenOps.vp | 3 +- App/lib/core/Gen/Func/FuncGenDef.vp | 14 ++---- App/lib/core/Gen/GenTypes.vp | 6 +-- App/lib/core/Gen/Stmt/StmtGenMixin.vp | 2 - App/lib/core/Gen/Translator.vp | 4 -- App/lib/core/Gen/VarGenMixin.vp | 70 ++++++++++++++++++++------- 10 files changed, 61 insertions(+), 50 deletions(-) diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index b17bec2..2134031 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -840,8 +840,6 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou done_flags[i] = 1 completed_this_pass += 1 continue - # [TEMP dbg] 阶段日志:定位 Phase A 崩溃点(定位后移除) - VLogger.info(f"PhaseA translating: {ent.Path}", "PhaseA") # Compute the package name (the directory part relative to source_dir) src_dir_len_pa: sizet = string.strlen(source_dir) pkg_pa: str = None diff --git a/App/lib/Stub/StubGen/Converter.vp b/App/lib/Stub/StubGen/Converter.vp index d56bf24..14d56d0 100644 --- a/App/lib/Stub/StubGen/Converter.vp +++ b/App/lib/Stub/StubGen/Converter.vp @@ -661,8 +661,6 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, cls: ast.ClassDef*, inden # Returns bytes written (0 on failure); rel_path should be absolute for error attribution. def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf: bytes, buf_size: sizet) -> sizet: """Generate .vpi stub content from an AST into buf""" - # [TEMP dbg] 阶段日志:进入 vpi 生成(定位崩溃点,定位后移除) - VLogger.info(f"vpi.gen: {rel_path}", "StubGen") if tree is None or buf is None or buf_size == 0: return 0 # Error attribution: point "current file" at the file being stubbed. Callers must pass diff --git a/App/lib/core/Gen/Call/CallGenCall.vp b/App/lib/core/Gen/Call/CallGenCall.vp index a67f0da..975f616 100644 --- a/App/lib/core/Gen/Call/CallGenCall.vp +++ b/App/lib/core/Gen/Call/CallGenCall.vp @@ -535,10 +535,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase): if arg_node.kind() != ast.ASTKind.Name: return 0 nm: ast.Name* = ast.Name*(arg_node) if nm.id is None: return 0 - ent: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) - if ent is None: return 0 - ent.Freed = 1 - return 1 + return self.Gen.VarGen._FreedMark(self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)) # _translate_method_call - translate obj.method(args) into a call with the # object pointer (obj_ptr, an alloca of Ptr(StructTy)) as the self argument. diff --git a/App/lib/core/Gen/Expr/ExprGenName.vp b/App/lib/core/Gen/Expr/ExprGenName.vp index 8a9898f..d6d9210 100644 --- a/App/lib/core/Gen/Expr/ExprGenName.vp +++ b/App/lib/core/Gen/Expr/ExprGenName.vp @@ -42,8 +42,7 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase): # P2 willFree:读取已被"消费"的变量(其所有权被 @life(willFree(...)) 标注的调用夺取)→ fail-fast。 # 只在**读**路径检查;对同名变量重新赋值会清除失效态(见 AssignGenMixin)。 if trans is not None: - ent_rd: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_id) - if ent_rd is not None and ent_rd.Freed != 0: + if self.Gen.VarGen._FreedCheck(self.Gen.VarGen.lookup_var(trans.SymTab, nm_id)) != 0: self.Gen.TypeGenErr.fatal_error(node, f"cannot use '{nm_id}': its value was released by a call annotated with @life(willFree(...)) and is no longer valid", "LifetimeError") # CDefine compile-time constant: return the constant (float -> double, int -> i32). # Existing variables win over CDefine table entries injected from vpi stubs (lookup_var guard). diff --git a/App/lib/core/Gen/Expr/ExprGenOps.vp b/App/lib/core/Gen/Expr/ExprGenOps.vp index 1e20b37..1f2bd6b 100644 --- a/App/lib/core/Gen/Expr/ExprGenOps.vp +++ b/App/lib/core/Gen/Expr/ExprGenOps.vp @@ -585,8 +585,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase): nm_ao: ast.Name* = ast.Name*(uo.operand) if nm_ao.id is not None: # P2 willFree:对已失效变量取地址 → fail-fast(与读取路径同一口径) - ve_ao: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_ao.id) - if ve_ao is not None and ve_ao.Freed != 0: + if self.Gen.VarGen._FreedCheck(self.Gen.VarGen.lookup_var(trans.SymTab, nm_ao.id)) != 0: self.Gen.TypeGenErr.fatal_error(node, f"cannot take the address of '{nm_ao.id}': its value was released by a call annotated with @life(willFree(...)) and is no longer valid", "LifetimeError") # self/cls object reference: return the object pointer itself (Ptr(Struct)), not the pointer variable's address (Ptr(Ptr(Struct))). if string.strcmp(nm_ao.id, "self") == 0 or string.strcmp(nm_ao.id, "cls") == 0: diff --git a/App/lib/core/Gen/Func/FuncGenDef.vp b/App/lib/core/Gen/Func/FuncGenDef.vp index 3c948ca..aaf5c55 100644 --- a/App/lib/core/Gen/Func/FuncGenDef.vp +++ b/App/lib/core/Gen/Func/FuncGenDef.vp @@ -254,9 +254,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): def translate_function_def(self, trans: HT.Translator*, node: ast.AST*) -> int: """Translate a function definition; returns the number of added variables (usually 0).""" fd: ast.FunctionDef* = ast.FunctionDef*(node) - if fd is None or fd.name is None: return 0 - # [TEMP dbg] 阶段日志:定位崩溃到具体函数(定位后移除) - VLogger.info(f"TR.fn: {fd.name}", "TR") + if fd is None or fd.name is None: return 0 # PEP 695 generic function templates are not emitted directly (same rule as forward_declare_functions): # clones are emitted at call sites by _specialize_generic_function; here we only ensure the template is registered. @@ -386,7 +384,6 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): # Record value-reference (&T / &temp T) parameter modes for call-site auto-address. self._register_func_auto_addr_modes(trans, mangled_name, fd) - VLogger.info("TR.p1.auto_addr.done", "TR") # Register the CExport function in the global table (for cross-module calls). t.CExport definitions use bare # names (@strlen); cross-module calls consult the table to use the bare name instead of @{sha1}.func. @@ -760,8 +757,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): GenApi.clear_scope_names(trans) # Reset the in-chain nonlocal heap prebuild flag (built once per function; multiple nested function definitions # share one heap, preventing repeated malloc + double VarEntry redirection). - trans._nl_chain_done = 0 - VLogger.info("TR.p1c.state.saved", "TR") + trans._nl_chain_done = 0 # Pre-scan the body: 1) Global/Nonlocal declarations first (Python semantics: they apply to the whole function # scope regardless of position, so register before any assignment); 2) AnnAssign to create local allocas early. @@ -775,17 +771,13 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): stmt: ast.AST* = body.get(bi) if stmt is not None: self.Gen.StmtGenDecl.pre_scan_allocas(trans, stmt) - VLogger.info("TR.p1d.prescan.done", "TR") - # M3 owned slife(§4.2 函数级 scope guard):进入函数体前把登记区间起点抬到当前栈顶。 # 表是函数间共享的一段栈式区间,嵌套函数各自独立;退出时由 _SlifeRestore 弹回外层起点。 # 放在这里(而非更早)是因为上方存在多条提前返回分支(泛型模板/async/extern), # 只有从这里开始才保证"有 reset 必有 restore"。 - old_slife_base: int = self.Gen.VarGen._SlifeReset(pool) - VLogger.info("TR.p2.slife_reset.done", "TR") + old_slife_base: int = self.Gen.VarGen._SlifeReset(pool) # Translate the function body - VLogger.info("TR.p3.body.begin", "TR") if body is not None: bn2: sizet = len(body) for bi2 in range(bn2): diff --git a/App/lib/core/Gen/GenTypes.vp b/App/lib/core/Gen/GenTypes.vp index a4422e4..2259ef7 100644 --- a/App/lib/core/Gen/GenTypes.vp +++ b/App/lib/core/Gen/GenTypes.vp @@ -79,9 +79,9 @@ class VarEntry: # 生命期标签(LifeTag,见文件头 define LIFE_*):该变量**当前值**的生命期。 # 追加在末尾以保持既有字段偏移不变。默认 LIFE_UNKNOWN(未登记 = 保守放行)。 Life: int - # P2 willFree:1=该变量已被某个带 @life(willFree(...)) 标注的调用"消费",其后再读/取址/传参 - # 即 LifetimeError(fail-fast)。追加在末尾以保持既有字段偏移不变;add_var 显式置 0。 - Freed: int + # 注:P2 的"已失效"状态**不放在这里** —— 给这个热结构加字段会改变布局、牵连整条变量注册 + # 路径(现场:任何工程编译时都在"函数形参注册刚结束"处 access-violation)。改由 + # VarGenMixin 的独立失效槽表 _g_freed_slots 保存(键 = 变量槽 alloca,槽本身唯一)。 # ============================================================ # Scope - scope node (formerly HandlesVar.Scope) diff --git a/App/lib/core/Gen/Stmt/StmtGenMixin.vp b/App/lib/core/Gen/Stmt/StmtGenMixin.vp index 403759f..2102d0a 100644 --- a/App/lib/core/Gen/Stmt/StmtGenMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenMixin.vp @@ -27,8 +27,6 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): def translate_stmt(self, trans: HT.Translator*, node: ast.AST*) -> int: """Translate a single statement with a depth guard; returns the number of new variables""" - # [TEMP dbg] 阶段日志:语句级(定位崩溃到具体语句,定位后移除) - if node is not None: VLogger.info(f"stmt.kind={node.kind()}", "TR") if trans is not None: # --traceback: statement-level DWARF location (no-op outside a DI-attached # define). Every instruction then carries ", !dbg !N". diff --git a/App/lib/core/Gen/Translator.vp b/App/lib/core/Gen/Translator.vp index 6349d3a..673a146 100644 --- a/App/lib/core/Gen/Translator.vp +++ b/App/lib/core/Gen/Translator.vp @@ -381,8 +381,6 @@ novirtual class Translator: """Translate the AST into LLVM IR.""" if tree is None: return 1 if _mbuddy is None: return 1 - # [TEMP dbg] 阶段日志:定位崩溃点(定位后移除) - VLogger.info(f"TR.enter sha1={self.ModuleSha1}", "TR") pool: memhub.MemBuddy* = _mbuddy @@ -455,8 +453,6 @@ novirtual class Translator: # (现场:Test/NewLifetimeTest 未 import stdlib,拿不到 stdlib vpi 的 declare)。 free_func: mlirlite.Function* = mlirlite.create_declare(decl_pool, mod, "free", void_ty_decl) if free_func is not None: mlirlite.add_param(decl_pool, free_func, i8_ptr_ty, "p") - # [TEMP dbg] 阶段日志:模块前言完成(定位崩溃点,定位后移除) - VLogger.info("TR.prelude.done", "TR") # Check whether the user defined a main function has_user_main: int = 0 diff --git a/App/lib/core/Gen/VarGenMixin.vp b/App/lib/core/Gen/VarGenMixin.vp index f092445..9af2ee8 100644 --- a/App/lib/core/Gen/VarGenMixin.vp +++ b/App/lib/core/Gen/VarGenMixin.vp @@ -44,6 +44,12 @@ define SCOPE_BLOCK = 2 # 0 号槽覆写外层登记项,且内层出口会把外层的对象一起 free 掉。 # 登记表容量(P5:128 → 256)。超限时 **fail-fast**(不再静默退化为"不登记 = 不释放"的泄漏)。 define _SLIFE_MAX = 256 +# P2 willFree 失效槽表:_g_freed_slots[0.._g_freed_count) 是"已被标注调用消费过的变量槽 alloca"。 +# **刻意不放进 VarEntry**(给热结构加字段会动布局、牵连整条变量注册路径)。 +# 键 = alloca,槽在整次翻译中唯一,所以是"全局累积表",无需按函数 reset/restore。 +define _FREED_MAX = 4096 +_g_freed_slots: sizet* = None +_g_freed_count: int = 0 _g_slife_owned: sizet* = None _g_slife_count: int = 0 _g_slife_top: int = 0 @@ -288,41 +294,69 @@ novirtual class VarGenMixin: """Add a variable to the variable table""" if name is None or alloca is None or vars_ptr is None: return 1 if var_count >= HT.MAX_VARS: return 1 - VLogger.info("dbg.a1.enter", "TR") entry_size: sizet = GenTypes.VarEntry.__sizeof__() entry_addr: uint64 = uint64(vars_ptr) + var_count * entry_size entry: GenTypes.VarEntry* = GenTypes.VarEntry*(entry_addr) - VLogger.info(f"dbg.a2.entry esz={entry_size} cnt={var_count}", "TR") + # Deep-copy the variable name into stdlib (survives pool reset): storing the AST Name.id pointer lets + # later allocations overwrite it, so find_var's strcmp silently misses and rebuilds a degenerate alloca. name_len: sizet = string.strlen(name) - VLogger.info(f"dbg.a3.namelen={name_len}", "TR") name_copy: bytes = stdlib.malloc(name_len + 1) if name_copy is None: return 1 string.strcpy(name_copy, name) - VLogger.info("dbg.a4.copied", "TR") entry.Name = name_copy - VLogger.info("dbg.a5.name", "TR") entry.Alloca = alloca entry.Used = 1 + # Explicitly zero the const flag (do not rely on the entry's zero initialization): set_var_const + # sets it to 1 only for real const objects, so zeroing avoids false positives. entry.IsConst = 0 - VLogger.info("dbg.a6.const", "TR") + # 生命期标签显式初始化:未登记 = LIFE_UNKNOWN(保守放行)。 entry.Life = GenTypes.LIFE_UNKNOWN - VLogger.info("dbg.a7.life", "TR") - # P2 willFree 的 entry.Freed **不在这里写**:init_vars 已对整块 memset 清零(见 init_vars), - # 逐个字段写末端偏移在"结构体刚加字段"时容易踩到;统一依赖整块清零。 # [S3 dual write] Value-type Spec (alloca.Ty is Ptr(value type), so take its pointee) entry.Spec = self._spec_of_var_alloca(alloca) - VLogger.info("dbg.a8.spec", "TR") + return 0 + + # ---- P2 willFree 失效槽表 API(不改 VarEntry 布局) ---- + def _FreedMark(self, slot: mlirlite.Value*) -> int: + """把变量槽标为"已失效"(被 @life(willFree(...)) 标注的调用消费过);1=已标记 / 0=未处理""" + global _g_freed_slots, _g_freed_count + if slot is None: return 0 + if self._FreedCheck(slot) != 0: return 1 + if _g_freed_slots is None: + _g_freed_slots = sizet*(stdlib.malloc(_FREED_MAX * 8)) + if _g_freed_slots is None: return 0 + if _g_freed_count >= _FREED_MAX: + self.Gen.TypeGenErr.fatal_error(None, "the willFree 'released variable' table is full (capacity 4096): too many released variables in one translation", "LifetimeError") + return 0 + _g_freed_slots[_g_freed_count] = sizet(slot) + _g_freed_count += 1 + return 1 + + def _FreedCheck(self, slot: mlirlite.Value*) -> int: + """该变量槽是否已被标为失效;1=已失效(其后再读/取址/传参即 LifetimeError)/ 0=有效""" + if slot is None or _g_freed_slots is None: return 0 + i: int = 0 + while i < _g_freed_count: + if uint64(_g_freed_slots[i]) == uint64(slot): return 1 + i += 1 + return 0 + + def _FreedClearSlot(self, slot: mlirlite.Value*) -> int: + """清除某槽的失效态(对同名变量重新赋值后调用);1=清除过 / 0=本来不在表里""" + global _g_freed_count + if slot is None or _g_freed_slots is None: return 0 + i: int = 0 + while i < _g_freed_count: + if uint64(_g_freed_slots[i]) == uint64(slot): + _g_freed_count -= 1 + _g_freed_slots[i] = _g_freed_slots[_g_freed_count] + return 1 + i += 1 return 0 def clear_var_freed(self, symtab: GenTypes.SymbolTable*, name: str) -> int: - """P2 willFree:对同名变量重新赋值后清除失效态;1=清除过 / 0=无该项 - - 变量已存在时赋值走 get_or_create_sym(不重建 VarEntry),所以失效态必须显式清除—— - 否则 `p = malloc(8); free(p); p = malloc(16); Use(p)` 会被误报。""" - e: GenTypes.VarEntry* = self.lookup_var_entry(symtab, name) - if e is None: return 0 - e.Freed = 0 - return 1 + """P2 willFree:对同名变量重新赋值后清除失效态;1=清除过 / 0=无该项""" + if symtab is None or name is None: return 0 + return self._FreedClearSlot(self.lookup_var(symtab, name)) # _spec_of_var_alloca - build a TypeSpec from the alloca's value type (S3 dual write). # alloca.Ty is Ptr(var_ty), so the pointee is the value type; None means failure or an unsupported type.