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",
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/baz")]
33 unsafe extern "C" {
34     #[link_name = "[task-return]foo"]
35     fn task_return_foo(ptr: *mut u8, len: usize);
36 }
37 #[cfg(not(target_arch = "wasm32"))]
38 unsafe extern "C" fn task_return_foo(_ptr: *mut u8, _len: usize) {
39     unreachable!()
40 }
41 
42 #[cfg(target_arch = "wasm32")]
43 #[link(wasm_import_module = "local:local/baz")]
44 unsafe extern "C" {
45     #[link_name = "[async-lower]foo"]
46     fn import_foo(ptr: *mut u8, len: usize, results: *mut u8) -> u32;
47 }
48 #[cfg(not(target_arch = "wasm32"))]
49 unsafe extern "C" fn import_foo(_ptr: *mut u8, _len: usize, _results: *mut u8) -> u32 {
50     unreachable!()
51 }
52 
53 #[unsafe(export_name = "[async-lift-stackful]local:local/baz#foo")]
54 unsafe extern "C" fn export_foo(ptr: *mut u8, len: usize) {
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     let s = format!(
62         "{} - entered guest",
63         String::from_utf8(Vec::from_raw_parts(ptr, len, len)).unwrap()
64     );
65 
66     let layout = Layout::from_size_align(8, 4).unwrap();
67 
68     let results = alloc::alloc(layout);
69 
70     let result = import_foo(s.as_ptr().cast_mut(), s.len(), results);
71     let mut status = result & 0xf;
72     let call = result >> 4;
73     let set = waitable_set_new();
74     if call != 0 {
75         waitable_join(call, set);
76     }
77     while status != STATUS_RETURNED {
78         // Note the use of `Box` here to avoid taking the address of a stack
79         // allocation.
80         let payload = Box::into_raw(Box::new([0i32; 2]));
81         let event = waitable_set_wait(set, payload.cast());
82         let payload = Box::from_raw(payload);
83         if event == EVENT_SUBTASK {
84             assert_eq!(call, payload[0] as u32);
85             status = payload[1] as u32;
86             if status == STATUS_RETURNED {
87                 subtask_drop(call);
88                 waitable_set_drop(set);
89             }
90         }
91     }
92 
93     let len = *results.add(4).cast::<usize>();
94     let s = format!(
95         "{} - exited guest",
96         String::from_utf8(Vec::from_raw_parts(*results.cast::<*mut u8>(), len, len)).unwrap()
97     );
98     alloc::dealloc(results, layout);
99 
100     task_return_foo(s.as_ptr().cast_mut(), s.len());
101 }
102 
103 // Unused function; required since this file is built as a `bin`:
104 fn main() {}
105