Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ the applicable gate.
sync and async, including composed components and overlapping exports.
- Runtime coverage includes canonical ABI values, resources, async host
imports, streams/futures, and background progress between export calls.
- Known gaps include deferred thread features, upstream-unimplemented
- Known gaps include the non-final/derived `thread.new-indirect` signature
restriction, upstream-unimplemented
features, and sync scheduling gaps. See
[architecture §11](docs/architecture.md#11-conformance-and-testing) and the
[issue tracker](https://github.com/polymorph-components/polyengine/issues).
Expand Down
41 changes: 31 additions & 10 deletions contracts/intrinsics.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ semantics remain governed by the pinned spec and `definitions.py`, subject to
[architecture §5](../docs/architecture.md#5-the-jspi-frame-rule-load-bearing-constraint).
2. **Trap and capability failures differ.** Guest violations raise `Trap`.
Unsupported operations raise capability errors and must not satisfy
conformance trap assertions. Component Model traps must be uncatchable, but
this runtime's JS exceptions do not fully provide that guarantee: a guest
`try_table catch_all` can catch a host trap. Adapter exception barriers
preserve the original diagnostic through `HostTrapState`; they do not
eliminate the limitation.
conformance trap assertions. Guest-facing trampoline failures cross Wasm as
native traps so `try_table catch_all` cannot intercept them; the runtime
binds each carrier identity to its semantic cause and originating physical
and logical activations, preserving both through nested barriers before
restoring the cause at the component boundary. Raw core-Wasm exceptions
escaping a canonical lift likewise become Component Model traps.
3. **Instance invariants are runtime obligations.** JSPI does not enforce
`may_leave`, borrow scopes, or task exclusivity. Reentrance into a live
instance is valid. Entry refusal is the runtime's per-instance poisoning
Expand Down Expand Up @@ -82,15 +83,35 @@ directly. `modules[].intrinsics` records import names and resolved categories.

Implemented groups are host import lowering; resource new/rep/drop and transfer;
transcoding; backpressure; task return/cancel; waitable sets and join; subtask
drop/cancel; stream/future operations; error contexts; context get/set; and
thread yield. Other explicit thread builtins are representable in the plan but
unsupported by the runtime.
drop/cancel; stream/future operations; error contexts; context get/set; and the
explicit-thread family: `thread.index`, `thread.new-indirect`,
`thread.resume-later`, `thread.suspend`, `thread.yield`, and the
`suspend`/`yield`-then-`resume`/`promote` forms. Explicit threads use the same
task scheduler and JSPI activation bridge as the implicit thread. Publishing a
task result does not destroy its remaining explicit threads; the worker owns
host-call teardown after result delivery.

`thread.new-indirect` currently accepts the canonical final `(i32) -> ()` and
`(i64) -> ()` start-function types. Its native `ref.test` validator rejects
some functions whose valid non-final/derived type is structurally equivalent
but has a different nominal reference identity. This is a runtime interface
capability restriction, not a Component Model validation rule and not a claim
that such guests are invalid. The JavaScript WebAssembly API exposes neither
function-signature reflection nor the reference-type relation needed to decide
the general case without calling the function (which `thread.new-indirect`
must not do during validation); native Wasmtime has a similar current
restriction but is corroborating evidence only. Full support requires
translator-supplied core-function metadata or table instrumentation/type
normalization and remains tracked by
[#12](https://github.com/polymorph-components/polyengine/issues/12).

Trampolines are materialized on first reference during instantiation.
Unsupported referenced kinds fail then with a capability diagnostic; unused
entries do not prevent instantiation. A supported blocking operation may still
require JSPI at call time. The runtime's `createTrampoline` switch is the
current implementation inventory; [plan-format.md](plan-format.md) defines the
require JSPI at call time. The inventory above is the explicit supported
surface; it is not a claim of unrestricted thread conformance because of the
`thread.new-indirect` type restriction. The runtime's `createTrampoline` switch
is the implementation inventory; [plan-format.md](plan-format.md) defines the
wire representation.

## Manifest
Expand Down
41 changes: 36 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ progress. There is no preemption.
| Resume | Scheduler resolves or consumes the relevant settlement |
| Callback ABI | Scheduler invokes the callback export with events; no suspended wasm stack |
| Waitable / waitable set | Host-side event state, consumed by stackful waits or callback return codes |
| Explicit cooperative threads | Task-owned logical threads; JSPI suspension points carry stackful parks and named direct switches |
| Sync `canon_lift` | Drive the task to resolution, retaining the reference's deadlock trap |
| Async `canon_lift` | Exit the driver on idle; an unresolved export Promise stays pending for later progress |

Expand Down Expand Up @@ -298,6 +299,16 @@ acts. An idle async-typed export may remain pending indefinitely; sync-typed
exports retain deadlock detection. See the
[function contract](../contracts/embedder-api.md#functions-and-async).

**Synchronous boundary and host latency.** A synchronous logical canonical
callee that reaches a Component Model park drives only work belonging to that
callee instance and traps when none can progress, as required by the reference
`canon_lift` loop. A Promise returned by a host import marked `suspending()` is
an embedding accommodation: its latency is not itself Component Model blocking
or proof that unrelated Component Model work can progress. Component traps and
escaping core Wasm exceptions cross guest Wasm via an uncatchable native trap
carrier keyed to the exact physical activation and semantic cause; there is no
reusable global cause slot.

**Host-import cancellation.** By default, cancellation resolves the
subtask promptly as `CANCELLED_BEFORE_RETURNED` and discards late Promise
settlements. The result is not lowered, and the discarded call no longer
Expand Down Expand Up @@ -409,8 +420,9 @@ the plan; see [descriptor IR](../contracts/descriptor-ir.md#resource-type-identi
destructor with synchronous canonical options. The destructor may not
Component-Model-block, though the spec permits spawning an explicit
thread that blocks without preventing the destructor's implicit thread
from returning. This does not imply support for the deferred explicit-thread
built-ins (§11). Both guest- and host-initiated drops of guest resources use
from returning. Such explicit threads use the scheduler described in §6 and
are not destroyed merely because the destructor result has returned. Both
guest- and host-initiated drops of guest resources use
`createDtorEntry` in `runtime/src/exec/boundary.ts`, creating a fresh
synchronous task and implicit thread rather than borrowing the caller's
task. A missing destructor still goes through that lift machinery.
Expand Down Expand Up @@ -573,16 +585,35 @@ normalization belongs to `TRAP_MESSAGE_EQUIVALENTS` in
`harness/src/runner.ts`, not the runtime.

Expected failures are classified, not counted as conformance. Known
classes include deferred thread support
([#12](https://github.com/polymorph-components/polyengine/issues/12)),
sync scheduling gaps
classes include the explicit-thread type-interface restriction
([#12](https://github.com/polymorph-components/polyengine/issues/12)), sync scheduling gaps
([#249](https://github.com/polymorph-components/polyengine/issues/249)),
and upstream-unimplemented features
([#248](https://github.com/polymorph-components/polyengine/issues/248)).
Per-lane overlays distinguish engine limitations from runtime failures.
The current base classification is in [harness/src/xfail.ts](../harness/src/xfail.ts).
Unexpected failures and stale expected failures fail their gate.

The current Deno and Chromium official-corpus baseline executes 1,506 of
1,511 commands: 1,468 pass, 38 are exact xfails, and five text directives are
unsupported. There are no runtime/capability skips. The corpus exercises the
implemented explicit-thread family using canonical-final start signatures; it
does not exercise the valid non-final/derived signature restriction described
in the intrinsic contract, so these counts are not a full-thread-conformance
claim.

The supplementary Wasmtime lane adapts native test controls only when their
semantic observation survives the adaptation. Its `gc` helper for the resource
destructor context test is a real synchronous host boundary: Wasmtime uses that
call to force a deferred frame, while this runtime eagerly materializes the
corresponding logical task/thread. No JavaScript GC is forced. Wasmtime's
table-capacity control is not exposed as a production option; a focused runtime
test instead checks bounded handle reuse across the same 1,000 cancelled
STARTING subtasks. The excluded `streams-massive-send` file asserts Wasmtime's
host-defined 128 MiB transfer-fuel policy over an exponentially expanded value,
not a Component Model limit; the exclusion does not imply that polyengine has
an equivalent aggregate transfer budget.

The [justfile](../justfile) is the command surface; CI job bodies live in
[.github/justfile](../.github/justfile). Required PR checks use the pinned
shell lanes alongside core tests. Browser lanes run post-merge and gate
Expand Down
55 changes: 50 additions & 5 deletions harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,58 @@ and their tracking issues are declared in
to [#372](https://github.com/polymorph-components/polyengine/issues/372)
(runtime-semantics, diagnostic-mismatch, imported-module, cascade,
provider-control, exception-handling — plan v0 / diagnostic gaps against
Wasmtime's own assertions, not the spec corpus above). `deferred-threads`
skips map to [#12](https://github.com/polymorph-components/polyengine/issues/12).
A handful of files are excluded outright (unbounded memory stress, GC, or
Wasmtime-specific validation configuration this translator doesn't share) —
see `WASMTIME_EXCLUSIONS` for the current list and reasons; the run fails if an
Wasmtime's own assertions, not the spec corpus above). The supplementary thread
fixtures now execute rather than being skipped. This does not imply
unrestricted explicit-thread conformance: valid non-final or derived
start-function signatures can be rejected by the runtime's nominal `ref.test`
validator, as documented in `contracts/intrinsics.md`; the current Wasmtime
fixtures use the supported canonical-final signatures and do not test that
interface restriction
([#12](https://github.com/polymorph-components/polyengine/issues/12)).
A handful of files are excluded outright; see `WASMTIME_EXCLUSIONS` for the
current list and reasons. In particular, `streams-massive-send.wast` asserts
Wasmtime's host-defined 128 MiB per-hostcall transfer-fuel policy using an
exponentially expanded nested-list value. The Component Model specifies no such
fuel limit, and executing it in this in-process V8 harness can exhaust the
bounded heap before a catchable result. This exclusion is not a claim that the
runtime has an equivalent production resource limit. The run fails if an
exclusion goes stale (the file gone from the manifest).

Two Wasmtime-private controls are treated by purpose rather than name. The
`context-in-resource-drop.wast` `wasmtime/gc` import is supplied only for that
file as a real synchronous host call. Wasmtime uses GC to force a deferred
destructor frame; polyengine eagerly materializes the logical task/thread, so
the boundary itself exercises context preservation and no JavaScript GC is
forced. Conversely, `set-max-table-capacity` is not emulated: its leak-detection
purpose is covered by
`runtime/tests/wasmtime/cancel_starting_reuse_test.ts`, which performs 1,000
STARTING-cancel-deliver-drop cycles and asserts bounded handle-slot reuse.

Some exact supplementary trap strings describe the same rejected operation at
different levels of detail. `runner.ts` records exact-only equivalents for the
five non-thread `task-return-traps.wast` diagnostic rows. Eleven future-write
rows remain classified instead: in each named fixture the reader was dropped,
but Wasmtime retains separate local/transmit completion guards while
polyengine's reference-shaped `WritableFutureEnd` has one `CopyState.DONE` and
reports its broader “previous write succeeded or readable end dropped” text.
That row-specific diagnostic divergence is spec-compatible, but it is not a
global message equivalence because the runtime text also covers a distinct
successful-prior-write condition.

At the current pin, the remaining async subset is 13 classified failures and
zero skips: those eleven diagnostic rows, the unavailable native
`set-max-table-capacity` provider row, and its one no-current-instance cascade.
The capacity knob is intentionally not a production capability; the bounded
reuse test above covers its leak-detection purpose without pretending to
reproduce Wasmtime's host configuration surface.

The official Deno/Chromium aggregate at the current Component Model pin is
1,511 commands, 1,506 executed, 1,468 passed, 38 exact xfails, zero
runtime/capability skips, and five unsupported text directives. Seeded runs
omit the three-command `async-calls-sync` deterministic-profile fixture, giving
1,508 commands, 1,503 executed, and 1,465 passed with the same 38 xfails and
five unsupported directives.

`just test-wasmtime-guests` builds the two upstream async guest binaries this
WAST corpus doesn't cover as executables — `async_round_trip_stackless` and
`async_short_reads` from `crates/test-programs/src/bin/` at the same locked
Expand Down
8 changes: 4 additions & 4 deletions harness/browser/expectations/chromium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ export const chromium: LaneExpectation = {
// Identical to the Deno lane's TOTAL row.
totals: {
commands: 1511,
executed: 1411,
passed: 1286,
executed: 1506,
passed: 1468,
failed: 0,
xfail: 125,
pendingRuntime: 95,
xfail: 38,
pendingRuntime: 0,
pendingCapability: 0,
unsupportedDirective: 5,
},
Expand Down
8 changes: 4 additions & 4 deletions harness/browser/expectations/firefox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ export const firefox: LaneExpectation = {
// does not gate on them (`required: false`).
totals: {
commands: 1511,
executed: 1411,
passed: 1286,
executed: 1506,
passed: 1468,
failed: 0,
xfail: 125,
pendingRuntime: 95,
xfail: 38,
pendingRuntime: 0,
pendingCapability: 0,
unsupportedDirective: 5,
},
Expand Down
8 changes: 4 additions & 4 deletions harness/shell/expectations/bun-pinned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ export const bunPinned: ShellLaneExpectation = {
deltas: [],
totals: {
commands: 1511,
executed: 1411,
passed: 1286,
executed: 1506,
passed: 1468,
failed: 0,
xfail: 125,
pendingRuntime: 95,
xfail: 38,
pendingRuntime: 0,
pendingCapability: 0,
unsupportedDirective: 5,
},
Expand Down
8 changes: 4 additions & 4 deletions harness/shell/expectations/jsc-pinned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ export const jscPinned: ShellLaneExpectation = {
deltas: [],
totals: {
commands: 1511,
executed: 1411,
passed: 1286,
executed: 1506,
passed: 1468,
failed: 0,
xfail: 125,
pendingRuntime: 95,
xfail: 38,
pendingRuntime: 0,
pendingCapability: 0,
unsupportedDirective: 5,
},
Expand Down
8 changes: 4 additions & 4 deletions harness/shell/expectations/jsc-trunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ export const jscTrunk: ShellLaneExpectation = {
deltas: [],
totals: {
commands: 1511,
executed: 1411,
passed: 1286,
executed: 1506,
passed: 1468,
failed: 0,
xfail: 125,
pendingRuntime: 95,
xfail: 38,
pendingRuntime: 0,
pendingCapability: 0,
unsupportedDirective: 5,
},
Expand Down
11 changes: 5 additions & 6 deletions harness/shell/expectations/node-pinned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,19 @@ import type { ShellLaneExpectation } from "./types.ts";
export const nodePinned: ShellLaneExpectation = {
lane: "node-pinned",
required: true,
notes:
"Node.js pinned (v26.7.0, nodejs.org tarball, sha256-verified, both " +
notes: "Node.js pinned (v26.7.0, nodejs.org tarball, sha256-verified, both " +
"arches). Exact Deno-lane parity with no flags (JSPI default-on in " +
">= 26): zero deltas, all capabilities true. Required gate. Node 24 LTS " +
"is deliberately not laned — flag-gated JSPI with 2 real deviations " +
"(see this file's header).",
deltas: [],
totals: {
commands: 1511,
executed: 1411,
passed: 1286,
executed: 1506,
passed: 1468,
failed: 0,
xfail: 125,
pendingRuntime: 95,
xfail: 38,
pendingRuntime: 0,
pendingCapability: 0,
unsupportedDirective: 5,
},
Expand Down
11 changes: 5 additions & 6 deletions harness/shell/expectations/sm-nightly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,17 @@ import type { ShellLaneExpectation } from "./types.ts";
export const smNightly: ShellLaneExpectation = {
lane: "sm-nightly",
required: false,
notes:
"SpiderMonkey nightly (linux-aarch64 jsshell). Full Deno parity: " +
notes: "SpiderMonkey nightly (linux-aarch64 jsshell). Full Deno parity: " +
"zero deltas, all compile-probes true (multi-memory/wasm-GC/EH/memory64/" +
"tail-calls/relaxed-simd), JSPI round trip verified end to end.",
deltas: [],
totals: {
commands: 1511,
executed: 1411,
passed: 1286,
executed: 1506,
passed: 1468,
failed: 0,
xfail: 125,
pendingRuntime: 95,
xfail: 38,
pendingRuntime: 0,
pendingCapability: 0,
unsupportedDirective: 5,
},
Expand Down
8 changes: 4 additions & 4 deletions harness/shell/expectations/sm-pinned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ export const smPinned: ShellLaneExpectation = {
deltas: [],
totals: {
commands: 1511,
executed: 1411,
passed: 1286,
executed: 1506,
passed: 1468,
failed: 0,
xfail: 125,
pendingRuntime: 95,
xfail: 38,
pendingRuntime: 0,
pendingCapability: 0,
unsupportedDirective: 5,
},
Expand Down
17 changes: 17 additions & 0 deletions harness/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ const TRAP_MESSAGE_EQUIVALENTS: Array<
"guest trapped: unreachable",
"guest trapped: unreachable executed",
"guest trapped: Unreachable code should not be executed",
"guest trapped: Unreachable code should not be executed (evaluating 'fn()')",
"guest trapped: Unreachable code should not be executed (evaluating 'fn(...args)')",
],
],
Expand All @@ -363,6 +364,7 @@ const TRAP_MESSAGE_EQUIVALENTS: Array<
"unreachable",
[
"guest trapped: Unreachable code should not be executed",
"guest trapped: Unreachable code should not be executed (evaluating 'fn()')",
"guest trapped: Unreachable code should not be executed (evaluating 'fn(...args)')",
],
],
Expand All @@ -376,6 +378,7 @@ const TRAP_MESSAGE_EQUIVALENTS: Array<
"guest trapped: unreachable",
"guest trapped: unreachable executed",
"guest trapped: Unreachable code should not be executed",
"guest trapped: Unreachable code should not be executed (evaluating 'fn()')",
"guest trapped: Unreachable code should not be executed (evaluating 'fn(...args)')",
],
],
Expand Down Expand Up @@ -416,6 +419,20 @@ const TRAP_MESSAGE_EQUIVALENTS: Array<
"cannot write after being notified that the readable end dropped",
["cannot write to stream after being notified that the readable end dropped"],
],
// Pinned Wasmtime task-return-traps.wast uses these umbrella diagnostics.
// The runtime reports the precise reference-state check which fired. Each
// pair is exact so unrelated task lifecycle failures remain mismatches.
[
"async-lifted export failed to produce a result",
["task finished all threads without resolving"],
],
[
"invalid `task.return` signature and/or options for current task",
[
"task.return with a result type that is not the task's result type",
"task.return with canonical options differing from the task's",
],
],
// The same-instance non-numeric guard is the exact check on both sides:
// runtime/src/task/streams.ts:587-591,708-711,725-728 and pinned Wasmtime
// futures_and_streams.rs:3330-3335. Future/stream names are diagnostic only.
Expand Down
Loading
Loading