from stdint import * import ast import mlirlite import memhub import string import stdlib import lib.core.Gen.Translator as HT import lib.core.Gen.GenTypes as GenTypes import lib.Stub.StubGen.VpiReader as VpiReader import lib.core.Gen.GenApi as GenApi from . import CallGenBase # ============================================================ # CallGenLib - CallGenMixin split (stage C): C-lib declares / return-type inference novirtual class CallGenLib(CallGenBase.CallGenBase): """C-lib declarations / return-type inference / kwargs call helpers (extends Base)""" def _ensure_c_lib_declare(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, name: char*) -> mlirlite.Function*: """Create a declare for a known C-lib function""" if pool is None or mod is None or name is None: return None i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool) i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool) i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool) i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty) ret_ty: mlirlite.LLVMType* = None params: int = 0 # parameter count if string.strcmp(name, "strlen") == 0 or string.strcmp(name, "strlength") == 0: ret_ty = i64_ty params = 1 elif string.strcmp(name, "memset") == 0: ret_ty = i8_ptr_ty params = 3 elif string.strcmp(name, "memcpy") == 0: ret_ty = i8_ptr_ty params = 3 elif string.strcmp(name, "memmove") == 0: ret_ty = i8_ptr_ty params = 3 elif string.strcmp(name, "memcmp") == 0: ret_ty = i32_ty params = 3 elif string.strcmp(name, "strcmp") == 0: ret_ty = i32_ty params = 2 elif string.strcmp(name, "strncmp") == 0: ret_ty = i32_ty params = 3 elif string.strcmp(name, "strcpy") == 0: ret_ty = i8_ptr_ty params = 2 elif string.strcmp(name, "strncpy") == 0: ret_ty = i8_ptr_ty params = 3 elif string.strcmp(name, "strcat") == 0: ret_ty = i8_ptr_ty params = 2 elif string.strcmp(name, "puts") == 0: ret_ty = i32_ty params = 1 elif string.strcmp(name, "atoi") == 0: ret_ty = i32_ty params = 1 elif string.strcmp(name, "atol") == 0: ret_ty = i64_ty params = 1 elif string.strcmp(name, "labs") == 0: ret_ty = i64_ty params = 1 else: return None # create the declare fn: mlirlite.Function* = mlirlite.create_declare(pool, mod, name, ret_ty) if fn is None: return None # add parameters if params == 1: if string.strcmp(name, "labs") == 0: mlirlite.add_param(pool, fn, i64_ty, "n") else: mlirlite.add_param(pool, fn, i8_ptr_ty, "s") elif params == 2: mlirlite.add_param(pool, fn, i8_ptr_ty, "dst") mlirlite.add_param(pool, fn, i8_ptr_ty, "src") elif params == 3: if string.strcmp(name, "memset") == 0: mlirlite.add_param(pool, fn, i8_ptr_ty, "dst") mlirlite.add_param(pool, fn, i32_ty, "c") mlirlite.add_param(pool, fn, i64_ty, "n") else: mlirlite.add_param(pool, fn, i8_ptr_ty, "dst") mlirlite.add_param(pool, fn, i8_ptr_ty, "src") mlirlite.add_param(pool, fn, i64_ty, "n") return fn def _promote_clib_call_args(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, call_name: char*, head: mlirlite.Value*, count: int) -> mlirlite.Value*: """Promote bare-name C-lib call arguments to their standard signature widths""" if pool is None or builder is None or call_name is None or head is None: return head # bare names only (no '.' -> no sha1 prefix); prefixed ones use vpi definition-side parsing if string.strchr(call_name, 46) is not None: return head # whitelist: index of the size_t (i64) length param (matches _ensure_c_lib_declare) size_idx: int = -1 if string.strcmp(call_name, "memset") == 0: size_idx = 2 elif string.strcmp(call_name, "memcpy") == 0: size_idx = 2 elif string.strcmp(call_name, "memmove") == 0: size_idx = 2 elif string.strcmp(call_name, "memcmp") == 0: size_idx = 2 elif string.strcmp(call_name, "strncmp") == 0: size_idx = 2 elif string.strcmp(call_name, "strncpy") == 0: size_idx = 2 elif string.strcmp(call_name, "strncat") == 0: size_idx = 2 elif string.strcmp(call_name, "vsnprintf") == 0: size_idx = 1 if size_idx < 0 or count <= size_idx: return head # locate the length param (index size_idx) cur: mlirlite.Value* = head idx: int = 0 while cur is not None and idx < size_idx: cur = cur.Next idx += 1 if cur is None or cur.Ty is None: return head i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool) promoted: mlirlite.Value* = None match cur.Ty: case mlirlite.LLVMType.Int(bits): if bits >= 64: return head if cur.IsConst != 0: # constant: use an i64 constant directly (bit pattern kept, unsigned length) promoted = mlirlite.const_int(pool, 64, cur.IntVal) else: # SSA value: zext to i64 (size_t is an unsigned length) promoted = mlirlite.build_zext(builder, cur, i64_ty) case _: return head if promoted is None: return head # rebuild the arg list: replace index size_idx with the promoted value new_head: mlirlite.Value* = None new_tail: mlirlite.Value* = None cur2: mlirlite.Value* = head i2: int = 0 while cur2 is not None: node_v: mlirlite.Value* = cur2 if i2 == size_idx: node_v = promoted if new_head is None: new_head = node_v else: new_tail.Next = node_v new_tail = node_v cur2 = cur2.Next i2 += 1 return new_head # _coerce_args_to_def_params: coerce cross-module call args to vpi definition-side param # types so call/declare/define widths match; bare C-lib names use the C-lib path instead. def _coerce_args_to_def_params(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, call_name: char*, head: mlirlite.Value*, count: int, cargs: list[ast.AST*]* = None, self_off: int = 0, trans: HT.Translator* = None) -> mlirlite.Value*: """Coerce cross-module call args to vpi definition-side param types; returns the rebuilt chain""" if pool is None or builder is None or call_name is None or head is None: return head if self._is_sha1_mangled_call(call_name) == 0: return head # split {sha1}.{rest}: if rest has '.', it is {cls}.{method}, else {func} rest: char* = call_name + 17 cls_nm: char* = None meth_nm: char* = rest # take the last '.': the method has none, and the first '.' would truncate qualified type args dot_r: char* = string.strrchr(rest, 46) if dot_r is not None: cls_len: sizet = uint64(dot_r) - uint64(rest) if cls_len <= 0: return head cls_buf: bytes = stdlib.malloc(cls_len + 1) if cls_buf is None: return head string.strncpy(cls_buf, rest, cls_len) cls_buf[cls_len] = '\0' cls_nm = cls_buf meth_nm = dot_r + 1 if meth_nm is None or meth_nm[0] == '\0': if cls_nm is not None: stdlib.free(cls_nm) return head sha1_buf: bytes = stdlib.malloc(17) if sha1_buf is None: if cls_nm is not None: stdlib.free(cls_nm) return head string.strncpy(sha1_buf, call_name, 16) sha1_buf[16] = '\0' # ensure-load + direct cache lookup (not _vpi_method_param_annot, which misses module funcs) if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.Gen.CallGenBase.TempDir) prm_annots: char* = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm) # Strict OBS: a nullable binding/field may be None and must not flow into a non-nullable # parameter. Same-module callees use the nullable registry, cross-module ones use their # vpi param annotations; an unknown callee signature is left alone. if trans is not None and trans.StrictOBS != 0 and cargs is not None: arg_n_chk: int = int(len(cargs)) ai_chk: int = 0 while ai_chk < arg_n_chk: an_chk: ast.AST* = cargs.get(sizet(ai_chk)) ai_chk += 1 if an_chk is None: continue if self.Gen.ExprGenAttr._base_is_nullable(pool, trans, an_chk) == 0: continue pidx_chk: int = ai_chk - 1 + self_off checked_chk: int = 0 allow_chk: int = 0 if self.Gen.CallGenBase._param_nullable_known(call_name) != 0: checked_chk = 1 if self.Gen.CallGenBase._param_is_nullable(call_name, pidx_chk) != 0: allow_chk = 1 elif prm_annots is not None: checked_chk = 1 if VpiReader._vpi_param_annot_seg_has_nullable(prm_annots, pidx_chk) != 0: allow_chk = 1 if checked_chk == 0: continue if allow_chk == 0: pf_chk: int = GenApi.sha1_prefix_len(call_name) short_chk: char* = call_name + pf_chk if pf_chk != 0 else call_name self.Gen.TypeGenErr.fatal_error(an_chk, f"parameter {ai_chk} of '{short_chk}' is not nullable: a nullable value may be None; declare it as `T?` or unwrap it with `match` first", "TypeError") # generic-specialized class: annotations live on the template class, so fall back to template lookup tpl_src: int = 0 if prm_annots is None: if cls_nm is not None and string.strchr(cls_nm, 91) is not None: tpl_src = 1 else: stdlib.free(sha1_buf) if cls_nm is not None: stdlib.free(cls_nm) return head i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool) i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) new_head: mlirlite.Value* = None new_tail: mlirlite.Value* = None cur: mlirlite.Value* = head idx: int = 0 while cur is not None and idx < count: node_v: mlirlite.Value* = cur tgt_ty: mlirlite.LLVMType* = None if tpl_src != 0: tgt_ty = VpiReader._resolve_vpi_param_annot_seg_template(pool, sha1_buf, cls_nm, meth_nm, idx, i32_ty, i8_ptr_ty) else: tgt_ty = VpiReader._resolve_vpi_param_annot_seg(pool, sha1_buf, cls_nm, meth_nm, idx, i32_ty, i8_ptr_ty) if tgt_ty is None and trans is not None and trans.Module is not None: # vpi text could not be resolved in this file's context (e.g. a generic # class name). The injected declare was built with the caller's context, # so its parameter type is the authoritative ABI type. dcl: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(trans.Module, call_name) if dcl is not None: p_d: mlirlite.Param* = mlirlite.function_get_param_head(dcl) pi_d: int = 0 while p_d is not None and pi_d < idx: p_d = mlirlite.param_get_next(p_d) pi_d += 1 if p_d is not None: tgt_ty = mlirlite.param_get_ty(p_d) if tgt_ty is not None and cur.Ty is not None: # Argument AST (for source-symbolic checks): AST index = chain index idx - self offset arg_idx: int = idx - self_off arg_node: ast.AST* = None if cargs is not None and arg_idx >= 0 and arg_idx < len(cargs): arg_node = cargs.get(arg_idx) # Value-reference params (&T / &temp T): auto-address the argument (cross-module callee). # The mode comes from the definition-side vpi annotation segment (shared resolver); if the # arg is not a pointer it is addressed (ref: lvalue) or copied into a temp slot (temp). vpi_cur_sha: char* = None if trans is not None: vpi_cur_sha = trans.ModuleSha1 mode_cm: int = self.Gen.CallGenBase._vpi_auto_addr_mode(call_name, vpi_cur_sha, idx) if mode_cm != 0 and trans is not None and trans.Module is not None: node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, trans.Module, trans, mode_cm, arg_node, node_v, tgt_ty) # "str / bytes are not generic pointer wildcards": void* / rawptr may # convert to any pointer, but str/bytes may not, even though both are i8* at LLVM level. if self.Gen.ExprGenBase._str_rejects_ptr_kind(cur) != 0: if VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "str") != 0 or VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "bytes") != 0: self.Gen.ExprGenBase.fail_if_str_annot_agg_ptr(cur, 1, arg_node) node_v = self._coerce_arg_value_to_ty(pool, builder, node_v, tgt_ty, arg_node, trans, tpl_src) if new_head is None: new_head = node_v else: new_tail.Next = node_v new_tail = node_v cur = cur.Next idx += 1 stdlib.free(sha1_buf) if cls_nm is not None: stdlib.free(cls_nm) return new_head # _coerce_arg_value_to_ty: convert one arg Value to the target param type -- same-width ints # pass through, narrow/wide ints trunc/extend, mismatched pointers bitcast; else unchanged. def _coerce_arg_value_to_ty(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, val: mlirlite.Value*, tgt_ty: mlirlite.LLVMType*, node: ast.AST* = None, trans: HT.Translator* = None, tpl_src: int = 0) -> mlirlite.Value*: """Convert one argument Value to the target param type; leaves it unchanged when unsafe""" if pool is None or builder is None or val is None or tgt_ty is None or val.Ty is None: return val match val.Ty: case mlirlite.LLVMType.Int(bits_a): match tgt_ty: case mlirlite.LLVMType.Int(bits_b): if bits_b == bits_a: return val if bits_b < bits_a: if val.IsConst != 0: nv_c: mlirlite.Value* = mlirlite.const_int(pool, bits_b, val.IntVal) if nv_c is not None: return nv_c else: nv_t: mlirlite.Value* = mlirlite.build_trunc(builder, val, tgt_ty) if nv_t is not None: return nv_t # widening (arg narrower than param): rebuild constants by decimal value if val.IsConst != 0: nv_w: mlirlite.Value* = mlirlite.const_int(pool, bits_b, val.IntVal) if nv_w is not None: return nv_w return val # SSA widening: direction from source signedness (i1/i8/unsigned -> zext, else sext) if bits_b > bits_a: if self.Gen.ExprGenBase._zext_or_sext_val(pool, val, node, trans) != 0: nv_zw: mlirlite.Value* = mlirlite.build_zext(builder, val, tgt_ty) if nv_zw is not None: return nv_zw else: nv_sw: mlirlite.Value* = mlirlite.build_sext(builder, val, tgt_ty) if nv_sw is not None: return nv_sw return val case _: pass case mlirlite.LLVMType.Ptr(_): match tgt_ty: case mlirlite.LLVMType.Ptr(_): # same-level pointers: bitcast when the arg type differs from the definition # side (aligned so the first call site's declare matches; llc otherwise errors). if self.Gen.AssignGen._stg_ty_equal(pool, val.Ty, tgt_ty) == 0: nv_p: mlirlite.Value* = mlirlite.build_bitcast(builder, val, tgt_ty) if nv_p is not None: return nv_p case mlirlite.LLVMType.Int(bits_pi): # A pointer passed where the callee's parameter degraded to an integer # (e.g. `rawptr` resolved as uint64 in a stale/mis-typed stub): convert at the # call boundary instead of failing the implicit-cast rule, so passing a # rawptr never breaks the build and the callee still receives the address. if bits_pi > 0: nv_p2: mlirlite.Value* = mlirlite.build_ptrtoint(builder, val, tgt_ty) if nv_p2 is not None: return nv_p2 case _: pass case _: # aggregate -> pointer: spill rvalue struct/array and take its address (params lower to Ptr) match tgt_ty: case mlirlite.LLVMType.Ptr(_): vk_a: int = val.Ty.kind() if vk_a == mlirlite.LLVMType.Struct or vk_a == mlirlite.LLVMType.Array: tmp_a: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, val.Ty) if tmp_a is not None: mlirlite.build_store(builder, val, tmp_a) return tmp_a case _: pass return val def _infer_external_func_ret_ty(self, pool: memhub.MemBuddy*, func_name: char*, node: ast.AST*, fatal_if_unknown: int) -> mlirlite.LLVMType*: """Infer the return type of an external includes function; a whitelist miss may fail fast""" if pool is None or func_name is None: return mlirlite.Int32(pool) # extract the bare function name by stripping the SHA1 prefix # (16 hex digits + '.'), e.g. "240a9a4157959a9f.parse" -> "parse". bare_name: char* = func_name dot_pos: char* = string.strrchr(func_name, 46) # 46 = ord('.') if dot_pos is not None: # the part after '.' is the bare function name bare_name = dot_pos + 1 i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) # functions returning i8* (truncating the pointer would be wrong) if string.strcmp(bare_name, "strchr") == 0 or string.strcmp(func_name, "strchr") == 0: return i8_ptr_ty if string.strcmp(bare_name, "strrchr") == 0 or string.strcmp(func_name, "strrchr") == 0: return i8_ptr_ty if string.strcmp(bare_name, "strstr") == 0 or string.strcmp(func_name, "strstr") == 0: return i8_ptr_ty if string.strcmp(bare_name, "strcpy") == 0 or string.strcmp(func_name, "strcpy") == 0: return i8_ptr_ty if string.strcmp(bare_name, "strncpy") == 0 or string.strcmp(func_name, "strncpy") == 0: return i8_ptr_ty if string.strcmp(bare_name, "memset") == 0 or string.strcmp(func_name, "memset") == 0: return i8_ptr_ty if string.strcmp(bare_name, "memset32") == 0 or string.strcmp(func_name, "memset32") == 0: return i8_ptr_ty if string.strcmp(bare_name, "memcpy") == 0 or string.strcmp(func_name, "memcpy") == 0: return i8_ptr_ty if string.strcmp(bare_name, "memmove") == 0 or string.strcmp(func_name, "memmove") == 0: return i8_ptr_ty # JSON parse functions return JsonValue* (pointer); include the json_parse alias # (from json.__parser import parse as json_parse). if string.strcmp(bare_name, "parse") == 0 or string.strcmp(func_name, "parse") == 0 or string.strcmp(func_name, "json_parse") == 0: return i8_ptr_ty # MemBuddy methods (may use the external-function path when type info is lost). # malloc: bare-name ExportT cross-module call (uhci/desktop mm.malloc -> @malloc), else ret is inferred i32 and the 64-bit pointer truncates. if string.strcmp(bare_name, "malloc") == 0 or string.strcmp(func_name, "malloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) if string.strcmp(func_name, "alloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) if string.strcmp(func_name, "calloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) if string.strcmp(func_name, "realloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) if string.strcmp(func_name, "alloc_buf") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) # VLogger module funcs (stubs not injected cross-module, pointers truncated to i32) if string.strcmp(func_name, "get_logger") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) if string.strcmp(func_name, "fmt_buf") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) # Config module funcs (stubs not injected cross-module, i8* truncated to i32) if string.strcmp(func_name, "get_includes_binary_dir") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) # HandlesType module func (returns ast.AST* union; inferred as i32 cross-module) if string.strcmp(bare_name, "_parse_type_string") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) # Functions returning i64 if string.strcmp(func_name, "strlen") == 0: return mlirlite.Int64(pool) if string.strcmp(func_name, "atoll") == 0: return mlirlite.Int64(pool) # mlirlite name-length accessors return sizet (i64); the default i32 clashes with the declare if string.strcmp(bare_name, "function_get_name_len") == 0 or string.strcmp(func_name, "function_get_name_len") == 0: return mlirlite.Int64(pool) # Functions returning double if string.strcmp(func_name, "atof") == 0: return mlirlite.Double(pool) # Functions returning i8 if string.strcmp(func_name, "samestr") == 0: return mlirlite.Int8(pool) # Win32 APIs returning a pointer (VOIDPTR/HANDLE); no stubs are injected, so infer # i8* to keep the pointer from being truncated to i32 and crashing on dereference. i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) if string.strcmp(func_name, "VirtualAlloc") == 0: return i8_ptr_ty if string.strcmp(func_name, "HeapAlloc") == 0: return i8_ptr_ty if string.strcmp(func_name, "HeapReAlloc") == 0: return i8_ptr_ty if string.strcmp(func_name, "HeapCreate") == 0: return i8_ptr_ty if string.strcmp(func_name, "GetProcessHeap") == 0: return i8_ptr_ty if string.strcmp(func_name, "GlobalAlloc") == 0: return i8_ptr_ty if string.strcmp(func_name, "GlobalFree") == 0: return i8_ptr_ty if string.strcmp(func_name, "GlobalLock") == 0: return i8_ptr_ty if string.strcmp(func_name, "LocalAlloc") == 0: return i8_ptr_ty if string.strcmp(func_name, "LocalFree") == 0: return i8_ptr_ty if string.strcmp(func_name, "GetProcAddress") == 0: return i8_ptr_ty if string.strcmp(func_name, "GetModuleHandleA") == 0: return i8_ptr_ty if string.strcmp(func_name, "GetModuleHandleW") == 0: return i8_ptr_ty # GetCommandLineA/W return LPSTR (i8*); without it a 64-bit pointer is truncated to i32 if string.strcmp(func_name, "GetCommandLineA") == 0: return i8_ptr_ty if string.strcmp(func_name, "GetCommandLineW") == 0: return i8_ptr_ty # gethostbyname returns hostent* (i8*) if string.strcmp(func_name, "gethostbyname") == 0: return i8_ptr_ty if string.strcmp(func_name, "LoadLibraryA") == 0: return i8_ptr_ty if string.strcmp(func_name, "LoadLibraryW") == 0: return i8_ptr_ty # Win32 handle-returning functions (HANDLE = void* = i8*) if string.strcmp(func_name, "GetStdHandle") == 0: return i8_ptr_ty if string.strcmp(func_name, "CreateFileA") == 0: return i8_ptr_ty if string.strcmp(func_name, "CreateFileW") == 0: return i8_ptr_ty if string.strcmp(func_name, "FindFirstFileA") == 0: return i8_ptr_ty if string.strcmp(func_name, "FindFirstFileW") == 0: return i8_ptr_ty # Win32 APIs returning i64 (SIZE_T) if string.strcmp(func_name, "VirtualQuery") == 0: return mlirlite.Int64(pool) if string.strcmp(func_name, "HeapSize") == 0: return mlirlite.Int64(pool) if string.strcmp(func_name, "HeapCompact") == 0: return mlirlite.Int64(pool) if string.strcmp(func_name, "GlobalSize") == 0: return mlirlite.Int64(pool) # mlirlite build_* return-type inference: most return Value*, terminators return void if string.strncmp(func_name, "build_", 6) == 0: # build_* returning void (terminators and void-returning ops) if string.strcmp(func_name, "build_ret") == 0 or string.strcmp(func_name, "build_ret_void") == 0 or string.strcmp(func_name, "build_store") == 0 or string.strcmp(func_name, "build_br") == 0 or string.strcmp(func_name, "build_cond_br") == 0 or string.strcmp(func_name, "build_unreachable") == 0 or string.strcmp(func_name, "build_switch") == 0: return mlirlite.Void(pool) # other build_* return Value* (pointer) return mlirlite.Ptr(pool, mlirlite.Int8(pool)) # mlirlite type factories / const constructors / accessors return pointers (LLVMType* / Value*) if string.strcmp(func_name, "Int1") == 0 or string.strcmp(func_name, "Int8") == 0 or string.strcmp(func_name, "Int16") == 0 or string.strcmp(func_name, "Int32") == 0 or string.strcmp(func_name, "Int64") == 0 or string.strcmp(func_name, "Ptr") == 0 or string.strcmp(func_name, "Func") == 0 or string.strcmp(func_name, "Array") == 0 or string.strcmp(func_name, "Struct") == 0 or string.strcmp(func_name, "Void") == 0 or string.strcmp(func_name, "Half") == 0 or string.strcmp(func_name, "Float") == 0 or string.strcmp(func_name, "Double") == 0 or string.strcmp(func_name, "FP128") == 0 or string.strcmp(func_name, "Label") == 0 or string.strcmp(func_name, "new_param_node") == 0 or string.strcmp(func_name, "new_phi_incoming") == 0 or string.strcmp(func_name, "const_int32") == 0 or string.strcmp(func_name, "const_int64") == 0 or string.strcmp(func_name, "ConstFloat") == 0 or string.strcmp(func_name, "ConstZero") == 0 or string.strcmp(func_name, "ConstNull") == 0 or string.strcmp(func_name, "ConstInt") == 0 or string.strcmp(func_name, "function_get_name") == 0 or string.strcmp(func_name, "param_get_ty") == 0 or string.strcmp(func_name, "value_get_ty") == 0 or string.strcmp(func_name, "value_get_next") == 0 or string.strcmp(func_name, "function_get_ret_ty") == 0 or string.strcmp(func_name, "function_get_params") == 0 or string.strcmp(func_name, "function_get_param_head") == 0 or string.strcmp(func_name, "param_get_next") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) # Default i32 placeholder: fatal_if_unknown=1 fails fast; =0 (Phase A mid-pass) stays silent. if fatal_if_unknown != 0: self.Gen.TypeGenErr.fatal_error(node, f"external function return type is unregistered and cannot be inferred conclusively (defaulting to i32 would truncate pointers/i64; provide a stub declaration or add it to the whitelist): {func_name}", "TypeError") return mlirlite.Int32(pool) def _get_external_func_param_ty(self, pool: memhub.MemBuddy*, func_name: char*, param_idx: int) -> mlirlite.LLVMType*: """Return the expected type of an external function's param_idx-th parameter, or None (fallback only)""" if pool is None or func_name is None: return None i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool) # Win32 API list: every non-pointer parameter is i64 in declare # (ULONG/DWORD/SIZE_T are all mapped to i64 during LLVM stub translation) win32_apis: list[str]* = ["VirtualAlloc", "VirtualFree", "VirtualProtect", "VirtualQuery", "VirtualLock", "VirtualUnlock", "HeapCreate", "HeapDestroy", "HeapAlloc", "HeapReAlloc", "HeapFree", "HeapSize", "HeapValidate", "HeapCompact", "GlobalAlloc", "GlobalFree", "GlobalLock", "GlobalUnlock", "GlobalSize", "LocalAlloc", "LocalFree", "GetLastError", "SetLastError", "Sleep", "SleepEx", "GetTickCount", "GetProcessHeap"] # list iteration is unsupported; use range + get instead api_count: sizet = len(win32_apis) for ai in range(int(api_count)): api_name: char* = win32_apis.get(ai) if string.strcmp(api_name, func_name) == 0: return i64_ty # Allocators: size params are declared i64 (size_t); without promotion on bare-name fallback call/define widths mismatch; calloc/realloc size is idx 1 if string.strcmp(func_name, "malloc") == 0 or string.strcmp(func_name, "alloc") == 0 or string.strcmp(func_name, "alloc_buf") == 0: if param_idx == 0: return i64_ty if string.strcmp(func_name, "calloc") == 0: if param_idx == 0 or param_idx == 1: return i64_ty if string.strcmp(func_name, "realloc") == 0: if param_idx == 1: return i64_ty return None def _is_sha1_mangled_call(self, call_name: char*) -> int: """Return whether call_name is a cross-module Viper function ("{16 hex}.func")""" if call_name is None: return 0 if GenApi.sha1_prefix_len(call_name) != 0: return 1 return 0 def _emit_llvm_memcpy_intrinsic(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, dst_val: mlirlite.Value*, src_val: mlirlite.Value*, num_val: mlirlite.Value*) -> mlirlite.Value*: """Map a memcpy call to the @llvm.memcpy intrinsic""" if pool is None or builder is None or mod is None: return None if dst_val is None or src_val is None or num_val is None: return None # Declare void_ty at the top of the function to avoid later shadowing void_ty: mlirlite.LLVMType* = mlirlite.Void(pool) # Declare @llvm.memcpy (if not already declared) memcpy_func: mlirlite.Function* = self.find_func_in_module(mod, "llvm.memcpy") if memcpy_func is None: memcpy_func = mlirlite.create_declare(pool, mod, "llvm.memcpy", void_ty) if memcpy_func is not None: i8_ptr_ty_d: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) i64_ty_d: mlirlite.LLVMType* = mlirlite.Int64(pool) i1_ty_d: mlirlite.LLVMType* = mlirlite.Int1(pool) mlirlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "dst") mlirlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "src") mlirlite.add_param(pool, memcpy_func, i64_ty_d, "len") mlirlite.add_param(pool, memcpy_func, i1_ty_d, "isvolatile") # Coercions: dst, src -> i8*; num -> i64 # Use the value_get_ty accessor for Ty to avoid local Value field-index offset i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool) # dst → i8* dst_coerced: mlirlite.Value* = dst_val dst_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(dst_val) if self.Gen.ExprGenBase.is_ptr_type(dst_ty) != 0: dst_coerced = mlirlite.build_bitcast(builder, dst_val, i8_ptr_ty) else: dst_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(dst_ty) if dst_bits != 0: if dst_bits < 64: dst_coerced = mlirlite.build_zext(builder, dst_val, i64_ty) dst_coerced = mlirlite.build_inttoptr(builder, dst_coerced, i8_ptr_ty) # src → i8* src_coerced: mlirlite.Value* = src_val src_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(src_val) if self.Gen.ExprGenBase.is_ptr_type(src_ty) != 0: src_coerced = mlirlite.build_bitcast(builder, src_val, i8_ptr_ty) else: src_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(src_ty) if src_bits != 0: if src_bits < 64: src_coerced = mlirlite.build_zext(builder, src_val, i64_ty) src_coerced = mlirlite.build_inttoptr(builder, src_coerced, i8_ptr_ty) # num → i64 num_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, num_val, i64_ty) if dst_coerced is None or src_coerced is None or num_coerced is None: return None # Append the i1 false arg (isvolatile) - llvm.memcpy needs 4 parameters false_val: mlirlite.Value* = mlirlite.ConstInt(pool, mlirlite.Int1(pool), 0, "false") # Build the arg list via include accessors to avoid local Value field-index offset # (f26cd4c78b4d554c.Value lacks GSListNode.Next; direct .Next access corrupts .Ty) mlirlite.value_set_next(dst_coerced, src_coerced) mlirlite.value_set_next(src_coerced, num_coerced) if false_val is not None: mlirlite.value_set_next(num_coerced, false_val) mlirlite.value_set_next(false_val, None) else: mlirlite.value_set_next(num_coerced, None) # Emit the call via build_call (it accesses include Value fields with correct indices) # _ll_name_needs_quote already exempts the llvm. prefix from quoting result: mlirlite.Value* = mlirlite.build_call(builder, "llvm.memcpy", dst_coerced, 4, void_ty, 0) # Return dst (matches C memcpy semantics: returns the dest pointer) return dst_coerced # _translate_call_with_kwargs: keyword-arg call; builds args in found_func.Params order (positional first, else match by param name) # ============================================================ def _translate_call_with_kwargs(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, func_name: char*, found_func: mlirlite.Function*, cargs: list[ast.AST*]*, can: sizet, keywords: list[ast.AST*]*, kw_count: sizet, trans: HT.Translator*) -> mlirlite.Value*: """Translate a call with keyword arguments, building args in parameter order""" head: mlirlite.Value* = None tail: mlirlite.Value* = None actual_count: sizet = 0 # memcpy-only: collect independent refs of the first three Values (avoid shared alloca Next pollution) mc_dst1: mlirlite.Value* = None mc_src1: mlirlite.Value* = None mc_num1: mlirlite.Value* = None # Default-arg lookup for omitted params (same source as CallGenMixin non-kwargs path): # module SHA1 is the "{sha1}.func" prefix or current module; the name+SHA1-keyed snapshot survives resets. kw_callee_sha: char* = None kw_found_name: char* = mlirlite.function_get_name(found_func) if kw_found_name is not None: if GenApi.sha1_prefix_len(kw_found_name) != 0: kw_sha_buf: char* = pool.alloc(17) if kw_sha_buf is not None: string.strncpy(kw_sha_buf, kw_found_name, 16) kw_sha_buf[16] = '\0' kw_callee_sha = kw_sha_buf if kw_callee_sha is None and self.Gen.TypeGenBase is not None: kw_callee_sha = self.Gen.TypeGenBase.CurrentModuleSha1 kw_gd: GenTypes.FuncDefaultsEntry* = None if kw_callee_sha is not None: kw_gd = self.Gen.CallGenLeafKit.find_func_defaults(func_name, kw_callee_sha) kw_default_count: int = 0 kw_default_off: int = 0 if kw_gd is not None: kw_default_count = kw_gd.DefaultCount kw_default_off = kw_gd.ParamCount - kw_gd.DefaultCount if kw_default_off < 0: kw_default_off = 0 # Walk the function parameter list (accessor gets Head) param_idx: sizet = 0 cur_param: mlirlite.Param* = mlirlite.function_get_param_head(found_func) while cur_param is not None: arg_val: mlirlite.Value* = None param_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param) if param_idx < can: # Prefer the positional argument arg: ast.AST* = cargs.get(param_idx) if arg is not None: # Pass the param type (char literal -> str param materializes a string; see translate_constant) arg_val = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans, param_ty) else: # Find the matching keyword argument param_name: char* = mlirlite.param_get_name(cur_param) if param_name is not None: for ki in range(int(kw_count)): kw: ast.Keyword* = ast.Keyword*(keywords.get(ki)) if kw is None or kw.arg is None or kw.value is None: continue if self.Gen.CallGenCall._kwarg_name_matches(kw.arg, param_name) == 1: arg_val = self.Gen.ExprGen.translate_value(builder, pool, mod, kw.value, None, 0, trans, param_ty) break # Param supplied by neither position nor keyword: fill with the definition-site default # (index = param_idx - param_count + default_count); else fail-fast, never silently drop/zero. if arg_val is None: if kw_default_count > 0 and int(param_idx) >= kw_default_off: kw_di: int = int(param_idx) - kw_default_off if kw_di >= 0 and kw_di < kw_default_count: arg_val = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, kw_gd, kw_di, trans, builder) if arg_val is None: kw_node: ast.AST* = None if keywords is not None and kw_count > 0: kw_node = keywords.get(0) self.Gen.TypeGenErr.fatal_error(kw_node, f"a parameter of call '{func_name}' is passed neither positionally nor by keyword, " f"and the definition has no default value to fill in (refusing to silently drop/zero a parameter)", "TypeError") if arg_val is not None: # Coerce by parameter type (via accessor) if param_ty is not None: arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, param_ty, None, trans) arg_val.Next = None if head is None: head = arg_val else: tail.Next = arg_val tail = arg_val # memcpy-only: save independent refs of the first three args if actual_count == 0: mc_dst1 = arg_val elif actual_count == 1: mc_src1 = arg_val elif actual_count == 2: mc_num1 = arg_val actual_count += 1 param_idx += 1 cur_param = mlirlite.param_get_next(cur_param) # declare and define both use found_func.Name: normal funcs carry the SHA1 prefix, t.ExternT/t.State/t.ExportT use bare names (bare names desync call/define symbols) call_name: char* = func_name found_name: char* = mlirlite.function_get_name(found_func) if found_name is not None: call_name = found_name # Return type (via accessor) call_ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(found_func) if call_ret_ty is None: # found_func has a declare but return type is unreadable (accessor anomaly, rare guard): # even if the whitelist misses, do not fail-fast; keep the default i32. call_ret_ty = self._infer_external_func_ret_ty(pool, func_name, None, 0) # memcpy -> @llvm.memcpy intrinsic (avoid a missing cross-module @memcpy declare under monomorphization) if string.strcmp(func_name, "memcpy") == 0 and actual_count >= 3: return self._emit_llvm_memcpy_intrinsic(pool, builder, mod, mc_dst1, mc_src1, mc_num1) return mlirlite.build_call(builder, call_name, head, actual_count, call_ret_ty, 0)