From bf0bbcc89b07275fa161cac895d78a544eaf456f Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 17 Oct 2026 19:19:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(vtable):=20=E8=99=9A=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E8=A1=A8=E6=8C=89=E7=B1=BB=E5=AE=9E=E9=99=85=E8=A7=84=E6=A8=A1?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E5=88=86=E9=85=8D=EF=BC=8C=E5=8E=BB=E6=8E=89?= =?UTF-8?q?=2032=20=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MemBuddy 继承 MemManager 后虚方法约 60+ 个,VTBL_METHOD_MAX=32 会触发 fail-fast(此前静默只收前 32 个,之后的调用点找不到槽位、退化成静态直调)。现在收集前先算容量上界:各父类已收集的 VTableMethodCount 之和 + 本类 FunctionDef 数量 + 1,三个 PermPool 数组按此一次性分配;两处越界检查保留为 InternalCompilerError(理论不可达)。删除 VTBL_METHOD_MAX。注:本次提交同时带上了该文件既有的 t.CPtr → rawptr 迁移与 _generate_default_init 删除等未提交改动。 --- App/lib/core/Gen/Class/ClassGenVTable.vp | 191 ++++++++++++----------- 1 file changed, 104 insertions(+), 87 deletions(-) diff --git a/App/lib/core/Gen/Class/ClassGenVTable.vp b/App/lib/core/Gen/Class/ClassGenVTable.vp index 28cc43f..6f4102e 100644 --- a/App/lib/core/Gen/Class/ClassGenVTable.vp +++ b/App/lib/core/Gen/Class/ClassGenVTable.vp @@ -1,6 +1,5 @@ import lib.core.Gen.GenTypes as GenTypes import lib.core.Gen.Type.TypeSpec as TSpec -import t, c from stdint import * import ast import mlirlite @@ -19,8 +18,8 @@ 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 -# Max number of vtable methods. -define VTBL_METHOD_MAX = 32 +# Vtable method tables are sized per class at collection time (parents' count + own methods); +# there is deliberately no fixed VTBL_METHOD_MAX. define SCOPE_FUNCTION = 1 # ============================================================ @@ -67,15 +66,35 @@ 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(8 * VTBL_METHOD_MAX) - method_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX) + full_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(vt_bytes) + method_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(vt_bytes) # Record each method's parent SHA1 (non-zero = inherited, mangle with parent module SHA1). - method_parent_sha1s: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX) + method_parent_sha1s: sizet* = CompilerFactory.factory.PermPool.alloc(vt_bytes) 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, 8 * VTBL_METHOD_MAX) - string.memset(method_names_buf, 0, 8 * VTBL_METHOD_MAX) - string.memset(method_parent_sha1s, 0, 8 * VTBL_METHOD_MAX) + string.memset(full_names_buf, 0, vt_bytes) + string.memset(method_names_buf, 0, vt_bytes) + string.memset(method_parent_sha1s, 0, vt_bytes) method_count: int = 0 @@ -83,7 +102,6 @@ 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) @@ -97,11 +115,10 @@ 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 >= 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") + 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") 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 @@ -169,10 +186,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 >= 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") + 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") mname: str = fd.name # Skip constructors and special methods (they must not enter the vtable). @@ -598,7 +615,7 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): if decl_reuse == 0: # Default-parameter info (args[0] is self for normal methods; @staticmethod has no self). # Assign the attribute to a typed list[...]* local before __len__() or GEP base becomes i32 0. - md_defaults: t.CPtr = None + md_defaults: rawptr = None md_default_count: int = 0 md_param_count: int = 0 md_args_node: ast.Arguments* = fd.args @@ -1114,71 +1131,71 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): field_ptr: mlirlite.Value* = mlirlite.build_gep_struct(func_builder, struct_ty, fe.Ty, self_val, fe.Index) if field_ptr is None: continue # Coerce and store. - default_val = self.Gen.ExprGenBase.coerce_to_type(func_builder, default_val, fe.Ty, fe.DefaultVal) - mlirlite.build_store(func_builder, default_val, field_ptr) - - # 3. ret void - mlirlite.build_ret_void(func_builder) - - return 0 - # _generate_default_init - emit define void @SHA1.ClassName.__init__(Ptr(StructTy) %self) { ret void }. - # Companion to _generate_before_init: exists purely so that explicit `obj.__init__()` source calls - # (which lower to a direct @SHA1.Class.__init__ call) link even when the class defines no __init__. - # The body must stay empty: __before_init__ already owns zero-fill / defaults / vtable setup. - def _generate_default_init(self, trans: HT.Translator*, struct_ty: mlirlite.LLVMType*, class_name: str) -> int: - """Emit an empty default __init__ so explicit obj.__init__() call sites link.""" - if trans is None or struct_ty is None or class_name is None: return 0 - - pool: memhub.MemBuddy* = trans.Pool - mod: mlirlite.LLVMModule* = trans.Module - funcs_ptr: GenTypes.FuncEntry* = trans._funcs - func_count: int = trans._func_count - - self_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, struct_ty) - void_ty: mlirlite.LLVMType* = mlirlite.Void(pool) - - # Build the function name: ClassName.__init__(SHA1 namespace applied by _mangle_name). - full_name_buf: str = pool.alloc(128) - if full_name_buf is None: return 0 - viperlib.snprintf(full_name_buf, 128, "%s.__init__", class_name) - full_name: str = full_name_buf - - mangled_name: str = self.Gen.FuncGenBase._mangle_name(trans, full_name) - - # A call site may have forward-declared __init__ first; upgrade the declaration to a - # definition (clear IsDeclared). If a body already exists, bail to avoid a duplicate symbol. - di_func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name) - di_decl_reuse: int = 0 - if di_func is not None: - if di_func.IsDeclared == 0: return 0 - di_decl_reuse = 1 - else: - di_func = mlirlite.create_function(pool, mod, mangled_name, void_ty) - if di_func is None: return 0 - func: mlirlite.Function* = di_func - - max_funcs: int = 1024 - rc_add: int = self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, full_name, func, max_funcs, None, 0, 0, pool) - if rc_add == 0: trans._func_count = func_count + 1 - - if di_decl_reuse != 0: - # Reusing the declaration: name/type its first param as %self (printer only emits named params). - di_first_param: mlirlite.Param* = mlirlite.function_get_param_head(func) - if di_first_param is not None: - di_first_param.Ty = self_ptr_ty - di_first_param.Name = "%self" - func.IsDeclared = 0 - else: - mlirlite.add_param(pool, func, self_ptr_ty, "%self") - - self.Gen.CallGenLeafKit.update_global_func_spec(full_name, func) - - entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry") - if entry_blk is None: return 0 - func_builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, func) - if func_builder is None: return 0 - mlirlite.position_at_end(func_builder, entry_blk) - - mlirlite.build_ret_void(func_builder) - + default_val = self.Gen.ExprGenBase.coerce_to_type(func_builder, default_val, fe.Ty, fe.DefaultVal) + mlirlite.build_store(func_builder, default_val, field_ptr) + + # 3. ret void + mlirlite.build_ret_void(func_builder) + + return 0 + # _generate_default_init - emit define void @SHA1.ClassName.__init__(Ptr(StructTy) %self) { ret void }. + # Companion to _generate_before_init: exists purely so that explicit `obj.__init__()` source calls + # (which lower to a direct @SHA1.Class.__init__ call) link even when the class defines no __init__. + # The body must stay empty: __before_init__ already owns zero-fill / defaults / vtable setup. + def _generate_default_init(self, trans: HT.Translator*, struct_ty: mlirlite.LLVMType*, class_name: str) -> int: + """Emit an empty default __init__ so explicit obj.__init__() call sites link.""" + if trans is None or struct_ty is None or class_name is None: return 0 + + pool: memhub.MemBuddy* = trans.Pool + mod: mlirlite.LLVMModule* = trans.Module + funcs_ptr: GenTypes.FuncEntry* = trans._funcs + func_count: int = trans._func_count + + self_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, struct_ty) + void_ty: mlirlite.LLVMType* = mlirlite.Void(pool) + + # Build the function name: ClassName.__init__(SHA1 namespace applied by _mangle_name). + full_name_buf: str = pool.alloc(128) + if full_name_buf is None: return 0 + viperlib.snprintf(full_name_buf, 128, "%s.__init__", class_name) + full_name: str = full_name_buf + + mangled_name: str = self.Gen.FuncGenBase._mangle_name(trans, full_name) + + # A call site may have forward-declared __init__ first; upgrade the declaration to a + # definition (clear IsDeclared). If a body already exists, bail to avoid a duplicate symbol. + di_func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name) + di_decl_reuse: int = 0 + if di_func is not None: + if di_func.IsDeclared == 0: return 0 + di_decl_reuse = 1 + else: + di_func = mlirlite.create_function(pool, mod, mangled_name, void_ty) + if di_func is None: return 0 + func: mlirlite.Function* = di_func + + max_funcs: int = 1024 + rc_add: int = self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, full_name, func, max_funcs, None, 0, 0, pool) + if rc_add == 0: trans._func_count = func_count + 1 + + if di_decl_reuse != 0: + # Reusing the declaration: name/type its first param as %self (printer only emits named params). + di_first_param: mlirlite.Param* = mlirlite.function_get_param_head(func) + if di_first_param is not None: + di_first_param.Ty = self_ptr_ty + di_first_param.Name = "%self" + func.IsDeclared = 0 + else: + mlirlite.add_param(pool, func, self_ptr_ty, "%self") + + self.Gen.CallGenLeafKit.update_global_func_spec(full_name, func) + + entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry") + if entry_blk is None: return 0 + func_builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, func) + if func_builder is None: return 0 + mlirlite.position_at_end(func_builder, entry_blk) + + mlirlite.build_ret_void(func_builder) + return 0 \ No newline at end of file