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"]
task_return_foo(ptr: *mut u8)35 fn task_return_foo(ptr: *mut u8);
36 }
37 #[cfg(not(target_arch = "wasm32"))]
task_return_foo(_ptr: *mut u8)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"]
import_foo(params: *mut u8, results: *mut u8) -> u3246 fn import_foo(params: *mut u8, results: *mut u8) -> u32;
47 }
48 #[cfg(not(target_arch = "wasm32"))]
import_foo(_params: *mut u8, _results: *mut u8) -> u3249 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")]
export_foo(args: *mut u8)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`:
main()124 fn main() {}
125