Files
ViperC/App/lib/core/Gen/Class/ClassGenVTable.vp
T
TermiNexus eda8e4465f refactor(codegen): one body pipeline for module functions and class methods
Module-level functions and class methods each carried their own copy of "resolve the
return type", "create the parameter allocas" and "emit the implicit epilogue". The copies
drifted: an unannotated method was still typed i32 by the class-method body path while its
declaration said void, so the epilogue emitted `llvm.return 0 : i32` inside a void function
and the MLIR verifier rejected it ("'llvm.return' op expected no operands", Scheduler._yield).

- FuncGenBase.resolve_func_ret_ty: the single return-type policy (annotation -> storage
  marker -> __init__/__new__ hooks -> unannotated = procedure void -> fail-fast). Used by
  the forward-declare pass, the body pass, class methods and generic specializations
- FuncGenBase.param_llvm_ty: the single parameter mapping (CVoid skipped, bare class ->
  Ptr(struct), ArrayT -> element pointer), used by every signature and alloca site
- FuncGenBase.emit_func_epilogue: the single implicit-return implementation (procedure ->
  ret void, unreachable merge, fail-fast for an annotated fall-through), shared by module
  functions, methods and nested functions
- FuncGenBase.translate_func_body: the shared body pipeline (scope entry, self/parameter
  storage, *args va_list, try/except out-params, pre-scan, capture preparation, slife,
  statements, epilogue, state restore). ClassGenVTable._translate_method now only handles
  method naming, vtable/overload registration and add_param
- the same policy reaches cross-module and generic declares: unannotated vpi functions and
  methods declare void, generic specialization declares use the shared resolver, and the
  list.append call sites emit a void call
- method parameters now carry the const / volatile / pointee-volatile / pure markers that
  the module-level path always had
- FuncGenMixin: the two dry-run helpers that return ints are annotated, as the language
  now requires (an unannotated def is a procedure)

Verified: 41 Test projects pass (MethodProcTest, CrossModuleSigTest, RawPtrParamTest,
AstTest, ListTest, OopInheritTest, ...), ViperC self-hosts with the new compiler, and the
6 tests that still fail reproduce identically with the previous compiler.
2026-10-29 19:37:55 +08:00

