Files
ViperC/App/lib/core/Gen/Class/ClassGenVTable.vp
T
TermiNexus bf0bbcc89b fix(vtable): 虚方法表按类实际规模动态分配,去掉 32 上限
MemBuddy 继承 MemManager 后虚方法约 60+ 个,VTBL_METHOD_MAX=32 会触发 fail-fast(此前静默只收前 32 个,之后的调用点找不到槽位、退化成静态直调)。现在收集前先算容量上界:各父类已收集的 VTableMethodCount 之和 + 本类 FunctionDef 数量 + 1,三个 PermPool 数组按此一次性分配;两处越界检查保留为 InternalCompilerError(理论不可达)。删除 VTBL_METHOD_MAX。注:本次提交同时带上了该文件既有的 t.CPtr → rawptr 迁移与 _generate_default_init 删除等未提交改动。
2026-10-17 19:20:03 +08:00

1201 lines
74 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Type.TypeSpec as TSpec
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.CompilerFactory as CompilerFactory
from . import ClassGenBase
import lib.core.Gen.GenApi as GenApi
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.
define SCOPE_FUNCTION = 1
# ============================================================
# ClassGenVTable - ClassGenMixin split (phase D): vtable collection/generation/method translation.
novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
"""vtable collection, generation, and method translation."""
# _vtable_strcat - append src to the end of dst; fail-fast when it does not fit.
def _vtable_strcat(self, dst: str, dst_size: sizet, src: str):
"""Append src to the end of dst.
A truncated vtable initializer stays plausible LLVM syntax but is cut in the middle of a
bitcast, which then surfaces as a verifier error pointing at an unrelated method.
"""
if dst is None or src is None: return
dlen: sizet = string.strlen(dst)
slen: sizet = string.strlen(src)
if dlen + slen + 1 > dst_size:
self.Gen.TypeGenErr.fatal_error(None, f"vtable initializer text exceeds its buffer: {dlen} byte(s) used + {slen} byte(s) to append + 1 NUL > capacity {dst_size} byte(s); the class has too many virtual methods for the fixed buffer", "OverflowError")
return
i: sizet = 0
while i < slen:
dst[dlen + i] = src[i]
i += 1
dst[dlen + i] = '\0'
# _vtable_name_needs_quote - check whether an LLVM identifier needs quoting.
def _vtable_name_needs_quote(self, name: str) -> int:
"""Check whether an LLVM identifier needs quoting."""
if name is None: return 0
if '0' <= name[0] <= '9': return 1
for i in name:
if i == '.': return 1
return 0
# _collect_vtable_methods - collect virtual methods (inherited first, then own) into the
# Vt* buffers and entry.VTableMethods/VTableMethodCount; returns 1 if any, else 0.
def _collect_vtable_methods(self, trans: HT.Translator*, cd: ast.ClassDef*, class_name: str) -> int:
"""Collect virtual methods into entry.VTableMethods/VTableMethodCount."""
if trans is None or cd is None or class_name is None: return 0
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
children: list[ast.AST*]* = cd.children
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)
# Record each method's parent SHA1 (non-zero = inherited, mangle with parent module SHA1).
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, vt_bytes)
string.memset(method_names_buf, 0, vt_bytes)
string.memset(method_parent_sha1s, 0, vt_bytes)
method_count: int = 0
# Inherit parent virtual methods first (stable slot order); overridden methods use the
# 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).
if parent_count > 0:
for pi in range(int(parent_count)):
pname: str = self.Gen.StructGen.get_parent_name_at(class_name, pi)
if pname is None: continue
# Look up the parent entry (prefer same-module SHA1 match).
vt_parent_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(pname, trans.ModuleSha1)
if vt_parent_entry is None:
# Cross-module inheritance: fall back to lookup by class name.
vt_parent_entry = self.Gen.StructGen.find_struct(pname)
parent_vt_count: int = 0
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")
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
# Multi-inheritance dedupe: skip a short name already present (first declared wins).
if self.Gen.ClassGenBase._field_name_exists(method_names_buf, method_count, pmethod_name) != 0: continue
# Check whether the subclass overrides the method and whether it is @t.NoVTable.
child_overrides: int = 0
child_nvt: int = 0
for ci2 in range(int(cn)):
stmt2: ast.AST* = children.get(ci2)
if stmt2 is None: continue
if stmt2.kind() != ast.ASTKind.FunctionDef: continue
fd2: ast.FunctionDef* = ast.FunctionDef*(stmt2)
if fd2 is not None and fd2.name is not None:
if string.strcmp(fd2.name, pmethod_name) == 0:
child_overrides = 1
# Check whether the overriding subclass method is marked @t.NoVTable.
if fd2.decorator_list is not None:
if self.Gen.ClassGenBase._has_decorator(fd2.decorator_list, "NoVTable") == 1: child_nvt = 1
break
# Override without NoVTable uses the subclass; otherwise resolve the actual defining
# class along the parent chain so full_name points to a real function (slot stays non-null).
def_class: str = class_name
if child_overrides == 1 and child_nvt == 0: def_class = class_name
else:
def_class = pname
# Multi-inheritance: probe pname's MRO ancestors for the real defining class (first declared wins).
mro_ptr: sizet* = self.Gen.StructGen.get_mro_chain(pool, pname)
mro_i: int = 1
while mro_ptr is not None and mro_ptr[mro_i] != 0:
anc: str = rawptr(mro_ptr[mro_i])
if anc is not None:
probe_buf: str = pool.alloc(128)
if probe_buf is not None:
viperlib.snprintf(probe_buf, 128, "%s.%s", anc, pmethod_name)
if self.Gen.CallGenLeafKit.find_func_global(probe_buf) is not None:
def_class = anc
break
mro_i += 1
pfname_buf: str = CompilerFactory.factory.PermPool.alloc(128)
if pfname_buf is not None:
viperlib.snprintf(pfname_buf, 128, "%s.%s", def_class, pmethod_name)
full_names_buf[method_count] = sizet(pfname_buf)
# Copy the method short name.
pmname_len: sizet = string.strlen(pmethod_name)
pmname_buf: str = CompilerFactory.factory.PermPool.alloc(pmname_len + 1)
if pmname_buf is not None:
string.strcpy(pmname_buf, pmethod_name)
method_names_buf[method_count] = sizet(pmname_buf)
# Record the SHA1 used for mangling (inherited methods use the defining class's module SHA1).
if child_overrides == 1 and child_nvt == 0:
# Subclass override: use the current module SHA1 (keep 0).
method_parent_sha1s[method_count] = 0
else:
# Inherited: use the defining class's module SHA1 so cross-module mangling is correct.
def_entry2: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(def_class)
if def_entry2 is not None and def_entry2.ModuleSha1 is not None: method_parent_sha1s[method_count] = sizet(def_entry2.ModuleSha1)
else: method_parent_sha1s[method_count] = 0
method_count += 1
# 2. Collect this class's virtual methods (skip ones already inherited).
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
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")
mname: str = fd.name
# Skip constructors and special methods (they must not enter the vtable).
if string.strcmp(mname, "__init__") == 0: continue
if string.strcmp(mname, "__before_init__") == 0: continue
if string.strcmp(mname, "__new__") == 0: continue
# Function-level decorator filter: decide whether the method belongs in the vtable.
if self.Gen.ClassGenBase._should_method_be_virtual(cd, fd, trans) == 0: continue
# Skip if already inherited (avoid duplicates).
already_in_vt: int = 0
for mi in range(int(method_count)):
exist_addr: sizet = method_names_buf[mi]
if exist_addr != 0:
exist_name: str = rawptr(exist_addr)
if exist_name is not None and string.strcmp(exist_name, mname) == 0:
already_in_vt = 1
break
if already_in_vt == 1: continue
# full_name = "ClassName.method_name"; must live in perm_pool because phase 1b reads
# VTableMethods cross-file after each 1a file resets the pool.
fname_buf: str = CompilerFactory.factory.PermPool.alloc(128)
if fname_buf is not None:
viperlib.snprintf(fname_buf, 128, "%s.%s", class_name, mname)
full_names_buf[method_count] = sizet(fname_buf)
# Copy the method name.
mname_len: sizet = string.strlen(mname)
mname_buf: str = CompilerFactory.factory.PermPool.alloc(mname_len + 1)
if mname_buf is not None:
string.strcpy(mname_buf, mname)
method_names_buf[method_count] = sizet(mname_buf)
method_count += 1
# Stash results for _generate_vtable reuse.
self.VtFullNames = full_names_buf
self.VtMethodNames = method_names_buf
self.VtParentSha1s = method_parent_sha1s
self.VtMethodCount = method_count
# Set entry metadata for virtual dispatch (must be ready before translating method bodies).
if vt_self_entry is not None and method_count > 0:
vt_self_entry.VTableMethodCount = method_count
vt_self_entry.VTableMethods = method_names_buf
if method_count == 0: return 0
return 1
# _generate_vtable - build the VTable global: collect virtual methods, build the
# { i8*, ... } type and initializer, create @"SHA1.ClassName.vtable", store names on the entry.
def _generate_vtable(self, trans: HT.Translator*, cd: ast.ClassDef*, class_name: str) -> int:
"""Build the VTable global variable."""
if trans is None or cd is None or class_name is None: return 0
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
# Collect virtual methods (inherited + own) into Vt* buffers and set entry.VTableMethodCount/VTableMethods.
# 带 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
method_count: int = self.VtMethodCount
# Re-fetch the entry (to store VTableMethods later).
vt_self_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(class_name, trans.ModuleSha1)
# 2. Build the vtable type: { i8*, i8*, ..., i8* }.
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
first_node: mlirlite.ParamNode* = None
tail_node: mlirlite.ParamNode* = None
for i in range(int(method_count)):
pnode: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i8_ptr_ty)
if pnode is None: continue
tail_node = mlirlite.param_list_append(first_node, tail_node, pnode)
if first_node is None: first_node = tail_node
vtable_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, first_node, method_count, None)
if vtable_ty is None: return 0
# Size the initializer from the real method list: each entry is
# "i8* bitcast (<func ty>* @<mangled> to i8*)", where the function type text alone can
# reach 1024 bytes and the mangled name carries a SHA1 prefix. The old fixed 16384 bytes
# silently cut long vtables in the middle of a bitcast.
init_cap: sizet = 64
for si in range(int(method_count)):
name_addr0: sizet = full_names_buf[si]
if name_addr0 == 0:
init_cap = init_cap + 12
else:
init_cap = init_cap + 1024 + 96 + string.strlen(rawptr(name_addr0)) + 128
init_buf: str = pool.alloc(init_cap)
if init_buf is None: return 0
init_buf[0] = '\0'
self._vtable_strcat(init_buf, init_cap, "{ ")
for i in range(int(method_count)):
if i > 0: self._vtable_strcat(init_buf, init_cap, ", ")
full_name_addr: sizet = full_names_buf[i]
if full_name_addr == 0:
self._vtable_strcat(init_buf, init_cap, "i8* null")
continue
full_name: str = rawptr(full_name_addr)
# Check whether this is an inherited method (non-zero parent SHA1 means parent-module mangling).
parent_sha1_addr: sizet = method_parent_sha1s[i]
if parent_sha1_addr != 0:
# Inherited method: mangle with the parent class's module SHA1.
parent_sha1: str = rawptr(parent_sha1_addr)
mangled_name: str = self.Gen.FuncGenBase._mangle_name_with_sha1(parent_sha1, full_name)
else:
# Current class method: mangle with the current module SHA1.
mangled_name: str = self.Gen.FuncGenBase._mangle_name(trans, full_name)
# Derive the function type text from the global entry.Spec (filled by update_global_func_spec,
# PermPool-resident, safe across 1b reset); do not deref func, which may be reclaimed.
ge: GenTypes.FuncEntry* = self.Gen.CallGenLeafKit.find_func_entry_global(full_name)
func_ty_buf: str = pool.alloc(1024)
if func_ty_buf is None:
self._vtable_strcat(init_buf, init_cap, "i8* null")
continue
func_ty_buf[0] = '\0'
if ge is None or ge.Spec is None or TSpec.SpecToLLVMText(ge.Spec, func_ty_buf, 1024) != 0:
self.Gen.TypeGenErr.fatal_error(cd, f"vtable method '{full_name}' has no function type", "InternalCompilerError")
continue
# Build the bitcast string: i8* bitcast (func_ty* @mangled to i8*).
self._vtable_strcat(init_buf, init_cap, "i8* bitcast (")
self._vtable_strcat(init_buf, init_cap, func_ty_buf)
self._vtable_strcat(init_buf, init_cap, "* @")
if self._vtable_name_needs_quote(mangled_name) != 0:
self._vtable_strcat(init_buf, init_cap, "\"")
self._vtable_strcat(init_buf, init_cap, mangled_name)
self._vtable_strcat(init_buf, init_cap, "\"")
else: self._vtable_strcat(init_buf, init_cap, mangled_name)
self._vtable_strcat(init_buf, init_cap, " to i8*)")
self._vtable_strcat(init_buf, init_cap, " }")
# 4. Build the vtable global variable name.
vtable_name_buf: str = pool.alloc(128)
if vtable_name_buf is None: return 0
viperlib.snprintf(vtable_name_buf, 128, "%s.vtable", class_name)
vtable_mangled: str = self.Gen.FuncGenBase._mangle_name(trans, vtable_name_buf)
# 5. Create the global variable.
gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, vtable_mangled, vtable_ty)
if gv is None: return 0
gv.Initializer = init_buf
gv.IsConstant = 1
# Default external linkage: isinstance() references other modules' vtable symbols, so
# internal linkage fails to link; names are globally unique via the module SHA1 prefix.
# Add to the module.
mlirlite.module_add_global(mod, gv)
# 6. Store the method name list on the entry (avoid cross-module same-name lookups).
if vt_self_entry is not None:
vt_self_entry.VTableMethods = method_names_buf
vt_self_entry.VTableMethodCount = method_count
return 0
# _translate_oop_methods - scan the class body, mark OOP/has_init, translate each method
# (SHA1.ClassName.method_name), then emit __before_init__.
def _translate_oop_methods(self, trans: HT.Translator*, cd: ast.ClassDef*, struct_ty: mlirlite.LLVMType*, class_name: str, mark_only: int = 0, reemit: int = 0) -> int:
"""Scan class-body methods, translate them, and emit __before_init__."""
if trans is None or cd is None or struct_ty is None or class_name is None: return 0
children: list[ast.AST*]* = cd.children
if children is None: return 0
cn: sizet = len(children)
has_method: int = 0
has_init: int = 0
has_new: int = 0
# Locate the entry by type pointer early (avoids cross-module same-name lookup).
oop_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(struct_ty)
# Pass 1: detect methods, mark OOP, and register decorator metadata.
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
if stmt.kind() == ast.ASTKind.FunctionDef:
has_method = 1
fd_check: ast.FunctionDef* = ast.FunctionDef*(stmt)
if fd_check is not None and fd_check.name is not None:
if string.strcmp(fd_check.name, "__init__") == 0: has_init = 1
if string.strcmp(fd_check.name, "__new__") == 0: has_new = 1
# Register decorator metadata (@property/@staticmethod/@classmethod/@x.setter etc.)
# here (including mark_only struct pre-registration) so other modules can resolve attributes.
fd_meta: int = self.Gen.ClassGenBase._extract_func_meta(fd_check.decorator_list)
if fd_meta != 0: self.Gen.ClassGenBase.register_method_meta(class_name, fd_check.name, fd_meta)
if has_method == 0:
# 语义规则(OOP):类体内没有任何函数定义(无 def)且**不带 virtual 标记**的 class 一律视为
# struct,不做 OOP 合成:
# - 不标记 IsOOP(构造点据此走「分配 + 零/默认值」路径,不再引用 __init__/__before_init__);
# - 不合成 __before_init__;不合成默认 __init__。
# has_method==0 恰好等价于「类体里没有 FunctionDef」(Pass 1 见到任一个 def 就置 1)。
#
# 「带 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:
return 0
# Mark the struct as OOP.
if oop_entry is not None:
oop_entry.IsOOP = 1
if has_init != 0: oop_entry.HasInit = 1
if has_new != 0: oop_entry.HasNew = 1
# mark_only mode: set flags only, do not translate bodies.
if mark_only != 0:
# Phase 1a also collects vtable metadata; otherwise callers may see HasVTable=1 but
# VTableMethods=None and fall back to static binding (crash); repeated collection is idempotent.
mk_has_vt: int = 0
if oop_entry is not None: mk_has_vt = oop_entry.HasVTable
if mk_has_vt == 1: self._collect_vtable_methods(trans, cd, class_name)
return 0
# Skip if MethodsEmitted==1 (bodies and __before_init__ already emitted; avoids duplicate
# symbols), except in reemit mode, where bodies still need forced emission.
if oop_entry is not None and oop_entry.MethodsEmitted == 1 and reemit == 0: return 0
# Mark MethodsEmitted=1 before translating bodies: __iter__'s return annotation can
# re-enter specialization and re-emit the same methods (invalid redefinition).
if oop_entry is not None: oop_entry.MethodsEmitted = 1
# Pass 2: pre-register vtable metadata before translating bodies, so self.<virtual> calls
# inside bodies (e.g. MemManager.calloc -> self.alloc) dispatch instead of binding statically.
oop_has_vt_pre: int = 0
if oop_entry is not None: oop_has_vt_pre = oop_entry.HasVTable
if oop_has_vt_pre == 1: self._collect_vtable_methods(trans, cd, class_name)
# Declare-before-define: forward-declare every method so lookups no longer depend on
# source order (e.g. FileW.__init__ calling self.seek defined later in the class).
for ci_pd in range(int(cn)):
stmt_pd: ast.AST* = children.get(ci_pd)
if stmt_pd is None: continue
if stmt_pd.kind() == ast.ASTKind.FunctionDef:
fd_pd: ast.FunctionDef* = ast.FunctionDef*(stmt_pd)
if fd_pd is not None and fd_pd.name is not None: self._translate_method(trans, fd_pd, struct_ty, class_name, 1)
for ci in range(int(cn)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
if stmt.kind() == ast.ASTKind.FunctionDef:
fd: ast.FunctionDef* = ast.FunctionDef*(stmt)
if fd is not None and fd.name is not None:
# 方法体翻译 = 下潜进一个源码上下文:推一帧(def 位置),供 traceback 回溯。
tr_mf: str = self.Gen.TypeGenResolve.get_current_file()
# §13 NLL:记录当前方法体(谓词 NllFrameConfined 要遍历它)
prev_nf: ast.AST* = self.Gen.StructGen.NllCurFunc
self.Gen.StructGen.NllCurFunc = fd
self.Gen.TypeGenBase.push_trace(tr_mf, fd.lineno)
self._translate_method(trans, fd, struct_ty, class_name)
self.Gen.TypeGenBase.pop_trace()
self.Gen.StructGen.NllCurFunc = prev_nf
# If there is a vtable, generate it before __before_init__ (which reads VTableMethodCount);
# check via oop_entry directly to avoid cross-module same-name lookups.
has_vt_flag: int = 0
if oop_entry is not None: has_vt_flag = oop_entry.HasVTable
if has_vt_flag == 1: self._generate_vtable(trans, cd, class_name)
# Emit __before_init__ (zero-fill + defaults + vtable pointer).
self._generate_before_init(trans, struct_ty, class_name)
# Emit a default (empty) __init__ when neither this class nor any MRO ancestor defines one.
# Why: an explicit `obj.__init__()` in source lowers to a direct call @SHA1.Class.__init__
# (the call site is never narrowed to a no-op), so the symbol must exist or ld.lld fails
# with "undefined symbol: ...__init__". The body is intentionally empty -- __before_init__
# owns zero-fill / defaults / vtable setup, and a class without a user __init__ has no init
# logic to run (matches the documented placement-new idiom `obj.__before_init__()` then
# `obj.__init__(args)`).
# Skipped when an ancestor has a user __init__: emitting a child default would shadow it and
# silently drop the inherited initialization for explicit `child.__init__()` calls.
di_needed: int = 0
if has_init == 0:
di_needed = 1
di_pool: memhub.MemBuddy* = trans.Pool
if di_pool is not None:
di_mro: sizet* = self.Gen.StructGen.get_mro_chain(di_pool, class_name)
di_mi: int = 1
while di_mro is not None and di_mro[di_mi] != 0:
di_anc: str = rawptr(di_mro[di_mi])
if di_anc is not None and string.strcmp(di_anc, class_name) != 0:
di_anc_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(di_anc)
if di_anc_entry is not None and di_anc_entry.HasInit != 0:
di_needed = 0
break
di_mi += 1
if di_needed != 0:
self._generate_default_init(trans, struct_ty, class_name)
return 0
# _translate_method - emit define <ret_ty> @SHA1.ClassName.method_name(Ptr(StructTy) %self, ...).
# self is registered as an SSA Ptr(struct_ty) value (no alloca) so self.field can GEP directly.
def _translate_method(self, trans: HT.Translator*, fd: ast.FunctionDef*, struct_ty: mlirlite.LLVMType*, class_name: str, sig_only: int = 0) -> int:
"""Translate a single method; sig_only emits a forward declaration only."""
if trans is None or fd is None or struct_ty is None or class_name is None: return 0
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
func_count: int = trans._func_count
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
self_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, struct_ty)
method_name: str = fd.name
if method_name is None:
# fail-fast: method name is missing (the parser cannot produce a nameless def).
VLogger.error("method name missing (fd.name is None)", "OOP")
self.Gen.TypeGenErr.fatal_error(fd, "method name missing (fd.name is None)", "TypeError")
# Extract decorator metadata (@property/@staticmethod/@classmethod/@x.setter/@x.deleter).
func_meta: int = self.Gen.ClassGenBase._extract_func_meta(fd.decorator_list)
# Property setter/deleter names get a $set/$del suffix to avoid clashing with the getter.
method_name_buf: str = pool.alloc(128)
if method_name_buf is None: return 0
viperlib.snprintf(method_name_buf, 128, "%s", fd.name)
if func_meta & GenTypes.META_PROP_SETTER != 0:
# 128-byte buffer: check remaining capacity before appending "$set" to avoid unbounded strcat.
mn_len2: sizet = string.strlen(method_name_buf)
if mn_len2 + 5 < 128: string.strcat(method_name_buf, "$set")
elif func_meta & GenTypes.META_PROP_DELETER != 0:
mn_len2: sizet = string.strlen(method_name_buf)
if mn_len2 + 5 < 128: string.strcat(method_name_buf, "$del")
method_name = method_name_buf
is_static_m: int = 0
is_class_m: int = 0
is_tls_m: int = 0
if func_meta & GenTypes.META_STATIC != 0: is_static_m = 1
if func_meta & GenTypes.META_CLASS != 0: is_class_m = 1
if func_meta & META_TLS != 0: is_tls_m = 1
# Build the full method name: ClassName.method_name.
full_name_buf: str = pool.alloc(128)
if full_name_buf is None: return 0
viperlib.snprintf(full_name_buf, 128, "%s.%s", class_name, method_name)
full_name: str = full_name_buf
# SHA1 namespace mangling.
mangled_name: str = self.Gen.FuncGenBase._mangle_name(trans, full_name)
# Infer the return type.
ret_ty: mlirlite.LLVMType* = None
if fd.returns is not None:
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a method return type")
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
if ret_ty is None and fd.returns is not None:
# t.State / t.CExport / t.CExtern markers mean Void: such annotations carry no real return
# type (written as "<original> | t.<marker>"); only the marker itself reaches here.
if self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0) != 0 or self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CExport", 0) != 0 or self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CExtern", 0) != 0:
ret_ty = mlirlite.Void(pool)
else:
# [S4 fail-fast] return annotation failed to resolve: do not fall back to infer_return_type (silent mismatch).
self.Gen.TypeGenErr.fatal_error(fd, f"method '{class_name}.{method_name}' return type annotation failed to resolve", "NameError")
return 0
if ret_ty is None:
# __init__ and __before_init__ return void.
if string.strcmp(method_name, "__init__") == 0: ret_ty = mlirlite.Void(pool)
# __new__ returns Ptr(struct_ty) (the struct pointer serves as storage).
elif string.strcmp(method_name, "__new__") == 0: ret_ty = mlirlite.Ptr(pool, struct_ty)
else:
param_types_str: str = self.Gen.TypeGenErr.build_param_types_str(pool, fd.args)
ret_ty = self.Gen.TypeGenErr.infer_return_type(pool, fd.children, param_types_str)
# Create/reuse the LLVM function. When the sig_only pre-scan already created a same-name
# declaration, skip param/table setup and just clear IsDeclared to define (mirrors FuncGenDef).
func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
decl_reuse: int = 0
if func is not None:
if func.IsDeclared == 0:
# Same-name body already emitted: refuse to emit again (duplicate symbol).
return 0
decl_reuse = 1
else:
func = mlirlite.create_declare(pool, mod, mangled_name, ret_ty)
if func is None: return 0
if sig_only != 0:
# Forward-declaration mode: already declared -> return; otherwise register params/table below.
if decl_reuse != 0: return 0
else:
# Clear IsDeclared before emitting the body, else the printer emits a declare and the body is lost.
func.IsDeclared = 0
# The parameter list is needed by both the body section and the param-creation block, so declare
# it outside the decl_reuse branch (reuse skips creation but the body still walks params).
args_node: ast.Arguments* = fd.args
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: rawptr = None
md_default_count: int = 0
md_param_count: int = 0
md_args_node: ast.Arguments* = fd.args
if md_args_node is not None:
md_ags: ast.Arguments* = ast.Arguments*(md_args_node)
if md_ags.args is not None:
md_alist: list[ast.AST*]* = md_ags.args
if is_static_m != 0: md_param_count = int(len(md_alist))
else: md_param_count = int(len(md_alist)) - 1
if md_ags.defaults is not None:
md_dlist: list[ast.AST*]* = md_ags.defaults
md_defaults = md_dlist
md_default_count = int(len(md_dlist))
# Register in the function table (lookup name ClassName.method_name, suffix matching).
max_funcs: int = 1024
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, full_name, func, max_funcs, md_defaults, md_default_count, md_param_count, pool) == 0:
trans._func_count = func_count + 1
# Add self/cls (Ptr(struct_ty)); skip for @staticmethod. @classmethod's cls also uses Ptr(struct_ty).
if is_static_m == 0:
first_pname: str = "self"
if is_class_m != 0 and fd.args is not None:
fd_ags: ast.Arguments* = ast.Arguments*(fd.args)
if fd_ags.args is not None and len(fd_ags.args) > 0:
fd_first_arg: ast.Arg* = ast.Arg*(fd_ags.args.get(0))
if fd_first_arg is not None and fd_first_arg.arg is not None: first_pname = fd_first_arg.arg
first_pname_buf: str = pool.alloc(32)
if first_pname_buf is not None: viperlib.snprintf(first_pname_buf, 32, "%%%s", first_pname)
mlirlite.add_param(pool, func, self_ptr_ty, first_pname_buf)
# Add remaining params (with annotations); @staticmethod starts at 0, others skip index 0 (self/cls).
if args_node is not None:
ags: ast.Arguments* = ast.Arguments*(args_node)
if ags.args is not None:
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
start_idx: int = 0
if is_static_m == 0: start_idx = 1
for ai in range(int(start_idx), int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is not None and arg.arg is not None:
param_ty: mlirlite.LLVMType* = i32_ty
if arg.annotation is not None:
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
if resolved is None:
# [S4 fail-fast] annotation present but failed to resolve: no silent i32 (signature/call/impl mismatch).
self.Gen.TypeGenErr.fatal_error(arg, f"method '{class_name}.{method_name}' param '{arg.arg}' type annotation failed to resolve", "NameError")
return 0
# Bare-class param -> Ptr(struct) (objects are pointers; by-value would dangle after append).
# CArray param -> element pointer ty* (C array decay, same as function params).
match resolved:
case mlirlite.LLVMType.Void():
# void is not a legal param type (only void* and typedef opaque structs); void*
# is already merged into i8* upstream, so fail fast at compile time instead of emitting invalid IR.
self.Gen.TypeGenErr.fatal_error(arg, f"method '{class_name}.{method_name}' param '{arg.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
return 0
case mlirlite.LLVMType.Struct(_, _, _):
param_ty = mlirlite.Ptr(pool, resolved)
case mlirlite.LLVMType.Array(aelem_ty, _):
param_ty = mlirlite.Ptr(pool, aelem_ty)
case _:
param_ty = resolved
pname: str = pool.alloc(32)
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
mlirlite.add_param(pool, func, param_ty, pname)
# Register value-reference (&T / &temp T) parameter modes (index 0 = self for non-static methods).
mode_buf: str = pool.alloc(an + 1)
if mode_buf is not None:
mode_cnt: int = 0
has_ref_m: int = 0
if is_static_m == 0:
mode_buf[mode_cnt] = char(48)
mode_cnt += 1
for ai_m in range(int(start_idx), int(an)):
arg_m: ast.Arg* = ast.Arg*(alist.get(ai_m))
mm: int = 0
if arg_m is not None and arg_m.annotation is not None:
if self.Gen.TypeGenResolve._is_t_attr(arg_m.annotation, "CVoid", imported_modules) == 0:
mm = self.Gen.TypeGenResolve.ref_param_mode(arg_m.annotation, imported_modules)
if mm == 1: mode_buf[mode_cnt] = char(49)
elif mm == 2: mode_buf[mode_cnt] = char(50)
else: mode_buf[mode_cnt] = char(48)
if mm != 0: has_ref_m = 1
mode_cnt += 1
mode_buf[mode_cnt] = char(0)
if has_ref_m != 0: self.Gen.CallGenBase._register_auto_addr_modes(mangled_name, mode_buf, mode_cnt)
# OOP iter protocol: __next__ gets a hidden __stop_iter_flag__ (i1*), set to 1 by raise
# StopIteration. It is not counted in md_param_count; the for loop passes it explicitly.
if string.strcmp(method_name, "__next__") == 0:
i1_ty_si: mlirlite.LLVMType* = mlirlite.Int1(pool)
if i1_ty_si is not None:
i1_ptr_ty_si: mlirlite.LLVMType* = mlirlite.Ptr(pool, i1_ty_si)
if i1_ptr_ty_si is not None: mlirlite.add_param(pool, func, i1_ptr_ty_si, "%__stop_iter_flag__")
# Params done: finalize the global function-table Spec (vtable derives type text from it after reset).
self.Gen.CallGenLeafKit.update_global_func_spec(full_name, func)
# Forward-declaration mode: signature and table registration done, do not emit the body.
if sig_only != 0: return 0
# Create the entry block.
entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry")
if entry_blk is None: return 0
# Create the method's own builder.
func_builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, func)
if func_builder is None: return 0
mlirlite.position_at_end(func_builder, entry_blk)
# --traceback: attach a DISubprogram to this method define (same as the
# module-level function path). No-op when DI is disabled.
dbg_pushed: int = DebugInfoGen.PushFunction(pool, func_builder, func, fd.lineno, fd.col_offset + 1)
# @t.TLS one-time init guard (same implementation as FuncGenMixin.translate_function_def, also for methods).
if is_tls_m != 0:
# Exact capacities: the reference used to be 64 while the definition was 128, so a long
# method name produced a reference to a different (nonexistent) global.
tls_m_flag_cap: sizet = string.strlen(method_name) + 24
tls_m_flag_name: str = pool.alloc(tls_m_flag_cap)
if tls_m_flag_name is not None:
viperlib.snprintf(tls_m_flag_name, tls_m_flag_cap, "__tls_init_done_%s", method_name)
tls_m_gv: mlirlite.GlobalVariable* = mlirlite.create_global_int(pool, mod, tls_m_flag_name, 1, 0)
if tls_m_gv is not None:
i1_ty_m: mlirlite.LLVMType* = mlirlite.Int1(pool)
if i1_ty_m is not None:
# GlobalVariable is not a Value subclass: build a Value view before load/store.
tls_m_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i1_ty_m)
tls_m_rn_cap: sizet = string.strlen(tls_m_flag_name) + 2
tls_m_rn: str = pool.alloc(tls_m_rn_cap)
if tls_m_rn is not None:
viperlib.snprintf(tls_m_rn, tls_m_rn_cap, "@%s", tls_m_flag_name)
tls_m_ref: mlirlite.Value* = mlirlite.SSAValue(pool, tls_m_ptr_ty, tls_m_rn)
if tls_m_ref is not None:
tls_m_ld: mlirlite.Value* = mlirlite.build_load(func_builder, i1_ty_m, tls_m_ref)
lc_m: int = trans._label_counter
trans._label_counter = lc_m + 1
tls_m_buf: str = pool.alloc(64)
if tls_m_buf is not None:
viperlib.snprintf(tls_m_buf, 64, "tls.skip.%d", lc_m)
tls_m_skip: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, tls_m_buf)
viperlib.snprintf(tls_m_buf, 64, "tls.cont.%d", lc_m)
tls_m_cont: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, tls_m_buf)
if tls_m_skip is not None and tls_m_cont is not None:
mlirlite.build_cond_br(func_builder, tls_m_ld, tls_m_skip, tls_m_cont)
mlirlite.position_at_end(func_builder, tls_m_skip)
match ret_ty:
case mlirlite.LLVMType.Void():
mlirlite.build_ret_void(func_builder)
case mlirlite.LLVMType.Int(_):
tlm0: mlirlite.Value* = mlirlite.ConstInt(pool, ret_ty, 0, "0")
if tlm0 is not None: mlirlite.build_ret(func_builder, tlm0)
case mlirlite.LLVMType.Float(_):
tlmf: mlirlite.Value* = mlirlite.ConstFloat(pool, ret_ty, 0.0)
if tlmf is not None: mlirlite.build_ret(func_builder, tlmf)
case _:
tlmn: mlirlite.Value* = mlirlite.ConstNull(pool, ret_ty, "null")
if tlmn is not None: mlirlite.build_ret(func_builder, tlmn)
mlirlite.position_at_end(func_builder, tls_m_cont)
tlmo: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty_m, 1, "1")
if tlmo is not None: mlirlite.build_store(func_builder, tlmo, tls_m_ref)
# Enter the function scope.
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION)
# Create and store alloca for self/cls (like other params); skip for @staticmethod. self must use
# alloca: an SSA value would load once more, turning Ptr(struct_ty) into a struct value (ABI mismatch).
if is_static_m == 0:
self_alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, self_ptr_ty)
if self_alloca is not None:
self_var_name: str = "self"
self_ssa_name: str = "%self"
if is_class_m != 0 and args_node is not None:
cags: ast.Arguments* = ast.Arguments*(args_node)
if cags.args is not None and len(cags.args) > 0:
cfirst: ast.Arg* = ast.Arg*(cags.args.get(0))
if cfirst is not None and cfirst.arg is not None:
self_var_name = cfirst.arg
self_ssa_name_buf: str = pool.alloc(32)
if self_ssa_name_buf is not None:
viperlib.snprintf(self_ssa_name_buf, 32, "%%%s", cfirst.arg)
self_ssa_name = self_ssa_name_buf
self.Gen.VarGen.define_var(trans.SymTab, self_var_name, self_alloca)
# Set the self/cls annotation class name (fallback for attribute lookup_field).
self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, self_var_name, class_name)
self_val: mlirlite.Value* = mlirlite.SSAValue(pool, self_ptr_ty, self_ssa_name)
mlirlite.build_store(func_builder, self_val, self_alloca)
# Create and store alloca for other params (like plain functions; @staticmethod starts at 0).
if args_node is not None:
ags2: ast.Arguments* = ast.Arguments*(args_node)
if ags2.args is not None:
alist2: list[ast.AST*]* = ags2.args
an2: sizet = len(alist2)
start_idx2: int = 0
if is_static_m == 0: start_idx2 = 1
for ai2 in range(int(start_idx2), int(an2)):
arg2: ast.Arg* = ast.Arg*(alist2.get(ai2))
if arg2 is not None and arg2.arg is not None:
param_ty2: mlirlite.LLVMType* = i32_ty
if arg2.annotation is not None:
resolved2: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg2.annotation, imported_modules, from_imports, trans, 0)
if resolved2 is None:
# fail-fast: param has an annotation but type resolution failed; no silent i32.
self.Gen.TypeGenErr.fatal_error(arg2, f"method '{class_name}.{method_name}' argument '{arg2.arg}' failed to resolve type", "NameError")
resolved2 = i32_ty # Unreachable (fatal_error already exits).
if resolved2 is not None:
# Keep in sync with add_param: bare class -> Ptr(struct) (objects are pointers); CArray -> element pointer ty*.
match resolved2:
case mlirlite.LLVMType.Void():
# void is not a legal param type (same rule as above).
self.Gen.TypeGenErr.fatal_error(arg2, f"method '{class_name}.{method_name}' param '{arg2.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
return 0
case mlirlite.LLVMType.Struct(_, _, _):
param_ty2 = mlirlite.Ptr(pool, resolved2)
case mlirlite.LLVMType.Array(aelem_ty2, _):
param_ty2 = mlirlite.Ptr(pool, aelem_ty2)
case _:
param_ty2 = resolved2
alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, param_ty2)
if alloca is not None:
self.Gen.VarGen.define_var(trans.SymTab, arg2.arg, alloca)
# Store the raw annotation class name (call detection falls back from Ptr(i8) to a class-name lookup).
if arg2.annotation is not None:
cls_nm_oc: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(arg2.annotation, imported_modules, pool, trans._from_imports)
if cls_nm_oc is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, arg2.arg, cls_nm_oc)
pname2: str = pool.alloc(32)
if pname2 is not None: viperlib.snprintf(pname2, 32, "%%%s", arg2.arg)
param_val: mlirlite.Value* = mlirlite.SSAValue(pool, param_ty2, pname2)
mlirlite.build_store(func_builder, param_val, alloca)
# OOP iter protocol: initialize __next__'s stop-flag (store 0) and save the pointer to trans
# so raise StopIteration writes 1; the name must match add_param's "%__stop_iter_flag__".
if string.strcmp(method_name, "__next__") == 0:
i1_ty_sf: mlirlite.LLVMType* = mlirlite.Int1(pool)
if i1_ty_sf is not None:
i1_ptr_ty_sf: mlirlite.LLVMType* = mlirlite.Ptr(pool, i1_ty_sf)
if i1_ptr_ty_sf is not None:
flag_ref_sf: mlirlite.Value* = mlirlite.SSAValue(pool, i1_ptr_ty_sf, "%__stop_iter_flag__")
if flag_ref_sf is not None:
trans._stop_iter_flag_ptr = flag_ref_sf
zero_i1_sf: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty_sf, 0, "0")
if zero_i1_sf is not None: mlirlite.build_store(func_builder, zero_i1_sf, flag_ref_sf)
# Save the module-level scope state.
old_func: mlirlite.Function* = trans._cur_func
old_builder: mlirlite.IRBuilder* = trans._cur_builder
old_global_count: int = trans._global_name_count
old_nonlocal_count: int = trans._nonlocal_name_count
old_env_count: int = trans._closure_env_count
old_stop_iter_flag: mlirlite.Value* = trans._stop_iter_flag_ptr
trans._cur_func = func
trans._cur_builder = func_builder
GenApi.clear_scope_names(trans)
# P2 willFree 失效表:方法体也走独立路径 → 同样做函数作用域复位(栈槽地址会被后续函数复用)
self.Gen.VarGen.freed_clear()
# M3 owned slife:方法体也走**独立**的转译路径(与 FuncGenMixin 的嵌套函数同款),
# 必须自己抬升/弹回登记区间,否则方法内登记会落进外层的区间 → 内层出口 free 外层对象,
# 外层出口再 free 一次 = double free(堆损坏)。
old_slife_base: int = self.Gen.VarGen._SlifeReset(pool)
# Pre-scan the body to create local allocas; pre-scan Global/Nonlocal first, otherwise a
# later-declared global is created as a local alloca and never written (reads all zeros).
body: list[ast.AST*]* = fd.children
if body is not None:
bn: sizet = len(body)
for bi in range(int(bn)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_scope_decls(trans, stmt)
for bi in range(int(bn)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_allocas(trans, stmt)
# Translate the method body.
if body is not None:
bn2: sizet = len(body)
for bi2 in range(int(bn2)):
stmt2: ast.AST* = body.get(bi2)
if stmt2 is not None: self.Gen.StmtGen.translate_stmt(trans, stmt2)
# Epilogue mirrors module-level functions: void/no-annotation falls through (ret void / ConstZero);
# control-flow last stmt with unreachable merge -> unreachable; annotated non-void fall-through -> fail-fast.
last_is_return: int = 0
# Init bn3 early so it is not undefined when body is None.
bn3: sizet = 0
if body is not None:
bn3 = len(body)
if bn3 > 0:
last_stmt_m: ast.AST* = body.get(bn3 - 1)
if last_stmt_m is not None and last_stmt_m.kind() == ast.ASTKind.Return: last_is_return = 1
if last_is_return == 0:
if mlirlite.builder_cur_block_is_terminated(func_builder) == 0:
last_is_ctrl: int = 0
if bn3 > 0:
last_stmt3_m: ast.AST* = body.get(bn3 - 1)
if last_stmt3_m is not None:
k3m: int = last_stmt3_m.kind()
if k3m == ast.ASTKind.If or k3m == ast.ASTKind.While or k3m == ast.ASTKind.For or k3m == ast.ASTKind.Try or k3m == ast.ASTKind.With or k3m == ast.ASTKind.Match:
last_is_ctrl = 1
_is_void: int = 0
if ret_ty is not None:
match ret_ty:
case mlirlite.LLVMType.Void():
_is_void = 1
case _:
pass
# Void detection at the annotation level (same as the module-level _is_void double-check):
# t.CVoiX / bare CVoid markers and -> None all count as void; the TypePrint fallback is not reliable.
if _is_void == 0 and fd.returns is not None:
if self.Gen.TypeGenResolve._is_cvoid_marker(fd.returns, imported_modules) != 0: _is_void = 1
elif fd.returns.kind() == ast.ASTKind.Constant:
ret_none_cn_m: ast.Constant* = (ast.Constant*)(fd.returns)
if ret_none_cn_m.const_kind == ast.CONST_NONE: _is_void = 1
if _is_void != 0:
# M3:隐式收尾也是函数出口,先释放本方法登记的 slife
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
mlirlite.build_ret_void(func_builder)
elif last_is_ctrl != 0 and trans._ctrl_merge_reachable == 0:
# Non-void with a control-flow last statement and a structurally unreachable merge ->
# unreachable; if the merge is reachable the normal epilogue must run (unreachable on a reachable block is UB).
mlirlite.build_unreachable(func_builder)
elif fd.returns is None:
# No return annotation -> treat as a void procedure, return a zero value.
if ret_ty is not None:
_m_zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty)
if _m_zero_val is not None:
# M3:隐式收尾出口,先释放 slife 再返回
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
mlirlite.build_ret(func_builder, _m_zero_val)
else: self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
else: self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
else:
# fail-fast: has a -> annotation but control truly falls off the end (traceback-style).
if ret_ty is not None:
rt_name_m: str = pool.alloc_tagged(4096, memhub.DETSITE_CLASSGENVTABLE_819)
if rt_name_m is not None:
mlirlite.TypePrint(rt_name_m, 4096, ret_ty, pool)
if rt_name_m[0] == '\0': rt_name_m = "?"
err_fn_m: str = pool.alloc(256)
if err_fn_m is not None:
viperlib.snprintf(err_fn_m, 256, "method '%s' flow falls off the end without an explicit return (return type %s), please add an explicit return (e.g. return 0 / return null)", method_name, rt_name_m)
self.Gen.TypeGenErr.fatal_error(fd, err_fn_m, "TypeError")
self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
else: self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
# Restore the module-level scope.
self.Gen.VarGen.exit_scope(trans.SymTab)
# M3:弹回外层登记区间(本方法的释放已在各出口完成)
self.Gen.VarGen._SlifeRestore(old_slife_base)
trans._cur_func = old_func
trans._cur_builder = old_builder
trans._global_name_count = old_global_count
trans._nonlocal_name_count = old_nonlocal_count
trans._closure_env_count = old_env_count
trans._stop_iter_flag_ptr = old_stop_iter_flag
if dbg_pushed != 0: DebugInfoGen.PopFunction(func_builder)
return 0
# _generate_before_init - emit define void @SHA1.ClassName.__before_init__(Ptr(StructTy) %self):
# zero-fill %self, store per-field defaults, then ret void.
def _generate_before_init(self, trans: HT.Translator*, struct_ty: mlirlite.LLVMType*, class_name: str) -> int:
"""Emit the __before_init__ function."""
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.__before_init__.
full_name_buf: str = pool.alloc(128)
if full_name_buf is None: return 0
viperlib.snprintf(full_name_buf, 128, "%s.__before_init__", class_name)
full_name: str = full_name_buf
mangled_name: str = self.Gen.FuncGenBase._mangle_name(trans, full_name)
# Create/reuse the LLVM function: constructing self inside a method (e.g. Vec2.__add__) already
# forward-declared __before_init__, so reuse it (clear IsDeclared) to avoid a declare+define clash.
bi_func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
bi_decl_reuse: int = 0
if bi_func is not None:
if bi_func.IsDeclared == 0:
# Same-name body already emitted: refuse to emit again (duplicate symbol).
return 0
bi_decl_reuse = 1
else:
bi_func = mlirlite.create_function(pool, mod, mangled_name, void_ty)
if bi_func is None: return 0
func: mlirlite.Function* = bi_func
# Register in the function table.
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 bi_decl_reuse != 0:
# Reusing the forward declaration: self already exists (unnamed); name it %self and fix its
# type, otherwise the body's %self is undefined (the printer only prints named params).
bi_first_param: mlirlite.Param* = mlirlite.function_get_param_head(func)
if bi_first_param is not None:
bi_first_param.Ty = self_ptr_ty
bi_first_param.Name = "%self"
func.IsDeclared = 0
else:
# Add the self parameter.
mlirlite.add_param(pool, func, self_ptr_ty, "%self")
# Params done: finalize the global function-table Spec.
rc_ut: int = self.Gen.CallGenLeafKit.update_global_func_spec(full_name, func)
# Create the entry block + builder.
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)
# Register self as an SSA value.
self_val: mlirlite.Value* = mlirlite.SSAValue(pool, self_ptr_ty, "%self")
# Zero-fill: store zeroinitializer into %self. Empty structs ({}) cannot use zeroinitializer
# (invalid type for null constant), so field-less classes skip it; @t.CVTable classes always have a slot.
struct_fcount_zi: int = mlirlite.get_struct_fcount(struct_ty)
if struct_fcount_zi > 0:
zero: mlirlite.Value* = mlirlite.ConstZero(pool, struct_ty)
if zero is not None: mlirlite.build_store(func_builder, zero, self_val)
# If there is a vtable, store the vtable global address into field 0 (__vtable__) via find_struct_by_type.
bi_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(struct_ty)
bi_has_vt: int = 0
if bi_entry is not None: bi_has_vt = bi_entry.HasVTable
if bi_has_vt == 1:
vt_method_count: int = 0
if bi_entry is not None: vt_method_count = bi_entry.VTableMethodCount
if vt_method_count > 0:
# Build the vtable type { i8*, i8*, ..., i8* }.
vt_i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
vt_i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, vt_i8_ty)
vt_first_node: mlirlite.ParamNode* = None
vt_prev_node: mlirlite.ParamNode* = None
for vti in range(int(vt_method_count)):
vtnode: mlirlite.ParamNode* = mlirlite.new_param_node(pool, vt_i8_ptr_ty)
if vtnode is None: continue
if vt_first_node is None: vt_first_node = vtnode
if vt_prev_node is not None: mlirlite.paramnode_set_next(vt_prev_node, vtnode)
vt_prev_node = vtnode
vt_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, vt_first_node, vt_method_count, None)
vt_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, vt_ty)
# Build the vtable global variable name.
vt_name_buf: str = pool.alloc(128)
if vt_name_buf is not None:
viperlib.snprintf(vt_name_buf, 128, "%s.vtable", class_name)
vt_full_name: str = vt_name_buf
vt_mangled: str = self.Gen.FuncGenBase._mangle_name(trans, vt_full_name)
# GEP to the __vtable__ field (index 0).
vt_slot: mlirlite.Value* = mlirlite.build_gep_struct(func_builder, struct_ty, vt_i8_ptr_ty, self_val, 0)
if vt_slot is not None:
# Create a Value referencing the global.
vt_ref: mlirlite.Value* = mlirlite.new_value(pool)
if vt_ref is not None:
mlirlite.value_set_ty(vt_ref, vt_ptr_ty)
mlirlite.value_set_isconst(vt_ref, 1)
# Name format: @"mangled" or @mangled.
vt_ref_name: str = pool.alloc(256)
if vt_ref_name is not None:
vt_ref_name[0] = '\0'
if self._vtable_name_needs_quote(vt_mangled) != 0: viperlib.snprintf(vt_ref_name, 256, "@\"%s\"", vt_mangled)
else: viperlib.snprintf(vt_ref_name, 256, "@%s", vt_mangled)
mlirlite.value_set_name(vt_ref, vt_ref_name)
# Bitcast to i8*.
vt_as_i8: mlirlite.Value* = mlirlite.build_bitcast(func_builder, vt_ref, vt_i8_ptr_ty)
if vt_as_i8 is not None: mlirlite.build_store(func_builder, vt_as_i8, vt_slot)
# 2. Store per-field defaults (via bi_entry, avoiding cross-module same-name lookups).
if bi_entry is not None:
for fi in range(int(bi_entry.FieldCount)):
fe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(bi_entry, fi)
if fe is None or fe.DefaultVal is None: continue
# Static-array field with a list-literal default: zero it then GEP+store each element, matching
# local AnnAssign array init (translate_value would mistranslate the list into a dynamic list pointer).
if fe.Ty is not None and self.Gen.ExprGenBase.is_array_type(fe.Ty) != 0 and fe.DefaultVal.kind() == ast.ASTKind.List:
field_ptr_arr: mlirlite.Value* = mlirlite.build_gep_struct(func_builder, struct_ty, fe.Ty, self_val, fe.Index)
if field_ptr_arr is not None:
zero_arr_f: mlirlite.Value* = mlirlite.ConstZero(pool, fe.Ty)
if zero_arr_f is not None: mlirlite.build_store(func_builder, zero_arr_f, field_ptr_arr)
# Mixin static binding: self._xxx only searches this class, so cross-mixin array init must
# go through the module-level HandlesAnnAssign delegate; owner_ctx is only for the fail-fast message.
arr_ctx: str = pool.alloc(192)
if arr_ctx is not None:
fname_dbg_v: str = fe.Name
if fname_dbg_v is None: fname_dbg_v = "?"
viperlib.snprintf(arr_ctx, 192, "class %s field %s: ", class_name, fname_dbg_v)
self.Gen.AssignGen._init_global_array_from_list(func_builder, pool, mod, field_ptr_arr, fe.DefaultVal, trans, arr_ctx)
continue
# Translate the default-value expression.
default_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(func_builder, pool, mod, fe.DefaultVal, None, 0, trans)
if default_val is None: continue
# GEP to the field.
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)
return 0