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_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(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)