Skip to content

Conversation

@zebreus
Copy link
Contributor

@zebreus zebreus commented Nov 18, 2025

WASIX context switching API

This PR implements an API for context switching (aka, greenthreads, stackswitching, fibers, ...) in WASIX.

Goal

Provide minimal stack-switching primitives in WASIX that enable implementing greenthreads.

Motivation

We need some sort of context switching to implement greenlet. We need greenlet because it is a dependency of many other Python packages. To this end, we propose a WASIX context switching API that our greenlet implementation can use to switch between stacks.

C API

This is the proposed C API (WIP)

// Header <wasix/context.h>

// --------------------------------------------------------------------------
// WASIX Context Switching API
// --------------------------------------------------------------------------
//
// The WASIX context switching API provides lightweight execution contexts
// with independent call stacks.
//
// A context represents an execution state with its own call stack. It may
// be in one of the following states:
//
// - **active**
//   Currently executing. Only one context is active within a given
//   context-switching environment.
// - **suspended**
//   Execution is suspended and may be resumed with `wasix_context_switch()`.
// - **terminated**
//   The entrypoint has returned, thrown an uncaught exception, or triggered a
//   trap.
// - **deleted**
//   All resources associated with the context have been released; equivalent to
//   a context that was never created.
//
// Contexts exist within a context-switching environment. A context may only
// be resumed in the environment in which it was created. Future versions
// of this API may relax this restriction.
//
// Each context-switching environment has a single main context which is always
// the first active context in an context-switching environment. The main
// context cannot be deleted. If the main context terminates, the entire
// context-switching environment terminates.
//
// If no context-switching environment is available, functions in this API fail
// with `EAGAIN`. The conditions under which a context-switching environment is
// available are undefined.
//
// Behavior not specified here is implementation-defined and may be specified
// in future revisions of this API.
//
// #### Wasmer implementation note
// In wasmer each thread corresponds to a context-switching environment
// beginning at the program’s `main` function. During early initialization, no
// context-switching environment may be available. This behavior may change in
// future versions. In practice you can assume that a context-switching
// environment is available starting from the call to `main`. This means
// that there is no context-switching environment available when static
// initializers are called. If you need that feature, open an issue.
//
// #### Interaction with forking
// Forking will NOT fork the context switching environment. If a process calls
// `fork()`, the replica of the process will not inherit the context-switching
// environment. Instead it will enter a new context_switching environment with
// the currently active context which will be its main context. Calling `fork()`
// is not supported on any context other than the main context.
//
// A process created by `vfork()` will not be in a context-switching
// environment until it calls a function from the `exec` family of functions.

// Opaque identifier referring to a WASIX context.
//
// A context identifier is valid only within the context-switching environment
// from which it was obtained with `wasix_context_create()` or
// `wasix_context_main`.
typedef __wasi_context_id_t wasix_context_id_t;

// Identifier of the main context of the current context-switching
// environment.
//
// If no context-switching environment is active, accessing
// `wasix_context_main` yields an undefined identifier.
#define wasix_context_main (__wasix_context_main())
extern wasix_context_id_t __wasix_context_main(void);

// Create a new context.
//
// Creates a new context in the suspended state. On its first resumption,
// `entrypoint` is invoked within that context.
//
// The entrypoint function must not return, raise an uncaught exception, or
// terminate with a trap. If it does, the context enters the terminated state.
// This typically causes termination of the WASIX program unless host code
// handles the trap or error. Exact behavior may change in future versions.
//
// #### Wasmer implementation note
// In the current Wasmer implementation, termination of the entrypoint always
// resumes the main context and produces a trap:
//
// - Entrypoint terminated with a trap:
//   The same trap is produced in the main context.
// - Entrypoint returned normally:
//   A custom user trap is produced in the main context.
// - Entrypoint threw an uncaught exception:
//   The exception is re-thrown in the main context.
//
// This behavior may change in future versions.
//
// #### Parameters
// - `context_id` pointer to where the new context identifier will be stored
// - `entrypoint` function invoked when the context is first resumed
//
// #### Return Value
// - `0` on success
// - `-1` on failure, with `errno` set
//
// #### Errors
// - `EINVAL` entrypoint is not a valid function with the required signature
// - `ENOTSUP` not in a context-switching environment
// - `<memory-error>` Implementations may set `errno` to one of the
// WASIX-defined
//   memory error codes if `context_id` or memory reachable from the arguments
//   is invalid or inaccessible.
int wasix_context_create(wasix_context_id_t *context_id,
                         void (*entrypoint)(void));

