Files

1365 lines
82 KiB
Plaintext

from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.GenTypes as GenTypes
from . import CallGenBase
# CallGenCBuiltin - c.*/asm/llvmir builtins
# Constants (kept consistent with TPC-side HandlesExprCall)
define _CEXPORT_MAX = 256
define ASM_MAX_OPERANDS = 16
# Clobber (~{...}) capacity is decoupled from the operand limit: vipersimd x8
# needs 21 entries, so ASM_MAX_OPERANDS=16 silently truncated it to 16.
define ASM_MAX_CLOBBERS = 32
# ASM_TEXT_BUF_SIZE holds the whole inline-asm template (a kernel switch statement
# template is already 2245 bytes). Overflow is fail-fast in _asm_append_cstr, never
# silently truncated: a cut-in-half template used to surface as a bogus llc parse
# error (e.g. a dangling `mov` with its operands chopped off).
define ASM_TEXT_BUF_SIZE = 8192
# Constraint buffer base size; the real capacity scales with operand/clobber count.
define ASM_CONST_BUF_SIZE = 512
define ASM_ARGS_BUF_SIZE = 512
define ASM_LINE_BUF_SIZE = 4096
define LLVMIR_MAX_OPS = 16
# Raw LLVM IR templates (c.LLVMIR) are multi-instruction text, not one-liners.
define LLVMIR_TEMPLATE_BUF = 4096
# CallGenCBuiltin - c.*/asm/llvmir builtins
novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
"""Translate c.* builtins, inline asm and raw LLVM IR."""
def translate_c_deref(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Load *ptr; the read width is decided solely by the pointee type."""
if cl is None: return None
cargs: list[ast.AST*]* = cl.args
if cargs is None or len(cargs) < 1: return None
ptr_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), None, 0, trans)
if ptr_val is None: return None
# rawptr[t.CVolatile | T] arg: the deref load in c.Deref(p) is volatile.
# 1 only when the arg resolves to VarEntry/FieldEntry, otherwise 0.
vol_deref: int = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, cargs.get(0))
# Read width is decided solely by the pointer type: load one pointee
# element. i8 pointee always reads 1 byte; wider reads must cast first.
if self.Gen.ExprGenBase.is_ptr_type(ptr_val.Ty) != 0:
pointee: mlirlite.LLVMType* = ptr_val.Ty.Pointee
if pointee is not None: return mlirlite.build_load(builder, pointee, ptr_val, vol_deref)
# Non-pointer arg means degraded type inference: fail-fast, never read as i8.
self.Gen.TypeGenErr.fatal_error(cl, "c.Deref argument is not a pointer type (type inference degraded)", "TypeError")
return None
def translate_c_derefas(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Store val to ptr after a bitcast; the single-arg form reads *ptr."""
if cl is None: return None
cargs: list[ast.AST*]* = cl.args
if cargs is None or len(cargs) < 1: return None
# Single-arg form is a deref read: previously len<2 returned None and
# fell through to an undefined call @DerefAs, so reuse translate_c_deref.
if len(cargs) < 2: return self.translate_c_deref(pool, builder, mod, cl, trans)
ptr_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), None, 0, trans)
val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(1), None, 0, trans)
if ptr_val is None or val is None: return None
# When both sides point to the same struct type, copy the struct value
# instead of the pointer, or a REnum variant tag would stay 0x0.
if self.Gen.ExprGenBase.is_ptr_type(ptr_val.Ty) != 0 and self.Gen.ExprGenBase.is_ptr_type(val.Ty) != 0:
ptr_pointee: mlirlite.LLVMType* = ptr_val.Ty.Pointee
val_pointee: mlirlite.LLVMType* = val.Ty.Pointee
if ptr_pointee is not None and val_pointee is not None:
# ptr_pointee is a registered struct type (checked via find_struct_by_type)
if self.Gen.StructGen.find_struct_by_type(ptr_pointee) is not None:
if ptr_pointee is val_pointee:
# Same struct type: load the value then store it (struct copy)
loaded: mlirlite.Value* = mlirlite.build_load(builder, val_pointee, val)
if loaded is not None:
# bitcast ptr to a struct pointer, then store
struct_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, ptr_pointee)
casted_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, ptr_val, struct_ptr_ty)
if casted_ptr is not None: mlirlite.build_store(builder, loaded, casted_ptr)
return val
# bitcast ptr to val.Ty* and store
val_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, val.Ty)
casted_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, ptr_val, val_ptr_ty)
if casted_ptr is None: return None
mlirlite.build_store(builder, val, casted_ptr)
return val
def translate_c_set(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Assign b to the lvalue a."""
if cl is None: return None
cargs: list[ast.AST*]* = cl.args
if cargs is None or len(cargs) < 2: return None
a_node: ast.AST* = cargs.get(0)
b_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(1), None, 0, trans)
if b_val is None: return None
# Form 0: c.Set(*ptr, b) -> *ptr = b. In an argument list a leading '*' is
# parsed as Starred (unpacking), so the new-syntax deref lvalue arrives as
# Starred(value=ptr) (UnaryOp/Deref also accepted). The old c.Deref(ptr)
# spelling is Form 1; without either the store is silently dropped.
ptr_expr: ast.AST* = None
if a_node is not None and a_node.kind() == ast.ASTKind.Starred:
st_a: ast.Starred* = ast.Starred*(a_node)
if st_a.value is not None: ptr_expr = st_a.value
elif a_node is not None and a_node.kind() == ast.ASTKind.UnaryOp:
uo_a: ast.UnaryOp* = ast.UnaryOp*(a_node)
if uo_a.op == ast.OpKind.Deref and uo_a.operand is not None: ptr_expr = uo_a.operand
if ptr_expr is not None:
ptr_a: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ptr_expr, None, 0, trans)
if ptr_a is None or ptr_a.Ty is None or self.Gen.ExprGenBase.is_ptr_type(ptr_a.Ty) == 0:
self.Gen.TypeGenErr.fatal_error(a_node, "c.Set dereference target must be a pointer type (type inference degraded)", "TypeError")
pointee_a: mlirlite.LLVMType* = ptr_a.Ty.Pointee
if pointee_a is None:
self.Gen.TypeGenErr.fatal_error(a_node, "c.Set dereference target pointee type missing", "TypeError")
sv_a: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, b_val, pointee_a, a_node, trans)
if sv_a is not None: mlirlite.build_store(builder, sv_a, ptr_a)
return b_val
# Form 1: c.Set(c.Deref(ptr), b) -> *ptr = b
if a_node is not None and a_node.kind() == ast.ASTKind.Call:
d_call: ast.Call* = ast.Call*(a_node)
if d_call.func is not None and d_call.func.kind() == ast.ASTKind.Attribute:
d_at: ast.Attribute* = ast.Attribute*(d_call.func)
if d_at.attr is not None and string.strcmp(d_at.attr, "Deref") == 0:
d_args: list[ast.AST*]* = d_call.args
if d_args is not None and len(d_args) >= 1:
ptr_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, d_args.get(0), None, 0, trans)
if ptr_val is not None:
# Store width follows the lvalue pointer's element type:
# coerce v to T first. Untyped (i8) pointers keep old behavior.
store_ptr: mlirlite.Value* = ptr_val
store_val: mlirlite.Value* = b_val
typed_elem: int = 0
elem_ty: mlirlite.LLVMType* = None
if ptr_val.Ty is not None: elem_ty = ptr_val.Ty.Pointee
if elem_ty is not None:
eb: int = self.Gen.ExprGenBase.get_llvm_type_bits(elem_ty)
if eb != 0 and eb != 8: typed_elem = 1
elif self.Gen.ExprGenBase.get_llvm_float_bits(elem_ty) != 0: typed_elem = 1
elif self.Gen.ExprGenBase.is_ptr_type(elem_ty) != 0: typed_elem = 1
elif self.Gen.ExprGenBase.is_array_type(elem_ty) != 0: typed_elem = 1
if typed_elem != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, b_val, elem_ty, a_node)
else:
val_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, b_val.Ty)
casted_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, ptr_val, val_ptr_ty)
if casted_ptr is not None: store_ptr = casted_ptr
if store_ptr is not None and store_val is not None: mlirlite.build_store(builder, store_val, store_ptr)
return b_val
# Other Call forms cannot be an lvalue
return b_val
# Form 2: assign to a Name variable
if a_node is not None and a_node.kind() == ast.ASTKind.Name and trans is not None:
nm_set: ast.Name* = ast.Name*(a_node)
if nm_set.id is not None:
if GenApi.is_global_name(trans, nm_set.id) != 0:
mod_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm_set.id)
if mod_alloca is not None:
tg_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: tg_ty = mod_alloca.Ty.Pointee
sv: mlirlite.Value* = b_val
if tg_ty is not None: sv = self.Gen.ExprGenBase.coerce_to_type(builder, b_val, tg_ty, a_node)
mlirlite.build_store(builder, sv, mod_alloca)
return b_val
if GenApi.is_nonlocal_name(trans, nm_set.id) != 0:
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm_set.id)
if nl_ptr is not None:
nl_ty: mlirlite.LLVMType* = None
if nl_ptr.Ty is not None: nl_ty = nl_ptr.Ty.Pointee
sv2: mlirlite.Value* = b_val
if nl_ty is not None: sv2 = self.Gen.ExprGenBase.coerce_to_type(builder, b_val, nl_ty, a_node)
mlirlite.build_store(builder, sv2, nl_ptr)
return b_val
alloca: mlirlite.Value* = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, nm_set.id, b_val.Ty)
if alloca is not None:
ac_ty: mlirlite.LLVMType* = None
if alloca.Ty is not None: ac_ty = alloca.Ty.Pointee
sv3: mlirlite.Value* = b_val
if ac_ty is not None: sv3 = self.Gen.ExprGenBase.coerce_to_type(builder, b_val, ac_ty, a_node)
mlirlite.build_store(builder, sv3, alloca)
return b_val
return b_val
# Form 3: assign to an Attribute field
if a_node is not None and a_node.kind() == ast.ASTKind.Attribute:
field_ptr: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, a_node, trans)
if field_ptr is not None:
fd_ty: mlirlite.LLVMType* = None
if field_ptr.Ty is not None: fd_ty = field_ptr.Ty.Pointee
sv4: mlirlite.Value* = b_val
if fd_ty is not None: sv4 = self.Gen.ExprGenBase.coerce_to_type(builder, b_val, fd_ty, a_node)
mlirlite.build_store(builder, sv4, field_ptr)
return b_val
return b_val
def translate_c_addr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Return the variable's alloca pointer without loading it."""
if cl is None: return None
cargs: list[ast.AST*]* = cl.args
if cargs is None or len(cargs) < 1: return None
arg0: ast.AST* = cargs.get(0)
if arg0 is None: return None
# Name form: c.Addr(var) returns the variable's alloca (no load)
if arg0.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(arg0)
if nm.id is None: return None
if trans is not None:
# self/cls object refs: c.Addr(self) returns the object pointer
# itself (Ptr(Struct)), not the address of the pointer variable.
if string.strcmp(nm.id, "self") == 0 or string.strcmp(nm.id, "cls") == 0:
self_entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if self_entry is not None and self_entry.Alloca is not None and self_entry.Alloca.Ty is not None:
match self_entry.Alloca.Ty:
case mlirlite.LLVMType.Ptr(inner1):
match inner1:
case mlirlite.LLVMType.Ptr(inner2):
obj_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, inner2)
return mlirlite.build_load(builder, obj_ty, self_entry.Alloca)
case _:
pass
case _:
pass
# nonlocal vars: read the object pointer from env (load_nonlocal_var);
# lookup_var must not hit the parent frame's alloca across functions.
if trans is not None:
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
nl_addr: mlirlite.Value* = self.Gen.StmtGenDecl.load_nonlocal_var(trans, nm.id)
if nl_addr is not None: return nl_addr
return_var: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
# REPL session var addressing: c.Addr(x) yields the ctx slot address.
# Runs after local lookup and before module globals/functions.
if return_var is None and trans is not None and trans.ReplCtx is not None:
sess_addr: mlirlite.Value* = self.Gen.VarGen.repl_try_addr(trans, nm.id, arg0)
if sess_addr is not None: return sess_addr
if return_var is None and mod is not None:
# Module global c.Addr(g) returns the global pointer; wrap via make_global_ref into a quoted Ptr(Ty) so ptrtoint renders correctly.
gv_ref_nm: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(mod, nm.id)
if gv_ref_nm is not None and gv_ref_nm.Name is not None and gv_ref_nm.Ty is not None: return_var = self.Gen.ExprGenAttr.make_global_ref(pool, gv_ref_nm.Name, gv_ref_nm.Ty)
# Module function address: c.Addr(fn) yields the symbol itself.
# Previously it returned None and fell through to an undefined @Addr.
if return_var is None and mod is not None:
f_addr_ref: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, nm.id)
if f_addr_ref is not None:
f_addr_name: str = mlirlite.function_get_name(f_addr_ref)
if f_addr_name is not None:
# Param chains and ParamNode chains are unrelated classes:
# rebuild via _FuncTyFromFunction.
fn_ty_ca: mlirlite.LLVMType* = self.Gen.CallGenBase._FuncTyFromFunction(pool, f_addr_ref)
# Quote the function reference name when needed (sha1-prefixed
# names start with a digit).
fnlen_ca: sizet = string.strlen(f_addr_name)
needs_q_ca: int = self.Gen.ClassGenVTable._vtable_name_needs_quote(f_addr_name)
faddr_buf_ca: str = pool.alloc(fnlen_ca + 4)
if faddr_buf_ca is not None and fn_ty_ca is not None:
if needs_q_ca != 0: viperlib.snprintf(faddr_buf_ca, fnlen_ca + 4, "@\"%s\"", f_addr_name)
else: viperlib.snprintf(faddr_buf_ca, fnlen_ca + 4, "@%s", f_addr_name)
return_var = mlirlite.SSAValue(pool, mlirlite.Ptr(pool, fn_ty_ca), faddr_buf_ca)
# Closure var: c.Addr(name) returns the closure object pointer (one load);
# the closure alloca is i8** holding {fn_ptr, env_ptr}.
if return_var is not None and return_var.Ty is not None:
if self.Gen.CallGenLeafKit.is_closure_name(nm.id) != 0:
i8_ty_cb: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_cb: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_cb)
return mlirlite.build_load(builder, i8_ptr_ty_cb, return_var)
if return_var is not None and return_var.Ty is not None:
# Object-pointer variable (alloca is Ptr(Ptr(Struct))): c.Addr(v)
# returns the object pointer itself (one load), like self/cls.
match return_var.Ty:
case mlirlite.LLVMType.Ptr(addr_inner1):
match addr_inner1:
case mlirlite.LLVMType.Ptr(addr_inner2):
match addr_inner2:
case mlirlite.LLVMType.Struct(_, _, _):
addr_obj_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, addr_inner2)
return mlirlite.build_load(builder, addr_obj_ty, return_var)
case _:
pass
case _:
pass
case _:
pass
return return_var
# Attribute form: c.Addr(self.field) takes the field address (GEP, no load),
# used by lock ops like atom.__atomic_test_and_set(c.Addr(self.lock_val)).
if arg0.kind() == ast.ASTKind.Attribute:
if trans is None: return None
at_addr: ast.Attribute* = ast.Attribute*(arg0)
if at_addr is None or at_addr.attr is None or at_addr.value is None: return None
# Cross-module function address c.Addr(serial.puts) yields the symbol; do not fall back to a global or it collides with the injected declare.
if at_addr.value.kind() == ast.ASTKind.Name and trans is not None and trans._imported_modules is not None:
mod_nm2: ast.Name* = ast.Name*(at_addr.value)
if mod_nm2.id is not None:
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, mod_nm2.id) != 0:
f_ref2: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, at_addr.attr)
if f_ref2 is not None:
fname2: str = mlirlite.function_get_name(f_ref2)
if fname2 is not None:
# Build an SSAValue with @ name (Ptr(FuncTy)): returning a Function node formats as void; rebuild the type from RetTy+Params (Params is a GSList, use function_get_param_head).
# Same: rebuild the ParamNode chain from the Function's Param chain.
fn_ty2: mlirlite.LLVMType* = self.Gen.CallGenBase._FuncTyFromFunction(pool, f_ref2)
# Quote the function reference name when needed
# (sha1-prefixed names start with a digit).
fnlen2: sizet = string.strlen(fname2)
needs_q2: int = self.Gen.ClassGenVTable._vtable_name_needs_quote(fname2)
faddr_buf2: str = pool.alloc(fnlen2 + 4)
if faddr_buf2 is not None and fn_ty2 is not None:
if needs_q2 != 0: viperlib.snprintf(faddr_buf2, fnlen2 + 4, "@\"%s\"", fname2)
else: viperlib.snprintf(faddr_buf2, fnlen2 + 4, "@%s", fname2)
return mlirlite.SSAValue(pool, mlirlite.Ptr(pool, fn_ty2), faddr_buf2)
else:
# Module-qualified global c.Addr(gdt.gp) yields the symbol; wrap via make_global_ref into a quoted Ptr(Ty) (a raw GlobalVariable renders "ptrtoint void").
g_ref2: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(mod, at_addr.attr)
if g_ref2 is not None and g_ref2.Name is not None and g_ref2.Ty is not None: return self.Gen.ExprGenAttr.make_global_ref(pool, g_ref2.Name, g_ref2.Ty)
# Resolve the base (Name var / Subscript array element) to an object pointer
obj_ptr: mlirlite.Value* = None
struct_ty: mlirlite.LLVMType* = None
if at_addr.value.kind() == ast.ASTKind.Subscript:
# c.Addr(s.threads[0].rsp): base is an array element, so GEP to the
# element first, then to the field; Name-only base used to fail.
sub_base: ast.Subscript* = ast.Subscript*(at_addr.value)
if sub_base.value is None or sub_base.slice is None: return None
bval: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_base.value, None, 0, trans)
bidx: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_base.slice, None, 0, trans)
if bval is None or bidx is None or bval.Ty is None: return None
bpointee: mlirlite.LLVMType* = None
if self.Gen.ExprGenBase.is_ptr_type(bval.Ty) != 0: bpointee = bval.Ty.Pointee
if bpointee is not None:
match bpointee:
case mlirlite.LLVMType.Array(bet, _):
obj_ptr = mlirlite.build_gep_array(builder, bpointee, bet, bval, bidx)
struct_ty = bet
case _:
return None
else: return None
elif at_addr.value.kind() == ast.ASTKind.Name:
self_nm: ast.Name* = ast.Name*(at_addr.value)
if self_nm.id is None: return None
self_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, self_nm.id)
if self_alloca is None or self_alloca.Ty is None: return None
# Extract the struct type from the self type (Ptr(Struct) or Ptr(Ptr(Struct)))
self_is_ptr_ptr: int = 0
match self_alloca.Ty:
case mlirlite.LLVMType.Ptr(inner1):
match inner1:
case mlirlite.LLVMType.Ptr(inner2):
struct_ty = inner2
self_is_ptr_ptr = 1
case _:
struct_ty = inner1
case _:
return None
# When self_alloca is Ptr(Ptr(Struct)), load it into a Ptr(Struct) object pointer
obj_ptr = self_alloca
if self_is_ptr_ptr == 1:
self_obj_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, struct_ty)
obj_ptr = mlirlite.build_load(builder, self_obj_ty, self_alloca)
else: return None
if obj_ptr is None or struct_ty is None: return None
# Look up the field
fe_addr: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(struct_ty, at_addr.attr)
if fe_addr is None: return None
# GEP to the field address
return mlirlite.build_gep_struct(builder, struct_ty, fe_addr.Ty, obj_ptr, fe_addr.Index)
# Subscript form: c.Addr(arr[i]) yields the element address (GEP, no load),
# via get_subscript_ptr which handles nested indices and ptr/str* forms.
if arg0.kind() == ast.ASTKind.Subscript:
if trans is None: return None
elem_ptr_ca: mlirlite.Value* = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, arg0, trans)
if elem_ptr_ca is not None: return elem_ptr_ca
# Fallback: single-level array base GEP (when get_subscript_ptr misses)
sub_addr: ast.Subscript* = ast.Subscript*(arg0)
if sub_addr is None or sub_addr.value is None or sub_addr.slice is None: return None
arr_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_addr.value, None, 0, trans)
if arr_val is None or arr_val.Ty is None: return None
idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_addr.slice, None, 0, trans)
if idx_val is None: return None
# Index zext is handled inside mlirlite.build_gep/build_gep_array (single source of truth).
arr_pointee_ca: mlirlite.LLVMType* = None
if self.Gen.ExprGenBase.is_ptr_type(arr_val.Ty) != 0: arr_pointee_ca = arr_val.Ty.Pointee
if arr_pointee_ca is not None:
match arr_pointee_ca:
case mlirlite.LLVMType.Array(arr_et_ca, _):
return mlirlite.build_gep_array(builder, arr_pointee_ca, arr_et_ca, arr_val, idx_val)
case _:
# Scalar/pointer elements (e.g. string a[4]) use a simple GEP;
# a scalar pointee previously fell through to an undefined @Addr.
return mlirlite.build_gep(builder, arr_pointee_ca, arr_val, idx_val)
return None
# Call form: c.Addr(expr) evaluates expr into a temp entry alloca and
# returns its address, e.g. tt: Coroutine = c.Addr(task()).
if arg0.kind() == ast.ASTKind.Call:
call_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg0, None, 0, trans)
if call_val is None or call_val.Ty is None: return None
tmp_slot: mlirlite.Value* = mlirlite.build_alloca_at_entry(builder, call_val.Ty)
if tmp_slot is None: return None
mlirlite.build_store(builder, call_val, tmp_slot)
return tmp_slot
# Other arg0 forms fail-fast: c.Addr is a compile-time instruction and must
# never fall through to a runtime call @Addr.
self.Gen.TypeGenErr.fatal_error(cl, f"c.Addr unsupported operand form (kind={arg0.kind()}); c.Addr only supports variable/global/function/array element/field", "TypeError")
return None
def translate_c_load(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Load *b and store it into *a."""
if cl is None: return None
cargs: list[ast.AST*]* = cl.args
if cargs is None or len(cargs) < 2: return None
dst_ptr: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), None, 0, trans)
src_ptr: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(1), None, 0, trans)
if dst_ptr is None or src_ptr is None: return None
# Determine the pointee type (prefer src's pointee, fall back to dst's)
pointee: mlirlite.LLVMType* = None
if src_ptr.Ty is not None: pointee = src_ptr.Ty.Pointee
if pointee is None and dst_ptr.Ty is not None: pointee = dst_ptr.Ty.Pointee
if pointee is None: return None
# bitcast src to pointee* and load
target_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, pointee)
src_cast: mlirlite.Value* = mlirlite.build_bitcast(builder, src_ptr, target_ptr_ty)
if src_cast is None: return None
loaded: mlirlite.Value* = mlirlite.build_load(builder, pointee, src_cast)
if loaded is None: return None
# bitcast dst to pointee* and store
dst_cast: mlirlite.Value* = mlirlite.build_bitcast(builder, dst_ptr, target_ptr_ty)
if dst_cast is None: return None
mlirlite.build_store(builder, loaded, dst_cast)
return loaded
def _asm_append_cstr(self, dst: str, dst_size: sizet, src: str, what: str = "inline asm"):
"""Append the C string src to the end of dst; fail-fast when it does not fit.
Silent truncation corrupts the generated text (a template cut in the middle of an
instruction yields a bogus llc/verifier error much later), so overflow is reported
here at the real cause.
"""
if dst is None or src is None: return
dlen: sizet = string.strlen(dst)
slen: sizet = string.strlen(src)
# Capacity includes the terminating NUL.
if dlen + slen + 1 > dst_size:
self.Gen.TypeGenErr.fatal_error(None, f"{what} text does not fit the fixed buffer: {dlen} byte(s) already used + {slen} byte(s) to append + 1 NUL > capacity {dst_size} byte(s); shorten or split the template (silent truncation would generate broken code)", "TypeError")
return
i: sizet = 0
while i < slen:
dst[dlen + i] = src[i]
i += 1
dst[dlen + i] = '\0'
def _asm_mangle_global_symbols(self, pool: memhub.MemBuddy*, asm_text: str, asm_size: sizet, mod: mlirlite.LLVMModule*) -> str:
"""Fail-fast when inline asm references a SHA1-mangled module global by bare name."""
if asm_text is None or mod is None: return asm_text
src_len: sizet = string.strlen(asm_text)
if src_len == 0: return asm_text
out: str = pool.alloc(asm_size * 2)
if out is None: return asm_text
out[0] = '\0'
out_pos: sizet = 0
i: sizet = 0
while i < src_len:
matched: int = 0
cur: mlirlite.GlobalVariable* = mod.GlobalHead
while cur is not None:
gname: str = cur.Name
if gname is not None and string.strchr(gname, 46) is not None:
dot_g: str = string.strchr(gname, 46)
bare_g: str = dot_g + 1
bare_len: sizet = string.strlen(bare_g)
if bare_len > 0 and i + bare_len <= src_len:
if string.strncmp(asm_text + i, bare_g, bare_len) == 0:
# Word boundary: neighbors are not identifier chars (letter/digit/_)
prev_ok: int = 1
next_ok: int = 1
if i > 0:
prev_ch: char = asm_text[i - 1]
if (prev_ch >= 'a' and prev_ch <= 'z') or (prev_ch >= 'A' and prev_ch <= 'Z') or (prev_ch >= '0' and prev_ch <= '9') or prev_ch == '_': prev_ok = 0
if i + bare_len < src_len:
next_ch: char = asm_text[i + bare_len]
if (next_ch >= 'a' and next_ch <= 'z') or (next_ch >= 'A' and next_ch <= 'Z') or (next_ch >= '0' and next_ch <= '9') or next_ch == '_': next_ok = 0
if prev_ok != 0 and next_ok != 0:
# fail-fast: an asm template referencing a SHA1-mangled
# global cannot be used in LLVM MC; mark it t.CExport.
self.Gen.TypeGenErr.fatal_error(None, f"Global symbol '{bare_g}' referenced by the asm template has a SHA1 mangling prefix ({gname}); inline assembly must reference bare-name symbols, so mark that global definition as t.CExport", "TypeError")
return asm_text
cur = cur.Next
if matched == 0:
# The output buffer is 2x the input, so this cannot trigger for a plain copy;
# keep it fail-fast so a future longer rewrite (mangled expansion) can never
# silently drop characters.
if out_pos + 1 >= asm_size * 2:
self.Gen.TypeGenErr.fatal_error(None, f"inline asm text overflows the symbol-mangling buffer (capacity {asm_size * 2} byte(s)); shorten the template", "TypeError")
return asm_text
out[out_pos] = asm_text[i]
out_pos += 1
i += 1
out[out_pos] = '\0'
return out
def _asm_escape_for_ir(self, pool: memhub.MemBuddy*, src: str) -> str:
"""Escape asm text for an LLVM IR string literal."""
if src is None: return None
slen: sizet = string.strlen(src)
buf: str = pool.alloc(slen * 4 + 8)
if buf is None: return None
pos: sizet = 0
for ch in src:
if ch == '\n':
buf[pos] = '\\'
pos += 1
buf[pos] = '0'
pos += 1
buf[pos] = 'A'
pos += 1
elif ch == '"':
buf[pos] = '\\'
pos += 1
buf[pos] = '2'
pos += 1
buf[pos] = '2'
pos += 1
elif ch == '\\':
buf[pos] = '\\'
pos += 1
buf[pos] = '5'
pos += 1
buf[pos] = 'C'
pos += 1
else:
buf[pos] = ch
pos += 1
buf[pos] = '\0'
return buf
def _resolve_register(self, attr_name: str) -> str:
"""Map an ASM_DESCR attribute name to its constraint string."""
if attr_name is None: return ""
# Clobber constraints
if string.strcmp(attr_name, "CLOBBER_MEMORY") == 0: return "memory"
if string.strcmp(attr_name, "CLOBBER_CC") == 0: return "cc"
if string.strcmp(attr_name, "CLOBBER_RAX") == 0: return "rax"
if string.strcmp(attr_name, "CLOBBER_RBX") == 0: return "rbx"
if string.strcmp(attr_name, "CLOBBER_RCX") == 0: return "rcx"
if string.strcmp(attr_name, "CLOBBER_RDX") == 0: return "rdx"
if string.strcmp(attr_name, "CLOBBER_RSI") == 0: return "rsi"
if string.strcmp(attr_name, "CLOBBER_RDI") == 0: return "rdi"
if string.strcmp(attr_name, "CLOBBER_RBP") == 0: return "rbp"
if string.strcmp(attr_name, "CLOBBER_RSP") == 0: return "rsp"
if string.strcmp(attr_name, "CLOBBER_R8") == 0: return "r8"
if string.strcmp(attr_name, "CLOBBER_R9") == 0: return "r9"
if string.strcmp(attr_name, "CLOBBER_R10") == 0: return "r10"
if string.strcmp(attr_name, "CLOBBER_R11") == 0: return "r11"
if string.strcmp(attr_name, "CLOBBER_R12") == 0: return "r12"
if string.strcmp(attr_name, "CLOBBER_R13") == 0: return "r13"
if string.strcmp(attr_name, "CLOBBER_R14") == 0: return "r14"
if string.strcmp(attr_name, "CLOBBER_R15") == 0: return "r15"
if string.strcmp(attr_name, "CLOBBER_EAX") == 0: return "eax"
if string.strcmp(attr_name, "CLOBBER_EBX") == 0: return "ebx"
if string.strcmp(attr_name, "CLOBBER_ECX") == 0: return "ecx"
if string.strcmp(attr_name, "CLOBBER_EDX") == 0: return "edx"
if string.strcmp(attr_name, "CLOBBER_ESI") == 0: return "esi"
if string.strcmp(attr_name, "CLOBBER_EDI") == 0: return "edi"
if string.strcmp(attr_name, "CLOBBER_DX") == 0: return "dx"
if string.strcmp(attr_name, "CLOBBER_AX") == 0: return "ax"
if string.strcmp(attr_name, "CLOBBER_CX") == 0: return "cx"
if string.strcmp(attr_name, "CLOBBER_SI") == 0: return "si"
if string.strcmp(attr_name, "CLOBBER_DI") == 0: return "di"
# 8-bit register clobbers (aligned with the t.py constant set): a missing AL
# dropped ~{al} on `rep stosb` and let LLVM clobber the page address in RAX.
if string.strcmp(attr_name, "CLOBBER_AL") == 0: return "al"
if string.strcmp(attr_name, "CLOBBER_AH") == 0: return "ah"
if string.strcmp(attr_name, "CLOBBER_BL") == 0: return "bl"
if string.strcmp(attr_name, "CLOBBER_BH") == 0: return "bh"
if string.strcmp(attr_name, "CLOBBER_CL") == 0: return "cl"
if string.strcmp(attr_name, "CLOBBER_CH") == 0: return "ch"
if string.strcmp(attr_name, "CLOBBER_DL") == 0: return "dl"
if string.strcmp(attr_name, "CLOBBER_DH") == 0: return "dh"
# Output constraints
if string.strcmp(attr_name, "OUTPUT_REG") == 0: return "=r"
if string.strcmp(attr_name, "OUTPUT_MEM") == 0: return "=m"
if string.strcmp(attr_name, "OUTPUT_EAX") == 0: return "=a"
if string.strcmp(attr_name, "OUTPUT_EBX") == 0: return "=b"
if string.strcmp(attr_name, "OUTPUT_ECX") == 0: return "=c"
if string.strcmp(attr_name, "OUTPUT_EDX") == 0: return "=d"
if string.strcmp(attr_name, "OUTPUT_ESI") == 0: return "=S"
if string.strcmp(attr_name, "OUTPUT_EDI") == 0: return "=D"
if string.strcmp(attr_name, "OUTPUT_RAX") == 0: return "=a"
if string.strcmp(attr_name, "OUTPUT_RBX") == 0: return "=b"
if string.strcmp(attr_name, "OUTPUT_RCX") == 0: return "=c"
if string.strcmp(attr_name, "OUTPUT_RDX") == 0: return "=d"
if string.strcmp(attr_name, "OUTPUT_RSI") == 0: return "=S"
if string.strcmp(attr_name, "OUTPUT_RDI") == 0: return "=D"
# Input/register constraints
if string.strcmp(attr_name, "REG_ANY") == 0: return "r"
if string.strcmp(attr_name, "REG_EAX") == 0: return "a"
if string.strcmp(attr_name, "REG_EBX") == 0: return "b"
if string.strcmp(attr_name, "REG_ECX") == 0: return "c"
if string.strcmp(attr_name, "REG_EDX") == 0: return "d"
if string.strcmp(attr_name, "REG_ESI") == 0: return "S"
if string.strcmp(attr_name, "REG_EDI") == 0: return "D"
if string.strcmp(attr_name, "REG_RAX") == 0: return "a"
if string.strcmp(attr_name, "REG_RBX") == 0: return "b"
if string.strcmp(attr_name, "REG_RCX") == 0: return "c"
if string.strcmp(attr_name, "REG_RDX") == 0: return "d"
if string.strcmp(attr_name, "REG_RSI") == 0: return "S"
if string.strcmp(attr_name, "REG_RDI") == 0: return "D"
if string.strcmp(attr_name, "INPUT_REG") == 0: return "r"
if string.strcmp(attr_name, "INPUT_MEM") == 0: return "m"
if string.strcmp(attr_name, "INPUT_EAX") == 0: return "a"
if string.strcmp(attr_name, "INPUT_EBX") == 0: return "b"
if string.strcmp(attr_name, "INPUT_ECX") == 0: return "c"
if string.strcmp(attr_name, "INPUT_EDX") == 0: return "d"
if string.strcmp(attr_name, "INPUT_ESI") == 0: return "S"
if string.strcmp(attr_name, "INPUT_EDI") == 0: return "D"
if string.strcmp(attr_name, "INPUT_RAX") == 0: return "a"
if string.strcmp(attr_name, "INPUT_RBX") == 0: return "b"
if string.strcmp(attr_name, "INPUT_RCX") == 0: return "c"
if string.strcmp(attr_name, "INPUT_RDX") == 0: return "d"
if string.strcmp(attr_name, "INPUT_RSI") == 0: return "S"
if string.strcmp(attr_name, "INPUT_RDI") == 0: return "D"
return ""
def _resolve_asm_descr(self, pool: memhub.MemBuddy*, node: ast.AST*) -> str:
"""Resolve a constraint expression recursively into its constraint string."""
if node is None: return ""
k: int = node.kind()
# BinOp with BitOr: concatenate left and right
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.op == ast.OpKind.BitOr:
left_str: str = self._resolve_asm_descr(pool, bop.left)
right_str: str = self._resolve_asm_descr(pool, bop.right)
llen: sizet = string.strlen(left_str)
rlen: sizet = string.strlen(right_str)
buf: str = pool.alloc(llen + rlen + 1)
if buf is None: return ""
string.strcpy(buf, left_str)
for i in range(int(rlen)): buf[llen + i] = right_str[i]
buf[llen + rlen] = '\0'
return buf
return ""
# Attribute: ASM_DESCR.XXX
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
mod_nm2: ast.Name* = ast.Name*(at.value)
if mod_nm2.id is not None and string.strcmp(mod_nm2.id, "ASM_DESCR") == 0: return self._resolve_register(at.attr)
return ""
# 裸枚举名(不带 ASM_DESCR. / t. 前缀):`op=[CLOBBER_MEMORY, CLOBBER_RAX]`。
# 只有确实解析成 ASM_DESCR 成员时才接受(_resolve_register 对未知名给不出非空串),
# 否则返回空串交由调用方按"未知约束"处理,避免把普通变量名误当约束串。
if k == ast.ASTKind.Name:
nm_b: ast.Name* = ast.Name*(node)
if nm_b.id is not None:
rs_b: str = self._resolve_register(nm_b.id)
if rs_b is not None and rs_b[0] != '\0': return rs_b
return ""
# Constant: string constant
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn.const_kind == ast.CONST_STR: return cn.str_val
return ""
return ""
def _asm_get_operand(self, base: GenTypes.AsmOperand*, idx: int) -> GenTypes.AsmOperand*:
"""Return a pointer to operand idx in the operand array."""
entry_size: sizet = GenTypes.AsmOperand.__sizeof__()
entry_addr: uint64 = uint64(base) + idx * entry_size
return GenTypes.AsmOperand*(entry_addr)
def _asm_is_c_call(self, node: ast.Call*) -> str:
"""Return the canonical name for an operand-marker call, else None."""
if node is None or node.func is None: return None
if node.func.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node.func)
if nm.id is not None:
if string.strcmp(nm.id, "asminp") == 0: return "AsmInp"
if string.strcmp(nm.id, "asmout") == 0: return "AsmOut"
if string.strcmp(nm.id, "irinp") == 0: return "LInp"
if string.strcmp(nm.id, "irout") == 0: return "LOut"
return None
return None
def _asm_name_is_mangled(self, name: str) -> int:
"""Return 1 when the LLVM name carries a SHA1-mangled prefix."""
if name is None: return 0
n: str = name
nlen: sizet = string.strlen(n)
# Strip the '@"..."' wrapper (reference name)
if nlen >= 3 and n[0] == '@' and n[1] == '"' and n[nlen - 1] == '"': n = n + 2
elif nlen >= 1 and n[0] == '@': n = n + 1
if GenApi.sha1_prefix_len(n) != 0: return 1
return 0
def _asm_add_operand(self, operands: GenTypes.AsmOperand*, count: int, val: mlirlite.Value*, constraint: str, is_output: int) -> int:
"""Append an operand, dedup by value and direction, and return the new count."""
# fail-fast: the operand array is fixed at ASM_MAX_OPERANDS slots; dropping the
# excess operand here would silently change the generated constraint/argument lists.
if count >= ASM_MAX_OPERANDS:
self.Gen.TypeGenErr.fatal_error(None, f"inline asm has more than {ASM_MAX_OPERANDS} operands (compiler limit); split the asm block into several ones", "TypeError")
return count
# fail-fast: AsmInp/AsmOut operands must not carry SHA1-mangled symbols;
# LLVM MC cannot parse digit-leading names, so mark them t.CExport.
if val is not None and val.Name is not None:
if self._asm_name_is_mangled(val.Name) != 0: self.Gen.TypeGenErr.fatal_error(None, f"__asm__ operand (AsmInp/AsmOut) does not allow a symbol with a SHA1 mangling prefix '{val.Name}'; mark the corresponding function/global definition as t.CExport (bare name)", "TypeError")
if val is not None:
for ei in range(int(count)):
eop: GenTypes.AsmOperand* = self._asm_get_operand(operands, ei)
if eop is not None and eop.Value == val and eop.IsOutput == is_output: return count
op: GenTypes.AsmOperand* = self._asm_get_operand(operands, count)
op.Value = val
# Default constraint: AsmInp(reg) with one arg has no descr, causing an
# llc mismatch. Input defaults to "r", output to "=r".
if constraint is None or string.strlen(constraint) == 0:
if is_output != 0: constraint = "=r"
else: constraint = "r"
op.Constraint = constraint
op.IsOutput = is_output
return count + 1
def translate_c_asm(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Translate an __asm__ block and return its result Value or None."""
if cl is None: return None
cargs: list[ast.AST*]* = cl.args
if cargs is None or len(cargs) < 1: return None
# Allocate the operand array
operand_size: sizet = GenTypes.AsmOperand.__sizeof__()
operands: GenTypes.AsmOperand* = pool.alloc(operand_size * ASM_MAX_OPERANDS)
if operands is None: return None
string.memset(operands, 0, operand_size * ASM_MAX_OPERANDS)
# Clobber name array: 8 bytes/slot, capacity ASM_MAX_CLOBBERS, decoupled from
# the operand limit; a single-level rawptr buffer with slots cast to str*.
clobbers: rawptr = pool.alloc(8 * ASM_MAX_CLOBBERS)
if clobbers is None: return None
string.memset(clobbers, 0, 8 * ASM_MAX_CLOBBERS)
# Allocate the asm text buffer
asm_text: str = pool.alloc(ASM_TEXT_BUF_SIZE)
if asm_text is None: return None
asm_text[0] = '\0'
operand_count: int = 0
clobber_count: int = 0
fv_count: int = 0
# Cache the first output's element type at collection time (like the Python
# reference), avoiding an unstable re-read that left ret as Void.
out_ret_ty_first: mlirlite.LLVMType* = None
# Cache all output element types in order (multi-output like vrandom's
# val+ok), taken from the alloca pointee at collection time.
out_ptys: sizet* = pool.alloc(8 * ASM_MAX_OPERANDS)
out_pty_n: int = 0
if out_ptys is not None: string.memset(out_ptys, 0, 8 * ASM_MAX_OPERANDS)
# fv index array: records each FormattedValue's original operand index
fv_orig_indices: int* = pool.alloc(4 * ASM_MAX_OPERANDS)
if fv_orig_indices is None: return None
string.memset(fv_orig_indices, 0, 4 * ASM_MAX_OPERANDS)
# 1. Parse the asm template (JoinedStr or Constant) - first pass: collect
# operands only; $N placeholders are numbered after reordering.
tmpl_node: ast.AST* = cargs.get(0)
if tmpl_node is not None:
tk: int = tmpl_node.kind()
if tk == ast.ASTKind.JoinedStr:
js: ast.JoinedStr* = ast.JoinedStr*(tmpl_node)
jvalues: list[ast.AST*]* = js.values
if jvalues is not None:
jvlen: sizet = len(jvalues)
for vi in range(int(jvlen)):
part: ast.AST* = jvalues.get(vi)
if part is None: continue
pk: int = part.kind()
if pk == ast.ASTKind.FormattedValue:
fv: ast.FormattedValue* = ast.FormattedValue*(part)
fexpr: ast.AST* = fv.value
if fexpr is None or fexpr.kind() != ast.ASTKind.Call: continue
fcall: ast.Call* = ast.Call*(fexpr)
attr_nm: str = self._asm_is_c_call(fcall)
if attr_nm is None: continue
is_out: int = 0
if string.strcmp(attr_nm, "AsmOut") == 0: is_out = 1
elif string.strcmp(attr_nm, "AsmInp") == 0: is_out = 0
else: continue
fargs: list[ast.AST*]* = fcall.args
if fargs is None or len(fargs) < 1: continue
val_node: ast.AST* = fargs.get(0)
cons_str: str = ""
if len(fargs) >= 2: cons_str = self._resolve_asm_descr(pool, fargs.get(1))
if is_out != 0:
# Output: get the variable alloca (Name local/global or Attribute field address)
if val_node is None: continue
alloca: mlirlite.Value* = None
if val_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(val_node)
if nm.id is None: continue
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None:
# Module-level global (lookup_var only sees locals):
# fall back to make_global_ref into a quoted Ptr(Ty).
gv_asm: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(mod, nm.id)
if gv_asm is not None and gv_asm.Name is not None and gv_asm.Ty is not None: alloca = self.Gen.ExprGenAttr.make_global_ref(pool, gv_asm.Name, gv_asm.Ty)
elif val_node.kind() == ast.ASTKind.Attribute:
# AsmOut(p.pml4_root): GEP to the field address, like
# translate_c_addr's Attribute branch; skipping it left a void operand.
at_a: ast.Attribute* = ast.Attribute*(val_node)
if at_a.attr is not None and at_a.value is not None and at_a.value.kind() == ast.ASTKind.Name:
base_nm_a: ast.Name* = ast.Name*(at_a.value)
if base_nm_a.id is not None:
base_alloca_a: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, base_nm_a.id)
if base_alloca_a is not None and base_alloca_a.Ty is not None:
struct_ty_a: mlirlite.LLVMType* = None
self_is_ptr_ptr_a: int = 0
match base_alloca_a.Ty:
case mlirlite.LLVMType.Ptr(inner1):
match inner1:
case mlirlite.LLVMType.Ptr(inner2):
struct_ty_a = inner2
self_is_ptr_ptr_a = 1
case _:
struct_ty_a = inner1
case _:
pass
if struct_ty_a is not None:
fe_a: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(struct_ty_a, at_a.attr)
if fe_a is not None:
obj_ptr_a: mlirlite.Value* = base_alloca_a
if self_is_ptr_ptr_a == 1: obj_ptr_a = mlirlite.build_load(builder, mlirlite.Ptr(pool, struct_ty_a), base_alloca_a)
if obj_ptr_a is not None: alloca = mlirlite.build_gep_struct(builder, struct_ty_a, fe_a.Ty, obj_ptr_a, fe_a.Index)
if alloca is None: continue
operand_count = self._asm_add_operand(operands, operand_count, alloca, cons_str, 1)
if out_ret_ty_first is None and alloca is not None and alloca.Ty is not None and alloca.Ty.Pointee != 0: out_ret_ty_first = alloca.Ty.Pointee
if out_ptys is not None and out_pty_n < ASM_MAX_OPERANDS and alloca is not None and alloca.Ty is not None and alloca.Ty.Pointee != 0:
out_ptys[out_pty_n] = uint64(alloca.Ty.Pointee)
out_pty_n += 1
else:
# Input: translate the value
ival: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, val_node, None, 0, trans)
if ival is None: continue
operand_count = self._asm_add_operand(operands, operand_count, ival, cons_str, 0)
# Record the fv's original operand index
if fv_count < ASM_MAX_OPERANDS:
fv_orig_indices[fv_count] = operand_count - 1
fv_count += 1
# 2. Parse keyword args out=/inp=/op=/clobber=
keywords: list[ast.AST*]* = cl.keywords
if keywords is not None:
kwlen: sizet = len(keywords)
for ki in range(int(kwlen)):
kw: ast.Keyword* = ast.Keyword*(keywords.get(ki))
if kw is None or kw.arg is None or kw.value is None: continue
kwk: int = kw.value.kind()
if string.strcmp(kw.arg, "out") == 0:
# Output operand list
if kwk != ast.ASTKind.List: continue
out_lst: ast.List* = ast.List*(kw.value)
out_elts: list[ast.AST*]* = out_lst.elts
if out_elts is None: continue
oelen: sizet = len(out_elts)
for oi in range(int(oelen)):
oelt: ast.AST* = out_elts.get(oi)
if oelt is None or oelt.kind() != ast.ASTKind.Call: continue
ocall: ast.Call* = ast.Call*(oelt)
oattr: str = self._asm_is_c_call(ocall)
if oattr is None or string.strcmp(oattr, "AsmOut") != 0: continue
oargs: list[ast.AST*]* = ocall.args
if oargs is None or len(oargs) < 1: continue
oval_node: ast.AST* = oargs.get(0)
ocons: str = ""
if len(oargs) >= 2: ocons = self._resolve_asm_descr(pool, oargs.get(1))
if oval_node is None or oval_node.kind() != ast.ASTKind.Name: continue
onm: ast.Name* = ast.Name*(oval_node)
if onm.id is None: continue
oalloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, onm.id)
if oalloca is None: continue
operand_count = self._asm_add_operand(operands, operand_count, oalloca, ocons, 1)
if out_ret_ty_first is None and oalloca is not None and oalloca.Ty is not None and oalloca.Ty.Pointee != 0: out_ret_ty_first = oalloca.Ty.Pointee
if out_ptys is not None and out_pty_n < ASM_MAX_OPERANDS and oalloca is not None and oalloca.Ty is not None and oalloca.Ty.Pointee != 0:
out_ptys[out_pty_n] = uint64(oalloca.Ty.Pointee)
out_pty_n += 1
elif string.strcmp(kw.arg, "inp") == 0 or string.strcmp(kw.arg, "inputs") == 0:
# Input operand list
if kwk != ast.ASTKind.List: continue
in_lst: ast.List* = ast.List*(kw.value)
in_elts: list[ast.AST*]* = in_lst.elts
if in_elts is None: continue
ielen: sizet = len(in_elts)
for ii in range(int(ielen)):
ielt: ast.AST* = in_elts.get(ii)
if ielt is None or ielt.kind() != ast.ASTKind.Call: continue
icall: ast.Call* = ast.Call*(ielt)
iattr: str = self._asm_is_c_call(icall)
if iattr is None or string.strcmp(iattr, "AsmInp") != 0: continue
iargs: list[ast.AST*]* = icall.args
if iargs is None or len(iargs) < 1: continue
ival_node: ast.AST* = iargs.get(0)
icons: str = ""
if len(iargs) >= 2: icons = self._resolve_asm_descr(pool, iargs.get(1))
ival2: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ival_node, None, 0, trans)
if ival2 is None: continue
operand_count = self._asm_add_operand(operands, operand_count, ival2, icons, 0)
elif string.strcmp(kw.arg, "op") == 0 or string.strcmp(kw.arg, "clobber") == 0:
# Clobber list
if kwk != ast.ASTKind.List: continue
cl_lst: ast.List* = ast.List*(kw.value)
cl_elts: list[ast.AST*]* = cl_lst.elts
if cl_elts is None: continue
clen: sizet = len(cl_elts)
for ci in range(int(clen)):
celt: ast.AST* = cl_elts.get(ci)
if celt is None: continue
cstr: str = self._resolve_asm_descr(pool, celt)
if cstr is None or cstr[0] == '\0': continue
# fail-fast: the clobber slot array is fixed at ASM_MAX_CLOBBERS;
# dropping the excess would silently weaken the clobber list.
if clobber_count >= ASM_MAX_CLOBBERS:
self.Gen.TypeGenErr.fatal_error(celt, f"inline asm has more than {ASM_MAX_CLOBBERS} clobbers (compiler limit); remove duplicates or split the asm block", "TypeError")
return None
cl_addr: uint64 = uint64(clobbers) + clobber_count * 8
cl_ptr: str* = (str*)(cl_addr)
cl_ptr[0] = cstr
clobber_count += 1
# 3. Parse the second positional arg as the clobber list
if len(cargs) >= 2:
pos_node: ast.AST* = cargs.get(1)
if pos_node is not None and pos_node.kind() == ast.ASTKind.List:
pos_lst: ast.List* = ast.List*(pos_node)
pos_elts: list[ast.AST*]* = pos_lst.elts
if pos_elts is not None:
plen: sizet = len(pos_elts)
for pi in range(int(plen)):
pelt: ast.AST* = pos_elts.get(pi)
if pelt is None: continue
pstr: str = self._resolve_asm_descr(pool, pelt)
if pstr is None or pstr[0] == '\0': continue
if clobber_count >= ASM_MAX_CLOBBERS:
self.Gen.TypeGenErr.fatal_error(pelt, f"inline asm has more than {ASM_MAX_CLOBBERS} clobbers (compiler limit); remove duplicates or split the asm block", "TypeError")
return None
pcl_addr: uint64 = uint64(clobbers) + clobber_count * 8
pcl_ptr: str* = (str*)(pcl_addr)
pcl_ptr[0] = pstr
clobber_count += 1
# 4. Count outputs/inputs and determine the return type
output_count: int = 0
input_count: int = 0
first_output_idx: int = -1
for oi2 in range(int(operand_count)):
op2: GenTypes.AsmOperand* = self._asm_get_operand(operands, oi2)
if op2.IsOutput != 0:
if first_output_idx < 0: first_output_idx = oi2
output_count += 1
else: input_count += 1
# Determine the return type. Multiple outputs return an anonymous struct
# {ty1, ty2, ...}; LLVM asm requires output constraints == struct elements.
ret_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
if output_count > 0 and first_output_idx >= 0:
if output_count > 1:
# The multi-output struct field list is a ParamNode chain (not
# Function*); annotating it as Function* broke implicit conversion.
out_pn_head: mlirlite.ParamNode* = None
out_pn_last: mlirlite.ParamNode* = None
out_pn_n: int = 0
out_seq: int = 0
for oi5 in range(int(operand_count)):
op5: GenTypes.AsmOperand* = self._asm_get_operand(operands, oi5)
if op5 is None or op5.IsOutput == 0: continue
pty5: mlirlite.LLVMType* = None
# Prefer the type cached in order at collection, then fall back to reading the slot
if out_ptys is not None and out_seq < out_pty_n: pty5 = mlirlite.LLVMType*(out_ptys[out_seq])
elif op5.Value is not None and op5.Value.Ty is not None and op5.Value.Ty.Pointee != 0: pty5 = op5.Value.Ty.Pointee
out_seq += 1
if pty5 is None: continue
pn5: mlirlite.ParamNode* = mlirlite.new_param_node(pool, pty5)
if pn5 is not None:
if out_pn_head is None: out_pn_head = pn5
else: out_pn_last.Next = pn5
out_pn_last = pn5
out_pn_n += 1
if out_pn_head is not None and out_pn_n > 0: ret_ty = mlirlite.Struct(pool, out_pn_head, out_pn_n, None)
else:
self.Gen.TypeGenErr.fatal_error(cl, "__asm__ output operand type missing (variable alloca type degraded)", "TypeError")
ret_ty = mlirlite.Int32(pool)
else:
out_op: GenTypes.AsmOperand* = self._asm_get_operand(operands, first_output_idx)
out_pt: mlirlite.LLVMType* = None
# Prefer the element type cached at collection, then fall back to reading the slot
if out_ret_ty_first is not None: out_pt = out_ret_ty_first
elif out_op is not None and out_op.Value is not None and out_op.Value.Ty is not None and out_op.Value.Ty.Pointee != 0: out_pt = out_op.Value.Ty.Pointee
if out_pt is not None: ret_ty = out_pt
else:
self.Gen.TypeGenErr.fatal_error(cl, "__asm__ output operand type missing (variable alloca type degraded)", "TypeError")
ret_ty = mlirlite.Int32(pool)
# Determine void directly from output_count to avoid match type issues
is_void: int = 1
if output_count > 0: is_void = 0
# [FIX] Outputs exist but ret is still Void: rescan all output operands
# before emitting, otherwise a named void asm reaches llc. Else fail-fast.
if is_void == 0:
match ret_ty:
case mlirlite.LLVMType.Void():
ret_re: mlirlite.LLVMType* = None
for re_i in range(int(operand_count)):
re_op: GenTypes.AsmOperand* = self._asm_get_operand(operands, re_i)
if re_op is None or re_op.IsOutput == 0: continue
if re_op.Value is not None and re_op.Value.Ty is not None and re_op.Value.Ty.Pointee != 0:
ret_re = re_op.Value.Ty.Pointee
break
if ret_re is None: self.Gen.TypeGenErr.fatal_error(cl, "__asm__ output operand type missing (variable alloca type degraded)", "TypeError")
ret_ty = ret_re
case _:
pass
# 4.5 Reorder operands: outputs first, inputs after (LLVM IR requirement).
# new_order maps new->orig; orig_to_new maps orig->new for $N numbering.
new_order: int* = pool.alloc(4 * (operand_count + 1))
orig_to_new: int* = pool.alloc(4 * (operand_count + 1))
if new_order is None or orig_to_new is None: return None
new_idx: int = 0
# Outputs first
for oi3 in range(int(operand_count)):
op3: GenTypes.AsmOperand* = self._asm_get_operand(operands, oi3)
if op3.IsOutput != 0:
new_order[new_idx] = oi3
new_idx += 1
# Then inputs
for oi4 in range(int(operand_count)):
op4: GenTypes.AsmOperand* = self._asm_get_operand(operands, oi4)
if op4.IsOutput == 0:
new_order[new_idx] = oi4
new_idx += 1
# Build the orig -> new mapping
for ni2 in range(int(operand_count)): orig_to_new[new_order[ni2]] = ni2
# 4.6 Generate asm text (second f-string pass, $N from reordered indices).
# The project uses Intel syntax via the IR inteldialect keyword.
if tmpl_node is not None:
tk2: int = tmpl_node.kind()
if tk2 == ast.ASTKind.JoinedStr:
js2: ast.JoinedStr* = ast.JoinedStr*(tmpl_node)
jvalues2: list[ast.AST*]* = js2.values
if jvalues2 is not None:
jvlen2: sizet = len(jvalues2)
fv_fi: int = 0
for vi2 in range(int(jvlen2)):
part2: ast.AST* = jvalues2.get(vi2)
if part2 is None: continue
pk2: int = part2.kind()
if pk2 == ast.ASTKind.Constant:
cn3: ast.Constant* = ast.Constant*(part2)
if cn3.const_kind == ast.CONST_STR and cn3.str_val is not None: self._asm_append_cstr(asm_text, ASM_TEXT_BUF_SIZE, cn3.str_val)
elif pk2 == ast.ASTKind.FormattedValue:
if fv_fi < fv_count:
orig_idx2: int = fv_orig_indices[fv_fi]
new_idx2: int = orig_to_new[orig_idx2]
ph_buf2: str = pool.alloc(16)
if ph_buf2 is not None:
viperlib.snprintf(ph_buf2, 16, "$%d", new_idx2)
self._asm_append_cstr(asm_text, ASM_TEXT_BUF_SIZE, ph_buf2)
fv_fi += 1
elif tk2 == ast.ASTKind.Constant:
cn4: ast.Constant* = ast.Constant*(tmpl_node)
if cn4.const_kind == ast.CONST_STR and cn4.str_val is not None: self._asm_append_cstr(asm_text, ASM_TEXT_BUF_SIZE, cn4.str_val)
# 5. Build the constraint string in reordered order (outputs first).
# Capacity scales with the operand/clobber count: a 32-entry clobber list alone is
# ~300 bytes, so the old fixed 512-byte cap could silently cut it. Overflow of this
# (or any asm text buffer) is fail-fast in _asm_append_cstr.
const_cap: sizet = ASM_CONST_BUF_SIZE + sizet(operand_count + clobber_count + 1) * 64
const_buf: str = pool.alloc(const_cap)
if const_buf is None: return None
const_buf[0] = '\0'
first_const: int = 1
for ci2 in range(int(operand_count)):
orig_i: int = new_order[ci2]
cop: GenTypes.AsmOperand* = self._asm_get_operand(operands, orig_i)
cons_val: str = cop.Constraint
if cons_val is None: cons_val = "r"
if first_const == 0: self._asm_append_cstr(const_buf, const_cap, ",")
first_const = 0
if cop.IsOutput != 0:
# Output constraint: ensure it starts with =
if cons_val[0] != '=': self._asm_append_cstr(const_buf, const_cap, "=")
self._asm_append_cstr(const_buf, const_cap, cons_val)
else: self._asm_append_cstr(const_buf, const_cap, cons_val)
# Append clobber constraints ~{name}
for cidx in range(int(clobber_count)):
cl_addr2: uint64 = uint64(clobbers) + cidx * 8
cname: str = (str*)(cl_addr2)[0]
if cname is None or cname[0] == '\0': continue
if first_const == 0: self._asm_append_cstr(const_buf, const_cap, ",")
first_const = 0
self._asm_append_cstr(const_buf, const_cap, "~{")
self._asm_append_cstr(const_buf, const_cap, cname)
self._asm_append_cstr(const_buf, const_cap, "}")
# 6. Build the argument list text (input operands only): each arg is type text
# (TypePrint is given a 4096-byte buffer below) plus the value name, so size the
# buffer from that worst case instead of underestimating it (544 bytes/operand
# silently cut wide inlined named structs).
asm_arg_cap: sizet = sizet(operand_count + 1) * 4224 + 64
args_buf: str = pool.alloc(asm_arg_cap)
if args_buf is None: return None
args_buf[0] = '\0'
first_arg: int = 1
for ai3 in range(int(operand_count)):
orig_i2: int = new_order[ai3]
aop: GenTypes.AsmOperand* = self._asm_get_operand(operands, orig_i2)
if aop.IsOutput != 0: continue
aval: mlirlite.Value* = aop.Value
if aval is None or aval.Ty is None or aval.Name is None: continue
if first_arg == 0: self._asm_append_cstr(args_buf, asm_arg_cap, ", ")
first_arg = 0
# Type text
ty_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_CALLGENCBUILTIN_1150)
if ty_buf is not None:
ty_buf[0] = '\0'
mlirlite.TypePrint(ty_buf, 4096, aval.Ty, pool)
self._asm_append_cstr(args_buf, asm_arg_cap, ty_buf)
self._asm_append_cstr(args_buf, asm_arg_cap, " ")
self._asm_append_cstr(args_buf, asm_arg_cap, aval.Name)
# 7. Escape the asm text. First replace bare symbols with mangled names
# (lea rax, [rip + gdt] -> [rip + 054f5e56cbefcaa4.gdt]).
mangled_asm: str = self._asm_mangle_global_symbols(pool, asm_text, ASM_TEXT_BUF_SIZE, mod)
if mangled_asm is None: mangled_asm = asm_text
escaped_asm: str = self._asm_escape_for_ir(pool, mangled_asm)
if escaped_asm is None: return None
# 8. Generate and emit the IR line
# The line buffer is sized after ret_ty_buf is ready (see line_cap below)
ssa_name: str = None
if is_void == 0:
ssa_name = pool.alloc(16)
if ssa_name is not None:
viperlib.snprintf(ssa_name, 16, "%%%d", builder.Counter)
builder.Counter += 1
# Get the return type text
ret_ty_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_CALLGENCBUILTIN_1176)
if ret_ty_buf is not None:
ret_ty_buf[0] = '\0'
mlirlite.TypePrint(ret_ty_buf, 4096, ret_ty, pool)
# Size the line buffer by actual content (type text can be 512 bytes; asm/constraints may be long)
line_cap: sizet = 512 + string.strlen(escaped_asm) + string.strlen(args_buf)
if const_buf is not None: line_cap += string.strlen(const_buf)
if ret_ty_buf is not None: line_cap += string.strlen(ret_ty_buf)
line: str = pool.alloc(line_cap)
if line is None: return None
if is_void == 1:
viperlib.snprintf(line, line_cap, "call %s asm sideeffect inteldialect \"%s\", \"%s\"(%s)", ret_ty_buf, escaped_asm, const_buf, args_buf)
else:
viperlib.snprintf(line, line_cap, "%s = call %s asm sideeffect inteldialect \"%s\", \"%s\"(%s)", ssa_name, ret_ty_buf, escaped_asm, const_buf, args_buf)
# Emit the IR into the current block
mlirlite.block_append_text(pool, builder.CurBlock, line)
# 9. Handle outputs: store results into the output variables
if is_void == 0 and first_output_idx >= 0:
result_val: mlirlite.Value* = mlirlite.SSAValue(pool, ret_ty, ssa_name)
out_emit_idx: int = 0
for oi_out in range(int(operand_count)):
orig_o: int = new_order[oi_out]
oo: GenTypes.AsmOperand* = self._asm_get_operand(operands, orig_o)
if oo is None or oo.IsOutput == 0: continue
if output_count > 1:
# Multi-output: extractvalue index out_emit_idx, then store
if oo.Value is not None and oo.Value.Ty is not None and oo.Value.Ty.Pointee != 0:
ev_name: str = pool.alloc(32)
# Size the line from its actual parts: ret_ty_buf alone can be ~4096
# bytes, so the old fixed cap could cut the instruction in half.
ev_cap: sizet = ASM_LINE_BUF_SIZE + 64
if ret_ty_buf is not None: ev_cap += string.strlen(ret_ty_buf)
if ssa_name is not None: ev_cap += string.strlen(ssa_name)
ev_line: str = pool.alloc(ev_cap)
if ev_name is not None and ev_line is not None:
# ssa_name is "%N"; build a unique "%asm.N.e{i}" (letter-
# initial) so %N.e{i} is not parsed as id plus a bad suffix.
viperlib.snprintf(ev_name, 32, "%%asm.%s.e%d", ssa_name + 1, out_emit_idx)
viperlib.snprintf(ev_line, ev_cap, "%s = extractvalue %s %s, %d", ev_name, ret_ty_buf, ssa_name, out_emit_idx)
mlirlite.block_append_text(pool, builder.CurBlock, ev_line)
ev_val: mlirlite.Value* = mlirlite.SSAValue(pool, oo.Value.Ty.Pointee, ev_name)
mlirlite.build_store(builder, ev_val, oo.Value)
else:
if oo.Value is not None: mlirlite.build_store(builder, result_val, oo.Value)
out_emit_idx += 1
return result_val
return None
def translate_c_llvmir(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Inline raw LLVM IR text into the current basic block; returns None."""
if cl is None or builder is None or pool is None or trans is None: return None
cargs: list[ast.AST*]* = cl.args
if cargs is None or len(cargs) < 1: self.Gen.TypeGenErr.fatal_error(cl, "c.LLVMIR missing template argument", "TypeError")
# LOut writeback records (alloca pointer + register name), up to LLVMIR_MAX_OPS
out_alloca: sizet* = pool.alloc(8 * LLVMIR_MAX_OPS)
out_regname: sizet* = pool.alloc(8 * LLVMIR_MAX_OPS)
if out_alloca is None or out_regname is None: return None
string.memset(out_alloca, 0, 8 * LLVMIR_MAX_OPS)
string.memset(out_regname, 0, 8 * LLVMIR_MAX_OPS)
out_count: int = 0
# Template text buffer
text: str = pool.alloc(LLVMIR_TEMPLATE_BUF)
if text is None: return None
text[0] = '\0'
# 1. Parse the IR template (JoinedStr or Constant), replacing LInp/LOut/exprs with value text
first_arg: ast.AST* = cargs.get(0)
if first_arg is None: return None
tk: int = first_arg.kind()
if tk == ast.ASTKind.JoinedStr:
js: ast.JoinedStr* = ast.JoinedStr*(first_arg)
jvalues: list[ast.AST*]* = js.values
if jvalues is not None:
jvlen: sizet = len(jvalues)
for vi in range(int(jvlen)):
part: ast.AST* = jvalues.get(vi)
if part is None: continue
pk: int = part.kind()
if pk == ast.ASTKind.Constant:
# String literal fragment
cn: ast.Constant* = ast.Constant*(part)
if cn.str_val is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn.str_val, "c.LLVMIR")
elif pk == ast.ASTKind.FormattedValue:
fv: ast.FormattedValue* = ast.FormattedValue*(part)
fexpr: ast.AST* = fv.value
if fexpr is None: continue
# Detect c.LInp / c.LOut
if fexpr.kind() == ast.ASTKind.Call:
fcall: ast.Call* = ast.Call*(fexpr)
attr_nm: str = self._asm_is_c_call(fcall)
if attr_nm is not None:
fargs: list[ast.AST*]* = fcall.args
if fargs is None or len(fargs) < 1: continue
val_node: ast.AST* = fargs.get(0)
if string.strcmp(attr_nm, "LInp") == 0:
# Input operand: translate to an LLVM value reference text (%x or constant)
inp_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, val_node, None, 0, trans)
if inp_val is not None and inp_val.Name is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, inp_val.Name, "c.LLVMIR")
elif string.strcmp(attr_nm, "LOut") == 0:
# Output operand: allocate a unique SSA register
# %llvmirN and record the writeback to the alloca.
if val_node is not None and val_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(val_node)
if nm.id is not None:
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None or out_count >= LLVMIR_MAX_OPS: self.Gen.TypeGenErr.fatal_error(fcall, f"c.LOut variable '{nm.id}' is undefined or the output count exceeds {LLVMIR_MAX_OPS}", "TypeError")
self.Gen.CallGenBase.LLVMIRRegSeq += 1
reg_buf: str = pool.alloc(32)
if reg_buf is not None:
viperlib.snprintf(reg_buf, 32, "%%llvmir_%d", self.Gen.CallGenBase.LLVMIRRegSeq)
self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, reg_buf, "c.LLVMIR")
out_alloca[out_count] = sizet(alloca)
out_regname[out_count] = sizet(reg_buf)
out_count += 1
continue
# Non-LInp/LOut f-string expression: translate and embed the value text
ival: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fexpr, None, 0, trans)
if ival is not None and ival.Name is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, ival.Name, "c.LLVMIR")
elif tk == ast.ASTKind.Constant:
cn2: ast.Constant* = ast.Constant*(first_arg)
if cn2.str_val is not None:
# A bare strcpy is unbounded and could overflow the pool block, so
# use the same bounded append as the JoinedStr branch.
self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn2.str_val, "c.LLVMIR")
# 2. Embed the template text directly (no instruction parsing) + LOut writeback
mlirlite.block_append_text(pool, builder.CurBlock, text)
# LOut writeback: store <ty> %llvmirN, <ty>* %var
oi: int
for oi in range(int(out_count)):
oa: mlirlite.Value* = mlirlite.Value*(out_alloca[oi])
rn: str = str(out_regname[oi])
if oa is None or oa.Ty is None or oa.Ty.Pointee == 0 or rn is None: continue
reg_val: mlirlite.Value* = mlirlite.new_value(pool)
if reg_val is None: continue
reg_val.Ty = oa.Ty.Pointee
reg_val.Name = rn
reg_val.IsConst = 0
mlirlite.build_store(builder, reg_val, oa)
return None
# _apply_struct_defaults - apply default values to struct fields, after
# zeroinitializer and before explicit args (which later overwrite defaults).