From ea54d5685d14c32e2f0c3c80a28e5c16dcda1b3e Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 13 Oct 2026 21:29:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(gen):=20virtual=EF=BC=88HasVTable=EF=BC=89?= =?UTF-8?q?=E7=B1=BB=E8=B1=81=E5=85=8D=E3=80=8C=E6=97=A0=20def=20=3D>=20st?= =?UTF-8?q?ruct=E3=80=8D=E8=A7=84=E5=88=99=EF=BC=8C=E5=BF=85=E9=A1=BB?= =?UTF-8?q?=E5=90=88=E6=88=90=20=5F=5Fbefore=5Finit=5F=5F/=5F=5Finit=5F=5F?= =?UTF-8?q?=20=E4=B8=94=E5=BF=85=E5=8F=91=20vtable=EF=BC=9B=E9=9D=9E?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E6=9E=84=E9=80=A0=E8=B7=AF=E5=BE=84=E6=8C=89?= =?UTF-8?q?=E7=AC=A6=E5=8F=B7=E5=AD=98=E5=9C=A8=E6=80=A7=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/lib/core/Gen/Call/CallGenCtor.vp | 31 ++++++++++++++++- App/lib/core/Gen/Class/ClassGenVTable.vp | 43 ++++++++++++++---------- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/App/lib/core/Gen/Call/CallGenCtor.vp b/App/lib/core/Gen/Call/CallGenCtor.vp index 173b412..3f00034 100644 --- a/App/lib/core/Gen/Call/CallGenCtor.vp +++ b/App/lib/core/Gen/Call/CallGenCtor.vp @@ -157,8 +157,37 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase): nw_bc: mlirlite.Value* = mlirlite.build_bitcast(builder, new_ptr, nw_ty) if nw_bc is not None: storage_ptr = nw_bc + # 与泛型路径(L1166-1181 的 HasInit/存在性判据)对齐:__before_init__ 只由 ClassGenVTable 对 + # 「有方法」或「带 virtual 标记」的类合成,纯数据类没有合成本函数。因此不能在 is_oop != 0 时 + # 无条件调用它,否则对未合成的类发出悬空调用(链接期 undefined symbol)。 + # 判据(任一成立即认为该类确有 __before_init__,均为充分条件,且覆盖跨模块): + # (1) ctor_entry.IsOOP != 0:_translate_oop_methods 正是对这类类合成 __before_init__(含跨模块推断置位); + # (2) ctor_entry.HasInit / 模块内本类 __init__ 声明 / 全局默认参数表:有 __init__ ⇒ 有方法 ⇒ OOP; + # (3) MRO 祖先 HasInit != 0:本类继承祖先 __init__(未合成默认 __init__),但仍合成自己的 __before_init__。 + bi_ok: int = 0 + if ctor_entry is not None and ctor_entry.IsOOP != 0: bi_ok = 1 + if bi_ok == 0 and ctor_entry is not None and ctor_entry.HasInit != 0: bi_ok = 1 + if bi_ok == 0 and class_name is not None: + bi_lk: str = pool.alloc(160) + if bi_lk is not None: + viperlib.snprintf(bi_lk, 160, "%s.__init__", class_name) + if self.find_func_in_module(mod, bi_lk) is not None: bi_ok = 1 + elif self.Gen.CallGenLeafKit.find_func_defaults(bi_lk) is not None: bi_ok = 1 + if bi_ok == 0 and class_name is not None: + # 本类未合成默认 __init__ 的继承场景(祖先有 __init__):仍有自己的 __before_init__。 + bi_mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name) + bi_mi: int = 1 + while bi_mro is not None and bi_mro[bi_mi] != 0: + bi_anc: str = rawptr(bi_mro[bi_mi]) + if bi_anc is not None and string.strcmp(bi_anc, class_name) != 0: + bi_ae: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(bi_anc) + if bi_ae is not None and bi_ae.HasInit != 0: + bi_ok = 1 + break + bi_mi += 1 # OOP struct: call __before_init__ to zero-fill and assign defaults - self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name,"__before_init__", storage_ptr, None, 0, trans, diag_node_ct, is_volatile) + if bi_ok != 0: + self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name,"__before_init__", storage_ptr, None, 0, trans, diag_node_ct, is_volatile) # If __init__ exists, call __init__(ptr, args...); rely only on ctor_entry.HasInit # (classes like JsonValue have __new__ but no __init__ -> forced inference causes undefined refs). diff --git a/App/lib/core/Gen/Class/ClassGenVTable.vp b/App/lib/core/Gen/Class/ClassGenVTable.vp index 0b34422..635c57e 100644 --- a/App/lib/core/Gen/Class/ClassGenVTable.vp +++ b/App/lib/core/Gen/Class/ClassGenVTable.vp @@ -219,7 +219,12 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): mod: mlirlite.LLVMModule* = trans.Module # Collect virtual methods (inherited + own) into Vt* buffers and set entry.VTableMethodCount/VTableMethods. - if self._collect_vtable_methods(trans, cd, class_name) == 0: return 0 + # 带 virtual 标记的类(调用方已保证 HasVTable==1)必须**始终**发射自己的 @".类名.vtable" + # 全局量,即便 VTableMethodCount==0:isinstance() 在运行期要引用该符号(CallGenLeafKit._make_vtable_ref_i8), + # 缺定义会在链接期 undefined symbol。故这里不因「收集到 0 个虚方法」而提前返回,而是继续构造一个 + # 零字段的 vtable(!llvm.struct<()> 合法),其形状与 VTableMethodCount==0 / 对象布局保持一致 + # (对象仅在槽位处保存 vtable 指针,vtable 自身元素个数不影响对象布局)。 + self._collect_vtable_methods(trans, cd, class_name) full_names_buf: sizet* = self.VtFullNames method_names_buf: sizet* = self.VtMethodNames method_parent_sha1s: sizet* = self.VtParentSha1s @@ -350,28 +355,30 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): if fd_meta != 0: self.Gen.ClassGenBase.register_method_meta(class_name, fd_check.name, fd_meta) if has_method == 0: - # 语义规则(OOP):类体内没有任何函数定义(无 def)的 class 一律视为 struct,不做 OOP 合成: + # 语义规则(OOP):类体内没有任何函数定义(无 def)且**不带 virtual 标记**的 class 一律视为 + # struct,不做 OOP 合成: # - 不标记 IsOOP(构造点据此走「分配 + 零/默认值」路径,不再引用 __init__/__before_init__); # - 不合成 __before_init__;不合成默认 __init__。 # has_method==0 恰好等价于「类体里没有 FunctionDef」(Pass 1 见到任一个 def 就置 1)。 - # 原实现按「继承到父类虚方法」把这类无方法子类提升为 OOP,从而给它合成了 __before_init__/默认 __init__, - # 违反上述规则(如 ast.Stmt / ast.ExprNode 这类纯占位基类),本条判据即消除该行为。 - # 但该类若因类级 virtual / 继承自虚类而带 vptr,仍必须登记并发射它自己的 vtable 全局量: - # 1) 字段展平要求子类保留 vptr 槽(与父类布局兼容); - # 2) isinstance(obj, 该类) 运行期要与「该类 + 全部已知子类」的 vtable 地址逐一比较 - # (CallGenLeafKit._translate_isinstance -> _make_vtable_ref_i8 引用 @".类名.vtable"), - # 缺该符号会在链接期 undefined symbol。故这里只跳过 OOP 构造合成,不跳过 vtable。 + # + # 「带 virtual 标记」在本工程里没有独立的「虚类」位,统一走 entry.HasVTable==1: + # - 该标记由 ClassGenDef 依据 ClassGenBase._detect_vtable_status 置位(ClassGenDef.vp:487); + # - _detect_vtable_status 覆盖:类级 @t.CVTable(`virtual class` 的语法糖,parser.vp:556-621 合成)、 + # 方法级 @t.CVTable(`virtual def` 同款合成)、以及继承链上父类 HasVTable==1 的推导 + # (ClassGenBase.vp:883-916)。 + # 即「显式 virtual 标记 或 由虚父类推导出的虚表」这一口径,且不含纯数据类。 + # + # 带 virtual 标记的类不计入上面的「无 def 按 struct」规则:无论类体有无 def,都必须照常构造 + # __before_init__ 与 __init__,并仍标为 OOP。理由(为什么 virtual 类必须保留默认构造): + # 1) 语义上它就是多态类,构造时必须初始化 vptr(否则虚调用 / isinstance 会在运行期错判或解引用空指针); + # 2) 该类的 __before_init__ 承载「写 vtable 指针 + 字段零填 + 默认值」,与是否有自有方法无关。 + # 故这里只对「既无 def 又无 virtual 标记」的纯数据类提前 return;带标记者继续走下方常规 + # OOP 合成(标 IsOOP、发 vtable、发 __before_init__、发默认 __init__)。vtable 全局量的发射 + # 由 _generate_vtable 负责(它即便方法数为 0 也需发射,见该函数注释)。 pd_has_vt: int = 0 if oop_entry is not None: pd_has_vt = oop_entry.HasVTable - if pd_has_vt == 1: - if mark_only != 0: - # Phase 1a:只登记 vtable 元数据(供跨文件查询),不发全局量、不翻译方法体。 - self._collect_vtable_methods(trans, cd, class_name) - return 0 - if oop_entry is not None and oop_entry.MethodsEmitted == 1 and reemit == 0: return 0 - if oop_entry is not None: oop_entry.MethodsEmitted = 1 - self._generate_vtable(trans, cd, class_name) - return 0 + if pd_has_vt != 1: + return 0 # Mark the struct as OOP. if oop_entry is not None: