fix(vtable): VTBL_METHOD_MAX 32 -> 1024

memhub.MemBuddy 这类工具类的虚方法数远超 32,上一轮加的 fail-fast 直接把它拦下了。三张槽位表由 PermPool 分配(各 8*VTBL_METHOD_MAX),1024 时每类 24 KiB,可接受。
This commit is contained in:
TermiNexus committed 2026-10-17 19:21:44 +08:00
1 parent bf0bbcc89b
commit eff88dd591
1 file changed
+20 -36
+20 -36
View File
@@ -18,8 +18,10 @@ import lib.core.Gen.DebugInfoGen as DebugInfoGen
# Method metadata flag: TLS (@t.TLS decorator; local copy avoids a cross-module CDefine lookup).
define META_TLS = 32
# Vtable method tables are sized per class at collection time (parents' count + own methods);
# there is deliberately no fixed VTBL_METHOD_MAX.
# Max number of vtable methods (per class). 32 was too small: large utility classes such as
# memhub.MemBuddy declare well over 32 methods. The three slot arrays are PermPool-allocated
# (8 * VTBL_METHOD_MAX each), so 1024 costs 24 KiB per class and survives pool resets.
define VTBL_METHOD_MAX = 1024
define SCOPE_FUNCTION = 1
# ============================================================
@@ -66,35 +68,15 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if children is None: return 0
cn: sizet = len(children)
# Capacity comes from the class itself: the parents' already-collected vtable methods
# (an upper bound — dedupe/override only shrink it) plus the number of own method
# definitions. A fixed 32 silently dropped every later method, after which a call missed
# the slot and fell back to a static call of the base implementation.
parent_count: int = self.Gen.StructGen.get_parent_count(class_name)
vt_cap: int = 0
if parent_count > 0:
for pi0 in range(int(parent_count)):
pname0: str = self.Gen.StructGen.get_parent_name_at(class_name, pi0)
if pname0 is None: continue
pe0: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(pname0, trans.ModuleSha1)
if pe0 is None: pe0 = self.Gen.StructGen.find_struct(pname0)
if pe0 is not None and pe0.VTableMethodCount > 0: vt_cap = vt_cap + pe0.VTableMethodCount
for ci0 in range(int(cn)):
stmt0: ast.AST* = children.get(ci0)
if stmt0 is not None and stmt0.kind() == ast.ASTKind.FunctionDef: vt_cap = vt_cap + 1
vt_cap = vt_cap + 1 # one spare slot
if vt_cap < 1: vt_cap = 1
vt_bytes: sizet = sizet(8 * vt_cap)
# Allocate with PermPool: VTableMethods must survive per-file pool resets.
full_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(vt_bytes)
method_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(vt_bytes)
full_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX)
method_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX)
# Record each method's parent SHA1 (non-zero = inherited, mangle with parent module SHA1).
method_parent_sha1s: sizet* = CompilerFactory.factory.PermPool.alloc(vt_bytes)
method_parent_sha1s: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX)
if full_names_buf is None or method_names_buf is None or method_parent_sha1s is None: return 0
string.memset(full_names_buf, 0, vt_bytes)
string.memset(method_names_buf, 0, vt_bytes)
string.memset(method_parent_sha1s, 0, vt_bytes)
string.memset(full_names_buf, 0, 8 * VTBL_METHOD_MAX)
string.memset(method_names_buf, 0, 8 * VTBL_METHOD_MAX)
string.memset(method_parent_sha1s, 0, 8 * VTBL_METHOD_MAX)
method_count: int = 0
@@ -102,6 +84,7 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
# subclass, others the parent; SHA1-aware lookup avoids cross-module same-name mismatches.
vt_self_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(class_name, trans.ModuleSha1)
# Multiple inheritance: walk parents in declaration order, dedupe by short name (first wins).
parent_count: int = self.Gen.StructGen.get_parent_count(class_name)
if parent_count > 0:
for pi in range(int(parent_count)):
pname: str = self.Gen.StructGen.get_parent_name_at(class_name, pi)
@@ -115,10 +98,11 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if vt_parent_entry is not None: parent_vt_count = vt_parent_entry.VTableMethodCount
if parent_vt_count > 0:
for pvi in range(int(parent_vt_count)):
if method_count >= vt_cap:
# Unreachable with the upper-bound sizing above; stay loud rather than
# writing past the PermPool array.
self.Gen.TypeGenErr.fatal_error(cd, f"internal error: vtable of '{class_name}' exceeded its computed capacity {vt_cap}", "InternalCompilerError")
if method_count >= VTBL_METHOD_MAX:
# fail-fast: the VTableMethods arrays are fixed at VTBL_METHOD_MAX;
# dropping the excess makes call sites miss the slot and silently fall
# back to a static call of the base implementation.
self.Gen.TypeGenErr.fatal_error(cd, f"class '{class_name}' inherits more than {VTBL_METHOD_MAX} virtual methods (compiler limit); raise VTBL_METHOD_MAX", "OverflowError")
pmethod_name: str = None
if vt_parent_entry is not None: pmethod_name = self.Gen.StructGen.get_vtable_method_name(pname, pvi)
if pmethod_name is None: continue
@@ -186,10 +170,10 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if stmt.kind() != ast.ASTKind.FunctionDef: continue
fd: ast.FunctionDef* = ast.FunctionDef*(stmt)
if fd is None or fd.name is None: continue
if method_count >= vt_cap:
# Unreachable with the upper-bound sizing above (own methods were counted);
# stay loud rather than writing past the PermPool array.
self.Gen.TypeGenErr.fatal_error(cd, f"internal error: vtable of '{class_name}' exceeded its computed capacity {vt_cap}", "InternalCompilerError")
if method_count >= VTBL_METHOD_MAX:
# fail-fast: see the inheritance branch above; a dropped method silently loses
# its vtable slot (subclass override stops taking effect).
self.Gen.TypeGenErr.fatal_error(cd, f"class '{class_name}' declares more than {VTBL_METHOD_MAX} virtual methods (compiler limit); raise VTBL_METHOD_MAX", "OverflowError")
mname: str = fd.name
# Skip constructors and special methods (they must not enter the vtable).