winch: Initial integration with wasmtime (#6119)
* Adding in trampoline compiling method for ISA * Adding support for indirect call to memory address * Refactoring frame to externalize defined locals, so it removes WASM depedencies in trampoline case * Adding initial version of trampoline for testing * Refactoring trampoline to be re-used by other architectures * Initial wiring for winch with wasmtime * Add a Wasmtime CLI option to select `winch` This is effectively an option to select the `Strategy` enumeration. * Implement `Compiler::compile_function` for Winch Hook this into the `TargetIsa::compile_function` hook as well. Currently this doesn't take into account `Tunables`, but that's left as a TODO for later. * Filling out Winch append_code method * Adding back in changes from previous branch Most of these are a WIP. It's missing trampolines for x64, but a basic one exists for aarch64. It's missing the handling of arguments that exist on the stack. It currently imports `cranelift_wasm::WasmFuncType` since it's what's passed to the `Compiler` trait. It's a bit awkward to use in the `winch_codegen` crate since it mostly operates on `wasmparser` types. I've had to hack in a conversion to get things working. Long term, I'm not sure it's wise to rely on this type but it seems like it's easier on the Cranelift side when creating the stub IR. * Small API changes to make integration easier * Adding in new FuncEnv, only a stub for now * Removing unneeded parts of the old PoC, and refactoring trampoline code * Moving FuncEnv into a separate file * More comments for trampolines * Adding in winch integration tests for first pass * Using new addressing method to fix stack pointer error * Adding test for stack arguments * Only run tests on x86 for now, it's more complete for winch * Add in missing documentation after rebase * Updating based on feedback in draft PR * Fixing formatting on doc comment for argv register * Running formatting * Lock updates, and turning on winch feature flags during tests * Updating configuration with comments to no longer gate Strategy enum * Using the winch-environ FuncEnv, but it required changing the sig * Proper comment formatting * Removing wasmtime-winch from dev-dependencies, adding the winch feature makes this not necessary * Update doc attr to include winch check * Adding winch feature to doc generation, which seems to fix the feature error in CI * Add the `component-model` feature to the cargo doc invocation in CI To match the metadata used by the docs.rs invocation when building docs. * Add a comment clarifying the usage of `component-model` for docs.rs * Correctly order wasmtime-winch and winch-environ in the publish script * Ensure x86 test dependencies are included in cfg(target_arch) * Further constrain Winch tests to x86_64 _and_ unix --------- Co-authored-by: Alex Crichton <alex@alexcrichton.com> Co-authored-by: Saúl Cabrera <saulecabrera@gmail.com>
This commit is contained in:
@@ -4,7 +4,7 @@ use wasmparser::ValType;
|
||||
/// Slots for stack arguments are addressed from the frame pointer.
|
||||
/// Slots for function-defined locals and for registers are addressed
|
||||
/// from the stack pointer.
|
||||
#[derive(Eq, PartialEq)]
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
enum Base {
|
||||
FP,
|
||||
SP,
|
||||
@@ -14,6 +14,7 @@ enum Base {
|
||||
///
|
||||
/// Represents the type, location and addressing mode of a local
|
||||
/// in the stack's local and argument area.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct LocalSlot {
|
||||
/// The offset of the local slot.
|
||||
pub offset: u32,
|
||||
|
||||
@@ -210,3 +210,10 @@ where
|
||||
let alignment_mask = alignment - 1.into();
|
||||
(value + alignment_mask) & !alignment_mask
|
||||
}
|
||||
|
||||
/// Calculates the delta needed to adjust a function's frame plus some
|
||||
/// addend to a given alignment.
|
||||
pub(crate) fn calculate_frame_adjustment(frame_size: u32, addend: u32, alignment: u32) -> u32 {
|
||||
let total = frame_size + addend;
|
||||
(alignment - (total % alignment)) % alignment
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//! calling convention, see [ABI].
|
||||
use super::CodeGenContext;
|
||||
use crate::{
|
||||
abi::{align_to, ABIArg, ABIResult, ABISig, ABI},
|
||||
masm::{MacroAssembler, OperandSize},
|
||||
abi::{align_to, calculate_frame_adjustment, ABIArg, ABIResult, ABISig, ABI},
|
||||
masm::{CalleeKind, MacroAssembler, OperandSize},
|
||||
reg::Reg,
|
||||
stack::Val,
|
||||
};
|
||||
@@ -39,7 +39,7 @@ pub(crate) struct FnCall<'a> {
|
||||
/// │ │
|
||||
/// │ Stack space created by any previous spills │
|
||||
/// │ from the value stack; and which memory values │
|
||||
/// │ are used as function arguments. │
|
||||
/// │ are used as function arguments. │
|
||||
/// │ │
|
||||
/// ├──────────────────────────────────────────────────┤ ---> The Wasm value stack at this point in time would look like:
|
||||
/// │ │ [ Reg | Reg | Mem(offset) | Mem(offset) ]
|
||||
@@ -51,7 +51,7 @@ pub(crate) struct FnCall<'a> {
|
||||
/// ├─────────────────────────────────────────────────┬┤ ---> The Wasm value stack at this point in time would look like:
|
||||
/// │ │ [ Mem(offset) | Mem(offset) | Mem(offset) | Mem(offset) ]
|
||||
/// │ │ Assuming that the callee takes 4 arguments, we calculate
|
||||
/// │ │ 2 spilled registers + 2 memory values; all of which will be used
|
||||
/// │ │ 2 spilled registers + 2 memory values; all of which will be used
|
||||
/// │ Stack space allocated for │ as arguments to the call via `assign_args`, thus the memory they represent is
|
||||
/// │ the callee function arguments in the stack; │ is considered to be consumed by the call.
|
||||
/// │ represented by `arg_stack_space` │
|
||||
@@ -59,7 +59,7 @@ pub(crate) struct FnCall<'a> {
|
||||
/// │ │
|
||||
/// │ │
|
||||
/// └──────────────────────────────────────────────────┘ ------> Stack pointer when emitting the call
|
||||
///
|
||||
///
|
||||
total_stack_space: u32,
|
||||
/// The total stack space needed for the callee arguments on the
|
||||
/// stack, including any adjustments to the function's frame and
|
||||
@@ -161,7 +161,7 @@ impl<'a> FnCall<'a> {
|
||||
) {
|
||||
masm.reserve_stack(self.arg_stack_space);
|
||||
self.assign_args(context, masm, <A as ABI>::scratch_reg());
|
||||
masm.call(callee);
|
||||
masm.call(CalleeKind::Direct(callee));
|
||||
masm.free_stack(self.total_stack_space);
|
||||
context.drop_last(self.abi_sig.params.len());
|
||||
// The stack pointer at the end of the function call
|
||||
@@ -213,10 +213,3 @@ impl<'a> FnCall<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the delta needed to adjust a function's frame plus some
|
||||
/// addend to a given alignment.
|
||||
fn calculate_frame_adjustment(frame_size: u32, addend: u32, alignment: u32) -> u32 {
|
||||
let total = frame_size + addend;
|
||||
(alignment - (total % alignment)) % alignment
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ mod context;
|
||||
pub(crate) use context::*;
|
||||
mod env;
|
||||
pub use env::*;
|
||||
mod call;
|
||||
pub mod call;
|
||||
|
||||
/// The code generation abstraction.
|
||||
pub(crate) struct CodeGen<'a, A, M>
|
||||
@@ -59,7 +59,7 @@ where
|
||||
pub fn emit(
|
||||
&mut self,
|
||||
body: &mut BinaryReader<'a>,
|
||||
validator: FuncValidator<ValidatorResources>,
|
||||
validator: &mut FuncValidator<ValidatorResources>,
|
||||
) -> Result<()> {
|
||||
self.emit_start()
|
||||
.and_then(|_| self.emit_body(body, validator))
|
||||
@@ -78,7 +78,7 @@ where
|
||||
fn emit_body(
|
||||
&mut self,
|
||||
body: &mut BinaryReader<'a>,
|
||||
mut validator: FuncValidator<ValidatorResources>,
|
||||
validator: &mut FuncValidator<ValidatorResources>,
|
||||
) -> Result<()> {
|
||||
self.spill_register_arguments();
|
||||
let defined_locals_range = &self.context.frame.defined_locals_range;
|
||||
|
||||
@@ -20,6 +20,47 @@ impl DefinedLocalsRange {
|
||||
}
|
||||
}
|
||||
|
||||
/// An abstraction to read the defined locals from the WASM binary for a function.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct DefinedLocals {
|
||||
/// The defined locals for a function.
|
||||
pub defined_locals: Locals,
|
||||
/// The size of the defined locals.
|
||||
pub stack_size: u32,
|
||||
}
|
||||
|
||||
impl DefinedLocals {
|
||||
/// Compute the local slots for a WASM function.
|
||||
pub fn new(
|
||||
reader: &mut BinaryReader<'_>,
|
||||
validator: &mut FuncValidator<ValidatorResources>,
|
||||
) -> Result<Self> {
|
||||
let mut next_stack = 0;
|
||||
// The first 32 bits of a WASM binary function describe the number of locals
|
||||
let local_count = reader.read_var_u32()?;
|
||||
let mut slots: Locals = Default::default();
|
||||
|
||||
for _ in 0..local_count {
|
||||
let position = reader.original_position();
|
||||
let count = reader.read_var_u32()?;
|
||||
let ty = reader.read()?;
|
||||
validator.define_locals(position, count, ty)?;
|
||||
|
||||
let ty: ValType = ty.try_into()?;
|
||||
for _ in 0..count {
|
||||
let ty_size = ty_size(&ty);
|
||||
next_stack = align_to(next_stack, ty_size) + ty_size;
|
||||
slots.push(LocalSlot::new(ty, next_stack));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
defined_locals: slots,
|
||||
stack_size: next_stack,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Frame handler abstraction.
|
||||
pub(crate) struct Frame {
|
||||
/// The size of the entire local area; the arguments plus the function defined locals.
|
||||
@@ -37,22 +78,29 @@ pub(crate) struct Frame {
|
||||
|
||||
impl Frame {
|
||||
/// Allocate a new Frame.
|
||||
pub fn new<A: ABI>(
|
||||
sig: &ABISig,
|
||||
body: &mut BinaryReader<'_>,
|
||||
validator: &mut FuncValidator<ValidatorResources>,
|
||||
abi: &A,
|
||||
) -> Result<Self> {
|
||||
pub fn new<A: ABI>(sig: &ABISig, defined_locals: &DefinedLocals, abi: &A) -> Result<Self> {
|
||||
let (mut locals, defined_locals_start) = Self::compute_arg_slots(sig, abi)?;
|
||||
let (defined_slots, defined_locals_end) =
|
||||
Self::compute_defined_slots(body, validator, defined_locals_start)?;
|
||||
locals.extend(defined_slots);
|
||||
let locals_size = align_to(defined_locals_end, abi.stack_align().into());
|
||||
|
||||
// The defined locals have a zero-based offset by default
|
||||
// so we need to add the defined locals start to the offset.
|
||||
locals.extend(
|
||||
defined_locals
|
||||
.defined_locals
|
||||
.iter()
|
||||
.map(|l| LocalSlot::new(l.ty, l.offset + defined_locals_start)),
|
||||
);
|
||||
|
||||
let locals_size = align_to(
|
||||
defined_locals_start + defined_locals.stack_size,
|
||||
abi.stack_align().into(),
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
locals,
|
||||
locals_size,
|
||||
defined_locals_range: DefinedLocalsRange(defined_locals_start..defined_locals_end),
|
||||
defined_locals_range: DefinedLocalsRange(
|
||||
defined_locals_start..defined_locals.stack_size,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -115,30 +163,4 @@ impl Frame {
|
||||
ABIArg::Stack { ty, offset } => LocalSlot::stack_arg(*ty, offset + arg_base_offset),
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_defined_slots(
|
||||
reader: &mut BinaryReader<'_>,
|
||||
validator: &mut FuncValidator<ValidatorResources>,
|
||||
next_stack: u32,
|
||||
) -> Result<(Locals, u32)> {
|
||||
let mut next_stack = next_stack;
|
||||
let local_count = reader.read_var_u32()?;
|
||||
let mut slots: Locals = Default::default();
|
||||
|
||||
for _ in 0..local_count {
|
||||
let position = reader.original_position();
|
||||
let count = reader.read_var_u32()?;
|
||||
let ty = reader.read()?;
|
||||
validator.define_locals(position, count, ty)?;
|
||||
|
||||
let ty: ValType = ty.try_into()?;
|
||||
for _ in 0..count {
|
||||
let ty_size = ty_size(&ty);
|
||||
next_stack = align_to(next_stack, ty_size) + ty_size;
|
||||
slots.push(LocalSlot::new(ty, next_stack));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((slots, next_stack))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
abi::local::LocalSlot,
|
||||
codegen::CodeGenContext,
|
||||
isa::reg::Reg,
|
||||
masm::{DivKind, MacroAssembler as Masm, OperandSize, RegImm, RemKind},
|
||||
masm::{CalleeKind, DivKind, MacroAssembler as Masm, OperandSize, RegImm, RemKind},
|
||||
};
|
||||
use cranelift_codegen::{settings, Final, MachBufferFinalized};
|
||||
|
||||
@@ -136,7 +136,7 @@ impl Masm for MacroAssembler {
|
||||
self.asm.str(src, dst, size);
|
||||
}
|
||||
|
||||
fn call(&mut self, _callee: u32) {
|
||||
fn call(&mut self, _callee: CalleeKind) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ impl Masm for MacroAssembler {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn sp_offset(&mut self) -> u32 {
|
||||
fn sp_offset(&self) -> u32 {
|
||||
self.sp_offset
|
||||
}
|
||||
|
||||
@@ -194,6 +194,10 @@ impl Masm for MacroAssembler {
|
||||
|
||||
self.sp_offset
|
||||
}
|
||||
|
||||
fn address_from_reg(&self, reg: Reg, offset: u32) -> Self::Address {
|
||||
Address::offset(reg, offset as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl MacroAssembler {
|
||||
|
||||
@@ -2,7 +2,7 @@ use self::regs::{scratch, ALL_GPR};
|
||||
use crate::{
|
||||
abi::ABI,
|
||||
codegen::{CodeGen, CodeGenContext},
|
||||
frame::Frame,
|
||||
frame::{DefinedLocals, Frame},
|
||||
isa::{Builder, TargetIsa},
|
||||
masm::MacroAssembler,
|
||||
regalloc::RegAlloc,
|
||||
@@ -86,14 +86,16 @@ impl TargetIsa for Aarch64 {
|
||||
sig: &FuncType,
|
||||
body: &FunctionBody,
|
||||
env: &dyn FuncEnv,
|
||||
mut validator: FuncValidator<ValidatorResources>,
|
||||
validator: &mut FuncValidator<ValidatorResources>,
|
||||
) -> Result<MachBufferFinalized<Final>> {
|
||||
let mut body = body.get_binary_reader();
|
||||
let mut masm = Aarch64Masm::new(self.shared_flags.clone());
|
||||
let stack = Stack::new();
|
||||
let abi = abi::Aarch64ABI::default();
|
||||
let abi_sig = abi.sig(sig);
|
||||
let frame = Frame::new(&abi_sig, &mut body, &mut validator, &abi)?;
|
||||
|
||||
let defined_locals = DefinedLocals::new(&mut body, validator)?;
|
||||
let frame = Frame::new(&abi_sig, &defined_locals, &abi)?;
|
||||
// TODO: Add floating point bitmask
|
||||
let regalloc = RegAlloc::new(RegSet::new(ALL_GPR, 0), scratch());
|
||||
let codegen_context = CodeGenContext::new(regalloc, stack, &frame);
|
||||
@@ -113,4 +115,8 @@ impl TargetIsa for Aarch64 {
|
||||
// See `cranelift_codegen::isa::TargetIsa::function_alignment`.
|
||||
32
|
||||
}
|
||||
|
||||
fn host_to_wasm_trampoline(&self, _ty: &FuncType) -> Result<MachBufferFinalized<Final>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,12 +90,13 @@ pub trait TargetIsa: Send + Sync {
|
||||
false
|
||||
}
|
||||
|
||||
/// Compile a function.
|
||||
fn compile_function(
|
||||
&self,
|
||||
sig: &FuncType,
|
||||
body: &FunctionBody,
|
||||
env: &dyn FuncEnv,
|
||||
validator: FuncValidator<ValidatorResources>,
|
||||
validator: &mut FuncValidator<ValidatorResources>,
|
||||
) -> Result<MachBufferFinalized<Final>>;
|
||||
|
||||
/// Get the default calling convention of the underlying target triple.
|
||||
@@ -119,6 +120,9 @@ pub trait TargetIsa: Send + Sync {
|
||||
|
||||
/// See `cranelift_codegen::isa::TargetIsa::function_alignment`.
|
||||
fn function_alignment(&self) -> u32;
|
||||
|
||||
/// Generate a trampoline that can be used to call a wasm function from wasmtime.
|
||||
fn host_to_wasm_trampoline(&self, ty: &FuncType) -> Result<MachBufferFinalized<Final>>;
|
||||
}
|
||||
|
||||
impl Debug for &dyn TargetIsa {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
isa::reg::Reg,
|
||||
masm::{DivKind, OperandSize, RemKind},
|
||||
masm::{CalleeKind, DivKind, OperandSize, RemKind},
|
||||
};
|
||||
use cranelift_codegen::{
|
||||
entity::EntityRef,
|
||||
@@ -469,17 +469,31 @@ impl Assembler {
|
||||
});
|
||||
}
|
||||
|
||||
/// Direct function call to a user defined function.
|
||||
pub fn call(&mut self, callee: u32) {
|
||||
let dest = ExternalName::user(UserExternalNameRef::new(callee as usize));
|
||||
self.emit(Inst::CallKnown {
|
||||
dest,
|
||||
info: Box::new(CallInfo {
|
||||
uses: smallvec![],
|
||||
defs: smallvec![],
|
||||
clobbers: Default::default(),
|
||||
opcode: Opcode::Call,
|
||||
}),
|
||||
});
|
||||
pub fn call(&mut self, callee: CalleeKind) {
|
||||
match callee {
|
||||
CalleeKind::Indirect(reg) => {
|
||||
self.emit(Inst::CallUnknown {
|
||||
dest: RegMem::reg(reg.into()),
|
||||
info: Box::new(CallInfo {
|
||||
uses: smallvec![],
|
||||
defs: smallvec![],
|
||||
clobbers: Default::default(),
|
||||
opcode: Opcode::Call,
|
||||
}),
|
||||
});
|
||||
}
|
||||
CalleeKind::Direct(index) => {
|
||||
let dest = ExternalName::user(UserExternalNameRef::new(index as usize));
|
||||
self.emit(Inst::CallKnown {
|
||||
dest,
|
||||
info: Box::new(CallInfo {
|
||||
uses: smallvec![],
|
||||
defs: smallvec![],
|
||||
clobbers: Default::default(),
|
||||
opcode: Opcode::Call,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ use super::{
|
||||
asm::{Assembler, Operand},
|
||||
regs::{self, rbp, rsp},
|
||||
};
|
||||
use crate::isa::reg::Reg;
|
||||
use crate::masm::{DivKind, MacroAssembler as Masm, OperandSize, RegImm, RemKind};
|
||||
use crate::{abi::LocalSlot, codegen::CodeGenContext, stack::Val};
|
||||
use crate::{isa::reg::Reg, masm::CalleeKind};
|
||||
use cranelift_codegen::{isa::x64::settings as x64_settings, settings, Final, MachBufferFinalized};
|
||||
|
||||
/// x64 MacroAssembler.
|
||||
@@ -114,7 +114,7 @@ impl Masm for MacroAssembler {
|
||||
self.decrement_sp(8);
|
||||
}
|
||||
|
||||
fn call(&mut self, callee: u32) {
|
||||
fn call(&mut self, callee: CalleeKind) {
|
||||
self.asm.call(callee);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ impl Masm for MacroAssembler {
|
||||
self.asm.mov(src, dst, size);
|
||||
}
|
||||
|
||||
fn sp_offset(&mut self) -> u32 {
|
||||
fn sp_offset(&self) -> u32 {
|
||||
self.sp_offset
|
||||
}
|
||||
|
||||
@@ -236,6 +236,10 @@ impl Masm for MacroAssembler {
|
||||
fn finalize(self) -> MachBufferFinalized<Final> {
|
||||
self.asm.finalize()
|
||||
}
|
||||
|
||||
fn address_from_reg(&self, reg: Reg, offset: u32) -> Self::Address {
|
||||
Address::offset(reg, offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl MacroAssembler {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use crate::abi::ABI;
|
||||
use crate::codegen::{CodeGen, CodeGenContext};
|
||||
use crate::frame::Frame;
|
||||
use crate::{
|
||||
abi::ABI,
|
||||
codegen::{CodeGen, CodeGenContext},
|
||||
};
|
||||
|
||||
use crate::frame::{DefinedLocals, Frame};
|
||||
use crate::isa::x64::masm::MacroAssembler as X64Masm;
|
||||
use crate::masm::MacroAssembler;
|
||||
use crate::regalloc::RegAlloc;
|
||||
use crate::stack::Stack;
|
||||
use crate::trampoline::Trampoline;
|
||||
use crate::FuncEnv;
|
||||
use crate::{
|
||||
isa::{Builder, TargetIsa},
|
||||
@@ -87,14 +91,16 @@ impl TargetIsa for X64 {
|
||||
sig: &FuncType,
|
||||
body: &FunctionBody,
|
||||
env: &dyn FuncEnv,
|
||||
mut validator: FuncValidator<ValidatorResources>,
|
||||
validator: &mut FuncValidator<ValidatorResources>,
|
||||
) -> Result<MachBufferFinalized<Final>> {
|
||||
let mut body = body.get_binary_reader();
|
||||
let mut masm = X64Masm::new(self.shared_flags.clone(), self.isa_flags.clone());
|
||||
let stack = Stack::new();
|
||||
let abi = abi::X64ABI::default();
|
||||
let abi_sig = abi.sig(sig);
|
||||
let frame = Frame::new(&abi_sig, &mut body, &mut validator, &abi)?;
|
||||
|
||||
let defined_locals = DefinedLocals::new(&mut body, validator)?;
|
||||
let frame = Frame::new(&abi_sig, &defined_locals, &abi)?;
|
||||
// TODO Add in floating point bitmask
|
||||
let regalloc = RegAlloc::new(RegSet::new(ALL_GPR, 0), regs::scratch());
|
||||
let codegen_context = CodeGenContext::new(regalloc, stack, &frame);
|
||||
@@ -113,4 +119,15 @@ impl TargetIsa for X64 {
|
||||
// See `cranelift_codegen`'s value of this for more information.
|
||||
16
|
||||
}
|
||||
|
||||
fn host_to_wasm_trampoline(&self, ty: &FuncType) -> Result<MachBufferFinalized<Final>> {
|
||||
let abi = abi::X64ABI::default();
|
||||
let mut masm = X64Masm::new(self.shared_flags.clone(), self.isa_flags.clone());
|
||||
|
||||
let mut trampoline = Trampoline::new(&mut masm, &abi, regs::scratch(), regs::argv());
|
||||
|
||||
trampoline.emit_host_to_wasm(ty);
|
||||
|
||||
Ok(masm.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,18 @@ pub(crate) fn scratch() -> Reg {
|
||||
r11()
|
||||
}
|
||||
|
||||
/// This register is used as a scratch register, in the context of trampolines only,
|
||||
/// where we assume that callee-saved registers are given the correct handling
|
||||
/// according to the system ABI. r12 is chosen given that it's a callee-saved,
|
||||
/// non-argument register.
|
||||
///
|
||||
/// In the context of all other internal functions, this register is not excluded
|
||||
/// from register allocation, so no extra assumptions should be made regarding
|
||||
/// its availability.
|
||||
pub(crate) fn argv() -> Reg {
|
||||
r12()
|
||||
}
|
||||
|
||||
fn fpr(enc: u8) -> Reg {
|
||||
Reg::new(PReg::new(enc as usize, RegClass::Float))
|
||||
}
|
||||
|
||||
@@ -16,4 +16,5 @@ mod masm;
|
||||
mod regalloc;
|
||||
mod regset;
|
||||
mod stack;
|
||||
mod trampoline;
|
||||
mod visitor;
|
||||
|
||||
@@ -39,6 +39,13 @@ pub(crate) enum RegImm {
|
||||
Imm(i64),
|
||||
}
|
||||
|
||||
pub(crate) enum CalleeKind {
|
||||
/// A function call to a raw address.
|
||||
Indirect(Reg),
|
||||
/// A function call to a local function.
|
||||
Direct(u32),
|
||||
}
|
||||
|
||||
impl RegImm {
|
||||
/// Register constructor.
|
||||
pub fn reg(r: Reg) -> Self {
|
||||
@@ -102,11 +109,14 @@ pub(crate) trait MacroAssembler {
|
||||
/// current position of the stack pointer (e.g. [sp + offset].
|
||||
fn address_at_sp(&self, offset: u32) -> Self::Address;
|
||||
|
||||
/// Emit a function call to a locally defined function.
|
||||
fn call(&mut self, callee: u32);
|
||||
/// Construct an address that is relative to the given register.
|
||||
fn address_from_reg(&self, reg: Reg, offset: u32) -> Self::Address;
|
||||
|
||||
/// Emit a function call to either a local or external function.
|
||||
fn call(&mut self, callee: CalleeKind);
|
||||
|
||||
/// Get stack pointer offset.
|
||||
fn sp_offset(&mut self) -> u32;
|
||||
fn sp_offset(&self) -> u32;
|
||||
|
||||
/// Perform a stack store.
|
||||
fn store(&mut self, src: RegImm, dst: Self::Address, size: OperandSize);
|
||||
|
||||
176
winch/codegen/src/trampoline.rs
Normal file
176
winch/codegen/src/trampoline.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
use crate::{
|
||||
abi::{align_to, calculate_frame_adjustment, ABIArg, ABIResult, ABI},
|
||||
masm::{CalleeKind, MacroAssembler, OperandSize, RegImm},
|
||||
reg::Reg,
|
||||
};
|
||||
use std::mem;
|
||||
use wasmparser::{FuncType, ValType};
|
||||
|
||||
/// A trampoline to provide interopt between different calling conventions.
|
||||
pub(crate) struct Trampoline<'a, A, M>
|
||||
where
|
||||
A: ABI,
|
||||
M: MacroAssembler,
|
||||
{
|
||||
/// The macro assembler.
|
||||
masm: &'a mut M,
|
||||
/// The ABI.
|
||||
abi: &'a A,
|
||||
/// The main scratch register for the current architecture. It is not allocatable for the callee.
|
||||
scratch_reg: Reg,
|
||||
/// A second scratch register. This will be allocatable for the callee, so it can only be used
|
||||
/// after the callee-saved registers are on the stack.
|
||||
alloc_scratch_reg: Reg,
|
||||
}
|
||||
|
||||
impl<'a, A, M> Trampoline<'a, A, M>
|
||||
where
|
||||
A: ABI,
|
||||
M: MacroAssembler,
|
||||
{
|
||||
/// Create a new trampoline.
|
||||
pub fn new(masm: &'a mut M, abi: &'a A, scratch_reg: Reg, alloc_scratch_reg: Reg) -> Self {
|
||||
Self {
|
||||
masm,
|
||||
abi,
|
||||
scratch_reg,
|
||||
alloc_scratch_reg,
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit the host to wasm trampoline.
|
||||
pub fn emit_host_to_wasm(&mut self, ty: &FuncType) {
|
||||
// The host to wasm trampoline is currently hard coded (see vmcontext.rs in the
|
||||
// wasmtime-runtime crate, VMTrampoline).
|
||||
// The first two parameters are VMContexts (not used at this time).
|
||||
// The third parameter is the function pointer to call.
|
||||
// The fourth parameter is an address to storage space for both the return value and the
|
||||
// arguments to the function.
|
||||
let trampoline_ty = FuncType::new(
|
||||
vec![ValType::I64, ValType::I64, ValType::I64, ValType::I64],
|
||||
vec![],
|
||||
);
|
||||
|
||||
// TODO: We should be passing a calling convention here so the signature can determine the
|
||||
// correct location of arguments. When we fully support system ABIs, this will need to be
|
||||
// updated.
|
||||
let trampoline_sig = self.abi.sig(&trampoline_ty);
|
||||
|
||||
// Hard-coding the size in bytes of the trampoline arguments since it's static, based on
|
||||
// the current signature we should always have 4 arguments, each of which is 8 bytes.
|
||||
let trampoline_arg_size = 32;
|
||||
|
||||
let callee_sig = self.abi.sig(ty);
|
||||
|
||||
let val_ptr = if let ABIArg::Reg { reg, ty: _ty } = &trampoline_sig.params[3] {
|
||||
Ok(RegImm::reg(*reg))
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Expected the val ptr to be in a register"))
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
self.masm.prologue();
|
||||
|
||||
// TODO: When we include support for passing calling conventions, we need to update this to
|
||||
// adhere to the system ABI. Currently, callee-saved registers are not preserved while we
|
||||
// are building this out.
|
||||
|
||||
let mut trampoline_arg_offsets: [u32; 4] = [0; 4];
|
||||
|
||||
trampoline_sig
|
||||
.params
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, param)| {
|
||||
if let ABIArg::Reg { reg, ty: _ty } = param {
|
||||
let offset = self.masm.push(*reg);
|
||||
trampoline_arg_offsets[i] = offset;
|
||||
}
|
||||
});
|
||||
|
||||
let val_ptr_offset = trampoline_arg_offsets[3];
|
||||
let func_ptr_offset = trampoline_arg_offsets[2];
|
||||
|
||||
self.masm.mov(
|
||||
val_ptr,
|
||||
RegImm::reg(self.scratch_reg),
|
||||
crate::masm::OperandSize::S64,
|
||||
);
|
||||
|
||||
// How much we need to adjust the stack pointer by to account for the alignment
|
||||
// required by the ISA.
|
||||
let delta = calculate_frame_adjustment(
|
||||
self.masm.sp_offset(),
|
||||
self.abi.arg_base_offset() as u32,
|
||||
self.abi.call_stack_align() as u32,
|
||||
);
|
||||
|
||||
// The total amount of stack space we need to reserve for the arguments.
|
||||
let total_arg_stack_space = align_to(
|
||||
callee_sig.stack_bytes + delta,
|
||||
self.abi.call_stack_align() as u32,
|
||||
);
|
||||
|
||||
self.masm.reserve_stack(total_arg_stack_space);
|
||||
|
||||
// The max size a value can be when reading from the params memory location.
|
||||
let value_size = mem::size_of::<u128>();
|
||||
|
||||
callee_sig.params.iter().enumerate().for_each(|(i, param)| {
|
||||
let value_offset = (i * value_size) as u32;
|
||||
|
||||
match param {
|
||||
ABIArg::Reg { reg, ty } => self.masm.load(
|
||||
self.masm.address_from_reg(self.scratch_reg, value_offset),
|
||||
*reg,
|
||||
(*ty).into(),
|
||||
),
|
||||
ABIArg::Stack { offset, ty } => {
|
||||
self.masm.load(
|
||||
self.masm.address_from_reg(self.scratch_reg, value_offset),
|
||||
self.alloc_scratch_reg,
|
||||
(*ty).into(),
|
||||
);
|
||||
self.masm.store(
|
||||
RegImm::reg(self.alloc_scratch_reg),
|
||||
self.masm.address_at_sp(*offset),
|
||||
(*ty).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Move the function pointer from it's stack location into a scratch register.
|
||||
self.masm.load(
|
||||
self.masm.address_from_sp(func_ptr_offset),
|
||||
self.scratch_reg,
|
||||
OperandSize::S64,
|
||||
);
|
||||
|
||||
// Call the function that was passed into the trampoline.
|
||||
self.masm.call(CalleeKind::Indirect(self.scratch_reg));
|
||||
|
||||
self.masm.free_stack(total_arg_stack_space);
|
||||
|
||||
// Move the val ptr back into the scratch register so we can load the return values.
|
||||
self.masm.load(
|
||||
self.masm.address_from_sp(val_ptr_offset),
|
||||
self.scratch_reg,
|
||||
OperandSize::S64,
|
||||
);
|
||||
|
||||
// Move the return values into the value ptr.
|
||||
// We are only support a single return value at this time.
|
||||
let ABIResult::Reg { reg, ty } = &callee_sig.result;
|
||||
self.masm.store(
|
||||
RegImm::reg(*reg),
|
||||
self.masm.address_from_reg(self.scratch_reg, 0),
|
||||
(*ty).unwrap().into(),
|
||||
);
|
||||
|
||||
// TODO: Once we support system ABIs better, callee-saved registers will need to be
|
||||
// restored here.
|
||||
|
||||
self.masm.epilogue(trampoline_arg_size);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ pub struct FuncEnv<'a> {
|
||||
/// Type information about a module, once it has been validated.
|
||||
pub types: &'a Types,
|
||||
/// The current ISA.
|
||||
pub isa: &'a dyn TargetIsa,
|
||||
pub isa: &'a Box<dyn TargetIsa>,
|
||||
}
|
||||
|
||||
impl<'a> winch_codegen::FuncEnv for FuncEnv<'a> {
|
||||
@@ -35,7 +35,7 @@ impl<'a> winch_codegen::FuncEnv for FuncEnv<'a> {
|
||||
|
||||
impl<'a> FuncEnv<'a> {
|
||||
/// Create a new function environment.
|
||||
pub fn new(module: &'a Module, types: &'a Types, isa: &'a dyn TargetIsa) -> Self {
|
||||
pub fn new(module: &'a Module, types: &'a Types, isa: &'a Box<dyn TargetIsa>) -> Self {
|
||||
Self { module, types, isa }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use target_lexicon::Architecture;
|
||||
use winch_codegen::TargetIsa;
|
||||
|
||||
/// Disassemble and print a machine code buffer.
|
||||
pub fn disasm(bytes: &[u8], isa: &dyn TargetIsa) -> Result<Vec<String>> {
|
||||
pub fn disasm(bytes: &[u8], isa: &Box<dyn TargetIsa>) -> Result<Vec<String>> {
|
||||
let dis = disassembler_for(isa)?;
|
||||
let insts = dis.disasm_all(bytes, 0x0).unwrap();
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn disasm(bytes: &[u8], isa: &dyn TargetIsa) -> Result<Vec<String>> {
|
||||
Ok(disassembled_lines)
|
||||
}
|
||||
|
||||
fn disassembler_for(isa: &dyn TargetIsa) -> Result<Capstone> {
|
||||
fn disassembler_for(isa: &Box<dyn TargetIsa>) -> Result<Capstone> {
|
||||
let disasm = match isa.triple().architecture {
|
||||
Architecture::X86_64 => Capstone::new()
|
||||
.x86()
|
||||
|
||||
@@ -109,7 +109,7 @@ mod test {
|
||||
let body_inputs = std::mem::take(&mut translation.function_body_inputs);
|
||||
let module = &translation.module;
|
||||
let types = translation.get_types();
|
||||
let env = FuncEnv::new(module, &types, &*isa);
|
||||
let env = FuncEnv::new(module, &types, &isa);
|
||||
|
||||
let binding = body_inputs
|
||||
.into_iter()
|
||||
@@ -151,11 +151,11 @@ mod test {
|
||||
.function_at(index.as_u32())
|
||||
.expect(&format!("function type at index {:?}", index.as_u32()));
|
||||
let FunctionBodyData { body, validator } = f.1;
|
||||
let validator = validator.into_validator(Default::default());
|
||||
let mut validator = validator.into_validator(Default::default());
|
||||
|
||||
let buffer = env
|
||||
.isa
|
||||
.compile_function(&sig, &body, env, validator)
|
||||
.compile_function(&sig, &body, env, &mut validator)
|
||||
.expect("Couldn't compile function");
|
||||
|
||||
disasm(buffer.data(), env.isa).unwrap()
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn run(opt: &Options) -> Result<()> {
|
||||
let body_inputs = std::mem::take(&mut translation.function_body_inputs);
|
||||
let module = &translation.module;
|
||||
let types = translation.get_types();
|
||||
let env = FuncEnv::new(module, &types, &*isa);
|
||||
let env = FuncEnv::new(module, &types, &isa);
|
||||
|
||||
body_inputs
|
||||
.into_iter()
|
||||
@@ -57,10 +57,10 @@ fn compile(env: &FuncEnv, f: (DefinedFuncIndex, FunctionBodyData<'_>)) -> Result
|
||||
.function_at(index.as_u32())
|
||||
.expect(&format!("function type at index {:?}", index.as_u32()));
|
||||
let FunctionBodyData { body, validator } = f.1;
|
||||
let validator = validator.into_validator(Default::default());
|
||||
let mut validator = validator.into_validator(Default::default());
|
||||
let buffer = env
|
||||
.isa
|
||||
.compile_function(&sig, &body, env, validator)
|
||||
.compile_function(&sig, &body, env, &mut validator)
|
||||
.expect("Couldn't compile function");
|
||||
|
||||
println!("Disassembly for function: {}", index.as_u32());
|
||||
@@ -68,5 +68,15 @@ fn compile(env: &FuncEnv, f: (DefinedFuncIndex, FunctionBodyData<'_>)) -> Result
|
||||
.iter()
|
||||
.for_each(|s| println!("{}", s));
|
||||
|
||||
let buffer = env
|
||||
.isa
|
||||
.host_to_wasm_trampoline(sig)
|
||||
.expect("Couldn't compile trampoline");
|
||||
|
||||
println!("Disassembly for trampoline: {}", index.as_u32());
|
||||
disasm(buffer.data(), env.isa)?
|
||||
.iter()
|
||||
.for_each(|s| println!("{}", s));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user