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