Skip to content

Commit b240d2c

Browse files
authored
Beef up documentation about TLS management in Wasmtime (#10547)
This comes out of discussions on the wasip3-prototyping repository where I was trying to re-learn what this all does. The goal here is to more exhaustively document how this module works.
1 parent 4d77cf2 commit b240d2c

File tree

1 file changed

+131
-16
lines changed

1 file changed

+131
-16
lines changed

crates/wasmtime/src/runtime/vm/traphandlers.rs

Lines changed: 131 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,27 @@ mod call_thread_state {
425425
use super::*;
426426
use crate::runtime::vm::Unwind;
427427

428-
/// Temporary state stored on the stack which is registered in the `tls` module
429-
/// below for calls into wasm.
428+
/// Temporary state stored on the stack which is registered in the `tls`
429+
/// module below for calls into wasm.
430+
///
431+
/// This structure is stored on the stack and allocated during the
432+
/// `catch_traps` function above. The purpose of this structure is to track
433+
/// the state of an "activation" or a sequence of 0-or-more contiguous
434+
/// WebAssembly call frames. A `CallThreadState` always lives on the stack
435+
/// and additionally maintains pointers to previous states to form a linked
436+
/// list of activations.
437+
///
438+
/// One of the primary goals of `CallThreadState` is to store the state of
439+
/// various fields in `VMStoreContext` when it was created. This is done
440+
/// because calling WebAssembly will clobber these fields otherwise.
441+
///
442+
/// Another major purpose of `CallThreadState` is to assist with unwinding
443+
/// and track state necessary when an unwind happens for the original
444+
/// creator of `CallThreadState` to determine why the unwind happened.
445+
///
446+
/// Note that this structure is pointed-to from TLS, hence liberal usage of
447+
/// interior mutability here since that only gives access to
448+
/// `&CallThreadState`.
430449
pub struct CallThreadState {
431450
pub(super) unwind: Cell<Option<(UnwindReason, Option<Backtrace>, Option<CoreDumpStack>)>>,
432451
pub(super) jmp_buf: Cell<*const u8>,
@@ -525,12 +544,31 @@ mod call_thread_state {
525544
self.prev.get()
526545
}
527546

547+
/// Pushes this `CallThreadState` activation on to the linked list
548+
/// stored in TLS.
549+
///
550+
/// This method will take the current head of the linked list, stored in
551+
/// our TLS pointer, and move it into `prev`. The TLS pointer is then
552+
/// updated to `self`.
553+
///
554+
/// # Panics
555+
///
556+
/// Panics if this activation is already in a linked list (e.g.
557+
/// `self.prev` is set).
528558
#[inline]
529559
pub(crate) unsafe fn push(&self) {
530560
assert!(self.prev.get().is_null());
531561
self.prev.set(tls::raw::replace(self));
532562
}
533563

564+
/// Pops this `CallThreadState` from the linked list stored in TLS.
565+
///
566+
/// This method will restore `self.prev` into the head of the linked
567+
/// list stored in TLS and will additionally null-out `self.prev`.
568+
///
569+
/// # Panics
570+
///
571+
/// Panics if this activation isn't the head of the list.
534572
#[inline]
535573
pub(crate) unsafe fn pop(&self) {
536574
let prev = self.prev.replace(ptr::null());
@@ -734,11 +772,84 @@ impl CallThreadState {
734772
}
735773
}
736774

737-
// A private inner module for managing the TLS state that we require across
738-
// calls in wasm. The WebAssembly code is called from C++ and then a trap may
739-
// happen which requires us to read some contextual state to figure out what to
740-
// do with the trap. This `tls` module is used to persist that information from
741-
// the caller to the trap site.
775+
/// A private inner module managing the state of Wasmtime's thread-local storage
776+
/// (TLS) state.
777+
///
778+
/// Wasmtime at this time has a single pointer of TLS. This single pointer of
779+
/// TLS is the totality of all TLS required by Wasmtime. By keeping this as
780+
/// small as possible it generally makes it easier to integrate with external
781+
/// systems and implement features such as fiber context switches. This single
782+
/// TLS pointer is declared in platform-specific modules to handle platform
783+
/// differences, so this module here uses getters/setters which delegate to
784+
/// platform-specific implementations.
785+
///
786+
/// The single TLS pointer used by Wasmtime is morally
787+
/// `Option<&CallThreadState>` meaning that it's a possibly-present pointer to
788+
/// some state. This pointer is a pointer to the most recent (youngest)
789+
/// `CallThreadState` activation, or the most recent call into WebAssembly.
790+
///
791+
/// This TLS pointer is additionally the head of a linked list of activations
792+
/// that are all stored on the stack for the current thread. Each time
793+
/// WebAssembly is recursively invoked by an embedder will push a new entry into
794+
/// this linked list. This singly-linked list is maintained with its head in TLS
795+
/// node pointers are stored in `CallThreadState::prev`.
796+
///
797+
/// An example stack might look like this:
798+
///
799+
/// ```text
800+
/// ┌─────────────────────┐◄───── highest, or oldest, stack address
801+
/// │ native stack frames │
802+
/// │ ... │
803+
/// │ ┌───────────────┐◄─┼──┐
804+
/// │ │CallThreadState│ │ │
805+
/// │ └───────────────┘ │ p
806+
/// ├─────────────────────┤ r
807+
/// │ wasm stack frames │ e
808+
/// │ ... │ v
809+
/// ├─────────────────────┤ │
810+
/// │ native stack frames │ │
811+
/// │ ... │ │
812+
/// │ ┌───────────────┐◄─┼──┼── TLS pointer
813+
/// │ │CallThreadState├──┼──┘
814+
/// │ └───────────────┘ │
815+
/// ├─────────────────────┤
816+
/// │ wasm stack frames │
817+
/// │ ... │
818+
/// ├─────────────────────┤
819+
/// │ native stack frames │
820+
/// │ ... │
821+
/// └─────────────────────┘◄───── smallest, or youngest, stack address
822+
/// ```
823+
///
824+
/// # Fibers and async
825+
///
826+
/// Wasmtime supports stack-switching with fibers to implement async. This means
827+
/// that Wasmtime will temporarily execute code on a separate stack and then
828+
/// suspend from this stack back to the embedder for async operations. Doing
829+
/// this safely requires manual management of the TLS pointer updated by
830+
/// Wasmtime.
831+
///
832+
/// For example when a fiber is suspended that means that the TLS pointer needs
833+
/// to be restored to whatever it was when the fiber was resumed. Additionally
834+
/// this may need to pop multiple `CallThreadState` activations, one for each
835+
/// one located on the fiber stack itself.
836+
///
837+
/// The `AsyncWasmCallState` and `PreviousAsyncWasmCallState` structures in this
838+
/// module are used to manage this state, namely:
839+
///
840+
/// * The `AsyncWasmCallState` structure represents the state of a suspended
841+
/// fiber. This is a linked list, in reverse order, from oldest activation on
842+
/// the fiber to youngest activation on the fiber.
843+
///
844+
/// * The `PreviousAsyncWasmCallState` structure represents a pointer within our
845+
/// thread's TLS linked list of activations when a fiber was resumed. This
846+
/// pointer is used during fiber suspension to know when to stop popping
847+
/// activations from the thread's linked list.
848+
///
849+
/// Note that this means that the directionality of linked list links is
850+
/// opposite when stored in TLS vs when stored for a suspended fiber. The
851+
/// thread's current list pointed to by TLS is youngest-to-oldest links, while a
852+
/// suspended fiber stores oldest-to-youngest links.
742853
pub(crate) mod tls {
743854
use super::CallThreadState;
744855

@@ -830,6 +941,9 @@ pub(crate) mod tls {
830941
//
831942
// When pushed onto a thread this linked list is traversed to get pushed
832943
// onto the current thread at the time.
944+
//
945+
// If this pointer is null then that means that the fiber this state is
946+
// associated with has no activations.
833947
state: raw::Ptr,
834948
}
835949

@@ -883,7 +997,7 @@ pub(crate) mod tls {
883997
///
884998
/// This is used when exiting a future in Wasmtime to assert that the
885999
/// current CallThreadState pointer does not point within the stack
886-
/// we're leaving (e.g. allocated for a fiber).
1000+
/// we're leaving (e.g. allocated for a fiber).
8871001
pub fn assert_current_state_not_in_range(range: core::ops::Range<usize>) {
8881002
let p = raw::get() as usize;
8891003
assert!(p < range.start || range.end < p);
@@ -892,14 +1006,15 @@ pub(crate) mod tls {
8921006

8931007
/// Opaque state used to help control TLS state across stack switches for
8941008
/// async support.
1009+
///
1010+
/// This structure is returned from [`AsyncWasmCallState::push`] and
1011+
/// represents the state of this thread's TLS variable prior to the push
1012+
/// operation.
8951013
#[cfg(feature = "async")]
8961014
pub struct PreviousAsyncWasmCallState {
897-
// The head of a linked list, similar to the TLS state. Note though that
898-
// this list is stored in reverse order to assist with `push` and `pop`
899-
// below.
900-
//
901-
// After a `push` call this stores the previous head for the current
902-
// thread so we know when to stop popping during a `pop`.
1015+
// The raw value of this thread's TLS pointer when this structure was
1016+
// created. This is not dereferenced or inspected but is used to halt
1017+
// linked list traversal in [`PreviousAsyncWasmCallState::restore`].
9031018
state: raw::Ptr,
9041019
}
9051020

@@ -909,8 +1024,8 @@ pub(crate) mod tls {
9091024
/// `AsyncWasmCallState`.
9101025
///
9111026
/// This will pop the top activation of this current thread continuously
912-
/// until it reaches whatever the current activation was when `push` was
913-
/// originally called.
1027+
/// until it reaches whatever the current activation was when
1028+
/// [`AsyncWasmCallState::push`] was originally called.
9141029
///
9151030
/// # Unsafety
9161031
///

0 commit comments

Comments
 (0)