Skip to main content

wit_bindgen/rt/
async_support.rs

1#![deny(missing_docs)]
2
3use alloc::boxed::Box;
4use alloc::collections::BTreeMap;
5use alloc::sync::Arc;
6use alloc::task::Wake;
7use core::ffi::c_void;
8use core::future::Future;
9use core::mem;
10use core::pin::Pin;
11use core::ptr;
12use core::sync::atomic::{AtomicU32, Ordering};
13use core::task::{Context, Poll, Waker};
14
15macro_rules! rtdebug {
16    ($($f:tt)*) => {
17        // Change this flag to enable debugging, right now we're not using a
18        // crate like `log` or such to reduce runtime deps. Intended to be used
19        // during development for now.
20        if false {
21            #[cfg(feature = "std")]
22            std::eprintln!($($f)*);
23        }
24    }
25}
26
27/// Helper macro to deduplicate foreign definitions of wasm functions.
28///
29/// This automatically imports when on wasm targets and then defines a dummy
30/// panicking shim for native targets to support native compilation but fail at
31/// runtime.
32macro_rules! extern_wasm {
33    (
34        $(#[$extern_attr:meta])*
35        unsafe extern "C" {
36            $(
37                $(#[$func_attr:meta])*
38                $vis:vis fn $func_name:ident ( $($args:tt)* ) $(-> $ret:ty)?;
39            )*
40        }
41    ) => {
42        $(
43            #[cfg(not(target_family = "wasm"))]
44            #[allow(unused, reason = "dummy shim for non-wasm compilation, never invoked")]
45            $vis unsafe fn $func_name($($args)*) $(-> $ret)? {
46                unreachable!();
47            }
48        )*
49
50        #[cfg(target_family = "wasm")]
51        $(#[$extern_attr])*
52        unsafe extern "C" {
53            $(
54                $(#[$func_attr])*
55                $vis fn $func_name($($args)*) $(-> $ret)?;
56            )*
57        }
58    };
59}
60
61mod abi_buffer;
62mod cabi;
63mod error_context;
64mod future_support;
65#[cfg(feature = "futures-stream")]
66mod futures_stream;
67#[cfg(feature = "inter-task-wakeup")]
68mod inter_task_wakeup;
69mod stream_support;
70mod subtask;
71mod try_lock;
72#[cfg(feature = "inter-task-wakeup")]
73mod unit_stream;
74mod waitable;
75mod waitable_set;
76
77#[cfg(not(feature = "inter-task-wakeup"))]
78use inter_task_wakeup_disabled as inter_task_wakeup;
79#[cfg(not(feature = "inter-task-wakeup"))]
80mod inter_task_wakeup_disabled;
81
82use self::waitable_set::WaitableSet;
83pub use abi_buffer::*;
84pub use error_context::*;
85pub use future_support::*;
86#[cfg(feature = "futures-stream")]
87pub use futures_stream::*;
88pub use stream_support::*;
89#[doc(hidden)]
90pub use subtask::Subtask;
91#[cfg(feature = "inter-task-wakeup")]
92pub use unit_stream::*;
93
94type BoxFuture<'a> = Pin<Box<dyn Future<Output = ()> + 'a>>;
95
96#[cfg(feature = "async-spawn")]
97mod spawn;
98#[cfg(feature = "async-spawn")]
99pub use spawn::spawn;
100#[cfg(not(feature = "async-spawn"))]
101mod spawn_disabled;
102#[cfg(not(feature = "async-spawn"))]
103use spawn_disabled as spawn;
104
105/// Represents a task created by either a call to an async-lifted export or a
106/// future run using `block_on` or `start_task`.
107struct FutureState<'a> {
108    /// Remaining work to do (if any) before this task can be considered "done".
109    ///
110    /// Note that we won't tell the host the task is done until this is drained
111    /// and `waitables` is empty.
112    tasks: spawn::Tasks<'a>,
113
114    /// The waitable set containing waitables created by this task, if any.
115    waitable_set: Option<WaitableSet>,
116
117    /// State of all waitables in `waitable_set`, and the ptr/callback they're
118    /// associated with.
119    //
120    // Note that this is a `BTreeMap` rather than a `HashMap` only because, as
121    // of this writing, initializing the default hasher for `HashMap` requires
122    // calling `wasi_snapshot_preview1:random_get`, which requires initializing
123    // the `wasi_snapshot_preview1` adapter when targeting `wasm32-wasip2` and
124    // later, and that's expensive enough that we'd prefer to avoid it for apps
125    // which otherwise make no use of the adapter.
126    waitables: BTreeMap<u32, (*mut c_void, unsafe extern "C" fn(*mut c_void, u32))>,
127
128    /// Raw structure used to pass to `cabi::wasip3_task_set`
129    wasip3_task: cabi::wasip3_task,
130
131    /// Rust-level state for the waker, notably a bool as to whether this has
132    /// been woken.
133    waker: Arc<FutureWaker>,
134
135    /// Clone of `waker` field, but represented as `std::task::Waker`.
136    waker_clone: Waker,
137
138    /// State related to supporting inter-task wakeup scenarios.
139    inter_task_wakeup: inter_task_wakeup::State,
140}
141
142impl FutureState<'_> {
143    fn new(future: BoxFuture<'_>) -> FutureState<'_> {
144        let waker = Arc::new(FutureWaker::default());
145        FutureState {
146            waker_clone: waker.clone().into(),
147            waker,
148            tasks: spawn::Tasks::new(future),
149            waitable_set: None,
150            waitables: BTreeMap::new(),
151            wasip3_task: cabi::wasip3_task {
152                // This pointer is filled in before calling `wasip3_task_set`.
153                ptr: ptr::null_mut(),
154                version: cabi::WASIP3_TASK_V1,
155                waitable_register,
156                waitable_unregister,
157            },
158            inter_task_wakeup: Default::default(),
159        }
160    }
161
162    fn get_or_create_waitable_set(&mut self) -> &WaitableSet {
163        self.waitable_set.get_or_insert_with(WaitableSet::new)
164    }
165
166    fn add_waitable(&mut self, waitable: u32) {
167        self.get_or_create_waitable_set().join(waitable)
168    }
169
170    fn remove_waitable(&mut self, waitable: u32) {
171        WaitableSet::remove_waitable_from_all_sets(waitable)
172    }
173
174    fn remaining_work(&self) -> bool {
175        !self.waitables.is_empty()
176    }
177
178    /// Handles the `event{0,1,2}` event codes and returns a corresponding
179    /// return code along with a flag whether this future is "done" or not.
180    fn callback(&mut self, event0: u32, event1: u32, event2: u32) -> CallbackCode {
181        match event0 {
182            EVENT_NONE => rtdebug!("EVENT_NONE"),
183            EVENT_SUBTASK => rtdebug!("EVENT_SUBTASK({event1:#x}, {event2:#x})"),
184            EVENT_STREAM_READ => rtdebug!("EVENT_STREAM_READ({event1:#x}, {event2:#x})"),
185            EVENT_STREAM_WRITE => rtdebug!("EVENT_STREAM_WRITE({event1:#x}, {event2:#x})"),
186            EVENT_FUTURE_READ => rtdebug!("EVENT_FUTURE_READ({event1:#x}, {event2:#x})"),
187            EVENT_FUTURE_WRITE => rtdebug!("EVENT_FUTURE_WRITE({event1:#x}, {event2:#x})"),
188            EVENT_CANCEL => {
189                rtdebug!("EVENT_CANCEL");
190
191                // Cancellation is mapped to destruction in Rust, so return a
192                // code/bool indicating we're done. The caller will then
193                // appropriately deallocate this `FutureState` which will
194                // transitively run all destructors.
195                return CallbackCode::Exit;
196            }
197            _ => unreachable!(),
198        }
199
200        self.with_p3_task_set(|me| {
201            // Transition our sleep state to ensure that the inter-task stream
202            // isn't used since there's no need to use that here.
203            me.waker
204                .sleep_state
205                .store(SLEEP_STATE_WOKEN, Ordering::Relaxed);
206
207            // With all of our context now configured, deliver the event
208            // notification this callback corresponds to.
209            //
210            // Note that this should happen under the reset of
211            // `waker.sleep_state` above to ensure that if a waker is woken it
212            // won't actually signal our inter-task stream since we're already
213            // in the process of handling the future.
214            if event0 != EVENT_NONE {
215                me.deliver_waitable_event(event1, event2)
216            }
217
218            // If there's still an in-progress read (e.g. `event{1,2}`) wasn't
219            // ourselves getting woken up, then cancel the read since we're
220            // processing the future here anyway.
221            me.cancel_inter_task_stream_read();
222
223            loop {
224                let mut context = Context::from_waker(&me.waker_clone);
225
226                // On each turn of this loop reset the state to "polling"
227                // which clears out any pending wakeup if one was sent. This
228                // in theory helps minimize wakeups from previous iterations
229                // happening in this iteration.
230                me.waker
231                    .sleep_state
232                    .store(SLEEP_STATE_POLLING, Ordering::Relaxed);
233
234                // Poll our future, seeing if it was able to make progress.
235                let poll = me.tasks.poll_next(&mut context);
236
237                match poll {
238                    // A future completed, yay! Keep going to see if more have
239                    // completed.
240                    Poll::Ready(Some(())) => (),
241
242                    // The task list is empty, but there might be remaining work
243                    // in terms of waitables through the cabi interface. In this
244                    // situation wait for all waitables to be resolved before
245                    // signaling that our own task is done.
246                    Poll::Ready(None) => {
247                        assert!(me.tasks.is_empty());
248                        if me.remaining_work() {
249                            let waitable = me.waitable_set.as_ref().unwrap().as_raw();
250                            break CallbackCode::Wait(waitable);
251                        } else {
252                            break CallbackCode::Exit;
253                        }
254                    }
255
256                    // Some future within `self.tasks` is not ready yet. If our
257                    // `waker` was signaled then that means this is a yield
258                    // operation, otherwise it means we're blocking on
259                    // something.
260                    Poll::Pending => {
261                        assert!(!me.tasks.is_empty());
262                        if me.waker.sleep_state.load(Ordering::Relaxed) == SLEEP_STATE_WOKEN {
263                            if me.remaining_work() {
264                                let (event0, event1, event2) =
265                                    me.waitable_set.as_ref().unwrap().poll();
266                                if event0 != EVENT_NONE {
267                                    me.deliver_waitable_event(event1, event2);
268                                    continue;
269                                }
270                            }
271                            break CallbackCode::Yield;
272                        }
273
274                        // Transition our state to "sleeping" so wakeup
275                        // notifications know that they need to signal the
276                        // inter-task stream.
277                        me.waker
278                            .sleep_state
279                            .store(SLEEP_STATE_SLEEPING, Ordering::Relaxed);
280                        me.read_inter_task_stream();
281                        let waitable = me.waitable_set.as_ref().unwrap().as_raw();
282                        break CallbackCode::Wait(waitable);
283                    }
284                }
285            }
286        })
287    }
288
289    /// Deliver the `code` event to the `waitable` store within our map. This
290    /// waitable should be present because it's part of the waitable set which
291    /// is kept in-sync with our map.
292    fn deliver_waitable_event(&mut self, waitable: u32, code: u32) {
293        self.remove_waitable(waitable);
294
295        if self
296            .inter_task_wakeup
297            .consume_waitable_event(waitable, code)
298        {
299            return;
300        }
301
302        let (ptr, callback) = self.waitables.remove(&waitable).unwrap();
303        unsafe {
304            callback(ptr, code);
305        }
306    }
307
308    fn with_p3_task_set<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
309        // Finish our `wasip3_task` by initializing its self-referential pointer,
310        // and then register it for the duration of this function with
311        // `wasip3_task_set`. The previous value of `wasip3_task_set` will get
312        // restored when this function returns.
313        struct ResetTask(*mut cabi::wasip3_task);
314        impl Drop for ResetTask {
315            fn drop(&mut self) {
316                unsafe {
317                    cabi::wasip3_task_set(self.0);
318                }
319            }
320        }
321        let self_raw = self as *mut FutureState<'_>;
322        self.wasip3_task.ptr = self_raw.cast();
323        let prev = unsafe { cabi::wasip3_task_set(&mut self.wasip3_task) };
324        let _reset = ResetTask(prev);
325
326        f(self)
327    }
328}
329
330impl Drop for FutureState<'_> {
331    fn drop(&mut self) {
332        // If there's an active read of the inter-task stream, go ahead and
333        // cancel it, since we're about to drop the stream anyway.
334        self.cancel_inter_task_stream_read();
335
336        // If this state has active tasks then they need to be dropped which may
337        // execute arbitrary code. This arbitrary code might require the p3 APIs
338        // for managing waitables, notably around removing them. In this
339        // situation we ensure that the p3 task is set while futures are being
340        // destroyed.
341        if !self.tasks.is_empty() {
342            self.with_p3_task_set(|me| {
343                me.tasks = Default::default();
344            })
345        }
346    }
347}
348
349unsafe extern "C" fn waitable_register(
350    ptr: *mut c_void,
351    waitable: u32,
352    callback: unsafe extern "C" fn(*mut c_void, u32),
353    callback_ptr: *mut c_void,
354) -> *mut c_void {
355    let ptr = ptr.cast::<FutureState<'static>>();
356    assert!(!ptr.is_null());
357    unsafe {
358        (*ptr).add_waitable(waitable);
359        match (*ptr).waitables.insert(waitable, (callback_ptr, callback)) {
360            Some((prev, _)) => prev,
361            None => ptr::null_mut(),
362        }
363    }
364}
365
366unsafe extern "C" fn waitable_unregister(ptr: *mut c_void, waitable: u32) -> *mut c_void {
367    let ptr = ptr.cast::<FutureState<'static>>();
368    assert!(!ptr.is_null());
369    unsafe {
370        (*ptr).remove_waitable(waitable);
371        match (*ptr).waitables.remove(&waitable) {
372            Some((prev, _)) => prev,
373            None => ptr::null_mut(),
374        }
375    }
376}
377
378/// Status for "this task is actively being polled"
379const SLEEP_STATE_POLLING: u32 = 0;
380/// Status for "this task has a wakeup scheduled, no more action need be taken".
381const SLEEP_STATE_WOKEN: u32 = 1;
382/// Status for "this task is not being polled and has not been woken"
383///
384/// Wakeups on this status signal the inter-task stream.
385const SLEEP_STATE_SLEEPING: u32 = 2;
386
387#[derive(Default)]
388struct FutureWaker {
389    /// One of `SLEEP_STATE_*` indicating the current status.
390    sleep_state: AtomicU32,
391    inter_task_stream: inter_task_wakeup::WakerState,
392}
393
394impl Wake for FutureWaker {
395    fn wake(self: Arc<Self>) {
396        Self::wake_by_ref(&self)
397    }
398
399    fn wake_by_ref(self: &Arc<Self>) {
400        match self.sleep_state.swap(SLEEP_STATE_WOKEN, Ordering::Relaxed) {
401            // If this future was currently being polled, or if someone else
402            // already woke it up, then there's nothing to do.
403            SLEEP_STATE_POLLING | SLEEP_STATE_WOKEN => {}
404
405            // If this future is sleeping, however, then this is a cross-task
406            // wakeup meaning that we need to write to its wakeup stream.
407            other => {
408                assert_eq!(other, SLEEP_STATE_SLEEPING);
409                self.inter_task_stream.wake();
410            }
411        }
412    }
413}
414
415const EVENT_NONE: u32 = 0;
416const EVENT_SUBTASK: u32 = 1;
417const EVENT_STREAM_READ: u32 = 2;
418const EVENT_STREAM_WRITE: u32 = 3;
419const EVENT_FUTURE_READ: u32 = 4;
420const EVENT_FUTURE_WRITE: u32 = 5;
421const EVENT_CANCEL: u32 = 6;
422
423#[derive(PartialEq, Debug)]
424enum CallbackCode {
425    Exit,
426    Yield,
427    Wait(u32),
428}
429
430impl CallbackCode {
431    fn encode(self) -> u32 {
432        match self {
433            CallbackCode::Exit => 0,
434            CallbackCode::Yield => 1,
435            CallbackCode::Wait(waitable) => 2 | (waitable << 4),
436        }
437    }
438}
439
440const STATUS_STARTING: u32 = 0;
441const STATUS_STARTED: u32 = 1;
442const STATUS_RETURNED: u32 = 2;
443const STATUS_STARTED_CANCELLED: u32 = 3;
444const STATUS_RETURNED_CANCELLED: u32 = 4;
445
446const BLOCKED: u32 = 0xffff_ffff;
447const COMPLETED: u32 = 0x0;
448const DROPPED: u32 = 0x1;
449const CANCELLED: u32 = 0x2;
450
451/// Return code of stream/future operations.
452#[derive(PartialEq, Debug, Copy, Clone)]
453enum ReturnCode {
454    /// The operation is blocked and has not completed.
455    Blocked,
456    /// The operation completed with the specified number of items.
457    Completed(u32),
458    /// The other end is dropped, but before that the specified number of items
459    /// were transferred.
460    Dropped(u32),
461    /// The operation was cancelled, but before that the specified number of
462    /// items were transferred.
463    Cancelled(u32),
464}
465
466impl ReturnCode {
467    fn decode(val: u32) -> ReturnCode {
468        if val == BLOCKED {
469            return ReturnCode::Blocked;
470        }
471        let amt = val >> 4;
472        match val & 0xf {
473            COMPLETED => ReturnCode::Completed(amt),
474            DROPPED => ReturnCode::Dropped(amt),
475            CANCELLED => ReturnCode::Cancelled(amt),
476            _ => panic!("unknown return code {val:#x}"),
477        }
478    }
479}
480
481/// Starts execution of the `task` provided, an asynchronous computation.
482///
483/// This is used for async-lifted exports at their definition site. The
484/// representation of the export is `task` and this function is called from the
485/// entrypoint. The code returned here is the same as the callback associated
486/// with this export, and the callback will be used if this task doesn't exit
487/// immediately with its result.
488#[doc(hidden)]
489pub fn start_task(task: impl Future<Output = ()> + 'static) -> i32 {
490    // Allocate a new `FutureState` which will track all state necessary for
491    // our exported task.
492    let state = Box::into_raw(Box::new(FutureState::new(Box::pin(task))));
493
494    // Store our `FutureState` into our context-local-storage slot and then
495    // pretend we got EVENT_NONE to kick off everything.
496    //
497    // SAFETY: we should own `context.set` as we're the root level exported
498    // task, and then `callback` is only invoked when context-local storage is
499    // valid.
500    unsafe {
501        assert!(context_get().is_null());
502        context_set(state.cast());
503        callback(EVENT_NONE, 0, 0) as i32
504    }
505}
506
507/// Handle a progress notification from the host regarding either a call to an
508/// async-lowered import or a stream/future read/write operation.
509///
510/// # Unsafety
511///
512/// This function assumes that `context_get()` returns a `FutureState`.
513#[doc(hidden)]
514pub unsafe fn callback(event0: u32, event1: u32, event2: u32) -> u32 {
515    // Acquire our context-local state, assert it's not-null, and then reset
516    // the state to null while we're running to help prevent any unintended
517    // usage.
518    let state = context_get().cast::<FutureState<'static>>();
519    assert!(!state.is_null());
520    unsafe {
521        context_set(ptr::null_mut());
522    }
523
524    // Use `state` to run the `callback` function in the context of our event
525    // codes we received. If the callback decides to exit then we're done with
526    // our future so deallocate it. Otherwise put our future back in
527    // context-local storage and forward the code.
528    unsafe {
529        let rc = (*state).callback(event0, event1, event2);
530        if rc == CallbackCode::Exit {
531            drop(Box::from_raw(state));
532        } else {
533            context_set(state.cast());
534        }
535        rtdebug!(" => (cb) {rc:?}");
536        rc.encode()
537    }
538}
539
540/// Run the specified future to completion, returning the result.
541///
542/// This uses `waitable-set.wait` to poll for progress on any in-progress calls
543/// to async-lowered imports as necessary.
544// TODO: refactor so `'static` bounds aren't necessary
545pub fn block_on<T: 'static>(future: impl Future<Output = T>) -> T {
546    let mut result = None;
547    let mut state = FutureState::new(Box::pin(async {
548        result = Some(future.await);
549    }));
550    let mut event = (EVENT_NONE, 0, 0);
551    loop {
552        match state.callback(event.0, event.1, event.2) {
553            CallbackCode::Exit => {
554                drop(state);
555                break result.unwrap();
556            }
557            CallbackCode::Yield => event = state.waitable_set.as_ref().unwrap().poll(),
558            CallbackCode::Wait(_) => event = state.waitable_set.as_ref().unwrap().wait(),
559        }
560    }
561}
562
563/// Call the `yield` canonical built-in function.
564///
565/// This yields control to the host temporarily, allowing other tasks to make
566/// progress. It's a good idea to call this inside a busy loop which does not
567/// otherwise ever yield control the host.
568///
569/// Note that this function is a blocking function, not an `async` function.
570/// That means that this is not an async yield which allows other tasks in this
571/// component to progress, but instead this will block the current function
572/// until the host gets back around to returning from this yield. Asynchronous
573/// functions should probably use [`yield_async`] instead.
574///
575/// # Return Value
576///
577/// This function returns a `bool` which indicates whether execution should
578/// continue after this yield point. A return value of `true` means that the
579/// task was not cancelled and execution should continue. A return value of
580/// `false`, however, means that the task was cancelled while it was suspended
581/// at this yield point. The caller should return back and exit from the task
582/// ASAP in this situation.
583pub fn yield_blocking() -> bool {
584    extern_wasm! {
585        #[link(wasm_import_module = "$root")]
586        unsafe extern "C" {
587            #[link_name = "[thread-yield]"]
588            fn yield_() -> bool;
589        }
590    }
591
592    // Note that the return value from the raw intrinsic is inverted, the
593    // canonical ABI returns "did this task get cancelled" while this function
594    // works as "should work continue going".
595    unsafe { !yield_() }
596}
597
598/// The asynchronous counterpart to [`yield_blocking`].
599///
600/// This function does not block the current task but instead gives the
601/// Rust-level executor a chance to yield control back to the host temporarily.
602/// This means that other Rust-level tasks may also be able to progress during
603/// this yield operation.
604///
605/// # Return Value
606///
607/// Unlike [`yield_blocking`] this function does not return anything. If this
608/// component task is cancelled while paused at this yield point then the future
609/// will be dropped and a Rust-level destructor will take over and clean up the
610/// task. It's not necessary to do anything with the return value of this
611/// function other than ensuring that you `.await` the function call.
612pub async fn yield_async() {
613    #[derive(Default)]
614    struct Yield {
615        yielded: bool,
616    }
617
618    impl Future for Yield {
619        type Output = ();
620
621        fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
622            if self.yielded {
623                Poll::Ready(())
624            } else {
625                self.yielded = true;
626                context.waker().wake_by_ref();
627                Poll::Pending
628            }
629        }
630    }
631
632    Yield::default().await;
633}
634
635/// Call the `backpressure.inc` canonical built-in function.
636pub fn backpressure_inc() {
637    extern_wasm! {
638        #[link(wasm_import_module = "$root")]
639        unsafe extern "C" {
640            #[link_name = "[backpressure-inc]"]
641            fn backpressure_inc();
642        }
643    }
644
645    unsafe { backpressure_inc() }
646}
647
648/// Call the `backpressure.dec` canonical built-in function.
649pub fn backpressure_dec() {
650    extern_wasm! {
651        #[link(wasm_import_module = "$root")]
652        unsafe extern "C" {
653            #[link_name = "[backpressure-dec]"]
654            fn backpressure_dec();
655        }
656    }
657
658    unsafe { backpressure_dec() }
659}
660
661fn context_get() -> *mut u8 {
662    extern_wasm! {
663        #[link(wasm_import_module = "$root")]
664        unsafe extern "C" {
665            #[link_name = "[context-get-0]"]
666            fn get() -> *mut u8;
667        }
668    }
669
670    unsafe { get() }
671}
672
673unsafe fn context_set(value: *mut u8) {
674    extern_wasm! {
675        #[link(wasm_import_module = "$root")]
676        unsafe extern "C" {
677            #[link_name = "[context-set-0]"]
678            fn set(value: *mut u8);
679        }
680    }
681
682    unsafe { set(value) }
683}
684
685#[doc(hidden)]
686pub struct TaskCancelOnDrop {
687    _priv: (),
688}
689
690impl TaskCancelOnDrop {
691    #[doc(hidden)]
692    pub fn new() -> TaskCancelOnDrop {
693        TaskCancelOnDrop { _priv: () }
694    }
695
696    #[doc(hidden)]
697    pub fn forget(self) {
698        mem::forget(self);
699    }
700}
701
702impl Drop for TaskCancelOnDrop {
703    fn drop(&mut self) {
704        extern_wasm! {
705            #[link(wasm_import_module = "[export]$root")]
706            unsafe extern "C" {
707                #[link_name = "[task-cancel]"]
708                fn cancel();
709            }
710        }
711
712        unsafe { cancel() }
713    }
714}