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
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(())
|
|
}
|
|
|