Files
ViperC/App/lib/core/Gen/Class/ClassGenVTable.vp
T
TermiNexus 848dc0ab38 fix(compiler): 跨模块调用补全 int→ptr 转换与别名默认参数;类型文本缓冲 512→4096
1) CallGenMixin.vp(跨模块 extern 实参提升):指针目标形参改为无条件走 coerce_to_type——原守卫按整数位宽比较,而指针类型的 bits 恒为 0,整段提升被跳过,致 VirtualAlloc(0,...) 以 i32 传入指针形参、mlir-opt 报 operand type mismatch(ViperAIChat 失败)。

2) CallGenMixin.vp(默认参数限定键):改用 StructGen._resolve_module_sha1 解析模块 SHA——原只精确匹配模块名,import x as y 这类别名取不到真实 SHA,默认值表落空、跨模块调用实参数目不符(ViperRequestsTest 失败)。

3) 11 个文件的 TypePrint 类型文本缓冲 512→4096(分配与传入 size 成对修改):48 字段类(如 MNIST 的 Net)的 MLIR 结构体类型文本超过 512 被截断并 fail-fast。
2026-10-07 08:48:09 +08:00

1001 lines
60 KiB
Plaintext

import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Type.TypeSpec as TSpec
import t, c
from stdint import *
import ast
import mlirlite
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
# Max number of vtable methods.
define VTBL_METHOD_MAX = 32
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.
def _vtable_strcat(self, dst: str, dst_size: sizet, src: str):
"""Append src to the end of dst."""
if dst is None or src is None: return
dlen: sizet = string.strlen(dst)
slen: sizet = string.strlen(src)
remain: sizet = dst_size - dlen
if remain <= 0: return
i: sizet = 0
while i < slen and i + 1 < remain:
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)
# Allocate with PermPool: VTableMethods must survive per-file pool resets.
full_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX)
method_names_buf: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX)
# Record each method's parent SHA1 (non-zero = inherited, mangle with parent module SHA1).
method_parent_sha1s: sizet* = CompilerFactory.factory.PermPool.alloc(8 * VTBL_METHOD_MAX)
if full_names_buf is None or method_names_buf is None or method_parent_sha1s is None: return 0
string.memset(full_names_buf, 0, 8 * VTBL_METHOD_MAX)
string.memset(method_names_buf, 0, 8 * VTBL_METHOD_MAX)
string.memset(method_parent_sha1s, 0, 8 * VTBL_METHOD_MAX)
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).
parent_count: int = self.Gen.StructGen.get_parent_count(class_name)
if parent_count > 0:
for pi in range(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(parent_vt_count):
if method_count >= VTBL_METHOD_MAX: break
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(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(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 >= VTBL_METHOD_MAX: break
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(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.
if self._collect_vtable_methods(trans, cd, class_name) == 0: return 0
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(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
# Build the initializer string; 16384 bytes so large vtables (MemBuddy 28+) are not truncated.
init_buf: str = pool.alloc(16384)
if init_buf is None: return 0
init_buf[0] = '\0'
self._vtable_strcat(init_buf, 16384, "{ ")
for i in range(method_count):
if i > 0: self._vtable_strcat(init_buf, 16384, ", ")
full_name_addr: sizet = full_names_buf[i]
if full_name_addr == 0:
self._vtable_strcat(init_buf, 16384, "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, 16384, "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, 16384, "i8* bitcast (")
self._vtable_strcat(init_buf, 16384, func_ty_buf)
self._vtable_strcat(init_buf, 16384, "* @")
if self._vtable_name_needs_quote(mangled_name) != 0:
self._vtable_strcat(init_buf, 16384, "\"")
self._vtable_strcat(init_buf, 16384, mangled_name)
self._vtable_strcat(init_buf, 16384, "\"")
else: self._vtable_strcat(init_buf, 16384, mangled_name)
self._vtable_strcat(init_buf, 16384, " to i8*)")
self._vtable_strcat(init_buf, 16384, " }")
# 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(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:
# Subclass has no own methods but may inherit parent virtual methods; use oop_entry.ParentName directly.
parent_name: str = None
if oop_entry is not None: parent_name = oop_entry.ParentName
if parent_name is not None:
parent_vt_count: int = self.Gen.StructGen.get_vtable_method_count(parent_name)
if parent_vt_count > 0:
has_method = 1 # Mark as having methods (inherited virtual methods).
if has_method == 0: 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(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(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: self._translate_method(trans, fd, struct_ty, class_name)
# 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)
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: 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: t.CPtr = 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 = len(md_alist)
else: md_param_count = 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 = 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(start_idx, 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)
# 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:
tls_m_flag_name: str = pool.alloc(128)
if tls_m_flag_name is not None:
viperlib.snprintf(tls_m_flag_name, 128, "__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: str = pool.alloc(64)
if tls_m_rn is not None:
viperlib.snprintf(tls_m_rn, 64, "@%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(start_idx2, 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)
# 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(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(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(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: 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: 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(4096)
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)
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(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(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