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
1 change: 1 addition & 0 deletions .github/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ _step-tolerated recipe:
core:
@just gha::_step version-guard-local
@just gha::_step version-guard-pr
@just gha::_step fmt-check
@just gha::_step build
@just gha::_step test-rust
@just gha::_step shim
Expand Down
10 changes: 8 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@ ci: (gha::core) (gha::browser)
# Includes the consumer smokes CI cannot run (they need the polymorph
# checkouts; docs/consumers.md).
# The full pre-commit pass (AGENTS.md "Gates"): everything.
gates: version-guard-local build test-rust test-protocol test-runtime test-wasi test-sockets-node test-ct-runner test-bundle test-version-guard publish-check test-npm examples test-translate conformance sched-seeds shells browsers smoke-tls smoke-c0
gates: version-guard-local fmt-check build test-rust test-protocol test-runtime test-wasi test-sockets-node test-ct-runner test-bundle test-version-guard publish-check test-npm examples test-translate conformance sched-seeds shells browsers smoke-tls smoke-c0

# Fast sanity: builds + native tests + type-checks, no suites.
check: build test-rust
check: fmt-check build test-rust
cd protocol && deno task check
cd runtime && deno task check
cd wasi && deno task check
cd ct-runner && deno task check

# The runtime package is formatter-clean (`deno fmt`, stock settings; the
# generated bindgen snapshots/envelopes and generator output are excluded in
# runtime/deno.json). Fix with `cd runtime && deno fmt`.
fmt-check:
cd runtime && deno fmt --check

# ----- builders ---------------------------------------------------------------

build:
Expand Down
66 changes: 34 additions & 32 deletions runtime/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# runtime — TS core runtime

Platform-neutral TypeScript runtime core (docs/architecture.md §4.3). Two layers exist
today:
Platform-neutral TypeScript runtime core (docs/architecture.md §4.3). Two layers
exist today:

1. the **canonical-ABI v1 value interpreter** (`src/cabi/`) with the
**definitions.py test ports** (`tests/`): docs/architecture.md §11 row 2, feeding §7
(canonical-ABI decisions) and §8 (the descriptor-IR interpreter);
2. the **plan executor on the task-model skeleton** (`src/plan/`,
`src/task/`, `src/exec/`, `src/intrinsics/`, `src/shim/`): loads the
translator shim's plan v0 (contracts/plan-format.md), instantiates and
links the component, and routes every lifted-export call through
Task/Thread structures (degenerate sync path, docs/architecture.md §6).
**definitions.py test ports** (`tests/`): docs/architecture.md §11 row 2,
feeding §7 (canonical-ABI decisions) and §8 (the descriptor-IR interpreter);
2. the **plan executor on the task-model skeleton** (`src/plan/`, `src/task/`,
`src/exec/`, `src/intrinsics/`, `src/shim/`): loads the translator shim's
plan v0 (contracts/plan-format.md), instantiates and links the component, and
routes every lifted-export call through Task/Thread structures (degenerate
sync path, docs/architecture.md §6).

## Layout

Expand Down Expand Up @@ -46,8 +46,9 @@ tests/integration/ full-pipeline e2e: shim wasm32 under Deno -> plan ->
```

Run tests: `deno task test` (from `runtime/`; it is `deno test
--allow-read=..` — the integration tests read build artifacts from the repo).
Type-check: `deno task check`.
--allow-read=..`
— the integration tests read build artifacts from the repo). Type-check:
`deno task check`.

