fix(TypeSpec): 修复类型驻留临时对象泄漏导致 PermPool 耗尽
_spec_to_text 的逐层临时缓冲由 PermPool 改为 stdlib.malloc/free 并配对释放;InternSpec 的键缓冲在所有退出路径归还,去重命中时归还临时 TypeSpec 及独占的 BaseName(与驻留条目共享指针时不释放)。PermPool 永不 reset,此前每次类型解析泄漏一个键缓冲与临时实例,翻译大模块时耗尽 1 GiB(现场 includes/ast/parser.vp)。
This commit is contained in:
1 parent
30e0a71cc6
commit
72eea1568c
1 file changed
+83
-23
@@ -2,6 +2,7 @@ from stdint import *
|
||||
import mlirlite
|
||||
import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import viperlib
|
||||
import lib.core.CompilerFactory as CompilerFactory
|
||||
import lib.core.Gen.GenApi as GenApi
|
||||
@@ -105,7 +106,7 @@ def SpecSignFromTypeNameSpan(text: str, start: sizet, end: sizet) -> int:
|
||||
if _spec_name_eq(text, start, end, "ushort") != 0: return TS_SIGN_UNSIGNED
|
||||
if _spec_name_eq(text, start, end, "uint") != 0: return TS_SIGN_UNSIGNED
|
||||
if _spec_name_eq(text, start, end, "ulong") != 0: return TS_SIGN_UNSIGNED
|
||||
if _spec_name_eq(text, start, end, "ulonglong") != 0: return TS_SIGN_UNSIGNED
|
||||
if _spec_name_eq(text, start, end, "ulonglong") != 0: return TS_SIGN_UNSIGNED
|
||||
if _spec_name_eq(text, start, end, "uintptr") != 0: return TS_SIGN_UNSIGNED
|
||||
if _spec_name_eq(text, start, end, "int8") != 0: return TS_SIGN_SIGNED
|
||||
if _spec_name_eq(text, start, end, "int16") != 0: return TS_SIGN_SIGNED
|
||||
@@ -122,7 +123,7 @@ def SpecSignFromTypeNameSpan(text: str, start: sizet, end: sizet) -> int:
|
||||
if _spec_name_eq(text, start, end, "short") != 0: return TS_SIGN_SIGNED
|
||||
if _spec_name_eq(text, start, end, "long") != 0: return TS_SIGN_SIGNED
|
||||
if _spec_name_eq(text, start, end, "longlong") != 0: return TS_SIGN_SIGNED
|
||||
if _spec_name_eq(text, start, end, "ptrdifft") != 0: return TS_SIGN_SIGNED
|
||||
if _spec_name_eq(text, start, end, "ptrdifft") != 0: return TS_SIGN_SIGNED
|
||||
if _spec_name_eq(text, start, end, "intptr") != 0: return TS_SIGN_SIGNED
|
||||
# --- builtin lowercase scalars ---
|
||||
if _spec_name_eq(text, start, end, "bool") != 0: return TS_SIGN_UNSIGNED
|
||||
@@ -430,26 +431,41 @@ def _spec_to_text(sp: TypeSpec*, buf: str, buf_size: sizet, depth: int) -> int:
|
||||
if sp is None or buf is None or buf_size == 0 or depth > 32: return 1
|
||||
buf[0] = '\0'
|
||||
if sp.Kind == TS_PTR:
|
||||
# p: + inner
|
||||
inner_buf: str = CompilerFactory.factory.PermPool.alloc(SPEC_TEXT_MAX)
|
||||
# p: + inner. Transient buffers come from the heap and are freed immediately:
|
||||
# PermPool is permanent, so per-call scratch there leaked ~256 B per recursion
|
||||
# level and exhausted the 1 GiB pool while translating large modules.
|
||||
inner_buf: str = stdlib.malloc(SPEC_TEXT_MAX)
|
||||
if inner_buf is None: return 1
|
||||
if _spec_to_text(sp.Elem, inner_buf, SPEC_TEXT_MAX, depth + 1) != 0: return 1
|
||||
if _spec_to_text(sp.Elem, inner_buf, SPEC_TEXT_MAX, depth + 1) != 0:
|
||||
stdlib.free(inner_buf)
|
||||
return 1
|
||||
pos_p: sizet = _spec_text_append(buf, buf_size, 0, "p:")
|
||||
if pos_p >= buf_size: return 1
|
||||
if pos_p >= buf_size:
|
||||
stdlib.free(inner_buf)
|
||||
return 1
|
||||
pos_p = _spec_text_append(buf, buf_size, pos_p, inner_buf)
|
||||
stdlib.free(inner_buf)
|
||||
if pos_p >= buf_size: return 1
|
||||
return 0
|
||||
if sp.Kind == TS_ARRAY:
|
||||
# arr[N]: + inner
|
||||
inner_buf_a: str = CompilerFactory.factory.PermPool.alloc(SPEC_TEXT_MAX)
|
||||
inner_buf_a: str = stdlib.malloc(SPEC_TEXT_MAX)
|
||||
if inner_buf_a is None: return 1
|
||||
if _spec_to_text(sp.Elem, inner_buf_a, SPEC_TEXT_MAX, depth + 1) != 0: return 1
|
||||
prefix_a: str = CompilerFactory.factory.PermPool.alloc(64)
|
||||
if prefix_a is None: return 1
|
||||
if _spec_to_text(sp.Elem, inner_buf_a, SPEC_TEXT_MAX, depth + 1) != 0:
|
||||
stdlib.free(inner_buf_a)
|
||||
return 1
|
||||
prefix_a: str = stdlib.malloc(64)
|
||||
if prefix_a is None:
|
||||
stdlib.free(inner_buf_a)
|
||||
return 1
|
||||
viperlib.snprintf(prefix_a, 64, "arr[%d]:", sp.ArrayCount)
|
||||
pos_a: sizet = _spec_text_append(buf, buf_size, 0, prefix_a)
|
||||
if pos_a >= buf_size: return 1
|
||||
stdlib.free(prefix_a)
|
||||
if pos_a >= buf_size:
|
||||
stdlib.free(inner_buf_a)
|
||||
return 1
|
||||
pos_a = _spec_text_append(buf, buf_size, pos_a, inner_buf_a)
|
||||
stdlib.free(inner_buf_a)
|
||||
if pos_a >= buf_size: return 1
|
||||
return 0
|
||||
if sp.Kind == TS_FUNC:
|
||||
@@ -463,10 +479,13 @@ def _spec_to_text(sp: TypeSpec*, buf: str, buf_size: sizet, depth: int) -> int:
|
||||
if pos >= buf_size: return 1
|
||||
param: TypeSpec* = _spec_param_at(sp, i)
|
||||
if param is None: return 1
|
||||
param_buf: str = CompilerFactory.factory.PermPool.alloc(SPEC_TEXT_MAX)
|
||||
param_buf: str = stdlib.malloc(SPEC_TEXT_MAX)
|
||||
if param_buf is None: return 1
|
||||
if _spec_to_text(param, param_buf, SPEC_TEXT_MAX, depth + 1) != 0: return 1
|
||||
if _spec_to_text(param, param_buf, SPEC_TEXT_MAX, depth + 1) != 0:
|
||||
stdlib.free(param_buf)
|
||||
return 1
|
||||
pos = _spec_text_append(buf, buf_size, pos, param_buf)
|
||||
stdlib.free(param_buf)
|
||||
if pos >= buf_size: return 1
|
||||
if sp.IsVarArg == 1:
|
||||
if sp.ParamCount > 0: pos = _spec_text_append(buf, buf_size, pos, ",...")
|
||||
@@ -474,10 +493,13 @@ def _spec_to_text(sp: TypeSpec*, buf: str, buf_size: sizet, depth: int) -> int:
|
||||
if pos >= buf_size: return 1
|
||||
pos = _spec_text_append(buf, buf_size, pos, ")->")
|
||||
if pos >= buf_size: return 1
|
||||
ret_buf: str = CompilerFactory.factory.PermPool.alloc(SPEC_TEXT_MAX)
|
||||
ret_buf: str = stdlib.malloc(SPEC_TEXT_MAX)
|
||||
if ret_buf is None: return 1
|
||||
if _spec_to_text(sp.Ret, ret_buf, SPEC_TEXT_MAX, depth + 1) != 0: return 1
|
||||
if _spec_to_text(sp.Ret, ret_buf, SPEC_TEXT_MAX, depth + 1) != 0:
|
||||
stdlib.free(ret_buf)
|
||||
return 1
|
||||
pos = _spec_text_append(buf, buf_size, pos, ret_buf)
|
||||
stdlib.free(ret_buf)
|
||||
if pos >= buf_size: return 1
|
||||
return 0
|
||||
# base type
|
||||
@@ -631,17 +653,39 @@ def _text_to_func(text: str, depth: int) -> TypeSpec*:
|
||||
return sp_f
|
||||
|
||||
|
||||
# _spec_discard_temp - return a dedup-hit temporary TypeSpec to PermPool.
|
||||
# The temporary's BaseName is an exclusive deep copy made by NewTypeStruct; a clone
|
||||
# (_spec_clone) instead shares the pointer with the interned entry, so only a distinct
|
||||
# pointer may be freed. Elem/Ret/Params are interned or caller-owned and never freed here.
|
||||
def _spec_discard_temp(pp: memhub.MemBuddy*, sp: TypeSpec*, hit: TypeSpec*) -> None:
|
||||
"""Free a temporary TypeSpec that lost dedup; keeps shared sub-buffers alive"""
|
||||
if sp is None or hit is None: return
|
||||
if uint64(sp) == uint64(hit): return
|
||||
if sp.BaseName is not None:
|
||||
if hit.BaseName is None or uint64(sp.BaseName) != uint64(hit.BaseName):
|
||||
pp.free(sp.BaseName)
|
||||
pp.free(sp)
|
||||
|
||||
|
||||
# InternSpec - canonical interning: one instance per key, returns the interned instance.
|
||||
# Table full -> fatal_error; key too long for SPEC_TEXT_MAX -> keep instance, skip dedup.
|
||||
def InternSpec(arena: TypeSpecArena*, sp: TypeSpec*) -> TypeSpec*:
|
||||
"""Intern a TypeSpec by canonical text key; None on failure"""
|
||||
"""Intern a TypeSpec by canonical text key; None on failure.
|
||||
|
||||
All callers pass a **freshly allocated** instance (NewType* / SpecWith*): on a dedup hit
|
||||
that temporary is unreachable, so it (and the key buffer) must be returned to PermPool.
|
||||
PermPool is permanent and never reset; leaking them per resolution exhausted the 1 GiB
|
||||
pool while translating large modules (现场:includes/ast/parser.vp).
|
||||
"""
|
||||
if arena is None or sp is None: return sp
|
||||
pp: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
||||
if SpecArenaInit(arena, 0) != 0: return None
|
||||
key_buf: str = CompilerFactory.factory.PermPool.alloc(SPEC_TEXT_MAX)
|
||||
key_buf: str = pp.alloc(SPEC_TEXT_MAX)
|
||||
if key_buf is None: return None
|
||||
if SpecToText(sp, key_buf, SPEC_TEXT_MAX) != 0:
|
||||
# Key too long (deep nesting): keep the instance but skip dedup. Never store a
|
||||
# truncated key, which would false-match another type and return a wrong instance.
|
||||
pp.free(key_buf)
|
||||
return sp
|
||||
# Hash-index fast path (expected O(1)). The index is kept complete as entries are
|
||||
# only appended, so a miss can append directly; fall back to linear scan only when unavailable.
|
||||
@@ -649,33 +693,49 @@ def InternSpec(arena: TypeSpecArena*, sp: TypeSpec*) -> TypeSpec*:
|
||||
found: int = _spec_index_find(arena, key_buf, key_hash)
|
||||
if found >= 0:
|
||||
slot_e: sizet* = (sizet*)(uint64(arena.Specs) + uint64(found * 8))
|
||||
if slot_e is None: return None
|
||||
return (TypeSpec*)(uint64(slot_e[0]))
|
||||
if slot_e is None:
|
||||
pp.free(key_buf)
|
||||
return None
|
||||
hit_e: TypeSpec* = (TypeSpec*)(uint64(slot_e[0]))
|
||||
pp.free(key_buf)
|
||||
_spec_discard_temp(pp, sp, hit_e)
|
||||
return hit_e
|
||||
if arena.Index is None:
|
||||
# index unavailable: fall back to the linear scan
|
||||
for i in range(int(arena.Count)):
|
||||
exist_key: str = _spec_key_at(arena.Keys, i)
|
||||
if exist_key is not None and string.strcmp(exist_key, key_buf) == 0:
|
||||
slot_e2: sizet* = (sizet*)(uint64(arena.Specs) + uint64(i * 8))
|
||||
if slot_e2 is None: return None
|
||||
return (TypeSpec*)(uint64(slot_e2[0]))
|
||||
if slot_e2 is None:
|
||||
pp.free(key_buf)
|
||||
return None
|
||||
hit_e2: TypeSpec* = (TypeSpec*)(uint64(slot_e2[0]))
|
||||
pp.free(key_buf)
|
||||
_spec_discard_temp(pp, sp, hit_e2)
|
||||
return hit_e2
|
||||
# Miss: append. Table full = fail-fast (never return None, which would silently
|
||||
# lose type info up the call chain and corrupt signedness -> zext/sext direction).
|
||||
if arena.Count >= arena.Cap:
|
||||
pp.free(key_buf)
|
||||
GenApi.fatal_error(None, f"TypeSpec intern table is full (limit {arena.Cap} entries), cannot register a new type", "InternalCompilerError")
|
||||
# Reachable only when fatal_error is a no-op: degrade to "no interning but return a
|
||||
# complete instance", keeping the never-return-None guarantee.
|
||||
return sp
|
||||
dst_key: str = _spec_key_at(arena.Keys, arena.Count)
|
||||
if dst_key is None: return None
|
||||
if dst_key is None:
|
||||
pp.free(key_buf)
|
||||
return None
|
||||
string.strcpy(dst_key, key_buf)
|
||||
slot_n: sizet* = (sizet*)(uint64(arena.Specs) + uint64(arena.Count * 8))
|
||||
if slot_n is None: return None
|
||||
if slot_n is None:
|
||||
pp.free(key_buf)
|
||||
return None
|
||||
slot_n[0] = sizet(sp)
|
||||
# Store into the hash index too (index = Count before append). Insert failure only
|
||||
# means later lookups for this key fall back to the linear scan.
|
||||
_spec_index_insert(arena, key_buf, key_hash, arena.Count)
|
||||
arena.Count += 1
|
||||
pp.free(key_buf)
|
||||
return sp
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user