Skip to content

refactor(moonbit): a possibly better async implementation for wasi p3#1659

Merged
alexcrichton merged 10 commits into
bytecodealliance:mainfrom
peter-jerry-ye:codex/moonbit-async-upstream-main
Jul 22, 2026
Merged

refactor(moonbit): a possibly better async implementation for wasi p3#1659
alexcrichton merged 10 commits into
bytecodealliance:mainfrom
peter-jerry-ye:codex/moonbit-async-upstream-main

Conversation

@peter-jerry-ye

Copy link
Copy Markdown
Contributor

Background

MoonBit already has an initial implementation of Component Model futures and
streams. It represents component endpoints with generic FutureReader,
FutureWriter, StreamReader, and StreamWriter types backed by
FutureVTable[T] and StreamVTable[T].

Those vtables contain the position-specific component intrinsics together with
payload allocation, lift, lower, and cleanup callbacks. This made the initial
implementation possible, but it also made Component Model endpoints part of the
MoonBit runtime model:

  • creating or using a future or stream requires a vtable tied to a particular
    WIT function position;
  • local construction is restricted to payloads and operations represented by
    that vtable;
  • recursive endpoint payloads require runtime composition of lift/lower logic;
  • endpoint ownership and local producer/consumer behavior are exposed through
    several closely related public types.

The intrinsic naming rules used by the core-wasm adapter reinforce this problem,
but they are not the fundamental reason for the redesign. Even with direct
component generation, a component endpoint and a local MoonBit async value have
different ownership and lifecycle semantics.

Design

This PR replaces the vtable model with a boundary-conversion model.

Future[T], Promise[T], Stream[T], and Sink[T] are local MoonBit
coordination types. They work with arbitrary MoonBit values and do not contain
component handles, endpoint indices, or operation tables.

Component future and stream endpoints remain generated implementation details.
For every concrete WIT position, the generator creates helpers that directly
call the corresponding intrinsics and recursively convert the payload. The
intrinsic names come from wit-parser; they are not reconstructed or selected
from T.

This means:

  • Future::new() creates a local Future/Promise pair, not a component
    future pair;
  • Stream::new() creates a local Stream/Sink pipe;
  • incoming component endpoints become lazy local sources;
  • outgoing local values are bridged to newly created component endpoint pairs;
  • recursive types such as future<future<stream<T>>> are converted one layer
    at a time at their actual canonical ABI positions;
  • the old FutureVTable and StreamVTable runtime representation is removed.

Generated bridges own the canonical ABI state needed to commit or reject
prepared values, preserve partial stream progress, cancel in-flight operations,
observe peer drop, and clean resources whose ownership did not cross the
boundary.

Background work

This PR also adds an explicit

background_group : @async-core.TaskGroup[Unit]

parameter to generated async export implementations.

MoonBit structured concurrency normally considers a function complete only
after its child tasks complete. Component Model task return has a different
boundary: an export may publish its result while work belonging to the
underlying task continues and is no longer observable by the caller.
(After task.return or task.cancel)

The generated adapter therefore publishes the component result when the user
implementation returns, while allowing tasks spawned into background_group to
continue. The work remains owned by the MoonBit task group, but its eventual
result cannot alter the already published export result.

This is required for hook-style APIs such as wasi:http@0.3.0: a handler can
return a response immediately and continue producing its body, trailers, or
processing-completion future afterward.

Other changes

  • Rework the event loop around per-component-task waitable sets and scheduler
    state.
  • Align local task groups, cancellation, semaphores, mutexes, and condition
    variables with moonbitlang/async where Component Model semantics do not
    require different behavior.
  • Support cancellation, peer drop, partial writes, bounded stream buffering,
    resource cleanup, and concurrent component exports.
  • Add coverage for local primitives, recursive endpoints, resource payloads,
    wasi:cli@0.3.0, and wasi:http@0.3.0.
  • Keep endpoint-free synchronous generation independent of the async runtime.

Scope

This establishes an MVP API and a clean generator boundary. It intentionally
does not include:

  • Component Model error-context
  • public BYOB or read_into
  • implicit raw endpoint forwarding
  • lowering newly produced futures or streams from synchronous WIT code without
    an active component async scope

The kebab-case package-name and @buffer.Buffer changes remain separate
prerequisite commits. Endpoint-free synchronous output remains unchanged.

@alexcrichton

Copy link
Copy Markdown
Member

Thanks! Happy to merge when you feel it's ready

@peter-jerry-ye
peter-jerry-ye force-pushed the codex/moonbit-async-upstream-main branch from cb46b80 to 9d5806e Compare July 21, 2026 11:28
@peter-jerry-ye
peter-jerry-ye marked this pull request as ready for review July 22, 2026 02:50
@peter-jerry-ye

Copy link
Copy Markdown
Contributor Author

I think it's ready. Though it still need to be battle tested with actual user feedbacks later.

@alexcrichton
alexcrichton enabled auto-merge July 22, 2026 03:58
@alexcrichton
alexcrichton added this pull request to the merge queue Jul 22, 2026
Merged via the queue into bytecodealliance:main with commit 5c2943b Jul 22, 2026
28 of 29 checks passed
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.

2 participants