把 TransPyV/App 下 57 个文件的运行时中文字符串(编译器报错/告警/日志、CLI 帮助与横幅、REPL 提示、Phase1/Phase2 流程日志、Codegen 各 Mixin 的类型与调用报错、Stub/Vpi 诊断等)共 1091 行译为英文。 口径:只改字符串字面量内部文字,行数零变化(+1092/-1092);printf 格式符与转义序列多重集合校验不变(_strcheck 异常 0);译文不引入撇号以免干扰单引号字面量解析。验证:三重重编 SUCCESS(136.20s)+ 全量 77/77 顺序回归通过 + VET 编译 SUCCESS / --search-paths 7 条 exit 0;新产物 help/日志输出已为英文。
1203 lines
66 KiB
Plaintext
1203 lines
66 KiB
Plaintext
import lib.core.Gen.GenTypes as GenTypes
|
|
import t, c
|
|
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
|
|
|
|
|
|
# ============================================================
|
|
# 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(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(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)"""
|
|
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 CVTable 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:
|
|
# CVTable -> 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 CVTable): 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(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(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(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(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(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(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(512)
|
|
if typed_buf is not None:
|
|
mlirlite.TypePrint(typed_buf, 512, 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(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(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(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(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, 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:
|
|
# 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(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
|
|
|
|
# array name (e.g. print(l), l: t.CArray[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.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(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: t.CPtr = 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 _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 if already present (avoid duplicates)
|
|
if self.find_func_in_table(self.GlobalFuncs, self.GlobalFuncCount, name) is not None: 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
|
|
entry: GenTypes.FuncEntry* = 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)
|
|
|
|
|
|
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(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: t.CPtr, 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*] | t.CPtr)(uint64(defaults))
|
|
di: int
|
|
for di in range(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:
|
|
# CDefine-valued default (e.g. share: ULONG = SHARE_READ): evaluate the
|
|
# CDefine at registration; only registered int CDefines are supported.
|
|
dnm: ast.Name* = ast.Name*(dnode)
|
|
if dnm.id is not None:
|
|
cdef_v: int = 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 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(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(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(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)
|
|
|