@polyengine/runtime 0.4.0-pre.g28b13d2 → 0.4.0-pre.g7fb65ed

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,15 +26,32 @@ export class LiftLowerContext {
26
26
  this.borrowScope = borrowScope;
27
27
  }
28
28
  /**
29
- * definitions.py `LiftLowerContext.reallocate` routes the call through
30
- * canon_lift (reentrance bookkeeping and may_leave toggling around a
31
- * guest-side realloc export). v1 simplification: call the provided realloc
32
- * directly. The full path returns with the task machinery.
29
+ * definitions.py `LiftLowerContext.reallocate`: the guest's realloc runs
30
+ * with `may_leave` cleared, so a realloc that lowers an import traps
31
+ * (`canon_lower`'s `trap_if(not ...may_leave)`, implemented here by
32
+ * exec/boundary.ts `createLoweredImport`). That bracket is implemented
33
+ * below. What remains deferred is only the reference's routing of the call
34
+ * through `canon_lift`; upstream component-model PR #705 removes that
35
+ * routing, leaving this bracket as the whole story. polyengine issue #147.
33
36
  */
34
37
  reallocate(old, oldByteLength, alignment, newByteLength) {
35
38
  const realloc = this.opts.realloc;
36
39
  trapIf(realloc === null, "realloc required but not provided");
37
- return realloc(old, oldByteLength, alignment, newByteLength);
40
+ // A null instance is the value-interpreter unit-test harness only
41
+ // (tests/support/driver.ts `mkCx`); every real runtime path constructs
42
+ // this context with an instance, so there is no flag to bracket.
43
+ if (this.inst === null) {
44
+ return realloc(old, oldByteLength, alignment, newByteLength);
45
+ }
46
+ assert_(this.inst.mayLeave, "realloc with may_leave already false");
47
+ this.inst.mayLeave = false;
48
+ // NO try/finally, deliberately: same bare bracket as the post-return one
49
+ // in intrinsics/fact_calls.ts (#91) and the reference's `assert`/restore
50
+ // pair. A trapping realloc skips the restore exactly as the reference
51
+ // does; the host-boundary unwind and instance poisoning handle the rest.
52
+ const ptr = realloc(old, oldByteLength, alignment, newByteLength);
53
+ this.inst.mayLeave = true;
54
+ return ptr;
38
55
  }