Regenerate fixtures: `deno task gen-fixtures` (needs `python3`; imports
`third_party/component-model/design/mvp/canonical-abi/definitions.py`
Expand All @@ -66,7 +67,7 @@ needed when the submodule's definitions.py changes.
| NaN canonicalization (deterministic profile) | `test_nan32/64` | `nan_test.ts` |
| char validation (surrogates, > 0x10FFFF trap) | `test_pairs(CharType...)` | `flat_test.ts` |
| strings: full utf8/utf16/latin1+utf16 matrix, both address types, byte-exact incl. realloc traffic | `test_string` matrix | `string_test.ts` (fixture-driven: 168 lift + 150 lower byte-exact checks + 504 roundtrips) |
| USVString lone-surrogate replacement (docs/architecture.md §7) | n/a in Python (strings always well-formed) | `string_test.ts` (TS-authored) |
| USVString lone-surrogate replacement (docs/architecture.md §7) | n/a in Python (strings always well-formed) | `string_test.ts` (TS-authored) |
| lists/records/variants/flags/map over heap memory, misalignment traps, i64 memories | `test_heap` | `heap_test.ts` (35 cases) |
| lift/lower_flat_values spilling (17 params, retp out-param, alignment traps) | reached via `test_roundtrips`/`canon_lower` upstream | `values_test.ts` (TS-authored) |
| handle Table (slab, free list LIFO, traps), resource.new/rep/drop, own transfer, borrow lend counting | `test_handles` (pure parts) | `handles_test.ts` |
Expand All @@ -80,9 +81,9 @@ through `Store`/`Task`/`Thread`: cross-component realloc, the full
cancellation, `thread.*`/`context.*` built-ins, and the
error-context/stream/future _value types_ (their layout/flatten is implemented;
their lift/lower throws `NotImplemented`). Each has an ignored placeholder in
`tests/deferred_test.ts` with the reason. Per docs/architecture.md §6 the scheduler is the
core deliverable and gets built as the runtime's spine — these ports become its
acceptance tests, not the other way round.
`tests/deferred_test.ts` with the reason. Per docs/architecture.md §6 the
scheduler is the core deliverable and gets built as the runtime's spine — these
ports become its acceptance tests, not the other way round.

## Decisions forced by JS semantics (not already settled by docs/architecture.md §7)

Expand Down Expand Up @@ -118,23 +119,24 @@ distinctions); flagged for plan review:
7. **Variant/record/flags value shapes** mirror definitions.py's semantics
(variants as `{kind, value}` objects — contracts/descriptor-ir.md §"Host
value shapes" — despecialized tuple records, label→bool maps); `list<u8>` is
`Uint8Array` per docs/architecture.md §7. Final host-facing representations for bindgen
remain open (below).
`Uint8Array` per docs/architecture.md §7. Final host-facing representations
for bindgen remain open (below).

Also plan-relevant: docs/architecture.md §7 defers **latin1+utf16** "until a test forces it"
— the ported definitions.py string matrix forces it, so the v1 interpreter now
implements it fully (both directions). If we prefer to keep the runtime surface
minimal, the store path can be re-deferred by ignoring the fixture subset again.
Also plan-relevant: docs/architecture.md §7 defers **latin1+utf16** "until a
test forces it" — the ported definitions.py string matrix forces it, so the v1
interpreter now implements it fully (both directions). If we prefer to keep the
runtime surface minimal, the store path can be re-deferred by ignoring the
fixture subset again.

## Open questions (types.ts is provisional)

- Wire format of the descriptor IR: `types.ts` is the in-memory sketch; the
translator shim (docs/architecture.md §4.2) will define the serialized form and likely
intern labels/types by index.
- Host-facing value representations for bindings (docs/architecture.md §9): tuples as arrays?
variants as `{ tag, val }`? `option<T>` as `T | undefined` with a
`Some`/`None` escape hatch for nesting? The interpreter's despecialized shapes
are faithful to the reference but not ergonomic.
translator shim (docs/architecture.md §4.2) will define the serialized form
and likely intern labels/types by index.
- Host-facing value representations for bindings (docs/architecture.md §9):
tuples as arrays? variants as `{ tag, val }`? `option<T>` as `T | undefined`
with a `Some`/`None` escape hatch for nesting? The interpreter's despecialized
shapes are faithful to the reference but not ergonomic.
- Whether host-provided `char`/integer values get validated (trap) or asserted
at the boundary — definitions.py asserts (guest-side values are trusted by
construction); a host API needs a decision.
Expand All @@ -144,10 +146,10 @@ minimal, the store path can be re-deferred by ignoring the fixture subset again.
## Upstream discrepancies found (definitions.py vs CanonicalABI.md)

- `canon_backpressure_set` existed in definitions.py but CanonicalABI.md
documents only `backpressure.inc`/`backpressure.dec`; the repo's own
`diff.py` flagged it. Vestigial back-compat shim — resolved upstream
independently (CM PR #690 removed it; findings tracker CM-2). The stale
`$async?` immediate on `resource.drop` (CanonicalABI.md ~line 4013,
noted in docs/architecture.md §7) is still open.
documents only `backpressure.inc`/`backpressure.dec`; the repo's own `diff.py`
flagged it. Vestigial back-compat shim — resolved upstream independently (CM
PR #690 removed it; findings tracker CM-2). The stale `$async?` immediate on
`resource.drop` (CanonicalABI.md ~line 4013, noted in docs/architecture.md §7)
is still open.
- `python3 run_tests.py` passes upstream unmodified (Python 3.13.7), so no
reference-snapshot copy was needed.
7 changes: 7 additions & 0 deletions runtime/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
"check": "deno check src tests",
"gen-fixtures": "python3 tests/fixtures/generate.py"
},
"fmt": {
"exclude": [
"tests/bindgen/generated/",
"tests/bindgen/fixtures/",
"tests/fixtures/"
]
},
"publish": {
"exclude": [
"tests/"
Expand Down
13 changes: 10 additions & 3 deletions runtime/src/cabi/async_values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ import {
ErrorContext,
ReadableFutureEnd,
ReadableStreamEnd,
sameElemType,
type SharedBase,
SharedFutureImpl,
sameElemType,
SharedStreamImpl,
} from "../task/streams.ts";

Expand Down Expand Up @@ -88,8 +88,15 @@ function liftAsyncValue(
assert_(inst !== null, `${what} lift requires a component instance`);
const e = inst!.handles.remove(i);
trapIf(!(e instanceof EndT), `${what} lift: handle is not a ${what} end`);
const end = e as { shared: SharedBase; state: CopyState; inWaitableSet(): boolean };
trapIf(!sameElemType(end.shared.t, elem), `${what} lift: element type mismatch`);
const end = e as {
shared: SharedBase;
state: CopyState;
inWaitableSet(): boolean;
};
trapIf(
!sameElemType(end.shared.t, elem),
`${what} lift: element type mismatch`,
);
trapIf(
end.state === CopyState.DONE,
what === "future"
Expand Down
6 changes: 4 additions & 2 deletions runtime/src/cabi/bulk_lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ const FLOAT_CTORS: Record<
f64: Float64Array,
};


function viewOf<
C extends { new (b: ArrayBufferLike, o: number, n: number): InstanceType<C>; readonly BYTES_PER_ELEMENT: number },
C extends {
new (b: ArrayBufferLike, o: number, n: number): InstanceType<C>;
readonly BYTES_PER_ELEMENT: number;
},
>(ctor: C, mem: MemInst, ptr: number, length: number): InstanceType<C> | null {
if (!PLATFORM_LITTLE_ENDIAN) return null;
const byteOffset = mem.bytes.byteOffset + ptr;
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/cabi/handles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

import { assert_, Trap, trap, trapIf } from "./trap.ts";
import {
entryRefusal,
NeedsJspi,
notifyInstancePoisoned,
PendingCapability,
entryRefusal,
} from "../task/scheduler.ts";
import { COMPONENT_INSTANCE } from "./context.ts";
import type {
Expand Down
6 changes: 1 addition & 5 deletions runtime/src/cabi/lift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ import {
type FieldType,
type ValType,
} from "./types.ts";
import {
liftErrorContext,
liftFuture,
liftStream,
} from "./async_values.ts";
import { liftErrorContext, liftFuture, liftStream } from "./async_values.ts";

/** Anything lift can pull core values from (CoreValueIter or the variant
* coercion iterator). */
Expand Down
6 changes: 1 addition & 5 deletions runtime/src/cabi/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ import {
type FieldType,
type ValType,
} from "./types.ts";
import {
liftErrorContext,
liftFuture,
liftStream,
} from "./async_values.ts";
import { liftErrorContext, liftFuture, liftStream } from "./async_values.ts";

export const MAX_LIST_BYTE_LENGTH = (1 << 28) - 1;

Expand Down
6 changes: 1 addition & 5 deletions runtime/src/cabi/lower.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ import {
type ValType,
type VariantValue,
} from "./types.ts";
import {
lowerErrorContext,
lowerFuture,
lowerStream,
} from "./async_values.ts";
import { lowerErrorContext, lowerFuture, lowerStream } from "./async_values.ts";

export function lowerFlat(
cx: LiftLowerContext,
Expand Down
10 changes: 6 additions & 4 deletions runtime/src/cabi/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,14 @@ export function loadPtr(mem: MemInst, ptr: number): number | bigint {
// bulk_lists.ts intentionally wraps instead — see that file's header for why.

function bigintFitsIn64(v: bigint, signed: boolean): boolean {
return signed
? v >= -(2n ** 63n) && v < 2n ** 63n
: v >= 0n && v < 2n ** 64n;
return signed ? v >= -(2n ** 63n) && v < 2n ** 63n : v >= 0n && v < 2n ** 64n;
}

function numberFitsInWidth(v: number, nbytes: 1 | 2 | 4, signed: boolean): boolean {
function numberFitsInWidth(
v: number,
nbytes: 1 | 2 | 4,
signed: boolean,
): boolean {
const bits = nbytes * 8;
if (signed) {
const min = -(2 ** (bits - 1));
Expand Down
6 changes: 1 addition & 5 deletions runtime/src/cabi/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ import {
type ValType,
type VariantValue,
} from "./types.ts";
import {
lowerErrorContext,
lowerFuture,
lowerStream,
} from "./async_values.ts";
import { lowerErrorContext, lowerFuture, lowerStream } from "./async_values.ts";

export function store(
cx: LiftLowerContext,
Expand Down
50 changes: 25 additions & 25 deletions runtime/src/cabi/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,21 +328,21 @@ function storeStringToUtf8(
assert_(worstCaseSize <= REALLOC_I32_MAX);
ptr = cx.reallocate(ptr, srcCodeUnits, 1, worstCaseSize);
trapIfRangeExceedsMemory(
mem,
ptr,
worstCaseSize,
REALLOC_OOB,
);
mem,
ptr,
worstCaseSize,
REALLOC_OOB,
);
const encoded = utf8Encoder.encode(src); // USVString: replaces lone surrogates
writeBytes(mem, ptr + i, encoded.subarray(i));
if (worstCaseSize > encoded.length) {
ptr = cx.reallocate(ptr, worstCaseSize, 1, encoded.length);
trapIfRangeExceedsMemory(
mem,
ptr,
encoded.length,
REALLOC_OOB,
);
mem,
ptr,
encoded.length,
REALLOC_OOB,
);
}
return [ptr, BigInt(encoded.length)];
}
Expand Down Expand Up @@ -380,11 +380,11 @@ function storeStringToLatin1OrUtf16(
ptr = cx.reallocate(ptr, srcCodeUnits, 2, worstCaseSize);
trapIf(ptr !== alignTo(ptr, 2), REALLOC_MISALIGNED);
trapIfRangeExceedsMemory(
mem,
ptr,
worstCaseSize,
REALLOC_OOB,
);
mem,
ptr,
worstCaseSize,
REALLOC_OOB,
);
for (let j = dstByteLength - 1; j >= 0; j--) {
mem.bytes[ptr + 2 * j] = mem.bytes[ptr + j];
mem.bytes[ptr + 2 * j + 1] = 0;
Expand All @@ -399,11 +399,11 @@ function storeStringToLatin1OrUtf16(
ptr = cx.reallocate(ptr, worstCaseSize, 2, encoded.length);
trapIf(ptr !== alignTo(ptr, 2), REALLOC_MISALIGNED);
trapIfRangeExceedsMemory(
mem,
ptr,
encoded.length,
REALLOC_OOB,
);
mem,
ptr,
encoded.length,
REALLOC_OOB,
);
}
const taggedCodeUnits = BigInt(encoded.length / 2) |
utf16TagBig(mem.ptrType());
Expand All @@ -414,11 +414,11 @@ function storeStringToLatin1OrUtf16(
ptr = cx.reallocate(ptr, srcCodeUnits, 2, dstByteLength);
trapIf(ptr !== alignTo(ptr, 2), REALLOC_MISALIGNED);
trapIfRangeExceedsMemory(
mem,
ptr,
dstByteLength,
REALLOC_OOB,
);
mem,
ptr,
dstByteLength,
REALLOC_OOB,
);
}
return [ptr, BigInt(dstByteLength)];
}
Expand Down
Loading
Loading