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);
            }
        }
    }
}