39
56
  allocate(alignment, byteLength) {
40
57
  return this.reallocate(0, 0, alignment, byteLength);
@@ -25,7 +25,7 @@ export { COPY_URL, RUNTIME_VERSION } from "./copy.js";
25
25
  // The A9 vocabulary, re-exported unchanged: embedder code needs no import
26
26
  // change, and consumers that want the multi-copy-robust spellings get them
27
27
  // from the same module they already import.
28
- export { copyCensus, DROPPED, ERROR_CONTEXT, FUTURE, hasBrand, INVALID_HANDLE, isDroppedError, isInvalidHandleError, isPeerTrappedError, isStreamProducerError, isSuspending, isTrap, isComponentException, PEER_TRAPPED, PROTOCOL_GENERATION, registerRuntimeCopy, RESOURCE_STATE, runtimeCopies, STREAM, STREAM_PRODUCER, SUSPENDING, TRAP, COMPONENT_EXCEPTION, } from "@polyengine/protocol";
28
+ export { copyCensus, defineRealmLocal, DROPPED, ERROR_CONTEXT, fromCloneable, FUTURE, hasBrand, INVALID_HANDLE, isDroppedError, isInvalidHandleError, isPeerTrappedError, isRealmLocal, isStreamProducerError, isSuspending, isTrap, isComponentException, PEER_TRAPPED, PROTOCOL_GENERATION, REALM_LOCAL, registerRuntimeCopy, RESOURCE_STATE, runtimeCopies, STREAM, STREAM_PRODUCER, SUSPENDING, toCloneable, TRAP, COMPONENT_EXCEPTION, } from "@polyengine/protocol";
29
29
  export { artifactsFromEnvelope, instantiate, instantiateEmbedder, resolveArtifacts, } from "./instantiate.js";
30
30
  export { requiredImports } from "./imports.js";
31
31
  export { DroppedError, InvalidHandleError, NameCollisionError, PeerTrappedError, Trap, ComponentException, } from "./errors.js";
@@ -14,7 +14,7 @@
14
14
  // | host receives borrow<R> | wrapper valid for the call | instance, mapping kept |
15
15
  // | host passes own<R> | wrapper invalidated | instance registered |
16
16
  // | host passes borrow<R> | wrapper stays valid | rep reused/allocated |
17
- import { RESOURCE_STATE } from "@polyengine/protocol";
17
+ import { defineRealmLocal, RESOURCE_STATE } from "@polyengine/protocol";
18
18
  import { hostDtorCall } from "../exec/boundary.js";
19
19
  import { COPY_URL, describeCrossCopy } from "./copy.js";
20
20
  import { InvalidHandleError } from "./errors.js";
@@ -33,6 +33,16 @@ import { camelCase, pascalCase } from "./casing.js";
33
33
  const STATE = RESOURCE_STATE;
34
34
  /** Base of every runtime-built guest-resource class. */
35
35
  export class GuestResource {
36
+ constructor() {
37
+ // A20 (contracts/embedder-api.md §"Realm boundaries and
38
+ // structured-clone-safe forms"; issue #131): guest-resource wrappers are
39
+ // realm-local by principle (their machinery lives in the minting
40
+ // copy's tables, issue #129's identity rule) — the pill makes a raw
41
+ // structuredClone/postMessage of one throw instead of husking. NOTE:
42
+ // `makeWrapper` below mints via `Object.create`, which bypasses this
43
+ // constructor entirely — it installs the pill itself.
44
+ defineRealmLocal(this);
45
+ }
36
46
  /** Drop the handle (alias of `[Symbol.dispose]`, so TS `using` works). */
37
47
  drop() {
38
48
  dropWrapper(this);
@@ -313,6 +323,9 @@ export function makeWrapper(
313
323
  // deno-lint-ignore no-explicit-any
314
324
  cls, rep, rt, owns) {
315
325
  const w = Object.create(cls.prototype);
326
+ // A20: `Object.create` bypasses `GuestResource`'s constructor, so the
327
+ // realm-local pill is installed explicitly here (see that constructor).
328
+ defineRealmLocal(w);
316
329
  initWrapper(w, {
317
330
  rep,
318
331
  valid: true,
@@ -10,7 +10,7 @@
10
10
  import { despecialize } from "../cabi/types.js";
11
11
  import { hostFuture, hostFutureFor, hostStream, hostStreamFor, } from "../exec/host_streams.js";
12
12
  import { CopyResult, dropSharedForTeardown, poisonFailureOf, } from "../task/mod.js";
13
- import { defineBrand, ERROR_CONTEXT, FUTURE, hasBrand, isStreamProducerError, STREAM, StreamProducerError, } from "@polyengine/protocol";
13
+ import { defineBrand, defineRealmLocal, ERROR_CONTEXT, FUTURE, hasBrand, isStreamProducerError, STREAM, StreamProducerError, } from "@polyengine/protocol";
14
14
  import { describeCrossCopy } from "./copy.js";
15
15
  import { DroppedError, PeerTrappedError } from "./errors.js";
16
16
  // `StreamProducerError`'s canonical definition moved to `@polyengine/protocol`
@@ -102,6 +102,11 @@ export class Stream {
102
102
  constructor(host, codec) {
103
103
  this.#host = host;
104
104
  this.#codec = codec;
105
+ // A20 (contracts/embedder-api.md §"Realm boundaries and
106
+ // structured-clone-safe forms"; issue #131): the realm-local pill —
107
+ // stateful handles must fail loud (DataCloneError) at a raw
108
+ // structuredClone/postMessage instead of husking silently.
109
+ defineRealmLocal(this);
105
110
  }
106
111
  /** Wrap a stream value that was lifted out of a guest. */
107
112
  static fromLifted(value, codec) {
@@ -295,6 +300,8 @@ export class StreamWriter {
295
300
  #stream;
296
301
  constructor(stream) {
297
302
  this.#stream = stream;
303
+ // A20 realm-local pill (see Stream's constructor above for rationale).
304
+ defineRealmLocal(this);
298
305
  }
299
306
  /**
300
307
  * Offer values; resolves with how many the reader took.
@@ -376,6 +383,8 @@ export class Future {
376
383
  this.#host = host;
377
384
  this.#hostP = hostP;
378
385
  this.#codec = codec;
386
+ // A20 realm-local pill (see Stream's constructor above for rationale).
387
+ defineRealmLocal(this);
379
388
  }
380
389
  static fromLifted(value, codec) {
381
390
  const h = hostFutureFor(value);
@@ -527,6 +536,12 @@ export class ErrorContext {
527
536
  constructor(internal) {
528
537
  this.internal = internal;
529
538
  this.message = internal.debugMessage;
539
+ // A20 realm-local pill (see Stream's constructor above for rationale).
540
+ // Note: envelope-encodable brands take precedence over the pill at
541
+ // toCloneable time (ErrorContext carries both ERROR_CONTEXT and the
542
+ // pill; it encodes) — the pill here is only the backstop for raw
543
+ // structuredClone/postMessage that skips toCloneable.
544
+ defineRealmLocal(this);
530
545
  }
531
546
  }
532
547
  // A9 brands (contracts/embedder-api.md §"Module identity"): the three
@@ -263,11 +263,20 @@ export function fromHost(v, t, o, inOption = false) {
263
263
  }
264
264
  if (v instanceof InternalErrorContext)
265
265
  return v;
266
- // Branded but not ours (amendment A9): an `ErrorContext` minted by
267
- // another runtime copy. It is stateful (it lives in that copy's handle
268
- // table), so it can never be lowered here but "recognized and named"
269
- // beats the generic "expected an ErrorContext" that sent issue #83
270
- // hunting in the wrong direction.
266
+ // A20 (contracts/embedder-api.md §"Error-context is message-valued";
267
+ // issue #131; definitions.py an error-context's state is exactly
268
+ // its debug message): a branded carrier of a string `message`, from
269
+ // any copy (or hand-rolled), is accepted by minting a FRESH local
270
+ // context never "the same" one, since there is nothing to alias.
271
+ if (hasBrand(v, ERROR_CONTEXT) &&
272
+ typeof v.message === "string") {
273
+ return new InternalErrorContext(v.message);
274
+ }
275
+ // Branded but no string `message` (amendment A9, superseded above only
276
+ // for the message-valued case): a genuinely foreign stateful handle —
277
+ // it lives in another copy's handle table, so it can never be lowered
278
+ // here — but "recognized and named" beats the generic "expected an
279
+ // ErrorContext" that sent issue #83 hunting in the wrong direction.
271
280
  if (hasBrand(v, ERROR_CONTEXT)) {
272
281
  throw new TypeError(`${o.where}: ${describeCrossCopy("this error-context")}`);
273
282
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polyengine/runtime",
3
- "version": "0.4.0-pre.g28b13d2",
3
+ "version": "0.4.0-pre.g7fb65ed",
4
4
  "description": "A WebAssembly Component Model host for JavaScript engines: plan executor, canonical ABI, 0.3 task scheduler, JSPI bridge, and embedder API.",
5
5
  "homepage": "https://github.com/polymorph-components/polyengine#readme",
6
6
  "repository": {
@@ -49,10 +49,13 @@ export declare class LiftLowerContext {
49
49
  borrowScope: SubtaskBorrowScope | TaskBorrowScope | null;
50
50
  constructor(opts: LiftLowerOptions, inst?: ComponentInstanceLike | null, borrowScope?: SubtaskBorrowScope | TaskBorrowScope | null);
51
51
  /**
52
- * definitions.py `LiftLowerContext.reallocate` routes the call through
53
- * canon_lift (reentrance bookkeeping and may_leave toggling around a
54
- * guest-side realloc export). v1 simplification: call the provided realloc
55
- * directly. The full path returns with the task machinery.
52
+ * definitions.py `LiftLowerContext.reallocate`: the guest's realloc runs
53
+ * with `may_leave` cleared, so a realloc that lowers an import traps
54
+ * (`canon_lower`'s `trap_if(not ...may_leave)`, implemented here by
55
+ * exec/boundary.ts `createLoweredImport`). That bracket is implemented
56
+ * below. What remains deferred is only the reference's routing of the call
57
+ * through `canon_lift`; upstream component-model PR #705 removes that
58
+ * routing, leaving this bracket as the whole story. polyengine issue #147.
56
59
  */
57
60
  reallocate(old: number, oldByteLength: number, alignment: number, newByteLength: number): number;
58
61
  allocate(alignment: number, byteLength: number): number;
@@ -1,5 +1,5 @@
1
1
  export { COPY_URL, RUNTIME_VERSION } from "./copy.js";
2
- export { copyCensus, DROPPED, ERROR_CONTEXT, FUTURE, hasBrand, INVALID_HANDLE, isDroppedError, isInvalidHandleError, isPeerTrappedError, isStreamProducerError, isSuspending, isTrap, isComponentException, PEER_TRAPPED, PROTOCOL_GENERATION, registerRuntimeCopy, RESOURCE_STATE, type RuntimeCopy, runtimeCopies, STREAM, STREAM_PRODUCER, SUSPENDING, TRAP, COMPONENT_EXCEPTION, } from "@polyengine/protocol";
2
+ export { copyCensus, defineRealmLocal, DROPPED, ERROR_CONTEXT, fromCloneable, FUTURE, hasBrand, INVALID_HANDLE, isDroppedError, isInvalidHandleError, isPeerTrappedError, isRealmLocal, isStreamProducerError, isSuspending, isTrap, isComponentException, PEER_TRAPPED, PROTOCOL_GENERATION, REALM_LOCAL, registerRuntimeCopy, RESOURCE_STATE, type RuntimeCopy, runtimeCopies, STREAM, STREAM_PRODUCER, SUSPENDING, toCloneable, TRAP, COMPONENT_EXCEPTION, } from "@polyengine/protocol";
3
3
  export { artifactsFromEnvelope, type ComponentArtifacts, type EmbedderInstance, type EmbedderOptions, type InstantiateSource, type UntranslatedArtifacts, instantiate, instantiateEmbedder, resolveArtifacts, } from "./instantiate.js";
4
4
  export { type FuncSummary, type ImportLeaf, type PlanLike, requiredImports } from "./imports.js";
5
5
  export { DroppedError, InvalidHandleError, NameCollisionError, PeerTrappedError, Trap, ComponentException, } from "./errors.js";
@@ -48,6 +48,7 @@ interface WrapperState {
48
48
  /** Base of every runtime-built guest-resource class. */
49
49
  export declare class GuestResource {
50
50
  [STATE]: WrapperState;
51
+ constructor();
51
52
  /** Drop the handle (alias of `[Symbol.dispose]`, so TS `using` works). */
52
53
  drop(): void;
53
54
  [Symbol.dispose](): void;