1 #![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")] 2 3 // This tests callback-less (AKA stackful) async exports. 4 // 5 // Testing this case using Rust's LLVM-based toolchain is tricky because, as of 6 // this writing, LLVM does not produce reentrance-safe code. Specifically, it 7 // allocates a single shadow stack for use whenever a program needs to take the 8 // address of a stack variable, which makes concurrent execution of multiple 9 // Wasm stacks in the same instance hazardous. 10 // 11 // Given the above, we write code directly against the component model ABI 12 // rather than use `wit-bindgen`, and we carefully avoid use of the shadow stack 13 // across yield points such as calls to `waitable-set.wait` in order to keep the 14 // code reentrant. 15 16 mod bindings { 17 wit_bindgen::generate!({ 18 path: "../misc/component-async-tests/wit", 19 world: "round-trip-many", 20 }); 21 } 22 23 use { 24 std::alloc::{self, Layout}, 25 test_programs::async_::{ 26 EVENT_SUBTASK, STATUS_RETURNED, subtask_drop, waitable_join, waitable_set_drop, 27 waitable_set_new, waitable_set_wait, 28 }, 29 }; 30 31 #[cfg(target_arch = "wasm32")] 32 #[link(wasm_import_module = "[export]local:local/many")] 33 unsafe extern "C" { 34 #[link_name = "[task-return]foo"] 35 fn task_return_foo(ptr: *mut u8); 36 } 37 #[cfg(not(target_arch = "wasm32"))] 38 unsafe extern "C" fn task_return_foo(_ptr: *mut u8) { 39 unreachable!() 40 } 41 42 #[cfg(target_arch = "wasm32")] 43 #[link(wasm_import_module = "local:local/many")] 44 unsafe extern "C" { 45 #[link_name = "[async-lower]foo"] 46 fn import_foo(params: *mut u8, results: *mut u8) -> u32; 47 } 48 #[cfg(not(target_arch = "wasm32"))] 49 unsafe extern "C" fn import_foo(_params: *mut u8, _results: *mut u8) -> u32 { 50 unreachable!() 51 } 52 53 #[unsafe(export_name = "[async-lift-stackful]local:local/many#foo")] 54 unsafe extern "C" fn export_foo(args: *mut u8) { 55 // Note that we're careful not to take the address of any stack-allocated 56 // value here. We need to avoid relying on the LLVM-generated shadow stack 57 // in order to correctly support reentrancy. It's okay to call functions 58 // which use the shadow stack, as long as they pop everything off before we 59 // reach a yield point such as a call to `waitable-set.wait`. 60 61 // type | size | align | offset 62 // ---------------------------------------------------------- 63 // string | 8 | 4 | 0 64 // u32 | 4 | 4 | 8 65 // list<u8> | 8 | 4 | 12 66 // tuple<u64, u64> | 16 | 8 | 24 67 // tuple<list<u8>, bool, u64> | 24 | 8 | 40 68 // option<tuple<list<u8>, bool, u64>> | 32 | 8 | 64 69 // result<tuple<list<u8>, bool, u64>> | 32 | 8 | 96 70 // ---------------------------------------------------------- 71 // total | 128 | 8 | 72 73 let len = *args.add(4).cast::<usize>(); 74 let s = format!( 75 "{} - entered guest", 76 String::from_utf8(Vec::from_raw_parts(*args.cast::<*mut u8>(), len, len)).unwrap() 77 ); 78 79 let layout = Layout::from_size_align(128, 8).unwrap(); 80 81 let params = alloc::alloc(layout); 82 *params.cast::<*mut u8>() = s.as_ptr().cast_mut(); 83 *params.add(4).cast::<usize>() = s.len(); 84 params.add(8).copy_from(args.add(8), 120); 85 86 let results = alloc::alloc(layout); 87 88 let result = import_foo(params, results); 89 let mut status = result & 0xf; 90 let call = result >> 4; 91 let set = waitable_set_new(); 92 if call != 0 { 93 waitable_join(call, set); 94 } 95 while status != STATUS_RETURNED { 96 // Note the use of `Box` here to avoid taking the address of a stack 97 // allocation. 98 let payload = Box::into_raw(Box::new([0i32; 2])); 99 let event = waitable_set_wait(set, payload.cast()); 100 let payload = Box::from_raw(payload); 101 if event == EVENT_SUBTASK { 102 assert_eq!(call, payload[0] as u32); 103 status = payload[1] as u32; 104 if status == STATUS_RETURNED { 105 subtask_drop(call); 106 waitable_set_drop(set); 107 } 108 } 109 } 110 alloc::dealloc(params, layout); 111 112 let len = *results.add(4).cast::<usize>(); 113 let s = format!( 114 "{} - exited guest", 115 String::from_utf8(Vec::from_raw_parts(*results.cast::<*mut u8>(), len, len)).unwrap() 116 ); 117 *results.cast::<*mut u8>() = s.as_ptr().cast_mut(); 118 *results.add(4).cast::<usize>() = s.len(); 119 120 task_return_foo(results); 121 } 122 123 // Unused function; required since this file is built as a `bin`: 124 fn main() {} 125