1 #![deny(unsafe_op_in_unsafe_fn)]
2 
3 use crate::prelude::*;
4 use crate::store::{Executor, StoreId, StoreInner, StoreOpaque};
5 use crate::vm::mpk::{self, ProtectionMask};
6 use crate::vm::{AsyncWasmCallState, VMStore};
7 use crate::{Engine, StoreContextMut};
8 use anyhow::{Result, anyhow};
9 use core::mem;
10 use core::ops::Range;
11 use core::pin::Pin;
12 use core::ptr::{self, NonNull};
13 use core::task::{Context, Poll};
14 use wasmtime_fiber::{Fiber, Suspend};
15 
16 type WasmtimeResume = Result<NonNull<Context<'static>>>;
17 type WasmtimeYield = StoreFiberYield;
18 type WasmtimeComplete = Result<()>;
19 type WasmtimeSuspend = Suspend<WasmtimeResume, WasmtimeYield, WasmtimeComplete>;
20 
21 /// State related to asynchronous computations stored within a `Store<T>`.
22 ///
23 /// This structure resides inside of a `Store<T>` and is used to manage the
24 /// various pieces of state associated with asynchronous computations. Chiefly
25 /// this manages the `WasmtimeSuspend` pointer as well as `&mut Context<'_>`
26 /// when polling futures. This serves as storage to use these pointers across a
27 /// WebAssembly function boundary, for example, where the values cannot
28 /// otherwise be explicitly threaded through.
29 pub(crate) struct AsyncState {
30     /// The `Suspend` for the current fiber (or null if no such fiber is
31     /// running).
32     ///
33     /// This pointer is provided by the `wasmtime_fiber` crate when a fiber
34     /// first starts, but this pointer is unable to be carried through
35     /// WebAssembly frames for example. This serves as an alternative storage
36     /// location for the pointer provided by `wasmtime_fiber` within a fiber's
37     /// execution.
38     ///
39     /// This pointer is null when a fiber is not executing, but it is also null
40     /// when a `BlockingContext` is created. Note that when a fiber is suspended
41     /// it's always through a `BlockingContext` so this field is null whenever a
42     /// fiber is suspended as well. Fiber resumption will save the prior value
43     /// in a store and then set it to null, where suspension will then restore
44     /// what was previously in the store.
45     current_suspend: Option<NonNull<WasmtimeSuspend>>,
46 
47     /// The `Context` pointer last provided in `Future for FiberFuture`.
48     ///
49     /// Like `current_suspend` above this is an example of a piece of context
50     /// which needs to be carried over a WebAssembly function frame which
51     /// otherwise doesn't take this as a parameter. This differs from
52     /// `current_suspend` though in that it is provided as part of a `Future`
53     /// poll operation but is "gone" after that poll operation completes. That
54     /// means that while `current_suspend` is the same for the lifetime of a
55     /// future this field is always changing.
56     ///
57     /// Like `current_suspend` though this is null either when a fiber isn't
58     /// running or when a `BlockingContext` is created (in which case this is
59     /// "take"en). That means that this is null on suspension/resumption of a
60     /// fiber.
61     ///
62     /// The value for this pointer is threaded directly through the
63     /// `WasmtimeResume` type which is how a pointer flows into this field from
64     /// a future-related poll call. This means that the `BlockingContext`
65     /// creation may take one value of a pointer here but restore another. That
66     /// would represent suspending in one call to `Future::poll` and then
67     /// resuming some time later in a different call to `Future::poll`.
68     ///
69     /// # Safety
70     ///
71     /// Note that this is a pretty unsafe field for two reasons. One is that
72     /// it's a raw pointer to a `Context` provided ephemerally to some call to
73     /// `Future::poll` on the stack. Another reason is that the lifetime
74     /// parameter of `Context` is unsafely changed to `'static` here which is
75     /// not correct. The ephemeral nature of this pointer is managed through the
76     /// take-style operations in `BlockingContext` and the `'static` lifetime is
77     /// handled by ensuring the signatures that work with `BlockingContext` all
78     /// use constrained anonymous lifetimes that are guaranteed to be shorter
79     /// than the original `Context` lifetime.
80     current_future_cx: Option<NonNull<Context<'static>>>,
81 
82     /// The last fiber stack that was in use by the store.
83     ///
84     /// We use this to cache and reuse stacks as a performance optimization.
85     // TODO: With stack switching and the Component Model Async ABI, there may
86     // be multiple concurrent fibers in play; consider caching more than one
87     // stack at a time and making the number tunable via `Config`.
88     last_fiber_stack: Option<wasmtime_fiber::FiberStack>,
89 }
90 
91 // SAFETY: it's known that `std::task::Context` is neither `Send` nor `Sync`,
92 // but despite this the storage here is purely temporary in getting these
93 // pointers across function frames. The actual types are not sent across threads
94 // as when a store isn't polling anything the pointer values are all set to
95 // `None`. Thus if a store is being sent across threads that's done because no
96 // fibers are active, and once fibers are active everything will stick within
97 // the same thread.
98 unsafe impl Send for AsyncState {}
99 unsafe impl Sync for AsyncState {}
100 
101 impl Default for AsyncState {
102     fn default() -> Self {
103         Self {
104             current_suspend: None,
105             current_future_cx: None,
106             last_fiber_stack: None,
107         }
108     }
109 }
110 
111 impl AsyncState {
112     pub(crate) fn last_fiber_stack(&mut self) -> &mut Option<wasmtime_fiber::FiberStack> {
113         &mut self.last_fiber_stack
114     }
115 }
116 
117 trait AsStoreOpaque {
118     fn as_store_opaque(&mut self) -> &mut StoreOpaque;
119 }
120 
121 impl AsStoreOpaque for StoreOpaque {
122     fn as_store_opaque(&mut self) -> &mut StoreOpaque {
123         self
124     }
125 }
126 
127 impl<T: 'static> AsStoreOpaque for StoreInner<T> {
128     fn as_store_opaque(&mut self) -> &mut StoreOpaque {
129         self
130     }
131 }
132 
133 /// A helper structure used to block a fiber.
134 ///
135 /// This is acquired via either `StoreContextMut::with_blocking` or
136 /// `StoreOpaque::with_blocking`. This structure represents the "taken" state of
137 /// pointers from a store's `AsyncState`, then modeling them as safe pointers.
138 ///
139 /// Note that the lifetimes here are carefully controlled in instances of this
140 /// structure through the construction of the `with` function.
141 pub(crate) struct BlockingContext<'a, 'b> {
142     /// Pointer to `wasmtime_fiber::Suspend` which was supplied when a fiber
143     /// first started.
144     ///
145     /// When a `BlockingContext` is first created this pointer is "taken" from
146     /// the store (the store is null'd out) and then the raw pointer previously
147     /// in the store is unsafely transformed to this safe pointer. This
148     /// represents how a `BlockingContext` temporarily has access to this
149     /// suspend but when the `BlockingContext` goes away this'll make its way
150     /// back into the store.
151     suspend: &'a mut WasmtimeSuspend,
152 
153     /// Pointer to the future `Context` that this fiber is being polled with.
154     ///
155     /// Similar to `suspend` above this is taken from a store when a
156     /// `BlockingContext` is created and it's restored when the
157     /// `BlockingContext` goes away. Note though that unlike `suspend`, as
158     /// alluded to in the documentation on `AsyncState`, this value changes over
159     /// time as calls to poll are made. This field becomes `None` during a
160     /// suspension because that means that the context is released and no longer
161     /// available. Upon resumption the context here is *optionally* provided.
162     /// Cancellation is a case where it isn't passed back and a re-poll is a
163     /// case where it's passed back.
164     future_cx: Option<&'a mut Context<'b>>,
165 }
166 
167 impl<'a, 'b> BlockingContext<'a, 'b> {
168     /// Method to go from a `store` provided (which internally contains a
169     /// `StoreOpaque`) to a `BlockingContext`.
170     ///
171     /// This function will "take" context from `store`'s `AsyncState` field. It
172     /// will then construct a `BlockingContext` and yield it to the closure `f`
173     /// provided. The closure can then block on futures, suspend, etc.
174     ///
175     /// Upon return of the closure `f` the state from `BlockingContext` is
176     /// restored within the store. The return value of `f` is the return value
177     /// of this function.
178     ///
179     /// Note that the `store` must be provided to this function as an argument
180     /// to originally acquire state from `AsyncState`. This store is then
181     /// supplied back to the closure `f` provided here so the store can be used
182     /// to construct an asynchronous or blocking computation which the
183     /// `BlockingContext` tries to block on.
184     ///
185     /// # Safety
186     ///
187     /// This method is safe to call at any time, but it's worth noting that the
188     /// safety of this function relies on the signature of this function.
189     /// Notably the lifetime parameters of `BlockingContext` in the `f` closure
190     /// here must be anonymous. That ensures that the `BlockingContext` that
191     /// callers get access to cannot be persisted outside of that closure call
192     /// and everything is scoped to just the closure `f` provided with nothing
193     /// escaping.
194     fn with<S, R>(store: &mut S, f: impl FnOnce(&mut S, &mut BlockingContext<'_, '_>) -> R) -> R
195     where
196         S: AsStoreOpaque,
197     {
198         let opaque = store.as_store_opaque();
199 
200         let state = opaque.fiber_async_state_mut();
201 
202         // SAFETY: this is taking pointers from `AsyncState` and then unsafely
203         // turning them into safe references. Lifetime-wise this should be safe
204         // because the inferred lifetimes for all these pointers is constrained
205         // by the signature of `f` provided here. That ensures that everything
206         // is scoped purely to the closure `f` and nothing should be persisted
207         // outside of this function call. This, for example, ensures that the
208         // `Context<'static>` doesn't leak out, it's only with an anonymous
209         // lifetime that's forcibly shorter.
210         //
211         // Provenance-wise this should be safe as if these fields in the store
212         // are non-null then the pointers are provided up-the-stack on this
213         // fiber and for this fiber. The "take" pattern here ensures that if
214         // this `BlockingContext` context acquires the pointers then there are
215         // no other instances of these pointers in use anywhere else.
216         let future_cx = unsafe { Some(state.current_future_cx.take().unwrap().as_mut()) };
217         let suspend = unsafe { state.current_suspend.take().unwrap().as_mut() };
218 
219         let mut reset = ResetBlockingContext {
220             store,
221             cx: BlockingContext { future_cx, suspend },
222         };
223         return f(&mut reset.store, &mut reset.cx);
224 
225         struct ResetBlockingContext<'a, 'b, S: AsStoreOpaque> {
226             store: &'a mut S,
227             cx: BlockingContext<'a, 'b>,
228         }
229 
230         impl<S: AsStoreOpaque> Drop for ResetBlockingContext<'_, '_, S> {
231             fn drop(&mut self) {
232                 let store = self.store.as_store_opaque();
233                 let state = store.fiber_async_state_mut();
234 
235                 debug_assert!(state.current_future_cx.is_none());
236                 debug_assert!(state.current_suspend.is_none());
237                 state.current_suspend = Some(NonNull::from(&mut *self.cx.suspend));
238 
239                 if let Some(cx) = &mut self.cx.future_cx {
240                     // SAFETY: while this is changing the lifetime to `'static`
241                     // it should never be used while it's `'static` given this
242                     // `BlockingContext` abstraction.
243                     state.current_future_cx =
244                         Some(NonNull::from(unsafe { change_context_lifetime(cx) }));
245                 }
246             }
247         }
248     }
249 
250     /// Blocks on the asynchronous computation represented by `future` and
251     /// produces the result here, in-line.
252     ///
253     /// This function is designed to only work when it's currently executing on
254     /// a native fiber. This fiber provides the ability for us to handle the
255     /// future's `Pending` state as "jump back to whomever called the fiber in
256     /// an asynchronous fashion and propagate `Pending`". This tight coupling
257     /// with `on_fiber` below is what powers the asynchronicity of calling wasm.
258     ///
259     /// This function takes a `future` and will (appear to) synchronously wait
260     /// on the result. While this function is executing it will fiber switch
261     /// to-and-from the original frame calling `on_fiber` which should be a
262     /// guarantee due to how async stores are configured.
263     ///
264     /// The return value here is either the output of the future `T`, or a trap
265     /// which represents that the asynchronous computation was cancelled. It is
266     /// not recommended to catch the trap and try to keep executing wasm, so
267     /// we've tried to liberally document this.
268     ///
269     /// Note that this function suspends (if needed) with
270     /// `StoreFiberYield::KeepStore`, indicating that the store must not be used
271     /// (and that no other fibers may be resumed) until this fiber resumes.
272     /// Therefore, it is not appropriate for use in e.g. guest calls to
273     /// async-lowered imports implemented as host functions, since it will
274     /// prevent any other tasks from being run.  Use `Instance::suspend` to
275     /// suspend and release the store to allow other tasks to run before this
276     /// fiber is resumed.
277     ///
278     /// # Return Value
279     ///
280     /// A return value of `Ok(value)` means that the future completed with
281     /// `value`. A return value of `Err(e)` means that the fiber and its future
282     /// have been cancelled and the fiber needs to exit and complete ASAP.
283     ///
284     /// # Safety
285     ///
286     /// This function is safe to call at any time but relies on a trait bound
287     /// that is manually placed here the compiler does not otherwise require.
288     /// Notably the `Send` bound on the future provided here is not required
289     /// insofar as things compile without that. The purpose of this, however, is
290     /// to make the `unsafe impl Send for StoreFiber` more safe. The `future`
291     /// here is state that is stored on the stack during the suspension of this
292     /// fiber and is otherwise not visible to the compiler. By having a `Send`
293     /// bound here it ensures that the future doesn't have things like `Rc` or
294     /// similar pointing into thread locals which would not be sound if this
295     /// fiber crosses threads.
296     pub(crate) fn block_on<F>(&mut self, future: F) -> Result<F::Output>
297     where
298         F: Future + Send,
299     {
300         let mut future = core::pin::pin!(future);
301         loop {
302             match future.as_mut().poll(self.future_cx.as_mut().unwrap()) {
303                 Poll::Ready(v) => break Ok(v),
304                 Poll::Pending => self.suspend(StoreFiberYield::KeepStore)?,
305             }
306         }
307     }
308 
309     /// Suspend this fiber with `yield_` as the reason.
310     ///
311     /// This function will suspend the current fiber and only return after the
312     /// fiber has resumed. This function return `Ok(())` if the fiber was
313     /// resumed to be completed, and `Err(e)` indicates that the fiber has been
314     /// cancelled and needs to exit/complete ASAP.
315     pub(crate) fn suspend(&mut self, yield_: StoreFiberYield) -> Result<()> {
316         // Over a suspension point we're guaranteed that the `Context` provided
317         // here is no longer valid, so discard it. If we're supposed to be able
318         // to poll afterwards this will be given back as part of the resume
319         // value given back.
320         self.future_cx.take();
321 
322         let mut new_future_cx: NonNull<Context<'static>> = self.suspend.suspend(yield_)?;
323 
324         // SAFETY: this function is unsafe as we're doing "funky" things to the
325         // `new_future_cx` we have been given. The safety here relies on the
326         // fact that the lifetimes of `BlockingContext` are all "smaller" than
327         // the original `Context` itself, and that should be guaranteed through
328         // the exclusive constructor of this type `BlockingContext::with`.
329         unsafe {
330             self.future_cx = Some(change_context_lifetime(new_future_cx.as_mut()));
331         }
332         Ok(())
333     }
334 }
335 
336 impl<T> StoreContextMut<'_, T> {
337     /// Blocks on the future computed by `f`.
338     ///
339     /// # Panics
340     ///
341     /// Panics if this is invoked outside the context of a fiber.
342     pub(crate) fn block_on<R>(
343         self,
344         f: impl FnOnce(StoreContextMut<'_, T>) -> Pin<Box<dyn Future<Output = R> + Send + '_>>,
345     ) -> Result<R> {
346         BlockingContext::with(self.0, |store, cx| {
347             cx.block_on(f(StoreContextMut(store)).as_mut())
348         })
349     }
350 
351     /// Creates a `BlockingContext` suitable for blocking on futures or
352     /// suspending the current fiber.
353     ///
354     /// # Panics
355     ///
356     /// Panics if this is invoked outside the context of a fiber.
357     pub(crate) fn with_blocking<R>(
358         self,
359         f: impl FnOnce(StoreContextMut<'_, T>, &mut BlockingContext<'_, '_>) -> R,
360     ) -> R {
361         BlockingContext::with(self.0, |store, cx| f(StoreContextMut(store), cx))
362     }
363 }
364 
365 impl<T> crate::store::StoreInner<T> {
366     /// Blocks on the future computed by `f`.
367     ///
368     /// # Panics
369     ///
370     /// Panics if this is invoked outside the context of a fiber.
371     pub(crate) fn block_on<R>(
372         &mut self,
373         f: impl FnOnce(StoreContextMut<'_, T>) -> Pin<Box<dyn Future<Output = R> + Send + '_>>,
374     ) -> Result<R> {
375         BlockingContext::with(self, |store, cx| {
376             cx.block_on(f(StoreContextMut(store)).as_mut())
377         })
378     }
379 }
380 
381 impl StoreOpaque {
382     /// Blocks on the future computed by `f`.
383     ///
384     /// # Panics
385     ///
386     /// Panics if this is invoked outside the context of a fiber.
387     pub(crate) fn block_on<R>(
388         &mut self,
389         f: impl FnOnce(&mut Self) -> Pin<Box<dyn Future<Output = R> + Send + '_>>,
390     ) -> Result<R> {
391         BlockingContext::with(self, |store, cx| cx.block_on(f(store).as_mut()))
392     }
393 
394     /// Creates a `BlockingContext` suitable for blocking on futures or
395     /// suspending the current fiber.
396     ///
397     /// # Panics
398     ///
399     /// Panics if this is invoked outside the context of a fiber.
400     #[expect(dead_code, reason = "will be used by async things soon")]
401     pub(crate) fn with_blocking<R>(
402         &mut self,
403         f: impl FnOnce(&mut Self, &mut BlockingContext<'_, '_>) -> R,
404     ) -> R {
405         BlockingContext::with(self, |store, cx| f(store, cx))
406     }
407 
408     /// Returns whether `block_on` will succeed or panic.
409     #[cfg(feature = "call-hook")]
410     pub(crate) fn can_block(&mut self) -> bool {
411         self.fiber_async_state_mut().current_future_cx.is_some()
412     }
413 }
414 
415 /// Indicates whether or not a fiber needs to retain exclusive access to its
416 /// store across a suspend/resume interval.
417 pub(crate) enum StoreFiberYield {
418     /// Indicates the fiber needs to retain exclusive access, meaning the store
419     /// should not be used outside of the fiber until after the fiber either
420     /// suspends with `ReleaseStore` or resolves.
421     KeepStore,
422     /// Indicates the fiber does _not_ need exclusive access across the
423     /// suspend/resume interval, meaning the store may be used as needed until
424     /// the fiber is resumed.
425     #[expect(dead_code, reason = "will be used by async thing soon")]
426     ReleaseStore,
427 }
428 
429 pub(crate) struct StoreFiber<'a> {
430     /// The raw `wasmtime_fiber::Fiber`.
431     ///
432     /// Note that using `StoreFiberYield` as the `Yield` type parameter allows
433     /// the fiber to indicate whether it needs exclusive access to the store
434     /// across suspend points (in which case it will pass `KeepStore` when
435     /// suspending , meaning the store must not be used at all until the fiber
436     /// is resumed again) or whether it is giving up exclusive access (in which
437     /// case it will pass `ReleaseStore` when yielding, meaning exclusive access
438     /// may be given to another fiber that runs concurrently.
439     ///
440     /// Note also that every `StoreFiber` is implicitly granted exclusive access
441     /// to the store when it is resumed.
442     fiber: Option<Fiber<'a, WasmtimeResume, WasmtimeYield, WasmtimeComplete>>,
443     /// See `FiberResumeState`
444     state: Option<FiberResumeState>,
445     /// The Wasmtime `Engine` to which this fiber belongs.
446     engine: Engine,
447     /// The id of the store with which this fiber was created.
448     ///
449     /// Any attempt to resume a fiber with a different store than the one with
450     /// which it was created will panic.
451     id: StoreId,
452 }
453 
454 impl StoreFiber<'_> {
455     pub(crate) fn dispose(&mut self, store: &mut StoreOpaque) {
456         if let Some(fiber) = &mut self.fiber {
457             if !fiber.done() {
458                 let result = resume_fiber(store, self, Err(anyhow!("future dropped")));
459                 debug_assert!(result.is_ok());
460             }
461         }
462     }
463 }
464 
465 // Note that this implementation will panic if the fiber is in-progress, which
466 // will abort the process if there is already a panic being unwound.  That
467 // should only happen if we failed to call `StoreFiber::dispose` on the
468 // in-progress fiber prior to dropping it, which indicates a bug in this crate
469 // which must be fixed.
470 impl Drop for StoreFiber<'_> {
471     fn drop(&mut self) {
472         if self.fiber.is_none() {
473             return;
474         }
475 
476         assert!(
477             self.fiber.as_ref().unwrap().done(),
478             "attempted to drop in-progress fiber without first calling `StoreFiber::dispose`"
479         );
480 
481         self.state.take().unwrap().dispose();
482 
483         unsafe {
484             self.engine
485                 .allocator()
486                 .deallocate_fiber_stack(self.fiber.take().unwrap().into_stack());
487         }
488     }
489 }
490 
491 // This is surely the most dangerous `unsafe impl Send` in the entire
492 // crate. There are two members in `StoreFiber` which cause it to not be
493 // `Send`. One is `suspend` and is entirely uninteresting.  This is just used to
494 // manage `Suspend` when resuming, and requires raw pointers to get it to happen
495 // easily.  Nothing too weird about the `Send`-ness, values aren't actually
496 // crossing threads.
497 //
498 // The really interesting piece is `fiber`. Now the "fiber" here is actual
499 // honest-to-god Rust code which we're moving around. What we're doing is the
500 // equivalent of moving our thread's stack to another OS thread. Turns out we,
501 // in general, have no idea what's on the stack and would generally have no way
502 // to verify that this is actually safe to do!
503 //
504 // Thankfully, though, Wasmtime has the power. Without being glib it's actually
505 // worth examining what's on the stack. It's unfortunately not super-local to
506 // this function itself. Our closure to `Fiber::new` runs `func`, which is given
507 // to us from the outside. Thankfully, though, we have tight control over
508 // this. Usage of `on_fiber` or `Instance::resume_fiber` is typically done
509 // *just* before entering WebAssembly itself, so we'll have a few stack frames
510 // of Rust code (all in Wasmtime itself) before we enter wasm.
511 //
512 // Once we've entered wasm, well then we have a whole bunch of wasm frames on
513 // the stack. We've got this nifty thing called Cranelift, though, which allows
514 // us to also have complete control over everything on the stack!
515 //
516 // Finally, when wasm switches back to the fiber's starting pointer (this future
517 // we're returning) then it means wasm has reentered Rust.  Suspension can only
518 // happen via either `block_on` or `Instance::suspend`. This, conveniently, also
519 // happens entirely in Wasmtime controlled code!
520 //
521 // There's an extremely important point that should be called out here.
522 // User-provided futures **are not on the stack** during suspension points. This
523 // is extremely crucial because we in general cannot reason about Send/Sync for
524 // stack-local variables since rustc doesn't analyze them at all. With our
525 // construction, though, we are guaranteed that Wasmtime owns all stack frames
526 // between the stack of a fiber and when the fiber suspends (and it could move
527 // across threads). At this time the only user-provided piece of data on the
528 // stack is the future itself given to us. Lo-and-behold as you might notice the
529 // future is required to be `Send`!
530 //
531 // What this all boils down to is that we, as the authors of Wasmtime, need to
532 // be extremely careful that on the async fiber stack we only store Send
533 // things. For example we can't start using `Rc` willy nilly by accident and
534 // leave a copy in TLS somewhere. (similarly we have to be ready for TLS to
535 // change while we're executing wasm code between suspension points).
536 //
537 // While somewhat onerous it shouldn't be too too hard (the TLS bit is the
538 // hardest bit so far). This does mean, though, that no user should ever have to
539 // worry about the `Send`-ness of Wasmtime. If rustc says it's ok, then it's ok.
540 //
541 // With all that in mind we unsafely assert here that Wasmtime is correct. We
542 // declare the fiber as only containing Send data on its stack, despite not
543 // knowing for sure at compile time that this is correct. That's what `unsafe`
544 // in Rust is all about, though, right?
545 unsafe impl Send for StoreFiber<'_> {}
546 // See the docs about the `Send` impl above, which also apply to this `Sync`
547 // impl.  `Sync` is needed since we store `StoreFiber`s and switch between them
548 // when executing components that export async-lifted functions.
549 unsafe impl Sync for StoreFiber<'_> {}
550 
551 /// State of the world when a fiber last suspended.
552 ///
553 /// This structure represents global state that a fiber clobbers during its
554 /// execution. For example TLS variables are updated, system resources like MPK
555 /// masks are updated, etc. The purpose of this structure is to track all of
556 /// this state and appropriately save/restore it around fiber suspension points.
557 struct FiberResumeState {
558     /// Saved list of `CallThreadState` activations that are stored on a fiber
559     /// stack.
560     ///
561     /// This is a linked list that references stack-stored nodes on the fiber
562     /// stack that is currently suspended. The `AsyncWasmCallState` type
563     /// documents this more thoroughly but the general gist is that when we this
564     /// fiber is resumed this linked list needs to be pushed on to the current
565     /// thread's linked list of activations.
566     tls: crate::runtime::vm::AsyncWasmCallState,
567 
568     /// Saved MPK protection mask, if enabled.
569     ///
570     /// When MPK is enabled then executing WebAssembly will modify the
571     /// processor's current mask of addressable protection keys. This means that
572     /// our current state may get clobbered when a fiber suspends. To ensure
573     /// that this function preserves context it will, when MPK is enabled, save
574     /// the current mask when this function is called and then restore the mask
575     /// when the function returns (aka the fiber suspends).
576     mpk: Option<ProtectionMask>,
577 
578     /// The current wasm stack limit, if in use.
579     ///
580     /// This field stores the old of `VMStoreContext::stack_limit` that this
581     /// fiber should be using during its execution. This is saved/restored when
582     /// a fiber is suspended/resumed to ensure that when there are multiple
583     /// fibers within the store they all maintain an appropriate fiber-relative
584     /// stack limit.
585     stack_limit: usize,
586 
587     /// The executor (e.g. the Pulley interpreter state) belonging to this
588     /// fiber.
589     ///
590     /// This is swapped with `StoreOpaque::executor` whenever this fiber is
591     /// resumed, suspended, or resolved.
592     executor: Executor,
593 }
594 
595 impl FiberResumeState {
596     unsafe fn replace(
597         self,
598         store: &mut StoreOpaque,
599         fiber: &mut StoreFiber<'_>,
600     ) -> PriorFiberResumeState {
601         let tls = unsafe { self.tls.push() };
602         let mpk = swap_mpk_states(self.mpk);
603         let async_guard_range = fiber
604             .fiber
605             .as_ref()
606             .unwrap()
607             .stack()
608             .guard_range()
609             .unwrap_or(ptr::null_mut()..ptr::null_mut());
610         let mut executor = self.executor;
611         store.swap_executor(&mut executor);
612         PriorFiberResumeState {
613             tls,
614             mpk,
615             executor,
616             stack_limit: store.replace_stack_limit(self.stack_limit),
617             async_guard_range: store.replace_async_guard_range(async_guard_range),
618 
619             // The current suspend/future_cx are always null upon resumption, so
620             // insert null. Save the old values through to get preserved across
621             // this resume/suspend.
622             current_suspend: store.replace_current_suspend(None),
623             current_future_cx: store.replace_current_future_cx(None),
624         }
625     }
626 
627     fn dispose(self) {
628         self.tls.assert_null();
629     }
630 }
631 
632 impl StoreOpaque {
633     /// Helper function to swap the `stack_limit` field in the `VMStoreContext`
634     /// within this store.
635     fn replace_stack_limit(&mut self, stack_limit: usize) -> usize {
636         mem::replace(
637             &mut self.vm_store_context_mut().stack_limit.get_mut(),
638             stack_limit,
639         )
640     }
641 
642     /// Helper function to swap the `async_guard_range` field in the `VMStoreContext`
643     /// within this store.
644     fn replace_async_guard_range(&mut self, range: Range<*mut u8>) -> Range<*mut u8> {
645         mem::replace(&mut self.vm_store_context_mut().async_guard_range, range)
646     }
647 
648     fn replace_current_suspend(
649         &mut self,
650         ptr: Option<NonNull<WasmtimeSuspend>>,
651     ) -> Option<NonNull<WasmtimeSuspend>> {
652         mem::replace(&mut self.fiber_async_state_mut().current_suspend, ptr)
653     }
654 
655     fn replace_current_future_cx(
656         &mut self,
657         ptr: Option<NonNull<Context<'static>>>,
658     ) -> Option<NonNull<Context<'static>>> {
659         mem::replace(&mut self.fiber_async_state_mut().current_future_cx, ptr)
660     }
661 }
662 
663 struct PriorFiberResumeState {
664     tls: crate::runtime::vm::PreviousAsyncWasmCallState,
665     mpk: Option<ProtectionMask>,
666     stack_limit: usize,
667     async_guard_range: Range<*mut u8>,
668     current_suspend: Option<NonNull<WasmtimeSuspend>>,
669     current_future_cx: Option<NonNull<Context<'static>>>,
670     executor: Executor,
671 }
672 
673 impl PriorFiberResumeState {
674     unsafe fn replace(self, store: &mut StoreOpaque) -> FiberResumeState {
675         let tls = unsafe { self.tls.restore() };
676         let mpk = swap_mpk_states(self.mpk);
677         // No need to save `_my_guard` since we can re-infer it from the fiber
678         // that this state is attached to.
679         let _my_guard = store.replace_async_guard_range(self.async_guard_range);
680 
681         // Restore the previous values of current_{suspend,future_cx} but we
682         // should be guaranteed that the prior values are null, so double-check
683         // that here.
684         let prev = store.replace_current_suspend(self.current_suspend);
685         assert!(prev.is_none());
686         let prev = store.replace_current_future_cx(self.current_future_cx);
687         assert!(prev.is_none());
688 
689         let mut executor = self.executor;
690         store.swap_executor(&mut executor);
691 
692         FiberResumeState {
693             tls,
694             mpk,
695             executor,
696             stack_limit: store.replace_stack_limit(self.stack_limit),
697         }
698     }
699 }
700 
701 fn swap_mpk_states(mask: Option<ProtectionMask>) -> Option<ProtectionMask> {
702     mask.map(|mask| {
703         let current = mpk::current_mask();
704         mpk::allow(mask);
705         current
706     })
707 }
708 
709 /// Resume the specified fiber, granting it exclusive access to the store with
710 /// which it was created.
711 ///
712 /// This will return `Ok(result)` if the fiber resolved, where `result` is the
713 /// returned value; it will return `Err(yield_)` if the fiber suspended, where
714 /// `yield_` indicates whether it released access to the store or not.  See
715 /// `StoreFiber::fiber` for details.
716 fn resume_fiber<'a>(
717     store: &mut StoreOpaque,
718     fiber: &mut StoreFiber<'a>,
719     result: WasmtimeResume,
720 ) -> Result<WasmtimeComplete, StoreFiberYield> {
721     assert_eq!(store.id(), fiber.id);
722 
723     struct Restore<'a, 'b> {
724         store: &'b mut StoreOpaque,
725         fiber: &'b mut StoreFiber<'a>,
726         state: Option<PriorFiberResumeState>,
727     }
728 
729     impl Drop for Restore<'_, '_> {
730         fn drop(&mut self) {
731             self.fiber.state = Some(unsafe { self.state.take().unwrap().replace(self.store) });
732         }
733     }
734     let result = unsafe {
735         let prev = fiber.state.take().unwrap().replace(store, fiber);
736         let restore = Restore {
737             store,
738             fiber,
739             state: Some(prev),
740         };
741         restore.fiber.fiber.as_ref().unwrap().resume(result)
742     };
743 
744     match &result {
745         // The fiber has finished, so recycle its stack by disposing of the
746         // underlying fiber itself.
747         Ok(_) => {
748             let stack = fiber.fiber.take().map(|f| f.into_stack());
749             if let Some(stack) = stack {
750                 store.deallocate_fiber_stack(stack);
751             }
752         }
753 
754         // The fiber has not yet finished, so it stays as-is.
755         Err(_) => {
756             // If `Err` is returned that means the fiber suspended, so we
757             // propagate that here.
758             //
759             // An additional safety check is performed when leaving this
760             // function to help bolster the guarantees of `unsafe impl Send`
761             // above. Notably this future may get re-polled on a different
762             // thread. Wasmtime's thread-local state points to the stack,
763             // however, meaning that it would be incorrect to leave a pointer in
764             // TLS when this function returns. This function performs a runtime
765             // assert to verify that this is the case, notably that the one TLS
766             // pointer Wasmtime uses is not pointing anywhere within the
767             // stack. If it is then that's a bug indicating that TLS management
768             // in Wasmtime is incorrect.
769             if let Some(range) = fiber.fiber.as_ref().unwrap().stack().range() {
770                 AsyncWasmCallState::assert_current_state_not_in_range(range);
771             }
772         }
773     }
774 
775     result
776 }
777 
778 /// Create a new `StoreFiber` which runs the specified closure.
779 pub(crate) fn make_fiber<'a>(
780     store: &mut dyn VMStore,
781     fun: impl FnOnce(&mut dyn VMStore) -> Result<()> + Send + Sync + 'a,
782 ) -> Result<StoreFiber<'a>> {
783     let engine = store.engine().clone();
784     let executor = Executor::new(&engine);
785     let id = store.store_opaque().id();
786     let stack = store.store_opaque_mut().allocate_fiber_stack()?;
787     let track_pkey_context_switch = store.has_pkey();
788     let store = &raw mut *store;
789     let fiber = Fiber::new(stack, move |result: WasmtimeResume, suspend| {
790         let future_cx = match result {
791             Ok(cx) => cx,
792             // Cancelled before we started? Just return.
793             Err(_) => return Ok(()),
794         };
795 
796         // SAFETY: This fiber will only be resumed using `resume_fiber`, which
797         // takes a `&mut StoreOpaque` parameter and has given us exclusive
798         // access to the store until we exit or yield it back to the resumer.
799         let store_ref = unsafe { &mut *store };
800 
801         // It should be a guarantee that the store has null pointers here upon
802         // starting a fiber, so now's the time to fill in the pointers now that
803         // the fiber is running and `future_cx` and `suspend` are both in scope.
804         // Note that these pointers are removed when this function returns as
805         // that's when they fall out of scope.
806         let async_state = store_ref.store_opaque_mut().fiber_async_state_mut();
807         assert!(async_state.current_suspend.is_none());
808         assert!(async_state.current_future_cx.is_none());
809         async_state.current_suspend = Some(NonNull::from(suspend));
810         async_state.current_future_cx = Some(future_cx);
811 
812         struct ResetCurrentPointersToNull<'a>(&'a mut dyn VMStore);
813 
814         impl Drop for ResetCurrentPointersToNull<'_> {
815             fn drop(&mut self) {
816                 let state = self.0.fiber_async_state_mut();
817 
818                 // Double-check that the current suspension isn't null (it
819                 // should be what's in this closure). Note though that we
820                 // can't check `current_future_cx` because it may either be
821                 // here or not be here depending on whether this was
822                 // cancelled or not.
823                 debug_assert!(state.current_suspend.is_some());
824 
825                 state.current_suspend = None;
826                 state.current_future_cx = None;
827             }
828         }
829         let reset = ResetCurrentPointersToNull(store_ref);
830 
831         fun(reset.0)
832     })?;
833     Ok(StoreFiber {
834         state: Some(FiberResumeState {
835             tls: crate::runtime::vm::AsyncWasmCallState::new(),
836             mpk: if track_pkey_context_switch {
837                 Some(ProtectionMask::all())
838             } else {
839                 None
840             },
841             stack_limit: usize::MAX,
842             executor,
843         }),
844         engine,
845         id,
846         fiber: Some(fiber),
847     })
848 }
849 
850 /// Run the specified function on a newly-created fiber and `.await` its
851 /// completion.
852 pub(crate) async fn on_fiber<R: Send + Sync>(
853     store: &mut StoreOpaque,
854     func: impl FnOnce(&mut StoreOpaque) -> R + Send + Sync,
855 ) -> Result<R> {
856     let config = store.engine().config();
857     debug_assert!(store.async_support());
858     debug_assert!(config.async_stack_size > 0);
859 
860     let mut result = None;
861     let fiber = make_fiber(store.traitobj_mut(), |store| {
862         result = Some(func(store));
863         Ok(())
864     })?;
865 
866     {
867         let fiber = FiberFuture {
868             store,
869             fiber: Some(fiber),
870             on_release: OnRelease::ReturnPending,
871         }
872         .await
873         .unwrap();
874 
875         debug_assert!(fiber.is_none());
876     }
877 
878     Ok(result.unwrap())
879 }
880 
881 /// Run the specified fiber until it either suspends with
882 /// `StoreFiberYield::ReleaseStore` or resolves.
883 ///
884 /// This will return `Some` if the fiber suspends with
885 /// `StoreFiberYield::ReleaseStore` or else `None` if it resolves.
886 #[cfg(feature = "component-model-async")]
887 #[expect(dead_code, reason = "will be used by async thing soon")]
888 pub(crate) async fn resolve_or_release<'a>(
889     store: &mut StoreOpaque,
890     fiber: StoreFiber<'a>,
891 ) -> Result<Option<StoreFiber<'a>>> {
892     FiberFuture {
893         store,
894         fiber: Some(fiber),
895         on_release: OnRelease::ReturnReady,
896     }
897     .await
898 }
899 
900 /// Tells a `FiberFuture` what to do if `poll_fiber` returns
901 /// `Err(StoreFiberYield::ReleaseStore)`.
902 enum OnRelease {
903     /// Return `Poll::Pending` from `FiberFuture::poll`
904     ReturnPending,
905     /// Return `Poll::Ready` from `FiberFuture::poll`, handing ownership of the
906     /// `StoreFiber` to the caller.
907     #[cfg(feature = "component-model-async")]
908     ReturnReady,
909 }
910 
911 /// A `Future` implementation for running a `StoreFiber` to completion, giving
912 /// it exclusive access to its store until it resolves.
913 struct FiberFuture<'a, 'b> {
914     store: &'a mut StoreOpaque,
915     fiber: Option<StoreFiber<'b>>,
916     on_release: OnRelease,
917 }
918 
919 impl<'b> Future for FiberFuture<'_, 'b> {
920     type Output = Result<Option<StoreFiber<'b>>>;
921 
922     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
923         let me = self.get_mut();
924 
925         // SAFETY: We need to carry over this `cx` into our fiber's runtime for
926         // when it tries to poll sub-futures that are created. Doing this must
927         // be done unsafely, however, since `cx` is only alive for this one
928         // singular function call. Here we do a `transmute` to extend the
929         // lifetime of `Context` so it can be stored in our `Store`, and then we
930         // replace the current polling context with this one.
931         //
932         // The safety of this extension relies on never actually using
933         // `Context<'static>` with `'static` actually there, which should be
934         // satisfied by the users of this in the `BlockingContext` structure
935         // where the lifetime parameters there are always more constrained than
936         // they are here.
937         let cx: &mut Context<'static> = unsafe { change_context_lifetime(cx) };
938         let cx = NonNull::from(cx);
939 
940         match resume_fiber(me.store, me.fiber.as_mut().unwrap(), Ok(cx)) {
941             Ok(Ok(())) => Poll::Ready(Ok(None)),
942             Ok(Err(e)) => Poll::Ready(Err(e)),
943             Err(StoreFiberYield::KeepStore) => Poll::Pending,
944             Err(StoreFiberYield::ReleaseStore) => match &me.on_release {
945                 OnRelease::ReturnPending => Poll::Pending,
946                 #[cfg(feature = "component-model-async")]
947                 OnRelease::ReturnReady => Poll::Ready(Ok(me.fiber.take())),
948             },
949         }
950     }
951 }
952 
953 impl Drop for FiberFuture<'_, '_> {
954     fn drop(&mut self) {
955         if let Some(fiber) = &mut self.fiber {
956             fiber.dispose(self.store);
957         }
958     }
959 }
960 
961 /// Changes the lifetime `'l` in `Context<'l>` to something else.
962 ///
963 /// # Safety
964 ///
965 /// Not a safe operation. Requires external knowledge about how the pointer is
966 /// being used to determine whether it's actually safe or not. See docs on
967 /// callers of this function. The purpose of this is to scope the `transmute` to
968 /// as small an operation as possible.
969 unsafe fn change_context_lifetime<'a, 'b>(cx: &'a mut Context<'_>) -> &'a mut Context<'b> {
970     // SAFETY: See the function documentation, this is not safe in general.
971     unsafe { mem::transmute::<&mut Context<'_>, &mut Context<'b>>(cx) }
972 }
973