// Destroy a suspended or terminated context.
//
// Destroys a context that is suspended or terminated. After a successful call,
// the context enters the deleted state; its identifier becomes invalid and its
// resources are released. Destroying an already deleted context is a no-op.
//
// The main context cannot be destroyed.
//
// Terminated contexts are effectively equivalent to deleted contexts, but
// `wasix_context_destroy()` must still be called to release resources.
//
// #### Wasmer implementation note
// Destroying a suspended context may produce an opaque user trap in the target
// context, requiring the entrypoint to terminate. External functions that call
// back into WASIX must propagate the trap unchanged; otherwise, behavior is
// undefined.
//
// #### Parameters
// - `context_id` identifier of the context to destroy
//
// #### Return Value
// - `0` on success
// - `-1` on failure, with `errno` set
//
// #### Errors
// - `EINVAL` can not destroy the active context
// - `EINVAL` can not destroy the main context
// - `ENOTSUP` not in a context-switching environment
int wasix_context_destroy(wasix_context_id_t context_id);

// Suspend the active context and resume another.
//
// Suspends the active context and resumes execution in `target_context_id`.
// The resumed context continues from where it was last suspended, or from its
// entrypoint if it has never been resumed.
//
// If `target_context_id` refers to the active context, the call is a no-op.
//
// #### Wasmer implementation note
// In the current Wasmer implementation, calling `wasix_context_switch()` in
// the main context may produce a trap if another context terminates while the
// main context is suspended. See the implementation note in
// `wasix_context_create()`.
//
// #### Parameters
// - `target_context_id` identifier of the suspended context to resume
//
// #### Return Value
// - `0` on success
// - `-1` on error, with `errno` set
//
// #### Errors
// - `EINVAL` target context is not suspended
// - `ENOTSUP` not in a context-switching environment
int wasix_context_switch(wasix_context_id_t target_context_id);

@Arshia001
Copy link
Member

LGTM overall. Looks like it should be easy to update this PR with the new async API once that hits, too.

@zebreus zebreus changed the base branch from greenthread-example-tmp to async-api-concurrent-exec-take-2 November 28, 2025 16:23
@Arshia001 Arshia001 force-pushed the async-api-concurrent-exec-take-2 branch from af07f08 to b909de9 Compare December 1, 2025 07:25
@zebreus zebreus force-pushed the wasix-context-api branch 6 times, most recently from 99c6d17 to 8317052 Compare December 1, 2025 16:24
@Arshia001 Arshia001 force-pushed the async-api-concurrent-exec-take-2 branch from 5f1b640 to 663111c Compare December 2, 2025 14:13
@zebreus zebreus force-pushed the wasix-context-api branch 2 times, most recently from b57d9ad to 6128879 Compare December 2, 2025 16:29
@zebreus
Copy link
Contributor Author

zebreus commented Dec 2, 2025

This needs a test that verifies that returning from a context entrypoint exits with 129

@Arshia001 Arshia001 force-pushed the async-api-concurrent-exec-take-2 branch from d573653 to f9adac8 Compare December 2, 2025 16:59
@zebreus zebreus force-pushed the wasix-context-api branch 3 times, most recently from ecccb01 to 067f233 Compare December 5, 2025 09:23
}
loop {
// Technically, it's an error for a vfork to return from main, but anyway...
match resume_vfork(&ctx, &mut store, &start, &call_ret) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: resume_vfork calls main synchronously, which it shouldn't anymore. Specifically, it's not an error to vfork in the presence of coroutines either, it's just fork that shouldn't happen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resume_vfork will now exit if a context-switching environment was present, as we can't reenter the previous environment.

zebreus and others added 26 commits December 10, 2025 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants