xref: /wasmtime-44.0.1/winch/codegen/src/frame/mod.rs (revision 72004aad)
1 use crate::{
2     abi::{align_to, ABIOperand, ABISig, LocalSlot, ABI},
3     masm::MacroAssembler,
4 };
5 use anyhow::Result;
6 use smallvec::SmallVec;
7 use std::ops::Range;
8 use wasmparser::{BinaryReader, FuncValidator, ValidatorResources};
9 use wasmtime_environ::{TypeConvert, WasmValType};
10 
11 // TODO:
12 // SpiderMonkey's implementation uses 16;
13 // (ref: https://searchfox.org/mozilla-central/source/js/src/wasm/WasmBCFrame.h#585)
14 // during instrumentation we should measure to verify if this is a good default.
15 pub(crate) type Locals = SmallVec<[LocalSlot; 16]>;
16 
17 /// Function defined locals start and end in the frame.
18 pub(crate) struct DefinedLocalsRange(Range<u32>);
19 
20 impl DefinedLocalsRange {
21     /// Get a reference to the inner range.
22     pub fn as_range(&self) -> &Range<u32> {
23         &self.0
24     }
25 }
26 
27 /// An abstraction to read the defined locals from the Wasm binary for a function.
28 #[derive(Default)]
29 pub(crate) struct DefinedLocals {
30     /// The defined locals for a function.
31     pub defined_locals: Locals,
32     /// The size of the defined locals.
33     pub stack_size: u32,
34 }
35 
36 impl DefinedLocals {
37     /// Compute the local slots for a Wasm function.
38     pub fn new<A: ABI>(
39         types: &impl TypeConvert,
40         reader: &mut BinaryReader<'_>,
41         validator: &mut FuncValidator<ValidatorResources>,
42     ) -> Result<Self> {
43         let mut next_stack: u32 = 0;
44         // The first 32 bits of a Wasm binary function describe the number of locals.
45         let local_count = reader.read_var_u32()?;
46         let mut slots: Locals = Default::default();
47 
48         for _ in 0..local_count {
49             let position = reader.original_position();
50             let count = reader.read_var_u32()?;
51             let ty = reader.read()?;
52             validator.define_locals(position, count, ty)?;
53 
54             let ty = types.convert_valtype(ty);
55             for _ in 0..count {
56                 let ty_size = <A as ABI>::sizeof(&ty);
57                 next_stack = align_to(next_stack, ty_size as u32) + (ty_size as u32);
58                 slots.push(LocalSlot::new(ty, next_stack));
59             }
60         }
61 
62         Ok(Self {
63             defined_locals: slots,
64             stack_size: next_stack,
65         })
66     }
67 }
68 
69 /// Frame handler abstraction.
70 pub(crate) struct Frame {
71     /// The size of the entire local area; the arguments plus the function defined locals.
72     pub locals_size: u32,
73 
74     /// The range in the frame corresponding to the defined locals range.
75     pub defined_locals_range: DefinedLocalsRange,
76 
77     /// The local slots for the current function.
78     ///
79     /// Locals get calculated when allocating a frame and are readonly
80     /// through the function compilation lifetime.
81     locals: Locals,
82 
83     /// The offset to the slot containing the `VMContext`.
84     pub vmctx_slot: LocalSlot,
85 
86     /// The slot holding the address of the results area.
87     pub results_base_slot: Option<LocalSlot>,
88 }
89 
90 impl Frame {
91     /// Allocate a new [`Frame`].
92     pub fn new<A: ABI>(sig: &ABISig, defined_locals: &DefinedLocals) -> Result<Self> {
93         let (mut locals, defined_locals_start) = Self::compute_arg_slots::<A>(sig)?;
94 
95         // The defined locals have a zero-based offset by default
96         // so we need to add the defined locals start to the offset.
97         locals.extend(
98             defined_locals
99                 .defined_locals
100                 .iter()
101                 .map(|l| LocalSlot::new(l.ty, l.offset + defined_locals_start)),
102         );
103 
104         let stack_align = <A as ABI>::stack_align();
105         let defined_locals_end = align_to(
106             defined_locals_start + defined_locals.stack_size,
107             stack_align as u32,
108         );
109 
110         // Handle the results base slot for multi value returns.
111         let (results_base_slot, locals_size) = if sig.params.has_retptr() {
112             match sig.params.unwrap_results_area_operand() {
113                 // If the results operand is a stack argument, ensure the
114                 // offset is correctly calculated, that is, that it includes the
115                 // argument base offset.
116                 // In this case, the locals size, remains untouched as we don't
117                 // need to create an extra slot for it.
118                 ABIOperand::Stack { ty, offset, .. } => (
119                     Some(LocalSlot::stack_arg(
120                         *ty,
121                         *offset + (<A as ABI>::arg_base_offset() as u32),
122                     )),
123                     defined_locals_end,
124                 ),
125                 // If the resuls operand is a register, we give this register
126                 // the same treatment as all the other argument registers and
127                 // spill it, therefore, we need to increase the locals size by
128                 // one slot.
129                 ABIOperand::Reg { ty, size, .. } => {
130                     let offs = align_to(defined_locals_end, *size) + *size;
131                     (
132                         Some(LocalSlot::new(*ty, offs)),
133                         align_to(offs, <A as ABI>::stack_align().into()),
134                     )
135                 }
136             }
137         } else {
138             (None, defined_locals_end)
139         };
140 
141         let vmctx_slot = *locals.get(0).expect("LocalSlot for VMContext");
142         Ok(Self {
143             locals,
144             locals_size,
145             vmctx_slot,
146             defined_locals_range: DefinedLocalsRange(
147                 defined_locals_start..(defined_locals_start + defined_locals.stack_size),
148             ),
149             results_base_slot,
150         })
151     }
152 
153     // Winch's ABI uses two extra parameters to store the callee and caller
154     // VMContext pointers.
155     // These arguments are spilled and treated as frame locals, but not
156     // WebAssembly locals.
157     const WASM_LOCALS_OFFSET: usize = 2;
158 
159     /// Get the [LocalSlot] for a WebAssembly local.
160     /// This method assumes that the index is bound to u32::MAX, representing
161     /// the index space for WebAssembly locals.
162     ///
163     /// # Panics
164     /// This method panics if the index is not associated to a valid WebAssembly
165     /// local.
166     pub fn get_wasm_local(&self, index: u32) -> &LocalSlot {
167         let local_index = Self::WASM_LOCALS_OFFSET + index as usize;
168         self.locals
169             .get(local_index)
170             .unwrap_or_else(|| panic!(" Expected WebAssembly local at slot: {index}"))
171     }
172 
173     /// Get the [LocalSlot] for a frame local.
174     /// This method doesn't make any asumptions about the local index passed in,
175     /// and simply delegates the [LocalSlot] retrieval to the underlying locals
176     /// vector.
177     ///
178     /// # Panics
179     /// This method panics if the index is not associated to a valid WebAssembly
180     /// local.
181     pub fn get_frame_local(&self, index: usize) -> &LocalSlot {
182         self.locals
183             .get(index)
184             .unwrap_or_else(|| panic!(" Expected Frame local at slot: {index}"))
185     }
186 
187     /// Returns the address of the local at the given index.
188     ///
189     /// # Panics
190     /// This function panics if the the index is not associated to a local.
191     pub fn get_local_address<M: MacroAssembler>(
192         &self,
193         index: u32,
194         masm: &mut M,
195     ) -> (WasmValType, M::Address) {
196         let slot = self.get_wasm_local(index);
197         (slot.ty, masm.local_address(&slot))
198     }
199 
200     fn compute_arg_slots<A: ABI>(sig: &ABISig) -> Result<(Locals, u32)> {
201         // Go over the function ABI-signature and
202         // calculate the stack slots.
203         //
204         //  for each parameter p; when p
205         //
206         //  Stack =>
207         //      The slot offset is calculated from the ABIOperand offset
208         //      relative the to the frame pointer (and its inclusions, e.g.
209         //      return address).
210         //
211         //  Register =>
212         //     The slot is calculated by accumulating into the `next_frame_size`
213         //     the size + alignment of the type that the register is holding.
214         //
215         //  NOTE
216         //      This implementation takes inspiration from SpiderMonkey's implementation
217         //      to calculate local slots for function arguments
218         //      (https://searchfox.org/mozilla-central/source/js/src/wasm/WasmBCFrame.cpp#83).
219         //      The main difference is that SpiderMonkey's implementation
220         //      doesn't append any sort of metadata to the locals regarding stack
221         //      addressing mode (stack pointer or frame pointer), the offset is
222         //      declared negative if the local belongs to a stack argument;
223         //      that's enough to later calculate address of the local later on.
224         //
225         //      Winch appends an addressing mode to each slot, in the end
226         //      we want positive addressing from the stack pointer
227         //      for both locals and stack arguments.
228 
229         let arg_base_offset = <A as ABI>::arg_base_offset().into();
230         let mut next_stack = 0u32;
231 
232         // Skip the results base param; if present, the [Frame] will create
233         // a dedicated slot for it.
234         let slots: Locals = sig
235             .params_without_retptr()
236             .into_iter()
237             .map(|arg| Self::abi_arg_slot(&arg, &mut next_stack, arg_base_offset))
238             .collect();
239 
240         Ok((slots, next_stack))
241     }
242 
243     fn abi_arg_slot(arg: &ABIOperand, next_stack: &mut u32, arg_base_offset: u32) -> LocalSlot {
244         match arg {
245             // Create a local slot, for input register spilling,
246             // with type-size aligned access.
247             ABIOperand::Reg { ty, size, .. } => {
248                 *next_stack = align_to(*next_stack, *size) + *size;
249                 LocalSlot::new(*ty, *next_stack)
250             }
251             // Create a local slot, with an offset from the arguments base in
252             // the stack; which is the frame pointer + return address.
253             ABIOperand::Stack { ty, offset, .. } => {
254                 LocalSlot::stack_arg(*ty, offset + arg_base_offset)
255             }
256         }
257     }
258 }
259