Skip to content

P3 async lowering: transform stream<T>/future<T>/async exports to RFC #46 host intrinsics #94

@avrabe

Description

@avrabe

Context

WASM P3 (WASI 0.3) introduces stream<T>, future<T>, and async component exports. Meld currently handles P2 components (synchronous only). RFC #46 establishes that Meld lowers component model constructs to core modules at build time, with the runtime providing host intrinsics.

For P3, Meld's job expands: lower async constructs to RFC #46 host intrinsic calls.

Problem

P3 async constructs can't be resolved at build time because they represent temporal relationships (data flowing over time). But they CAN be lowered to host intrinsic calls at build time, just like Meld already lowers canonical ABI lift/lower.

Proposal

1. Lower stream<T> to host intrinsics

P3 component-level:

export process: func(input: stream<command>) -> stream<telemetry>;

Meld-lowered core module:

;; Import stream operations from host
(import "pulseengine:async" "stream_new" (func $stream_new (result i64)))
(import "pulseengine:async" "stream_read" (func $stream_read (param i32 i32 i32 i32) (result i32)))
(import "pulseengine:async" "stream_write" (func $stream_write (param i32 i32 i32 i32) (result i32)))
(import "pulseengine:async" "stream_close_readable" (func $close_r (param i32)))
(import "pulseengine:async" "stream_close_writable" (func $close_w (param i32)))

2. Lower async exports to callback trampolines

P3 callback lifting mode (preferred for embedded):

  • Component exports a callback function
  • Host calls callback when async event occurs
  • No persistent stack per in-flight async call
  • Perfect for cFS-style "process one message at a time" pattern

Meld generates:

;; Callback trampoline: called by host when stream has data
(func $__callback_trampoline (param $event_type i32) (param $payload i32)
    ;; Dispatch based on event type
    ;; EVENT_STREAM_READ=2 → call component's message handler
    ;; EVENT_SUBTASK=1 → call component's subtask completion handler
)
(export "__callback" (func $__callback_trampoline))

3. Generate stream adapter code for cross-component streams

When two fused components share a stream<T>:

  • Same memory (single-memory mode): Direct ring buffer access, zero-copy
  • Different memories (multi-memory mode): Adapter copies elements across memory boundaries via stream_readstream_write chain

4. Static validation at build time

Meld can verify at build time:

  • Stream type compatibility (source stream<T> matches sink stream<T>)
  • Backpressure configuration (bounded channels have capacity set)
  • No circular stream dependencies (would deadlock)
  • Resource lifetime correctness across async boundaries

P3 Callback vs Stackful

Meld needs to support both P3 async lifting modes:

Callback mode (preferred for embedded/Gale):

  • Component provides (callback ...) canonical option
  • Meld generates callback dispatch trampoline
  • One shared stack per component
  • Lower memory overhead

Stackful mode (for languages with native async/await):

  • Component uses task.wait/task.yield to suspend
  • Meld generates calls to thread_new/thread_switch_to intrinsics
  • Each in-flight async call needs its own stack
  • Higher memory overhead but more natural for Rust async, Go, Java

Connects to

  • kiln#226 (cFS Software Bus as host routing — streams ARE the SB)
  • kiln P3 runtime issue (host intrinsic implementations)
  • Gale typed ring buffer extension

Priority

High — P3 support is the next major feature for the Component Model ecosystem.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions