Path: blob/main/crates/test-programs/src/bin/async_round_trip_many_stackful.rs
1693 views
#![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")]12// This tests callback-less (AKA stackful) async exports.3//4// Testing this case using Rust's LLVM-based toolchain is tricky because, as of5// this writing, LLVM does not produce reentrance-safe code. Specifically, it6// allocates a single shadow stack for use whenever a program needs to take the7// address of a stack variable, which makes concurrent execution of multiple8// Wasm stacks in the same instance hazardous.9//10// Given the above, we write code directly against the component model ABI11// rather than use `wit-bindgen`, and we carefully avoid use of the shadow stack12// across yield points such as calls to `waitable-set.wait` in order to keep the13// code reentrant.1415mod bindings {16wit_bindgen::generate!({17path: "../misc/component-async-tests/wit",18world: "round-trip-many",19});20}2122use {23std::alloc::{self, Layout},24test_programs::async_::{25EVENT_SUBTASK, STATUS_RETURNED, subtask_drop, waitable_join, waitable_set_drop,26waitable_set_new, waitable_set_wait,27},28};2930#[cfg(target_arch = "wasm32")]31#[link(wasm_import_module = "[export]local:local/many")]32unsafe extern "C" {33#[link_name = "[task-return][async]foo"]34fn task_return_foo(ptr: *mut u8);35}36#[cfg(not(target_arch = "wasm32"))]37unsafe extern "C" fn task_return_foo(_ptr: *mut u8) {38unreachable!()39}4041#[cfg(target_arch = "wasm32")]42#[link(wasm_import_module = "local:local/many")]43unsafe extern "C" {44#[link_name = "[async-lower][async]foo"]45fn import_foo(params: *mut u8, results: *mut u8) -> u32;46}47#[cfg(not(target_arch = "wasm32"))]48unsafe extern "C" fn import_foo(_params: *mut u8, _results: *mut u8) -> u32 {49unreachable!()50}5152#[unsafe(export_name = "[async-lift-stackful]local:local/many#[async]foo")]53unsafe extern "C" fn export_foo(args: *mut u8) {54// Note that we're careful not to take the address of any stack-allocated55// value here. We need to avoid relying on the LLVM-generated shadow stack56// in order to correctly support reentrancy. It's okay to call functions57// which use the shadow stack, as long as they pop everything off before we58// reach a yield point such as a call to `waitable-set.wait`.5960// type | size | align | offset61// ----------------------------------------------------------62// string | 8 | 4 | 063// u32 | 4 | 4 | 864// list<u8> | 8 | 4 | 1265// tuple<u64, u64> | 16 | 8 | 2466// tuple<list<u8>, bool, u64> | 24 | 8 | 4067// option<tuple<list<u8>, bool, u64>> | 32 | 8 | 6468// result<tuple<list<u8>, bool, u64>> | 32 | 8 | 9669// ----------------------------------------------------------70// total | 128 | 8 |7172let len = *args.add(4).cast::<usize>();73let s = format!(74"{} - entered guest",75String::from_utf8(Vec::from_raw_parts(*args.cast::<*mut u8>(), len, len)).unwrap()76);7778let layout = Layout::from_size_align(128, 8).unwrap();7980let params = alloc::alloc(layout);81*params.cast::<*mut u8>() = s.as_ptr().cast_mut();82*params.add(4).cast::<usize>() = s.len();83params.add(8).copy_from(args.add(8), 120);8485let results = alloc::alloc(layout);8687let result = import_foo(params, results);88let mut status = result & 0xf;89let call = result >> 4;90let set = waitable_set_new();91if call != 0 {92waitable_join(call, set);93}94while status != STATUS_RETURNED {95// Note the use of `Box` here to avoid taking the address of a stack96// allocation.97let payload = Box::into_raw(Box::new([0i32; 2]));98let event = waitable_set_wait(set, payload.cast());99let payload = Box::from_raw(payload);100if event == EVENT_SUBTASK {101assert_eq!(call, payload[0] as u32);102status = payload[1] as u32;103if status == STATUS_RETURNED {104subtask_drop(call);105waitable_set_drop(set);106}107}108}109alloc::dealloc(params, layout);110111let len = *results.add(4).cast::<usize>();112let s = format!(113"{} - exited guest",114String::from_utf8(Vec::from_raw_parts(*results.cast::<*mut u8>(), len, len)).unwrap()115);116*results.cast::<*mut u8>() = s.as_ptr().cast_mut();117*results.add(4).cast::<usize>() = s.len();118119task_return_foo(results);120}121122// Unused function; required since this file is built as a `bin`:123fn main() {}124125126