diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index f8f5f2c..5f58f94 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -192,6 +192,13 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): if fld_only != 0: cl_sf: int = self._FrameArgsEscapeClears(trans, cargs, can, fld_only) if cl_sf != 0: esc_mask = esc_mask & (~cl_sf) + # ==== 声明式指令最终裁决(@life(escape/noescape):unsafe 但强制)==== + # 必须放在所有推断/裁剪**之后**:常量特化、同命裁剪、跨模块回退都改不动契约。 + # escape(...) 无条件置位、noescape(...) 无条件清位。 + fe_m: int = GenApi.get_param_escape_force(func_name) + ne_m: int = GenApi.get_param_noescape_force(func_name) + if fe_m != 0 or ne_m != 0: + esc_mask = (esc_mask | fe_m) & (~ne_m) if esc_mask != 0: _dummy_ptr: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) for ai in range(can): diff --git a/App/lib/core/Gen/GenApi.vp b/App/lib/core/Gen/GenApi.vp index c3e7751..2e1a8fc 100644 --- a/App/lib/core/Gen/GenApi.vp +++ b/App/lib/core/Gen/GenApi.vp @@ -497,6 +497,20 @@ def get_param_escapes_hard_method(pool: memhub.MemBuddy*, class_name: str, metho return 0 +# ---- 声明式指令(@life(escape/noescape))的最终裁决掩码 ---- +# 契约表与推断表分开:调用点在所有推断/裁剪之后最后套用,任何推导都不能推翻。 +def get_param_escape_force(name: str) -> int: + """`@life(escape(...))` 强制逃逸位掩码;0 = 无该标注""" + if _g_gen is not None: return _g_gen.StructGen.get_param_escape_force(name) + return 0 + + +def get_param_noescape_force(name: str) -> int: + """`@life(noescape(...))` 强制不逃逸位掩码;0 = 无该标注""" + if _g_gen is not None: return _g_gen.StructGen.get_param_noescape_force(name) + return 0 + + # escape_leak_line - **错误路径专用**:被调者第 want_pi 号形参的"首个逃逸写入点"行号。 # 只在 fail-fast 报错时调用(会重扫一次被调者函数体);跨模块拿不到 AST 时返回 0。 def escape_leak_line(name: str, want_pi: int) -> int: diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 0a64737..6fe2eb4 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -173,7 +173,12 @@ novirtual class StructGenMixin: # escape_leak_line 重扫被调者函数体前,把"想要的形参位"放进 EscLeakWant;_MarkEscapingStore # 命中该位时把**首个**造成逃逸的写入点行号写进 EscLeakLine。-1 = 不采集(登记/落盘路径恒为 -1)。 EscLeakWant: int - EscLeakLine: int + EscLeakLine: int + # ── 声明式指令表(`@life(escape(...))` / `@life(noescape(...))`)── + # 3 槽/项:[名哈希, 强制逃逸位, 强制不逃逸位]。调用点在**所有推断与裁剪之后**最后套用, + # 是不可被推翻的契约("unsafe 但强制")。每文件重置计数。 + EscDirTable: sizet* + EscDirCount: int def InitStructGen(self) -> int: """Initialize struct-mixin fields; called inline from subclass constructors.""" @@ -203,7 +208,9 @@ novirtual class StructGenMixin: self.EscFieldTable = None self.EscFieldCount = 0 self.EscLeakWant = -1 - self.EscLeakLine = 0 + self.EscLeakLine = 0 + self.EscDirTable = None + self.EscDirCount = 0 self.StructNameIdx = None self.StructTypeIdx = None self.StructIdxCap = 0 @@ -358,6 +365,11 @@ novirtual class StructGenMixin: self.EscFieldTable = sizet*(esc_pool.alloc(esc_cap * 16)) if self.EscFieldTable is not None: string.memset(self.EscFieldTable, 0, esc_cap * 16) self.EscFieldCount = 0 + # 声明式指令表(escape/noescape):同样只分配一次,每文件重置计数 + if self.EscDirTable is None: + self.EscDirTable = sizet*(esc_pool.alloc(esc_cap * 24)) + if self.EscDirTable is not None: string.memset(self.EscDirTable, 0, esc_cap * 24) + self.EscDirCount = 0 self.EscFuncCount = 0 self.EscConstEnv = None self.EscIndCount = 0 @@ -512,11 +524,36 @@ novirtual class StructGenMixin: 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 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]) + + # ---- 声明式指令(@life(escape/noescape)):调用点的**最终裁决**表 ---- + # 与 EscTable 分开存:EscTable 的掩码是"推断结果"(能被常量特化/同命裁剪改动), + # 指令是"契约"(不能被任何推断推翻),调用点在所有推断之后最后套用一次。 + def _DirLookup(self, name: str) -> int: + """指令表项下标;未找到返回 -1""" + if name is None or self.EscDirTable is None: return -1 + h: sizet = self._NameHash(name) + i: int = 0 + while i < self.EscDirCount: + if self.EscDirTable[i * 3] == h: return i + i += 1 + return -1 + + def get_param_escape_force(self, name: str) -> int: + """`@life(escape(...))` 强制逃逸位掩码;0 = 无该标注""" + idx: int = self._DirLookup(name) + if idx < 0: return 0 + return int(self.EscDirTable[idx * 3 + 1]) + + def get_param_noescape_force(self, name: str) -> int: + """`@life(noescape(...))` 强制不逃逸位掩码;0 = 无该标注""" + idx: int = self._DirLookup(name) + if idx < 0: return 0 + return int(self.EscDirTable[idx * 3 + 2]) # ---- P2 willFree:声明式标注表(直接来自 @life(willFree(...)) / @willFree(...),不做数据流) ---- def _WillFreeLookup(self, name: str) -> int: @@ -599,8 +636,10 @@ novirtual class StructGenMixin: # 显式标注(声明式契约)**在扫描之后**叠加:escape 无条件置位、noescape 无条件清位。 # 顺序是关键:常量折叠发生在扫描里,所以 `escape(p)` 不会被"死分支"折掉 —— 契约的本意 # 正是"编译器看不见的路径";`noescape(p)` 也不会被任何推导重新置位。 - # 借 g 的空槽(68/69,此刻 env 已用完)当输出缓冲,不依赖任何额外 scratch。 - ann_out: sizet* = sizet*(uint64(g) + 68 * 8) + # 输出缓冲借用 box[3]/box[4](= 槽 68/69,此刻 env 已用完),不依赖额外 scratch; + # 这两个槽同时把**声明式指令**交给调用方(_RegisterFuncEscapeSummary 落 EscDirTable → + # 调用点最后再套用一次,那是不可被推断推翻的最终裁决)。 + ann_out: sizet* = sizet*(uint64(box) + 3 * 8) if GenApi.compute_escape_directives(fd, ann_out) == 0: ef_m: int = int(ann_out[0]) ne_m: int = int(ann_out[1]) @@ -1311,6 +1350,15 @@ novirtual class StructGenMixin: self.EscFuncTable[fs * 2 + 0] = sizet(self._NameHash(key)) self.EscFuncTable[fs * 2 + 1] = sizet(fd) self.EscFuncCount = fs + 1 + # 声明式指令(@life(escape/noescape))单独落表:调用点在所有推断/裁剪之后最后套用。 + # box[3]/box[4] 由 compute_escape_summary 借给 compute_escape_directives 当输出(无标注时恒 0)。 + if self.EscDirTable is not None and self.EscDirCount < 1024: + if box[3] != 0 or box[4] != 0: + d_s: int = self.EscDirCount + self.EscDirTable[d_s * 3 + 0] = sizet(self._NameHash(key)) + self.EscDirTable[d_s * 3 + 1] = box[3] + self.EscDirTable[d_s * 3 + 2] = box[4] + self.EscDirCount = d_s + 1 return 0 def _CollectGlobalNames(self, stmts: list[ast.AST*]*, gset: sizet*, depth: int) -> int: