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.
1075 lines
65 KiB
Plaintext
1075 lines
65 KiB
Plaintext
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 |