diff --git a/App/lib/core/Gen/VarGenMixin.vp b/App/lib/core/Gen/VarGenMixin.vp index 9af2ee8..ee53ae1 100644 --- a/App/lib/core/Gen/VarGenMixin.vp +++ b/App/lib/core/Gen/VarGenMixin.vp @@ -40,10 +40,16 @@ define SCOPE_BLOCK = 2 # · 为什么是**函数级**而不是块级:循环体/with/分支体不建立符号作用域,局部存储一律 # 入口块 alloca(函数级寿命),块级释放会让"块之后仍可达的变量"变成悬垂。 # · 登记区间是**栈式**的:[_g_slife_count, _g_slife_top) 属于当前函数。进入嵌套函数时 -# 起点抬到当前栈顶(_SlifeReset),退出时弹回(_SlifeRestore)——否则内层函数会在 -# 0 号槽覆写外层登记项,且内层出口会把外层的对象一起 free 掉。 +# 起点抬到当前栈顶(_SlifeReset),退出时**两个边界一起弹回**(_SlifeRestore 用 _g_slife_save +# 保存的旧值还原)——否则内层函数会在 0 号槽覆写外层登记项,且内层出口会把外层的对象一起 free 掉。 +# · 表的存储必须**跨文件存活**:trans.Pool 是每文件池,Phase2 每翻完一个文件就 reset_to(mark2) +# (见 Phase2/__init__.vp)。若表落在这个池里,第二个文件起表体已是被回收的内存,后续 +# 写入就是往活跃数据上砸(现场:TransPyV.exe 编译任何工程 access-violation on 0x0, +# 崩点每次都不同)。故与 _g_freed_slots 同口径,用 stdlib.malloc。 # 登记表容量(P5:128 → 256)。超限时 **fail-fast**(不再静默退化为"不登记 = 不释放"的泄漏)。 define _SLIFE_MAX = 256 +# 区间保存栈深度(嵌套函数/方法体层数);只存两个边界值,每层 16 字节。 +define _SLIFE_DEPTH_MAX = 64 # P2 willFree 失效槽表:_g_freed_slots[0.._g_freed_count) 是"已被标注调用消费过的变量槽 alloca"。 # **刻意不放进 VarEntry**(给热结构加字段会动布局、牵连整条变量注册路径)。 # 键 = alloca,槽在整次翻译中唯一,所以是"全局累积表",无需按函数 reset/restore。 @@ -53,6 +59,9 @@ _g_freed_count: int = 0 _g_slife_owned: sizet* = None _g_slife_count: int = 0 _g_slife_top: int = 0 +# 区间保存栈:[d*2+0]=进入第 d 层前的 _g_slife_count,[d*2+1]=进入前的 _g_slife_top。 +_g_slife_save: sizet* = None +_g_slife_depth: int = 0 novirtual class VarGenMixin: @@ -482,22 +491,39 @@ novirtual class VarGenMixin: # M3 owned slife 登记 API(§4.2 函数级 scope guard / §4.3 不许手动 free) # ============================================================ def _SlifeReset(self, pool: memhub.MemBuddy*) -> int: - """函数入口:把登记区间起点抬到当前栈顶;返回旧起点(退出时交还 _SlifeRestore,-1=不可用)""" - global _g_slife_owned, _g_slife_count, _g_slife_top + """函数入口:保存外层区间两个边界、把本层起点抬到当前栈顶;返回令牌(-1=不可用,须原样交还 _SlifeRestore)""" + global _g_slife_owned, _g_slife_save, _g_slife_count, _g_slife_top, _g_slife_depth if _g_slife_owned is None: - if pool is None: return -1 - _g_slife_owned = sizet*(pool.alloc(_SLIFE_MAX * 16)) + # 表体跨文件存活:必须用 stdlib.malloc,**不能**用 pool(trans.Pool 是每文件池, + # Phase2 每文件结束 reset_to 后表体即悬垂,后续写入会砸活跃数据)。 + _g_slife_owned = sizet*(stdlib.malloc(_SLIFE_MAX * 16)) if _g_slife_owned is None: return -1 - old_base: int = _g_slife_count + if _g_slife_save is None: + _g_slife_save = sizet*(stdlib.malloc(_SLIFE_DEPTH_MAX * 16)) + if _g_slife_save is None: return -1 + if _g_slife_depth >= _SLIFE_DEPTH_MAX: + self.Gen.TypeGenErr.fatal_error(None, "function nesting is too deep (capacity 64): the slife ownership save stack is full", "LifetimeError") + return -1 + _g_slife_save[_g_slife_depth * 2] = sizet(_g_slife_count) + _g_slife_save[_g_slife_depth * 2 + 1] = sizet(_g_slife_top) + tok: int = _g_slife_depth + _g_slife_depth = _g_slife_depth + 1 _g_slife_count = _g_slife_top - return old_base + return tok - def _SlifeRestore(self, old_base: int) -> int: - """函数出口:丢弃本函数的登记区间、弹回外层起点(不释放;释放由 _SlifeEmitFrees 先做)""" - global _g_slife_count, _g_slife_top - if old_base < 0: return 0 - _g_slife_top = _g_slife_count - _g_slife_count = old_base + def _SlifeRestore(self, tok: int) -> int: + """函数出口:**两个边界一起弹回**进入前的值(不释放;释放由 _SlifeEmitFrees 先做)。 + + 关键:必须把 _g_slife_top 还原成进入前的值。只还原 count 会让 top 随函数数量单调增长, + 上一文件/上一函数的陈旧条目就一直留在 [_g_slife_count, _g_slife_top) 被扫描—— + 而每文件池 reset_to 会复用同样的地址,于是"陈旧条目"能与新 alloca 地址相同, + _SlifeShadow 返回悬垂的影子槽,_SlifeNoteStore 直接写坏内存。""" + global _g_slife_count, _g_slife_top, _g_slife_depth + if tok < 0 or _g_slife_save is None: return 0 + if tok >= _g_slife_depth: return 0 + _g_slife_count = int(_g_slife_save[tok * 2]) + _g_slife_top = int(_g_slife_save[tok * 2 + 1]) + _g_slife_depth = tok return 1 def _SlifeShadow(self, alloca: mlirlite.Value*) -> mlirlite.Value*: @@ -638,7 +664,7 @@ novirtual class VarGenMixin: 判等放在 IR 里做(编译期无法知道两个槽是否指向同一对象)。""" if target_slot is None or src_slot is None or trans is None: return 0 sh: mlirlite.Value* = self._SlifeShadow(target_slot) - if sh is None or target_slot.Ty is None: return 0 + if sh is None or target_slot.Ty is None or target_slot.Ty.Pointee == 0: return 0 el_ty: mlirlite.LLVMType* = target_slot.Ty.Pointee qv: mlirlite.Value* = mlirlite.build_load(builder, el_ty, target_slot) pv: mlirlite.Value* = mlirlite.build_load(builder, el_ty, src_slot)