feat(gen): virtual(HasVTable)类豁免「无 def => struct」规则,必须合成 __before_init__/__init__ 且必发 vtable;非泛型构造路径按符号存在性判定调用

This commit is contained in:
TermiNexus committed 2026-10-13 21:29:24 +08:00
1 parent 99a021ad67
commit ea54d5685d
2 files changed
+55 -19

No files matched your search

+30 -1
View File
@@ -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).
+25 -18
View File
@@ -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)必须**始终**发射自己的 @"<sha1>.类名.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 引用 @"<sha1>.类名.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: