1653 lines
91 KiB
Plaintext
1653 lines
91 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: str) -> 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: str) -> 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: str) -> 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: str = 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: str, 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: str = 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: str = 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: str = 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: str = 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: str) -> 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: str = 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: str = 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: str = 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: str, 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: str = pool.alloc(256)
|
|
if buf is None: return None
|
|
viperlib.snprintf(buf, 256, "%s.vtable", class_name)
|
|
vn: str = rawptr(buf)
|
|
sha1: str = 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: str = self.Gen.FuncGenBase._mangle_name_with_sha1(sha1, vn)
|
|
ref_name: str = 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: str) -> 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: str = 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: str = 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: str) -> 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: str) -> 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: str, ret_ty: mlirlite.LLVMType*, reason: str) -> None:
|
|
"""Fail compilation when a function-pointer signature registration cannot be honored"""
|
|
cap: int = CallGenBase.MAX_FUNC_PTR_SIGS
|
|
ret_str: str = "?"
|
|
if ret_ty is not None:
|
|
typed_buf: str = 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: str = 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: str, 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: str) -> 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: str, 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: str = 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)
|
|
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)
|
|
# 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: str = 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:
|
|
if arg_bits == 1:
|
|
token = "%d"
|
|
arg_val = mlirlite.build_zext(builder, arg_val, i32_ty)
|
|
elif arg_bits == 8:
|
|
token = "%d"
|
|
arg_val = mlirlite.build_sext(builder, arg_val, i32_ty)
|
|
elif arg_bits == 16:
|
|
token = "%d"
|
|
arg_val = mlirlite.build_sext(builder, arg_val, i32_ty)
|
|
elif arg_bits == 32: token = "%d"
|
|
else: token = "%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: str, owner_sha1: str = 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: str = 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: str, func: mlirlite.Function*, max_count: int, defaults: rawptr = None, default_count: int = 0, param_count: int = 0, pool: memhub.MemBuddy* = None, def_sha1: str = 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: str, 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: str, 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: str = 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: str, func: mlirlite.Function*) -> int:
|
|
"""Update a global table entry's Spec after all its params are added"""
|
|
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)
|
|
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: str, key: str, 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: str, owner_sha1: str, 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: str = 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: str = self.Gen.ExprGenOps._attr_receiver_class_name(pool, at.value, trans)
|
|
if cls_nm is None: return None
|
|
bare: str = cls_nm
|
|
dot_a: str = 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: str, owner_sha1: str, 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: str = 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: str) -> 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: str) -> 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: str, defaults: rawptr, default_count: int, param_count: int, def_sha1: str = 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: str = 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: str = 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: str = 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: str = 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: str, kinds: int*, ints: int64*, strs: sizet*, count: int, param_count: int, def_sha1: str) -> 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: str = 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: str = 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: str, module_sha1: str = 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: str = 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: str) -> 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: str, owner_sha1: str) -> 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: str = 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)
|
|
|