1776 lines
120 KiB
Plaintext
1776 lines
120 KiB
Plaintext
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 |