From d0ffb094c2a869d66e3ce1603dcd495e27bf4f7e Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 9 Oct 2026 20:49:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(lifetime):=20=C2=A713=20NLL=E2=80=94?= =?UTF-8?q?=E2=80=94=E9=BB=98=E8=AE=A4=E7=94=9F=E5=91=BD=E6=9C=9F=E7=9A=84?= =?UTF-8?q?=20new=20heap=20=E8=AF=81=E6=98=8E=E5=B8=A7=E5=86=85=E5=B0=81?= =?UTF-8?q?=E9=97=AD=E6=97=B6=E9=99=8D=E7=BA=A7=E4=B8=BA=E5=B8=A7=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit StructGenMixin: 新增 NllFrameConfined 及遍历器(_NllStmts/_NllExprUnsafe/_NllMentions/_NllInSet/_NllAdd/_NllCalleeEscMask)+ has_param_escapes(区分"有记录=权威不逃逸"与"无记录=判不定");别名集只增不减故单遍即可,if/while/for 全走、 不进嵌套 def/class;认不出的语句"不提及别名才放行";取址/实参位逃逸/return/长命目标一律按外泄拒绝。 CallGenCtor._new_is_nll_demotable:只降级"堆且未显式指定生命期"的 new;AssignGenMixin.propagate_life 判据成立则 life=FRAME(于是调用点同命裁剪放过 g(l,h)),_slife_note_assign 接 target_name、判据成立按托管堆 对象登记 → 复用 slife 的作用域出口释放(手动 free/del 也会被既有检查挡住)。 三条函数体翻译入口记录 NllCurFunc(谓词要遍历当前函数体),与 traceback 推帧同一处。 注:谓词按 §13.2 保守优先,判不定一律维持 HEAP;本提交未编译验证。 --- App/lib/core/Gen/AssignGenMixin.vp | 18 +- App/lib/core/Gen/Call/CallGenCtor.vp | 13 ++ App/lib/core/Gen/Class/ClassGenVTable.vp | 4 + App/lib/core/Gen/Stmt/StmtGenMixin.vp | 4 + App/lib/core/Gen/StructGenMixin.vp | 206 ++++++++++++++++++++++- App/lib/core/Gen/TranslatorGenMixin.vp | 4 + 6 files changed, 243 insertions(+), 6 deletions(-) diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 124f183..a6417b3 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -711,6 +711,11 @@ novirtual class AssignGenMixin: if GenApi.is_global_name(trans, tnm.id) != 0: return 1 if GenApi.is_nonlocal_name(trans, tnm.id) != 0: return 1 life: int = self.life_of_expr(trans, rhs_node, target_ty) + # §13 NLL:默认生命期的 `new heap T()` 若证明在本函数内封闭(不外泄)→ 按帧生命期处理。 + # 与 `new heap slife` 同口径(生命期 FRAME + 作用域出口释放,见 _slife_note_assign)。 + if life == GenTypes.LIFE_HEAP: + if self.Gen.CallGenCtor._new_is_nll_demotable(trans, rhs_node, tnm.id) != 0: + life = GenTypes.LIFE_FRAME self.Gen.VarGen.set_var_life(trans.SymTab, tnm.id, life) return 0 @@ -762,12 +767,17 @@ novirtual class AssignGenMixin: # ② 其它 RHS(`new stack` / `new … hlife` / 裸名别名 `q = p` / 外来指针 / None): # 槽已登记时交还所有权(释放旧拥有对象 + 影子置 0)——绝不把外来指针当自己的对象释放。 # **必须在值写入之前调用**:覆盖释放要读到槽里的旧值,晚于写入就变成"释放刚写进去的对象"。 - def _slife_note_assign(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, target_slot: mlirlite.Value*, rhs_node: ast.AST*) -> int: + def _slife_note_assign(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, target_slot: mlirlite.Value*, rhs_node: ast.AST*, target_name: str = None) -> int: """M3:赋值处的 slife 所有权记账;返回 1=该槽此刻是编译器拥有的 slife 绑定""" if target_slot is None or rhs_node is None or trans is None: return 0 if rhs_node.kind() == ast.ASTKind.New: nn: ast.New* = ast.New*(rhs_node) - if self.Gen.CallGenCtor._new_is_owned_heap(nn) != 0: + owned: int = self.Gen.CallGenCtor._new_is_owned_heap(nn) + if owned == 0: + # §13 NLL:判据成立时该 `new heap` 也按"编译器托管的堆对象"处理 —— + # 生命期已是 FRAME(propagate_life 同一谓词),这里补上**出口释放**这一半。 + owned = self.Gen.CallGenCtor._new_is_nll_demotable(trans, rhs_node, target_name) + if owned != 0: sh: mlirlite.Value* = self.Gen.VarGen._SlifeEnsureOwned(pool, builder, target_slot) if sh is None: return 0 self._SlifeDropOwned(pool, builder, mod, trans, target_slot) @@ -1989,7 +1999,7 @@ novirtual class AssignGenMixin: vol_laa = 1 # M3 所有权记账:必须**先**记账再写值——覆盖释放要读到槽里的旧值 # (`p: T* = new heap slife …` → 释放旧对象 + 影子置 1;`q: T* = p` → 交还所有权) - self._slife_note_assign(pool, builder, mod, trans, alloca, aa.value) + self._slife_note_assign(pool, builder, mod, trans, alloca, aa.value, nm.id) mlirlite.build_store(builder, rhs_val, alloca, vol_laa) # P2 willFree:带注解声明/赋值后清除失效态(新变量本就为 0;复用槽兜底) self.Gen.VarGen.clear_var_freed(trans.SymTab, nm.id) @@ -2548,7 +2558,7 @@ novirtual class AssignGenMixin: vol_las: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) # M3 所有权记账:必须先记账再写值(覆盖释放要读到旧值); # `p = new heap slife …` → 释放旧对象 + 影子置 1;`q = p` / 外来指针 → 交还所有权 - self._slife_note_assign(pool, builder, mod, trans, alloca, asgn.value) + self._slife_note_assign(pool, builder, mod, trans, alloca, asgn.value, nm.id) mlirlite.build_store(builder, store_val, alloca, vol_las) # P2 willFree:对同名变量重新赋值即重新获得有效值,清除失效态 # (新变量由 add_var 置 0;已存在变量走这里,见 clear_var_freed 注释) diff --git a/App/lib/core/Gen/Call/CallGenCtor.vp b/App/lib/core/Gen/Call/CallGenCtor.vp index 8885b19..5be0152 100644 --- a/App/lib/core/Gen/Call/CallGenCtor.vp +++ b/App/lib/core/Gen/Call/CallGenCtor.vp @@ -318,6 +318,19 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase): # heap(或来源缺省=heap)+ slife → malloc + 函数出口自动 free(RAII); # stack(含 stack slife)→ 帧内 alloca,随帧消失,不产生 free; # memgr(x) slife → 归还池(D6 待定),本阶段不接管。 + # _new_is_nll_demotable - §13 NLL:可降级为"帧对象"的 `new heap`(判据见 Debugger/ownership-and-new.md §13.2) + def _new_is_nll_demotable(self, trans: HT.Translator*, rhs: ast.AST*, var_name: str) -> int: + """1 = 该 `new` 可当帧对象处理(生命期按 FRAME、出口释放复用 slife 通道);0 = 不必/判不定。 + + 只降级"堆且**未显式**指定生命期"的:显式 `hlife` 是用户对自由生命期的要求,不动; + `slife` 本来就走帧生命期 + 出口释放,无需降级。判据本体在 StructGenMixin.NllFrameConfined。""" + if rhs is None or rhs.kind() != ast.ASTKind.New: return 0 + nn: ast.New* = ast.New*(rhs) + if nn is None: return 0 + if nn.Source != ast.NEW_SRC_HEAP and nn.Source != ast.NEW_SRC_DEFAULT: return 0 + if nn.Life != ast.NEW_LIFE_DEFAULT: return 0 + return self.Gen.StructGen.NllFrameConfined(trans, var_name, rhs) + def _new_is_owned_heap(self, new_node: ast.New*) -> int: """1=`new` 生成编译器独占释放的堆绑定 / 0=不是""" if new_node is None: return 0 diff --git a/App/lib/core/Gen/Class/ClassGenVTable.vp b/App/lib/core/Gen/Class/ClassGenVTable.vp index 285814a..e2cfc6b 100644 --- a/App/lib/core/Gen/Class/ClassGenVTable.vp +++ b/App/lib/core/Gen/Class/ClassGenVTable.vp @@ -405,9 +405,13 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): if fd is not None and fd.name is not None: # 方法体翻译 = 下潜进一个源码上下文:推一帧(def 位置),供 traceback 回溯。 tr_mf: str = self.Gen.TypeGenResolve.get_current_file() + # §13 NLL:记录当前方法体(谓词 NllFrameConfined 要遍历它) + prev_nf: ast.AST* = self.Gen.StructGen.NllCurFunc + self.Gen.StructGen.NllCurFunc = fd self.Gen.TypeGenBase.push_trace(tr_mf, fd.lineno) self._translate_method(trans, fd, struct_ty, class_name) self.Gen.TypeGenBase.pop_trace() + self.Gen.StructGen.NllCurFunc = prev_nf # If there is a vtable, generate it before __before_init__ (which reads VTableMethodCount); # check via oop_entry directly to avoid cross-module same-name lookups. diff --git a/App/lib/core/Gen/Stmt/StmtGenMixin.vp b/App/lib/core/Gen/Stmt/StmtGenMixin.vp index 48cc863..5ebc0e8 100644 --- a/App/lib/core/Gen/Stmt/StmtGenMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenMixin.vp @@ -53,9 +53,13 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): tr_nf: str = self.Gen.TypeGenResolve.get_current_file() tr_nl: int = 0 if nfd_t is not None: tr_nl = nfd_t.lineno + # §13 NLL:记录当前函数体(谓词 NllFrameConfined 要遍历它) + prev_nf: ast.AST* = self.Gen.StructGen.NllCurFunc + self.Gen.StructGen.NllCurFunc = node self.Gen.TypeGenBase.push_trace(tr_nf, tr_nl) rc_nf: int = self.Gen.FuncGen.translate_nested_function_def(trans, node) self.Gen.TypeGenBase.pop_trace() + self.Gen.StructGen.NllCurFunc = prev_nf return rc_nf elif k == ast.ASTKind.Return: return self.Gen.StmtGenCif.translate_return(trans, node) elif k == ast.ASTKind.If: return self.Gen.StmtGenCif.translate_if(trans, node) diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 44748f5..74225d1 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -178,7 +178,10 @@ novirtual class StructGenMixin: # 3 槽/项:[名哈希, 强制逃逸位, 强制不逃逸位]。调用点在**所有推断与裁剪之后**最后套用, # 是不可被推翻的契约("unsafe 但强制")。每文件重置计数。 EscDirTable: sizet* - EscDirCount: int + EscDirCount: int + # §13 NLL:当前正在翻译的函数定义(谓词 NllFrameConfined 要拿它的函数体做遍历)。 + # 由三条函数体翻译入口设置/还原(顶层函数体 / 嵌套函数体 / 方法体)。 + NllCurFunc: ast.AST* def InitStructGen(self) -> int: """Initialize struct-mixin fields; called inline from subclass constructors.""" @@ -210,7 +213,8 @@ novirtual class StructGenMixin: self.EscLeakWant = -1 self.EscLeakLine = 0 self.EscDirTable = None - self.EscDirCount = 0 + self.EscDirCount = 0 + self.NllCurFunc = None self.StructNameIdx = None self.StructTypeIdx = None self.StructIdxCap = 0 @@ -524,6 +528,14 @@ novirtual class StructGenMixin: if idx < 0: return 0 return int(self.EscTable[idx * 3 + 1]) + def has_param_escapes(self, name: str) -> int: + """本文件摘要表**是否有**该函数的记录(1/0)。 + + 与"掩码是否为 0"必须区分:`@life(noescape(...))` 会把掩码清成 0,但那是**有记录** + (权威结论 = 不逃逸);而无记录 = 判不定(跨模块 / 未登记),调用方应保守处理。""" + if self._EscLookup(name) >= 0: return 1 + return 0 + def get_ret_borrow(self, name: str) -> int: """返回借用标记:1=该函数直接返回某形参;0=无记录""" idx: int = self._EscLookup(name) @@ -681,6 +693,196 @@ novirtual class StructGenMixin: box[2] = (box[2] | sizet(ef_m)) & (~sizet(ne_m)) return 0 + # ============================================================ + # §13 NLL:非词法生命期 —— 堆对象降级为帧对象 + # ============================================================ + # 判定"变量 v 在本函数内封闭":不外泄(判据 A)。单函数内所有局部存储同帧消亡, + # 故"末次使用在本帧内"(判据 B)自动成立 —— 判据 A 成立即可降级为 LIFE_FRAME, + # 释放复用 slife 的 RAII 出口(见 §13.3)。 + # 保守原则:**任何判不定都返回 0**(调用方维持 LIFE_HEAP = 现状),别名集**只增不减**。 + def _NllInSet(self, al: sizet*, name: str) -> int: + """name 是否在别名集里([0]=计数,[1..]=名哈希);1=是 / 0=否""" + if al is None or name is None: return 0 + h: sizet = self._NameHash(name) + c: sizet = al[0] + i: sizet = 0 + while i < c: + if al[1 + i] == h: return 1 + i += 1 + return 0 + + def _NllAdd(self, al: sizet*, name: str) -> int: + """并入别名集(只增不减;满 64 则忽略 —— 只增语义下忽略仍是保守的)""" + if al is None or name is None: return 0 + if self._NllInSet(al, name) != 0: return 0 + c: sizet = al[0] + if c >= 63: return 0 + al[1 + c] = self._NameHash(name) + al[0] = c + 1 + return 0 + + def _NllMentions(self, node: ast.AST*, al: sizet*) -> int: + """表达式/子树里是否出现别名集里的变量(保守:拿不到子树就算"出现")""" + if node is None: return 0 + k: int = node.kind() + if k == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(node) + if nm.id is not None and self._NllInSet(al, nm.id) != 0: return 1 + return 0 + if k == ast.ASTKind.Constant: return 0 + ch: list[ast.AST*]* = node.children + if ch is None: return 1 + n: sizet = len(ch) + i: sizet = 0 + while i < n: + if self._NllMentions(ch.get(i), al) != 0: return 1 + i += 1 + return 0 + + def _NllAliasSource(self, value: ast.AST*, al: sizet*) -> int: + """RHS 是否"恰好是一个别名变量"(`w = v`)→ 1""" + if value is None or value.kind() != ast.ASTKind.Name: return 0 + vn: ast.Name* = ast.Name*(value) + if vn.id is None: return 0 + return self._NllInSet(al, vn.id) + + def _NllCalleeEscMask(self, cl: ast.Call*) -> int: + """被调者形参逃逸掩码;**-1 = 判不定**(调用方按"不安全"处理)。 + + 只认"本文件有摘要记录的裸名函数":其掩码是本次编译重算/登记的权威值。 + 方法调用 / 跨模块 / 认不出的名字(含 extern C:可能留存指针)一律 -1。""" + if cl is None or cl.func is None: return -1 + if cl.func.kind() != ast.ASTKind.Name: return -1 + fnm: ast.Name* = ast.Name*(cl.func) + if fnm.id is None: return -1 + if self.has_param_escapes(fnm.id) == 0: return -1 + return self.get_param_escapes(fnm.id) + + def _NllExprUnsafe(self, node: ast.AST*, al: sizet*) -> int: + """表达式里别名是否被放到"不安全位置"→ 1(取址 / 当被调者 / 交给会逃逸的形参位)""" + if node is None: return 0 + k: int = node.kind() + if k == ast.ASTKind.UnaryOp: + # 取址(以及任何一元运算):保守视为不安全 + uo: ast.UnaryOp* = ast.UnaryOp*(node) + return self._NllMentions(uo.operand, al) + if k == ast.ASTKind.Call: + cl: ast.Call* = ast.Call*(node) + if self._NllMentions(cl.func, al) != 0: return 1 + if cl.args is None: return 0 + mask: int = self._NllCalleeEscMask(cl) + na: sizet = len(cl.args) + j: sizet = 0 + while j < na: + a: ast.AST* = cl.args.get(j) + idx: int = int(j) + j += 1 + # 只有"实参恰好是别名变量本身"才算交出该变量(`h.q` 只是读字段值,不算交出 h) + if a is None or a.kind() != ast.ASTKind.Name: continue + an: ast.Name* = ast.Name*(a) + if an.id is None or self._NllInSet(al, an.id) == 0: continue + if mask < 0: return 1 + if ((mask >> idx) & 1) != 0: return 1 + return 0 + ch: list[ast.AST*]* = node.children + if ch is None: return 0 + n: sizet = len(ch) + i: sizet = 0 + while i < n: + if self._NllExprUnsafe(ch.get(i), al) != 0: return 1 + i += 1 + return 0 + + def _NllStmts(self, stmts: list[ast.AST*]*, al: sizet*, def_node: ast.AST*, depth: int) -> int: + """遍历一个块;1=块内所有别名使用都帧内安全 / 0=发现外泄(放弃降级) + + 别名集只增不减 ⇒ 单遍即可(无需循环固定点)。控制流感知体现在"if/while/for 的 + 分支与循环体全部走一遍";**不进嵌套 def/class**(闭包捕获按外泄处理)。""" + if stmts is None or al is None or depth > 64: return 1 + n: sizet = len(stmts) + i: sizet = 0 + while i < n: + st: ast.AST* = stmts.get(i) + i += 1 + if st is None: continue + k: int = st.kind() + if k == ast.ASTKind.Return: + if self._NllMentions(ast.Return*(st).value, al) != 0: return 0 + elif k == ast.ASTKind.Assign: + asg: ast.Assign* = ast.Assign*(st) + if def_node is not None and asg.value is not None: + if uint64(asg.value) == uint64(def_node): continue # 定义处本身,跳过 + if asg.targets is not None: + nt: sizet = len(asg.targets) + t: sizet = 0 + while t < nt: + tg: ast.AST* = asg.targets.get(t) + t += 1 + if tg is None: continue + if tg.kind() == ast.ASTKind.Name: + tnm: ast.Name* = ast.Name*(tg) + if tnm.id is not None: + if self._NllAliasSource(asg.value, al) != 0: + self._NllAdd(al, tnm.id) # w = v → w 也是别名 + else: + if self._NllInSet(al, tnm.id) != 0: return 0 # 把别名重绑成别的 + if self._NllMentions(asg.value, al) != 0: return 0 + continue + if self._NllMentions(asg.value, al) != 0: return 0 + if self._NllExprUnsafe(asg.value, al) != 0: return 0 + elif k == ast.ASTKind.AnnAssign: + aa: ast.AnnAssign* = ast.AnnAssign*(st) + if def_node is not None and aa.value is not None: + if uint64(aa.value) == uint64(def_node): continue + if aa.target is not None and aa.target.kind() == ast.ASTKind.Name: + tnm2: ast.Name* = ast.Name*(aa.target) + if tnm2.id is not None: + if self._NllAliasSource(aa.value, al) != 0: + self._NllAdd(al, tnm2.id) + else: + if self._NllInSet(al, tnm2.id) != 0: return 0 + if self._NllMentions(aa.value, al) != 0: return 0 + elif self._NllMentions(aa.value, al) != 0: return 0 + if self._NllExprUnsafe(aa.value, al) != 0: return 0 + elif k == ast.ASTKind.Expr: + if self._NllExprUnsafe(ast.Expr*(st).value, al) != 0: return 0 + elif k == ast.ASTKind.If: + ifn: ast.If* = ast.If*(st) + if self._NllExprUnsafe(ifn.test, al) != 0: return 0 + if self._NllStmts(ifn.children, al, def_node, depth + 1) == 0: return 0 + if self._NllStmts(ifn.orelse, al, def_node, depth + 1) == 0: return 0 + elif k == ast.ASTKind.While: + wn: ast.While* = ast.While*(st) + if self._NllExprUnsafe(wn.test, al) != 0: return 0 + if self._NllStmts(wn.children, al, def_node, depth + 1) == 0: return 0 + if self._NllStmts(wn.orelse, al, def_node, depth + 1) == 0: return 0 + elif k == ast.ASTKind.For: + fr: ast.For* = ast.For*(st) + if self._NllExprUnsafe(fr.iter, al) != 0: return 0 + if self._NllStmts(fr.children, al, def_node, depth + 1) == 0: return 0 + if self._NllStmts(fr.orelse, al, def_node, depth + 1) == 0: return 0 + else: + # 认不出的语句(pass/assert/del/嵌套 def 等):整棵子树不提及别名就放行, + # 提及则保守拒绝(嵌套 def 捕获闭包变量走这条)。 + if self._NllMentions(st, al) != 0: return 0 + return 1 + + def NllFrameConfined(self, trans: HT.Translator*, var_name: str, def_node: ast.AST*) -> int: + """§13.2 判据:`new heap` 出来的 var_name 在本函数内封闭(不外泄)→ 1;判不定 → 0。 + + def_node = 该 `new` 表达式节点本身(用来跳过"定义处那条语句"的重绑定判定)。""" + if var_name is None or self.NllCurFunc is None: return 0 + fd: ast.FunctionDef* = ast.FunctionDef*(self.NllCurFunc) + if fd is None or fd.children is None: return 0 + al: sizet* = sizet*(stdlib.malloc(64 * 8)) + if al is None: return 0 + string.memset(al, 0, 64 * 8) + al[0] = 1 + al[1] = self._NameHash(var_name) + r: int = self._NllStmts(fd.children, al, def_node, 0) + stdlib.free(al) + return r + # escape_leak_line - **错误路径专用**:重扫本文件被调者函数体,取 want_pi 号形参的 # "首个逃逸写入点"行号(0 = 未知)。只在 fail-fast 报错时调用一次,不进热路径; # 跨模块拿不到 AST(EscFuncTable 按文件重置)时返回 0,调用点就不打泄漏点帧。 diff --git a/App/lib/core/Gen/TranslatorGenMixin.vp b/App/lib/core/Gen/TranslatorGenMixin.vp index b59742a..47a969b 100644 --- a/App/lib/core/Gen/TranslatorGenMixin.vp +++ b/App/lib/core/Gen/TranslatorGenMixin.vp @@ -70,9 +70,13 @@ novirtual class TranslatorGenMixin: # 函数体翻译 = 编译器下潜进一个源码上下文:推一帧(def 位置), # 让函数体内部的错误在 traceback 里回溯到"正在翻译哪个函数"(调用链)。 tr_f: str = self.Gen.TypeGenResolve.get_current_file() + # §13 NLL:记录当前函数体(谓词 NllFrameConfined 要遍历它) + prev_nf: ast.AST* = self.Gen.StructGen.NllCurFunc + self.Gen.StructGen.NllCurFunc = child self.Gen.TypeGenBase.push_trace(tr_f, fd_tc.lineno) added: int = self.Gen.FuncGenDef.translate_function_def(trans, child) self.Gen.TypeGenBase.pop_trace() + self.Gen.StructGen.NllCurFunc = prev_nf added_total += added elif trans._declare_only == 1: self._register_cexport_from_funcdef(trans, child)