Skip to content

Commit

Permalink
Move ContinuationObject and StackChain to runtime crate (bytecodealli…
Browse files Browse the repository at this point in the history
…ance#125)

This PR moves the definitions of `ContinuationObject`,
`ContinuationReference`, `StackChain`, and `StackChainCell` from the
`continuations` crate to the `runtime` crate. The latter crate is the
more natural home for these types and this move is a preparation step
for merging the `wasmtime-fibre` crate into `runtime`.

The reason for not storing these types in `continuations` was that we
need access to the layout of these types from various other crates that
cannot depend on `runtime`. This layout information is provided by the
the `offsets` module in the `continuations` crate, containing the
offsets of various fields inside these types. Defining these offsets is
much easier if we have access to the involved types directly.

Now that these types are not defined in `continuations` any more, the
offsets are hard-coded instead. New tests in the `runtime` crate check
that the offset values don't go out of sync with the actual layout of
`ContinuationObject`. In the process, I've changed the types of the
offsets to `usize`. This is the more natural choice, instead of the
previous `i32`.

Moving the definitions into `runtime` also required made doc comments
mandatory on certain fields, which I've added.
  • Loading branch information
frank-emrich authored Mar 12, 2024
1 parent 7dee03b commit 395d2c4
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 221 deletions.
218 changes: 36 additions & 182 deletions crates/continuations/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{cell::UnsafeCell, ptr};
use std::ptr;
use wasmtime_fibre::Fiber;

pub use wasmtime_fibre::{SwitchDirection, SwitchDirectionEnum, TagId};
Expand Down Expand Up @@ -73,145 +73,13 @@ impl StackLimits {
}
}

const STACK_CHAIN_ABSENT_DISCRIMINANT: usize = 0;
const STACK_CHAIN_MAIN_STACK_DISCRIMINANT: usize = 1;
const STACK_CHAIN_CONTINUATION_DISCRIMINANT: usize = 2;

/// This type represents a linked lists of stacks, additionally associating a
/// `StackLimits` object with each element of the list. Here, a "stack" is
/// either a continuation or the main stack. Note that the linked list character
/// arises from the fact that `StackChain::Continuation` variants have a pointer
/// to have `ContinuationObject`, which in turn has a `parent_chain` value of
/// type `StackChain`.
///
/// There are generally two uses of such chains:
///
/// 1. The `typed_continuations_chain` field in the VMContext contains such a
/// chain of stacks, where the head of the list denotes the stack that is
/// currently executing (either a continuation or the main stack), as well as
/// the parent stacks, in case of a continuation currently running. Note that in
/// this case, the linked list must contains 0 or more `Continuation` elements,
/// followed by a final `MainStack` element. In particular, this list always
/// ends with `MainStack` and never contains an `Absent` variant.
///
/// 2. When a continuation is suspended, its chain of parents eventually ends
/// with an `Absent` variant in its `parent_chain` field. Note that a suspended
/// continuation never appears in the stack chain in the VMContext!
///
///
/// As mentioned before, each stack in a `StackChain` has a corresponding
/// `StackLimits` object. For continuations, this is stored in the `limits`
/// fields of the corresponding `ContinuationObject`. For the main stack, the
/// `MainStack` variant contains a pointer to the
/// `typed_continuations_main_stack_limits` field of the VMContext.
///
/// The following invariants hold for these `StackLimits` objects, and the data
/// in `VMRuntimeLimits`.
///
/// Currently executing stack:
/// For the currently executing stack (i.e., the stack that is at the head of
/// the VMContext's `typed_continuations_chain` list), the associated
/// `StackLimits` object contains stale/undefined data. Instead, the live data
/// describing the limits for the currently executing stack is always maintained
/// in `VMRuntimeLimits`. Note that as a general rule independently from any
/// execution of continuations, the `last_wasm_exit*` fields in the
/// `VMRuntimeLimits` contain undefined values while executing wasm.
///
/// Parents of currently executing stack:
/// For stacks that appear in the tail of the VMContext's
/// `typed_continuations_chain` list (i.e., stacks that are not currently
/// executing themselves, but are a parent of the currently executing stack), we
/// have the following: All the fields in the stack's StackLimits are valid,
/// describing the stack's stack limit, and pointers where executing for that
/// stack entered and exited WASM.
///
/// Suspended continuations:
/// For suspended continuations (including their parents), we have the
/// following. Note that the main stack can never be in this state. The
/// `stack_limit` and `last_enter_wasm_sp` fields of the corresponding
/// `StackLimits` object contain valid data, while the `last_exit_wasm_*` fields
/// contain arbitrary values.
/// There is only one exception to this: Note that a continuation that has been
/// created with cont.new, but never been resumed so far, is considered
/// "suspended". However, its `last_enter_wasm_sp` field contains undefined
/// data. This is justified, because when resume-ing a continuation for the
/// first time, a native-to-wasm trampoline is called, which sets up the
/// `last_wasm_entry_sp` in the `VMRuntimeLimits` with the correct value, thus
/// restoring the necessary invariant.
#[derive(Debug, Clone, PartialEq)]
#[repr(usize, C)]
pub enum StackChain {
/// If stored in the VMContext, used to indicate that the MainStack entry
/// has not been set, yet. If stored in a ContinuationObject's parent_chain
/// field, means that there is currently no parent.
Absent = STACK_CHAIN_ABSENT_DISCRIMINANT,
MainStack(*mut StackLimits) = STACK_CHAIN_MAIN_STACK_DISCRIMINANT,
Continuation(*mut ContinuationObject) = STACK_CHAIN_CONTINUATION_DISCRIMINANT,
}

