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]
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]
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