Files
ViperC/App/lib/core/Gen/Call/CallGenLeafKit.vp
T
TermiNexus 4bf04c2452 feat(vpc): binary .vpb stub companion and str->char* migration in call gen
- add VpiStub: fixed-layout blob written next to every .vpi (dual write at all
  writers, Ensure() backfills a missing or stale companion on cache reuse)
- serve arity/signature queries for top-level functions from the blob, with a
  silent fallback to the text scanner (methods, varargs, duplicates, corrupt file)
- prefer the injected declare over the text arity in the cross-module check
- CDefine/type text indexes and in-place SSA renumbering and bulk appends
- WIP: char* conversion in CallGenBase and CallGenLeafKit
2026-10-31 16:25:18 +08:00

1675 lines
93 KiB
Plaintext

import lib.core.Gen.GenTypes as GenTypes
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.Translator as HT
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.Type.TypeSpec as TSpec
from . import CallGenCBuiltin
from . import CallGenBase
# 重载决议上限:同名候选数与单次调用的形参个数(超出即 fail-fast,不静默截断)
define OVERLOAD_CAND_MAX = 16
define OVERLOAD_ARG_MAX = 32
# ============================================================
# CallGenLeafKit - CallGenMixin split 3/9: EH/isinstance/closures/print/func table
# ============================================================
# Constants
define _CEXPORT_MAX = 256
define ASM_MAX_OPERANDS = 16
define ASM_TEXT_BUF_SIZE = 2048
define ASM_CONST_BUF_SIZE = 512
define ASM_ARGS_BUF_SIZE = 512
define ASM_LINE_BUF_SIZE = 4096
define LLVMIR_MAX_OPS = 16
define LLVMIR_TEMPLATE_BUF = 512
# Global function-table name index (name -> slot, open addressing / linear probing):
# capacity 16384 = 2 * GLOBAL_FUNC_MAX; slots store (index + 1), 0 = empty.
define GLOBAL_FUNC_IDX_CAP = 16384
# index hash constants (32-bit FNV-1a, same pattern as StructGenMixin)
define GFN_FNV_OFFSET = 2166136261
define GFN_FNV_PRIME = 16777619
define GFN_FNV_MASK = 4294967295
def _gfn_hash(name: char*) -> uint64:
"""Function name to 64-bit hash (32-bit FNV-1a, byte-wise until NUL)"""
h: uint64 = uint64(GFN_FNV_OFFSET)
if name is None: return h
i: sizet = 0
while name[i] != '\0':
h = (h ^ uint64(int(name[i]))) * uint64(GFN_FNV_PRIME)
h = h & uint64(GFN_FNV_MASK)
i += 1
return h
def _gfn_idx_slot(idx_base: sizet*, si: sizet) -> sizet*:
"""Return the si-th index slot (8 bytes; stores slot index + 1, 0 = empty)"""
return (sizet*)(uint64(idx_base) + uint64(si) * 8)
def _gfn_idx_find(funcs: GenTypes.FuncEntry*, idx_base: sizet*, cap: int, name: char*) -> int:
"""Probe a name in the global func-name index; return the matching slot (>=0) or -1"""
if funcs is None or idx_base is None or cap <= 0 or name is None: return -1
h: uint64 = _gfn_hash(name)
mask: sizet = sizet(cap - 1)
si: sizet = sizet(h) & mask
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
for i in range(int(cap)):
slot: sizet* = _gfn_idx_slot(idx_base, si)
sv: sizet = slot[0]
if sv == 0: return -1
se: int = int(sv) - 1
entry_addr: uint64 = uint64(funcs) + se * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
if entry is not None and entry.Name is not None:
if string.strcmp(entry.Name, name) == 0: return se
si = (si + 1) & mask
return -1
def _gfn_idx_insert(idx_base: sizet*, cap: int, slot_index: int, name: char*) -> int:
"""Insert (name -> slot_index) into the index; 0 = success, 1 = failure"""
if idx_base is None or cap <= 0 or name is None: return 1
h: uint64 = _gfn_hash(name)
mask: sizet = sizet(cap - 1)
si: sizet = sizet(h) & mask
for i in range(int(cap)):
slot: sizet* = _gfn_idx_slot(idx_base, si)
if slot[0] == 0:
slot[0] = sizet(slot_index + 1)
return 0
si = (si + 1) & mask
return 1
# ============================================================
# CallGenLeafKit - EH/isinstance/closures/print/func table
novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
"""EH guard + isinstance + closures + print + global func table (extends CBuiltin)"""
# _func_has_eh_params - whether the callee signature has the try-except EH output
# params (%__eh_msg_out__/__eh_code_out__), injected for functions containing raise.
def _func_has_eh_params(self, func: mlirlite.Function*) -> int:
"""Return 1 if the function takes an EH out-parameter, else 0"""
if func is None: return 0
cur_p: mlirlite.Param* = mlirlite.function_get_param_head(func)
while cur_p is not None:
pn: char* = mlirlite.param_get_name(cur_p)
if pn is not None and string.strcmp(pn, "%__eh_msg_out__") == 0: return 1
cur_p = mlirlite.param_get_next(cur_p)
return 0
# _emit_eh_guarded_call - caller-side try-except EH check (aligned with TPC
# HandlesExprCall). Reuses an existing msg/code alloca or out-param, else a local null alloca.
# ============================================================
def _emit_eh_guarded_call(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, func: mlirlite.Function*, call_name: char*, head: mlirlite.Value*, count: int, ret_ty: mlirlite.LLVMType*, is_variadic: int) -> mlirlite.Value*:
"""Call a function with try-except EH detection; returns the call result (positioned at ok block)"""
# Argument-count check (fail-fast at the front end instead of leaking to the LLVM verifier).
# Callee params include the injected EH out-params (%__eh_msg_out__/__eh_code_out__), which the
# caller does not pass explicitly (they are appended later in this function), so subtract them.
# 跨模块调用时 found_func 恒为 None(调用方只拿到 mangle 名),这里按 call_name 取回模块内的
# declare 再检查;否则操作数不足的调用会一路漏到 mlir-opt 才报 "incorrect number of operands"。
chk_func: mlirlite.Function* = func
if chk_func is None and mod is not None and call_name is not None:
chk_func = self.Gen.CallGenBase.find_func_in_module(mod, call_name)
if chk_func is not None and is_variadic == 0:
n_declared: int = 0
p_chk: mlirlite.Param* = mlirlite.function_get_param_head(chk_func)
while p_chk is not None:
n_declared += 1
p_chk = mlirlite.param_get_next(p_chk)
if self._func_has_eh_params(chk_func) == 1: n_declared = n_declared - 2
if n_declared >= 0 and count != n_declared:
fname_chk: char* = mlirlite.function_get_name(chk_func)
if fname_chk is None: fname_chk = call_name
self.Gen.TypeGenErr.fatal_error(
None,
f"Argument count mismatch calling '{fname_chk}': the callee declares {n_declared} parameter(s) but the call passes {count} argument(s)",
"TypeError")
if trans is None or self._func_has_eh_params(func) == 0: return mlirlite.build_call(builder, call_name, head, count, ret_ty, is_variadic)
# ---- 1. determine output pointers ----
i8_ty_g: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_g: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_g)
i32_ty_g: mlirlite.LLVMType* = mlirlite.Int32(pool)
if i8_ty_g is None or i8_ptr_ty_g is None or i32_ty_g is None: return mlirlite.build_call(builder, call_name, head, count, ret_ty, is_variadic)
msg_ptr: mlirlite.Value* = None
code_ptr: mlirlite.Value* = None
if trans._eh_msg_ptr is not None and trans._eh_code_ptr is not None:
msg_ptr = trans._eh_msg_ptr
code_ptr = trans._eh_code_ptr
elif trans._cur_func_has_eh != 0 and trans._eh_msg_out_param is not None and trans._eh_code_out_param is not None:
msg_ptr = trans._eh_msg_out_param
code_ptr = trans._eh_code_out_param
if msg_ptr is None:
msg_ptr = self.Gen.VarGen._alloca_at_entry(builder, i8_ptr_ty_g)
code_ptr = self.Gen.VarGen._alloca_at_entry(builder, i32_ty_g)
# ---- 2. clear before the call ----
null_msg_g: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty_g, "null")
zero_code_g: mlirlite.Value* = mlirlite.const_int32(pool, 0)
if null_msg_g is not None: mlirlite.build_store(builder, null_msg_g, msg_ptr)
if zero_code_g is not None: mlirlite.build_store(builder, zero_code_g, code_ptr)
# ---- 3. append args (tail of list + count+2) ----
msg_ptr.Next = code_ptr
code_ptr.Next = None
if head is None: head = msg_ptr
else:
tail_g: mlirlite.Value* = head
while tail_g.Next is not None: tail_g = tail_g.Next
tail_g.Next = msg_ptr
count = count + 2
# ---- 4. call + check ----
result: mlirlite.Value* = mlirlite.build_call(builder, call_name, head, count, ret_ty, is_variadic)
cur_func_g: mlirlite.Function* = trans._cur_func
if cur_func_g is None or null_msg_g is None: return result
msg_val_g: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty_g, msg_ptr)
if msg_val_g is None: return result
is_err_g: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, msg_val_g, null_msg_g)
if is_err_g is None: return result
lc_g: int = trans._label_counter
trans._label_counter = lc_g + 1
bb_buf_g: char* = pool.alloc(64)
if bb_buf_g is None: return result
viperlib.snprintf(bb_buf_g, 64, "eh.err.%d", lc_g)
err_bb_g: mlirlite.BasicBlock* = mlirlite.create_block(pool, cur_func_g, bb_buf_g)
viperlib.snprintf(bb_buf_g, 64, "eh.ok.%d", lc_g)
ok_bb_g: mlirlite.BasicBlock* = mlirlite.create_block(pool, cur_func_g, bb_buf_g)
if err_bb_g is None or ok_bb_g is None: return result
mlirlite.build_cond_br(builder, is_err_g, err_bb_g, ok_bb_g)
# err block
mlirlite.position_at_end(builder, err_bb_g)
if trans._eh_dispatch_bb is not None:
# inside try: the exception is already in try's alloca (code_ptr);
# branch to DispatchBB to hand off to the except chain.
mlirlite.build_br(builder, trans._eh_dispatch_bb)
else:
# not in try: the exception is in this function's out-param (reused) or a
# local alloca; just return the default and let the caller check our out-param.
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
ret_ty_g2: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(cur_func_g)
if ret_ty_g2 is not None:
match ret_ty_g2:
case mlirlite.LLVMType.Void():
mlirlite.build_ret_void(builder)
case mlirlite.LLVMType.Ptr(_pg):
nv_g: mlirlite.Value* = mlirlite.ConstNull(pool, ret_ty_g2, "null")
if nv_g is not None: mlirlite.build_ret(builder, nv_g)
case mlirlite.LLVMType.Float(_fg):
zf_g: mlirlite.Value* = mlirlite.ConstFloat(pool, ret_ty_g2, 0.0)
if zf_g is not None: mlirlite.build_ret(builder, zf_g)
case _:
zv_g: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty_g2)
if zv_g is not None: mlirlite.build_ret(builder, zv_g)
# ok block: continue
mlirlite.position_at_end(builder, ok_bb_g)
return result
# isinstance - runtime vtable subclass-set comparison: precompute the class's known
# subclasses via reverse BFS, then compare obj->vtable against their vtable addresses.
def _translate_isinstance(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, obj_node: ast.AST*, cls_node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""isinstance(obj, Cls): runtime vtable subclass-set comparison; returns i1"""
if obj_node is None or cls_node is None:
# missing args is a call error: fail fast, never hide it by returning True
self.Gen.TypeGenErr.fatal_error(cls_node, "isinstance() argument type inference failed", "TypeError")
obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, obj_node, None, 0, trans)
if obj_val is None or obj_val.Ty is None:
# obj type inference failed: fail fast, never hide it by returning True
self.Gen.TypeGenErr.fatal_error(cls_node, "isinstance() argument type inference failed", "TypeError")
# normalize the object pointer: load through a pointer-to-pointer (var alloca / Class*)
obj_ptr: mlirlite.Value* = obj_val
match obj_val.Ty:
case mlirlite.LLVMType.Ptr(inner_ty):
match inner_ty:
case mlirlite.LLVMType.Ptr(_):
loaded: mlirlite.Value* = mlirlite.build_load(builder, inner_ty, obj_val)
if loaded is not None: obj_ptr = loaded
case _:
pass
obj_is_ptr: int = self.Gen.ExprGenBase.is_ptr_type(obj_ptr.Ty)
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)
# read the object vtable pointer lazily (once, at the first VTableT comparison)
vtable_ptr: mlirlite.Value* = None
vtable_ready: int = 0
res: mlirlite.Value* = None
cls_names: sizet* = self._extract_isinstance_class_names(pool, cls_node)
if cls_names is None:
# class-name extraction failed: fail fast, never hide it by returning True
self.Gen.TypeGenErr.fatal_error(cls_node, "isinstance() argument type inference failed", "TypeError")
ci: int = 0
while cls_names[ci] != 0:
cn: char* = rawptr(cls_names[ci])
if cn is not None:
if self.Gen.StructGen.is_novtable_by_name(cn) != 0 or self.Gen.StructGen.has_vtable_by_name(cn) == 0:
# NoVTable / unknown class -> compile-time check (obj static-type MRO)
st: int = self._isinstance_compile_time(pool, obj_ptr, cn)
c1: mlirlite.Value* = mlirlite.const_int(pool, 1, 1 if st > 0 else 0)
if res is None: res = c1
else: res = mlirlite.build_or(builder, res, c1)
elif obj_is_ptr != 0:
# VTableT -> runtime vtable compare (read the object header vtable pointer)
if vtable_ready == 0:
vtable_slot: mlirlite.Value* = mlirlite.build_bitcast(builder, obj_ptr, i8_ptr_ptr_ty)
if vtable_slot is not None: vtable_ptr = mlirlite.build_load(builder, i8_ptr_ty, vtable_slot)
vtable_ready = 1
if vtable_ptr is not None:
subs: sizet* = self._collect_subclass_vtable_classes(pool, cn)
if subs is not None:
k: int = 0
while subs[k] != 0:
scn: char* = rawptr(subs[k])
if scn is not None:
sub_i8: mlirlite.Value* = self._make_vtable_ref_i8(pool, builder, mod, scn, trans)
if sub_i8 is not None:
cmp: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, vtable_ptr, sub_i8)
if cmp is not None:
if res is None: res = cmp
else: res = mlirlite.build_or(builder, res, cmp)
k += 1
ci += 1
if res is None:
# no comparison produced (obj non-pointer, all classes VTableT): fail fast
self.Gen.TypeGenErr.fatal_error(cls_node, "isinstance() argument type inference failed", "TypeError")
return res
def _extract_isinstance_class_names(self, pool: memhub.MemBuddy*, node: ast.AST*) -> sizet*:
"""Extract the class-name array from isinstance's second argument (Name/Attribute/Tuple)"""
arr: sizet* = pool.alloc(8 * 16)
if arr is None: return None
# zero the whole array: pool memory may be non-zero, so uninitialized slots would
# be dereferenced as class-name pointers by the while cls_names[ci]!=0 loop.
string.memset(arr, 0, 8 * 16)
arr[0] = 0
if node is None: return arr
count: int = 0
k: int = node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None: arr[0] = sizet(nm.id)
elif k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.attr is not None: arr[0] = sizet(at.attr)
elif k == ast.ASTKind.Tuple:
tp: ast.Tuple* = ast.Tuple*(node)
elts: list[ast.AST*]* = tp.elts
if elts is not None:
n: sizet = len(elts)
for i in range(int(n)):
if count >= 15: break
elt: ast.AST* = elts.get(i)
if elt is None: continue
ek: int = elt.kind()
if ek == ast.ASTKind.Name:
enm: ast.Name* = ast.Name*(elt)
if enm.id is not None:
arr[count] = sizet(enm.id)
count += 1
elif ek == ast.ASTKind.Attribute:
eat: ast.Attribute* = ast.Attribute*(elt)
if eat.attr is not None:
arr[count] = sizet(eat.attr)
count += 1
arr[count] = 0
return arr
def _collect_subclass_vtable_classes(self, pool: memhub.MemBuddy*, class_name: char*) -> sizet*:
"""Reverse BFS collect the target class and all subclasses (0-terminated array)"""
if class_name is None: return None
names: sizet* = pool.alloc(8 * (CallGenBase.ISINSTANCE_CLASS_MAX + 1))
queue: sizet* = pool.alloc(8 * CallGenBase.ISINSTANCE_CLASS_MAX)
visited: sizet* = pool.alloc(8 * CallGenBase.ISINSTANCE_CLASS_MAX)
if names is None or queue is None or visited is None: return None
string.memset(names, 0, 8 * (CallGenBase.ISINSTANCE_CLASS_MAX + 1))
string.memset(queue, 0, 8 * CallGenBase.ISINSTANCE_CLASS_MAX)
string.memset(visited, 0, 8 * CallGenBase.ISINSTANCE_CLASS_MAX)
qcount: int = 1
outcount: int = 0
vcount: int = 0
queue[0] = sizet(class_name) # uncertain
while qcount > 0:
qcount -= 1
cur_addr: sizet = queue[qcount]
cur: char* = rawptr(cur_addr)
if cur is None: continue
dup: int = 0
for vi in range(int(vcount)):
va: sizet = visited[vi]
if va != 0:
vn: char* = rawptr(va)
if vn is not None and string.strcmp(vn, cur) == 0:
dup = 1
break
if dup != 0: continue
if vcount < CallGenBase.ISINSTANCE_CLASS_MAX:
visited[vcount] = sizet(cur)
vcount += 1
centry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(cur)
if centry is not None and centry.HasVTable != 0:
if outcount < CallGenBase.ISINSTANCE_CLASS_MAX:
names[outcount] = sizet(cur)
outcount += 1
# enqueue subclasses: full scan over registered structs whose ParentNames
# contain cur (by ordinal, not by name -- do not switch to find_struct).
scnt: int = self.Gen.StructGen.get_struct_count()
for i in range(int(scnt)):
e2: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(i)
if e2 is None or e2.Name is None: continue
if e2.ParentCount > 0 and e2.ParentNames is not None:
parr: sizet* = (sizet*)(e2.ParentNames)
for j in range(int(e2.ParentCount)):
pa: sizet = parr[j]
if pa != 0:
pn: char* = rawptr(pa)
if pn is not None and string.strcmp(pn, cur) == 0:
if qcount < CallGenBase.ISINSTANCE_CLASS_MAX:
queue[qcount] = sizet(e2.Name)
qcount += 1
break
names[outcount] = 0
return names
def _make_vtable_ref_i8(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: char*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Build a class vtable address reference (i8*): defined here directly, external cross-module"""
if class_name is None: return None
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
buf: char* = pool.alloc(256)
if buf is None: return None
viperlib.snprintf(buf, 256, "%s.vtable", class_name)
vn: char* = rawptr(buf)
sha1: char* = self.Gen.StructGen.get_struct_sha1(class_name)
if sha1 is None:
if trans is None: return None
sha1 = trans.ModuleSha1
if sha1 is None: return None
mangled: char* = self.Gen.FuncGenBase._mangle_name_with_sha1(sha1, vn)
ref_name: char* = pool.alloc(256)
if ref_name is None: return None
# vtable names carry the sha1 dot prefix, so always use the @"..." quoted form
viperlib.snprintf(ref_name, 256, "@\"%s\"", mangled)
gv: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(mod, mangled)
ref_ty: mlirlite.LLVMType* = None
if gv is not None:
# vtable defined in this module (constant {i8* x N}) -> reference type Ptr(struct)
ref_ty = mlirlite.Ptr(pool, gv.Ty)
else:
# cross-module: declare @mangled = external global i8*, reference type i8**
gv2: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, mangled, i8_ptr_ty)
if gv2 is None: return None
mlirlite.module_add_global(mod, gv2)
ref_ty = mlirlite.Ptr(pool, i8_ptr_ty)
ref: mlirlite.Value* = mlirlite.SSAValue(pool, ref_ty, ref_name)
if ref is None: return None
return mlirlite.build_bitcast(builder, ref, i8_ptr_ty)
def _isinstance_compile_time(self, pool: memhub.MemBuddy*, obj_ptr: mlirlite.Value*, class_name: char*) -> int:
"""Static check: whether obj's static type equals class_name or lists it in its MRO"""
if obj_ptr is None or obj_ptr.Ty is None or class_name is None: return 0
match obj_ptr.Ty:
case mlirlite.LLVMType.Ptr(obj_struct_ty):
sname: char* = self.Gen.StructGen.get_class_name_by_type(pool, obj_struct_ty)
if sname is not None:
if string.strcmp(sname, class_name) == 0: return 1
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, sname)
if mro is not None:
k: int = 0
while mro[k] != 0:
mn: char* = rawptr(mro[k])
if mn is not None and string.strcmp(mn, class_name) == 0: return 1
k += 1
case _:
pass
return 0
# P4 cluster 5+6 migrated methods (bodies from HandlesExprCall.vp: closure call /
# function pointer / printf / print / stdarg sugar / varargs promotion / func tables).
def translate_closure_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, closure_alloca: mlirlite.Value*, cargs: list[ast.AST*]*, can: sizet, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Indirect call through a closure pointer (supports parameterized lambdas)"""
if builder is None or closure_alloca is None: return None
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
# 1. load the closure pointer (i8*)
closure_ptr: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, closure_alloca)
if closure_ptr is None: return None
# 2. load fn_ptr (offset 0): bitcast to i8**, load i8*
fn_ptr_addr: mlirlite.Value* = mlirlite.build_bitcast(builder, closure_ptr, mlirlite.Ptr(pool, i8_ptr_ty))
if fn_ptr_addr is None: return None
fn_raw: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, fn_ptr_addr)
if fn_raw is None: return None
# 3. load env_ptr (offset 8): GEP i8 closure 8; load i8*
eight_val: mlirlite.Value* = mlirlite.const_int32(pool, 8)
env_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, closure_ptr, eight_val)
if env_addr is None: return None
env_ptr: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, env_addr)
if env_ptr is None: return None
# 4. build args: env head + arg tail (translate args for a parameterized lambda)
total_args: int = 1
arg_head: mlirlite.Value* = env_ptr
arg_tail: mlirlite.Value* = env_ptr
param_node: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i8_ptr_ty)
pn_tail: mlirlite.ParamNode* = param_node
if can > 0 and cargs is not None and trans is not None:
for ai in range(int(can)):
arg: ast.AST* = cargs.get(ai)
if arg is None: continue
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans)
if arg_val is None or arg_val.Ty is None: continue
arg_val.Next = None
if arg_tail is not None: arg_tail.Next = arg_val
arg_tail = arg_val
pn_new: mlirlite.ParamNode* = mlirlite.new_param_node(pool, arg_val.Ty)
if pn_new is not None:
pn_tail.Next = pn_new
pn_tail = pn_new
total_args += 1
if total_args == 1: env_ptr.Next = None
# 5. bitcast fn_raw to the function-pointer type i32(i8*, [args...])*
func_ty: mlirlite.LLVMType* = mlirlite.Func(pool, i32_ty, param_node, total_args)
func_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, func_ty)
fn_typed: mlirlite.Value* = mlirlite.build_bitcast(builder, fn_raw, func_ptr_ty)
if fn_typed is None: return None
# 6. indirect call: call i32 %fn_typed(env, args...)
return mlirlite.build_call_indirect(builder, fn_typed, arg_head, total_args, i32_ty)
def _is_func_ptr_alloca(self, alloca: mlirlite.Value*) -> int:
"""Return whether an alloca is a function-pointer variable (type Ptr(Ptr(i8))); 1 = yes"""
if alloca is None or alloca.Ty is None: return 0
if self.Gen.ExprGenBase.is_ptr_type(alloca.Ty) == 0: return 0
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
if pointee is None or self.Gen.ExprGenBase.is_ptr_type(pointee) == 0: return 0
inner: mlirlite.LLVMType* = pointee.Pointee
if inner is None or self.Gen.ExprGenBase.is_ptr_type(inner) != 0: return 0
return 1
def _ensure_closure_names(self) -> int:
"""Ensure the closure-name table is allocated; returns 0 on failure"""
if self.ClosureNames is not None: return 1
self.ClosureNames = CompilerFactory.factory.PermPool.alloc(64 * CallGenBase.MAX_CLOSURE_NAMES)
if self.ClosureNames is None: return 0
string.memset(self.ClosureNames, 0, 64 * CallGenBase.MAX_CLOSURE_NAMES)
return 1
def register_closure_name(self, name: char*) -> int:
"""Register a nested function (closure variable) name; returns 0 on success"""
if name is None: return 1
if self._ensure_closure_names() == 0: return 1
if self.ClosureCount >= CallGenBase.MAX_CLOSURE_NAMES: return 1
idx: sizet = sizet(self.ClosureCount) * 64
string.strncpy(self.ClosureNames + idx, name, 63)
self.ClosureCount += 1
return 0
def is_closure_name(self, name: char*) -> int:
"""Return whether name is registered as a closure variable name (1 = yes, 0 = no)"""
if name is None or self.ClosureNames is None: return 0
i: sizet
for i in range(int(self.ClosureCount)):
if string.strcmp(self.ClosureNames + i * 64, name) == 0: return 1
return 0
def _ensure_funcptr_sigs(self) -> int:
"""Ensure the function-pointer signature table is allocated (lazy stdlib.malloc)"""
if self.FuncptrNames is not None: return 1
self.FuncptrNames = CompilerFactory.factory.PermPool.alloc(64 * CallGenBase.MAX_FUNC_PTR_SIGS)
self.FuncptrRets = CompilerFactory.factory.PermPool.alloc(8 * CallGenBase.MAX_FUNC_PTR_SIGS)
if self.FuncptrNames is None or self.FuncptrRets is None: return 0
string.memset(self.FuncptrNames, 0, 64 * CallGenBase.MAX_FUNC_PTR_SIGS)
return 1
def _func_ptr_sig_fatal(self, pool: memhub.MemBuddy*, node: ast.AST*, name: char*, ret_ty: mlirlite.LLVMType*, reason: char*) -> None:
"""Fail compilation when a function-pointer signature registration cannot be honored"""
cap: int = CallGenBase.MAX_FUNC_PTR_SIGS
ret_str: char* = "?"
if ret_ty is not None:
typed_buf: char* = pool.alloc_tagged(4096, memhub.DETSITE_LEAFKIT_539)
if typed_buf is not None:
mlirlite.TypePrint(typed_buf, 4096, ret_ty, pool)
if typed_buf[0] != '\0': ret_str = typed_buf
msg_buf: char* = pool.alloc(1024)
if msg_buf is not None:
viperlib.snprintf(msg_buf, 1024, "Function pointer signature table registration failed: variable '%s' (return type %s) -- %s. " "Current limit MAX_FUNC_PTR_SIGS=%d, %d entries registered. " "Duplicate registration with the same name and return type hits in place and does not occupy a new slot; " "if more function pointer variables are really needed, raise the " "MAX_FUNC_PTR_SIGS (the single repo-wide source; changing this one place takes effect everywhere)", name, ret_str, reason, cap, self.FuncptrCount)
self.Gen.TypeGenErr.fatal_error(node, msg_buf, "TypeError")
return
self.Gen.TypeGenErr.fatal_error(node, "Function pointer signature table registration failed (table full or variable name conflict)", "TypeError")
def register_func_ptr_sig(self, pool: memhub.MemBuddy*, name: char*, ret_ty: mlirlite.LLVMType*, node: ast.AST* = None) -> int:
"""Register a function-pointer variable's return type; 0 = success, 1 = failed (already reported)"""
if name is None or ret_ty is None: return 1
if self._ensure_funcptr_sigs() == 0:
self._func_ptr_sig_fatal(pool, node, name, ret_ty, "Signature table allocation failed (PermPool out of memory)")
return 1
# idempotence/conflict check: linear scan of registered bare names (cap 64, cheap)
i: sizet
for i in range(int(self.FuncptrCount)):
idx_hit: sizet = sizet(i) * 64
if string.strcmp(self.FuncptrNames + idx_hit, name) == 0:
prev_ret: mlirlite.LLVMType* = (mlirlite.LLVMType*)(self.FuncptrRets[i])
if prev_ret is not None and self.Gen.AssignGen._stg_ty_equal(pool, prev_ret, ret_ty) != 0: return 0
self._func_ptr_sig_fatal(pool, node, name, ret_ty, "This name is already registered with a different return type")
return 1
if self.FuncptrCount >= CallGenBase.MAX_FUNC_PTR_SIGS:
self._func_ptr_sig_fatal(pool, node, name, ret_ty, "Signature table is full")
return 1
idx: sizet = sizet(self.FuncptrCount) * 64
# slot is 64 bytes fixed; use strncpy clamping like register_closure_name
string.strncpy(self.FuncptrNames + idx, name, 63)
# ret_ty is a caller pool-allocated LLVMType that dangles after the per-file reset;
# deep-copy into perm_pool (lookup_func_ptr_ret reads it across files).
ret_copy_fp: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, ret_ty)
if ret_copy_fp is not None: self.FuncptrRets[self.FuncptrCount] = uint64(ret_copy_fp)
else: self.FuncptrRets[self.FuncptrCount] = uint64(ret_ty)
self.FuncptrCount += 1
return 0
def lookup_func_ptr_ret(self, name: char*) -> mlirlite.LLVMType*:
"""Look up a function-pointer variable's return type; None = unregistered (defaults to i32)"""
if name is None or self.FuncptrNames is None: return None
for i in range(int(self.FuncptrCount)):
idx: sizet = sizet(i) * 64
if string.strcmp(self.FuncptrNames + idx, name) == 0: return (mlirlite.LLVMType*)(self.FuncptrRets[i])
return None
def _translate_func_ptr_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, fp_alloca: mlirlite.Value*, func_name: char*, cargs: list[ast.AST*]*, can: sizet, trans: HT.Translator*) -> mlirlite.Value*:
"""Indirect call through a function-pointer variable; returns the result Value"""
if builder is None or fp_alloca is None: return None
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
# 1. load the function address (i8*)
fn_i8: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, fp_alloca)
if fn_i8 is None: return None
# 2. translate args and link them into a Value list
head: mlirlite.Value* = None
tail: mlirlite.Value* = None
arg_count: int = 0
ai: sizet
for ai in range(int(can)):
arg: ast.AST* = cargs.get(ai)
if arg is not None:
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans)
if arg_val is not None:
arg_val.Next = None
if head is None: head = arg_val
else: tail.Next = arg_val
tail = arg_val
arg_count += 1
# 3. build the signature from arg types + registered ret type (missing = fail fast)
ret_ty: mlirlite.LLVMType* = self.lookup_func_ptr_ret(func_name)
if ret_ty is None:
self.Gen.TypeGenErr.fatal_error(None, f"Return type of function pointer variable '{func_name}' is not registered (t.Callable annotation missing)", "TypeError")
ret_ty = i32_ty
fp_first: mlirlite.ParamNode* = None
fp_prev: mlirlite.ParamNode* = None
cur_v: mlirlite.Value* = head
for pi in range(int(arg_count)):
if cur_v is None: break
cur_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(cur_v)
pn2: mlirlite.ParamNode* = mlirlite.new_param_node(pool, cur_ty)
if pn2 is not None:
if fp_first is None: fp_first = pn2
if fp_prev is not None: mlirlite.paramnode_set_next(fp_prev, pn2)
fp_prev = pn2
cur_v = mlirlite.value_get_next(cur_v)
func_ty: mlirlite.LLVMType* = mlirlite.Func(pool, ret_ty, fp_first, arg_count)
if func_ty is None: return None
func_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, func_ty)
# 4. bitcast i8* to the function-pointer type and call indirectly
fn_typed: mlirlite.Value* = mlirlite.build_bitcast(builder, fn_i8, func_ptr_ty)
if fn_typed is None: return None
return mlirlite.build_call_indirect(builder, fn_typed, head, arg_count, ret_ty)
def translate_printf_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> None:
"""Translate a printf call (special-cases the format string)"""
cargs: list[ast.AST*]* = cl.args
if cargs is None: return
can: sizet = len(cargs)
if can == 0: return
arg0: ast.AST* = cargs.get(0)
if arg0 is None: return
fmt_str: char* = None
arg0_kind: int = arg0.kind()
if arg0_kind == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(arg0)
if cn.const_kind == ast.CONST_STR: fmt_str = cn.str_val
if fmt_str is None: return
fmt_ptr: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, fmt_str, trans)
if fmt_ptr is None: return
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
# translate extra args and link them
prev: mlirlite.Value* = fmt_ptr
for ai in range(1, int(can)):
arg: ast.AST* = cargs.get(ai)
if arg is not None:
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans)
if arg_val is not None:
# pure_str value: printf consumes the payload, not the struct.
ps_pv: mlirlite.Value* = self.Gen.ExprGenBase._pure_str_payload(builder, pool, arg_val)
if ps_pv is not None: arg_val = ps_pv
# C ABI integer promotion: i8/i16 -> i32 (required by printf varargs).
# Pass the argument expression and trans so uint16/uint8 zext (movzwl) instead
# of the default sext: the source signedness comes from the AST annotation.
arg_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_val.Ty)
if arg_bits != 0 and arg_bits < 32: arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, i32_ty, arg, trans)
# C ABI float promotion: float -> double (required by printf varargs)
arg_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(arg_val.Ty)
if arg_fbits != 0 and arg_fbits < 64:
double_ty: mlirlite.LLVMType* = mlirlite.Double(pool)
arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, double_ty, arg)
arg_val.Next = None
prev.Next = arg_val
prev = arg_val
prev.Next = None
# is_variadic=1: emit a call with an explicit function type (e.g. call i32 (i8*, ...)),
# so llc treats it as variadic and generates correct Win64 ABI code.
mlirlite.build_call(builder, "printf", fmt_ptr, can, i32_ty, 1)
def translate_print_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> None:
"""Translate a print call (type-aware formatting + trailing newline)"""
cargs: list[ast.AST*]* = cl.args
if cargs is None: return
FMT_BUF_SIZE: sizet = 256
fmt_buf: bytes = stdlib.malloc(FMT_BUF_SIZE)
if fmt_buf is None: return
fmt_buf[0] = '\0'
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
dbl_ty: mlirlite.LLVMType* = mlirlite.Double(pool)
can: sizet = len(cargs)
arg_head: mlirlite.Value* = None
arg_tail: mlirlite.Value* = None
arg_count: int = 1 # the format string is printf arg 1
for ai in range(int(can)):
arg: ast.AST* = cargs.get(ai)
if arg is None: continue
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans)
if arg_val is None or arg_val.Ty is None:
# fail fast: a failed print-arg translation must abort, never write a
# misleading format string that hides a compiler bug as a data problem.
VLogger.error(f"print argument translation failed (expression returned None): [arg={ai}]", "PRINT")
self.Gen.TypeGenErr.fatal_error(arg, "print argument translation failed (expression returned None)", "TypeError")
return
# object defines __str__ -> print its i8* return via %s (TPC-aligned)
str_ov_pr: mlirlite.Value* = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, arg_val, "__str__", trans)
if str_ov_pr is not None: arg_val = str_ov_pr
# pure_str is a value struct {s, size}: print its payload as %s.
ps_pr: mlirlite.Value* = self.Gen.ExprGenBase._pure_str_payload(builder, pool, arg_val)
if ps_pr is not None:
arg_val = ps_pr
arg = None
# array name (e.g. print(l), l: t.ArrayT[char, N]): use the array alloca address
# per decay semantics -- char array -> %s, other element types -> 0x%llx.
array_ptr: mlirlite.Value* = None
array_elem_bits: int = 0
if arg is not None and arg.kind() == ast.ASTKind.Name and trans is not None:
anm: ast.Name* = ast.Name*(arg)
if anm.id is not None:
arr_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, anm.id)
if arr_alloca is not None and arr_alloca.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(arr_alloca.Ty) != 0:
arr_pointee: mlirlite.LLVMType* = arr_alloca.Ty.Pointee
if arr_pointee is not None:
match arr_pointee:
case mlirlite.LLVMType.Array(arr_et, arr_n):
array_ptr = arr_alloca
array_elem_bits = self.Gen.ExprGenBase.get_llvm_type_bits(arr_et)
case _:
pass
token: char* = None
chain_arg: int = 1 # 1 = this token consumes an arg, 0 = text only
arg_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_val.Ty)
arg_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(arg_val.Ty)
if array_ptr is not None:
# array decay: char array -> %s (bitcast [N x i8]* to i8*); else 0x%llx
if array_elem_bits == 8:
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
token = "%s"
arg_val = mlirlite.build_bitcast(builder, array_ptr, i8_ptr_ty)
else:
token = "0x%llx"
arg_val = mlirlite.build_ptrtoint(builder, array_ptr, i64_ty)
elif self.Gen.ExprGenBase.is_ptr_type(arg_val.Ty) != 0:
# i8* (str/bytes) -> %s; other pointers -> 0x%llx
pointee: mlirlite.LLVMType* = arg_val.Ty.Pointee
if pointee is not None and self.Gen.ExprGenBase.get_llvm_type_bits(pointee) == 8: token = "%s"
else:
token = "0x%llx"
arg_val = mlirlite.build_ptrtoint(builder, arg_val, i64_ty)
elif arg_bits != 0:
# Format/extension follow the source signedness: a uint16/uint8 printed with %d
# plus sext shows negative values (-9028 for 0xDCBC), so unsigned sources use
# %u/%llu and zext.
is_u_pr: int = 0
if arg is not None and trans is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, arg, trans) != 0: is_u_pr = 1
if arg_bits == 1:
token = "%d"
arg_val = mlirlite.build_zext(builder, arg_val, i32_ty)
elif arg_bits == 8:
if is_u_pr != 0:
token = "%u"
arg_val = mlirlite.build_zext(builder, arg_val, i32_ty)
else:
token = "%d"
arg_val = mlirlite.build_sext(builder, arg_val, i32_ty)
elif arg_bits == 16:
if is_u_pr != 0:
token = "%u"
arg_val = mlirlite.build_zext(builder, arg_val, i32_ty)
else:
token = "%d"
arg_val = mlirlite.build_sext(builder, arg_val, i32_ty)
elif arg_bits == 32: token = "%u" if is_u_pr != 0 else "%d"
else: token = "%llu" if is_u_pr != 0 else "%lld"
elif arg_fbits != 0:
token = "%f"
if arg_fbits < 64: arg_val = mlirlite.build_fpext(builder, arg_val, dbl_ty)
else:
# struct value / array / function -> "(unknown)", no arg passed
token = "(unknown)"
chain_arg = 0
# append the format token
if string.strlen(fmt_buf) < FMT_BUF_SIZE - 16: viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 16, "%s", token)
# link the argument
if chain_arg != 0:
arg_val.Next = None
if arg_tail is None: arg_head = arg_val
else: arg_tail.Next = arg_val
arg_tail = arg_val
arg_count += 1
# append the default newline (Python print end="\n")
if string.strlen(fmt_buf) < FMT_BUF_SIZE - 2: viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 2, "\n")
fmt_ptr: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, fmt_buf, trans)
stdlib.free(fmt_buf)
if fmt_ptr is None: return
fmt_ptr.Next = arg_head
# is_variadic=1: with an explicit function type (C ABI varargs call)
mlirlite.build_call(builder, "printf", fmt_ptr, arg_count, i32_ty, 1)
def init_func_table(self, pool: memhub.MemBuddy*, max_count: int) -> GenTypes.FuncEntry*:
"""Allocate and zero a func table array"""
size: sizet = max_count * GenTypes.FuncEntry.__sizeof__()
funcs_ptr: GenTypes.FuncEntry* = pool.alloc(size)
if funcs_ptr is not None: string.memset(funcs_ptr, 0, size)
return funcs_ptr
def find_func_in_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, owner_sha1: char* = None) -> mlirlite.Function*:
"""Look up a function by name in a func table, preferring this module's entry when owner_sha1 is set"""
if name is None or funcs is None: return None
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
# global func table + no owner: equivalent to "first same-name entry's Func", so use
# the name-index fast path; otherwise fall through to the linear scan below.
gfuncs_match: int = 0
if owner_sha1 is None and self.GlobalFuncs is not None:
if uint64(funcs) == uint64(self.GlobalFuncs) and func_count == self.GlobalFuncCount: gfuncs_match = 1
if gfuncs_match != 0 and self.GlobalFuncNameIdxBuilt == self.GlobalFuncCount and self.GlobalFuncNameIdx is not None and self.GlobalFuncNameIdxCap > 0:
fi_t: int = _gfn_idx_find(self.GlobalFuncs, self.GlobalFuncNameIdx, self.GlobalFuncNameIdxCap, name)
if fi_t >= 0:
fe_t: GenTypes.FuncEntry* = GenTypes.FuncEntry*((uint64(self.GlobalFuncs) + fi_t * entry_size))
if fe_t is not None: return fe_t.Func
# index trusted => a miss means "no such name"; the global table is append-only and
# never rewrites names. Otherwise fall back to the linear scan below.
return None
# Single pass: record the first same-bare-name entry as fallback; return this module's entry early.
fallback_entry: GenTypes.FuncEntry* = None
for i in range(int(func_count)):
entry_addr: uint64 = uint64(funcs) + i * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
if entry is not None and entry.Name is not None:
# fast path: equal names imply equal first chars, so skip strcmp when the
# first chars differ (avoids a function call per table slot).
if entry.Name[0] == name[0] and string.strcmp(entry.Name, name) == 0:
if fallback_entry is None: fallback_entry = entry
if entry.Func is not None and owner_sha1 is not None:
fn_nm_o: char* = mlirlite.function_get_name(entry.Func)
if fn_nm_o is not None:
if string.strncmp(fn_nm_o, owner_sha1, 16) == 0 and fn_nm_o[16] == '.': return entry.Func
if fallback_entry is not None: return fallback_entry.Func
return None
def add_func_to_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, func: mlirlite.Function*, max_count: int, defaults: rawptr = None, default_count: int = 0, param_count: int = 0, pool: memhub.MemBuddy* = None, def_sha1: char* = None) -> int:
"""Add a function to a func table; returns 0 on success"""
if name is None or func is None or funcs is None: return 1
if func_count >= max_count: return 1
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
entry_addr: uint64 = uint64(funcs) + func_count * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
entry.Name = name
entry.Func = func
entry.Defaults = defaults
entry.DefaultCount = default_count
entry.ParamCount = param_count
# [S3 dual-write] Spec is filled by update_global_func_spec after params are added
entry.Spec = None
# also register into the global func table (for cross-module vtable init lookup)
self._register_global_func(name, func, pool)
# also register into the global defaults table (to fill defaults on cross-module calls)
if default_count > 0: self._register_global_defaults(name, defaults, default_count, param_count, def_sha1)
return 0
def _init_global_func_table(self) -> int:
"""Initialize the global func table (stdlib.malloc, lives for the whole program)"""
if self.GlobalFuncs is not None: return 1
size: sizet = CallGenBase.GLOBAL_FUNC_MAX * GenTypes.FuncEntry.__sizeof__()
self.GlobalFuncs = CompilerFactory.factory.PermPool.alloc(size)
if self.GlobalFuncs is None: return 0
string.memset(self.GlobalFuncs, 0, size)
self.GlobalFuncCount = 0
# name index (PermPool, same lifetime as the table). On alloc failure keep it
# untrusted (Built=-1) and fall back to linear scans; init still succeeds.
idx_bytes: sizet = sizet(GLOBAL_FUNC_IDX_CAP) * 8
idx_buf: sizet* = CompilerFactory.factory.PermPool.alloc(idx_bytes)
if idx_buf is None:
self.GlobalFuncNameIdx = None
self.GlobalFuncNameIdxCap = 0
self.GlobalFuncNameIdxBuilt = -1
else:
string.memset(idx_buf, 0, idx_bytes)
self.GlobalFuncNameIdx = idx_buf
self.GlobalFuncNameIdxCap = GLOBAL_FUNC_IDX_CAP
self.GlobalFuncNameIdxBuilt = 0
return 1
def _global_func_registered(self, name: char*, func: mlirlite.Function*) -> int:
"""Whether this exact (name, Func) pair is already in the global table; return 1=yes"""
if name is None or func is None or self.GlobalFuncs is None: return 0
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
for i in range(int(self.GlobalFuncCount)):
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(self.GlobalFuncs) + uint64(i * entry_size))
if entry is None or entry.Name is None: continue
if entry.Name[0] != name[0] or string.strcmp(entry.Name, name) != 0: continue
if entry.Func is not None and uint64(entry.Func) == uint64(func): return 1
return 0
def _register_global_func(self, name: char*, func: mlirlite.Function*, pool: memhub.MemBuddy* = None) -> int:
"""Register a function in the global table, skipping duplicates; returns 0 on success"""
if name is None or func is None: return 1
if self._init_global_func_table() == 0: return 1
# Skip only a re-registration of the SAME function. Same-name entries with a different
# Func are overloads and must all stay in the table (the call site resolves by signature).
if self._global_func_registered(name, func) != 0: return 0
if self.GlobalFuncCount >= CallGenBase.GLOBAL_FUNC_MAX: return 1
# write directly into the global table (no add_func_to_table, to avoid recursion)
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
entry_addr: uint64 = uint64(self.GlobalFuncs) + self.GlobalFuncCount * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
# deep-copy Name into stdlib: the global table outlives files, and pool names would
# dangle after the per-file reset_to(mark1) (strcmp crash after find_func_global).
name_len: sizet = string.strlen(name)
name_copy: char* = CompilerFactory.factory.PermPool.alloc(name_len + 1)
if name_copy is None: return 1
string.strcpy(name_copy, name)
entry.Name = name_copy
entry.Func = func
# [S3 dual-write] Spec is filled later by update_global_func_spec (params not added yet)
entry.Spec = None
# Spec is not built here: params are added after add_func_to_table, so the caller
# must call update_global_func_spec to complete it.
self.GlobalFuncCount += 1
# sync the name index (slot = new entry index) only if it was trusted before the
# insert (Built==Count-1) and the insert succeeded; otherwise mark untrusted.
if self.GlobalFuncNameIdxBuilt == self.GlobalFuncCount - 1 and _gfn_idx_insert(self.GlobalFuncNameIdx, self.GlobalFuncNameIdxCap, self.GlobalFuncCount - 1, name_copy) == 0:
self.GlobalFuncNameIdxBuilt = self.GlobalFuncCount
else: self.GlobalFuncNameIdxBuilt = -1
return 0
def update_global_func_spec(self, name: char*, func: mlirlite.Function*, ret_sign: int = 0) -> int:
"""Update a global table entry's Spec after all its params are added.
ret_sign: GenTypes.FUNC_RET_SIGN_* of the declared return annotation (0 = keep the
previously recorded value). Needed because Spec/Ret comes from the LLVM type, which
cannot express unsignedness.
"""
if name is None or func is None: return 1
# 重载:同名多条时 Spec 必须写到**这个 Function 对应**的条目上,否则第二个重载会
# 覆盖第一个的签名,决议时所有候选看起来都是同一个签名。
entry: GenTypes.FuncEntry* = None
if self.GlobalFuncs is not None:
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
for i in range(int(self.GlobalFuncCount)):
cur: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(self.GlobalFuncs) + uint64(i * entry_size))
if cur is None or cur.Name is None or cur.Func is None: continue
if string.strcmp(cur.Name, name) != 0: continue
if uint64(cur.Func) != uint64(func): continue
entry = cur
break
if entry is None: entry = self.find_func_entry_global(name)
if entry is None: return 1
entry.Spec = self._build_func_spec(func)
if ret_sign != 0: entry.RetSign = ret_sign
return 0
# _build_func_spec: build the full-signature TypeSpec (Kind=TS_FUNC) from a function after all
# params are added; unlike SpecFromLLVMType's Func branch it carries func.IsVarArg.
def _build_func_spec(self, func: mlirlite.Function*) -> TSpec.TypeSpec*:
"""Build the full-signature TypeSpec from a function; returns None on failure"""
if func is None: return None
arena: TSpec.TypeSpecArena* = self.Gen.TypeGenBase.ensure_spec_arena()
if arena is None: return None
ret_s: TSpec.TypeSpec* = TSpec.SpecFromLLVMType(arena, mlirlite.function_get_ret_ty(func))
if ret_s is None: return None
# pass 1: count
cnt: int = 0
cur: mlirlite.Param* = mlirlite.function_get_param_head(func)
while cur is not None and cnt < 256:
cnt += 1
cur = mlirlite.param_get_next(cur)
# pass 2: build each Spec and write into a contiguous param array
params_arr: sizet* = None
if cnt > 0:
params_arr = CompilerFactory.factory.PermPool.alloc(8 * cnt)
if params_arr is None: return None
cur = mlirlite.function_get_param_head(func)
i: int = 0
while cur is not None and i < cnt:
ps: TSpec.TypeSpec* = TSpec.SpecFromLLVMType(arena, mlirlite.param_get_ty(cur))
if ps is None: return None
slot: sizet* = (sizet*)(uint64(params_arr) + uint64(i * 8))
if slot is None: return None
slot[0] = sizet(ps)
cur = mlirlite.param_get_next(cur)
i += 1
return TSpec.NewTypeFunc(arena, ret_s, params_arr, cnt, func.IsVarArg)
# ============================================================
# 重载决议(C++ 风格:精确 > 提升/拓宽 > 转换 > 省略号;返回值不参与)
# 规则:候选集 = 同名且属于本模块(符号名以 {模块sha1}. 开头)的函数表项;
# 唯一"不劣于所有其它且至少一个实参更优"的候选胜出,否则判歧义 fail-fast;
# 任一实参类型无法静态确定时,候选 >=2 直接 fail-fast(必须显式强转)。
# ============================================================
# _overload_name_matches - 候选名匹配:exact 模式要求同名;prefix 模式接受
# "键" 本身或 "键_v{hash}"(方法重载:类内同名方法的表键带签名后缀)。
def _overload_name_matches(self, entry_name: char*, key: char*, prefix_mode: int) -> int:
"""函数表项名是否属于该重载组;1=是"""
if entry_name is None or key is None: return 0
if prefix_mode == 0:
if string.strcmp(entry_name, key) == 0: return 1
return 0
kn: sizet = string.strlen(key)
if string.strncmp(entry_name, key, kn) != 0: return 0
if entry_name[kn] == '\0': return 1
if entry_name[kn] == '_' and entry_name[kn + 1] == 'v': return 1
return 0
# count_owned_overloads - 统计某名字在本模块下的函数表项数
def count_owned_overloads(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, owner_sha1: char*, prefix_mode: int = 0) -> int:
"""同名且符号属于 owner_sha1 模块的函数表项个数"""
if funcs is None or name is None: return 0
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
cnt: int = 0
for i in range(int(func_count)):
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(funcs) + uint64(i * entry_size))
if entry is None or entry.Name is None or entry.Func is None: continue
if self._overload_name_matches(entry.Name, name, prefix_mode) == 0: continue
if owner_sha1 is not None:
fn_nm: char* = mlirlite.function_get_name(entry.Func)
if fn_nm is None: continue
if string.strncmp(fn_nm, owner_sha1, 16) != 0: continue
cnt += 1
if cnt >= OVERLOAD_CAND_MAX: break
return cnt
# _overload_arg_spec - 实参的静态类型(不生成 IR);无法确定时返回 None
def _overload_arg_spec(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> TSpec.TypeSpec*:
"""尽力而为的实参类型推导;None = 无法静态确定"""
if node is None or pool is None: return None
arena: TSpec.TypeSpecArena* = self.Gen.TypeGenBase.ensure_spec_arena()
if arena is None: return None
k: int = node.kind()
if k == ast.ASTKind.Constant:
c: ast.Constant* = ast.Constant*(node)
if c is None: return None
if c.const_kind == ast.CONST_INT:
bits: int = int(c.int_width)
if bits <= 0: bits = 32
if c.int_unsigned != 0: return TSpec.NewTypeSpec(arena, TSpec.TS_UINT, bits, TSpec.TS_SIGN_UNSIGNED)
return TSpec.NewTypeSpec(arena, TSpec.TS_INT, bits, TSpec.TS_SIGN_SIGNED)
if c.const_kind == ast.CONST_BOOL: return TSpec.NewTypeSpec(arena, TSpec.TS_BOOL, 1, TSpec.TS_SIGN_UNSIGNED)
if c.const_kind == ast.CONST_FLOAT: return TSpec.NewTypeSpec(arena, TSpec.TS_FLOAT, 64, TSpec.TS_SIGN_SIGNED)
if c.const_kind == ast.CONST_CHAR: return TSpec.NewTypeSpec(arena, TSpec.TS_INT, 8, TSpec.TS_SIGN_SIGNED)
if c.const_kind == ast.CONST_STR:
i8_s: TSpec.TypeSpec* = TSpec.NewTypeSpec(arena, TSpec.TS_INT, 8, TSpec.TS_SIGN_SIGNED)
if i8_s is None: return None
return TSpec.NewTypePtr(arena, i8_s)
# None 及其它常量:类型不可定
return None
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm is None or nm.id is None or trans is None or trans.SymTab is None: return None
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if ve is None or ve.Spec is None: return None
if ve.Spec.Kind == TSpec.TS_UNKNOWN: return None
return ve.Spec
if k == ast.ASTKind.UnaryOp:
uo: ast.UnaryOp* = ast.UnaryOp*(node)
if uo is None or uo.operand is None: return None
inner_u: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, uo.operand)
if inner_u is None: return None
if uo.op == ast.OpKind.AddrOf:
# &x -> 指针(PtrLevel+1);数组取地址退化为元素指针(与实参翻译的数组衰变一致)
if inner_u.Kind == TSpec.TS_ARRAY:
if inner_u.Elem is None: return None
return TSpec.NewTypePtr(arena, inner_u.Elem)
return TSpec.NewTypePtr(arena, inner_u)
if uo.op == ast.OpKind.Deref:
if inner_u.Kind == TSpec.TS_PTR: return inner_u.Elem
if inner_u.Kind == TSpec.TS_ARRAY: return inner_u.Elem
return None
return None
if k == ast.ASTKind.Subscript:
sb: ast.Subscript* = ast.Subscript*(node)
if sb is None or sb.value is None: return None
base_s: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, sb.value)
if base_s is None: return None
if base_s.Kind == TSpec.TS_PTR: return base_s.Elem
if base_s.Kind == TSpec.TS_ARRAY: return base_s.Elem
return None
if k == ast.ASTKind.Attribute:
# 字段访问:按接收者的类名找 StructEntry,再按字段名取 FieldEntry.Spec
# (字段 Spec 在 add_field 时已用注解符号性修正,uint 字段不会退化成 i32)。
at: ast.Attribute* = ast.Attribute*(node)
if at is None or at.value is None or at.attr is None or trans is None: return None
cls_nm: char* = self.Gen.ExprGenOps._attr_receiver_class_name(pool, at.value, trans)
if cls_nm is None: return None
bare: char* = cls_nm
dot_a: char* = self.Gen.StructGen._last_dot_outside_brackets(cls_nm)
if dot_a is not None: bare = dot_a + 1
if bare is None: return None
st_a: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(bare)
if st_a is None: return None
for fi_a in range(int(st_a.FieldCount)):
fe_a: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(st_a, fi_a)
if fe_a is None or fe_a.Name is None: continue
if string.strcmp(fe_a.Name, at.attr) != 0: continue
return fe_a.Spec
return None
if k == ast.ASTKind.Call:
# 函数调用结果:只处理裸名调用(模块级函数);返回值取被调者 Spec.Ret。
# 被调者本身同名重载时无法在此再决议(避免递归),按"未知"处理。
cl_r: ast.Call* = ast.Call*(node)
if cl_r is None or cl_r.func is None: return None
if cl_r.func.kind() != ast.ASTKind.Name: return None
fn_nm: ast.Name* = ast.Name*(cl_r.func)
if fn_nm is None or fn_nm.id is None or trans is None: return None
if trans._funcs is None: return None
if self.count_owned_overloads(trans._funcs, trans._func_count, fn_nm.id, trans.ModuleSha1) >= 2: return None
e_r: GenTypes.FuncEntry* = self.find_func_entry_in_table_owned(trans._funcs, trans._func_count, fn_nm.id, trans.ModuleSha1)
if e_r is None or e_r.Func is None: return None
if e_r.Spec is None: e_r.Spec = self._build_func_spec(e_r.Func)
if e_r.Spec is None or e_r.Spec.Ret is None: return None
return e_r.Spec.Ret
if k == ast.ASTKind.Compare:
# 比较结果恒为 bool
return TSpec.NewTypeSpec(arena, TSpec.TS_BOOL, 1, TSpec.TS_SIGN_UNSIGNED)
if k == ast.ASTKind.BinOp:
bo: ast.BinOp* = ast.BinOp*(node)
if bo is None or bo.left is None or bo.right is None: return None
if bo.op == ast.OpKind.And or bo.op == ast.OpKind.Or:
# 布尔逻辑:结果与左值同型(两边同型是语言保证)
return self._overload_arg_spec(pool, trans, bo.left)
ls: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, bo.left)
rs: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, bo.right)
if ls is None or rs is None: return None
# 字符串拼接(两边都是指针)按左值类型
if ls.Kind == TSpec.TS_PTR and rs.Kind == TSpec.TS_PTR: return ls
if ls.Kind == TSpec.TS_FLOAT or rs.Kind == TSpec.TS_FLOAT:
if ls.Kind == TSpec.TS_FLOAT and rs.Kind == TSpec.TS_FLOAT:
if ls.Bits >= rs.Bits: return ls
return rs
# 混合整浮:按 Viper 缺省浮点宽度 f64
return TSpec.NewTypeSpec(arena, TSpec.TS_FLOAT, 64, TSpec.TS_SIGN_SIGNED)
if ls.Kind == TSpec.TS_INT or ls.Kind == TSpec.TS_UINT:
if rs.Kind != TSpec.TS_INT and rs.Kind != TSpec.TS_UINT: return None
if ls.Bits > rs.Bits: return ls
if rs.Bits > ls.Bits: return rs
# 同宽:无符号优先(C++ 常规算术转换)
if ls.Kind == TSpec.TS_UINT: return ls
if rs.Kind == TSpec.TS_UINT: return rs
return ls
return None
return None
# _overload_rank - 实参类型对形参类型的档位;-1 = 不可行
# 0 精确 / 1 提升与拓宽 / 2 转换(同宽符号性变化、任意指针→void*)/ 3 省略号
def _overload_rank(self, arg: TSpec.TypeSpec*, param: TSpec.TypeSpec*) -> int:
"""实参→形参的匹配档位;-1 表示该候选不可行"""
if arg is None or param is None: return -1
if param.Kind == TSpec.TS_PTR:
if arg.Kind != TSpec.TS_PTR: return -1
if arg.PtrLevel != param.PtrLevel: return -1
inner: int = self._overload_rank(arg.Elem, param.Elem)
if inner == 0: return 0
# void*(本编译器中 void* 与 i8* 同一表示):任意同级指针可转换
if param.Elem is not None:
if param.Elem.Kind == TSpec.TS_INT and param.Elem.Bits == 8 and param.PtrLevel == 1: return 2
if inner == 1: return 1
return -1
if param.Kind == TSpec.TS_INT or param.Kind == TSpec.TS_UINT:
if arg.Kind == TSpec.TS_INT or arg.Kind == TSpec.TS_UINT:
if arg.Kind == param.Kind and arg.Bits == param.Bits: return 0
if arg.Bits == param.Bits: return 2 # 同宽符号性变化(有符号↔无符号)
if param.Bits > arg.Bits: return 1 # 拓宽
return -1 # 收窄不允许
if arg.Kind == TSpec.TS_BOOL: return 1
return -1 # 浮点→整数有损,禁止
if param.Kind == TSpec.TS_FLOAT:
if arg.Kind == TSpec.TS_FLOAT:
if arg.Bits == param.Bits: return 0
if param.Bits > arg.Bits: return 1
return -1
if arg.Kind == TSpec.TS_INT or arg.Kind == TSpec.TS_UINT:
if param.Bits >= arg.Bits: return 1 # 整→浮 需浮点位宽 >= 整数位宽(无损)
return -1
return -1
if param.Kind == TSpec.TS_BOOL:
if arg.Kind == TSpec.TS_BOOL: return 0
return -1
if param.Kind == TSpec.TS_STRUCT:
if arg.Kind != TSpec.TS_STRUCT: return -1
if param.BaseName is None or arg.BaseName is None: return -1
if string.strcmp(param.BaseName, arg.BaseName) == 0: return 0
return -1 # 类层次上转型:本轮不支持
return -1
# _overload_better - a 是否严格优于 b(逐实参不劣且至少一个更优)
def _overload_better(self, ma: int*, mb: int*, argc: int) -> int:
"""a 的部分序严格优于 b 时返回 1"""
if ma is None or mb is None: return 0
better: int = 0
for i in range(argc):
if ma[i] > mb[i]: return 0
if ma[i] < mb[i]: better = 1
return better
# resolve_overload_call - 在候选集中决议唯一的重载;歧义/无法定型时 fail-fast
def resolve_overload_call(self, pool: memhub.MemBuddy*, trans: HT.Translator*, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, owner_sha1: char*, cargs: list[ast.AST*]*, can: sizet, call_node: ast.AST*, prefix_mode: int = 0, param_base: int = 0, arg_start: int = 0) -> mlirlite.Function*:
"""按实参静态类型选择唯一最优重载;返回 None 表示已报错。
prefix_mode=1 用于方法(表键 "Class.m" / "Class.m_v{hash}");
param_base=1 表示候选签名第 0 个形参是 self/cls,需跳过;
arg_start 表示 cargs 前若干个是预置实参(obj/cls),不参与匹配。"""
if funcs is None or name is None: return None
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
argv_n: int = int(can)
if argv_n > OVERLOAD_ARG_MAX:
self.Gen.TypeGenErr.fatal_error(call_node, f"overloaded call '{name}' has too many arguments ({argv_n} > {OVERLOAD_ARG_MAX})", "TypeError")
return None
cand: sizet* = stdlib.malloc(8 * OVERLOAD_CAND_MAX)
if cand is None: return None
n: int = 0
for i in range(int(func_count)):
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(funcs) + uint64(i * entry_size))
if entry is None or entry.Name is None or entry.Func is None: continue
if self._overload_name_matches(entry.Name, name, prefix_mode) == 0: continue
if owner_sha1 is not None:
fn_nm_c: char* = mlirlite.function_get_name(entry.Func)
if fn_nm_c is None: continue
if string.strncmp(fn_nm_c, owner_sha1, 16) != 0: continue
if n < OVERLOAD_CAND_MAX:
cand[n] = sizet(entry)
n += 1
if n == 0:
stdlib.free(cand)
return None
if n == 1:
only: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(cand[0]))
stdlib.free(cand)
if only is None: return None
return only.Func
# 实参类型:任一无法确定 -> 候选 >=2 时 fail-fast(必须显式强转)
argspec: sizet* = stdlib.malloc(8 * argv_n)
if argspec is None:
stdlib.free(cand)
return None
for ai in range(argv_n):
node_a: ast.AST* = None
if cargs is not None: node_a = cargs.get(arg_start + ai)
argspec[ai] = sizet(self._overload_arg_spec(pool, trans, node_a))
for ai in range(argv_n):
if argspec[ai] == 0:
arg_bad: ast.AST* = None
if cargs is not None: arg_bad = cargs.get(arg_start + ai)
if arg_bad is None: arg_bad = call_node
stdlib.free(argspec)
stdlib.free(cand)
self.Gen.TypeGenErr.fatal_error(arg_bad, f"cannot resolve overload '{name}': argument {ai + 1} type is unknown; add an explicit cast", "TypeError")
return None
# 档位矩阵 + 可行候选
mat: int* = stdlib.malloc(4 * n * argv_n)
ok: int* = stdlib.malloc(4 * n)
if mat is None or ok is None:
if mat is not None: stdlib.free(mat)
if ok is not None: stdlib.free(ok)
stdlib.free(argspec)
stdlib.free(cand)
return None
viable: int = 0
for ci in range(n):
e: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(cand[ci]))
ok[ci] = 0
if e is None or e.Func is None: continue
# 模块函数表项从不填 Spec(只有全局表由 update_global_func_spec 填写),
# 这里按需构建并缓存,避免决议时所有候选看起来都"无签名"。
if e.Spec is None: e.Spec = self._build_func_spec(e.Func)
if e.Spec is None: continue
pc: int = e.ParamCount
dc: int = e.DefaultCount
vararg: int = 0
if e.Spec.IsVarArg == 1: vararg = 1
if argv_n > pc and vararg == 0: continue
if argv_n < pc - dc: continue
good: int = 1
for ai in range(argv_n):
r: int = 0
if ai >= pc:
if vararg == 0:
good = 0
break
r = 3
else:
p: TSpec.TypeSpec* = TSpec.SpecParamAt(e.Spec, param_base + ai)
r = self._overload_rank((TSpec.TypeSpec*)(uint64(argspec[ai])), p)
if r < 0:
good = 0
break
mat[ci * argv_n + ai] = r
if good != 0:
ok[ci] = 1
viable += 1
stdlib.free(argspec)
if viable == 0:
stdlib.free(mat)
stdlib.free(ok)
stdlib.free(cand)
self.Gen.TypeGenErr.fatal_error(call_node, f"no overload of '{name}' matches the argument types", "TypeError")
return None
best: int = -1
ambiguous: int = 0
for ci in range(n):
if ok[ci] == 0: continue
dom: int = 1
for cj in range(n):
if cj == ci or ok[cj] == 0: continue
ma: int* = (int*)(uint64(mat) + uint64(ci * argv_n * 4))
mb: int* = (int*)(uint64(mat) + uint64(cj * argv_n * 4))
if self._overload_better(ma, mb, argv_n) == 0:
dom = 0
break
if dom != 0:
if best >= 0: ambiguous = 1
else: best = ci
if best < 0 or ambiguous != 0:
stdlib.free(mat)
stdlib.free(ok)
stdlib.free(cand)
self.Gen.TypeGenErr.fatal_error(call_node, f"ambiguous call to overloaded function '{name}': no unique best match", "TypeError")
return None
pick: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(cand[best]))
stdlib.free(mat)
stdlib.free(ok)
stdlib.free(cand)
if pick is None: return None
return pick.Func
def find_func_global(self, name: char*) -> mlirlite.Function*:
"""Look up a name in the global func table; returns a Function pointer or None"""
if name is None or self.GlobalFuncs is None: return None
return self.find_func_in_table(self.GlobalFuncs, self.GlobalFuncCount, name)
def find_func_entry_global(self, name: char*) -> GenTypes.FuncEntry*:
"""Look up a name in the global func table; returns the full FuncEntry (with Spec) or None"""
if name is None or self.GlobalFuncs is None: return None
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
# name-index fast path: when trusted, one probe returns the first same-name entry;
# a miss means none exists. If untrusted, fall back to the linear scan below.
if self.GlobalFuncNameIdxBuilt == self.GlobalFuncCount and self.GlobalFuncNameIdx is not None and self.GlobalFuncNameIdxCap > 0:
gi: int = _gfn_idx_find(self.GlobalFuncs, self.GlobalFuncNameIdx, self.GlobalFuncNameIdxCap, name)
if gi >= 0:
entry_g: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(self.GlobalFuncs) + gi * entry_size)
return entry_g
return None
for i in range(int(self.GlobalFuncCount)):
entry_addr: uint64 = uint64(self.GlobalFuncs) + i * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
if entry.Name is not None:
# fast path: skip strcmp when the first chars differ (equal names imply
# equal first chars).
if entry.Name[0] == name[0] and string.strcmp(entry.Name, name) == 0: return entry
return None
def _init_global_defaults_table(self) -> int:
"""Initialize the global default-argument table"""
if self.GlobalDefaults is not None: return 1
size: sizet = CallGenBase.GLOBAL_DEFAULTS_MAX * GenTypes.FuncDefaultsEntry.__sizeof__()
self.GlobalDefaults = CompilerFactory.factory.PermPool.alloc(size)
if self.GlobalDefaults is None: return 0
string.memset(self.GlobalDefaults, 0, size)
self.GlobalDefaultsCount = 0
return 1
def _register_global_defaults(self, name: char*, defaults: rawptr, default_count: int, param_count: int, def_sha1: char* = None) -> int:
"""Register a function's default arguments in the global table; returns 0 on success"""
if name is None or defaults is None: return 1
if self._init_global_defaults_table() == 0: return 1
# qualified key = the defining module's SHA1: one entry per (name, module) removes
# cross-module same-name collisions. def_sha1 comes from the injection path.
cur_sha: char* = def_sha1
if cur_sha is None and self.Gen is not None and self.Gen.TypeGenBase is not None: cur_sha = self.Gen.TypeGenBase.CurrentModuleSha1
# skip if the same name already exists for this module
if self.find_func_defaults(name, cur_sha) is not None: return 0
if self.GlobalDefaultsCount >= CallGenBase.GLOBAL_DEFAULTS_MAX: return 1
entry_size: sizet = GenTypes.FuncDefaultsEntry.__sizeof__()
entry_addr: uint64 = uint64(self.GlobalDefaults) + self.GlobalDefaultsCount * entry_size
entry: GenTypes.FuncDefaultsEntry* = GenTypes.FuncDefaultsEntry*(entry_addr)
# deep-copy Name into stdlib (the global table outlives files)
nm_len: sizet = string.strlen(name)
nm_copy: char* = CompilerFactory.factory.PermPool.alloc(nm_len + 1)
if nm_copy is None: return 1
string.strcpy(nm_copy, name)
entry.Name = nm_copy
entry.ModuleSha1 = None
if cur_sha is not None:
sh_len: sizet = string.strlen(cur_sha)
sh_copy: char* = CompilerFactory.factory.PermPool.alloc(sh_len + 1)
if sh_copy is not None:
string.strcpy(sh_copy, cur_sha)
entry.ModuleSha1 = sh_copy
entry.Defaults = defaults
entry.DefaultCount = default_count
entry.ParamCount = param_count
# range guard: on an abnormal default_count (corrupt vpi tree) skip snapshot
# extraction to avoid a huge alloc / out-of-bounds write into perm_pool.
if default_count > 128:
entry.Defaults = None
entry.DefaultCount = 0
return 0
# extract the constant snapshot into stdlib
if default_count > 0:
kinds: int* = CompilerFactory.factory.PermPool.alloc(default_count * 4)
ints: int64* = CompilerFactory.factory.PermPool.alloc(default_count * 8)
strs: sizet* = CompilerFactory.factory.PermPool.alloc(default_count * 8)
if kinds is not None and ints is not None and strs is not None:
string.memset(kinds, 0, default_count * 4)
string.memset(ints, 0, default_count * 8)
string.memset(strs, 0, default_count * 8)
dl: list[ast.AST*]* = list[ast.AST*]*(uint64(defaults))
di: int
for di in range(int(default_count)):
dnode: ast.AST* = dl.get(di)
if dnode is not None and dnode.kind() == ast.ASTKind.Constant:
dcn: ast.Constant* = ast.Constant*(dnode)
kinds[di] = dcn.const_kind
ints[di] = dcn.int_val
if dcn.const_kind == ast.CONST_STR and dcn.str_val is not None:
slen: sizet = string.strlen(dcn.str_val)
scopy: char* = CompilerFactory.factory.PermPool.alloc(slen + 1)
if scopy is not None:
string.strcpy(scopy, dcn.str_val)
strs[di] = sizet(scopy)
elif dnode is not None and dnode.kind() == ast.ASTKind.Name:
# DefineT-valued default (e.g. share: ULONG = SHARE_READ): evaluate the
# DefineT at registration; only registered int CDefines are supported.
dnm: ast.Name* = ast.Name*(dnode)
if dnm.id is not None:
cdef_v: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(dnm.id)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
kinds[di] = ast.CONST_INT
ints[di] = cdef_v
elif dnode is not None and dnode.kind() == ast.ASTKind.UnaryOp:
# unary-literal default (e.g. ex3: int = -1): AST is UnaryOp over a
# Constant; evaluate only when the operand is an int Constant.
du: ast.UnaryOp* = ast.UnaryOp*(dnode)
if du.operand is not None and du.operand.kind() == ast.ASTKind.Constant:
doc: ast.Constant* = ast.Constant*(du.operand)
if doc.const_kind == ast.CONST_INT:
if du.op == ast.OpKind.USub:
kinds[di] = ast.CONST_INT
ints[di] = -doc.int_val
elif du.op == ast.OpKind.UAdd:
kinds[di] = ast.CONST_INT
ints[di] = doc.int_val
elif du.op == ast.OpKind.Invert:
kinds[di] = ast.CONST_INT
ints[di] = ~doc.int_val
entry.ConstKinds = kinds
entry.ConstInts = ints
entry.ConstStrs = strs
self.GlobalDefaultsCount += 1
return 0
def register_global_defaults_literals(self, name: char*, kinds: int*, ints: int64*, strs: sizet*, count: int, param_count: int, def_sha1: char*) -> int:
"""Register default args already parsed into a (kinds, ints, strs) snapshot; 0=ok
Used by the .vpi pre-scan (VpiReader.PreRegisterVpiDefaults), which has literal values
but no AST defaults list. Semantics match _register_global_defaults: one entry per
(name, defining-module SHA1), values deep-copied into PermPool so they outlive the file.
"""
if name is None or kinds is None or ints is None: return 1
if count <= 0 or count > 128: return 1
if self._init_global_defaults_table() == 0: return 1
if self.find_func_defaults(name, def_sha1) is not None: return 0
if self.GlobalDefaultsCount >= CallGenBase.GLOBAL_DEFAULTS_MAX: return 1
entry_size: sizet = GenTypes.FuncDefaultsEntry.__sizeof__()
entry_addr: uint64 = uint64(self.GlobalDefaults) + sizet(self.GlobalDefaultsCount) * entry_size
entry: GenTypes.FuncDefaultsEntry* = GenTypes.FuncDefaultsEntry*(entry_addr)
nm_len: sizet = string.strlen(name)
nm_copy: char* = CompilerFactory.factory.PermPool.alloc(nm_len + 1)
if nm_copy is None: return 1
string.strcpy(nm_copy, name)
entry.Name = nm_copy
entry.ModuleSha1 = None
if def_sha1 is not None:
sh_len: sizet = string.strlen(def_sha1)
sh_copy: char* = CompilerFactory.factory.PermPool.alloc(sh_len + 1)
if sh_copy is not None:
string.strcpy(sh_copy, def_sha1)
entry.ModuleSha1 = sh_copy
# Defaults AST list stays None: cross-module callers rebuild from the snapshot only.
entry.Defaults = None
entry.DefaultCount = count
entry.ParamCount = param_count
k_copy: int* = CompilerFactory.factory.PermPool.alloc(sizet(count) * 4)
i_copy: int64* = CompilerFactory.factory.PermPool.alloc(sizet(count) * 8)
s_copy: sizet* = CompilerFactory.factory.PermPool.alloc(sizet(count) * 8)
if k_copy is None or i_copy is None or s_copy is None: return 1
string.memset(k_copy, 0, sizet(count) * 4)
string.memset(i_copy, 0, sizet(count) * 8)
string.memset(s_copy, 0, sizet(count) * 8)
di: int
for di in range(int(count)):
k_copy[di] = kinds[di]
i_copy[di] = ints[di]
if strs is not None: s_copy[di] = strs[di]
entry.ConstKinds = k_copy
entry.ConstInts = i_copy
entry.ConstStrs = s_copy
self.GlobalDefaultsCount += 1
return 0
def find_func_defaults(self, name: char*, module_sha1: char* = None) -> GenTypes.FuncDefaultsEntry*:
"""Find default-argument info by name and optional defining-module SHA1, or None"""
if name is None or self.GlobalDefaults is None: return None
entry_size: sizet = GenTypes.FuncDefaultsEntry.__sizeof__()
for i in range(int(self.GlobalDefaultsCount)):
entry_addr: uint64 = uint64(self.GlobalDefaults) + i * entry_size
entry: GenTypes.FuncDefaultsEntry* = GenTypes.FuncDefaultsEntry*(entry_addr)
if entry.Name is None: continue
if string.strcmp(entry.Name, name) != 0: continue
if module_sha1 is not None:
if entry.ModuleSha1 is None: continue
if string.strcmp(entry.ModuleSha1, module_sha1) != 0: continue
return entry
return None
def _build_const_from_snapshot(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, gd_entry: GenTypes.FuncDefaultsEntry*, di: int, trans: HT.Translator*, builder: mlirlite.IRBuilder*) -> mlirlite.Value*:
"""Rebuild the di-th default-value constant IR from the snapshot"""
if gd_entry is None or gd_entry.ConstKinds is None or gd_entry.ConstInts is None: return None
if di < 0 or di >= gd_entry.DefaultCount: return None
kind: int = gd_entry.ConstKinds[di]
ival: int64 = gd_entry.ConstInts[di]
if kind == ast.CONST_INT:
if ival > 2147483647 or ival < -2147483648: return mlirlite.const_int64(pool, ival)
return mlirlite.const_int32(pool, ival)
elif kind == ast.CONST_BOOL:
if ival != 0: return mlirlite.const_int32(pool, 1)
return mlirlite.const_int32(pool, 0)
elif kind == ast.CONST_NONE:
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
return mlirlite.ConstNull(pool, i8_ptr_ty, "null")
elif kind == ast.CONST_STR:
if gd_entry.ConstStrs is not None and gd_entry.ConstStrs[di] != 0:
sv: char* = rawptr(gd_entry.ConstStrs[di])
return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, sv, trans)
# return None for unreconstructable values (CONST_FLOAT / complex expr); caller falls back to AST
return None
def find_func_entry_in_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*) -> GenTypes.FuncEntry*:
"""Look up a name in a func table; returns the full FuncEntry or None"""
if name is None or funcs is None: return None
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
for i in range(int(func_count)):
entry_addr: uint64 = uint64(funcs) + i * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
if entry.Name is not None:
# fast path: skip strcmp when the first chars differ (equal names imply equal first chars)
if entry.Name[0] == name[0] and string.strcmp(entry.Name, name) == 0: return entry
return None
def find_func_entry_in_table_owned(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, owner_sha1: char*) -> GenTypes.FuncEntry*:
"""Find a func table entry by bare name and defining-module SHA1, or None"""
if name is None or funcs is None: return None
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
for i in range(int(func_count)):
entry_addr: uint64 = uint64(funcs) + i * entry_size
entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(entry_addr)
if entry is None or entry.Name is None: continue
# fast path: skip strcmp when the first chars differ (hot: this compares the
# whole table by bare name + defining-module SHA1).
if entry.Name[0] != name[0] or string.strcmp(entry.Name, name) != 0: continue
if owner_sha1 is None: return entry
if entry.Func is not None:
fn_nm_o: char* = mlirlite.function_get_name(entry.Func)
if fn_nm_o is not None:
if string.strncmp(fn_nm_o, owner_sha1, 16) == 0 and fn_nm_o[16] == '.': return entry
return None
def NewExprCallHandle(self, pool: memhub.MemBuddy*, trans: HT.Translator*) -> ExprCallHandle*:
h: ExprCallHandle* = pool.alloc(ExprCallHandle.__sizeof__())
if h is None: return None
string.memset(h, 0, ExprCallHandle.__sizeof__())
h.Trans = trans
return h
# ============================================================
# ExprCallHandle - function-call handler (moved from HandlesExprCall); delegates to GenApi.
novirtual class ExprCallHandle(HT.Mixin):
"""Function-call handler: inherits the mixin to get the Trans back-pointer"""
def __init__(self, trans: HT.Translator*):
self.Trans = trans
# ============================================================
# HandleCall - translate a function call, returning the call result Value
def HandleCall(self, node: ast.AST*) -> mlirlite.Value*:
"""Translate a function call using shared state from self.Trans"""
return GenApi.translate_call(self.Trans.Pool, self.Trans._cur_builder, self.Trans.Module, node, self.Trans._funcs, self.Trans._func_count, self.Trans)
# ============================================================
# HandlePrintfCall - translate a printf call
def HandlePrintfCall(self, cl: ast.Call*):
"""Translate a printf call using shared state from self.Trans"""
GenApi.translate_printf_call(self.Trans.Pool, self.Trans._cur_builder, self.Trans.Module, cl, self.Trans)
# ============================================================
# HandlePrintCall - translate a print call
def HandlePrintCall(self, cl: ast.Call*):
"""Translate a print call (type-aware formatting + trailing newline)"""
GenApi.translate_print_call(self.Trans.Pool, self.Trans._cur_builder, self.Trans.Module, cl, self.Trans)