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