// =======================================================
// CallInst
// =======================================================
///|
pub(all) enum TailCallKind {
NoTail
Tail
MustTail
}
///|
pub impl Show for TailCallKind with output(self, logger) {
let str = match self {
NoTail => ""
Tail => "tail"
MustTail => "musttail"
}
logger.write_string(str)
}
///|
/// CallInst represents a function call instruction that invokes a function with the specified arguments.
///
/// **Note**:
///
/// Use `IRBuilder::createCall` to create a `CallInst`.
///
/// ```mbt check
/// test {
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
/// let i32_ty = ctx.getInt32Ty()
/// let add_fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
/// let main_fty = ctx.getFunctionType(i32_ty, [])
/// let add_func = mod.addFunction(add_fty, "add")
/// let main_func = mod.addFunction(main_fty, "main")
/// let bb = main_func.addBasicBlock(name="entry")
/// let arg1 = ctx.getConstInt32(10)
/// let arg2 = ctx.getConstInt32(20)
/// builder.setInsertPoint(bb)
/// let call = builder.createCall(add_func, [arg1, arg2], name="sum")
/// inspect(call, content=" %sum = call i32 @add(i32 10, i32 20)")
/// assert_true(call.asValueEnum() is CallInst(_))
/// let void_fty = ctx.getFunctionType(ctx.getVoidTy(), [])
/// let void_func = mod.addFunction(void_fty, "void_func")
/// let void_call = builder.createCall(void_func, [])
/// inspect(void_call, content=" call void @void_func()")
/// }
/// ```
pub struct CallInst {
uid : UInt64
vty : &Type
users : Array[&User]
mut name : String?
function_type : FunctionType
callee : Either[Function, &Value]
args : Array[&Value]
parent : Function
// --- InstBase ---
bb : Ref[BasicBlock?]
prev : Ref[&Instruction?]
next : Ref[&Instruction?]
mut tailCallKind : TailCallKind
}
///|
fn CallInst::new(
callee : Function,
args : Array[&Value],
parent : Function,
name~ : String?,
) -> CallInst {
let fty = callee.getFunctionType()
let name = match fty.getReturnType().asTypeEnum() {
VoidType(_) => None
_ => name
}
let uid = valueUIDAssigner.assign()
let vty = fty.getReturnType()
let bb : Ref[BasicBlock?] = Ref::new(None)
let prev : Ref[&Instruction?] = Ref::new(None)
let next : Ref[&Instruction?] = Ref::new(None)
let inst = CallInst::{
uid,
vty,
users: [],
name,
function_type: fty,
callee: Left(callee),
args,
parent,
bb,
prev,
next,
tailCallKind: NoTail,
}
callee.addUser(inst)
args.each(arg => arg.addUser(inst))
inst
}
///|
fn CallInst::new_indirect(
callee_val : &Value, // Must be a pointer
function_type : FunctionType,
args : Array[&Value],
parent : Function,
name~ : String?,
) -> CallInst raise LLVMValueError {
guard callee_val.getType().asTypeEnum() is PointerType(_) else {
let msg = "CallInst indirect callee type mismatch: " +
"expected pointer type, got \{callee_val.getType()}"
raise LLVMValueError(msg)
}
let uid = valueUIDAssigner.assign()
let vty = function_type.getReturnType()
let bb : Ref[BasicBlock?] = Ref::new(None)
let prev : Ref[&Instruction?] = Ref::new(None)
let next : Ref[&Instruction?] = Ref::new(None)
let name = match vty.asTypeEnum() {
VoidType(_) => None
_ => name
}
let inst = CallInst::{
uid,
vty,
users: [],
name,
function_type,
callee: Right(callee_val),
args,
parent,
bb,
prev,
next,
tailCallKind: NoTail,
}
callee_val.addUser(inst)
args.each(arg => arg.addUser(inst))
inst
}
///|
pub fn CallInst::isTailCall(self : CallInst) -> Bool {
not(self.tailCallKind is NoTail)
}
///|
pub fn CallInst::getTailCallKind(self : CallInst) -> TailCallKind {
self.tailCallKind
}
///|
pub fn CallInst::setTailCallKind(
self : CallInst,
tailCallKind : TailCallKind,
) -> Unit {
self.tailCallKind = tailCallKind
}
///|
pub fn CallInst::getFunctionType(self : CallInst) -> FunctionType {
self.function_type
}
///|
pub fn CallInst::getCallee(self : CallInst) -> Function {
guard self.getOperand(0) is Some(callee_val)
guard callee_val.asValueEnum() is Function(callee)
callee
}
///|
pub fn CallInst::getArgOperand(self : Self, idx : Int) -> &Value? {
self.args.get(idx)
}
///|
pub fn CallInst::getNumArgs(self : Self) -> Int {
self.function_type.getNumParams()
}
///|
pub impl Value for CallInst with getValueBase(self) {
ValueBase::{ uid: self.uid, vty: self.vty, users: self.users }
}
///|
pub impl Value for CallInst with asValueEnum(self) {
CallInst(self)
}
///|
/// Get simple representation of the value.
///
/// ```mbt check
/// test {
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
/// let i32_ty = ctx.getInt32Ty()
/// let add_fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
/// let main_fty = ctx.getFunctionType(i32_ty, [])
/// let add_func = mod.addFunction(add_fty, "add")
/// let main_func = mod.addFunction(main_fty, "main")
/// let bb = main_func.addBasicBlock(name="entry")
/// let arg1 = ctx.getConstInt32(10)
/// let arg2 = ctx.getConstInt32(20)
/// builder.setInsertPoint(bb)
/// let call = builder.createCall(add_func, [arg1, arg2])
/// inspect(call.getValueRepr(), content="%0")
/// call.setName("sum")
/// inspect(call.getValueRepr(), content="%sum")
/// }
/// ```
pub impl Value for CallInst with getValueRepr(self) {
if self.vty.asTypeEnum() is VoidType(_) {
return ""
}
match self.getNameOrSlot() {
Some(Left(name)) => "%\{name}"
Some(Right(slot)) => "%\{slot}"
None => ""
}
}
///|
/// Get the name of the instruction.
///
/// **Note**:
///
/// If the instruction has no name, return `None`.
///
/// ```mbt check
/// test {
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
/// let i32_ty = ctx.getInt32Ty()
/// let add_fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
/// let main_fty = ctx.getFunctionType(i32_ty, [])
/// let add_func = mod.addFunction(add_fty, "add")
/// let main_func = mod.addFunction(main_fty, "main")
/// let bb = main_func.addBasicBlock(name="entry")
/// let arg1 = ctx.getConstInt32(10)
/// let arg2 = ctx.getConstInt32(20)
/// builder.setInsertPoint(bb)
/// let call = builder.createCall(add_func, [arg1, arg2])
/// inspect(call.getName(), content="None")
/// call.setName("sum")
/// inspect(call.getName(), content="Some(\"sum\")")
/// }
/// ```
pub impl Value for CallInst with getName(self) {
self.name
}
///|
pub impl Value for CallInst with getNameOrSlot(self) {
if self.vty.asTypeEnum() is VoidType(_) {
return None
}
match self.name {
Some(name) => Some(Left(name))
None =>
match self.getParent().getSlot(self) {
Some(slot) => Some(Right(slot))
None => None
}
}
}
///|
/// Set the name of the instruction.
///
/// **Note**:
///
/// If the name has already been used in the parent function,
/// it will raise Error. Cannot set name for CallInst with void return type.
///
/// ```mbt check
/// test {
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
/// let i32_ty = ctx.getInt32Ty()
/// let add_fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
/// let main_fty = ctx.getFunctionType(i32_ty, [])
/// let add_func = mod.addFunction(add_fty, "add")
/// let main_func = mod.addFunction(main_fty, "main")
/// let bb = main_func.addBasicBlock(name="entry")
/// let arg1 = ctx.getConstInt32(10)
/// let arg2 = ctx.getConstInt32(20)
/// builder.setInsertPoint(bb)
/// let call = builder.createCall(add_func, [arg1, arg2])
/// inspect(call.getName(), content="None")
/// call.setName("sum")
/// inspect(call.getName(), content="Some(\"sum\")")
/// }
/// ```
pub impl Value for CallInst with setName(self, name) {
if self.vty.asTypeEnum() is VoidType(_) {
let msg = "Misuse `CallInst::setName`: " +
"cannot set name for CallInst with void return type."
raise LLVMValueError(msg)
}
match self.getParent().setSymbol(name, self) {
EmptyName => {
let msg = "Misuse `CallInst::setName`: name cannot be empty."
raise LLVMValueError(msg)
}
InvalidName => {
let msg =
$|Misuse `CallInst::setName`:
$|name '\{name}' contains illegal characters,
$|only alphanumeric characters and underscores are allowed
raise LLVMValueError(msg)
}
DuplicateName(existed) => {
let msg =
$|Misuse `CallInst::setName`:
$|name '\{name}' already exists in the parent function,
$|it is used by:
$|\{existed}"
raise LLVMValueError(msg)
}
Success => self.name = Some(name)
}
}
///|
pub impl Value for CallInst with removeName(self) {
match self.name {
None => ()
Some(name) => {
self.getParent().symbols.remove(name)
self.name = None
}
}
}
///|
pub impl User for CallInst with asUserEnum(self) {
CallInst(self)
}
///|
pub impl User for CallInst with getUserBase(self) {
let operands : Array[&Value] = []
match self.callee {
Left(func) => operands.push(func)
Right(val) => operands.push(val)
}
self.args.each(arg => operands.push(arg))
UserBase::{ operands, }
}
///|
pub impl Instruction for CallInst with getInstBase(self) {
InstBase::{ bb: self.bb, prev: self.prev, next: self.next }
}
///|
pub impl Instruction for CallInst with asInstEnum(self) {
InstEnum::CallInst(self)
}
///|
pub impl Instruction for CallInst with getParent(self) {
self.parent
}
///|
pub impl Show for CallInst with output(self, logger) {
let callee = self.callee
let fty = self.function_type
let ret_ty = fty.getReturnType()
let is_var_arg = fty.isVarArg
let is_void_ret = ret_ty.asTypeEnum() is VoidType(_)
let prefix = if is_void_ret { "" } else { self.getValueRepr() }
let tail_str = self.tailCallKind.to_string()
let ret_attrs = if callee is Left(callee) {
callee.getReturnAttrs()
} else {
Set::new()
}
let ret_attrs_str = if ret_attrs.is_empty() {
""
} else {
" " + ret_attrs.iter().map(fn(a) { "\{a}" }).join(" ")
}
let arg_strs = []
for idx, arg in self.args {
let arg_ty = arg.getType()
let arg_repr = arg.getValueRepr()
let arg_attrs = if callee is Left(callee) {
callee.getParamAttrs(idx.reinterpret_as_uint())
} else {
None
}
let arg_attrs_str = if not(arg_attrs is None) {
" " + arg_attrs.iter().map(fn(a) { "\{a}" }).join(" ")
} else {
""
}
// When the argument is a Function, print it as "ptr @fn" instead of "fn_type @fn"
// This matches LLVM's behavior where functions decay to pointers when used as values
let arg_ty_str = match arg.asValueEnum() {
Function(_) => "ptr"
_ => arg_ty.to_string()
}
arg_strs.push("\{arg_ty_str}\{arg_attrs_str} \{arg_repr}")
}
let args_str = arg_strs.join(", ")
let call_prefix = if tail_str.is_empty() {
"call"
} else {
"\{tail_str} call"
}
let var_arg_func_mark = if is_var_arg {
let param_ty_strs = fty.getParamTypes().map(t => t.to_string())
param_ty_strs.push("...")
let param_ty_str = param_ty_strs.join(", ")
" (\{param_ty_str})"
} else {
""
}
if self.callee is Left(callee) {
if is_void_ret {
logger.write_string(
" \{call_prefix}\{ret_attrs_str} \{ret_ty}\{var_arg_func_mark} @\{callee.name}(\{args_str})",
)
} else {
logger.write_string(
" \{prefix} = \{call_prefix}\{ret_attrs_str} \{ret_ty}\{var_arg_func_mark} @\{callee.name}(\{args_str})",
)
}
return
}
guard self.callee is Right(callee)
if is_void_ret {
logger.write_string(
" \{call_prefix}\{ret_attrs_str} \{ret_ty}\{var_arg_func_mark} \{callee.getValueRepr()}(\{args_str})",
)
} else {
logger.write_string(
" \{prefix} = \{call_prefix}\{ret_attrs_str} \{ret_ty}\{var_arg_func_mark} \{callee.getValueRepr()}(\{args_str})",
)
}
}