fix(gen): slife 登记表改为跨文件存活,并修正区间弹回
- _g_slife_owned 原先从 trans.Pool(每文件池)分配,Phase2 每文件 reset_to(mark2) 后表体即悬垂,第二个文件起每次登记都写坏活跃池数据(现场 access-violation on 0x0,崩点随分配漂移、ASan 无法给栈) - _SlifeRestore 只还了 count、未还 top,top 随函数数量单调增长;上一文件/函数的陈旧条目留在扫描区间内,因每文件池地址复用被误判为新变量,_SlifeShadow 返回悬垂影子槽,_SlifeNoteStore 直接写坏内存 - 新增 _g_slife_save/_g_slife_depth:进入时保存 count/top 两个边界,退出时一起弹回 - _SlifeDropOwnedUnlessSame 补 Pointee==0 守卫(与 _SlifeEmitGuardedFree 同口径)
This commit is contained in:
1 parent
1a3ef521b3
commit
eb0c3dfd31
1 file changed
+41
-15
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user