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
4 changes: 3 additions & 1 deletion runtime/src/cabi/flatten.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export const MAX_FLAT_PARAMS = 16;
export const MAX_FLAT_ASYNC_PARAMS = 4;
// Mutable to mirror run_tests.py toggling definitions.MAX_FLAT_RESULTS.
export let MAX_FLAT_RESULTS = 1;
export function setMaxFlatResults(n: number): number {
// Test-only mirror of run_tests.py's constant toggling; not for production
// use (naming convention: see `schedulerSeedForTesting`).
export function setMaxFlatResultsForTesting(n: number): number {
const prev = MAX_FLAT_RESULTS;
MAX_FLAT_RESULTS = n;
return prev;
Expand Down
6 changes: 3 additions & 3 deletions runtime/src/cabi/handles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ function isThenable(v: unknown): v is PromiseLike<unknown> {
/**
* Invoke a resource destructor through the reference's entry bracket.
*
* definitions.py `canon_resource_drop` (line 2318) does not call `rt.dtor`
* definitions.py `canon_resource_drop` (line 2319) does not call `rt.dtor`
* directly. It builds the dtor into a function instance and calls it through
* `Store.lift` / `Store.lower` (lines 2326-2333):
* `Store.lift` / `Store.lower` (lines 2330-2333):
*
* ```python
* dtor = rt.dtor or (lambda rep: [])
Expand Down Expand Up @@ -310,7 +310,7 @@ export function canonResourceDrop(
trapIf(rh.numLends !== 0, "handle still lent out");
if (rh.own) {
assert_(rh.borrowScope === null);
// definitions.py line 2325-2333: the dtor runs through the store's
// definitions.py line 2326-2333: the dtor runs through the store's
// lift/lower bracket. SCOPE NOTE (#85): the call below is a JS frame
// inside the drop trampoline, so a *guest*-initiated drop whose dtor
// suspends traps under the JSPI frame rule. That is deterministic and
Expand Down
11 changes: 7 additions & 4 deletions runtime/src/cabi/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,16 +232,19 @@ export function storeString(
): void {
const mem = requireMemory(cx.opts);
const [begin, taggedCodeUnits] = storeStringIntoRange(cx, v);
// Write order matches the reference (store_string, definitions.py:1613-1616):
// begin pointer first, then tagged length. Unobservable here (no trap can
// intervene between the two writes), but kept in step for parity.
storeInt(
mem,
mem.ptrSize() === 4 ? Number(taggedCodeUnits) : taggedCodeUnits,
ptr + mem.ptrSize(),
mem.ptrSize() === 4 ? begin : BigInt(begin),
ptr,
mem.ptrSize(),
);
storeInt(
mem,
mem.ptrSize() === 4 ? begin : BigInt(begin),
ptr,
mem.ptrSize() === 4 ? Number(taggedCodeUnits) : taggedCodeUnits,
ptr + mem.ptrSize(),
mem.ptrSize(),
);
}
Expand Down
6 changes: 6 additions & 0 deletions runtime/src/exec/boundary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@
stringEncoding: opts.stringEncoding,
memory: opts.memory,
realloc: opts.realloc === null ? null : (o, os, a, n) => {
const realloc = require(opts.realloc, "realloc")!;

Check warning on line 223 in runtime/src/exec/boundary.ts

View workflow job for this annotation

GitHub Actions / core (ubuntu-24.04)

unable to analyze dynamic import

Check warning on line 223 in runtime/src/exec/boundary.ts

View workflow job for this annotation

GitHub Actions / core (ubuntu-24.04-arm)

unable to analyze dynamic import
const p = callCore(realloc, [o, os, a, n]);
trapIf(p.length !== 1 || typeof p[0] !== "number", "realloc result");
return (p[0] as number) >>> 0;
Expand Down Expand Up @@ -1847,6 +1847,12 @@
export function unpackCallbackResult(
packed: number,
): [code: CallbackCode, waitableSetIndex: number] {
// Reference parity insurance only: callers already guarantee this range via
// core-result normalization before calling in.
assert_(
packed >= 0 && packed < 2 ** 32,
`unpack-callback-result: packed out of range: ${packed}`,
);
const code = packed & 0xf;
trapIf(code > CALLBACK_CODE_MAX, `invalid callback code ${code}`);
return [code as CallbackCode, packed >>> 4];
Expand Down Expand Up @@ -1892,7 +1898,7 @@
task.return_(results);
// Post-return runs after the results were read out of guest memory,
// with may_leave cleared (reference canon_lift).
const postReturn = require(opts.postReturn, `${name} post-return`);

Check warning on line 1901 in runtime/src/exec/boundary.ts

View workflow job for this annotation

GitHub Actions / core (ubuntu-24.04)

unable to analyze dynamic import

Check warning on line 1901 in runtime/src/exec/boundary.ts

View workflow job for this annotation

GitHub Actions / core (ubuntu-24.04-arm)

unable to analyze dynamic import
if (postReturn !== null) {
assert_(inst.mayLeave, "post-return with may_leave already false");
inst.mayLeave = false;
Expand Down Expand Up @@ -1938,7 +1944,7 @@
// *mixed* activation, which pin (c) punishes: the first Suspending import
// it reached would trap.
const callback = enterWasm(
require(opts.callback, `${name} callback`)!,

Check warning on line 1947 in runtime/src/exec/boundary.ts

View workflow job for this annotation

GitHub Actions / core (ubuntu-24.04)

unable to analyze dynamic import

Check warning on line 1947 in runtime/src/exec/boundary.ts

View workflow job for this annotation

GitHub Actions / core (ubuntu-24.04-arm)

unable to analyze dynamic import
input.mode,
);
const [packed] = normalizeCoreValues(
Expand Down
3 changes: 2 additions & 1 deletion runtime/src/intrinsics/async_builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,8 @@ export function createSubtaskCancel(
// reports the resolved state through the same tail as the
// non-blocking path. Mirrors SITE 4 (stream_builtins.ts:305-323)
// and `Waitable.waitForPendingEvent` (definitions.py:786-790,
// reached from canon_subtask_cancel :2491): `hasSyncWaiter` must
// reached from canon_subtask_cancel's `subtask.wait_for_pending_event()`
// call, :2484): `hasSyncWaiter` must
// be set for the duration so a concurrent `waitable.join` on
// this subtask traps (async_builtins.ts:362-365) instead of
// racing the SUBTASK event away from this resume (#87).
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/intrinsics/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { UnsupportedFeatureError } from "./errors.ts";

/**
* Number of `i32` context slots per thread. definitions.py `Thread.storage`
* is initialised `[0,0]` (line 346) and `canon_context_{get,set}` assert
* is initialised `[0,0]` (line 347) and `canon_context_{get,set}` assert
* `i < len(thread.storage)` — so exactly two, matching the intrinsic names
* `context-*-i32-0` and `context-*-i32-1`.
*/
Expand Down
8 changes: 7 additions & 1 deletion runtime/src/intrinsics/fact_calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,13 @@ export function createSyncStartCall(
mode: ctx.suspensionMode,
canBlock: ctx.calleeCanBlock?.(callee) ?? false,
onCallerResults: (r) => {
callerResults = r ?? [];
// sync-start-call's callee always uses the async ABI (comment above),
// but the *caller* side here is the sync `canon_lower` path: the
// reference's on_resolve(None) case is reached only when a
// cancellation was requested, and a sync-lowered subtask has no
// handle and hence no cancel channel — so `r` can never be null here.
assert_(r !== null, "sync-start-call: caller results missing");
callerResults = r;
},
lenderScope,
});
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/intrinsics/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class SyncCallScope {
this.lenders.push(h);
}

/** definitions.py `Subtask.release_lenders`. */
/** definitions.py `Subtask.deliver_resolve` (lines 902-906): releases lenders at delivery time. */
releaseLenders(): void {
for (const h of this.lenders) h.numLends -= 1;
this.lenders.length = 0;
Expand Down
14 changes: 13 additions & 1 deletion runtime/src/intrinsics/stream_builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ function streamCopy(input: {
const cx = new LiftLowerContext(cabiOptions(opts), inst, null);
const buffer = new GuestBuffer(elem, cx, ptr, n);

// definitions.py `stream_copy`: `assert(not isinstance(stream_t, CharType))`
// — plan validation is expected to reject a char-typed stream before this
// point (streams of `char` are not a representable component type), so this
// documents that invariant rather than defending against a reachable case.
assert_(elem === null || elem.kind !== "char", "stream copy: char element");

// definitions.py `stream_event`: the payload is computed at *delivery* time,
// so `buffer.progress` reflects everything copied by the time the guest
// looks — including copies that happened after the event was armed.
Expand All @@ -187,6 +193,12 @@ function streamCopy(input: {
buffer.progress <= BUFFER_MAX_LENGTH,
"stream progress out of packing range",
);
// definitions.py `stream_copy`/`stream_event`: `assert(0 <= result < 2**4)`.
// `CopyResult` is a fixed 0..2 enum so this can't fire; kept for parity.
assert_(
result >= 0 && result < 2 ** 4,
"stream event: packed result out of 4-bit range",
);
return [eventCode, i, (result | (buffer.progress << 4)) >>> 0];
};

Expand Down Expand Up @@ -391,7 +403,7 @@ function takeCancelEvent(
);
// UPSTREAM DIVERGENCE (definitions.py is wrong here; wasmtime is right).
//
// `cancel_copy` (definitions.py line 2645) returns an already-armed pending
// `cancel_copy` (definitions.py line 2654) returns an already-armed pending
// event verbatim, so cancelling a stream write that had been partially
// satisfied yields COMPLETED with the copied count. wasmtime instead
// *supersedes* an undelivered stream COMPLETED with CANCELLED, keeping the
Expand Down
1 change: 0 additions & 1 deletion runtime/src/task/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,6 @@ export interface SchedulableThread {
*/
export class Store {
readonly waiting: SchedulableThread[] = [];
nestingDepth = 0;

/**
* Host-import promises this store is waiting on. Non-empty means progress
Expand Down
3 changes: 2 additions & 1 deletion runtime/src/task/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,8 @@ export class SharedFutureImpl implements SharedBase {

read(inst: unknown, dstBuffer: GuestBuffer, onCopyDone: OnCopyDone): void {
// #84 leg (c): the reader arrives AFTER the writable side was abandoned.
// definitions.py:1141 asserts `not self.dropped` here because the drop
// definitions.py:1154 (SharedFutureImpl.read) asserts `not self.dropped`
// here because the drop
// trap keeps that unreachable; for our abandoned state the honest answer
// is the same trap the parked reader gets, delivered synchronously.
if (this.dropped && this.abandonReason !== null) {
Expand Down
10 changes: 8 additions & 2 deletions runtime/src/task/subtask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,16 @@ export class Subtask extends Waitable {

/**
* definitions.py `canon_lower`'s `on_progress`/`subtask_event` closure
* (line 2296). The event payload is computed **at delivery time** and
* (lines 2297-2298). The event payload is computed **at delivery time** and
* delivering it is what runs `deliver_resolve` — so the lent handles are
* released exactly when the guest observes the resolution, not when it
* happens.
*
* The `!this.resolveDelivered()` guard has no reference analogue: it exists
* so this can coexist with `unwindLenders()` (which may itself have already
* delivered the resolve on an abandoned path). The reference's
* `subtask_event` calls `deliver_resolve()` unconditionally and would
* assert on a double delivery.
*/
setSubtaskPendingEvent(subtaski: number): void {
this.setPendingEvent(() => {
Expand All @@ -159,7 +165,7 @@ export class Subtask extends Waitable {

/**
* Pack a `canon_lower` async return value: `state | (subtaski << 4)`
* (definitions.py line 2308, with the accompanying asserts on the ranges).
* (definitions.py line 2306, with the accompanying asserts on the ranges).
*/
export function packSubtaskResult(
state: SubtaskState,
Expand Down
4 changes: 4 additions & 0 deletions runtime/src/task/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export class Thread implements SchedulableThread {
* write *this*, not per-task state: two threads of the same task have
* independent context. wit-bindgen 0.60 keeps its async task pointer in
* slot 0.
*
* Slots are plain JS numbers: a `context.set` of an i64 value above
* 2^53-1 would lose precision. Moot while memory64/threads support is
* deferred (issue #12) — revisit this when that issue's closure lands.
*/
readonly storage: number[] = [0, 0];

Expand Down
Loading