1 #![cfg(not(miri))]
2
3 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
4 use wasmtime::*;
5 use wasmtime_test_macros::wasmtime_test;
6
7 #[test]
host_always_has_some_stack() -> Result<()>8 fn host_always_has_some_stack() -> Result<()> {
9 static HITS: AtomicUsize = AtomicUsize::new(0);
10 // assume hosts always have at least 128k of stack
11 const HOST_STACK: usize = 128 * 1024;
12
13 let mut store = if cfg!(target_arch = "x86_64") {
14 let mut config = Config::new();
15 // Force cranelift-based libcalls to show up by ensuring that platform
16 // support is turned off.
17 unsafe {
18 config.cranelift_flag_set("has_avx", "false");
19 config.cranelift_flag_set("has_sse42", "false");
20 config.cranelift_flag_set("has_sse41", "false");
21 config.cranelift_flag_set("has_ssse3", "false");
22 config.cranelift_flag_set("has_sse3", "false");
23 }
24 Store::new(&Engine::new(&config)?, ())
25 } else {
26 Store::<()>::default()
27 };
28
29 // Create a module that's infinitely recursive, but calls the host on each
30 // level of wasm stack to always test how much host stack we have left.
31 //
32 // Each of the function exports of this module calls out to the host in a
33 // different way, and each one is tested below to make sure that the way of
34 // exiting out to the host is tested thoroughly.
35 let module = Module::new(
36 store.engine(),
37 r#"
38 (module
39 (import "" "" (func $host1))
40 (import "" "" (func $host2))
41
42 ;; exit via wasm-to-native trampoline
43 (func $recursive1 (export "f1")
44 call $host1
45 call $recursive1)
46
47 ;; exit via wasm-to-array trampoline
48 (func $recursive2 (export "f2")
49 call $host2
50 call $recursive2)
51
52 ;; exit via a wasmtime-based libcall
53 (memory 1)
54 (func $recursive3 (export "f3")
55 (drop (memory.grow (i32.const 0)))
56 call $recursive3)
57
58 ;; exit via a cranelift-based libcall
59 (func $recursive4 (export "f4")
60 (drop (call $f32_ceil (f32.const 0)))
61 call $recursive4)
62 (func $f32_ceil (param f32) (result f32)
63 (f32.ceil (local.get 0)))
64 )
65 "#,
66 )?;
67 let host1 = Func::wrap(&mut store, test_host_stack);
68 let ty = FuncType::new(store.engine(), [], []);
69 let host2 = Func::new(&mut store, ty, |_, _, _| {
70 test_host_stack();
71 Ok(())
72 });
73 let instance = Instance::new(&mut store, &module, &[host1.into(), host2.into()])?;
74 let f1 = instance.get_typed_func::<(), ()>(&mut store, "f1")?;
75 let f2 = instance.get_typed_func::<(), ()>(&mut store, "f2")?;
76 let f3 = instance.get_typed_func::<(), ()>(&mut store, "f3")?;
77 let f4 = instance.get_typed_func::<(), ()>(&mut store, "f4")?;
78
79 // Make sure that our function traps and the trap says that the call stack
80 // has been exhausted.
81 let hits1 = HITS.load(SeqCst);
82 let trap = f1.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
83 assert_eq!(trap, Trap::StackOverflow);
84 let hits2 = HITS.load(SeqCst);
85 let trap = f2.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
86 assert_eq!(trap, Trap::StackOverflow);
87 let hits3 = HITS.load(SeqCst);
88 let trap = f3.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
89 assert_eq!(trap, Trap::StackOverflow);
90 let hits4 = HITS.load(SeqCst);
91 let trap = f4.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
92 assert_eq!(trap, Trap::StackOverflow);
93 let hits5 = HITS.load(SeqCst);
94
95 // Additionally, however, and this is the crucial test, make sure that the
96 // host function actually completed. If HITS is 1 then we entered but didn't
97 // exit meaning we segfaulted while executing the host, yet still tried to
98 // recover from it with a jump.
99 assert_eq!(hits1, 0);
100 assert_eq!(hits2, 0);
101 assert_eq!(hits3, 0);
102 assert_eq!(hits4, 0);
103 assert_eq!(hits5, 0);
104
105 return Ok(());
106
107 fn test_host_stack() {
108 HITS.fetch_add(1, SeqCst);
109 assert!(consume_some_stack(0, HOST_STACK) > 0);
110 HITS.fetch_sub(1, SeqCst);
111 }
112
113 #[inline(never)]
114 fn consume_some_stack(ptr: usize, stack: usize) -> usize {
115 if stack == 0 {
116 return ptr;
117 }
118 let mut space = [0u8; 1024];
119 consume_some_stack(space.as_mut_ptr() as usize, stack.saturating_sub(1024))
120 }
121 }
122
123 #[wasmtime_test]
big_stack_works_ok(config: &mut Config) -> Result<()>124 fn big_stack_works_ok(config: &mut Config) -> Result<()> {
125 // This test takes 1m+ in ASAN and isn't too useful, so prune it.
126 if cfg!(asan) {
127 return Ok(());
128 }
129
130 const N: usize = 10000;
131
132 // Build a module with a function that uses a very large amount of stack space,
133 // modeled here by calling an i64-returning-function many times followed by
134 // adding them all into one i64.
135 //
136 // This should exercise the ability to consume multi-page stacks and
137 // only touch a few internals of it at a time.
138 let mut s = String::new();
139 s.push_str("(module\n");
140 s.push_str("(func (export \"\") (result i64)\n");
141 s.push_str("i64.const 0\n");
142 for _ in 0..N {
143 s.push_str("call $get\n");
144 }
145 for _ in 0..N {
146 s.push_str("i64.add\n");
147 }
148 s.push_str(")\n");
149 s.push_str("(func $get (result i64) i64.const 0)\n");
150 s.push_str(")\n");
151
152 // Disable cranelift optimizations to ensure that this test doesn't take too
153 // long in debug mode due to the large size of its code.
154 config.cranelift_opt_level(OptLevel::None);
155 config.cranelift_regalloc_algorithm(RegallocAlgorithm::SinglePass);
156 let engine = Engine::new(config)?;
157
158 let mut store = Store::new(&engine, ());
159 let module = Module::new(store.engine(), &s)?;
160 let instance = Instance::new(&mut store, &module, &[])?;
161 let func = instance.get_typed_func::<(), i64>(&mut store, "")?;
162 assert_eq!(func.call(&mut store, ())?, 0);
163 Ok(())
164 }
165
166 #[test]
infinite_wasm_stack_does_not_panic() -> Result<()>167 fn infinite_wasm_stack_does_not_panic() -> Result<()> {
168 let mut config = Config::new();
169 config.max_wasm_stack(usize::MAX);
170 config.async_stack_size(usize::MAX);
171 let engine = Engine::new(&config)?;
172 // If the store can't get allocated that's probably because we're using
173 // pulley and can't allocate a usize::MAX stack, ignore that failure.
174 // This'll still run on cranelift-native platforms.
175 let Ok(mut store) = Store::try_new(&engine, ()) else {
176 return Ok(());
177 };
178 let module = Module::new(store.engine(), r#"(module (func (export "f")))"#)?;
179 let instance = Instance::new(&mut store, &module, &[])?;
180 let func = instance.get_typed_func::<(), ()>(&mut store, "f")?;
181 func.call(&mut store, ())?;
182 Ok(())
183 }
184