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