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