Path: blob/main/winch/codegen/src/frame/mod.rs
1693 views
use crate::{1abi::{ABI, ABIOperand, ABISig, LocalSlot, align_to},2codegen::{CodeGenPhase, Emission, Prologue},3masm::MacroAssembler,4};5use anyhow::Result;6use smallvec::SmallVec;7use std::marker::PhantomData;8use std::ops::Range;9use wasmparser::{BinaryReader, FuncValidator, ValidatorResources};10use wasmtime_environ::{TypeConvert, WasmValType};1112/// WebAssembly locals.13// TODO:14// SpiderMonkey's implementation uses 16;15// (ref: https://searchfox.org/mozilla-central/source/js/src/wasm/WasmBCFrame.h#585)16// during instrumentation we should measure to verify if this is a good default.17pub(crate) type WasmLocals = SmallVec<[LocalSlot; 16]>;18/// Special local slots used by the compiler.19// Winch's ABI uses two extra parameters to store the callee and caller20// VMContext pointers.21// These arguments are spilled and treated as frame locals, but not22// WebAssembly locals.23pub(crate) type SpecialLocals = [LocalSlot; 2];2425/// Function defined locals start and end in the frame.26pub(crate) struct DefinedLocalsRange(Range<u32>);2728impl DefinedLocalsRange {29/// Get a reference to the inner range.30pub fn as_range(&self) -> &Range<u32> {31&self.032}33}3435/// An abstraction to read the defined locals from the Wasm binary for a function.36#[derive(Default)]37pub(crate) struct DefinedLocals {38/// The defined locals for a function.39pub defined_locals: WasmLocals,40/// The size of the defined locals.41pub stack_size: u32,42}4344impl DefinedLocals {45/// Compute the local slots for a Wasm function.46pub fn new<A: ABI>(47types: &impl TypeConvert,48reader: &mut BinaryReader<'_>,49validator: &mut FuncValidator<ValidatorResources>,50) -> Result<Self> {51let mut next_stack: u32 = 0;52// The first 32 bits of a Wasm binary function describe the number of locals.53let local_count = reader.read_var_u32()?;54let mut slots: WasmLocals = Default::default();5556for _ in 0..local_count {57let position = reader.original_position();58let count = reader.read_var_u32()?;59let ty = reader.read()?;60validator.define_locals(position, count, ty)?;6162let ty = types.convert_valtype(ty)?;63for _ in 0..count {64let ty_size = <A as ABI>::sizeof(&ty);65next_stack = align_to(next_stack, ty_size as u32) + (ty_size as u32);66slots.push(LocalSlot::new(ty, next_stack));67}68}6970Ok(Self {71defined_locals: slots,72stack_size: next_stack,73})74}75}7677/// Frame handler abstraction.78pub(crate) struct Frame<P: CodeGenPhase> {79/// The size of the entire local area; the arguments plus the function defined locals.80pub locals_size: u32,8182/// The range in the frame corresponding to the defined locals range.83pub defined_locals_range: DefinedLocalsRange,8485/// The local slots for the current function.86///87/// Locals get calculated when allocating a frame and are readonly88/// through the function compilation lifetime.89wasm_locals: WasmLocals,90/// Special locals used by the internal ABI. See [`SpecialLocals`].91special_locals: SpecialLocals,9293/// The slot holding the address of the results area.94pub results_base_slot: Option<LocalSlot>,95marker: PhantomData<P>,96}9798impl Frame<Prologue> {99/// Allocate a new [`Frame`].100pub fn new<A: ABI>(sig: &ABISig, defined_locals: &DefinedLocals) -> Result<Frame<Prologue>> {101let (special_locals, mut wasm_locals, defined_locals_start) =102Self::compute_arg_slots::<A>(sig)?;103104// The defined locals have a zero-based offset by default105// so we need to add the defined locals start to the offset.106wasm_locals.extend(107defined_locals108.defined_locals109.iter()110.map(|l| LocalSlot::new(l.ty, l.offset + defined_locals_start)),111);112113let stack_align = <A as ABI>::stack_align();114let defined_locals_end = align_to(115defined_locals_start + defined_locals.stack_size,116stack_align as u32,117);118119// Handle the results base slot for multi value returns.120let (results_base_slot, locals_size) = if sig.params.has_retptr() {121match sig.params.unwrap_results_area_operand() {122// If the results operand is a stack argument, ensure the123// offset is correctly calculated, that is, that it includes the124// argument base offset.125// In this case, the locals size, remains untouched as we don't126// need to create an extra slot for it.127ABIOperand::Stack { ty, offset, .. } => (128Some(LocalSlot::stack_arg(129*ty,130*offset + (<A as ABI>::arg_base_offset() as u32),131)),132defined_locals_end,133),134// If the results operand is a register, we give this register135// the same treatment as all the other argument registers and136// spill it, therefore, we need to increase the locals size by137// one slot.138ABIOperand::Reg { ty, size, .. } => {139let offs = align_to(defined_locals_end, *size) + *size;140(141Some(LocalSlot::new(*ty, offs)),142align_to(offs, <A as ABI>::stack_align().into()),143)144}145}146} else {147(None, defined_locals_end)148};149150Ok(Self {151wasm_locals,152special_locals,153locals_size,154defined_locals_range: DefinedLocalsRange(155defined_locals_start..(defined_locals_start + defined_locals.stack_size),156),157results_base_slot,158marker: PhantomData,159})160}161162/// Returns an iterator over all the [`LocalSlot`]s in the frame, including163/// the [`SpecialLocals`].164pub fn locals(&self) -> impl Iterator<Item = &LocalSlot> {165self.special_locals.iter().chain(self.wasm_locals.iter())166}167168/// Prepares the frame for the [`Emission`] code generation phase.169pub fn for_emission(self) -> Frame<Emission> {170Frame {171wasm_locals: self.wasm_locals,172special_locals: self.special_locals,173locals_size: self.locals_size,174defined_locals_range: self.defined_locals_range,175results_base_slot: self.results_base_slot,176marker: PhantomData,177}178}179180fn compute_arg_slots<A: ABI>(sig: &ABISig) -> Result<(SpecialLocals, WasmLocals, u32)> {181// Go over the function ABI-signature and182// calculate the stack slots.183//184// for each parameter p; when p185//186// Stack =>187// The slot offset is calculated from the ABIOperand offset188// relative the to the frame pointer (and its inclusions, e.g.189// return address).190//191// Register =>192// The slot is calculated by accumulating into the `next_frame_size`193// the size + alignment of the type that the register is holding.194//195// NOTE196// This implementation takes inspiration from SpiderMonkey's implementation197// to calculate local slots for function arguments198// (https://searchfox.org/mozilla-central/source/js/src/wasm/WasmBCFrame.cpp#83).199// The main difference is that SpiderMonkey's implementation200// doesn't append any sort of metadata to the locals regarding stack201// addressing mode (stack pointer or frame pointer), the offset is202// declared negative if the local belongs to a stack argument;203// that's enough to later calculate address of the local later on.204//205// Winch appends an addressing mode to each slot, in the end206// we want positive addressing from the stack pointer207// for both locals and stack arguments.208209let arg_base_offset = <A as ABI>::arg_base_offset().into();210let mut next_stack = 0u32;211212// Skip the results base param; if present, the [Frame] will create213// a dedicated slot for it.214let mut params_iter = sig.params_without_retptr().into_iter();215216// Handle special local slots.217let callee_vmctx = params_iter218.next()219.map(|arg| Self::abi_arg_slot(&arg, &mut next_stack, arg_base_offset))220.expect("Slot for VMContext");221222let caller_vmctx = params_iter223.next()224.map(|arg| Self::abi_arg_slot(&arg, &mut next_stack, arg_base_offset))225.expect("Slot for VMContext");226227let slots: WasmLocals = params_iter228.map(|arg| Self::abi_arg_slot(&arg, &mut next_stack, arg_base_offset))229.collect();230231Ok(([callee_vmctx, caller_vmctx], slots, next_stack))232}233234fn abi_arg_slot(arg: &ABIOperand, next_stack: &mut u32, arg_base_offset: u32) -> LocalSlot {235match arg {236// Create a local slot, for input register spilling,237// with type-size aligned access.238ABIOperand::Reg { ty, size, .. } => {239*next_stack = align_to(*next_stack, *size) + *size;240LocalSlot::new(*ty, *next_stack)241}242// Create a local slot, with an offset from the arguments base in243// the stack; which is the frame pointer + return address.244ABIOperand::Stack { ty, offset, .. } => {245LocalSlot::stack_arg(*ty, offset + arg_base_offset)246}247}248}249}250251impl Frame<Emission> {252/// Get the [`LocalSlot`] for a WebAssembly local.253/// This method assumes that the index is bound to u32::MAX, representing254/// the index space for WebAssembly locals.255///256/// # Panics257/// This method panics if the index is not associated to a valid WebAssembly258/// local.259pub fn get_wasm_local(&self, index: u32) -> &LocalSlot {260self.wasm_locals261.get(index as usize)262.unwrap_or_else(|| panic!(" Expected WebAssembly local at slot: {index}"))263}264265/// Get the [`LocalSlot`] for a special local.266///267/// # Panics268/// This method panics if the index is not associated to a valid special269/// local.270pub fn get_special_local(&self, index: usize) -> &LocalSlot {271self.special_locals272.get(index)273.unwrap_or_else(|| panic!(" Expected special local at slot: {index}"))274}275276/// Get the special [`LocalSlot`] for the `VMContext`.277pub fn vmctx_slot(&self) -> &LocalSlot {278self.get_special_local(0)279}280281/// Returns the address of the local at the given index.282///283/// # Panics284/// This function panics if the index is not associated to a local.285pub fn get_local_address<M: MacroAssembler>(286&self,287index: u32,288masm: &mut M,289) -> Result<(WasmValType, M::Address)> {290let slot = self.get_wasm_local(index);291Ok((slot.ty, masm.local_address(&slot)?))292}293}294295296