1075 lines
65 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 stdlib
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 DefineT 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
# _method_slot_name - 方法在函数表 / vtable 里的短名。
# 类内同名方法 >=2 构成重载组时追加 "_v{参数签名hash}"(跳过 self);三者必须一致:
# 函数表键 "Class.m_v{hash}"、LLVM 符号 "{sha1}.Class.m_v{hash}"、vtable 槽名 "m_v{hash}"。
def _method_slot_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, cd: ast.ClassDef*, fd: ast.FunctionDef*) -> str:
"""方法短名(重载组带签名后缀);失败返回 None(已报错)"""
if fd is None or fd.name is None: return None
if cd is None or cd.children is None: return fd.name
if self.Gen.FuncGenDef._overload_name_count(cd.children, fd.name) < 2: return fd.name
if fd.type_params is not None:
self.Gen.TypeGenErr.fatal_error(fd, f"generic method '{fd.name}' cannot be overloaded (generic overloads are not supported)", "TypeError")
return None
# @staticmethod 没有 self;实例方法 / 类方法的第 0 个形参是 self,不参与签名
skip_self: int = 1
fm_s: int = self.Gen.ClassGenBase._extract_func_meta(fd.decorator_list)
if fm_s & GenTypes.META_STATIC != 0: skip_self = 0
sig: str = self.Gen.FuncGenDef._overload_sig_text(pool, fd, trans, skip_self)
if sig is None: return None
h: str = TSpec.SpecSigHash16(sig)
stdlib.free(sig)
if h is None: return None
base_len: sizet = string.strlen(fd.name)
out: str = pool.alloc(base_len + 24)
if out is None: return None
viperlib.snprintf(out, base_len + 24, "%s_v%s", fd.name, h)
return out
# _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
# 重载:先算出每个 def 的槽名(类内同名 >=2 时带 "_v{签名hash}")。
# "继承槽覆盖判定"与"本类槽加入"共用这张表,两处名字必须完全一致,
# 否则重载方法要么被误判成覆盖、要么占两个槽。
slot_names: sizet* = pool.alloc(8 * cn)
if slot_names is None: return 0
string.memset(slot_names, 0, sizet(8 * cn))
for si in range(int(cn)):
stmt_s: ast.AST* = children.get(si)
if stmt_s is None: continue
if stmt_s.kind() != ast.ASTKind.FunctionDef: continue
fd_s: ast.FunctionDef* = ast.FunctionDef*(stmt_s)
if fd_s is None or fd_s.name is None: continue
slot_nm_s: str = self._method_slot_name(pool, trans, cd, fd_s)
if slot_nm_s is None: return 0
slot_names[si] = sizet(slot_nm_s)
# 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)
# 覆盖判定按"槽名"比较(重载组带签名后缀):只有签名相同的同名方法才算覆盖
fd2_slot: str = None
if slot_names[ci2] != 0: fd2_slot = rawptr(slot_names[ci2])
if fd2 is not None and fd2_slot is not None:
if string.strcmp(fd2_slot, 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 = rawptr(slot_names[ci])
if mname is None: mname = fd.name
# Skip constructors and special methods (they must not enter the vtable).
# 用 fd.name 判断:重载组的槽名带后缀,用槽名会漏判把 __init__ 塞进 vtable。
if string.strcmp(fd.name, "__init__") == 0: continue
if string.strcmp(fd.name, "__before_init__") == 0: continue
if string.strcmp(fd.name, "__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.VTableT(`virtual class` 的语法糖,parser.vp:556-621 合成)、
# 方法级 @t.VTableT(`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. File.__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, cd)
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, 0, cd)
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, cd: ast.ClassDef* = None) -> 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
# 重载:类内同名方法 >=2 时短名追加 "_v{签名hash}",使符号、函数表键与 vtable 槽名一致。
# 属性 setter/deleter 已带 $set/$del 后缀,不参与重载。
if func_meta & GenTypes.META_PROP_SETTER == 0 and func_meta & GenTypes.META_PROP_DELETER == 0:
slot_nm_m: str = self._method_slot_name(pool, trans, cd, fd)
if slot_nm_m is None: return 0
method_name = slot_nm_m
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)
# Return type: one shared policy (annotation -> marker -> unannotated = procedure void).
# The same call types the forward declare, so a declare and its body cannot disagree.
m_ctx: str = f"method '{class_name}.{method_name}'"
m_check: int = 1
if sig_only != 0: m_check = 0
ret_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.resolve_func_ret_ty(trans, fd, m_ctx, 1, method_name, struct_ty, m_check)
if ret_ty is None: return 0
# 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 None or arg.arg is None: continue
# One shared type mapping: bare class -> Ptr(struct), ArrayT -> element pointer.
param_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.param_llvm_ty(trans, arg, m_ctx, 0)
if param_ty is None: continue
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_builtin_marker(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)
# *args variadic: set IsVarArg=1 so the signature carries the ... suffix (the body
# pipeline allocates the va_list buffer, same as a module-level function).
if args_node is not None:
m_ags_v: ast.Arguments* = ast.Arguments*(args_node)
if m_ags_v.vararg is not None: func.IsVarArg = 1
# 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, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
# 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)
# Receiver name for the body: a @classmethod's first parameter may be named `cls`...
self_name_m: str = "self"
if is_static_m == 0 and is_class_m != 0 and args_node is not None:
cags_m: ast.Arguments* = ast.Arguments*(args_node)
if cags_m.args is not None and len(cags_m.args) > 0:
cfirst_m: ast.Arg* = ast.Arg*(cags_m.args.get(0))
if cfirst_m is not None and cfirst_m.arg is not None: self_name_m = cfirst_m.arg
skip_self_m: int = 0
if is_static_m == 0: skip_self_m = 1
# OOP iter protocol: initialize __next__'s stop flag (store 0) and hand the pointer to
# the body pipeline; raise StopIteration writes 1. Not counted in md_param_count.
stop_flag_m: mlirlite.Value* = None
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:
stop_flag_m = mlirlite.SSAValue(pool, i1_ptr_ty_sf, "%__stop_iter_flag__")
if stop_flag_m is not None:
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, stop_flag_m)
# The body itself: one shared pipeline with module-level functions
# (FuncGenBase.translate_func_body) - scope entry, self/parameter storage, pre-scan,
# capture preparation, statements, implicit return, state restore. Methods get no EH
# output params (their call sites do not pass them), so eh_params stays 0.
self.Gen.FuncGenBase.translate_func_body(trans, fd, func, func_builder, ret_ty, m_ctx, skip_self_m, self_ptr_ty, self_name_m, class_name, 0, 0, 1, stop_flag_m)
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.VTableT 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 or string default: zero it then GEP+store
# each element, matching local AnnAssign array init (translate_value would
# mistranslate the literal into a dynamic pointer).
fd_kind_v: int = fe.DefaultVal.kind()
fd_is_str_v: int = 0
if fd_kind_v == ast.ASTKind.Constant:
fd_cn_v: ast.Constant* = ast.Constant*(fe.DefaultVal)
if fd_cn_v.const_kind == ast.CONST_STR: fd_is_str_v = 1
if fe.Ty is not None and self.Gen.ExprGenBase.is_array_type(fe.Ty) != 0 and (fd_kind_v == ast.ASTKind.List or fd_is_str_v != 0):
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)
# arr_ty_opt = fe.Ty: the GEP result may carry no pointee type, and the helper
# used to bail out silently, leaving the field zero-initialized (sha1.state).
arr_init_rc: int = 0
if fd_is_str_v != 0:
arr_init_rc = self.Gen.AssignGen._init_array_from_string(func_builder, pool, mod, field_ptr_arr, fe.DefaultVal, trans, arr_ctx, 0, fe.Ty)
else:
arr_init_rc = self.Gen.AssignGen._init_global_array_from_list(func_builder, pool, mod, field_ptr_arr, fe.DefaultVal, trans, arr_ctx, 0, fe.Ty)
if arr_init_rc != 0:
self.Gen.TypeGenErr.fatal_error(fe.DefaultVal, f"class {class_name} field {fe.Name}: array default initializer could not be emitted (the declared array type or the literal was rejected)", "TypeError")
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, trans)
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