Skip to content

Commit 80f46dd

Browse files
alexcrichtondicej
andauthored
Implement a C ABI for async import/export communication (#1254)
* Implement a C ABI for async import/export communication This commit is an implementation of a solution for WebAssembly/component-model#485 for Rust. This should enable releasing multiple versions of `wit-bindgen` into the wild and have them all work together for now. Integration with `wasi-libc` will come in the future in theory. * Refactor with separate objects Try to work around symbol/export trickery * Update crates/guest-rust/rt/src/async_support/waitable.rs Co-authored-by: Joel Dice <joel.dice@fermyon.com> * Update wasi-sdk in CI * Review comments * Get crate compiling on native --------- Co-authored-by: Joel Dice <joel.dice@fermyon.com>
1 parent 9518eaf commit 80f46dd

20 files changed

+361
-87
lines changed

.github/actions/install-wasi-sdk/action.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ runs:
55
using: composite
66
steps:
77
- run: |
8-
curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-linux.tar.gz -L | tar xzvf -
9-
echo "WASI_SDK_PATH=`pwd`/wasi-sdk-24.0-x86_64-linux" >> $GITHUB_ENV
8+
curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz -L | tar xzvf -
9+
echo "WASI_SDK_PATH=`pwd`/wasi-sdk-25.0-x86_64-linux" >> $GITHUB_ENV
1010
if: runner.os == 'Linux'
1111
shell: bash
1212
- run: |
13-
curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-macos.tar.gz -L | tar xzvf -
14-
echo "WASI_SDK_PATH=`pwd`/wasi-sdk-24.0-x86_64-macos" >> $GITHUB_ENV
13+
curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-macos.tar.gz -L | tar xzvf -
14+
echo "WASI_SDK_PATH=`pwd`/wasi-sdk-25.0-x86_64-macos" >> $GITHUB_ENV
1515
if: runner.os == 'macOS'
1616
shell: bash
1717
- run: |
18-
curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-windows.tar.gz -L | tar xzvf -
19-
echo "WASI_SDK_PATH=`pwd`/wasi-sdk-24.0-x86_64-windows" >> $GITHUB_ENV
18+
curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-windows.tar.gz -L | tar xzvf -
19+
echo "WASI_SDK_PATH=`pwd`/wasi-sdk-25.0-x86_64-windows" >> $GITHUB_ENV
2020
if: runner.os == 'Windows'
2121
shell: bash
2222
- name: Setup `wasm-tools`

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ jobs:
151151
- name: Install wasm32-wasip1 target
152152
run: rustup target add wasm32-wasip1
153153

154-
# Verify the output of the `./ci/rebuild-libcabi-realloc.sh` script is
154+
# Verify the output of the `./ci/rebuild-libwit-bindgen-cabi.sh` script is
155155
# up-to-date.
156156
- uses: ./.github/actions/install-wasi-sdk
157-
- run: ./ci/rebuild-libcabi-realloc.sh
157+
- run: ./ci/rebuild-libwit-bindgen-cabi.sh
158158
- run: git diff --exit-code
159159

160160
# Test various feature combinations, make sure they all build

ci/rebuild-libcabi-realloc.sh renamed to ci/rebuild-libwit-bindgen-cabi.sh

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@ set -ex
4141

4242
version=$(./ci/print-current-version.sh | sed 's/\./_/g')
4343

44-
sym=cabi_realloc_wit_bindgen_$version
44+
realloc=cabi_realloc_wit_bindgen_$version
4545

46-
cat >./crates/guest-rust/rt/src/cabi_realloc.rs <<-EOF
46+
rm -f crates/guest-rust/rt/src/wit_bindgen_*.{rs,o,c}
47+
rm -f crates/guest-rust/rt/src/libwit_bindgen_cabi.a
48+
49+
cat >./crates/guest-rust/rt/src/wit_bindgen_cabi_realloc.rs <<-EOF
4750
// This file is generated by $0
4851
4952
#[unsafe(no_mangle)]
50-
pub unsafe extern "C" fn $sym(
53+
pub unsafe extern "C" fn $realloc(
5154
old_ptr: *mut u8,
5255
old_len: usize,
5356
align: usize,
@@ -57,29 +60,48 @@ pub unsafe extern "C" fn $sym(
5760
}
5861
EOF
5962

60-
cat >./crates/guest-rust/rt/src/cabi_realloc.c <<-EOF
63+
cat >./crates/guest-rust/rt/src/wit_bindgen_cabi_realloc.c <<-EOF
6164
// This file is generated by $0
6265
6366
#include <stdint.h>
6467
65-
extern void *$sym(void *ptr, size_t old_size, size_t align, size_t new_size);
68+
extern void *$realloc(void *ptr, size_t old_size, size_t align, size_t new_size);
6669
6770
__attribute__((__weak__, __export_name__("cabi_realloc")))
6871
void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) {
69-
return $sym(ptr, old_size, align, new_size);
72+
return $realloc(ptr, old_size, align, new_size);
7073
}
7174
EOF
7275

73-
rm -f crates/guest-rust/rt/src/cabi_realloc.o
74-
$WASI_SDK_PATH/bin/clang crates/guest-rust/rt/src/cabi_realloc.c \
75-
-O -c -o crates/guest-rust/rt/src/cabi_realloc.o
76+
cat >./crates/guest-rust/rt/src/wit_bindgen_cabi_wasip3.c <<-EOF
77+
// This file is generated by $0
78+
79+
#include <stdlib.h>
80+
81+
static void *WASIP3_TASK = NULL;
82+
83+
__attribute__((__weak__))
84+
void *wasip3_task_set(void *ptr) {
85+
void *ret = WASIP3_TASK;
86+
WASIP3_TASK = ptr;
87+
return ret;
88+
}
89+
EOF
90+
91+
build() {
92+
file=$1
93+
$WASI_SDK_PATH/bin/clang crates/guest-rust/rt/src/$1.c \
94+
-O -c -o crates/guest-rust/rt/src/$1.o
95+
# Remove the `producers` section. This appears to differ whether the host for
96+
# clang is either macOS or Linux. Not needed here anyway, so discard it to help
97+
# either host produce the same object.
98+
wasm-tools strip -d producers ./crates/guest-rust/rt/src/$1.o \
99+
-o ./crates/guest-rust/rt/src/$1.o
100+
}
76101

77-
# Remove the `producers` section. This appears to differ whether the host for
78-
# clang is either macOS or Linux. Not needed here anyway, so discard it to help
79-
# either host produce the same object.
80-
wasm-tools strip -d producers ./crates/guest-rust/rt/src/cabi_realloc.o \
81-
-o ./crates/guest-rust/rt/src/cabi_realloc.o
102+
build wit_bindgen_cabi_realloc
103+
build wit_bindgen_cabi_wasip3
82104

83-
rm -f crates/guest-rust/rt/src/libwit_bindgen_cabi_realloc.a
84-
$WASI_SDK_PATH/bin/llvm-ar crus crates/guest-rust/rt/src/libwit_bindgen_cabi_realloc.a \
85-
crates/guest-rust/rt/src/cabi_realloc.o
105+
$WASI_SDK_PATH/bin/llvm-ar crus crates/guest-rust/rt/src/libwit_bindgen_cabi.a \
106+
crates/guest-rust/rt/src/wit_bindgen_cabi_realloc.o \
107+
crates/guest-rust/rt/src/wit_bindgen_cabi_wasip3.o

crates/guest-rust/rt/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ crate is to contain "runtime" code related to the macro-expansion of the
88
be removed in some situations.
99

1010
This crate contains a precompiled object file and archive at
11-
`src/cabi_realloc.o` and `src/libwit_bindgen_cabi_realloc.a`. This is compiled
12-
from the source `src/cabi_realloc.c` and is checked in as precompiled to avoid
13-
needing a C compiler at compile-time which isn't always available. This object
14-
file is only used on wasm targets.
11+
`src/wit_bindgen_cabi.o` and `src/libwit_bindgen_cabi.a`. This is compiled
12+
from the source `src/wit_bindgen_cabi.c` and is checked in as precompiled to
13+
avoid needing a C compiler at compile-time which isn't always available. This
14+
object file is only used on wasm targets.
1515

1616
The object file is compiled by
17-
[this script]https://github.com/bytecodealliance/wit-bindgen/blob/main/ci/rebuild-libcabi-realloc.sh)
17+
[this script]https://github.com/bytecodealliance/wit-bindgen/blob/main/ci/rebuild-libwit-bindgen-cabi.sh)
1818
and is verified in repository continuous integration that the checked-in
1919
versions match what CI produces.
2020

crates/guest-rust/rt/build.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ fn main() {
1616

1717
let mut src = env::current_dir().unwrap();
1818
src.push("src");
19-
src.push("libwit_bindgen_cabi_realloc.a");
19+
src.push("libwit_bindgen_cabi.a");
2020

2121
let dst_name = format!(
22-
"wit_bindgen_cabi_realloc{}",
22+
"wit_bindgen_cabi{}",
2323
env!("CARGO_PKG_VERSION").replace(".", "_")
2424
);
2525
let dst = out_dir.join(format!("lib{dst_name}.a"));

crates/guest-rust/rt/src/async_support.rs

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ extern crate std;
88
use core::sync::atomic::{AtomicBool, Ordering};
99
use std::boxed::Box;
1010
use std::collections::HashMap;
11+
use std::ffi::c_void;
1112
use std::fmt::{self, Debug, Display};
1213
use std::future::Future;
1314
use std::mem;
1415
use std::pin::Pin;
1516
use std::ptr;
1617
use std::string::String;
1718
use std::sync::Arc;
18-
use std::task::{Context, Poll, Wake, Waker};
19+
use std::task::{Context, Poll, Wake};
1920
use std::vec::Vec;
2021

2122
use futures::channel::oneshot;
@@ -36,6 +37,7 @@ macro_rules! rtdebug {
3637
}
3738

3839
mod abi_buffer;
40+
mod cabi;
3941
mod future_support;
4042
mod stream_support;
4143
mod waitable;
@@ -60,21 +62,32 @@ struct FutureState {
6062
tasks: Option<FuturesUnordered<BoxFuture>>,
6163
/// The waitable set containing waitables created by this task, if any.
6264
waitable_set: Option<u32>,
63-
/// A map of waitables to the corresponding waker and completion code.
64-
///
65-
/// This is primarily filled in and managed by `WaitableOperation<S>`. The
66-
/// waker here comes straight from `std::task::Context` and the pointer is
67-
/// otherwise stored within the `WaitableOperation<S>` The raw pointer here
68-
/// has a disconnected lifetime with each future but the management of the
69-
/// internal states with respect to drop should always ensure that this is
70-
/// only ever pointing to active waitable operations.
71-
///
72-
/// When a waitable notification is received the corresponding entry in this
73-
/// map is removed, the status code is filled in, and the waker is notified.
74-
wakers: HashMap<u32, (Waker, *mut Option<u32>)>,
65+
66+
/// State of all waitables in `waitable_set`, and the ptr/callback they're
67+
/// associated with.
68+
waitables: HashMap<u32, (*mut c_void, unsafe extern "C" fn(*mut c_void, u32))>,
69+
70+
/// Raw structure used to pass to `cabi::wasip3_task_set`
71+
wasip3_task: cabi::wasip3_task,
7572
}
7673

7774
impl FutureState {
75+
fn new(future: BoxFuture) -> FutureState {
76+
FutureState {
77+
todo: 0,
78+
tasks: Some([future].into_iter().collect()),
79+
waitable_set: None,
80+
waitables: HashMap::new(),
81+
wasip3_task: cabi::wasip3_task {
82+
// This pointer is filled in before calling `wasip3_task_set`.
83+
ptr: ptr::null_mut(),
84+
version: cabi::WASIP3_TASK_V1,
85+
waitable_register,
86+
waitable_unregister,
87+
},
88+
}
89+
}
90+
7891
fn get_or_create_waitable_set(&mut self) -> u32 {
7992
*self.waitable_set.get_or_insert_with(waitable_set_new)
8093
}
@@ -88,7 +101,32 @@ impl FutureState {
88101
}
89102

90103
fn remaining_work(&self) -> bool {
91-
self.todo > 0 || !self.wakers.is_empty()
104+
self.todo > 0 || !self.waitables.is_empty()
105+
}
106+
}
107+
108+
unsafe extern "C" fn waitable_register(
109+
ptr: *mut c_void,
110+
waitable: u32,
111+
callback: unsafe extern "C" fn(*mut c_void, u32),
112+
callback_ptr: *mut c_void,
113+
) -> *mut c_void {
114+
let ptr = ptr.cast::<FutureState>();
115+
assert!(!ptr.is_null());
116+
(*ptr).add_waitable(waitable);
117+
match (*ptr).waitables.insert(waitable, (callback_ptr, callback)) {
118+
Some((prev, _)) => prev,
119+
None => ptr::null_mut(),
120+
}
121+
}
122+
123+
unsafe extern "C" fn waitable_unregister(ptr: *mut c_void, waitable: u32) -> *mut c_void {
124+
let ptr = ptr.cast::<FutureState>();
125+
assert!(!ptr.is_null());
126+
(*ptr).remove_waitable(waitable);
127+
match (*ptr).waitables.remove(&waitable) {
128+
Some((prev, _)) => prev,
129+
None => ptr::null_mut(),
92130
}
93131
}
94132

@@ -145,6 +183,22 @@ unsafe fn poll(state: *mut FutureState) -> Poll<()> {
145183
}
146184
}
147185

186+
// Finish our `wasip3_task` by initializing its self-referential pointer,
187+
// and then register it for the duration of this function with
188+
// `wasip3_task_set`. The previous value of `wasip3_task_set` will get
189+
// restored when this function returns.
190+
struct ResetTask(*mut cabi::wasip3_task);
191+
impl Drop for ResetTask {
192+
fn drop(&mut self) {
193+
unsafe {
194+
cabi::wasip3_task_set(self.0);
195+
}
196+
}
197+
}
198+
(*state).wasip3_task.ptr = state.cast();
199+
let prev = cabi::wasip3_task_set(&mut (*state).wasip3_task);
200+
let _reset = ResetTask(prev);
201+
148202
loop {
149203
if let Some(futures) = (*state).tasks.as_mut() {
150204
let old = CURRENT;
@@ -191,16 +245,9 @@ pub fn first_poll<T: 'static>(
191245
future: impl Future<Output = T> + 'static,
192246
fun: impl FnOnce(&T) + 'static,
193247
) -> i32 {
194-
let state = Box::into_raw(Box::new(FutureState {
195-
todo: 0,
196-
tasks: Some(
197-
[Box::pin(future.map(|v| fun(&v))) as BoxFuture]
198-
.into_iter()
199-
.collect(),
200-
),
201-
waitable_set: None,
202-
wakers: HashMap::new(),
203-
}));
248+
let state = Box::into_raw(Box::new(FutureState::new(Box::pin(
249+
future.map(|v| fun(&v)),
250+
))));
204251
let done = unsafe { poll(state).is_ready() };
205252
unsafe { callback_code(state, done) }
206253
}
@@ -339,9 +386,8 @@ unsafe fn callback_with_state(
339386
"EVENT_{{STREAM,FUTURE}}_{{READ,WRITE}}({event0:#x}, {event1:#x}, {event2:#x})"
340387
);
341388
(*state).remove_waitable(event1 as u32);
342-
let (waker, code) = (*state).wakers.remove(&(event1 as u32)).unwrap();
343-
*code = Some(event2 as u32);
344-
waker.wake();
389+
let (ptr, callback) = (*state).waitables.remove(&(event1 as u32)).unwrap();
390+
callback(ptr, event2 as u32);
345391

346392
let done = poll(state).is_ready();
347393
callback_code(state, done)
@@ -469,16 +515,7 @@ pub fn spawn(future: impl Future<Output = ()> + 'static) {
469515
// TODO: refactor so `'static` bounds aren't necessary
470516
pub fn block_on<T: 'static>(future: impl Future<Output = T> + 'static) -> T {
471517
let (tx, mut rx) = oneshot::channel();
472-
let state = &mut FutureState {
473-
todo: 0,
474-
tasks: Some(
475-
[Box::pin(future.map(move |v| drop(tx.send(v)))) as BoxFuture]
476-
.into_iter()
477-
.collect(),
478-
),
479-
waitable_set: None,
480-
wakers: HashMap::new(),
481-
};
518+
let state = &mut FutureState::new(Box::pin(future.map(move |v| drop(tx.send(v)))) as BoxFuture);
482519
loop {
483520
match unsafe { poll(state) } {
484521
Poll::Ready(()) => break rx.try_recv().unwrap().unwrap(),

0 commit comments

Comments
 (0)