Files

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