Files

1776 lines
120 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import viperlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.GenApi as GenApi
from . import CallGenBase
# CallGenCall - method-call dispatch split out of CallGenMixin (stage B):
# includes super / virtual dispatch / kwargs name matching.
novirtual class CallGenCall(CallGenBase.CallGenBase):
"""Method-call dispatch: super / virtual calls / kwargs name matching."""
# _do_virtual_call - indirect virtual method call through the vtable
# (GEP vtable ptr -> load method slot -> bitcast -> call).
# ============================================================
def _do_virtual_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, self_ptr: mlirlite.Value*, args_head: mlirlite.Value*, args_count: int, found_func: mlirlite.Function*, ret_ty: mlirlite.LLVMType*, vtable_base: mlirlite.Value* = None) -> mlirlite.Value*:
"""Call a virtual method indirectly through the vtable; returns None for void."""
if pool is None or builder is None or class_name is None or method_name is None: return None
if self_ptr is None or args_head is None: return None
if vtable_base is None: vtable_base = self_ptr
# 1. Get the struct type (find_struct_by_type avoids cross-module same-name
# collisions). Must use vtable_base (object head), not self_ptr.
vc_entry: GenTypes.StructEntry* = None
struct_ty: mlirlite.LLVMType* = None
if vtable_base is not None and vtable_base.Ty is not None:
# vb_ty (LLVMType) and vc_entry (StructEntry) are different structs and
# must use separate variables (strict ptr rules forbid struct<->struct).
vb_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(vtable_base)
if vb_ty is not None:
# vb_ty is the struct type (LLVMType); find its StructEntry.
vc_entry = self.Gen.StructGen.find_struct_by_type(vb_ty)
if vc_entry is not None: struct_ty = vc_entry.Ty
if struct_ty is None and self_ptr is not None and self_ptr.Ty is not None:
# fallback: use self_ptr when vtable_base has no type info.
sp_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(self_ptr)
if sp_ty is not None:
vc_entry = self.Gen.StructGen.find_struct_by_type(sp_ty)
if vc_entry is not None: struct_ty = vc_entry.Ty
if struct_ty is None:
struct_ty = self.Gen.StructGen.get_struct_type(class_name)
if struct_ty is None: return None
# 2. Find the method index in the vtable (direct lookup via entry).
method_index: int = -1
if vc_entry is not None and vc_entry.VTableMethods is not None:
methods_arr: sizet* = (sizet*)(vc_entry.VTableMethods)
for mi in range(int(vc_entry.VTableMethodCount)):
mname_addr: sizet = methods_arr[mi]
if mname_addr == 0: continue
mname: str = rawptr(mname_addr)
if mname is not None and string.strcmp(mname, method_name) == 0:
method_index = mi
break
else: method_index = self.Gen.StructGen.get_vtable_method_index(class_name, method_name)
if method_index < 0: return None
# 3. Basic types.
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
i8_ptr_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty)
# 4. bitcast to i8** to access the __vtable__ field (index 0). The vtable
# pointer is always first, so a direct bitcast works even for opaque types.
vtable_slot: mlirlite.Value* = mlirlite.build_bitcast(builder, vtable_base, i8_ptr_ptr_ty)
if vtable_slot is None: return None
# 5. Load the vtable pointer (i8*).
vtable_ptr: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, vtable_slot)
if vtable_ptr is None: return None
# 6. Build the vtable type {i8*, i8*, ...}.
method_count: int = 0
if vc_entry is not None: method_count = vc_entry.VTableMethodCount
else: method_count = self.Gen.StructGen.get_vtable_method_count(class_name)
if method_count <= 0: return None
# Field/list nodes are mlirlite.ParamNode (new_param_node returns
# rawptr[ParamNode]); an earlier Function* annotation violated ptr rules.
vt_first: mlirlite.ParamNode* = None
vt_prev: mlirlite.ParamNode* = None
for vi in range(int(method_count)):
pn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i8_ptr_ty)
if pn is None: continue
if vt_first is None: vt_first = pn
if vt_prev is not None: mlirlite.paramnode_set_next(vt_prev, pn)
vt_prev = pn
vtable_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, vt_first, method_count, None)
if vtable_ty is None: return None
vtable_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, vtable_ty)
# 7. Bitcast i8* to the vtable type pointer.
vtable_typed: mlirlite.Value* = mlirlite.build_bitcast(builder, vtable_ptr, vtable_ptr_ty)
if vtable_typed is None: return None
# 8. GEP to the method slot.
func_slot: mlirlite.Value* = mlirlite.build_gep_struct(builder, vtable_ty, i8_ptr_ty, vtable_typed, method_index)
if func_slot is None: return None
# 9. Load the function pointer (i8*).
func_i8: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, func_slot)
if func_i8 is None: return None
# 10. Build the function type RetTy(params) from args_head by walking the
# Value list (avoids cross-module found_func field-offset issues).
fp_first: mlirlite.ParamNode* = None
fp_prev: mlirlite.ParamNode* = None
fp_count: int = 0
cur_val: mlirlite.Value* = args_head
for vi in range(int(args_count)):
if cur_val is None: break
cur_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(cur_val)
fpn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, cur_ty)
if fpn is not None:
if fp_first is None: fp_first = fpn
if fp_prev is not None: mlirlite.paramnode_set_next(fp_prev, fpn)
fp_prev = fpn
cur_val = mlirlite.value_get_next(cur_val)
fp_count += 1
func_ty: mlirlite.LLVMType* = mlirlite.Func(pool, ret_ty, fp_first, fp_count)
if func_ty is None: return None
func_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, func_ty)
# 11. Bitcast i8* to the function pointer type.
func_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, func_i8, func_ptr_ty)
if func_ptr is None: return None
# 12. Indirect call.
return mlirlite.build_call_indirect(builder, func_ptr, args_head, args_count, ret_ty)
# _call_method_on_ptr - call a method on a struct pointer, with self as the
# first argument (goes through virtual dispatch if the method is in the vtable).
# ============================================================
def _call_method_on_ptr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, self_ptr: mlirlite.Value*, extra_args: sizet*, extra_count: int, trans: HT.Translator*, node: ast.AST* = None, is_volatile: int = 0, arg_nodes: list[ast.AST*]* = None, recv_life: int = -1) -> mlirlite.Value*:
"""Call a method on a struct pointer; adds a volatile barrier for constructors."""
if pool is None or builder is None or class_name is None or method_name is None: return None
if self_ptr is None: return None
# 调用点生命期检查(M2.3 覆盖面扩展,构造器):`__init__` 里最常见的逃逸就是
# `self.f = p`(_MarkEscapingStore 已把该形参置位)。实参链 = (self, 用户实参…),
# 故第 ai 个用户实参对应 bit (ai+1);默认值是**右对齐追加**在末尾,不影响前缀对应。
# `__new__` 不查:其形参与 __init__ 共享且可能被裁剪,位置对应不可靠。
if arg_nodes is not None and string.strcmp(method_name, "__init__") == 0:
esc_mask_ci: int = GenApi.get_param_escapes_method(pool, class_name, method_name)
sha_ci: str = None
if esc_mask_ci == 0:
# 跨模块回退(v3):构造器常定义在别的模块(self.f = p 写在那边)
sha_ci = self.Gen.StructGen.get_struct_sha1(class_name)
pk_ci: int = self.Gen.CallGenBase.lookup_escape_via_vpi(pool, sha_ci, class_name, method_name)
if pk_ci != 0: esc_mask_ci = int(pk_ci & 4294967295)
# 声明式指令最终裁决(@life(escape/noescape):unsafe 但强制),在推断/回退之后最后套用
f_ci: int = GenApi.get_param_escape_force_method(pool, class_name, method_name)
n_ci: int = GenApi.get_param_noescape_force_method(pool, class_name, method_name)
if f_ci == 0 and n_ci == 0 and sha_ci is not None:
# 跨模块:本文件指令表为空,改查对方模块 .vpi 的 esce=/escn=
f_ci = self.Gen.CallGenBase.lookup_escape_dir_via_vpi(sha_ci, class_name, method_name, 0)
n_ci = self.Gen.CallGenBase.lookup_escape_dir_via_vpi(sha_ci, class_name, method_name, 1)
if f_ci != 0 or n_ci != 0: esc_mask_ci = (esc_mask_ci | f_ci) & (~n_ci)
# 同命裁剪(构造器路径):被构造对象的生命期由**调用方**(CallGenCtor)算出并经
# recv_life 传入 —— 这里只有 self_ptr(LLVM 值),拿不到 AST/生命期。
# recv_life == -1 = 未知 → **不做裁剪**(与接这条之前的行为完全一致)。
# 对象在本帧(裸 `T()` / `new stack` / `slife`)时,其余实参也都在本帧即可清位。
if recv_life >= 0 and esc_mask_ci != 0:
if recv_life == GenTypes.LIFE_FRAME or recv_life == GenTypes.LIFE_VALUE:
hard_ci: int = GenApi.get_param_escapes_hard_method(pool, class_name, method_name)
fld_ci: int = esc_mask_ci & (~hard_ci)
if fld_ci != 0:
can_ci2: sizet = len(arg_nodes)
cl_ci: int = self.Gen.CallGen._FrameArgsEscapeClearsMethod(trans, None, arg_nodes, can_ci2, fld_ci)
if cl_ci != 0: esc_mask_ci = esc_mask_ci & (~cl_ci)
if esc_mask_ci != 0:
dummy_ptr_ci: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
can_ci: sizet = len(arg_nodes)
for ai_ci in range(int(can_ci)):
if ((esc_mask_ci >> int(ai_ci + 1)) & 1) != 0:
GenApi.fail_if_stack_addr_escape(arg_nodes.get(ai_ci), "a constructor parameter stored into a field", trans, dummy_ptr_ci)
# Fetch the struct entry early (find_struct_by_type avoids cross-module
# same-name collisions) for SHA1 lookup and vtable dispatch.
cmop_entry: GenTypes.StructEntry* = None
cmop_struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(self_ptr)
if cmop_struct_ty is not None: cmop_entry = self.Gen.StructGen.find_struct_by_type(cmop_struct_ty)
if cmop_entry is None: cmop_entry = self.Gen.StructGen.find_struct(class_name)
# fallback: derive the type from entry when cmop_struct_ty is None
# (self_ptr type info is lost on cross-module calls).
if cmop_struct_ty is None and cmop_entry is not None: cmop_struct_ty = cmop_entry.Ty
# Build the lookup name: ClassName.method_name.
lookup_name: str = pool.alloc(128)
if lookup_name is None: return None
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, method_name)
# Find the function (supports SHA1 suffix matching).
found_func: mlirlite.Function* = self.find_func_in_module(mod, lookup_name)
# Determine the call name.
call_name: str = lookup_name
if found_func is not None:
found_name: str = mlirlite.function_get_name(found_func)
if found_name is not None: call_name = found_name
else:
# found_func is None (stub not injected): build the call name from the
# class module SHA1, preferring the entry's SHA1.
cls_sha1: str = None
if cmop_entry is not None: cls_sha1 = cmop_entry.ModuleSha1
else: cls_sha1 = self.Gen.StructGen.get_struct_sha1(class_name)
if cls_sha1 is None and trans is not None:
# fallback: look up the class module SHA1 from from_imports.
cls_sha1 = self._lookup_mod_sha1(pool, trans._from_imports, class_name)
if cls_sha1 is None:
# [D6 truncation] Allocate by exact length instead of a fixed 256
# buffer so long class/method names are not silently truncated.
msg_fmt: str = "Class '%s' method '%s': module SHA1 not found"
msg_size: sizet = string.strlen(msg_fmt) + string.strlen(class_name) + string.strlen(method_name) - 4 + 1
err_msg: str = pool.alloc(msg_size)
if err_msg is not None:
viperlib.snprintf(err_msg, msg_size, msg_fmt, class_name, method_name)
self.Gen.TypeGenErr.fatal_error(node, err_msg)
self.Gen.TypeGenErr.fatal_error(node, "Module SHA1 not found")
# Build the call name: {cls_sha1}.{lookup_name}.
mangled2: str = pool.alloc(string.strlen(cls_sha1) + string.strlen(lookup_name) + 2)
if mangled2 is not None:
viperlib.snprintf(mangled2, string.strlen(cls_sha1) + string.strlen(lookup_name) + 2, "%s.%s", cls_sha1, lookup_name)
call_name = mangled2
# Determine the return type (__before_init__ and __init__ return void).
void_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
call_ret_ty: mlirlite.LLVMType* = void_ty
if found_func is not None:
frt: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(found_func)
if frt is not None: call_ret_ty = frt
elif string.strcmp(method_name, "__getitem__") == 0 or string.strcmp(method_name, "__new__") == 0:
# fail-fast: __getitem__/__new__ must return a pointer; a missing
# return type means a broken stub declare (silent ptr truncation).
msg_fmt1: str = "Cannot determine return type of method '%s' (stub declare return type missing)"
msg_size1: sizet = string.strlen(msg_fmt1) + string.strlen(method_name) - 2 + 1
err_cm1: str = pool.alloc(msg_size1)
if err_cm1 is not None:
viperlib.snprintf(err_cm1, msg_size1, msg_fmt1, method_name)
self.Gen.TypeGenErr.fatal_error(node, err_cm1)
self.Gen.TypeGenErr.fatal_error(node, "Cannot determine method return type")
else:
# found_func is None (stub not injected): cannot read the return type
# from the declare. __before_init__/__init__ always return void.
if string.strcmp(method_name, "__before_init__") == 0 or string.strcmp(method_name, "__init__") == 0: call_ret_ty = void_ty
# __new__ returns Ptr(struct_ty) (inferable). __getitem__ cannot be
# inferred from self; fail fast when its stub declare is missing.
elif string.strcmp(method_name, "__new__") == 0:
if cmop_struct_ty is not None: call_ret_ty = mlirlite.Ptr(pool, cmop_struct_ty)
else:
# [D6 truncation] Use exact length instead of a fixed 256 buffer.
msg_fmt2: str = "Unknown self struct type of method '%s' (field type degraded, dependency module not preregistered?)"
msg_size2: sizet = string.strlen(msg_fmt2) + string.strlen(method_name) - 2 + 1
err_cm2: str = pool.alloc(msg_size2)
if err_cm2 is not None:
viperlib.snprintf(err_cm2, msg_size2, msg_fmt2, method_name)
self.Gen.TypeGenErr.fatal_error(node, err_cm2)
self.Gen.TypeGenErr.fatal_error(node, "Unknown self struct type")
elif string.strcmp(method_name, "__getitem__") == 0:
# [D6 truncation] Use exact length instead of a fixed 256 buffer.
msg_fmt3: str = "Method '%s.__getitem__': stub declare not injected (dependency module vpi missing or wrong topological order); cannot determine return type"
msg_size3: sizet = string.strlen(msg_fmt3) + string.strlen(class_name) - 2 + 1
err_cm3: str = pool.alloc(msg_size3)
if err_cm3 is not None:
viperlib.snprintf(err_cm3, msg_size3, msg_fmt3, class_name)
self.Gen.TypeGenErr.fatal_error(node, err_cm3)
self.Gen.TypeGenErr.fatal_error(node, "__getitem__ stub declare not injected")
else:
# Other cross-module methods: resolve the return type precisely via
# text.ll / vpi; fail fast rather than silently keeping void.
ret_cm: mlirlite.LLVMType* = None
# is_local_mod exemption: when the target module is the one being
# translated, take the return type from the current IR, not text.ll.
if cls_sha1 is not None and trans is not None and trans.ModuleSha1 is not None and string.strncmp(cls_sha1, trans.ModuleSha1, 16) == 0:
local_lk_cm2: str = pool.alloc(string.strlen(class_name) + string.strlen(method_name) + 2)
if local_lk_cm2 is not None:
viperlib.snprintf(local_lk_cm2, string.strlen(class_name) + string.strlen(method_name) + 2, "%s.%s", class_name, method_name)
local_fn_cm2: mlirlite.Function* = self.find_func_in_module(mod, local_lk_cm2)
if local_fn_cm2 is not None:
lrt_cm2: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(local_fn_cm2)
if lrt_cm2 is not None: ret_cm = lrt_cm2
if cls_sha1 is not None: ret_cm = self._lookup_method_ret_ty_via_vpi(pool, cls_sha1, class_name, method_name)
if ret_cm is None and cls_sha1 is not None: ret_cm = self._LookupMethodRetTyFromTextLL(pool, cls_sha1, class_name, method_name)
if ret_cm is not None: call_ret_ty = ret_cm
else:
# Phase A: the dependency's text.ll is not generated yet, so keep
# the default type and continue (dependency not ready).
if cls_sha1 is None or (cls_sha1 is not None and self._dep_text_ll_exists(pool, cls_sha1) == 0):
# Dependency not ready: keep default i32 (build_call(Void)
# returns None and breaks expression translation).
call_ret_ty = mlirlite.Int32(pool)
else:
err_cm4: str = pool.alloc(256)
if err_cm4 is not None:
viperlib.snprintf(err_cm4, 256, "Cannot determine return type of method '%s.%s' (neither text.ll nor vpi hit, cross-module stub not injected)", class_name, method_name)
self.Gen.TypeGenErr.fatal_error(node, err_cm4)
self.Gen.TypeGenErr.fatal_error(node, "Cannot determine method return type")
# __new__'s signature may be shorter than __init__'s, so trim trailing
# args to the callee's non-self param count (__init__ consumes them).
if string.strcmp(method_name, "__new__") == 0 and found_func is not None:
nn_pc: int = 0
nn_pn: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
while nn_pn is not None:
nn_pc += 1
nn_pn = mlirlite.param_get_next(nn_pn)
# nn_pc == 1 (only injected self) still needs trimming: extra_count -> 0.
if nn_pc >= 1 and extra_count > nn_pc - 1: extra_count = nn_pc - 1
# Build the argument list: self_ptr -> extra_args...
mlirlite.value_set_next(self_ptr, None)
head: mlirlite.Value* = self_ptr
tail: mlirlite.Value* = self_ptr
if extra_args is not None and extra_count > 0:
for ei in range(int(extra_count)):
arg_addr: sizet = extra_args[ei]
if arg_addr == 0: continue
av: mlirlite.Value* = mlirlite.Value*(arg_addr)
if av is not None:
mlirlite.value_set_next(av, None)
mlirlite.value_set_next(tail, av)
tail = av
total_count: int = 1 + extra_count
# Value-reference (&T / &temp T) params: auto-address the user args before default-filling and
# the param-type coercion below (a temp copy must be taken from the source-typed value). call_name
# is the resolved call symbol ({sha}.Class.method / Class.method), matching the registration key.
head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, arg_nodes, head, total_count, 1)
# Recover the tail after the (possibly rebuilt) prefix so default args append to the end.
tail = head
while tail is not None and mlirlite.value_get_next(tail) is not None: tail = mlirlite.value_get_next(tail)
# Default-argument filling: if the call site supplies fewer args than the
# definition, fill the missing ones (defined default, else zero).
mdf_defaults: rawptr = None
mdf_default_count: int = 0
mdf_param_count: int = 0
if trans is not None and trans._funcs is not None:
mdf_entry: GenTypes.FuncEntry* = self.Gen.CallGenLeafKit.find_func_entry_in_table(trans._funcs, trans._func_count, lookup_name)
if mdf_entry is not None:
mdf_defaults = mdf_entry.Defaults
mdf_default_count = mdf_entry.DefaultCount
mdf_param_count = mdf_entry.ParamCount
# Always query the global default table: the snapshot-rebuild branch below
# depends on mdf_gd (the only holder of stdlib constant snapshots).
mdf_gd: GenTypes.FuncDefaultsEntry* = self.Gen.CallGenLeafKit.find_func_defaults(lookup_name)
if mdf_defaults is None and mdf_gd is not None:
mdf_defaults = mdf_gd.Defaults
mdf_default_count = mdf_gd.DefaultCount
mdf_param_count = mdf_gd.ParamCount
# Cross-module stub fallback: when found_func is None and the table has no
# ParamCount, fill it from the vpi cache to align declare/call with define.
vpi_pa: str = None
if mdf_param_count == 0 and found_func is None:
vpi_sha1_p: str = None
if cmop_entry is not None and cmop_entry.ModuleSha1 is not None: vpi_sha1_p = cmop_entry.ModuleSha1
else: vpi_sha1_p = self.Gen.StructGen.get_struct_sha1(class_name)
if vpi_sha1_p is not None:
vpi_pa = self.Gen.CallGenBase._vpi_method_param_annot(pool, vpi_sha1_p, class_name, method_name)
if vpi_pa is not None and vpi_pa[0] != '\0':
vpi_pc2: int = 1
vi2: sizet = 0
while vpi_pa[vi2] != '\0':
if vpi_pa[vi2] == '@': vpi_pc2 += 1
vi2 += 1
mdf_param_count = vpi_pc2 - 1 # non-self param count
# [FIX 2026-08-25] Fall back to found_func's param count when the tables
# lack ParamCount, excluding the two EH output params injected for raise.
if mdf_param_count == 0 and found_func is not None:
ppcnt_f2: int = 0
ppn_f2: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
while ppn_f2 is not None:
fp_nm_f2: str = mlirlite.param_get_name(ppn_f2)
if fp_nm_f2 is None or (string.strcmp(fp_nm_f2, "%__eh_msg_out__") != 0 and string.strcmp(fp_nm_f2, "%__eh_code_out__") != 0):
ppcnt_f2 += 1
ppn_f2 = mlirlite.param_get_next(ppn_f2)
if ppcnt_f2 > 1:
mdf_param_count = ppcnt_f2 - 1 # non-self, non-EH param count
# Compute the params to fill (excluding self): extra_count provided vs
# mdf_param_count required.
if mdf_param_count > extra_count:
mdf_offset: int = mdf_param_count - mdf_default_count
mdf_pi: int = extra_count
while mdf_pi < mdf_param_count:
# Prefer the defined default value.
if mdf_defaults is not None and mdf_default_count > 0 and mdf_pi >= mdf_offset:
mdf_di: int = mdf_pi - mdf_offset
if mdf_di < mdf_default_count:
# Rebuild from the stdlib constant snapshot (the pool AST may be
# freed); fail fast rather than read a dangling AST.
mdf_val: mlirlite.Value* = None
if mdf_gd is not None: mdf_val = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, mdf_gd, mdf_di, trans, builder)
if mdf_val is None:
# Use node (this function's line-info node), not cl which is
# undefined here and would report "line 0".
self.Gen.TypeGenErr.fatal_error(node, f"Cross-module default argument cannot be rebuilt (snapshot missing, the default value must be a simple constant): {lookup_name}", "TypeError")
continue
mlirlite.value_set_next(mdf_val, None)
mlirlite.value_set_next(tail, mdf_val)
tail = mdf_val
total_count += 1
mdf_pi += 1
continue
# Without a default, fall back to the zero value (needs found_func for
# the param type); a missing entry does not mean no default.
if found_func is not None:
param_node_z: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
skip_z: int = 0
while skip_z <= mdf_pi and param_node_z is not None:
param_node_z = mlirlite.param_get_next(param_node_z)
skip_z += 1
if param_node_z is not None:
zero_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(param_node_z)
zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, zero_ty)
if zero_val is not None:
mlirlite.value_set_next(zero_val, None)
mlirlite.value_set_next(tail, zero_val)
tail = zero_val
total_count += 1
elif vpi_pa is not None:
# found_func is None: resolve the missing param type from the vpi
# annotation (segment mdf_pi+1) and use its zero value.
seg_idx_v: int = mdf_pi + 1
seg_no_v: int = 0
seg_cur_v: sizet = 0
while seg_no_v < seg_idx_v and vpi_pa[seg_cur_v] != '\0':
if vpi_pa[seg_cur_v] == '@': seg_no_v += 1
seg_cur_v += 1
seg_start_v: sizet = seg_cur_v
seg_len_v: sizet = 0
while vpi_pa[seg_start_v + seg_len_v] != '\0' and vpi_pa[seg_start_v + seg_len_v] != '@': seg_len_v += 1
if seg_no_v == seg_idx_v:
pty_v: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
if seg_len_v > 0:
seg_buf_v: bytes = stdlib.malloc(seg_len_v + 1)
if seg_buf_v is not None:
string.strncpy(seg_buf_v, vpi_pa + seg_start_v, seg_len_v)
seg_buf_v[seg_len_v] = '\0'
rty_v: mlirlite.LLVMType* = self.Gen.CallGenBase._vpi_resolve_param_annot_ty(pool, seg_buf_v)
if rty_v is not None: pty_v = rty_v
stdlib.free(seg_buf_v)
zero_val_v: mlirlite.Value* = mlirlite.ConstZero(pool, pty_v)
if zero_val_v is not None:
mlirlite.value_set_next(zero_val_v, None)
mlirlite.value_set_next(tail, zero_val_v)
tail = zero_val_v
total_count += 1
mdf_pi += 1
elif found_func is not None:
# No default info but found_func exists: fill missing params with zeros
# (a missing default table does not mean no default).
param_node: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
skip_idx: int = 0
while skip_idx < total_count and param_node is not None:
param_node = mlirlite.param_get_next(param_node)
skip_idx += 1
while param_node is not None:
missing_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(param_node)
zero_val2: mlirlite.Value* = mlirlite.ConstZero(pool, missing_ty)
if zero_val2 is not None:
mlirlite.value_set_next(zero_val2, None)
mlirlite.value_set_next(tail, zero_val2)
tail = zero_val2
total_count += 1
param_node = mlirlite.param_get_next(param_node)
# Coerce every argument (including self) to found_func's param types; the
# self ptr may need a bitcast to match the declared signature.
if found_func is not None and head is not None:
pc_param: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
pc_node: mlirlite.Value* = head
new_head_pc: mlirlite.Value* = head
prev_pc: mlirlite.Value* = None
pc_idx: int = 0
while pc_node is not None:
pc_next_save: mlirlite.Value* = pc_node.Next
coerced_pc: mlirlite.Value* = pc_node
# 每个实参带上**它自己的 AST 节点**(arg_nodes 与链上 slot 1.. 对应,slot 0 是
# self):转换失败时报错才能定位到这个实参表达式(如 `&_on_submit`),而不是
# 因 node=None 退回"函数定义行"、只剩一帧。
pc_arg_node: ast.AST* = node
if pc_idx >= 1 and arg_nodes is not None and (pc_idx - 1) < len(arg_nodes):
pc_arg_node = arg_nodes.get(pc_idx - 1)
if pc_param is not None:
pty_pc: mlirlite.LLVMType* = mlirlite.param_get_ty(pc_param)
if pty_pc is not None:
c_pc: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, pc_node, pty_pc, pc_arg_node)
if c_pc is not None: coerced_pc = c_pc
pc_param = mlirlite.param_get_next(pc_param)
coerced_pc.Next = None
if prev_pc is None: new_head_pc = coerced_pc
else: prev_pc.Next = coerced_pc
prev_pc = coerced_pc
pc_idx += 1
pc_node = pc_next_save
head = new_head_pc
# Virtual dispatch: if the class has a vtable and the method is in it,
# call indirectly, reusing the earlier cmop_entry.
if found_func is not None:
cmop_has_vt: int = 0
cmop_method_idx: int = -1
if cmop_entry is not None:
cmop_has_vt = cmop_entry.HasVTable
if cmop_has_vt == 1 and cmop_entry.VTableMethods is not None:
cmop_methods_arr: sizet* = (sizet*)(cmop_entry.VTableMethods)
for cmop_mi in range(int(cmop_entry.VTableMethodCount)):
cmop_mname_addr: sizet = cmop_methods_arr[cmop_mi]
if cmop_mname_addr == 0: continue
cmop_mname: str = rawptr(cmop_mname_addr)
if cmop_mname is not None and string.strcmp(cmop_mname, method_name) == 0:
cmop_method_idx = cmop_mi
break
if cmop_has_vt == 1 and cmop_method_idx >= 0:
return self._do_virtual_call(pool, builder, mod, class_name, method_name, self_ptr, head, total_count, found_func, call_ret_ty)
# Defensive: fail fast on an i0 return type; never emit illegal IR.
# Root cause fixed, this guards against a zero-value LLVMType reaching llc.
if call_ret_ty is not None:
cd_i0: int = 0
match call_ret_ty:
case mlirlite.LLVMType.Int(bits):
if bits == 0: cd_i0 = 1
case _:
pass
if cd_i0 != 0: call_ret_ty = self._fail_method_ret_ty(method_name, node, class_name)
# Cross-module call (found_func is None): create a declare to avoid undefined value.
if found_func is None:
# Coerce args to the definition-side params before building the declare;
# otherwise a generic ctor's i32 literal pins sizet(i64) too narrow.
head = self.Gen.CallGenLib._coerce_args_to_def_params(pool, builder, call_name, head, total_count, None, 1, trans)
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, head, total_count)
call_res_cmop: mlirlite.Value* = mlirlite.build_call(builder, call_name, head, total_count, call_ret_ty, 0)
# Value-position volatile construction: the call has no volatile qualifier,
# so add an i8 volatile load barrier on self for the three ctor methods.
if is_volatile != 0 and self_ptr is not None and self_ptr.Ty is not None:
if string.strcmp(method_name, "__new__") == 0 or string.strcmp(method_name, "__before_init__") == 0 or string.strcmp(method_name, "__init__") == 0:
cmop_i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
cmop_i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, cmop_i8_ty)
cmop_bar_ptr: mlirlite.Value* = self_ptr
if uint64(self_ptr.Ty) != uint64(cmop_i8_ptr_ty): cmop_bar_ptr = mlirlite.build_bitcast(builder, self_ptr, cmop_i8_ptr_ty)
if cmop_bar_ptr is not None: mlirlite.build_load(builder, cmop_i8_ty, cmop_bar_ptr, 1)
return call_res_cmop
def _MarkWillFreeArgName(self, trans: HT.Translator*, arg_node: ast.AST*) -> int:
"""P2 willFree:把实参变量槽标为"已失效";只处理简单变量名(1=标记过 / 0=未处理)。
与模块级调用同一口径(VarEntry.Freed):该槽其后被读 / 取地址 / 再传时,由
ExprGenName.translate_name_value 或 ExprGenOps 的 AddrOf 分支报 LifetimeError。"""
if trans is None or arg_node is None: return 0
if arg_node.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(arg_node)
if nm.id is None: return 0
return self.Gen.VarGen._FreedMark(self.Gen.VarGen.lookup_var(trans.SymTab, nm.id))
# _translate_method_call - translate obj.method(args) into a call with the
# object pointer (obj_ptr, an alloca of Ptr(StructTy)) as the self argument.
# ============================================================
def _translate_method_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, obj_ptr: mlirlite.Value*, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*:
"""Translate a method call; returns the call result Value."""
if pool is None or builder is None or class_name is None or method_name is None: return None
if obj_ptr is None or cl is None: return None
# 调用点生命期检查(M2.3 覆盖面扩展):方法摘要键为 "Class.method"(含 MRO 回退)。
# 摘要的形参下标含 self(bit 0),故用户第 ai 个实参对应 bit (ai+1)。语义与模块级
# 函数那条检查一致:被调者"会逃逸或直接返回"的形参位上,实参不得交出本帧栈地址。
cargs_mc: list[ast.AST*]* = cl.args
if cargs_mc is not None:
esc_mask_mc: int = GenApi.get_param_escapes_method(pool, class_name, method_name)
sha_mc: str = None
if esc_mask_mc == 0:
# 跨模块回退(v3):本文件摘要表按文件重置,跨模块只能查 .vpi 落盘的摘要
sha_mc = self.Gen.StructGen.get_struct_sha1(class_name)
pk_mc: int = self.Gen.CallGenBase.lookup_escape_via_vpi(pool, sha_mc, class_name, method_name)
if pk_mc != 0: esc_mask_mc = int(pk_mc & 4294967295)
# 跨模块常量特化:用实参常量套用对方模块 .vpi 落盘的守卫记录(形参位含 self 偏移)
if esc_mask_mc != 0:
cl_mc: int = self.Gen.CallGenBase.lookup_escape_clears_via_vpi(pool, sha_mc, class_name, method_name, cargs_mc, len(cargs_mc), 1)
if cl_mc != 0: esc_mask_mc = esc_mask_mc & (~cl_mc)
# 跨过程常量特化(P3 收尾):同文件能拿到方法体时,用常量实参重算摘要(形参位含 self 偏移)
if esc_mask_mc != 0:
can_rf: sizet = len(cargs_mc)
esc_mask_mc = GenApi.refine_escape_mask_method(pool, class_name, method_name, cargs_mc, can_rf, esc_mask_mc)
# 同命裁剪(**方法路径,此前缺失**):只清"写进形参对象字段"那一类位 —— 若其余实参(含
# 接收者)也都在本帧,则接收者与本帧同命、并不真的逃逸。现场:`h.set_q(l)`,h 是
# `new heap slife S()`(生命期本就是 FRAME)、l 是栈数组 → 两都在本帧 → 不该报。
if esc_mask_mc != 0:
hard_mc: int = GenApi.get_param_escapes_hard_method(pool, class_name, method_name)
fld_mc: int = esc_mask_mc & (~hard_mc)
if fld_mc != 0:
recv_mc: ast.AST* = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
recv_mc = ast.Attribute*(cl.func).value
can_sf: sizet = len(cargs_mc)
cl_sfm: int = self.Gen.CallGen._FrameArgsEscapeClearsMethod(trans, recv_mc, cargs_mc, can_sf, fld_mc)
if cl_sfm != 0: esc_mask_mc = esc_mask_mc & (~cl_sfm)
# 声明式指令最终裁决(@life(escape/noescape):unsafe 但强制),在推断/回退之后最后套用
f_mc: int = GenApi.get_param_escape_force_method(pool, class_name, method_name)
n_mc: int = GenApi.get_param_noescape_force_method(pool, class_name, method_name)
if f_mc == 0 and n_mc == 0 and sha_mc is not None:
# 跨模块:本文件指令表为空,改查对方模块 .vpi 的 esce=/escn=
f_mc = self.Gen.CallGenBase.lookup_escape_dir_via_vpi(sha_mc, class_name, method_name, 0)
n_mc = self.Gen.CallGenBase.lookup_escape_dir_via_vpi(sha_mc, class_name, method_name, 1)
if f_mc != 0 or n_mc != 0: esc_mask_mc = (esc_mask_mc | f_mc) & (~n_mc)
if esc_mask_mc != 0:
dummy_ptr_mc: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
can_mc: sizet = len(cargs_mc)
for ai_mc in range(int(can_mc)):
if ((esc_mask_mc >> int(ai_mc + 1)) & 1) != 0:
GenApi.fail_if_stack_addr_escape(cargs_mc.get(ai_mc), "an escaping method parameter", trans, dummy_ptr_mc)
# P2 willFree:方法级标注查询(本文件表 → 跨模块 .vpi 回退)。
# 实参链含 self(bit 0),故用户第 ai 个实参对应形参位 (ai+1);
# 标记动作在**实参翻译之后**(见下方 kwargs / 位置实参两条循环)。
wf_mask_mc: int = 0
if cargs_mc is not None:
wf_mask_mc = GenApi.get_param_willfree_method(pool, class_name, method_name)
if wf_mask_mc == 0:
sha_wf_mc: str = self.Gen.StructGen.get_struct_sha1(class_name)
wf_mask_mc = self.Gen.CallGenBase.lookup_willfree_via_vpi(pool, sha_wf_mc, class_name, method_name)
# list generic container method: dispatch to _translate_list_builtin_call inline
# when the method is a list builtin; use the receiver's actual struct name.
lb_src: str = class_name
if lb_src is None or lb_src[0] == '\0':
lb_sty_s: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(obj_ptr)
if lb_sty_s is not None:
lb_nm_s: str = mlirlite.get_struct_name(lb_sty_s)
if lb_nm_s is not None: lb_src = lb_nm_s
if method_name is not None and lb_src is not None:
lb_cls: str = lb_src
# Strip the cross-module specialization sha1 prefix "{16hex}." using
# the single source GenApi.sha1_prefix_len.
lb_pfx_d: int = GenApi.sha1_prefix_len(lb_cls)
if string.strncmp(lb_cls, "list", 4) != 0 and lb_pfx_d != 0:
if string.strncmp(lb_cls + lb_pfx_d, "list", 4) == 0: lb_cls = lb_cls + lb_pfx_d
# The trigger also uses the receiver's actual type
# (_is_list_builtin_receiver); fall back to lb_cls if unavailable.
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, obj_ptr, lb_cls, method_name, cl) != 0:
# Double-ptr slot form (rawptr[list[...]] alloca is Ptr(Ptr(Struct)));
# load one level to get the Ptr(Struct) object pointer.
lb_obj: mlirlite.Value* = obj_ptr
if lb_obj is not None and lb_obj.Ty is not None:
match lb_obj.Ty:
case mlirlite.LLVMType.Ptr(lb_inner):
match lb_inner:
case mlirlite.LLVMType.Ptr(lb_inner2):
lb_obj = mlirlite.build_load(builder, lb_inner, lb_obj)
case _:
pass
case _:
pass
if lb_obj is not None:
lb_cargs: list[ast.AST*]* = cl.args
lb_can: sizet = 0
if lb_cargs is not None: lb_can = len(lb_cargs)
return self._translate_list_builtin_call(pool, builder, mod, cl, method_name, lb_obj, lb_cargs, lb_can, trans)
# [D25 fail-fast] A non-builtin method call on a bare generic template base
# (e.g. "list") has an unknown element type; fail fast rather than assume it.
cm_bare: str = None
if lb_src is not None:
cm_pfx: int = GenApi.sha1_prefix_len(lb_src)
cm_bare = lb_src + cm_pfx if cm_pfx != 0 else lb_src
if cm_bare is not None and string.strchr(cm_bare, '[') is None:
cm_tpl: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(cm_bare)
if cm_tpl is not None:
self.Gen.TypeGenErr.fatal_error(cl, f"Generic class '{cm_bare}' method '{method_name}' call is missing type arguments" f" (receiver static type is a bare generic base, element type unknown); " f"silent specialization as the default list[int] is forbidden (treating elements as i32 truncates pointers), " f"annotate the variable type with arguments (e.g. list[ast.AST*])", "TypeError")
# Fetch the struct entry early (find_struct_by_type avoids cross-module
# same-name collisions) for MRO walking, SHA1 lookup and vtable dispatch.
mc_entry: GenTypes.StructEntry* = None
mc_struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(obj_ptr)
if mc_struct_ty is not None: mc_entry = self.Gen.StructGen.find_struct_by_type(mc_struct_ty)
if mc_entry is None: mc_entry = self.Gen.StructGen.find_struct(class_name)
# [D25] Generic container receivers use the actual struct name (the IR-level
# truth); resolve via CallGenProp._resolve_generic_receiver_name.
mc_act_d: str = self.Gen.CallGenProp._resolve_generic_receiver_name(pool, mc_struct_ty, method_name, cl)
if mc_act_d is not None: class_name = mc_act_d
# Build the lookup name; allocate by exact length ([D6]) since the
# specialized class name can be long (e.g. list[rawptr[sha.AST]]).
lk_cap: sizet = string.strlen(class_name) + string.strlen(method_name) + 2
lookup_name: str = pool.alloc(lk_cap)
if lookup_name is None: return None
viperlib.snprintf(lookup_name, lk_cap, "%s.%s", class_name, method_name)
# Find the function (supports SHA1 suffix matching).
found_func: mlirlite.Function* = self.find_func_in_module(mod, lookup_name)
# Generic template methods: the specialized symbol carries type args
# ({sha1}.list[int].get), so match by suffix using mc_entry.Name.
if found_func is None and mc_entry is not None and mc_entry.Name is not None:
if string.strcmp(mc_entry.Name, class_name) != 0:
spec_lookup: str = pool.alloc(160)
if spec_lookup is not None:
viperlib.snprintf(spec_lookup, 160, "%s.%s", mc_entry.Name, method_name)
found_func = self.find_func_in_module(mod, spec_lookup)
# Walk the MRO ancestor chain for the parent method (first parent wins).
mc_anc: str = None # the method's defining class (for multi-inheritance this-adjust)
if found_func is None:
mro_ptr: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
mi: int = 1
while mro_ptr is not None and mro_ptr[mi] != 0:
anc: str = rawptr(mro_ptr[mi])
if anc is not None:
parent_lookup: str = pool.alloc(128)
if parent_lookup is not None:
viperlib.snprintf(parent_lookup, 128, "%s.%s", anc, method_name)
parent_func: mlirlite.Function* = self.find_func_in_module(mod, parent_lookup)
if parent_func is not None:
found_func = parent_func
mc_anc = anc
break
mi += 1
# Sibling Mixin method resolution: the receiver must be a direct parent of a
# registered class AND share a composite class's MRO with the candidate.
if found_func is None:
sib_recv_local: int = 1
if trans is not None and trans.ModuleSha1 is not None:
sib_recv_sha1: str = None
if mc_entry is not None and mc_entry.ModuleSha1 is not None: sib_recv_sha1 = mc_entry.ModuleSha1
else: sib_recv_sha1 = self.Gen.StructGen.get_struct_sha1(class_name)
if sib_recv_sha1 is not None:
if string.strcmp(sib_recv_sha1, trans.ModuleSha1) != 0: sib_recv_local = 0
if sib_recv_local != 0:
if self._recv_is_mixin_base(class_name) != 0:
sc_i: int = 0
sc_n: int = self.Gen.StructGen.get_struct_count()
while sc_i < sc_n and found_func is None:
cand_entry: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(sc_i)
if cand_entry is not None and cand_entry.Name is not None:
cand_name: str = cand_entry.Name
if string.strcmp(cand_name, class_name) != 0:
# Specialized generic class name contains '[': not a Mixin candidate.
if string.strchr(cand_name, '[') is None:
if self._mixin_sibling_same_composite(pool, class_name, cand_name) != 0:
cand_lookup: str = pool.alloc(128)
if cand_lookup is not None:
viperlib.snprintf(cand_lookup, 128, "%s.%s", cand_name, method_name)
cand_func: mlirlite.Function* = self.find_func_in_module(mod, cand_lookup)
if cand_func is not None:
found_func = cand_func
mc_anc = cand_name
sc_i += 1
# Determine the call name (default "ClassName.method"; when not found, build
# it along the MRO using the defining class's SHA1).
call_name: str = lookup_name
if found_func is not None:
found_name: str = mlirlite.function_get_name(found_func)
if found_name is not None: call_name = found_name
# Determine the return type.
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
call_ret_ty: mlirlite.LLVMType* = i32_ty
if found_func is not None:
frt: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(found_func)
if frt is not None: call_ret_ty = frt
else:
# found_func is None (stub not injected): handle compiler builtins first.
sp_ty2: mlirlite.LLVMType* = self._special_method_ret_ty(pool, class_name, method_name)
if sp_ty2 is not None:
call_ret_ty = sp_ty2
# Special methods (e.g. __before_init__) also need the sha1 prefix on
# cross-module calls, else the bare name links to an undefined symbol.
sp_anc_name: str = class_name
sp_anc_sha1: str = None
if mc_entry is not None: sp_anc_sha1 = mc_entry.ModuleSha1
else: sp_anc_sha1 = self.Gen.StructGen.get_struct_sha1(class_name)
if sp_anc_sha1 is None and mc_struct_ty is not None:
match mc_struct_ty:
case mlirlite.LLVMType.Struct(_fs_sp, _fc_sp, type_name_sp):
if type_name_sp is not None and type_name_sp[0] != '\0':
# Only extract sha1 when the type name is "{16hex}.{Class}"
# (via GenApi.sha1_prefix_len), avoiding false positives.
if GenApi.sha1_prefix_len(type_name_sp) != 0:
sb_sp: str = pool.alloc(17)
if sb_sp is not None:
string.strncpy(sb_sp, type_name_sp, 16)
sb_sp[16] = '\0'
sp_anc_sha1 = sb_sp
case _:
pass
if sp_anc_sha1 is not None:
ml_sp: sizet = string.strlen(sp_anc_sha1) + string.strlen(sp_anc_name) + string.strlen(method_name) + 3
mangled_sp: str = pool.alloc(ml_sp)
if mangled_sp is not None:
viperlib.snprintf(mangled_sp, ml_sp, "%s.%s.%s", sp_anc_sha1, sp_anc_name, method_name)
call_name = mangled_sp
else:
# The method's defining class (source of cross-module name / return
# type); defaults to class_name and updates on MRO fallback.
ret_anc_name: str = class_name
ret_anc_sha1: str = None
if mc_entry is not None: ret_anc_sha1 = mc_entry.ModuleSha1
else: ret_anc_sha1 = self.Gen.StructGen.get_struct_sha1(class_name)
if ret_anc_sha1 is None:
# Walk the MRO for the first class with a SHA1.
mro_s0: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
mi_s0: int = 1
while mro_s0 is not None and mro_s0[mi_s0] != 0 and ret_anc_sha1 is None:
anc_s0: str = rawptr(mro_s0[mi_s0])
if anc_s0 is not None: ret_anc_sha1 = self.Gen.StructGen.get_struct_sha1(anc_s0)
mi_s0 += 1
# Generic specialization: the specialized function may not be
# registered yet; trigger it (idempotent), then re-lookup.
gen_done: int = 0
# Hoist and initialize the text.ll result explicitly: an uninitialized
# value would overwrite a correct call_ret_ty with junk.
looked_up_ty: mlirlite.LLVMType* = None
if class_name is not None:
lbkt_g: str = string.strchr(class_name, '[')
if lbkt_g is not None:
rbkt_g: str = string.strrchr(class_name, ']')
if rbkt_g is not None and rbkt_g > lbkt_g:
base_len_g: sizet = sizet(uint64(lbkt_g) - uint64(class_name))
arg_len_g: sizet = sizet(uint64(rbkt_g) - uint64(lbkt_g) - 1)
if base_len_g > 0 and arg_len_g > 0:
base_buf_g: str = pool.alloc(base_len_g + 1)
arg_buf_g: str = pool.alloc(arg_len_g + 1)
if base_buf_g is not None and arg_buf_g is not None:
string.strncpy(base_buf_g, class_name, base_len_g)
base_buf_g[base_len_g] = '\0'
string.strncpy(arg_buf_g, lbkt_g + 1, arg_len_g)
arg_buf_g[arg_len_g] = '\0'
ta_list_g: list[str]* = list[str](pool, 8)
if ta_list_g is not None:
# Comma-separated multiple type args (list[X] / dict[K, V]).
cur_arg_g: str = arg_buf_g
while cur_arg_g is not None:
comma_g: str = string.strchr(cur_arg_g, ',')
if comma_g is None:
ta_list_g.append(cur_arg_g)
break
seg_len_g: sizet = sizet(uint64(comma_g) - uint64(cur_arg_g))
seg_buf_g: str = pool.alloc(seg_len_g + 1)
if seg_buf_g is not None:
string.strncpy(seg_buf_g, cur_arg_g, seg_len_g)
seg_buf_g[seg_len_g] = '\0'
ta_list_g.append(seg_buf_g)
cur_arg_g = comma_g + 1
# base_buf_g may carry a template module SHA1 prefix
# ({tpl}.GSList); strip it before template lookup.
base_clean_g: str = base_buf_g
# [C10] SHA1 prefix detection uses the single source;
# take the cut length from its return value.
base_pfx_g: int = GenApi.sha1_prefix_len(base_buf_g)
if base_pfx_g != 0: base_clean_g = base_buf_g + sizet(base_pfx_g)
spec_name_g: str = self.Gen.ClassGenGeneric._specialize_generic_class(trans, base_clean_g, ta_list_g)
if spec_name_g is not None:
# Re-lookup the specialized function after specialization;
# find_func_in_module matches both bare and tpl-prefixed names.
spec_lk_g: str = pool.alloc(string.strlen(class_name) + string.strlen(method_name) + 2)
if spec_lk_g is not None:
viperlib.snprintf(spec_lk_g, string.strlen(class_name) + string.strlen(method_name) + 2, "%s.%s", class_name, method_name)
spec_found_g: mlirlite.Function* = self.find_func_in_module(mod, spec_lk_g)
if spec_found_g is not None:
frt_g: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(spec_found_g)
if frt_g is not None:
call_ret_ty = frt_g
found_func = spec_found_g
gen_done = 1
if gen_done == 0:
# is_local_mod exemption: when the method is defined in the module
# being translated, take the return type from the current IR.
if trans is not None and ret_anc_sha1 is not None and trans.ModuleSha1 is not None and string.strncmp(ret_anc_sha1, trans.ModuleSha1, 16) == 0:
local_lk_lm: str = pool.alloc(string.strlen(ret_anc_name) + string.strlen(method_name) + 2)
if local_lk_lm is not None:
viperlib.snprintf(local_lk_lm, string.strlen(ret_anc_name) + string.strlen(method_name) + 2, "%s.%s", ret_anc_name, method_name)
local_fn_lm: mlirlite.Function* = self.find_func_in_module(mod, local_lk_lm)
if local_fn_lm is not None:
lrt_lm: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(local_fn_lm)
if lrt_lm is not None:
call_ret_ty = lrt_lm
gen_done = 1
# Try vpi for the real return type (authoritative annotation; no hardcoded whitelist).
vpi_ret_ty: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, ret_anc_sha1, ret_anc_name, method_name)
# Retry along the MRO ancestor chain: the method may be defined in a
# base class in another module; query vpi per ancestor class + sha1.
if vpi_ret_ty is None:
mro_v2: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
mi_v2: int = 1
while mro_v2 is not None and mro_v2[mi_v2] != 0 and vpi_ret_ty is None:
anc_v2: str = rawptr(mro_v2[mi_v2])
if anc_v2 is not None and string.strcmp(anc_v2, class_name) != 0:
anc_sha1_v2: str = self.Gen.StructGen.get_struct_sha1(anc_v2)
vpi_ret_ty = self._lookup_method_ret_ty_via_vpi(pool, anc_sha1_v2, anc_v2, method_name)
if vpi_ret_ty is not None:
ret_anc_name = anc_v2
ret_anc_sha1 = anc_sha1_v2
mi_v2 += 1
if gen_done == 0 and vpi_ret_ty is not None: call_ret_ty = vpi_ret_ty
elif gen_done == 0:
# Fallback: find the real return type from the text.ll define line so
# pointer returns are not truncated by the default i32.
looked_up_ty = self._LookupMethodRetTyFromTextLL(pool, ret_anc_sha1, ret_anc_name, method_name)
# Retry along the MRO, using each ancestor class name + its module sha1.
if looked_up_ty is None:
mro_t2: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
mi_t2: int = 1
while mro_t2 is not None and mro_t2[mi_t2] != 0 and looked_up_ty is None:
anc_t2: str = rawptr(mro_t2[mi_t2])
if anc_t2 is not None and string.strcmp(anc_t2, class_name) != 0:
anc_sha1_t2: str = self.Gen.StructGen.get_struct_sha1(anc_t2)
looked_up_ty = self._LookupMethodRetTyFromTextLL(pool, anc_sha1_t2, anc_t2, method_name)
if looked_up_ty is not None:
ret_anc_name = anc_t2
ret_anc_sha1 = anc_sha1_t2
mi_t2 += 1
if looked_up_ty is not None: call_ret_ty = looked_up_ty
else:
# Phase A: the dependency's text.ll is not generated yet, so keep
# the default type; ret_anc_sha1 None also means not ready.
if ret_anc_sha1 is None or (ret_anc_sha1 is not None and self._dep_text_ll_exists(pool, ret_anc_sha1) == 0):
# Dependency not ready: keep default i32, not Void, since
# build_call(Void) returns None and breaks expression translation.
call_ret_ty = mlirlite.Int32(pool)
else:
# The method may be defined in an MRO ancestor (Mixin) module
# not yet translated; if any ancestor is unready, defer.
mro_p2: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
mi_p2: int = 1
anc_missing_p2: int = 0
while mro_p2 is not None and mro_p2[mi_p2] != 0:
anc_p2: str = rawptr(mro_p2[mi_p2])
if anc_p2 is not None:
anc_sha1_p2: str = self.Gen.StructGen.get_struct_sha1(anc_p2)
if anc_sha1_p2 is None or self._dep_text_ll_exists(pool, anc_sha1_p2) == 0:
anc_missing_p2 = 1
break
mi_p2 += 1
if anc_missing_p2 != 0:
# Dependency not ready: keep default i32 (build_call(Void)
# returns None and breaks expression translation).
call_ret_ty = mlirlite.Int32(pool)
else: call_ret_ty = self._fail_method_ret_ty(method_name, cl, class_name, ret_anc_name, ret_anc_sha1)
# found_func missed but the return type resolved: build the cross-module
# call name from the defining class to avoid undefined references.
if ret_anc_name is not None and ret_anc_sha1 is None:
# When ret_anc_sha1 is missing, extract the sha1 prefix from the
# object's actual LLVM type name ("{sha1}.{ClassName}").
if mc_struct_ty is not None:
match mc_struct_ty:
case mlirlite.LLVMType.Struct(_fields_st, _fcount_st, type_name_st):
if type_name_st is not None and type_name_st[0] != '\0':
# Only extract sha1 when the type name is "{16hex}.{Class}"
# (via the single source GenApi.sha1_prefix_len).
if GenApi.sha1_prefix_len(type_name_st) != 0:
sha1_from_ty: str = pool.alloc(17)
if sha1_from_ty is not None:
string.strncpy(sha1_from_ty, type_name_st, 16)
sha1_from_ty[16] = '\0'
ret_anc_sha1 = sha1_from_ty
case _:
pass
if ret_anc_name is not None and ret_anc_sha1 is not None:
ml_anc: sizet = string.strlen(ret_anc_sha1) + string.strlen(ret_anc_name) + string.strlen(method_name) + 3
mangled_anc: str = pool.alloc(ml_anc)
if mangled_anc is not None:
viperlib.snprintf(mangled_anc, ml_anc, "%s.%s.%s", ret_anc_sha1, ret_anc_name, method_name)
call_name = mangled_anc
# The extracted sha1 is validated only by the "{16hex}." prefix format
# (sha1_prefix_len), not against registered modules.
elif ret_anc_name is not None and ret_anc_sha1 is None:
# fail-fast: the defining class's SHA1 is unresolvable and type-name
# extraction failed; never emit a silent bare name.
self.Gen.TypeGenErr.fatal_error(cl, f"Cannot resolve module SHA1 for class '{ret_anc_name}' of method '{method_name}' (neither text.ll/vpi nor type name hit); cross-module call name construction failed", "TypeError")
# Translate the call arguments.
cargs: list[ast.AST*]* = cl.args
can: sizet = 0
if cargs is not None: can = len(cargs)
kw_calls: list[ast.AST*]* = cl.keywords
kwc: sizet = 0
if kw_calls is not None: kwc = len(kw_calls)
# Multi-inheritance this-adjust (byte-offset): delta = field offset in the
# subclass minus in the parent layout; delta>0 GEPs and bitcasts to the parent.
self_arg: mlirlite.Value* = obj_ptr
if mc_anc is not None and string.strcmp(mc_anc, class_name) != 0:
# Check whether mc_anc is the first parent (prefix-aligned, no adjust);
# non-first parents must adjust self or fail fast, never use obj_ptr as-is.
is_first_parent_th: int = 0
mro_fp_th: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
if mro_fp_th is not None and mro_fp_th[1] != 0:
first_parent_th: str = rawptr(mro_fp_th[1])
if first_parent_th is not None and string.strcmp(first_parent_th, mc_anc) == 0: is_first_parent_th = 1
anc_entry_th: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(mc_anc)
# Parent with no fields (FieldCount==0): the method body reads no layout
# fields, so this needs no byte adjust; pass it through unchanged.
if anc_entry_th is not None and anc_entry_th.FieldCount > 0:
anc_first_name_th: str = None
for afi_th in range(int(anc_entry_th.FieldCount)):
afe_th: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(anc_entry_th, afi_th)
if afe_th is not None:
afn_th: str = self.Gen.StructGen.get_field_name_ptr(afe_th)
if afn_th is not None and string.strcmp(afn_th, "__vtable__") != 0:
anc_first_name_th = afn_th
break
if anc_first_name_th is not None:
sub_entry_th: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(class_name)
if sub_entry_th is not None:
anc_field_th: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(anc_entry_th.Ty, anc_first_name_th)
sub_field_th: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(sub_entry_th.Ty, anc_first_name_th)
if anc_field_th is not None and sub_field_th is not None:
anc_off_th: int = self._field_byte_offset(pool, anc_entry_th, anc_field_th.Index)
sub_off_th: int = self._field_byte_offset(pool, sub_entry_th, sub_field_th.Index)
delta_th: int = sub_off_th - anc_off_th
if delta_th > 0:
i8_ty_th: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_th: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_th)
obj_i8_th: mlirlite.Value* = mlirlite.build_bitcast(builder, obj_ptr, i8_ptr_th)
delta_val_th: mlirlite.Value* = mlirlite.const_int32(pool, delta_th)
adj_th: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty_th, obj_i8_th, delta_val_th)
if adj_th is not None:
anc_ptr_ty_th: mlirlite.LLVMType* = mlirlite.Ptr(pool, anc_entry_th.Ty)
# For a cross-module base, ensure the opaque declaration exists
# before bitcasting to it, else llc reports "use of undefined type".
mlirlite.module_ensure_opaque_for_type(mod, pool, anc_ptr_ty_th)
self_arg = mlirlite.build_bitcast(builder, adj_th, anc_ptr_ty_th)
elif sub_field_th is None and anc_field_th is not None:
# Sibling Mixin case: the defining class's fields are absent here and
# it sits earlier in the composite MRO; move this forward by its size.
anc_size_th: int = self.Gen.ClassGenEnum._get_type_size(anc_entry_th.Ty)
if anc_size_th > 0:
i8_ty_th2: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_th2: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_th2)
obj_i8_th2: mlirlite.Value* = mlirlite.build_bitcast(builder, obj_ptr, i8_ptr_th2)
sib_delta_th: mlirlite.Value* = mlirlite.const_int32(pool, 0 - anc_size_th)
adj_th2: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty_th2, obj_i8_th2, sib_delta_th)
if adj_th2 is not None:
anc_ptr_ty_th2: mlirlite.LLVMType* = mlirlite.Ptr(pool, anc_entry_th.Ty)
mlirlite.module_ensure_opaque_for_type(mod, pool, anc_ptr_ty_th2)
self_arg = mlirlite.build_bitcast(builder, adj_th2, anc_ptr_ty_th2)
else:
# Defining class struct size unavailable (opaque/unloaded): fail fast.
self.Gen.TypeGenErr.fatal_error(cl, f"this-adjust sibling Mixin defining class size unknown: struct size computation failed for method '{method_name}' defining class '{mc_anc}'", "InternalCompilerError")
elif is_first_parent_th == 0:
self.Gen.TypeGenErr.fatal_error(cl, f"this-adjust field lookup failed: first field '{anc_first_name_th}' of method '{method_name}' defining class '{mc_anc}' not found in the layout of class '{class_name}'/'{mc_anc}'", "InternalCompilerError")
elif is_first_parent_th == 0: self.Gen.TypeGenErr.fatal_error(cl, f"this-adjust subclass struct not registered: method '{method_name}' defining class '{mc_anc}', struct of receiver class '{class_name}' not found", "InternalCompilerError")
else:
# Parent has only the __vtable__ field: the method body reads no
# layout fields, so no adjustment is needed.
pass
elif anc_entry_th is not None and anc_entry_th.FieldCount == 0:
# Parent with no fields: no byte adjustment needed (see above).
pass
elif is_first_parent_th == 0:
# Parent struct not registered: fail fast, never use an unadjusted
# obj_ptr as self.
self.Gen.TypeGenErr.fatal_error(cl, f"this-adjust parent struct not registered: struct of method '{method_name}' defining class '{mc_anc}' not found", "InternalCompilerError")
# Coerce self to the declared first-param type: the object pointer may be
# i8* or an adjusted ancestor pointer and needs a bitcast to match.
if found_func is not None:
pp_self: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
if pp_self is not None:
pty_self: mlirlite.LLVMType* = mlirlite.param_get_ty(pp_self)
if pty_self is not None:
cself_tm: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, self_arg, pty_self, None)
if cself_tm is not None: self_arg = cself_tm
# Build the argument list: self -> arg1 -> arg2 -> ...
mlirlite.value_set_next(self_arg, None)
head: mlirlite.Value* = self_arg
tail: mlirlite.Value* = self_arg
arg_count_eff: int = 0
if kwc > 0 and found_func is not None:
# Keyword args: walk the definition params (skip self), positional first,
# then match by name; missing params get typed zero placeholders.
cur_param_k: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
if cur_param_k is not None:
cur_param_k = mlirlite.param_get_next(cur_param_k) # skip self
pi_k: int = 0
while cur_param_k is not None:
arg_node_k: ast.AST* = None
if pi_k < can: arg_node_k = cargs.get(pi_k)
else:
pname_k: str = mlirlite.param_get_name(cur_param_k)
if pname_k is not None:
ki_k: int
for ki_k in range(int(kwc)):
kw_k: ast.Keyword* = ast.Keyword*(kw_calls.get(ki_k))
if kw_k is None or kw_k.arg is None or kw_k.value is None: continue
if self._kwarg_name_matches(kw_k.arg, pname_k) == 1:
arg_node_k = kw_k.value
break
arg_val_k: mlirlite.Value* = None
if arg_node_k is not None:
# Fetch the param type first (char literal -> str param needs a materialized string).
pty_k0: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param_k)
ao_name_k: str = None
if found_func is not None: ao_name_k = mlirlite.function_get_name(found_func)
arg_val_k = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, ao_name_k, pi_k, arg_node_k, pty_k0, None)
if arg_val_k is None:
if arg_node_k is not None:
# (a) The arg node exists but translated to None: fail fast;
# never substitute a zero value for an unresolved argument.
self.Gen.TypeGenErr.fatal_error(arg_node_k, f"Method call '{class_name}.{method_name}': argument expression for parameter {pi_k + 1} translation failed (returned None, zero-value fallback by type forbidden)","TypeError")
continue
# (b)/(c) Neither keyword nor positional supplied this param: insert a
# typed zero placeholder to keep positions (not a failure fallback).
pty_k: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param_k)
if pty_k is not None: arg_val_k = mlirlite.ConstZero(pool, pty_k)
if arg_val_k is not None:
pty_k2: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param_k)
if pty_k2 is not None: arg_val_k = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val_k, pty_k2, arg_node_k)
mlirlite.value_set_next(arg_val_k, None)
mlirlite.value_set_next(tail, arg_val_k)
tail = arg_val_k
arg_count_eff += 1
# P2 willFree:该形参位带标注且实参是变量名 → 标记失效(实参已翻译完)
if wf_mask_mc != 0 and arg_node_k is not None and ((wf_mask_mc >> (pi_k + 1)) & 1) != 0:
self._MarkWillFreeArgName(trans, arg_node_k)
pi_k += 1
cur_param_k = mlirlite.param_get_next(cur_param_k)
else:
# Positional arguments (original logic).
for ai in range(int(can)):
arg_node: ast.AST* = cargs.get(ai)
if arg_node is None:
# (a) Missing arg node (AST list hole): fail fast, never skip silently
# (skipping would reduce arg_count_eff and get zero-filled later).
self.Gen.TypeGenErr.fatal_error(cl, f"Method call '{class_name}.{method_name}': argument node {ai + 1} missing (hole in the AST argument list, cannot translate)", "TypeError")
continue
# Fetch the param type first: a CONST_CHAR passed to a str/pointer param
# must be materialized, else inttoptr treats the char as a pointer.
pp: mlirlite.Param* = None
pty: mlirlite.LLVMType* = None
if found_func is not None:
pp = mlirlite.function_get_param_head(found_func)
pi: int = 0
while pp is not None and pi < ai + 1:
if pi == ai + 1: break
pp = mlirlite.param_get_next(pp)
pi += 1
if pp is not None: pty = mlirlite.param_get_ty(pp)
# Value-reference params (&T / &temp T): auto-address (self is param 0, arg ai -> param ai+1).
ao_name_p: str = None
if found_func is not None: ao_name_p = mlirlite.function_get_name(found_func)
arg_val: mlirlite.Value* = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, ao_name_p, int(ai) + 1, arg_node, pty, None)
if arg_val is None:
# (a) The arg expression translated to None: fail fast, never continue;
# skipping would reduce arg_count_eff and get zero-filled later.
self.Gen.TypeGenErr.fatal_error(arg_node, f"Method call '{class_name}.{method_name}': argument expression {ai + 1} translation failed (returned None, zero-value fallback by type forbidden)", "TypeError")
continue
# Coerce to the param type (self is param 0, arg ai maps to param ai+1);
# an i32 constant passed to an i64 param would read garbage high bits.
if found_func is not None:
if pty is not None: arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, pty, arg_node)
else:
# found_func=None: decay array args to element pointers per C semantics,
# else the declare built from [N x ty]* would mismatch the definition.
if arg_val.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(arg_val.Ty) != 0:
arr_pointee_ma: mlirlite.LLVMType* = arg_val.Ty.Pointee
if arr_pointee_ma is not None:
match arr_pointee_ma:
case mlirlite.LLVMType.Array(arr_et_ma, _):
decayed_ma: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_pointee_ma, arr_et_ma, arg_val, mlirlite.const_int32(pool, 0))
if decayed_ma is not None: arg_val = decayed_ma
case _:
pass
mlirlite.value_set_next(arg_val, None)
mlirlite.value_set_next(tail, arg_val)
tail = arg_val
arg_count_eff += 1
# P2 willFree:该形参位带标注且实参是变量名 → 标记失效(实参已翻译完)
if wf_mask_mc != 0 and ((wf_mask_mc >> int(ai + 1)) & 1) != 0:
self._MarkWillFreeArgName(trans, arg_node)
total_count: int = 1 + arg_count_eff
# Default-argument filling when the call supplies fewer args than the
# definition; look up the function's defaults info.
mdf_defaults: rawptr = None
mdf_default_count: int = 0
mdf_param_count: int = 0
if trans is not None and trans._funcs is not None:
mdf_entry: GenTypes.FuncEntry* = self.Gen.CallGenLeafKit.find_func_entry_in_table(trans._funcs, trans._func_count, lookup_name)
if mdf_entry is not None:
mdf_defaults = mdf_entry.Defaults
mdf_default_count = mdf_entry.DefaultCount
mdf_param_count = mdf_entry.ParamCount
# Always query the global default table: the snapshot-rebuild branch below
# depends on mdf_gd (the only holder of stdlib constant snapshots).
mdf_gd: GenTypes.FuncDefaultsEntry* = self.Gen.CallGenLeafKit.find_func_defaults(lookup_name)
if mdf_defaults is None and mdf_gd is not None:
mdf_defaults = mdf_gd.Defaults
mdf_default_count = mdf_gd.DefaultCount
mdf_param_count = mdf_gd.ParamCount
# Cross-module stub fallback: when the dependency's vpi is not loaded the
# table lacks ParamCount; fill it from the vpi text cache.
vpi_pa: str = None
if mdf_param_count == 0 and found_func is None:
vpi_sha1_p: str = None
if mc_entry is not None and mc_entry.ModuleSha1 is not None: vpi_sha1_p = mc_entry.ModuleSha1
else: vpi_sha1_p = self.Gen.StructGen.get_struct_sha1(class_name)
if vpi_sha1_p is not None:
vpi_pa = self.Gen.CallGenBase._vpi_method_param_annot(pool, vpi_sha1_p, class_name, method_name)
if vpi_pa is not None and vpi_pa[0] != '\0':
vpi_pc2: int = 1
vi2: sizet = 0
while vpi_pa[vi2] != '\0':
if vpi_pa[vi2] == '@': vpi_pc2 += 1
vi2 += 1
mdf_param_count = vpi_pc2 - 1 # non-self param count
# [FIX 2026-08-25] Fall back to found_func's param count when the tables
# lack ParamCount, excluding the two EH output params injected for raise.
if mdf_param_count == 0 and found_func is not None:
ppcnt_f: int = 0
ppn_f: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
while ppn_f is not None:
fp_nm_f: str = mlirlite.param_get_name(ppn_f)
if fp_nm_f is None or (string.strcmp(fp_nm_f, "%__eh_msg_out__") != 0 and string.strcmp(fp_nm_f, "%__eh_code_out__") != 0):
ppcnt_f += 1
ppn_f = mlirlite.param_get_next(ppn_f)
if ppcnt_f > 1:
mdf_param_count = ppcnt_f - 1 # non-self, non-EH param count
# Compute the params to fill (excluding self): arg_count_eff provided vs
# mdf_param_count required.
if mdf_param_count > arg_count_eff:
mdf_offset: int = mdf_param_count - mdf_default_count
mdf_pi: int = arg_count_eff
while mdf_pi < mdf_param_count:
# Prefer the defined default value.
if mdf_defaults is not None and mdf_default_count > 0 and mdf_pi >= mdf_offset:
mdf_di: int = mdf_pi - mdf_offset
if mdf_di < mdf_default_count:
# Rebuild from the stdlib constant snapshot (the pool AST may be
# freed); fail fast rather than read a dangling AST.
mdf_val: mlirlite.Value* = None
if mdf_gd is not None: mdf_val = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, mdf_gd, mdf_di, trans, builder)
if mdf_val is None:
self.Gen.TypeGenErr.fatal_error(cl, f"Cross-module default argument cannot be rebuilt (snapshot missing, the default value must be a simple constant): {lookup_name}", "TypeError")
continue
mlirlite.value_set_next(mdf_val, None)
mlirlite.value_set_next(tail, mdf_val)
tail = mdf_val
total_count += 1
mdf_pi += 1
continue
# (c) A known default region and this param index before it means a
# missing required arg: fail fast instead of a silent zero fill.
if mdf_default_count > 0 and mdf_pi < mdf_offset:
self.Gen.TypeGenErr.fatal_error(cl, f"Method call '{lookup_name}': missing required argument: parameter {mdf_pi + 1} has no default (call site provides only {arg_count_eff} arguments; the first {mdf_offset} parameters are required)", "TypeError")
mdf_pi += 1
continue
# (b) Other missing params come from missing default info (a missing
# entry is not "no default"); add a typed zero only to align arity.
if found_func is not None:
param_node_z: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
skip_z: int = 0
while skip_z <= mdf_pi and param_node_z is not None:
param_node_z = mlirlite.param_get_next(param_node_z)
skip_z += 1
if param_node_z is not None:
zero_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(param_node_z)
zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, zero_ty)
if zero_val is not None:
mlirlite.value_set_next(zero_val, None)
mlirlite.value_set_next(tail, zero_val)
tail = zero_val
total_count += 1
elif vpi_pa is not None:
# found_func is None: resolve the missing param type from the vpi
# annotation (segment mdf_pi+1) and use its zero value.
seg_idx_v: int = mdf_pi + 1
seg_no_v: int = 0
seg_cur_v: sizet = 0
while seg_no_v < seg_idx_v and vpi_pa[seg_cur_v] != '\0':
if vpi_pa[seg_cur_v] == '@': seg_no_v += 1
seg_cur_v += 1
seg_start_v: sizet = seg_cur_v
seg_len_v: sizet = 0
while vpi_pa[seg_start_v + seg_len_v] != '\0' and vpi_pa[seg_start_v + seg_len_v] != '@': seg_len_v += 1
if seg_no_v == seg_idx_v:
pty_v: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
if seg_len_v > 0:
seg_buf_v: bytes = stdlib.malloc(seg_len_v + 1)
if seg_buf_v is not None:
string.strncpy(seg_buf_v, vpi_pa + seg_start_v, seg_len_v)
seg_buf_v[seg_len_v] = '\0'
rty_v: mlirlite.LLVMType* = self.Gen.CallGenBase._vpi_resolve_param_annot_ty(pool, seg_buf_v)
if rty_v is not None: pty_v = rty_v
stdlib.free(seg_buf_v)
zero_val_v: mlirlite.Value* = mlirlite.ConstZero(pool, pty_v)
if zero_val_v is not None:
mlirlite.value_set_next(zero_val_v, None)
mlirlite.value_set_next(tail, zero_val_v)
tail = zero_val_v
total_count += 1
mdf_pi += 1
# Virtual dispatch: if the class has a vtable and the method is in it, call
# indirectly (does not depend on found_func; reuses the earlier mc_entry).
mc_has_vt: int = 0
mc_method_idx: int = -1
if mc_entry is not None:
mc_has_vt = mc_entry.HasVTable
if mc_has_vt == 1 and mc_entry.VTableMethods is not None:
mc_methods_arr: sizet* = (sizet*)(mc_entry.VTableMethods)
for mc_mi in range(int(mc_entry.VTableMethodCount)):
mc_mname_addr: sizet = mc_methods_arr[mc_mi]
if mc_mname_addr == 0: continue
mc_mname: str = rawptr(mc_mname_addr)
if mc_mname is not None and string.strcmp(mc_mname, method_name) == 0:
mc_method_idx = mc_mi
break
if mc_has_vt == 1 and mc_method_idx >= 0:
# Virtual dispatch must pass the adjusted self_arg (multi-inheritance
# this-adjust) but read the vtable from the object head obj_ptr.
# Virtual dispatch returns before the cross-module vpi coercion below, so the value-reference
# (&T / &temp T) auto-address must be applied here (same-module table first, then vpi).
head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, cargs, head, total_count, 1)
return self._do_virtual_call(pool, builder, mod, class_name, method_name, self_arg, head, total_count, found_func, call_ret_ty, obj_ptr)
# Cross-module call: coerce args to the vpi definition-side params (width
# alignment), then create the declare to avoid llc undefined value.
head = self.Gen.CallGenLib._coerce_args_to_def_params(pool, builder, call_name, head, total_count, cargs, 1, trans)
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, head, total_count)
# Check variadic functions + C ABI promotion (cargs are arg ASTs, self_offset=1).
is_variadic_3441: int = self.Gen.CallGenVa._check_and_promote_variadic_call(pool, builder, mod, call_name, head, total_count, cargs, 1, trans)
# try-except: methods with raise (EH output params) append output args and check afterward.
return self.Gen.CallGenLeafKit._emit_eh_guarded_call(builder, pool, mod, trans, found_func, call_name, head, total_count, call_ret_ty, is_variadic_3441)
# _translate_list_builtin_call - inline translation of list builtin methods
# (layout: __data__(0) __count__(8) __elem_size__(32) ...); lm_obj is Ptr(list-struct).
def _translate_list_builtin_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, lm_name: str, lm_obj: mlirlite.Value*, cargs: list[ast.AST*]*, can: sizet, trans: HT.Translator*) -> mlirlite.Value*:
"""Translate a list builtin method call; returns None for void methods."""
if pool is None or builder is None or lm_obj is None or lm_name is None: return None
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
# Element type T parsed from the list specialization name (list[int]->i32,
# list[str]->i8*; unknown/pointer elements -> i8*).
lm_esty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(lm_obj)
lm_ename: str = None
if lm_esty is not None: lm_ename = mlirlite.get_struct_name(lm_esty)
lm_elem_ty: mlirlite.LLVMType* = None
if lm_ename is not None: lm_elem_ty = self.Gen.ExprGenName._list_elem_type_from_name(pool, lm_ename)
if lm_elem_ty is None: lm_elem_ty = i8_ptr_ty
# get / __getitem__: return the element at index.
if string.strcmp(lm_name, "get") == 0 or string.strcmp(lm_name, "__getitem__") == 0:
if can >= 1 and cargs is not None:
# Load __data__ (offset 0).
g_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
g_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, g_idx0)
g_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, g_dpp)
# Load __elem_size__ (offset 32, index 4).
g_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
g_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, g_idx4)
g_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, g_epp)
# Translate the index.
g_idx_node: ast.AST* = cargs.get(0)
g_idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, g_idx_node, None, 0, trans)
if g_data is not None and g_esize is not None and g_idx_val is not None:
g_idx_i64: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, g_idx_val, i64_ty, g_idx_node)
g_offset: mlirlite.Value* = mlirlite.build_mul(builder, g_idx_i64, g_esize)
g_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, g_data, g_offset)
if g_addr is not None:
g_ep: mlirlite.Value* = mlirlite.build_bitcast(builder, g_addr, mlirlite.Ptr(pool, lm_elem_ty))
if g_ep is not None: return mlirlite.build_load(builder, lm_elem_ty, g_ep)
# set / __setitem__: set the element at index.
elif string.strcmp(lm_name, "set") == 0 or string.strcmp(lm_name, "__setitem__") == 0:
if can >= 2 and cargs is not None:
s_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
s_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, s_idx0)
s_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, s_dpp)
s_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
s_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, s_idx4)
s_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, s_epp)
s_idx_node: ast.AST* = cargs.get(0)
s_val_node: ast.AST* = cargs.get(1)
s_idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, s_idx_node, None, 0, trans)
s_val_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, s_val_node, None, 0, trans)
if s_data is not None and s_esize is not None and s_idx_val is not None and s_val_val is not None:
s_idx_i64: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, s_idx_val, i64_ty, s_idx_node)
s_offset: mlirlite.Value* = mlirlite.build_mul(builder, s_idx_i64, s_esize)
s_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s_data, s_offset)
if s_addr is not None:
s_val_elem: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, s_val_val, lm_elem_ty, s_val_node)
s_ep: mlirlite.Value* = mlirlite.build_bitcast(builder, s_addr, mlirlite.Ptr(pool, lm_elem_ty))
if s_ep is not None:
mlirlite.build_store(builder, s_val_elem, s_ep)
return None
# pop: return the last element and decrement count.
elif string.strcmp(lm_name, "pop") == 0:
p_idx1: mlirlite.Value* = mlirlite.const_int64(pool, 1)
p_cpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, p_idx1)
p_count: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, p_cpp)
p_nc: mlirlite.Value* = mlirlite.build_sub(builder, p_count, mlirlite.const_int64(pool, 1))
mlirlite.build_store(builder, p_nc, p_cpp)
p_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
p_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, p_idx0)
p_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, p_dpp)
p_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
p_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, p_idx4)
p_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, p_epp)
if p_data is not None and p_esize is not None and p_nc is not None:
p_offset: mlirlite.Value* = mlirlite.build_mul(builder, p_nc, p_esize)
p_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, p_data, p_offset)
if p_addr is not None:
p_ep: mlirlite.Value* = mlirlite.build_bitcast(builder, p_addr, mlirlite.Ptr(pool, lm_elem_ty))
if p_ep is not None: return mlirlite.build_load(builder, lm_elem_ty, p_ep)
# clear: count = 0
elif string.strcmp(lm_name, "clear") == 0:
c_idx1: mlirlite.Value* = mlirlite.const_int64(pool, 1)
c_cpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, c_idx1)
mlirlite.build_store(builder, mlirlite.const_int64(pool, 0), c_cpp)
return None
# __iter__: iter_index = 0, return self
elif string.strcmp(lm_name, "__iter__") == 0:
it_idx5: mlirlite.Value* = mlirlite.const_int64(pool, 5)
it_ipp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, it_idx5)
mlirlite.build_store(builder, mlirlite.const_int64(pool, 0), it_ipp)
return lm_obj
# __len__: return __count__ (offset 8, index 1).
elif string.strcmp(lm_name, "__len__") == 0:
l_idx1: mlirlite.Value* = mlirlite.const_int64(pool, 1)
l_cpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, l_idx1)
if l_cpp is not None: return mlirlite.build_load(builder, i64_ty, l_cpp)
# append: call list.append(self, item) directly; it needs capacity checks and
# reallocation, so use the _list.py implementation.
elif string.strcmp(lm_name, "append") == 0:
if can >= 1 and cargs is not None:
ap_sha1: str = self._lookup_module_sha1("_list")
if ap_sha1 is None:
# Dependency module not compiled (_list not in the build chain): fail
# fast, never silently skip append.
self.Gen.TypeGenErr.fatal_error(cl, "list.append: _list module SHA1 not found (dependency module not compiled)", "InternalCompilerError")
if ap_sha1 is not None:
# Extract the specialized class name: list.append's symbol is
# {sha1}.list[AST*].append, not {sha1}.list.append.
ap_spec_nm: str = None
ap_struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(lm_obj)
if ap_struct_ty is not None: ap_spec_nm = self.Gen.StructGen.get_class_name_by_type(pool, ap_struct_ty)
if ap_spec_nm is None: ap_spec_nm = "list"
# Trigger generic specialization translation (idempotent).
if trans is not None and ap_spec_nm is not None:
lbkt_ap: str = string.strchr(ap_spec_nm, '[')
if lbkt_ap is not None:
rbkt_ap: str = string.strrchr(ap_spec_nm, ']')
if rbkt_ap is not None and rbkt_ap > lbkt_ap:
base_len_ap: sizet = sizet(uint64(lbkt_ap) - uint64(ap_spec_nm))
arg_len_ap: sizet = sizet(uint64(rbkt_ap) - uint64(lbkt_ap) - 1)
if base_len_ap > 0 and arg_len_ap > 0:
base_buf_ap: str = pool.alloc(base_len_ap + 1)
arg_buf_ap: str = pool.alloc(arg_len_ap + 1)
if base_buf_ap is not None and arg_buf_ap is not None:
string.strncpy(base_buf_ap, ap_spec_nm, base_len_ap)
base_buf_ap[base_len_ap] = '\0'
string.strncpy(arg_buf_ap, lbkt_ap + 1, arg_len_ap)
arg_buf_ap[arg_len_ap] = '\0'
args_ap: list[str]* = list[str](pool, 8)
if args_ap is not None:
# Comma-separated multiple type args (list[X] / dict[K, V]).
cur_ap: str = arg_buf_ap
while cur_ap is not None:
comma_ap: str = string.strchr(cur_ap, ',')
if comma_ap is None:
args_ap.append(cur_ap)
break
seg_len_ap: sizet = sizet(uint64(comma_ap) - uint64(cur_ap))
seg_buf_ap: str = pool.alloc(seg_len_ap + 1)
if seg_buf_ap is not None:
string.strncpy(seg_buf_ap, cur_ap, seg_len_ap)
seg_buf_ap[seg_len_ap] = '\0'
args_ap.append(seg_buf_ap)
cur_ap = comma_ap + 1
self.Gen.ClassGenGeneric._specialize_generic_class(trans, base_buf_ap, args_ap)
# Call name "{sha1}.{spec}.append":
# sha1(16) + '.'(1) + spec + '.append'(7) + NUL(1) = spec + 25
ap_mlen: sizet = string.strlen(ap_sha1) + string.strlen(ap_spec_nm) + 26
ap_mangled: str = pool.alloc(ap_mlen)
if ap_mangled is not None:
viperlib.snprintf(ap_mangled, ap_mlen, "%s.%s.append", ap_sha1, ap_spec_nm)
ap_item_node: ast.AST* = cargs.get(0)
ap_item_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ap_item_node, None, 0, trans)
if ap_item_val is not None:
mlirlite.value_set_next(lm_obj, None)
mlirlite.value_set_next(ap_item_val, None)
mlirlite.value_set_next(lm_obj, ap_item_val)
ap_ret_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
self._ensure_method_declare(pool, mod, ap_mangled, ap_ret_ty, lm_obj, 2)
return mlirlite.build_call(builder, ap_mangled, lm_obj, 2, ap_ret_ty, 0)
return None
# _translate_super_call - super().method(args) as a static parent-method call:
# current class from self, first MRO ancestor defining the method, no virtual.
# ============================================================
def _translate_super_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, method_name: str, trans: HT.Translator*) -> mlirlite.Value*:
"""Translate super().method(args) as a static parent-class method call."""
if pool is None or builder is None or cl is None or method_name is None: return None
if trans is None: return None
# 1. Current class: from the self variable's annotation class name. Do not use
# Mixin fields, whose layout differs across declaring classes.
self_entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, "self")
if self_entry is None: self_entry = self.Gen.VarGen.lookup_var_entry(trans.SymTab, "cls")
if self_entry is None:
self.Gen.TypeGenErr.fatal_error(cl, "super(): no self variable in the current scope (unavailable inside a static method)", "TypeError")
return None
cur_class: str = self.Gen.VarGen.var_annot_class_name(self_entry)
if cur_class is None:
self.Gen.TypeGenErr.fatal_error(cl, "super() can only be used inside a class method (self has no annotated class name)")
return None
# 2. Walk the MRO, skipping self, for the first ancestor defining the method.
anc: str = None
mro_sp: sizet* = self.Gen.StructGen.get_mro_chain(pool, cur_class)
mi_sp: int = 1
while mro_sp is not None and mro_sp[mi_sp] != 0:
anc_sp: str = rawptr(mro_sp[mi_sp])
if anc_sp is not None:
probe_sp: str = pool.alloc(160)
if probe_sp is not None:
viperlib.snprintf(probe_sp, 160, "%s.%s", anc_sp, method_name)
if self.Gen.CallGenLeafKit.find_func_global(probe_sp) is not None:
anc = anc_sp
break
mi_sp += 1
if anc is None:
err_sp: str = pool.alloc(256)
if err_sp is not None:
viperlib.snprintf(err_sp, 256, "super(): the parent chain of current class %s does not define method %s", cur_class, method_name)
self.Gen.TypeGenErr.fatal_error(cl, err_sp, "TypeError")
self.Gen.TypeGenErr.fatal_error(cl, "super(): the parent chain does not define the method", "TypeError")
return None
# 3. self pointer (load from the self variable's alloca).
if self_entry.Alloca is None or self_entry.Alloca.Ty is None:
self.Gen.TypeGenErr.fatal_error(cl, "super(): no self variable in the current scope (unavailable inside a static method)", "TypeError")
return None
self_ptr: mlirlite.Value* = None
match self_entry.Alloca.Ty:
case mlirlite.LLVMType.Ptr(inner1):
match inner1:
case mlirlite.LLVMType.Ptr(inner2):
obj_ty_sp: mlirlite.LLVMType* = mlirlite.Ptr(pool, inner2)
self_ptr = mlirlite.build_load(builder, obj_ty_sp, self_entry.Alloca)
case _:
self_ptr = mlirlite.build_load(builder, self_entry.Alloca.Ty, self_entry.Alloca)
case _:
self_ptr = self_entry.Alloca
if self_ptr is None: return None
# 3. this_adjust (byte-offset method: parent field area offset in the subclass).
self_arg: mlirlite.Value* = self_ptr
if string.strcmp(anc, cur_class) != 0:
anc_entry_sp: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(anc)
if anc_entry_sp is not None and anc_entry_sp.FieldCount > 0:
first_name_sp: str = None
for afi_sp in range(int(anc_entry_sp.FieldCount)):
afe_sp: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(anc_entry_sp, afi_sp)
if afe_sp is not None:
afn_sp: str = self.Gen.StructGen.get_field_name_ptr(afe_sp)
if afn_sp is not None and string.strcmp(afn_sp, "__vtable__") != 0:
first_name_sp = afn_sp
break
if first_name_sp is not None:
sub_entry_sp: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(cur_class)
if sub_entry_sp is not None:
anc_field_sp: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(anc_entry_sp.Ty, first_name_sp)
sub_field_sp: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(sub_entry_sp.Ty, first_name_sp)
if anc_field_sp is not None and sub_field_sp is not None:
anc_off_sp: int = self._field_byte_offset(pool, anc_entry_sp, anc_field_sp.Index)
sub_off_sp: int = self._field_byte_offset(pool, sub_entry_sp, sub_field_sp.Index)
delta_sp: int = sub_off_sp - anc_off_sp
if delta_sp > 0:
i8_ty_sp: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_sp: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_sp)
obj_i8_sp: mlirlite.Value* = mlirlite.build_bitcast(builder, self_ptr, i8_ptr_sp)
dval_sp: mlirlite.Value* = mlirlite.const_int32(pool, delta_sp)
adj_sp: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty_sp, obj_i8_sp, dval_sp)
if adj_sp is not None:
anc_ptr_sp: mlirlite.LLVMType* = mlirlite.Ptr(pool, anc_entry_sp.Ty)
# As above: ensure the opaque declaration exists before
# bitcasting a cross-module ancestor type.
mlirlite.module_ensure_opaque_for_type(mod, pool, anc_ptr_sp)
self_arg = mlirlite.build_bitcast(builder, adj_sp, anc_ptr_sp)
# 4. Find the parent method function and build the call name (SHA1-mangled).
anc_lookup: str = pool.alloc(160)
if anc_lookup is None: return None
viperlib.snprintf(anc_lookup, 160, "%s.%s", anc, method_name)
found_func: mlirlite.Function* = self.find_func_in_module(mod, anc_lookup)
call_name: str = anc_lookup
anc_sha1_sp: str = self.Gen.StructGen.get_struct_sha1(anc)
if anc_sha1_sp is None: anc_sha1_sp = trans.ModuleSha1
if found_func is not None:
fname_sp: str = mlirlite.function_get_name(found_func)
if fname_sp is not None: call_name = fname_sp
else:
if anc_sha1_sp is not None:
mangled_sp: str = self.Gen.FuncGenBase._mangle_name_with_sha1(anc_sha1_sp, anc_lookup)
if mangled_sp is not None: call_name = mangled_sp
# 5. Translate args and coerce to params (self is param 0, arg ai maps to ai+1).
cargs_sp: list[ast.AST*]* = cl.args
can_sp: sizet = 0
if cargs_sp is not None: can_sp = len(cargs_sp)
mlirlite.value_set_next(self_arg, None)
head: mlirlite.Value* = self_arg
tail: mlirlite.Value* = self_arg
arg_count_sp: int = 1
for ai_sp in range(int(can_sp)):
arg_node_sp: ast.AST* = cargs_sp.get(ai_sp)
if arg_node_sp is None: continue
# Fetch the param type first (char literal -> str param needs a materialized string).
pp_sp: mlirlite.Param* = None
pty_sp: mlirlite.LLVMType* = None
if found_func is not None:
pp_sp = mlirlite.function_get_param_head(found_func)
pi_sp: int = 0
while pp_sp is not None and pi_sp < ai_sp + 1:
if pi_sp == ai_sp + 1: break
pp_sp = mlirlite.param_get_next(pp_sp)
pi_sp += 1
if pp_sp is not None: pty_sp = mlirlite.param_get_ty(pp_sp)
# Value-reference params (&T / &temp T): translate_call_arg applies the same-module auto-address
# before the coercion below (cross-module vpi addressing is applied in bulk for the whole chain).
arg_val_sp: mlirlite.Value* = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, call_name, int(ai_sp) + 1, arg_node_sp, pty_sp, None)
if arg_val_sp is None: continue
if pty_sp is not None: arg_val_sp = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val_sp, pty_sp, arg_node_sp)
else:
# found_func=None: decay array args to element pointers per C semantics
# (consistent with _translate_method_call).
if arg_val_sp.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(arg_val_sp.Ty) != 0:
arr_pointee_sp: mlirlite.LLVMType* = arg_val_sp.Ty.Pointee
if arr_pointee_sp is not None:
match arr_pointee_sp:
case mlirlite.LLVMType.Array(arr_et_sp, _):
decayed_sp: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_pointee_sp, arr_et_sp, arg_val_sp, mlirlite.const_int32(pool, 0))
if decayed_sp is not None: arg_val_sp = decayed_sp
case _:
pass
mlirlite.value_set_next(arg_val_sp, None)
mlirlite.value_set_next(tail, arg_val_sp)
tail = arg_val_sp
arg_count_sp += 1
# 6. Return type: found_func -> special methods -> text.ll/vpi.
ret_ty_sp: mlirlite.LLVMType* = mlirlite.Int32(pool)
if found_func is not None:
frt_sp: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(found_func)
if frt_sp is not None: ret_ty_sp = frt_sp
else:
sp_ty_sp: mlirlite.LLVMType* = self._special_method_ret_ty(pool, anc, method_name)
if sp_ty_sp is not None: ret_ty_sp = sp_ty_sp
else:
vpi_ret_sp: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, anc_sha1_sp, anc, method_name)
if vpi_ret_sp is not None: ret_ty_sp = vpi_ret_sp
else:
looked_sp: mlirlite.LLVMType* = self._LookupMethodRetTyFromTextLL(pool, anc_sha1_sp, anc, method_name)
if looked_sp is not None: ret_ty_sp = looked_sp
else:
# Phase A not ready (anc_sha1_sp None / defining module text.ll
# missing): keep default i32 (build_call(Void) breaks expressions).
if anc_sha1_sp is None or self._dep_text_ll_exists(pool, anc_sha1_sp) == 0: ret_ty_sp = mlirlite.Int32(pool)
else: self._fail_method_ret_ty(method_name, cl, cur_class, anc, anc_sha1_sp)
# 7. Cross-module: create the declare; emit the call.
# Value-reference (&T / &temp T) params: auto-address the parent-method args (self is slot 0).
head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, cargs_sp, head, arg_count_sp, 1)
if found_func is None: self._ensure_method_declare(pool, mod, call_name, ret_ty_sp, head, arg_count_sp)
is_variadic_sp: int = self.Gen.CallGenVa._check_and_promote_variadic_call(pool, builder, mod, call_name, head, arg_count_sp, cargs_sp, 1, trans)
return mlirlite.build_call(builder, call_name, head, arg_count_sp, ret_ty_sp, is_variadic_sp)
# _recv_is_mixin_base - is the receiver used as a parent of a registered class?
# Only a real direct parent can have its field area in a composite class layout.
def _recv_is_mixin_base(self, class_name: str) -> int:
"""Is the receiver class a direct parent of any registered class; 1=yes / 0=no."""
if class_name is None: return 0
if self.Gen is None: return 0
n: int = self.Gen.StructGen.get_struct_count()
for j in range(int(n)):
e: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(j)
if e is not None and e.Name is not None:
pc: int = self.Gen.StructGen.get_parent_count(e.Name)
for pj in range(int(pc)):
pn: str = self.Gen.StructGen.get_parent_name_at(e.Name, pj)
if pn is not None:
if string.strcmp(pn, class_name) == 0: return 1
return 0
# _mixin_sibling_same_composite - do both classes share a composite class's MRO?
# A registered class K whose MRO contains both is the necessary condition.
def _mixin_sibling_same_composite(self, pool: memhub.MemBuddy*, class_name: str, cand_name: str) -> int:
"""Do both classes share a registered composite class's MRO; 1=yes / 0=no."""
if class_name is None or cand_name is None or pool is None: return 0
if self.Gen is None: return 0
n: int = self.Gen.StructGen.get_struct_count()
for j in range(int(n)):
k_entry: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(j)
if k_entry is not None and k_entry.Name is not None:
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, k_entry.Name)
if mro is not None:
has_self: int = 0
has_cand: int = 0
mi: int = 0
while mro[mi] != 0:
nm: str = rawptr(mro[mi])
if nm is not None:
if string.strcmp(nm, class_name) == 0: has_self = 1
elif string.strcmp(nm, cand_name) == 0: has_cand = 1
if has_self != 0 and has_cand != 0: return 1
mi += 1
return 0
# _field_byte_offset - byte offset of a field in the struct layout (with padding).
# field_index is FieldEntry.Index; used by multi-inheritance this-adjust.
def _field_byte_offset(self, pool: memhub.MemBuddy*, entry: GenTypes.StructEntry*, field_index: int) -> int:
"""Compute a field's byte offset in the struct layout; 0 if unavailable."""
if pool is None or entry is None or entry.Ty is None or field_index < 0: return 0
match entry.Ty:
case mlirlite.LLVMType.Struct(fields, fcount, name):
offset_fb: int = 0
# packed (@c.Attribute(t.attr.packed)) structs have no field padding:
# offset = sum of preceding field sizes (LLVM packed struct layout).
is_packed_fb: int = entry.IsPacked
cur_fb: mlirlite.ParamNode* = fields
i_fb: int = 0
while cur_fb is not None and i_fb < field_index:
if cur_fb.Ty is not None:
fty_fb: mlirlite.LLVMType* = (mlirlite.LLVMType*)(cur_fb.Ty)
fsize_fb: int = self.Gen.ClassGenEnum._get_type_size(fty_fb)
falign_fb: int = self.Gen.ClassGenEnum._get_type_align(fty_fb)
if is_packed_fb == 0 and falign_fb > 1:
rem_fb: int = offset_fb % falign_fb
if rem_fb != 0: offset_fb += falign_fb - rem_fb
offset_fb += fsize_fb
cur_fb = cur_fb.Next
i_fb += 1
# Align the field itself.
if cur_fb is not None and cur_fb.Ty is not None:
fty2_fb: mlirlite.LLVMType* = (mlirlite.LLVMType*)(cur_fb.Ty)
falign2_fb: int = self.Gen.ClassGenEnum._get_type_align(fty2_fb)
if is_packed_fb == 0 and falign2_fb > 1:
rem2_fb: int = offset_fb % falign2_fb
if rem2_fb != 0: offset_fb += falign2_fb - rem2_fb
return offset_fb
case _:
return 0
# _kwarg_name_matches - check whether a keyword arg name matches a param name.
# Param names carry a "%" prefix in IR; compare after skipping it.
def _kwarg_name_matches(self, kw_arg: str, param_name: str) -> int:
"""Check whether kwarg name kw_arg matches param param_name (skips '%' prefix)."""
if kw_arg is None or param_name is None: return 0
start: sizet = 0
if param_name[0] == '%': start = 1
for i, ch in enumerate(kw_arg):
c: char = param_name[start + i]
if c == '\0' or c != ch: return 0
if param_name[start + i + 1] != '\0': return 0
return 1