1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
//! Function call emission. For more details around the ABI and
//! calling convention, see [ABI].
use super::CodeGenContext;
use crate::{
abi::{align_to, calculate_frame_adjustment, ABIArg, ABIResult, ABISig, ABI},
masm::{CalleeKind, MacroAssembler, OperandSize},
reg::Reg,
stack::Val,
};
/// All the information needed to emit a function call.
pub(crate) struct FnCall<'a> {
/// The total stack space in bytes used by the function call.
/// This amount includes the sum of:
///
/// 1. The amount of stack space that needs to be explicitly
/// allocated at the callsite for callee arguments that
/// go in the stack, plus any alignment.
/// 2. The amount of stack space created by saving any live
/// registers at the callsite.
/// 3. The amount of space used by any memory entries in the value
/// stack present at the callsite, that will be used as
/// arguments for the function call. Any memory values in the
/// value stack that are needed as part of the function
/// arguments, will be consumed by the function call (either by
/// assigning those values to a register or by storing those
/// values to a memory location if the callee argument is on
/// the stack), so we track that stack space to reclaim it once
/// the function call has ended. This could also be done in
/// `assign_args` everytime a memory entry needs to be assigned
/// to a particular location, but doing so, will incur in more
/// instructions (e.g. a pop per argument that needs to be
/// assigned); it's more efficient to track the space needed by
/// those memory values and reclaim it at once.
///
/// The machine stack state that this amount is capturing, is the following:
/// ┌──────────────────────────────────────────────────┐
/// │ │
/// │ │
/// │ Stack space created by any previous spills │
/// │ from the value stack; and which memory values │
/// │ are used as function arguments. │
/// │ │
/// ├──────────────────────────────────────────────────┤ ---> The Wasm value stack at this point in time would look like:
/// │ │ [ Reg | Reg | Mem(offset) | Mem(offset) ]
/// │ │
/// │ Stack space created by saving │
/// │ any live registers at the callsite. │
/// │ │
/// │ │
/// ├─────────────────────────────────────────────────┬┤ ---> The Wasm value stack at this point in time would look like:
/// │ │ [ Mem(offset) | Mem(offset) | Mem(offset) | Mem(offset) ]
/// │ │ Assuming that the callee takes 4 arguments, we calculate
/// │ │ 2 spilled registers + 2 memory values; all of which will be used
/// │ Stack space allocated for │ as arguments to the call via `assign_args`, thus the memory they represent is
/// │ the callee function arguments in the stack; │ is considered to be consumed by the call.
/// │ represented by `arg_stack_space` │
/// │ │
/// │ │
/// │ │
/// └──────────────────────────────────────────────────┘ ------> Stack pointer when emitting the call
///
total_stack_space: u32,
/// The total stack space needed for the callee arguments on the
/// stack, including any adjustments to the function's frame and
/// aligned to to the required ABI alignment.
arg_stack_space: u32,
/// The ABI-specific signature of the callee.
abi_sig: &'a ABISig,
/// The stack pointer offset prior to preparing and emitting the
/// call. This is tracked to assert the position of the stack
/// pointer after the call has finished.
sp_offset_at_callsite: u32,
}
impl<'a> FnCall<'a> {
/// Allocate and setup a new function call.
///
/// The setup process, will first save all the live registers in
/// the value stack, tracking down those spilled for the function
/// arguments(see comment below for more details) it will also
/// track all the memory entries consumed by the function
/// call. Then, it will calculate any adjustments needed to ensure
/// the alignment of the caller's frame. It's important to note
/// that the order of operations in the setup is important, as we
/// want to calculate any adjustments to the caller's frame, after
/// having saved any live registers, so that we can account for
/// any pushes generated by register spilling.
pub fn new<A: ABI, M: MacroAssembler>(
abi: &A,
callee_sig: &'a ABISig,
context: &mut CodeGenContext,
masm: &mut M,
) -> Self {
let stack = &context.stack;
let arg_stack_space = callee_sig.stack_bytes;
let callee_params = &callee_sig.params;
let sp_offset_at_callsite = masm.sp_offset();
let (spilled_regs, memory_values) = match callee_params.len() {
0 => {
let _ = context.spill_regs_and_count_memory_in(masm, ..);
(0, 0)
}
_ => {
// Here we perform a "spill" of the register entries
// in the Wasm value stack, we also count any memory
// values that will be used used as part of the callee
// arguments. Saving the live registers is done by
// emitting push operations for every `Reg` entry in
// the Wasm value stack. We do this to be compliant
// with Winch's internal ABI, in which all registers
// are treated as caller-saved. For more details, see
// [ABI].
//
// The next few lines, partition the value stack into
// two sections:
// +------------------+--+--- (Stack top)
// | | |
// | | | 1. The top `n` elements, which are used for
// | | | function arguments; for which we save any
// | | | live registers, keeping track of the amount of registers
// +------------------+ | saved plus the amount of memory values consumed by the function call;
// | | | with this information we can later reclaim the space used by the function call.
// | | |
// +------------------+--+---
// | | | 2. The rest of the items in the stack, for which
// | | | we only save any live registers.
// | | |
// +------------------+ |
assert!(stack.len() >= callee_params.len());
let partition = stack.len() - callee_params.len();
let _ = context.spill_regs_and_count_memory_in(masm, 0..partition);
context.spill_regs_and_count_memory_in(masm, partition..)
}
};
let delta = calculate_frame_adjustment(
masm.sp_offset(),
abi.arg_base_offset() as u32,
abi.call_stack_align() as u32,
);
let arg_stack_space = align_to(arg_stack_space + delta, abi.call_stack_align() as u32);
Self {
abi_sig: &callee_sig,
arg_stack_space,
total_stack_space: (spilled_regs * <A as ABI>::word_bytes())
+ (memory_values * <A as ABI>::word_bytes())
+ arg_stack_space,
sp_offset_at_callsite,
}
}
/// Emit the function call.
pub fn emit<M: MacroAssembler, A: ABI>(
&self,
masm: &mut M,
context: &mut CodeGenContext,
callee: u32,
) {
masm.reserve_stack(self.arg_stack_space);
self.assign_args(context, masm, <A as ABI>::scratch_reg());
masm.call(CalleeKind::Direct(callee));
masm.free_stack(self.total_stack_space);
context.drop_last(self.abi_sig.params.len());
// The stack pointer at the end of the function call
// cannot be less than what it was when starting the
// function call.
assert!(self.sp_offset_at_callsite >= masm.sp_offset());
self.handle_result(context, masm);
}
fn assign_args<M: MacroAssembler>(
&self,
context: &mut CodeGenContext,
masm: &mut M,
scratch: Reg,
) {
let arg_count = self.abi_sig.params.len();
let stack = &context.stack;
let mut stack_values = stack.peekn(arg_count);
for arg in &self.abi_sig.params {
let val = stack_values
.next()
.unwrap_or_else(|| panic!("expected stack value for function argument"));
match &arg {
&ABIArg::Reg { ty, reg } => {
context.move_val_to_reg(&val, *reg, masm, (*ty).into());
}
&ABIArg::Stack { ty, offset } => {
let addr = masm.address_at_sp(*offset);
let size: OperandSize = (*ty).into();
context.move_val_to_reg(val, scratch, masm, size);
masm.store(scratch.into(), addr, size);
}
}
}
}
fn handle_result<M: MacroAssembler>(&self, context: &mut CodeGenContext, masm: &mut M) {
let result = &self.abi_sig.result;
if result.is_void() {
return;
}
match result {
&ABIResult::Reg { ty: _, reg } => {
assert!(context.regalloc.gpr_available(reg));
let result_reg = Val::reg(context.gpr(reg, masm));
context.stack.push(result_reg);
}
}
}
}