xref: /wasmtime-44.0.1/winch/codegen/src/frame/mod.rs (revision f9f8a4df)
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, WasmType};
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 = 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) + ty_size;
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     pub 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         // Align the locals to add a slot for the VMContext pointer.
105         let ptr_size = <A as ABI>::word_bytes();
106         let vmctx_offset =
107             align_to(defined_locals_start + defined_locals.stack_size, ptr_size) + ptr_size;
108 
109         let (results_base_slot, locals_size) = if sig.params.has_retptr() {
110             match sig.params.unwrap_results_area_operand() {
111                 ABIOperand::Stack { ty, offset, .. } => (
112                     Some(LocalSlot::stack_arg(
113                         *ty,
114                         *offset + (<A as ABI>::arg_base_offset() as u32),
115                     )),
116                     align_to(vmctx_offset, <A as ABI>::stack_align().into()),
117                 ),
118                 ABIOperand::Reg { ty, .. } => {
119                     let offs = align_to(vmctx_offset, ptr_size) + ptr_size;
120                     (
121                         Some(LocalSlot::new(*ty, offs)),
122                         align_to(offs, <A as ABI>::stack_align().into()),
123                     )
124                 }
125             }
126         } else {
127             (
128                 None,
129                 align_to(vmctx_offset, <A as ABI>::stack_align().into()),
130             )
131         };
132 
133         Ok(Self {
134             locals,
135             locals_size,
136             vmctx_slot: LocalSlot::i64(vmctx_offset),
137             defined_locals_range: DefinedLocalsRange(
138                 defined_locals_start..(defined_locals_start + defined_locals.stack_size),
139             ),
140             results_base_slot,
141         })
142     }
143 
144     /// Get a local slot.
145     pub fn get_local(&self, index: u32) -> Option<&LocalSlot> {
146         self.locals.get(index as usize)
147     }
148 
149     /// Returns the address of the local at the given index.
150     ///
151     /// # Panics
152     /// This function panics if the the index is not associated to a local.
153     pub fn get_local_address<M: MacroAssembler>(
154         &self,
155         index: u32,
156         masm: &mut M,
157     ) -> (WasmType, M::Address) {
158         self.get_local(index)
159             .map(|slot| (slot.ty, masm.local_address(slot)))
160             .unwrap_or_else(|| panic!("Invalid local slot: {}", index))
161     }
162 
163     fn compute_arg_slots<A: ABI>(sig: &ABISig) -> Result<(Locals, u32)> {
164         // Go over the function ABI-signature and
165         // calculate the stack slots.
166         //
167         //  for each parameter p; when p
168         //
169         //  Stack =>
170         //      The slot offset is calculated from the ABIOperand offset
171         //      relative the to the frame pointer (and its inclusions, e.g.
172         //      return address).
173         //
174         //  Register =>
175         //     The slot is calculated by accumulating into the `next_frame_size`
176         //     the size + alignment of the type that the register is holding.
177         //
178         //  NOTE
179         //      This implementation takes inspiration from SpiderMonkey's implementation
180         //      to calculate local slots for function arguments
181         //      (https://searchfox.org/mozilla-central/source/js/src/wasm/WasmBCFrame.cpp#83).
182         //      The main difference is that SpiderMonkey's implementation
183         //      doesn't append any sort of metadata to the locals regarding stack
184         //      addressing mode (stack pointer or frame pointer), the offset is
185         //      declared negative if the local belongs to a stack argument;
186         //      that's enough to later calculate address of the local later on.
187         //
188         //      Winch appends an addressing mode to each slot, in the end
189         //      we want positive addressing from the stack pointer
190         //      for both locals and stack arguments.
191 
192         let arg_base_offset = <A as ABI>::arg_base_offset().into();
193         let mut next_stack = 0u32;
194 
195         // Skip the results base param; if present, the [Frame] will create
196         // a dedicated slot for it.
197         let slots: Locals = sig
198             .params_without_retptr()
199             .into_iter()
200             .map(|arg| Self::abi_arg_slot(&arg, &mut next_stack, arg_base_offset))
201             .collect();
202 
203         Ok((slots, next_stack))
204     }
205 
206     fn abi_arg_slot(arg: &ABIOperand, next_stack: &mut u32, arg_base_offset: u32) -> LocalSlot {
207         match arg {
208             // Create a local slot, for input register spilling,
209             // with type-size aligned access.
210             ABIOperand::Reg { ty, size, .. } => {
211                 *next_stack = align_to(*next_stack, *size) + *size;
212                 LocalSlot::new(*ty, *next_stack)
213             }
214             // Create a local slot, with an offset from the arguments base in
215             // the stack; which is the frame pointer + return address.
216             ABIOperand::Stack { ty, offset, .. } => {
217                 LocalSlot::stack_arg(*ty, offset + arg_base_offset)
218             }
219         }
220     }
221 }
222