impl StackChain {
pub const ABSENT_DISCRIMINANT: usize = STACK_CHAIN_ABSENT_DISCRIMINANT;
pub const MAIN_STACK_DISCRIMINANT: usize = STACK_CHAIN_MAIN_STACK_DISCRIMINANT;
pub const CONTINUATION_DISCRIMINANT: usize = STACK_CHAIN_CONTINUATION_DISCRIMINANT;

pub fn is_main_stack(&self) -> bool {
matches!(self, StackChain::MainStack(_))
}

// We don't implement IntoIterator because our iterator is unsafe, so at
// least this gives us some way of indicating this, even though the actual
// unsafety lies in the `next` function.
//
/// # Safety
///
/// This function is not unsafe per see, but it returns an object
/// whose usage is unsafe.
pub unsafe fn into_iter(self) -> ContinuationChainIterator {
ContinuationChainIterator(self)
}
}

pub struct ContinuationChainIterator(StackChain);

impl Iterator for ContinuationChainIterator {
type Item = (Option<*mut ContinuationObject>, *mut StackLimits);

fn next(&mut self) -> Option<Self::Item> {
match self.0 {
StackChain::Absent => None,
StackChain::MainStack(ms) => {
let next = (None, ms);
self.0 = StackChain::Absent;
Some(next)
}
StackChain::Continuation(ptr) => {
let continuation = unsafe { ptr.as_mut().unwrap() };
let next = (Some(ptr), (&mut continuation.limits) as *mut StackLimits);
self.0 = continuation.parent_chain.clone();
Some(next)
}
}
}
}

#[repr(transparent)]
pub struct StackChainCell(pub UnsafeCell<StackChain>);

impl StackChainCell {
pub fn absent() -> Self {
StackChainCell(UnsafeCell::new(StackChain::Absent))
}
}

// Since `StackChainCell` and `StackLimits` objects appear in the `StoreOpaque`,
// Since `StackLimits` objects appear in the `StoreOpaque`,
// they need to be `Send` and `Sync`.
// This is safe for the same reason it is for `VMRuntimeLimits` (see comment
// there): Both types are pod-type with no destructor, and we don't access any
// of their fields from other threads.
unsafe impl Send for StackLimits {}
unsafe impl Sync for StackLimits {}
unsafe impl Send for StackChainCell {}
unsafe impl Sync for StackChainCell {}

pub struct Payloads {
/// Number of currently occupied slots.
Expand Down Expand Up @@ -241,6 +109,16 @@ impl Payloads {
}
}

/// Discriminant of variant `Absent` in
/// `wasmtime_runtime::continuation::StackChain`.
pub const STACK_CHAIN_ABSENT_DISCRIMINANT: usize = 0;
/// Discriminant of variant `MainStack` in
/// `wasmtime_runtime::continuation::StackChain`.
pub const STACK_CHAIN_MAIN_STACK_DISCRIMINANT: usize = 1;
/// Discriminant of variant `Continiation` in
/// `wasmtime_runtime::continuation::StackChain`.
pub const STACK_CHAIN_CONTINUATION_DISCRIMINANT: usize = 2;

/// Encodes the life cycle of a `ContinuationObject`.
#[derive(PartialEq)]
#[repr(i32)]
Expand Down Expand Up @@ -272,75 +150,51 @@ impl From<State> for i32 {
}
}

