1365 lines
82 KiB
Plaintext
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).
|