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