winch(x64): Improve ABI support in trampolines (#6204)
This commit improves ABI support in Winch's trampolines mainly by: * Adding support for the `fastcall` calling convention. * By storing/restoring callee-saved registers. One of the explicit goals of this change is to make tests available in the x86_64 target as a whole and remove the need exclude the windows target. This commit also introduces a `CallingConvention` enum, to better reflect the subset of calling conventions that are supported by Winch.
This commit is contained in:
@@ -34,8 +34,8 @@ mod traps;
|
|||||||
mod wait_notify;
|
mod wait_notify;
|
||||||
mod wasi_testsuite;
|
mod wasi_testsuite;
|
||||||
mod wast;
|
mod wast;
|
||||||
// Currently Winch is only supported in x86_64 unix platforms.
|
// Currently Winch is only supported in x86_64.
|
||||||
#[cfg(all(target_arch = "x86_64", target_family = "unix"))]
|
#[cfg(all(target_arch = "x86_64"))]
|
||||||
mod winch;
|
mod winch;
|
||||||
|
|
||||||
/// A helper to compile a module in a new store with reference types enabled.
|
/// A helper to compile a module in a new store with reference types enabled.
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
//! | + arguments |
|
//! | + arguments |
|
||||||
//! | | ----> Space allocated for calls
|
//! | | ----> Space allocated for calls
|
||||||
//! | |
|
//! | |
|
||||||
use crate::isa::reg::Reg;
|
use crate::isa::{reg::Reg, CallingConvention};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::ops::{Add, BitAnd, Not, Sub};
|
use std::ops::{Add, BitAnd, Not, Sub};
|
||||||
use wasmparser::{FuncType, ValType};
|
use wasmparser::{FuncType, ValType};
|
||||||
@@ -65,7 +65,7 @@ pub(crate) trait ABI {
|
|||||||
|
|
||||||
/// Construct the ABI-specific signature from a WebAssembly
|
/// Construct the ABI-specific signature from a WebAssembly
|
||||||
/// function type.
|
/// function type.
|
||||||
fn sig(&self, wasm_sig: &FuncType) -> ABISig;
|
fn sig(&self, wasm_sig: &FuncType, call_conv: &CallingConvention) -> ABISig;
|
||||||
|
|
||||||
/// Returns the number of bits in a word.
|
/// Returns the number of bits in a word.
|
||||||
fn word_bits() -> u32;
|
fn word_bits() -> u32;
|
||||||
@@ -77,6 +77,10 @@ pub(crate) trait ABI {
|
|||||||
|
|
||||||
/// Returns the designated scratch register.
|
/// Returns the designated scratch register.
|
||||||
fn scratch_reg() -> Reg;
|
fn scratch_reg() -> Reg;
|
||||||
|
|
||||||
|
/// Returns the callee-saved registers for the given
|
||||||
|
/// calling convention.
|
||||||
|
fn callee_saved_regs(call_conv: &CallingConvention) -> SmallVec<[Reg; 9]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ABI-specific representation of a function argument.
|
/// ABI-specific representation of a function argument.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
abi::{ABISig, ABI},
|
abi::{ABISig, ABI},
|
||||||
masm::{MacroAssembler, OperandSize},
|
masm::{MacroAssembler, OperandSize},
|
||||||
|
CallingConvention,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use call::FnCall;
|
use call::FnCall;
|
||||||
@@ -127,7 +128,7 @@ where
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
let sig = self.abi.sig(&callee.ty);
|
let sig = self.abi.sig(&callee.ty, &CallingConvention::Default);
|
||||||
let fncall = FnCall::new(self.abi, &sig, &mut self.context, self.masm);
|
let fncall = FnCall::new(self.abi, &sig, &mut self.context, self.masm);
|
||||||
fncall.emit::<M, A>(self.masm, &mut self.context, index);
|
fncall.emit::<M, A>(self.masm, &mut self.context, index);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::regs;
|
use super::regs;
|
||||||
use crate::abi::{ABIArg, ABIResult, ABISig, ABI};
|
use crate::abi::{ABIArg, ABIResult, ABISig, ABI};
|
||||||
use crate::isa::reg::Reg;
|
use crate::isa::{reg::Reg, CallingConvention};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use wasmparser::{FuncType, ValType};
|
use wasmparser::{FuncType, ValType};
|
||||||
|
|
||||||
@@ -59,7 +59,9 @@ impl ABI for Aarch64ABI {
|
|||||||
64
|
64
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sig(&self, wasm_sig: &FuncType) -> ABISig {
|
fn sig(&self, wasm_sig: &FuncType, call_conv: &CallingConvention) -> ABISig {
|
||||||
|
assert!(call_conv.is_apple_aarch64() || call_conv.is_default());
|
||||||
|
|
||||||
if wasm_sig.results().len() > 1 {
|
if wasm_sig.results().len() > 1 {
|
||||||
panic!("multi-value not supported");
|
panic!("multi-value not supported");
|
||||||
}
|
}
|
||||||
@@ -84,6 +86,10 @@ impl ABI for Aarch64ABI {
|
|||||||
fn scratch_reg() -> Reg {
|
fn scratch_reg() -> Reg {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn callee_saved_regs(_call_conv: &CallingConvention) -> SmallVec<[Reg; 9]> {
|
||||||
|
regs::callee_saved()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Aarch64ABI {
|
impl Aarch64ABI {
|
||||||
@@ -118,6 +124,7 @@ mod tests {
|
|||||||
abi::{ABIArg, ABI},
|
abi::{ABIArg, ABI},
|
||||||
isa::aarch64::regs,
|
isa::aarch64::regs,
|
||||||
isa::reg::Reg,
|
isa::reg::Reg,
|
||||||
|
isa::CallingConvention,
|
||||||
};
|
};
|
||||||
use wasmparser::{
|
use wasmparser::{
|
||||||
FuncType,
|
FuncType,
|
||||||
@@ -140,7 +147,7 @@ mod tests {
|
|||||||
let wasm_sig = FuncType::new([I32, I64, I32, I64, I32, I32, I64, I32, I64], []);
|
let wasm_sig = FuncType::new([I32, I64, I32, I64, I32, I32, I64, I32, I64], []);
|
||||||
|
|
||||||
let abi = Aarch64ABI::default();
|
let abi = Aarch64ABI::default();
|
||||||
let sig = abi.sig(&wasm_sig);
|
let sig = abi.sig(&wasm_sig, &CallingConvention::Default);
|
||||||
let params = sig.params;
|
let params = sig.params;
|
||||||
|
|
||||||
match_reg_arg(params.get(0).unwrap(), I32, regs::xreg(0));
|
match_reg_arg(params.get(0).unwrap(), I32, regs::xreg(0));
|
||||||
@@ -159,7 +166,7 @@ mod tests {
|
|||||||
let wasm_sig = FuncType::new([F32, F64, F32, F64, F32, F32, F64, F32, F64], []);
|
let wasm_sig = FuncType::new([F32, F64, F32, F64, F32, F32, F64, F32, F64], []);
|
||||||
|
|
||||||
let abi = Aarch64ABI::default();
|
let abi = Aarch64ABI::default();
|
||||||
let sig = abi.sig(&wasm_sig);
|
let sig = abi.sig(&wasm_sig, &CallingConvention::Default);
|
||||||
let params = sig.params;
|
let params = sig.params;
|
||||||
|
|
||||||
match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));
|
match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));
|
||||||
@@ -178,7 +185,7 @@ mod tests {
|
|||||||
let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []);
|
let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []);
|
||||||
|
|
||||||
let abi = Aarch64ABI::default();
|
let abi = Aarch64ABI::default();
|
||||||
let sig = abi.sig(&wasm_sig);
|
let sig = abi.sig(&wasm_sig, &CallingConvention::Default);
|
||||||
let params = sig.params;
|
let params = sig.params;
|
||||||
|
|
||||||
match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));
|
match_reg_arg(params.get(0).unwrap(), F32, regs::vreg(0));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{
|
|||||||
abi::ABI,
|
abi::ABI,
|
||||||
codegen::{CodeGen, CodeGenContext},
|
codegen::{CodeGen, CodeGenContext},
|
||||||
frame::{DefinedLocals, Frame},
|
frame::{DefinedLocals, Frame},
|
||||||
isa::{Builder, TargetIsa},
|
isa::{Builder, CallingConvention, TargetIsa},
|
||||||
masm::MacroAssembler,
|
masm::MacroAssembler,
|
||||||
regalloc::RegAlloc,
|
regalloc::RegAlloc,
|
||||||
regset::RegSet,
|
regset::RegSet,
|
||||||
@@ -92,7 +92,7 @@ impl TargetIsa for Aarch64 {
|
|||||||
let mut masm = Aarch64Masm::new(self.shared_flags.clone());
|
let mut masm = Aarch64Masm::new(self.shared_flags.clone());
|
||||||
let stack = Stack::new();
|
let stack = Stack::new();
|
||||||
let abi = abi::Aarch64ABI::default();
|
let abi = abi::Aarch64ABI::default();
|
||||||
let abi_sig = abi.sig(sig);
|
let abi_sig = abi.sig(sig, &CallingConvention::Default);
|
||||||
|
|
||||||
let defined_locals = DefinedLocals::new(&mut body, validator)?;
|
let defined_locals = DefinedLocals::new(&mut body, validator)?;
|
||||||
let frame = Frame::new(&abi_sig, &defined_locals, &abi)?;
|
let frame = Frame::new(&abi_sig, &defined_locals, &abi)?;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use crate::isa::reg::Reg;
|
use crate::isa::reg::Reg;
|
||||||
use regalloc2::{PReg, RegClass};
|
use regalloc2::{PReg, RegClass};
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
/// Construct a X-register from an index.
|
/// Construct a X-register from an index.
|
||||||
pub(crate) const fn xreg(num: u8) -> Reg {
|
pub(crate) const fn xreg(num: u8) -> Reg {
|
||||||
@@ -135,3 +136,25 @@ const NON_ALLOCATABLE_GPR: u32 = (1 << ip0().hw_enc())
|
|||||||
|
|
||||||
/// Bitmask to represent the available general purpose registers.
|
/// Bitmask to represent the available general purpose registers.
|
||||||
pub(crate) const ALL_GPR: u32 = u32::MAX & !NON_ALLOCATABLE_GPR;
|
pub(crate) const ALL_GPR: u32 = u32::MAX & !NON_ALLOCATABLE_GPR;
|
||||||
|
|
||||||
|
/// Returns the callee-saved registers.
|
||||||
|
///
|
||||||
|
/// This function will return the set of registers that need to be saved
|
||||||
|
/// according to the system ABI and that are known not to be saved during the
|
||||||
|
/// prologue emission.
|
||||||
|
// TODO: Once float registers are supported,
|
||||||
|
// account for callee-saved float registers.
|
||||||
|
pub(crate) fn callee_saved() -> SmallVec<[Reg; 9]> {
|
||||||
|
smallvec![
|
||||||
|
xreg(19),
|
||||||
|
xreg(20),
|
||||||
|
xreg(21),
|
||||||
|
xreg(22),
|
||||||
|
xreg(23),
|
||||||
|
xreg(24),
|
||||||
|
xreg(25),
|
||||||
|
xreg(26),
|
||||||
|
xreg(27),
|
||||||
|
xreg(28),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,6 +70,60 @@ pub(crate) enum LookupError {
|
|||||||
SupportDisabled,
|
SupportDisabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calling conventions supported by Winch. Winch supports the `Wasmtime*`
|
||||||
|
/// variations of the system's ABI calling conventions and an internal default
|
||||||
|
/// calling convention.
|
||||||
|
///
|
||||||
|
/// This enum is a reduced subset of the calling conventions defined in
|
||||||
|
/// [cranelift_codegen::isa::CallConv]. Introducing this enum makes it easier
|
||||||
|
/// to enforce the invariant of all the calling conventions supported by Winch.
|
||||||
|
pub enum CallingConvention {
|
||||||
|
/// See [cranelift_codegen::isa::CallConv::WasmtimeSystemV]
|
||||||
|
WasmtimeSystemV,
|
||||||
|
/// See [cranelift_codegen::isa::CallConv::WasmtimeFastcall]
|
||||||
|
WasmtimeFastcall,
|
||||||
|
/// See [cranelift_codegen::isa::CallConv::WasmtimeAppleAarch64]
|
||||||
|
WasmtimeAppleAarch64,
|
||||||
|
/// The default calling convention for Winch. It largely follows SystemV
|
||||||
|
/// for parameter and result handling. This calling convention is part of
|
||||||
|
/// Winch's default ABI [crate::abi::ABI].
|
||||||
|
Default,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallingConvention {
|
||||||
|
/// Returns true if the current calling convention is `WasmtimeFastcall`.
|
||||||
|
fn is_fastcall(&self) -> bool {
|
||||||
|
match &self {
|
||||||
|
CallingConvention::WasmtimeFastcall => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the current calling convention is `WasmtimeSystemV`.
|
||||||
|
fn is_systemv(&self) -> bool {
|
||||||
|
match &self {
|
||||||
|
CallingConvention::WasmtimeSystemV => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the current calling convention is `WasmtimeAppleAarch64`.
|
||||||
|
fn is_apple_aarch64(&self) -> bool {
|
||||||
|
match &self {
|
||||||
|
CallingConvention::WasmtimeAppleAarch64 => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the current calling convention is `Default`.
|
||||||
|
fn is_default(&self) -> bool {
|
||||||
|
match &self {
|
||||||
|
CallingConvention::Default => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A trait representing commonalities between the supported
|
/// A trait representing commonalities between the supported
|
||||||
/// instruction set architectures.
|
/// instruction set architectures.
|
||||||
pub trait TargetIsa: Send + Sync {
|
pub trait TargetIsa: Send + Sync {
|
||||||
@@ -100,10 +154,21 @@ pub trait TargetIsa: Send + Sync {
|
|||||||
) -> Result<MachBufferFinalized<Final>>;
|
) -> Result<MachBufferFinalized<Final>>;
|
||||||
|
|
||||||
/// Get the default calling convention of the underlying target triple.
|
/// Get the default calling convention of the underlying target triple.
|
||||||
fn call_conv(&self) -> CallConv {
|
fn default_call_conv(&self) -> CallConv {
|
||||||
CallConv::triple_default(&self.triple())
|
CallConv::triple_default(&self.triple())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derive Wasmtime's calling convention from the triple's default
|
||||||
|
/// calling convention.
|
||||||
|
fn wasmtime_call_conv(&self) -> CallingConvention {
|
||||||
|
match self.default_call_conv() {
|
||||||
|
CallConv::AppleAarch64 => CallingConvention::WasmtimeAppleAarch64,
|
||||||
|
CallConv::SystemV => CallingConvention::WasmtimeSystemV,
|
||||||
|
CallConv::WindowsFastcall => CallingConvention::WasmtimeFastcall,
|
||||||
|
cc => unimplemented!("calling convention: {:?}", cc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the endianess of the underlying target triple.
|
/// Get the endianess of the underlying target triple.
|
||||||
fn endianness(&self) -> target_lexicon::Endianness {
|
fn endianness(&self) -> target_lexicon::Endianness {
|
||||||
self.triple().endianness().unwrap()
|
self.triple().endianness().unwrap()
|
||||||
@@ -131,7 +196,7 @@ impl Debug for &dyn TargetIsa {
|
|||||||
f,
|
f,
|
||||||
"Target ISA {{ triple: {:?}, calling convention: {:?} }}",
|
"Target ISA {{ triple: {:?}, calling convention: {:?} }}",
|
||||||
self.triple(),
|
self.triple(),
|
||||||
self.call_conv()
|
self.default_call_conv()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::regs;
|
use super::regs;
|
||||||
use crate::{
|
use crate::{
|
||||||
abi::{ABIArg, ABIResult, ABISig, ABI},
|
abi::{ABIArg, ABIResult, ABISig, ABI},
|
||||||
isa::reg::Reg,
|
isa::{reg::Reg, CallingConvention},
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use wasmparser::{FuncType, ValType};
|
use wasmparser::{FuncType, ValType};
|
||||||
@@ -12,15 +12,39 @@ use wasmparser::{FuncType, ValType};
|
|||||||
/// The first element tracks the general purpose register index.
|
/// The first element tracks the general purpose register index.
|
||||||
/// The second element tracks the floating point register index.
|
/// The second element tracks the floating point register index.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct RegIndexEnv(u8, u8);
|
struct RegIndexEnv {
|
||||||
|
/// General purpose register index or the field used for absolute
|
||||||
|
/// counts.
|
||||||
|
gpr_or_absolute_count: u8,
|
||||||
|
/// Floating point register index.
|
||||||
|
fpr: u8,
|
||||||
|
/// Whether the count should be absolute rather than per register class.
|
||||||
|
/// When this field is true, only the `gpr_or_absolute_count` field is
|
||||||
|
/// incremented.
|
||||||
|
absolute_count: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegIndexEnv {
|
||||||
|
fn with_absolute_count() -> Self {
|
||||||
|
Self {
|
||||||
|
gpr_or_absolute_count: 0,
|
||||||
|
fpr: 0,
|
||||||
|
absolute_count: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RegIndexEnv {
|
impl RegIndexEnv {
|
||||||
fn next_gpr(&mut self) -> u8 {
|
fn next_gpr(&mut self) -> u8 {
|
||||||
Self::increment(&mut self.0)
|
Self::increment(&mut self.gpr_or_absolute_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_fpr(&mut self) -> u8 {
|
fn next_fpr(&mut self) -> u8 {
|
||||||
Self::increment(&mut self.1)
|
if self.absolute_count {
|
||||||
|
Self::increment(&mut self.gpr_or_absolute_count)
|
||||||
|
} else {
|
||||||
|
Self::increment(&mut self.fpr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn increment(index: &mut u8) -> u8 {
|
fn increment(index: &mut u8) -> u8 {
|
||||||
@@ -60,22 +84,33 @@ impl ABI for X64ABI {
|
|||||||
64
|
64
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sig(&self, wasm_sig: &FuncType) -> ABISig {
|
fn sig(&self, wasm_sig: &FuncType, call_conv: &CallingConvention) -> ABISig {
|
||||||
|
assert!(call_conv.is_fastcall() || call_conv.is_systemv() || call_conv.is_default());
|
||||||
|
|
||||||
if wasm_sig.results().len() > 1 {
|
if wasm_sig.results().len() > 1 {
|
||||||
panic!("multi-value not supported");
|
panic!("multi-value not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut stack_offset = 0;
|
let is_fastcall = call_conv.is_fastcall();
|
||||||
let mut index_env = RegIndexEnv::default();
|
// In the fastcall calling convention, the callee gets a contiguous
|
||||||
|
// stack area of 32 bytes (4 register arguments) just before its frame.
|
||||||
|
// See
|
||||||
|
// https://learn.microsoft.com/en-us/cpp/build/stack-usage?view=msvc-170#stack-allocation
|
||||||
|
let (mut stack_offset, mut index_env) = if is_fastcall {
|
||||||
|
(32, RegIndexEnv::with_absolute_count())
|
||||||
|
} else {
|
||||||
|
(0, RegIndexEnv::default())
|
||||||
|
};
|
||||||
|
|
||||||
let params: SmallVec<[ABIArg; 6]> = wasm_sig
|
let params: SmallVec<[ABIArg; 6]> = wasm_sig
|
||||||
.params()
|
.params()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| Self::to_abi_arg(arg, &mut stack_offset, &mut index_env))
|
.map(|arg| Self::to_abi_arg(arg, &mut stack_offset, &mut index_env, is_fastcall))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let ty = wasm_sig.results().get(0).map(|e| e.clone());
|
let ty = wasm_sig.results().get(0).map(|e| e.clone());
|
||||||
// NOTE temporarily defaulting to rax.
|
// The `Default`, `WasmtimeFastcall` and `WasmtimeSystemV use `rax`.
|
||||||
|
// NOTE This should be updated when supporting multi-value.
|
||||||
let reg = regs::rax();
|
let reg = regs::rax();
|
||||||
let result = ABIResult::reg(ty, reg);
|
let result = ABIResult::reg(ty, reg);
|
||||||
|
|
||||||
@@ -85,6 +120,10 @@ impl ABI for X64ABI {
|
|||||||
fn scratch_reg() -> Reg {
|
fn scratch_reg() -> Reg {
|
||||||
regs::scratch()
|
regs::scratch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn callee_saved_regs(call_conv: &CallingConvention) -> SmallVec<[Reg; 9]> {
|
||||||
|
regs::callee_saved(call_conv)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl X64ABI {
|
impl X64ABI {
|
||||||
@@ -92,11 +131,16 @@ impl X64ABI {
|
|||||||
wasm_arg: &ValType,
|
wasm_arg: &ValType,
|
||||||
stack_offset: &mut u32,
|
stack_offset: &mut u32,
|
||||||
index_env: &mut RegIndexEnv,
|
index_env: &mut RegIndexEnv,
|
||||||
|
fastcall: bool,
|
||||||
) -> ABIArg {
|
) -> ABIArg {
|
||||||
let (reg, ty) = match wasm_arg {
|
let (reg, ty) = match wasm_arg {
|
||||||
ty @ (ValType::I32 | ValType::I64) => (Self::int_reg_for(index_env.next_gpr()), ty),
|
ty @ (ValType::I32 | ValType::I64) => {
|
||||||
|
(Self::int_reg_for(index_env.next_gpr(), fastcall), ty)
|
||||||
|
}
|
||||||
|
|
||||||
ty @ (ValType::F32 | ValType::F64) => (Self::float_reg_for(index_env.next_fpr()), ty),
|
ty @ (ValType::F32 | ValType::F64) => {
|
||||||
|
(Self::float_reg_for(index_env.next_fpr(), fastcall), ty)
|
||||||
|
}
|
||||||
|
|
||||||
ty => unreachable!("Unsupported argument type {:?}", ty),
|
ty => unreachable!("Unsupported argument type {:?}", ty),
|
||||||
};
|
};
|
||||||
@@ -111,28 +155,36 @@ impl X64ABI {
|
|||||||
reg.map_or_else(default, |reg| ABIArg::reg(reg, *ty))
|
reg.map_or_else(default, |reg| ABIArg::reg(reg, *ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn int_reg_for(index: u8) -> Option<Reg> {
|
fn int_reg_for(index: u8, fastcall: bool) -> Option<Reg> {
|
||||||
match index {
|
match (fastcall, index) {
|
||||||
0 => Some(regs::rdi()),
|
(false, 0) => Some(regs::rdi()),
|
||||||
1 => Some(regs::rsi()),
|
(false, 1) => Some(regs::rsi()),
|
||||||
2 => Some(regs::rdx()),
|
(false, 2) => Some(regs::rdx()),
|
||||||
3 => Some(regs::rcx()),
|
(false, 3) => Some(regs::rcx()),
|
||||||
4 => Some(regs::r8()),
|
(false, 4) => Some(regs::r8()),
|
||||||
5 => Some(regs::r9()),
|
(false, 5) => Some(regs::r9()),
|
||||||
|
(true, 0) => Some(regs::rcx()),
|
||||||
|
(true, 1) => Some(regs::rdx()),
|
||||||
|
(true, 2) => Some(regs::r8()),
|
||||||
|
(true, 3) => Some(regs::r9()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn float_reg_for(index: u8) -> Option<Reg> {
|
fn float_reg_for(index: u8, fastcall: bool) -> Option<Reg> {
|
||||||
match index {
|
match (fastcall, index) {
|
||||||
0 => Some(regs::xmm0()),
|
(false, 0) => Some(regs::xmm0()),
|
||||||
1 => Some(regs::xmm1()),
|
(false, 1) => Some(regs::xmm1()),
|
||||||
2 => Some(regs::xmm2()),
|
(false, 2) => Some(regs::xmm2()),
|
||||||
3 => Some(regs::xmm3()),
|
(false, 3) => Some(regs::xmm3()),
|
||||||
4 => Some(regs::xmm4()),
|
(false, 4) => Some(regs::xmm4()),
|
||||||
5 => Some(regs::xmm5()),
|
(false, 5) => Some(regs::xmm5()),
|
||||||
6 => Some(regs::xmm6()),
|
(false, 6) => Some(regs::xmm6()),
|
||||||
7 => Some(regs::xmm7()),
|
(false, 7) => Some(regs::xmm7()),
|
||||||
|
(true, 0) => Some(regs::xmm0()),
|
||||||
|
(true, 1) => Some(regs::xmm1()),
|
||||||
|
(true, 2) => Some(regs::xmm2()),
|
||||||
|
(true, 3) => Some(regs::xmm3()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,6 +197,7 @@ mod tests {
|
|||||||
abi::{ABIArg, ABI},
|
abi::{ABIArg, ABI},
|
||||||
isa::reg::Reg,
|
isa::reg::Reg,
|
||||||
isa::x64::regs,
|
isa::x64::regs,
|
||||||
|
isa::CallingConvention,
|
||||||
};
|
};
|
||||||
use wasmparser::{
|
use wasmparser::{
|
||||||
FuncType,
|
FuncType,
|
||||||
@@ -162,12 +215,21 @@ mod tests {
|
|||||||
assert_eq!(index_env.next_gpr(), 2);
|
assert_eq!(index_env.next_gpr(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reg_index_env_absolute_count() {
|
||||||
|
let mut e = RegIndexEnv::with_absolute_count();
|
||||||
|
assert!(e.next_gpr() == 0);
|
||||||
|
assert!(e.next_fpr() == 1);
|
||||||
|
assert!(e.next_gpr() == 2);
|
||||||
|
assert!(e.next_fpr() == 3);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn int_abi_sig() {
|
fn int_abi_sig() {
|
||||||
let wasm_sig = FuncType::new([I32, I64, I32, I64, I32, I32, I64, I32], []);
|
let wasm_sig = FuncType::new([I32, I64, I32, I64, I32, I32, I64, I32], []);
|
||||||
|
|
||||||
let abi = X64ABI::default();
|
let abi = X64ABI::default();
|
||||||
let sig = abi.sig(&wasm_sig);
|
let sig = abi.sig(&wasm_sig, &CallingConvention::Default);
|
||||||
let params = sig.params;
|
let params = sig.params;
|
||||||
|
|
||||||
match_reg_arg(params.get(0).unwrap(), I32, regs::rdi());
|
match_reg_arg(params.get(0).unwrap(), I32, regs::rdi());
|
||||||
@@ -185,7 +247,7 @@ mod tests {
|
|||||||
let wasm_sig = FuncType::new([F32, F64, F32, F64, F32, F32, F64, F32, F64], []);
|
let wasm_sig = FuncType::new([F32, F64, F32, F64, F32, F32, F64, F32, F64], []);
|
||||||
|
|
||||||
let abi = X64ABI::default();
|
let abi = X64ABI::default();
|
||||||
let sig = abi.sig(&wasm_sig);
|
let sig = abi.sig(&wasm_sig, &CallingConvention::Default);
|
||||||
let params = sig.params;
|
let params = sig.params;
|
||||||
|
|
||||||
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
|
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
|
||||||
@@ -204,7 +266,7 @@ mod tests {
|
|||||||
let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []);
|
let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []);
|
||||||
|
|
||||||
let abi = X64ABI::default();
|
let abi = X64ABI::default();
|
||||||
let sig = abi.sig(&wasm_sig);
|
let sig = abi.sig(&wasm_sig, &CallingConvention::Default);
|
||||||
let params = sig.params;
|
let params = sig.params;
|
||||||
|
|
||||||
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
|
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
|
||||||
@@ -218,6 +280,41 @@ mod tests {
|
|||||||
match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());
|
match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn system_v_call_conv() {
|
||||||
|
let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []);
|
||||||
|
|
||||||
|
let abi = X64ABI::default();
|
||||||
|
let sig = abi.sig(&wasm_sig, &CallingConvention::WasmtimeSystemV);
|
||||||
|
let params = sig.params;
|
||||||
|
|
||||||
|
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
|
||||||
|
match_reg_arg(params.get(1).unwrap(), I32, regs::rdi());
|
||||||
|
match_reg_arg(params.get(2).unwrap(), I64, regs::rsi());
|
||||||
|
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm1());
|
||||||
|
match_reg_arg(params.get(4).unwrap(), I32, regs::rdx());
|
||||||
|
match_reg_arg(params.get(5).unwrap(), F32, regs::xmm2());
|
||||||
|
match_reg_arg(params.get(6).unwrap(), F64, regs::xmm3());
|
||||||
|
match_reg_arg(params.get(7).unwrap(), F32, regs::xmm4());
|
||||||
|
match_reg_arg(params.get(8).unwrap(), F64, regs::xmm5());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fastcall_call_conv() {
|
||||||
|
let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []);
|
||||||
|
|
||||||
|
let abi = X64ABI::default();
|
||||||
|
let sig = abi.sig(&wasm_sig, &CallingConvention::WasmtimeFastcall);
|
||||||
|
let params = sig.params;
|
||||||
|
|
||||||
|
match_reg_arg(params.get(0).unwrap(), F32, regs::xmm0());
|
||||||
|
match_reg_arg(params.get(1).unwrap(), I32, regs::rdx());
|
||||||
|
match_reg_arg(params.get(2).unwrap(), I64, regs::r8());
|
||||||
|
match_reg_arg(params.get(3).unwrap(), F64, regs::xmm3());
|
||||||
|
match_stack_arg(params.get(4).unwrap(), I32, 32);
|
||||||
|
match_stack_arg(params.get(5).unwrap(), F32, 40);
|
||||||
|
}
|
||||||
|
|
||||||
fn match_reg_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_reg: Reg) {
|
fn match_reg_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_reg: Reg) {
|
||||||
match abi_arg {
|
match abi_arg {
|
||||||
&ABIArg::Reg { reg, ty } => {
|
&ABIArg::Reg { reg, ty } => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::frame::{DefinedLocals, Frame};
|
use crate::frame::{DefinedLocals, Frame};
|
||||||
use crate::isa::x64::masm::MacroAssembler as X64Masm;
|
use crate::isa::{x64::masm::MacroAssembler as X64Masm, CallingConvention};
|
||||||
use crate::masm::MacroAssembler;
|
use crate::masm::MacroAssembler;
|
||||||
use crate::regalloc::RegAlloc;
|
use crate::regalloc::RegAlloc;
|
||||||
use crate::stack::Stack;
|
use crate::stack::Stack;
|
||||||
@@ -97,7 +97,7 @@ impl TargetIsa for X64 {
|
|||||||
let mut masm = X64Masm::new(self.shared_flags.clone(), self.isa_flags.clone());
|
let mut masm = X64Masm::new(self.shared_flags.clone(), self.isa_flags.clone());
|
||||||
let stack = Stack::new();
|
let stack = Stack::new();
|
||||||
let abi = abi::X64ABI::default();
|
let abi = abi::X64ABI::default();
|
||||||
let abi_sig = abi.sig(sig);
|
let abi_sig = abi.sig(sig, &CallingConvention::Default);
|
||||||
|
|
||||||
let defined_locals = DefinedLocals::new(&mut body, validator)?;
|
let defined_locals = DefinedLocals::new(&mut body, validator)?;
|
||||||
let frame = Frame::new(&abi_sig, &defined_locals, &abi)?;
|
let frame = Frame::new(&abi_sig, &defined_locals, &abi)?;
|
||||||
@@ -123,8 +123,10 @@ impl TargetIsa for X64 {
|
|||||||
fn host_to_wasm_trampoline(&self, ty: &FuncType) -> Result<MachBufferFinalized<Final>> {
|
fn host_to_wasm_trampoline(&self, ty: &FuncType) -> Result<MachBufferFinalized<Final>> {
|
||||||
let abi = abi::X64ABI::default();
|
let abi = abi::X64ABI::default();
|
||||||
let mut masm = X64Masm::new(self.shared_flags.clone(), self.isa_flags.clone());
|
let mut masm = X64Masm::new(self.shared_flags.clone(), self.isa_flags.clone());
|
||||||
|
let call_conv = self.wasmtime_call_conv();
|
||||||
|
|
||||||
let mut trampoline = Trampoline::new(&mut masm, &abi, regs::scratch(), regs::argv());
|
let mut trampoline =
|
||||||
|
Trampoline::new(&mut masm, &abi, regs::scratch(), regs::argv(), &call_conv);
|
||||||
|
|
||||||
trampoline.emit_host_to_wasm(ty);
|
trampoline.emit_host_to_wasm(ty);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
//! X64 register definition.
|
//! X64 register definition.
|
||||||
|
|
||||||
use crate::isa::reg::Reg;
|
use crate::isa::{reg::Reg, CallingConvention};
|
||||||
use regalloc2::{PReg, RegClass};
|
use regalloc2::{PReg, RegClass};
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
const ENC_RAX: u8 = 0;
|
const ENC_RAX: u8 = 0;
|
||||||
const ENC_RCX: u8 = 1;
|
const ENC_RCX: u8 = 1;
|
||||||
@@ -154,3 +155,24 @@ const NON_ALLOCATABLE_GPR: u32 = (1 << ENC_RBP) | (1 << ENC_RSP) | (1 << ENC_R11
|
|||||||
|
|
||||||
/// Bitmask to represent the available general purpose registers.
|
/// Bitmask to represent the available general purpose registers.
|
||||||
pub(crate) const ALL_GPR: u32 = ALLOCATABLE_GPR & !NON_ALLOCATABLE_GPR;
|
pub(crate) const ALL_GPR: u32 = ALLOCATABLE_GPR & !NON_ALLOCATABLE_GPR;
|
||||||
|
|
||||||
|
/// Returns the callee-saved registers according to a particular calling
|
||||||
|
/// convention.
|
||||||
|
///
|
||||||
|
/// This function will return the set of registers that need to be saved
|
||||||
|
/// according to the system ABI and that are known not to be saved during the
|
||||||
|
/// prologue emission.
|
||||||
|
pub(crate) fn callee_saved(call_conv: &CallingConvention) -> SmallVec<[Reg; 9]> {
|
||||||
|
use CallingConvention::*;
|
||||||
|
match call_conv {
|
||||||
|
WasmtimeSystemV => {
|
||||||
|
smallvec![rbx(), r12(), r13(), r14(), r15(),]
|
||||||
|
}
|
||||||
|
// TODO: Once float registers are supported,
|
||||||
|
// account for callee-saved float registers.
|
||||||
|
WasmtimeFastcall => {
|
||||||
|
smallvec![rbx(), rdi(), rsi(), r12(), r13(), r14(), r15(),]
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
abi::{align_to, calculate_frame_adjustment, ABIArg, ABIResult, ABI},
|
abi::{align_to, calculate_frame_adjustment, ABIArg, ABIResult, ABI},
|
||||||
|
isa::CallingConvention,
|
||||||
masm::{CalleeKind, MacroAssembler, OperandSize, RegImm},
|
masm::{CalleeKind, MacroAssembler, OperandSize, RegImm},
|
||||||
reg::Reg,
|
reg::Reg,
|
||||||
};
|
};
|
||||||
|
use smallvec::SmallVec;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use wasmparser::{FuncType, ValType};
|
use wasmparser::{FuncType, ValType};
|
||||||
|
|
||||||
/// A trampoline to provide interopt between different calling conventions.
|
/// A trampoline to provide interopt between different calling
|
||||||
|
/// conventions.
|
||||||
pub(crate) struct Trampoline<'a, A, M>
|
pub(crate) struct Trampoline<'a, A, M>
|
||||||
where
|
where
|
||||||
A: ABI,
|
A: ABI,
|
||||||
@@ -16,11 +19,19 @@ where
|
|||||||
masm: &'a mut M,
|
masm: &'a mut M,
|
||||||
/// The ABI.
|
/// The ABI.
|
||||||
abi: &'a A,
|
abi: &'a A,
|
||||||
/// The main scratch register for the current architecture. It is not allocatable for the callee.
|
/// The main scratch register for the current architecture. It is
|
||||||
|
/// not allocatable for the callee.
|
||||||
scratch_reg: Reg,
|
scratch_reg: Reg,
|
||||||
/// A second scratch register. This will be allocatable for the callee, so it can only be used
|
/// A second scratch register. This will be allocatable for the
|
||||||
/// after the callee-saved registers are on the stack.
|
/// callee, so it can only be used after the callee-saved
|
||||||
|
/// registers are on the stack.
|
||||||
alloc_scratch_reg: Reg,
|
alloc_scratch_reg: Reg,
|
||||||
|
/// Registers to be saved as part of the trampoline's prologue and epilogue.
|
||||||
|
callee_saved_regs: SmallVec<[Reg; 9]>,
|
||||||
|
/// The calling convention used by the trampoline,
|
||||||
|
/// which is the Wasmtime variant of the system ABI's
|
||||||
|
/// calling convention.
|
||||||
|
call_conv: &'a CallingConvention,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, A, M> Trampoline<'a, A, M>
|
impl<'a, A, M> Trampoline<'a, A, M>
|
||||||
@@ -29,38 +40,44 @@ where
|
|||||||
M: MacroAssembler,
|
M: MacroAssembler,
|
||||||
{
|
{
|
||||||
/// Create a new trampoline.
|
/// Create a new trampoline.
|
||||||
pub fn new(masm: &'a mut M, abi: &'a A, scratch_reg: Reg, alloc_scratch_reg: Reg) -> Self {
|
pub fn new(
|
||||||
|
masm: &'a mut M,
|
||||||
|
abi: &'a A,
|
||||||
|
scratch_reg: Reg,
|
||||||
|
alloc_scratch_reg: Reg,
|
||||||
|
call_conv: &'a CallingConvention,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
masm,
|
masm,
|
||||||
abi,
|
abi,
|
||||||
scratch_reg,
|
scratch_reg,
|
||||||
alloc_scratch_reg,
|
alloc_scratch_reg,
|
||||||
|
callee_saved_regs: <A as ABI>::callee_saved_regs(call_conv),
|
||||||
|
call_conv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit the host to wasm trampoline.
|
/// Emit the host to wasm trampoline.
|
||||||
pub fn emit_host_to_wasm(&mut self, ty: &FuncType) {
|
pub fn emit_host_to_wasm(&mut self, ty: &FuncType) {
|
||||||
// The host to wasm trampoline is currently hard coded (see vmcontext.rs in the
|
// The host to wasm trampoline is currently hard coded (see
|
||||||
// wasmtime-runtime crate, VMTrampoline).
|
// vmcontext.rs in the wasmtime-runtime crate, VMTrampoline).
|
||||||
// The first two parameters are VMContexts (not used at this time).
|
// The first two parameters are VMContexts (not used at this
|
||||||
// The third parameter is the function pointer to call.
|
// 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
|
// The fourth parameter is an address to storage space for
|
||||||
// arguments to the function.
|
// both the return value and the arguments to the function.
|
||||||
let trampoline_ty = FuncType::new(
|
let trampoline_ty = FuncType::new(
|
||||||
vec![ValType::I64, ValType::I64, ValType::I64, ValType::I64],
|
vec![ValType::I64, ValType::I64, ValType::I64, ValType::I64],
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: We should be passing a calling convention here so the signature can determine the
|
let trampoline_sig = self.abi.sig(&trampoline_ty, self.call_conv);
|
||||||
// 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
|
// Hard-coding the size in bytes of the trampoline arguments
|
||||||
// the current signature we should always have 4 arguments, each of which is 8 bytes.
|
// 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 trampoline_arg_size = 32;
|
||||||
|
|
||||||
let callee_sig = self.abi.sig(ty);
|
let callee_sig = self.abi.sig(ty, &CallingConvention::Default);
|
||||||
|
|
||||||
let val_ptr = if let ABIArg::Reg { reg, ty: _ty } = &trampoline_sig.params[3] {
|
let val_ptr = if let ABIArg::Reg { reg, ty: _ty } = &trampoline_sig.params[3] {
|
||||||
Ok(RegImm::reg(*reg))
|
Ok(RegImm::reg(*reg))
|
||||||
@@ -69,11 +86,7 @@ where
|
|||||||
}
|
}
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.masm.prologue();
|
self.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];
|
let mut trampoline_arg_offsets: [u32; 4] = [0; 4];
|
||||||
|
|
||||||
@@ -91,21 +104,19 @@ where
|
|||||||
let val_ptr_offset = trampoline_arg_offsets[3];
|
let val_ptr_offset = trampoline_arg_offsets[3];
|
||||||
let func_ptr_offset = trampoline_arg_offsets[2];
|
let func_ptr_offset = trampoline_arg_offsets[2];
|
||||||
|
|
||||||
self.masm.mov(
|
self.masm
|
||||||
val_ptr,
|
.mov(val_ptr, RegImm::reg(self.scratch_reg), OperandSize::S64);
|
||||||
RegImm::reg(self.scratch_reg),
|
|
||||||
crate::masm::OperandSize::S64,
|
|
||||||
);
|
|
||||||
|
|
||||||
// How much we need to adjust the stack pointer by to account for the alignment
|
// How much we need to adjust the stack pointer by to account
|
||||||
// required by the ISA.
|
// for the alignment required by the ISA.
|
||||||
let delta = calculate_frame_adjustment(
|
let delta = calculate_frame_adjustment(
|
||||||
self.masm.sp_offset(),
|
self.masm.sp_offset(),
|
||||||
self.abi.arg_base_offset() as u32,
|
self.abi.arg_base_offset() as u32,
|
||||||
self.abi.call_stack_align() as u32,
|
self.abi.call_stack_align() as u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
// The total amount of stack space we need to reserve for the arguments.
|
// The total amount of stack space we need to reserve for the
|
||||||
|
// arguments.
|
||||||
let total_arg_stack_space = align_to(
|
let total_arg_stack_space = align_to(
|
||||||
callee_sig.stack_bytes + delta,
|
callee_sig.stack_bytes + delta,
|
||||||
self.abi.call_stack_align() as u32,
|
self.abi.call_stack_align() as u32,
|
||||||
@@ -113,7 +124,8 @@ where
|
|||||||
|
|
||||||
self.masm.reserve_stack(total_arg_stack_space);
|
self.masm.reserve_stack(total_arg_stack_space);
|
||||||
|
|
||||||
// The max size a value can be when reading from the params memory location.
|
// The max size a value can be when reading from the params
|
||||||
|
// memory location.
|
||||||
let value_size = mem::size_of::<u128>();
|
let value_size = mem::size_of::<u128>();
|
||||||
|
|
||||||
callee_sig.params.iter().enumerate().for_each(|(i, param)| {
|
callee_sig.params.iter().enumerate().for_each(|(i, param)| {
|
||||||
@@ -140,7 +152,8 @@ where
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Move the function pointer from it's stack location into a scratch register.
|
// Move the function pointer from it's stack location into a
|
||||||
|
// scratch register.
|
||||||
self.masm.load(
|
self.masm.load(
|
||||||
self.masm.address_from_sp(func_ptr_offset),
|
self.masm.address_from_sp(func_ptr_offset),
|
||||||
self.scratch_reg,
|
self.scratch_reg,
|
||||||
@@ -152,25 +165,42 @@ where
|
|||||||
|
|
||||||
self.masm.free_stack(total_arg_stack_space);
|
self.masm.free_stack(total_arg_stack_space);
|
||||||
|
|
||||||
// Move the val ptr back into the scratch register so we can load the return values.
|
// Move the val ptr back into the scratch register so we can
|
||||||
|
// load the return values.
|
||||||
self.masm.load(
|
self.masm.load(
|
||||||
self.masm.address_from_sp(val_ptr_offset),
|
self.masm.address_from_sp(val_ptr_offset),
|
||||||
self.scratch_reg,
|
self.scratch_reg,
|
||||||
OperandSize::S64,
|
OperandSize::S64,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Move the return values into the value ptr.
|
// Move the return values into the value ptr. We are only
|
||||||
// We are only support a single return value at this time.
|
// supporting a single return value at this time.
|
||||||
let ABIResult::Reg { reg, ty } = &callee_sig.result;
|
let ABIResult::Reg { reg, ty } = &callee_sig.result;
|
||||||
self.masm.store(
|
self.masm.store(
|
||||||
RegImm::reg(*reg),
|
RegImm::reg(*reg),
|
||||||
self.masm.address_from_reg(self.scratch_reg, 0),
|
self.masm.address_from_reg(self.scratch_reg, 0),
|
||||||
(*ty).unwrap().into(),
|
(*ty).unwrap().into(),
|
||||||
);
|
);
|
||||||
|
self.epilogue(trampoline_arg_size);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Once we support system ABIs better, callee-saved registers will need to be
|
/// The trampoline's prologue.
|
||||||
// restored here.
|
fn prologue(&mut self) {
|
||||||
|
self.masm.prologue();
|
||||||
|
// Save any callee-saved registers.
|
||||||
|
for r in &self.callee_saved_regs {
|
||||||
|
self.masm.push(*r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.masm.epilogue(trampoline_arg_size);
|
/// The trampoline's epilogue.
|
||||||
|
fn epilogue(&mut self, arg_size: u32) {
|
||||||
|
// Free the stack space allocated by pushing the trampoline arguments.
|
||||||
|
self.masm.free_stack(arg_size);
|
||||||
|
// Restore the callee-saved registers.
|
||||||
|
for r in self.callee_saved_regs.iter().rev() {
|
||||||
|
self.masm.pop(*r);
|
||||||
|
}
|
||||||
|
self.masm.epilogue(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user