Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

winch(trampolines): Save SP, FP and return address #6400

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 94 additions & 28 deletions tests/all/winch.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anyhow::{bail, Result};
use wasmtime::*;

const MODULE: &'static str = r#"
Expand Down Expand Up @@ -45,80 +46,145 @@ fn add_fn(store: impl AsContextMut) -> Func {

#[test]
#[cfg_attr(miri, ignore)]
fn array_to_wasm() {
fn array_to_wasm() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c).unwrap();
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, MODULE).unwrap();
let module = Module::new(&engine, MODULE)?;

let add_fn = add_fn(store.as_context_mut());
let instance = Instance::new(&mut store, &module, &[add_fn.into()]).unwrap();
let instance = Instance::new(&mut store, &module, &[add_fn.into()])?;

let constant = instance
.get_func(&mut store, "42")
.ok_or(anyhow::anyhow!("test function not found"))
.unwrap();
.ok_or(anyhow::anyhow!("test function not found"))?;
let mut returns = vec![Val::null(); 1];
constant.call(&mut store, &[], &mut returns).unwrap();
constant.call(&mut store, &[], &mut returns)?;

assert_eq!(returns.len(), 1);
assert_eq!(returns[0].unwrap_i32(), 42);

let sum = instance
.get_func(&mut store, "sum10")
.ok_or(anyhow::anyhow!("sum10 function not found"))
.unwrap();
.ok_or(anyhow::anyhow!("sum10 function not found"))?;
let mut returns = vec![Val::null(); 1];
let args = vec![Val::I32(1); 10];
sum.call(&mut store, &args, &mut returns).unwrap();
sum.call(&mut store, &args, &mut returns)?;

assert_eq!(returns.len(), 1);
assert_eq!(returns[0].unwrap_i32(), 10);
Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
fn native_to_wasm() {
fn native_to_wasm() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c).unwrap();
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, MODULE).unwrap();
let module = Module::new(&engine, MODULE)?;

let add_fn = add_fn(store.as_context_mut());
let instance = Instance::new(&mut store, &module, &[add_fn.into()]).unwrap();
let instance = Instance::new(&mut store, &module, &[add_fn.into()])?;

let f = instance
.get_typed_func::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32), i32>(
&mut store, "sum10",
)
.unwrap();
let f = instance.get_typed_func::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32), i32>(
&mut store, "sum10",
)?;

let args = (1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
let result = f.call(&mut store, args).unwrap();
let result = f.call(&mut store, args)?;

assert_eq!(result, 10);
Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
fn wasm_to_native() {
fn wasm_to_native() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c).unwrap();
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, MODULE).unwrap();

let module = Module::new(&engine, MODULE)?;
let add_fn = add_fn(store.as_context_mut());
let instance = Instance::new(&mut store, &module, &[add_fn.into()]).unwrap();

let f = instance
.get_typed_func::<(i32, i32), i32>(&mut store, "call_add")
.unwrap();
let instance = Instance::new(&mut store, &module, &[add_fn.into()])?;

let f = instance.get_typed_func::<(i32, i32), i32>(&mut store, "call_add")?;

let args = (41, 1);
let result = f.call(&mut store, args).unwrap();
let result = f.call(&mut store, args)?;

assert_eq!(result, 42);
Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
#[cfg_attr(windows, ignore)]
// NB
//
// This and the following test(`native_to_wasm_trap` and `wasm_to_native_trap`),
// are mostly smoke tests to ensure Winch's trampolines are compliant with fast
// stack walking. The ideal state is one in which we should not have to worry
// about testing the backtrace implementation per compiler, but instead be
// certain that a single set of test cases is enough to ensure that the machine
// code generated by Winch and Cranelift is compliant. One way to achieve this
// could be to share the implementation of trampolines between Cranelift and
// Winch.
//
// FIXME The following two tests are also temporarily ignored on Windows, since
// we are not emitting the require unwind information yet.
fn native_to_wasm_trap() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let wat = r#"
(module
(func $div_by_zero (result i32)
(i32.const 1)
(i32.const 0)
i32.div_u)

(export "div_by_zero" (func $div_by_zero)))
"#;
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, wat)?;
let instance = Instance::new(&mut store, &module, &[])?;
let f = instance.get_typed_func::<(), i32>(&mut store, "div_by_zero")?;
let result = f.call(&mut store, ()).unwrap_err();

assert!(result.downcast_ref::<WasmBacktrace>().is_some());

Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
#[cfg_attr(windows, ignore)]
fn wasm_to_native_trap() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let wat = r#"
(module
(import "" "" (func $fail))
(func $call_fail
call $fail)

(export "call_fail" (func $call_fail)))
"#;
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, wat)?;
let func = Func::wrap::<(), (), Result<()>>(&mut store, || bail!("error"));
let instance = Instance::new(&mut store, &module, &[func.into()])?;
let f = instance.get_typed_func::<(), ()>(&mut store, "call_fail")?;
let result = f.call(&mut store, ()).unwrap_err();

assert!(result.downcast_ref::<WasmBacktrace>().is_some());

Ok(())
}
6 changes: 6 additions & 0 deletions winch/codegen/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub(crate) trait ABI {
/// The offset to the argument base, relative to the frame pointer.
fn arg_base_offset(&self) -> u8;

/// The offset to the return address, relative to the frame pointer.
fn ret_addr_offset() -> u8;

/// Construct the ABI-specific signature from a WebAssembly
/// function type.
fn sig(&self, wasm_sig: &FuncType, call_conv: &CallingConvention) -> ABISig;
Expand All @@ -82,6 +85,9 @@ pub(crate) trait ABI {
/// Returns the frame pointer register.
fn fp_reg() -> Reg;

/// Returns the stack pointer register.
fn sp_reg() -> Reg;

/// Returns the pinned register used to hold
/// the `VMContext`.
fn vmctx_reg() -> Reg;
Expand Down
8 changes: 8 additions & 0 deletions winch/codegen/src/isa/aarch64/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ impl ABI for Aarch64ABI {
16
}

fn ret_addr_offset() -> u8 {
8
}

fn word_bits() -> u32 {
64
}
Expand Down Expand Up @@ -87,6 +91,10 @@ impl ABI for Aarch64ABI {
todo!()
}

fn sp_reg() -> Reg {
todo!()
}

fn fp_reg() -> Reg {
regs::fp()
}
Expand Down
16 changes: 16 additions & 0 deletions winch/codegen/src/isa/x64/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ impl ABI for X64ABI {
16
}

fn ret_addr_offset() -> u8 {
// 1 8-byte slot.
// ┌──────────┬
// │ Ret │
// │ Addr │
// ├──────────┼ * offset
// │ │
// │ FP │
// └──────────┴
8
}

fn word_bits() -> u32 {
64
}
Expand Down Expand Up @@ -125,6 +137,10 @@ impl ABI for X64ABI {
regs::rbp()
}

fn sp_reg() -> Reg {
regs::rsp()
}

fn vmctx_reg() -> Reg {
regs::vmctx()
}
Expand Down
Loading