Skip to content

Commit 329c484

Browse files
authored
Merge pull request #63 from lann/investigate-stream-traversal-54
A3 + #54: list<u8> stores go bulk; stream values survive host round trips
2 parents c966262 + ec72a7e commit 329c484

17 files changed

Lines changed: 1014 additions & 77 deletions

File tree

contracts/embedder-api.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ containing object as `this`; amendment A3 (2026-08-11) lets `instantiate`
1010
accept untranslated artifacts (`{ componentBytes, translator }`) and run
1111
the translation internally; amendment A4 (2026-08-11) blesses the
1212
translation envelope as the build-time artifact
13-
(`artifactsFromEnvelope`).** This document supersedes `descriptor-ir.md`'s interim
13+
(`artifactsFromEnvelope`); amendment A5 (2026-08-11) makes host wrapping
14+
of one stream/future idempotent (pass-through round trips —
15+
host→guest→host — hand back the same handle machinery instead of
16+
asserting), legalizes host↔host rendezvous for every element type, and
17+
pins u8 stream chunks as `Uint8Array` in both directions.** This document supersedes `descriptor-ir.md`'s interim
1418
"host value mapping" table as the destination for host-facing value shapes.
1519
The runtime's *raw* boundary (`instance.exports`, `HostImports`) keeps the
1620
`definitions.py` interpreter shapes as an **internal** surface; the
@@ -349,8 +353,29 @@ class DroppedError extends Error { … } // awaiting a dropped future rejects
349353
`Promise<T>` or `Future<T>`. Bindgen adapts and **owns the pumping**:
350354
the driving arms auto-close on end/`DROPPED` (eliminating the
351355
deadlock-masking activity-lifetime footgun — R-fix review note 2), and
352-
double-wrap / cross-store reuse are runtime-asserted errors, not silent
353-
misbehavior (note 3).
356+
cross-store reuse is a runtime-asserted error, not silent misbehavior
357+
(note 3).
358+
- **Stream values survive round trips** (amendment A5). A `stream`/`future`
359+
is an identity: lifting one that the host already handled — a
360+
host-created stream a guest passed back (result or import position), or
361+
a guest-created stream on its second hop — is **idempotent**, yielding a
362+
handle over the same underlying end rather than the v0.2
363+
double-wrap error. Consequences, all normative:
364+
- host → guest → host pass-through works with the guest never reading;
365+
the payload then moves host↔host without touching guest memory;
366+
- a readable end may hop the boundary any number of times (each lower
367+
transfers it, exactly as between two guests);
368+
- host↔host rendezvous is legal for **every** element type — the
369+
same-instance restriction applies to component instances only;
370+
- a `Stream.create()` writer keeps feeding the same stream across hops
371+
(the writer half addresses the shared end, not a particular handle).
372+
- **u8 chunks are `Uint8Array` in both directions** (amendment A5, the
373+
write-side mirror of `Chunk<u8>`): `StreamWriter.write`/`writeAll` take
374+
`Chunk<T>`, and a `Uint8Array` chunk is treated as already-lowered bytes
375+
— passed by reference to the rendezvous (borrowed until the returned
376+
promise settles) and copied exactly once, at the rendezvous itself.
377+
Reads hand back that copy unchanged: one copy end-to-end for
378+
host↔host, one memory copy each way when a guest is the peer.
354379
- Writer-side host ends (`hostStream()`-era API) remain the low-level seam
355380
underneath; the conventions layer exposes them as
356381
`Stream.create<T>(): { stream: Stream<T>, writer: StreamWriter<T> }`

docs/architecture.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,13 @@ decide deliberately and document here.
356356
`latin1+utf16` implemented in the v1 interpreter (the ported reference
357357
tests forced it immediately; wit-bindgen guests themselves use utf8).
358358
- **Numbers.** `u64`/`s64``BigInt`; everything else ↔ `number`.
359-
`list<u8>``Uint8Array` (copy; views into guest memory are never exposed).
359+
`list<u8>``Uint8Array` (copy; views into guest memory are never
360+
exposed). Both directions are bulk copies: lift via a `Uint8Array` slice,
361+
lower via `Uint8Array.set` (issue #54 — the per-element interpreted store
362+
cost ~45 ns/byte and capped host→guest byte traffic at ~22 MB/s). Stream
363+
payload copies share these paths, and u8 stream chunks stay `Uint8Array`
364+
through host buffers too, so a host-side stream read costs exactly the one
365+
rendezvous copy.
360366
- **Memory views** are re-acquired after any call that can grow memory
361367
(`ArrayBuffer` detach on `memory.grow`).
362368
- **Resources.** Host-facing handles are classes with `Symbol.dispose`

examples/build.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ TARGET=wasm32-unknown-unknown
2020
BUILD_DIR=guests/build
2121
export CARGO_TARGET_DIR="$PWD/guests/target"
2222

23-
GUESTS="hello values resources async-probe yield-only context-user backpressure-probe stream-echo future-user test-suite"
23+
GUESTS="hello values resources async-probe yield-only context-user backpressure-probe stream-echo stream-pass future-user test-suite"
2424

2525
# wasm-tools validation features per guest (component-model always on;
2626
# CM 0.3 async guests additionally need the cm-async feature).
2727
features_for() {
2828
case "$1" in
29-
async-probe|yield-only|context-user|backpressure-probe|stream-echo|future-user|test-suite)
29+
async-probe|yield-only|context-user|backpressure-probe|stream-echo|stream-pass|future-user|test-suite)
3030
echo "component-model,cm-async" ;;
3131
*) echo "component-model" ;;
3232
esac

0 commit comments

Comments
 (0)