/// TODO
#[repr(C)]
pub struct ContinuationObject {
pub limits: StackLimits,

pub parent_chain: StackChain,

pub fiber: *mut ContinuationFiber,

/// Used to store
/// 1. The arguments to the function passed to cont.new
/// 2. The return values of that function
/// Note that this is *not* used for tag payloads.
pub args: Payloads,

// Once a continuation is suspended, this buffer is used to hold payloads
// provided by cont.bind and resume and received at the suspend site.
// In particular, this may only be Some when `state` is `Invoked`.
pub tag_return_values: Payloads,

pub state: State,
}

/// M:1 Many-to-one mapping. A single ContinuationObject may be
/// referenced by multiple ContinuationReference, though, only one
/// ContinuationReference may hold a non-null reference to the object
/// at a given time.
#[repr(C)]
pub struct ContinuationReference(pub Option<*mut ContinuationObject>);

/// Defines offsets of the fields in the types defined earlier
/// Defines offsets of the fields in the continuation-related types
pub mod offsets {
/// Offsets of fields in `Payloads`
pub mod payloads {
use crate::Payloads;
use memoffset::offset_of;

/// Offset of `capacity` field
pub const CAPACITY: i32 = offset_of!(Payloads, capacity) as i32;
pub const CAPACITY: usize = offset_of!(Payloads, capacity);
/// Offset of `data` field
pub const DATA: i32 = offset_of!(Payloads, data) as i32;
pub const DATA: usize = offset_of!(Payloads, data);
/// Offset of `length` field
pub const LENGTH: i32 = offset_of!(Payloads, length) as i32;
pub const LENGTH: usize = offset_of!(Payloads, length);
}

/// Offsets of fields in `ContinuationObject`
/// Offsets of fields in `wasmtime_runtime::continuation::ContinuationObject`.
/// We uses tests there to ensure these values are correct.
pub mod continuation_object {
use crate::ContinuationObject;
use memoffset::offset_of;
use crate::Payloads;

/// Offset of `limits` field
pub const LIMITS: i32 = offset_of!(ContinuationObject, limits) as i32;
/// Offset of `args` field
pub const ARGS: i32 = offset_of!(ContinuationObject, args) as i32;
pub const LIMITS: usize = 0;
/// Offset of `parent_chain` field
pub const PARENT_CHAIN: i32 = offset_of!(ContinuationObject, parent_chain) as i32;
/// Offset of `state` field
pub const STATE: i32 = offset_of!(ContinuationObject, state) as i32;
pub const PARENT_CHAIN: usize = LIMITS + 4 * std::mem::size_of::<usize>();
/// Offset of `fiber` field
pub const FIBER: usize = PARENT_CHAIN + 2 * std::mem::size_of::<usize>();
/// Offset of `args` field
pub const ARGS: usize = FIBER + std::mem::size_of::<usize>();
/// Offset of `tag_return_values` field
pub const TAG_RETURN_VALUES: i32 = offset_of!(ContinuationObject, tag_return_values) as i32;
pub const TAG_RETURN_VALUES: usize = ARGS + std::mem::size_of::<Payloads>();
/// Offset of `state` field
pub const STATE: usize = TAG_RETURN_VALUES + std::mem::size_of::<Payloads>();
}

pub mod stack_limits {
use crate::StackLimits;
use memoffset::offset_of;

pub const STACK_LIMIT: i32 = offset_of!(StackLimits, stack_limit) as i32;
pub const LAST_WASM_EXIT_FP: i32 = offset_of!(StackLimits, last_wasm_exit_fp) as i32;
pub const LAST_WASM_EXIT_PC: i32 = offset_of!(StackLimits, last_wasm_exit_pc) as i32;
pub const LAST_WASM_ENTRY_SP: i32 = offset_of!(StackLimits, last_wasm_entry_sp) as i32;
pub const STACK_LIMIT: usize = offset_of!(StackLimits, stack_limit);
pub const LAST_WASM_EXIT_FP: usize = offset_of!(StackLimits, last_wasm_exit_fp);
pub const LAST_WASM_EXIT_PC: usize = offset_of!(StackLimits, last_wasm_exit_pc);
pub const LAST_WASM_ENTRY_SP: usize = offset_of!(StackLimits, last_wasm_entry_sp);
}

/// Size of type `wasmtime_runtime::continuation::StackChain`.
/// We test there that this value is correct.
pub const STACK_CHAIN_SIZE: usize = 2 * std::mem::size_of::<usize>();
}
Loading

0 comments on commit 395d2c4

Please sign in to comment.