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