#![cfg(not(miri))]
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wasmtime::*;
use wasmtime_test_macros::wasmtime_test;
#[test]
fn host_always_has_some_stack() -> Result<()> {
static HITS: AtomicUsize = AtomicUsize::new(0);
const HOST_STACK: usize = 128 * 1024;
let mut store = if cfg!(target_arch = "x86_64") {
let mut config = Config::new();
unsafe {
config.cranelift_flag_set("has_avx", "false");
config.cranelift_flag_set("has_sse42", "false");
config.cranelift_flag_set("has_sse41", "false");
config.cranelift_flag_set("has_ssse3", "false");
config.cranelift_flag_set("has_sse3", "false");
}
Store::new(&Engine::new(&config)?, ())
} else {
Store::<()>::default()
};
let module = Module::new(
store.engine(),
r#"
(module
(import "" "" (func $host1))
(import "" "" (func $host2))
;; exit via wasm-to-native trampoline
(func $recursive1 (export "f1")
call $host1
call $recursive1)
;; exit via wasm-to-array trampoline
(func $recursive2 (export "f2")
call $host2
call $recursive2)
;; exit via a wasmtime-based libcall
(memory 1)
(func $recursive3 (export "f3")
(drop (memory.grow (i32.const 0)))
call $recursive3)
;; exit via a cranelift-based libcall
(func $recursive4 (export "f4")
(drop (call $f32_ceil (f32.const 0)))
call $recursive4)
(func $f32_ceil (param f32) (result f32)
(f32.ceil (local.get 0)))
)
"#,
)?;
let host1 = Func::wrap(&mut store, test_host_stack);
let ty = FuncType::new(store.engine(), [], []);
let host2 = Func::new(&mut store, ty, |_, _, _| {
test_host_stack();
Ok(())
});
let instance = Instance::new(&mut store, &module, &[host1.into(), host2.into()])?;
let f1 = instance.get_typed_func::<(), ()>(&mut store, "f1")?;
let f2 = instance.get_typed_func::<(), ()>(&mut store, "f2")?;
let f3 = instance.get_typed_func::<(), ()>(&mut store, "f3")?;
let f4 = instance.get_typed_func::<(), ()>(&mut store, "f4")?;
let hits1 = HITS.load(SeqCst);
let trap = f1.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
assert_eq!(trap, Trap::StackOverflow);
let hits2 = HITS.load(SeqCst);
let trap = f2.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
assert_eq!(trap, Trap::StackOverflow);
let hits3 = HITS.load(SeqCst);
let trap = f3.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
assert_eq!(trap, Trap::StackOverflow);
let hits4 = HITS.load(SeqCst);
let trap = f4.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
assert_eq!(trap, Trap::StackOverflow);
let hits5 = HITS.load(SeqCst);
assert_eq!(hits1, 0);
assert_eq!(hits2, 0);
assert_eq!(hits3, 0);
assert_eq!(hits4, 0);
assert_eq!(hits5, 0);
return Ok(());
fn test_host_stack() {
HITS.fetch_add(1, SeqCst);
assert!(consume_some_stack(0, HOST_STACK) > 0);
HITS.fetch_sub(1, SeqCst);
}
#[inline(never)]
fn consume_some_stack(ptr: usize, stack: usize) -> usize {
if stack == 0 {
return ptr;
}
let mut space = [0u8; 1024];
consume_some_stack(space.as_mut_ptr() as usize, stack.saturating_sub(1024))
}
}
#[wasmtime_test]
fn big_stack_works_ok(config: &mut Config) -> Result<()> {
if cfg!(asan) {
return Ok(());
}
const N: usize = 10000;
let mut s = String::new();
s.push_str("(module\n");
s.push_str("(func (export \"\") (result i64)\n");
s.push_str("i64.const 0\n");
for _ in 0..N {
s.push_str("call $get\n");
}
for _ in 0..N {
s.push_str("i64.add\n");
}
s.push_str(")\n");
s.push_str("(func $get (result i64) i64.const 0)\n");
s.push_str(")\n");
config.cranelift_opt_level(OptLevel::None);
config.cranelift_regalloc_algorithm(RegallocAlgorithm::SinglePass);
let engine = Engine::new(config)?;
let mut store = Store::new(&engine, ());
let module = Module::new(store.engine(), &s)?;
let instance = Instance::new(&mut store, &module, &[])?;
let func = instance.get_typed_func::<(), i64>(&mut store, "")?;
assert_eq!(func.call(&mut store, ())?, 0);
Ok(())
}