wit_bindgen/rt/
async_support.rs

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