257 lines
16 KiB
Plaintext
257 lines
16 KiB
Plaintext
from stdint import *
|
|
import ast
|
|
import mlirlite
|
|
import memhub
|
|
import string
|
|
import lib.core.Gen.Translator as HT
|
|
|
|
from . import CallGenBase
|
|
|
|
|
|
# CallGenVa - CallGenMixin split (stage B): va_arg variadics (start/arg/end / variadic call promotion)
|
|
# ============================================================
|
|
novirtual class CallGenVa(CallGenBase.CallGenBase):
|
|
"""va_arg variadics (start/arg/end and variadic call promotion)."""
|
|
|
|
# _func_has_eh_params - whether callee signature has try-except EH out params (main excluded)
|
|
# ============================================================
|
|
|
|
def _va_arg_get_alloca_ptr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, arg_node: ast.AST*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
|
|
"""Return the alloca pointer of a va_list variable without loading it."""
|
|
if arg_node is None: return None
|
|
# Name node: look up the alloca pointer from the symbol table (no load)
|
|
if arg_node.kind() == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(arg_node)
|
|
if nm.id is not None:
|
|
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
|
if alloca is not None: return alloca
|
|
# Fallback: translate_value (may return a loaded value; only valid when already a pointer)
|
|
return self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, funcs_ptr, func_count, trans)
|
|
def _resolve_va_arg_type(self, pool: memhub.MemBuddy*, type_node: ast.AST*, trans: HT.Translator*) -> mlirlite.LLVMType*:
|
|
"""Resolve the va_arg/arg type argument to an LLVMType, defaulting to i32."""
|
|
if type_node is None: return mlirlite.Int32(pool)
|
|
# Resolve the type expression via HandlesType.resolve_annotation_type
|
|
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, type_node, trans._imported_modules, trans._from_imports, trans, 0)
|
|
if resolved is not None: return resolved
|
|
# Type node present but unresolvable: fail-fast, never fall back to i32 (would read the vararg slot at the wrong width)
|
|
self.Gen.TypeGenErr.fatal_error(type_node, "va_arg type resolution failed", "TypeError")
|
|
return mlirlite.Int32(pool)
|
|
def _translate_va_arg_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
|
|
"""Emit a va_arg instruction reading the next variadic argument from the va_list."""
|
|
if can == 0 or cargs is None: return mlirlite.const_int32(pool, 0)
|
|
|
|
# First arg: va_list (evaluate to an i8* address; aligned with TPC _HandleVaArgLlvm). In own *args
|
|
# translate_value returns the buffer address; in forwarding (sprintf -> vsnprintf) it loads the param.
|
|
ap_node: ast.AST* = cargs.get(0)
|
|
ap_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ap_node, None, 0, trans)
|
|
if ap_val is None: return mlirlite.const_int32(pool, 0)
|
|
ap_ptr: mlirlite.Value* = ap_val
|
|
|
|
# Second arg: target type (optional, defaults to i32)
|
|
arg_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
|
if can >= 2:
|
|
type_node: ast.AST* = cargs.get(1)
|
|
arg_ty = self._resolve_va_arg_type(pool, type_node, trans)
|
|
|
|
# Win64 vararg slots are 8 bytes: read sub-64-bit ints as i64 (TPC emit_va_arg L72-73)
|
|
# then trunc to target width; otherwise high bits are garbage (%d/%i wrong, A6 root cause).
|
|
read_ty: mlirlite.LLVMType* = arg_ty
|
|
abits_va: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_ty)
|
|
if abits_va != 0 and abits_va < 64:
|
|
read_ty = mlirlite.Int64(pool)
|
|
va_raw: mlirlite.Value* = mlirlite.build_va_arg(builder, ap_ptr, read_ty)
|
|
if va_raw is None: return mlirlite.const_int32(pool, 0)
|
|
# 8-byte slot -> target width (low bits); caller int64(...) re-applies sext/zext
|
|
return mlirlite.build_trunc(builder, va_raw, arg_ty)
|
|
|
|
# Emit the va_arg instruction
|
|
return mlirlite.build_va_arg(builder, ap_ptr, read_ty)
|
|
def _translate_arg_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
|
|
"""Emit va_arg reading from the enclosing variadic's own va_list or a forwarded va_list."""
|
|
# arg() has two forms:
|
|
# (1) Own-variadic form: inside `def f(a, *args)`, trans._va_list_ptr is the i8* address of
|
|
# the function's implicit va_list buffer; read the next vararg straight from it.
|
|
# (2) Forwarding form: in a NON-variadic wrapper such as vsnprintf(buf, size, fmt, args),
|
|
# the caller's va_list pointer is passed by value through the 4th param named "args"
|
|
# (vprintf style, see includes/viperlib.vp); there is no implicit va_list here, so
|
|
# trans._va_list_ptr is None and the pointer must be recovered from that param.
|
|
va_ptr: mlirlite.Value* = trans._va_list_ptr
|
|
if va_ptr is None:
|
|
# The "args" param's alloca is an i8** slot holding the forwarded i8* va_list value, so it
|
|
# MUST be loaded once: va_arg needs the pointer value itself (i8*), not the slot address.
|
|
args_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, "args")
|
|
if args_alloca is None: return mlirlite.const_int32(pool, 0)
|
|
if args_alloca.Ty is None: return mlirlite.const_int32(pool, 0)
|
|
va_ptr = mlirlite.build_load(builder, args_alloca.Ty.Pointee, args_alloca)
|
|
if va_ptr is None: return mlirlite.const_int32(pool, 0)
|
|
|
|
# First arg: target type (optional, defaults to i32)
|
|
arg_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
|
if can >= 1 and cargs is not None:
|
|
type_node: ast.AST* = cargs.get(0)
|
|
arg_ty = self._resolve_va_arg_type(pool, type_node, trans)
|
|
|
|
# Win64 vararg slots are 8 bytes: read sub-64-bit ints as i64 (same as _translate_va_arg_sugar),
|
|
# then trunc to target width (%d/%i high-bit garbage fix, see _translate_va_arg_sugar).
|
|
read_ty: mlirlite.LLVMType* = arg_ty
|
|
abits_arg: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_ty)
|
|
if abits_arg != 0 and abits_arg < 64:
|
|
read_ty = mlirlite.Int64(pool)
|
|
arg_raw: mlirlite.Value* = mlirlite.build_va_arg(builder, va_ptr, read_ty)
|
|
if arg_raw is None: return mlirlite.const_int32(pool, 0)
|
|
return mlirlite.build_trunc(builder, arg_raw, arg_ty)
|
|
|
|
# Emit va_arg, using va_ptr as the va_list address (own buffer or forwarded pointer)
|
|
return mlirlite.build_va_arg(builder, va_ptr, read_ty)
|
|
def _translate_va_start_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
|
|
"""Emit llvm.va_start to initialize the va_list (the second C-ABI argument is unused)."""
|
|
if can == 0 or cargs is None: return mlirlite.const_int32(pool, 0)
|
|
|
|
# First arg: va_list pointer (get its alloca, no load)
|
|
ap_node: ast.AST* = cargs.get(0)
|
|
ap_ptr: mlirlite.Value* = self._va_arg_get_alloca_ptr(pool, builder, mod, ap_node, funcs_ptr, func_count, trans)
|
|
if ap_ptr is None: return mlirlite.const_int32(pool, 0)
|
|
|
|
# bitcast to i8* (@llvm.va_start takes i8*)
|
|
i8_ty_vs: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
|
i8_ptr_ty_vs: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_vs)
|
|
ap_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, ap_ptr, i8_ptr_ty_vs)
|
|
if ap_i8 is None: return mlirlite.const_int32(pool, 0)
|
|
|
|
void_ty_vs: mlirlite.LLVMType* = mlirlite.Void(pool)
|
|
mlirlite.build_call(builder, "llvm.va_start", ap_i8, 1, void_ty_vs, 0)
|
|
return mlirlite.const_int32(pool, 0)
|
|
def _translate_va_end_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*:
|
|
"""Emit llvm.va_end to clean up the va_list (a no-op on most platforms)."""
|
|
if can == 0 or cargs is None: return mlirlite.const_int32(pool, 0)
|
|
|
|
# First arg: va_list pointer (get its alloca, no load)
|
|
ap_node: ast.AST* = cargs.get(0)
|
|
ap_ptr: mlirlite.Value* = self._va_arg_get_alloca_ptr(pool, builder, mod, ap_node, funcs_ptr, func_count, trans)
|
|
if ap_ptr is None: return mlirlite.const_int32(pool, 0)
|
|
|
|
# bitcast to i8* (@llvm.va_end takes i8*)
|
|
i8_ty_ve: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
|
i8_ptr_ty_ve: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_ve)
|
|
ap_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, ap_ptr, i8_ptr_ty_ve)
|
|
if ap_i8 is None: return mlirlite.const_int32(pool, 0)
|
|
|
|
void_ty_ve: mlirlite.LLVMType* = mlirlite.Void(pool)
|
|
mlirlite.build_call(builder, "llvm.va_end", ap_i8, 1, void_ty_ve, 0)
|
|
return mlirlite.const_int32(pool, 0)
|
|
def _get_func_fixed_param_count(self, func: mlirlite.Function*) -> int:
|
|
"""Return the count of fixed params by walking the Params list."""
|
|
if func is None: return 0
|
|
count: int = 0
|
|
param: mlirlite.Param* = mlirlite.function_get_param_head(func)
|
|
while param is not None:
|
|
count += 1
|
|
param = mlirlite.param_get_next(param)
|
|
return count
|
|
def _get_known_vararg_cfunc_fixed_count(self, name: str) -> int:
|
|
"""Return the fixed-param count for known variadic C functions (-1 if unknown)."""
|
|
if name is None: return -1
|
|
if string.strcmp(name, "snprintf") == 0: return 3
|
|
if string.strcmp(name, "sprintf") == 0: return 2
|
|
if string.strcmp(name, "vsprintf") == 0: return 2
|
|
return -1
|
|
|
|
def _get_known_vararg_cfunc_param_ty(self, pool: memhub.MemBuddy*, name: str, idx: int) -> mlirlite.LLVMType*:
|
|
"""Return the type of the idx-th fixed param of a known variadic C function."""
|
|
if pool is None or name is None or idx < 0: return None
|
|
i8_ptr_kv: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
|
i64_kv: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
|
if string.strcmp(name, "snprintf") == 0:
|
|
if idx == 0: return i8_ptr_kv
|
|
if idx == 1: return i64_kv
|
|
if idx == 2: return i8_ptr_kv
|
|
return None
|
|
if string.strcmp(name, "sprintf") == 0 or string.strcmp(name, "vsprintf") == 0:
|
|
if idx == 0: return i8_ptr_kv
|
|
if idx == 1: return i8_ptr_kv
|
|
return None
|
|
return None
|
|
def _check_and_promote_variadic_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, call_name: str, head: mlirlite.Value*, can: sizet, arg_nodes: list[ast.AST*]* = None, self_offset: int = 0, trans: HT.Translator* = None) -> int:
|
|
"""Promote variadic args of a callee to the C ABI; returns 1 if variadic else 0."""
|
|
if mod is None or call_name is None or head is None: return 0
|
|
# Look up the function object
|
|
func: mlirlite.Function* = self.find_func_in_module(mod, call_name)
|
|
fixed_count: int = -1
|
|
if func is not None and func.IsVarArg == 1: fixed_count = self._get_func_fixed_param_count(func)
|
|
else:
|
|
# Fall back to the known variadic C-lib signature table (snprintf etc.) when func is None (stub not yet injected)
|
|
# or IsVarArg=0 (ad hoc declare); otherwise i32 varargs aren't promoted and the Win64 8-byte slot keeps only 4 bytes.
|
|
fixed_count = self._get_known_vararg_cfunc_fixed_count(call_name)
|
|
if fixed_count < 0: return 0
|
|
|
|
# No varargs (arg count <= fixed count) but still report is_variadic=1
|
|
if can <= fixed_count: return 1
|
|
|
|
# Walk to the fixed_count-th node (the last fixed param)
|
|
cur: mlirlite.Value* = head
|
|
prev: mlirlite.Value* = None
|
|
idx: int = 0
|
|
while cur is not None and idx < fixed_count:
|
|
prev = cur
|
|
cur = cur.Next
|
|
idx += 1
|
|
|
|
# cur now points at the first vararg; apply C ABI promotion to all varargs
|
|
i32_ty_promote: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
|
double_ty_promote: mlirlite.LLVMType* = mlirlite.Double(pool)
|
|
v_idx: int = fixed_count
|
|
while cur is not None:
|
|
next_node: mlirlite.Value* = cur.Next
|
|
promoted: mlirlite.Value* = cur
|
|
# C ABI integer promotion: i8/i16 → i32
|
|
arg_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(cur.Ty)
|
|
if arg_bits != 0 and arg_bits < 32:
|
|
# Unsigned ints (uint8/CUInt16T etc.) -> zext; signed/unknown -> sext.
|
|
# Fix: printf("%02x", buf[i]) with CUInt8T must zext to 0xF3, not sext to 0xFFFFFFF3.
|
|
is_u_va: int = 0
|
|
if arg_nodes is not None and trans is not None:
|
|
ast_idx_va: int = v_idx - self_offset
|
|
if ast_idx_va >= 0 and ast_idx_va < len(arg_nodes):
|
|
an_va: ast.AST* = arg_nodes.get(ast_idx_va)
|
|
if an_va is not None:
|
|
if self.Gen.ExprGenOps._node_type_unsigned(pool, an_va, trans) != 0: is_u_va = 1
|
|
if is_u_va != 0: promoted = mlirlite.build_zext(builder, cur, i32_ty_promote)
|
|
else:
|
|
# If is_u_va is unknown (missing arg_nodes/trans or no annotation), default i8 to zext
|
|
# (aligned with should_zext_widen); sext would print uint8 bytes as ffffffXX garbage.
|
|
if self.Gen.ExprGenBase.get_llvm_type_bits(cur.Ty) == 8: promoted = mlirlite.build_zext(builder, cur, i32_ty_promote)
|
|
else: promoted = self.Gen.ExprGenBase.coerce_to_type(builder, cur, i32_ty_promote, None)
|
|
if promoted is not None:
|
|
promoted.Next = next_node
|
|
if prev is not None: prev.Next = promoted
|
|
# C ABI float promotion: float → double
|
|
arg_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(promoted.Ty)
|
|
if arg_fbits != 0 and arg_fbits < 64:
|
|
promoted = self.Gen.ExprGenBase.coerce_to_type(builder, promoted, double_ty_promote, None)
|
|
if promoted is not None:
|
|
promoted.Next = next_node
|
|
if prev is not None: prev.Next = promoted
|
|
# Win64 vararg slots are 8 bytes: i8/i16/i32 -> i64. The va_arg read side always reads i64
|
|
# (_translate_va_arg_sugar), so callers must fill all 8 bytes or stale stack data remains.
|
|
i64_ty_promote: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
|
pb_va: int = self.Gen.ExprGenBase.get_llvm_type_bits(promoted.Ty)
|
|
if pb_va != 0 and pb_va < 64:
|
|
promoted_va2: mlirlite.Value* = None
|
|
is_u_va2: int = 0
|
|
if arg_nodes is not None and trans is not None:
|
|
ast_idx_va2: int = v_idx - self_offset
|
|
if ast_idx_va2 >= 0 and ast_idx_va2 < len(arg_nodes):
|
|
an_va2: ast.AST* = arg_nodes.get(ast_idx_va2)
|
|
if an_va2 is not None:
|
|
if self.Gen.ExprGenOps._node_type_unsigned(pool, an_va2, trans) != 0: is_u_va2 = 1
|
|
if is_u_va2 != 0: promoted_va2 = mlirlite.build_zext(builder, promoted, i64_ty_promote)
|
|
else: promoted_va2 = mlirlite.build_sext(builder, promoted, i64_ty_promote)
|
|
if promoted_va2 is not None:
|
|
promoted_va2.Next = next_node
|
|
if prev is not None: prev.Next = promoted_va2
|
|
promoted = promoted_va2
|
|
prev = promoted
|
|
cur = next_node
|
|
v_idx += 1
|
|
|
|
return 1 |