You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

396 lines
11 KiB

use anyhow::bail;
use wasmtime::*;
const MODULE: &'static str = r#"
(module
(import "" "" (func $add (param i32 i32) (result i32)))
(func $test (result i32)
(i32.const 42)
)
(func $sum10 (param $arg_1 i32) (param $arg_2 i32) (param $arg_3 i32) (param $arg_4 i32) (param $arg_5 i32) (param $arg_6 i32) (param $arg_7 i32) (param $arg_8 i32) (param $arg_9 i32) (param $arg_10 i32) (result i32)
local.get $arg_1
local.get $arg_2
i32.add
local.get $arg_3
i32.add
local.get $arg_4
i32.add
local.get $arg_5
i32.add
local.get $arg_6
i32.add
local.get $arg_7
i32.add
local.get $arg_8
i32.add
local.get $arg_9
i32.add
local.get $arg_10
i32.add)
(func $call_add (param i32 i32) (result i32)
(local.get 0)
(local.get 1)
(call $add))
(export "42" (func $test))
(export "sum10" (func $sum10))
(export "call_add" (func $call_add))
)
"#;
const MIXED: &'static str = r#"
(module
(import "" "" (func $id_float (param f64 f64 f64 f64 f32 f32 f32 f32 f64 i32 i64) (result f64)))
(func $call_id_float (param f64 f64 f64 f64 f32 f32 f32 f32 f64 i32 i64) (result f64)
(local.get 0)
(local.get 1)
(local.get 2)
(local.get 3)
(local.get 4)
(local.get 5)
(local.get 6)
(local.get 7)
(local.get 8)
(local.get 9)
(local.get 10)
(call $id_float)
)
(export "call_id_float" (func $call_id_float)))
"#;
fn add_fn(store: impl AsContextMut) -> Func {
Func::wrap(store, |a: i32, b: i32| a + b)
}
fn id_float(store: impl AsContextMut) -> Func {
Func::wrap(
store,
|_: f64, _: f64, _: f64, _: f64, _: f32, _: f32, _: f32, _: f32, x: f64, _: i32, _: i64| x,
)
}
#[test]
#[cfg_attr(miri, ignore)]
fn array_to_wasm() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
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()])?;
let constant = instance
.get_func(&mut store, "42")
.ok_or(anyhow::anyhow!("test function not found"))?;
let mut returns = vec![Val::null_func_ref(); 1];
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"))?;
let mut returns = vec![Val::null_func_ref(); 1];
let args = vec![Val::I32(1); 10];
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() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
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()])?;
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)?;
assert_eq!(result, 10);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn wasm_to_native() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
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()])?;
let call_add = instance.get_typed_func::<(i32, i32), i32>(&mut store, "call_add")?;
let result = call_add.call(&mut store, (41, 1))?;
assert_eq!(result, 42);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn mixed_roundtrip() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, MIXED)?;
let import = id_float(store.as_context_mut());
let instance = Instance::new(&mut store, &module, &[import.into()])?;
let call_id_float = instance
.get_typed_func::<(f64, f64, f64, f64, f32, f32, f32, f32, f64, i32, i64), f64>(
&mut store,
"call_id_float",
)?;
let result = call_id_float.call(
&mut store,
(1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 0, 5),
)?;
assert_eq!(result, 1.9);
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(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn dynamic_heap() -> Result<()> {
let mut c = Config::new();
c.strategy(Strategy::Winch);
c.static_memory_maximum_size(0);
c.static_memory_guard_size(0);
c.guard_before_linear_memory(false);
c.dynamic_memory_guard_size(0);
let engine = Engine::new(&c)?;
let wat = r#"
(module
(type (;0;) (func (result i32)))
(func (;0;) (type 0) (result i32)
(local i32 i64)
global.get 0
i32.eqz
if ;; label = @1
unreachable
end
global.get 0
i32.const 1
i32.sub
global.set 0
memory.size
local.set 0
block ;; label = @1
block ;; label = @2
memory.size
i32.const 65536
i32.mul
i32.const 65511
local.get 0
i32.add
i32.le_u
br_if 0 (;@2;)
local.get 0
i32.const 0
i32.le_s
br_if 0 (;@2;)
local.get 0
i64.load8_s offset=65503
local.set 1
br 1 (;@1;)
end
i64.const 0
local.set 1
end
local.get 1
drop
i32.const 0
)
(memory (;0;) 1 3)
(global (;0;) (mut i32) i32.const 1000)
(export " " (func 0))
(export "" (memory 0))
)
"#;
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, " ")?;
let result = f.call(&mut store, ())?;
assert!(result == 0);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
#[cfg_attr(windows, ignore)]
fn static_oob() -> Result<()> {
let mut c = Config::new();
c.static_memory_maximum_size(65536);
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let wat = r#"
(module
(memory 0 1)
(func (export "") (result i32)
(i32.const 0)
(i32.const 1)
(i32.store offset=726020653)
(i32.const 1)
(memory.grow)
)
)
"#;
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, "")?;
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 dynamic_heap_with_zero_max_size() -> Result<()> {
let mut c = Config::new();
c.static_memory_maximum_size(0);
c.strategy(Strategy::Winch);
let engine = Engine::new(&c)?;
let wat = r#"
(module
(type (;0;) (func (result i64)))
(type (;1;) (func (param f32)))
(func (;0;) (type 0) (result i64)
(local i32 i32 i32 i32 i32 i32 f32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
global.get 0
i32.eqz
if ;; label = @1
unreachable
end
global.get 0
i32.const 1
i32.sub
global.set 0
memory.size
f64.load offset=3598476644
loop (result i32) ;; label = @1
global.get 0
i32.eqz
if ;; label = @2
unreachable
end
global.get 0
i32.const 1
i32.sub
global.set 0
unreachable
end
unreachable
)
(memory (;0;) 0 8)
(global (;0;) (mut i32) i32.const 1000)
(export "" (func 0))
)
"#;
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::<(), i64>(&mut store, "")?;
let result = f.call(&mut store, ()).unwrap_err();
assert!(result.downcast_ref::<WasmBacktrace>().is_some());
Ok(())
}