@polyengine/runtime 0.1.0-pre.g633468a

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.
Files changed (126) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +23 -0
  3. package/esm/cabi/async_values.js +162 -0
  4. package/esm/cabi/bulk_lists.js +198 -0
  5. package/esm/cabi/context.js +42 -0
  6. package/esm/cabi/flatten.js +145 -0
  7. package/esm/cabi/float.js +67 -0
  8. package/esm/cabi/handles.js +253 -0
  9. package/esm/cabi/layout.js +149 -0
  10. package/esm/cabi/lift.js +196 -0
  11. package/esm/cabi/load.js +146 -0
  12. package/esm/cabi/lower.js +141 -0
  13. package/esm/cabi/memory.js +182 -0
  14. package/esm/cabi/mod.js +22 -0
  15. package/esm/cabi/store.js +186 -0
  16. package/esm/cabi/strings.js +336 -0
  17. package/esm/cabi/trap.js +38 -0
  18. package/esm/cabi/types.js +264 -0
  19. package/esm/cabi/values.js +64 -0
  20. package/esm/cache/core.js +156 -0
  21. package/esm/cache/dir.js +170 -0
  22. package/esm/cache/mod.js +4 -0
  23. package/esm/cache/web.js +136 -0
  24. package/esm/digest/digest.js +332 -0
  25. package/esm/digest/mod.js +3 -0
  26. package/esm/digest/verify.js +129 -0
  27. package/esm/embedder/casing.js +56 -0
  28. package/esm/embedder/copy.js +42 -0
  29. package/esm/embedder/errors.js +26 -0
  30. package/esm/embedder/imports.js +63 -0
  31. package/esm/embedder/instantiate.js +978 -0
  32. package/esm/embedder/mod.js +40 -0
  33. package/esm/embedder/resources.js +406 -0
  34. package/esm/embedder/streams.js +770 -0
  35. package/esm/embedder/values.js +450 -0
  36. package/esm/embedder/version.js +273 -0
  37. package/esm/exec/boundary.js +1902 -0
  38. package/esm/exec/executor.js +1004 -0
  39. package/esm/exec/host_streams.js +818 -0
  40. package/esm/exec/mod.js +4 -0
  41. package/esm/intrinsics/async_builtins.js +510 -0
  42. package/esm/intrinsics/context.js +90 -0
  43. package/esm/intrinsics/errors.js +13 -0
  44. package/esm/intrinsics/fact_calls.js +865 -0
  45. package/esm/intrinsics/mod.js +564 -0
  46. package/esm/intrinsics/stream_builtins.js +578 -0
  47. package/esm/intrinsics/transcode.js +443 -0
  48. package/esm/jspi/bridge.js +579 -0
  49. package/esm/jspi/mechanics.js +89 -0
  50. package/esm/jspi/mod.js +5 -0
  51. package/esm/jspi/suspending.js +15 -0
  52. package/esm/jspi/types.js +29 -0
  53. package/esm/package.json +3 -0
  54. package/esm/plan/format.js +5 -0
  55. package/esm/plan/loader.js +657 -0
  56. package/esm/plan/mod.js +3 -0
  57. package/esm/shim/mod.js +2 -0
  58. package/esm/shim/translator.js +94 -0
  59. package/esm/task/mod.js +483 -0
  60. package/esm/task/scheduler.js +1028 -0
  61. package/esm/task/streams.js +786 -0
  62. package/esm/task/subtask.js +135 -0
  63. package/esm/task/thread.js +255 -0
  64. package/esm/task/waitable.js +144 -0
  65. package/package.json +91 -0
  66. package/types/cabi/async_values.d.ts +35 -0
  67. package/types/cabi/bulk_lists.d.ts +18 -0
  68. package/types/cabi/context.d.ts +59 -0
  69. package/types/cabi/flatten.d.ts +14 -0
  70. package/types/cabi/float.d.ts +14 -0
  71. package/types/cabi/handles.d.ts +70 -0
  72. package/types/cabi/layout.d.ts +13 -0
  73. package/types/cabi/lift.d.ts +25 -0
  74. package/types/cabi/load.d.ts +14 -0
  75. package/types/cabi/lower.d.ts +10 -0
  76. package/types/cabi/memory.d.ts +58 -0
  77. package/types/cabi/mod.d.ts +15 -0
  78. package/types/cabi/store.d.ts +12 -0
  79. package/types/cabi/strings.d.ts +23 -0
  80. package/types/cabi/trap.d.ts +11 -0
  81. package/types/cabi/types.d.ts +206 -0
  82. package/types/cabi/values.d.ts +5 -0
  83. package/types/cache/core.d.ts +97 -0
  84. package/types/cache/dir.d.ts +6 -0
  85. package/types/cache/mod.d.ts +3 -0
  86. package/types/cache/web.d.ts +10 -0
  87. package/types/digest/digest.d.ts +17 -0
  88. package/types/digest/mod.d.ts +2 -0
  89. package/types/digest/verify.d.ts +48 -0
  90. package/types/embedder/casing.d.ts +40 -0
  91. package/types/embedder/copy.d.ts +24 -0
  92. package/types/embedder/errors.d.ts +11 -0
  93. package/types/embedder/imports.d.ts +47 -0
  94. package/types/embedder/instantiate.d.ts +88 -0
  95. package/types/embedder/mod.d.ts +11 -0
  96. package/types/embedder/resources.d.ts +158 -0
  97. package/types/embedder/streams.d.ts +202 -0
  98. package/types/embedder/values.d.ts +70 -0
  99. package/types/embedder/version.d.ts +85 -0
  100. package/types/exec/boundary.d.ts +360 -0
  101. package/types/exec/executor.d.ts +125 -0
  102. package/types/exec/host_streams.d.ts +165 -0
  103. package/types/exec/mod.d.ts +3 -0
  104. package/types/intrinsics/async_builtins.d.ts +69 -0
  105. package/types/intrinsics/context.d.ts +28 -0
  106. package/types/intrinsics/errors.d.ts +5 -0
  107. package/types/intrinsics/fact_calls.d.ts +120 -0
  108. package/types/intrinsics/mod.d.ts +187 -0
  109. package/types/intrinsics/stream_builtins.d.ts +113 -0
  110. package/types/intrinsics/transcode.d.ts +21 -0
  111. package/types/jspi/bridge.d.ts +227 -0
  112. package/types/jspi/mechanics.d.ts +50 -0
  113. package/types/jspi/mod.d.ts +3 -0
  114. package/types/jspi/suspending.d.ts +1 -0
  115. package/types/jspi/types.d.ts +26 -0
  116. package/types/plan/format.d.ts +369 -0
  117. package/types/plan/loader.d.ts +113 -0
  118. package/types/plan/mod.d.ts +2 -0
  119. package/types/shim/mod.d.ts +1 -0
  120. package/types/shim/translator.d.ts +55 -0
  121. package/types/task/mod.d.ts +257 -0
  122. package/types/task/scheduler.d.ts +421 -0
  123. package/types/task/streams.d.ts +370 -0
  124. package/types/task/subtask.d.ts +96 -0
  125. package/types/task/thread.d.ts +73 -0
  126. package/types/task/waitable.d.ts +67 -0
@@ -0,0 +1,145 @@
1
+ // Flattening (definitions.py `## Flattening`): component function types to
2
+ // core function signatures, and component value types to flat core types.
3
+ import { despecialize, discriminantType, } from "./types.js";
4
+ import { requireMemory } from "./context.js";
5
+ export const MAX_FLAT_PARAMS = 16;
6
+ export const MAX_FLAT_ASYNC_PARAMS = 4;
7
+ // Mutable to mirror run_tests.py toggling definitions.MAX_FLAT_RESULTS.
8
+ export let MAX_FLAT_RESULTS = 1;
9
+ // Test-only mirror of run_tests.py's constant toggling; not for production
10
+ // use (naming convention: see `schedulerSeedForTesting`).
11
+ export function setMaxFlatResultsForTesting(n) {
12
+ const prev = MAX_FLAT_RESULTS;
13
+ MAX_FLAT_RESULTS = n;
14
+ return prev;
15
+ }
16
+ export function flattenFunctype(opts, ft, context) {
17
+ let flatParams = flattenTypes(ft.params, opts);
18
+ let flatResults = flattenTypes(ft.results, opts);
19
+ if (!opts.async_) {
20
+ if (flatParams.length > MAX_FLAT_PARAMS) {
21
+ flatParams = [requireMemory(opts).ptrType()];
22
+ }
23
+ if (flatResults.length > MAX_FLAT_RESULTS) {
24
+ switch (context) {
25
+ case "lift":
26
+ flatResults = [requireMemory(opts).ptrType()];
27
+ break;
28
+ case "lower":
29
+ flatParams = [...flatParams, requireMemory(opts).ptrType()];
30
+ flatResults = [];
31
+ break;
32
+ }
33
+ }
34
+ return { params: flatParams, results: flatResults };
35
+ }
36
+ else {
37
+ switch (context) {
38
+ case "lift":
39
+ if (flatParams.length > MAX_FLAT_PARAMS) {
40
+ flatParams = [requireMemory(opts).ptrType()];
41
+ }
42
+ if (opts.callback) {
43
+ flatResults = ["i32"];
44
+ }
45
+ else {
46
+ flatResults = [];
47
+ }
48
+ break;
49
+ case "lower":
50
+ if (flatParams.length > MAX_FLAT_ASYNC_PARAMS) {
51
+ flatParams = [requireMemory(opts).ptrType()];
52
+ }
53
+ if (flatResults.length > 0) {
54
+ flatParams = [...flatParams, requireMemory(opts).ptrType()];
55
+ }
56
+ flatResults = ["i32"];
57
+ break;
58
+ }
59
+ return { params: flatParams, results: flatResults };
60
+ }
61
+ }
62
+ export function flattenTypes(ts, opts) {
63
+ return ts.flatMap((t) => flattenType(t, opts));
64
+ }
65
+ export function flattenType(t, opts) {
66
+ const d = despecialize(t);
67
+ switch (d.kind) {
68
+ case "bool":
69
+ case "u8":
70
+ case "u16":
71
+ case "u32":
72
+ case "s8":
73
+ case "s16":
74
+ case "s32":
75
+ case "char":
76
+ return ["i32"];
77
+ case "s64":
78
+ case "u64":
79
+ return ["i64"];
80
+ case "f32":
81
+ return ["f32"];
82
+ case "f64":
83
+ return ["f64"];
84
+ case "string": {
85
+ const pt = requireMemory(opts).ptrType();
86
+ return [pt, pt];
87
+ }
88
+ case "error-context":
89
+ return ["i32"];
90
+ case "list":
91
+ return flattenList(d.element, d.length ?? null, opts);
92
+ case "record":
93
+ return flattenRecord(d.fields, opts);
94
+ case "variant":
95
+ return flattenVariant(d.cases, opts);
96
+ case "flags":
97
+ return ["i32"];
98
+ case "own":
99
+ case "borrow":
100
+ case "stream":
101
+ case "future":
102
+ return ["i32"];
103
+ }
104
+ }
105
+ export function flattenList(elemType, maybeLength, opts) {
106
+ if (maybeLength !== null) {
107
+ const flat = [];
108
+ const one = flattenType(elemType, opts);
109
+ for (let i = 0; i < maybeLength; i++)
110
+ flat.push(...one);
111
+ return flat;
112
+ }
113
+ const pt = requireMemory(opts).ptrType();
114
+ return [pt, pt];
115
+ }
116
+ export function flattenRecord(fields, opts) {
117
+ const flat = [];
118
+ for (const f of fields)
119
+ flat.push(...flattenType(f.type, opts));
120
+ return flat;
121
+ }
122
+ export function flattenVariant(cases, opts) {
123
+ const flat = [];
124
+ for (const c of cases) {
125
+ if (c.type !== null) {
126
+ flattenType(c.type, opts).forEach((ft, i) => {
127
+ if (i < flat.length) {
128
+ flat[i] = join(flat[i], ft);
129
+ }
130
+ else {
131
+ flat.push(ft);
132
+ }
133
+ });
134
+ }
135
+ }
136
+ return [...flattenType(discriminantType(cases), opts), ...flat];
137
+ }
138
+ export function join(a, b) {
139
+ if (a === b)
140
+ return a;
141
+ if ((a === "i32" && b === "f32") || (a === "f32" && b === "i32")) {
142
+ return "i32";
143
+ }
144
+ return "i64";
145
+ }
@@ -0,0 +1,67 @@
1
+ // Float bit handling (definitions.py NaN canonicalization and
2
+ // reinterpretation helpers).
3
+ //
4
+ // The reference has a DETERMINISTIC_PROFILE toggle; this port implements only
5
+ // the deterministic profile (NaNs canonicalized on lift and on
6
+ // store/lower). JS forces this: NaN payloads are not reliably observable or
7
+ // preservable through JS numbers, so the "scramble" branch of the reference
8
+ // is not implementable — and run_tests.py itself runs the deterministic
9
+ // profile. Recorded as a decision in runtime/README.md.
10
+ export const CANONICAL_FLOAT32_NAN = 0x7fc00000;
11
+ export const CANONICAL_FLOAT64_NAN = 0x7ff8000000000000n;
12
+ const scratch = new DataView(new ArrayBuffer(8));
13
+ export function coreF32ReinterpretI32(i) {
14
+ scratch.setUint32(0, i, true);
15
+ return scratch.getFloat32(0, true);
16
+ }
17
+ export function coreF64ReinterpretI64(i) {
18
+ scratch.setBigUint64(0, i, true);
19
+ return scratch.getFloat64(0, true);
20
+ }
21
+ export function coreI32ReinterpretF32(f) {
22
+ scratch.setFloat32(0, f, true);
23
+ return scratch.getUint32(0, true);
24
+ }
25
+ export function coreI64ReinterpretF64(f) {
26
+ scratch.setFloat64(0, f, true);
27
+ return scratch.getBigUint64(0, true);
28
+ }
29
+ export function canonicalizeNan32(f) {
30
+ if (Number.isNaN(f))
31
+ return coreF32ReinterpretI32(CANONICAL_FLOAT32_NAN);
32
+ return f;
33
+ }
34
+ export function canonicalizeNan64(f) {
35
+ if (Number.isNaN(f))
36
+ return coreF64ReinterpretI64(CANONICAL_FLOAT64_NAN);
37
+ return f;
38
+ }
39
+ export function decodeI32AsFloat(i) {
40
+ return canonicalizeNan32(coreF32ReinterpretI32(i));
41
+ }
42
+ export function decodeI64AsFloat(i) {
43
+ return canonicalizeNan64(coreF64ReinterpretI64(i));
44
+ }
45
+ // definitions.py maybe_scramble_nan{32,64}: deterministic profile only —
46
+ // write the canonical NaN bit pattern explicitly, never relying on engine
47
+ // NaN propagation through DataView.
48
+ export function maybeScrambleNan32(f) {
49
+ if (Number.isNaN(f))
50
+ return coreF32ReinterpretI32(CANONICAL_FLOAT32_NAN);
51
+ return f;
52
+ }
53
+ export function maybeScrambleNan64(f) {
54
+ if (Number.isNaN(f))
55
+ return coreF64ReinterpretI64(CANONICAL_FLOAT64_NAN);
56
+ return f;
57
+ }
58
+ export function encodeFloatAsI32(f) {
59
+ if (Number.isNaN(f))
60
+ return CANONICAL_FLOAT32_NAN;
61
+ return coreI32ReinterpretF32(f);
62
+ }
63
+ export function encodeFloatAsI64(f) {
64
+ if (Number.isNaN(f))
65
+ return CANONICAL_FLOAT64_NAN;
66
+ return coreI64ReinterpretF64(f);
67
+ }
@@ -0,0 +1,253 @@
1
+ // Handle tables and resource handles (definitions.py `### Table State`,
2
+ // `### Resource State`, `canon resource.{new,drop,rep}`, and the
3
+ // own/borrow lift/lower functions).
4
+ //
5
+ // The Table and ResourceHandle mechanics are pure and ported fully. What is
6
+ // simplified here (pending the task machinery):
7
+ // - canon_resource_* take the instance explicitly instead of reading
8
+ // current_instance() from the running thread;
9
+ // - canon_resource_drop routes the dtor through `callDtorGated` below,
10
+ // which reconstructs the reference's store.lift/store.lower bracket
11
+ // (may_enter gating + trap poisoning) around the destructor call (#85).
12
+ // Host-initiated drops do NOT come here: they run the dtor through the
13
+ // real lift harness (`hostDtorCall`, exec/boundary.ts) — see #160.
14
+ import { assert_, Trap, trap, trapIf } from "./trap.js";
15
+ import { NeedsJspi, notifyInstancePoisoned, PendingCapability, withPoisonCause, } from "../task/scheduler.js";
16
+ export class Table {
17
+ static MAX_LENGTH = 2 ** 28 - 1;
18
+ array = [null];
19
+ free = [];
20
+ get(i) {
21
+ trapIf(i >= this.array.length, "table index out of range");
22
+ trapIf(this.array[i] === null, "table entry empty");
23
+ return this.array[i];
24
+ }
25
+ add(e) {
26
+ let i;
27
+ if (this.free.length > 0) {
28
+ i = this.free.pop();
29
+ assert_(this.array[i] === null);
30
+ this.array[i] = e;
31
+ }
32
+ else {
33
+ i = this.array.length;
34
+ trapIf(i > Table.MAX_LENGTH, "table full");
35
+ this.array.push(e);
36
+ }
37
+ return i;
38
+ }
39
+ remove(i) {
40
+ const e = this.get(i);
41
+ this.array[i] = null;
42
+ this.free.push(i);
43
+ return e;
44
+ }
45
+ *[Symbol.iterator]() {
46
+ for (const e of this.array) {
47
+ if (e !== null)
48
+ yield e;
49
+ }
50
+ }
51
+ }
52
+ export class ResourceHandle {
53
+ rt;
54
+ rep;
55
+ own;
56
+ borrowScope;
57
+ numLends = 0;
58
+ constructor(rt, rep, own, borrowScope = null) {
59
+ this.rt = rt;
60
+ this.rep = rep;
61
+ this.own = own;
62
+ this.borrowScope = borrowScope;
63
+ }
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // own/borrow lift & lower (called from load/store/lift/lower dispatchers)
67
+ // ---------------------------------------------------------------------------
68
+ function requireInst(cx) {
69
+ assert_(cx.inst !== null, "context requires a component instance");
70
+ return cx.inst;
71
+ }
72
+ export function liftOwn(cx, i, t) {
73
+ const h = requireInst(cx).handles.remove(i);
74
+ trapIf(!(h instanceof ResourceHandle), "not a resource handle");
75
+ const rh = h;
76
+ trapIf(rh.rt !== t.rt, "resource type mismatch");
77
+ trapIf(rh.numLends !== 0, "handle still lent out");
78
+ trapIf(!rh.own, "expected own handle");
79
+ return rh.rep;
80
+ }
81
+ export function liftBorrow(cx, i, t) {
82
+ const scope = cx.borrowScope;
83
+ assert_(scope !== null && typeof scope.addLender === "function", "lifting a borrow requires a subtask borrow scope");
84
+ const h = requireInst(cx).handles.get(i);
85
+ trapIf(!(h instanceof ResourceHandle), "not a resource handle");
86
+ const rh = h;
87
+ trapIf(rh.rt !== t.rt, "resource type mismatch");
88
+ scope.addLender(rh);
89
+ return rh.rep;
90
+ }
91
+ export function lowerOwn(cx, rep, t) {
92
+ const h = new ResourceHandle(t.rt, rep, true);
93
+ return requireInst(cx).handles.add(h);
94
+ }
95
+ export function lowerBorrow(cx, rep, t) {
96
+ const scope = cx.borrowScope;
97
+ assert_(scope !== null && typeof scope.numBorrows === "number", "lowering a borrow requires a task borrow scope");
98
+ if (cx.inst !== null && cx.inst === t.rt.impl) {
99
+ return rep;
100
+ }
101
+ const h = new ResourceHandle(t.rt, rep, false, scope);
102
+ scope.numBorrows += 1;
103
+ return requireInst(cx).handles.add(h);
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // canon resource.new / resource.drop / resource.rep
107
+ // (instance passed explicitly; see module comment)
108
+ // ---------------------------------------------------------------------------
109
+ export function canonResourceNew(inst, rt, rep) {
110
+ trapIf(!inst.mayLeave, "may_leave violation");
111
+ const h = new ResourceHandle(rt, rep, true);
112
+ return inst.handles.add(h);
113
+ }
114
+ function asGate(x) {
115
+ if (x === null || typeof x !== "object")
116
+ return null;
117
+ const g = x;
118
+ return typeof g.mayEnterFrom === "function" &&
119
+ typeof g.enterFrom === "function" && typeof g.leaveTo === "function"
120
+ ? x
121
+ : null;
122
+ }
123
+ function isThenable(v) {
124
+ return typeof v === "object" && v !== null &&
125
+ typeof v.then === "function";
126
+ }
127
+ /**
128
+ * Invoke a resource destructor through the reference's entry bracket.
129
+ *
130
+ * definitions.py `canon_resource_drop` (line 2319) does not call `rt.dtor`
131
+ * directly. It builds the dtor into a function instance and calls it through
132
+ * `Store.lift` / `Store.lower` (lines 2330-2333):
133
+ *
134
+ * ```python
135
+ * dtor = rt.dtor or (lambda rep: [])
136
+ * callee = inst.store.lift(dtor, ft, opts, rt.impl)
137
+ * caller = inst.store.lower(callee, ft, opts, inst)
138
+ * caller([h.rep])
139
+ * ```
140
+ *
141
+ * so the dtor inherits `Store.lift`'s gate verbatim (lines 579-584):
142
+ * `trap_if(not inst.may_enter_from(caller))`, `enter_from(caller)`, the call,
143
+ * then `leave_to(caller)` — which a trap skips, leaving the *implementing*
144
+ * instance permanently unenterable (poisoned).
145
+ *
146
+ * Two consequences that are easy to get wrong, both taken from the reference
147
+ * rather than from intuition:
148
+ *
149
+ * - the bracket runs even when `rt.dtor is None` (the `or (lambda rep: [])`
150
+ * above), so a dtor-less resource whose impl instance is mid-execution is
151
+ * still a trap. `may_enter_from`/`enter_from` walk `entering_set(caller)`
152
+ * (line 230), which is empty when the caller *is* the implementing
153
+ * instance — that, not a special case, is the same-instance exemption:
154
+ * a component dropping a handle to its own resource never traps.
155
+ * - poisoning applies to `rt.impl`, not to the dropping instance. The
156
+ * dropper's own bracket (its `Store.lift` frame) is broken by the same
157
+ * propagating trap at its own level; here only the callee is retired.
158
+ *
159
+ * Capability signals (`NeedsJspi`, `PendingCapability`) are not traps — see
160
+ * `isCapabilitySignal` in exec/boundary.ts — so they release the gate.
161
+ *
162
+ * SCOPE (#160): this is the **guest-initiated** path only. A guest-initiated
163
+ * drop must complete synchronously (the reference lifts the dtor with
164
+ * `async_ = False`), so a thenable here is a trap. The host-initiated path
165
+ * used to share this function with an `allowAsync` flag that held the entry
166
+ * bracket across the dtor's promise; it now goes through the full lift
167
+ * harness instead (`hostDtorCall` in exec/boundary.ts), which is what
168
+ * definitions.py actually does and what unwedges #160.
169
+ */
170
+ export function callDtorGated(rt, rep, caller) {
171
+ const impl = asGate(rt.impl);
172
+ // Always the raw synchronous dtor: `dtorHost` is the host path's lifted
173
+ // entry, which is not callable from inside a guest activation.
174
+ const dtorFn = rt.dtor;
175
+ // No gate available: an imported (host-implemented) resource has
176
+ // `impl === null` by construction (executor.ts `bindImportedResources`),
177
+ // and there is no component instance to gate entry into. Test doubles that
178
+ // supply a bare `{handles, mayLeave}` instance land here too.
179
+ if (impl === null) {
180
+ const r = dtorFn?.(rep);
181
+ trapIf(isThenable(r), "resource destructor did not complete synchronously");
182
+ return;
183
+ }
184
+ // definitions.py `entering_set` (line 230): `self_and_ancestors() -
185
+ // caller.self_and_ancestors()`. The caller is only meaningful when it is a
186
+ // real component instance; a host-initiated drop passes null, which is the
187
+ // reference's `caller = None` (Store.invoke).
188
+ const callerInst = asGate(caller) === null ? null : caller;
189
+ // A poisoned target's refusal names the original trap (polyengine#145).
190
+ if (!impl.mayEnterFrom(callerInst)) {
191
+ trap(withPoisonCause(impl, "cannot enter component instance"));
192
+ }
193
+ impl.enterFrom(callerInst);
194
+ const poison = (e) => {
195
+ if (e instanceof NeedsJspi || e instanceof PendingCapability) {
196
+ // Not a trap: the reference reaches `leave_to` on every execution these
197
+ // stand in for, so the instance stays enterable.
198
+ impl.leaveTo(callerInst);
199
+ return;
200
+ }
201
+ // `leave_to` is NOT reached (the gate stays taken, permanently), and the
202
+ // poisoned instance's live stream/future ends are retired (#66) through
203
+ // the same seam fact_calls.ts uses for its bracket-break sites.
204
+ notifyInstancePoisoned(impl, e);
205
+ };
206
+ let out;
207
+ try {
208
+ out = dtorFn?.(rep);
209
+ }
210
+ catch (e) {
211
+ poison(e);
212
+ throw e;
213
+ }
214
+ if (isThenable(out)) {
215
+ // A guest-initiated drop is lifted with `async_ = False`: the dtor must
216
+ // resolve before `canon_resource_drop` returns. Reaching here means the
217
+ // dtor's activation escaped, which is a trap that poisons the impl.
218
+ const e = new Trap("resource destructor did not complete synchronously");
219
+ poison(e);
220
+ throw e;
221
+ }
222
+ impl.leaveTo(callerInst);
223
+ }
224
+ export function canonResourceDrop(inst, rt, i) {
225
+ trapIf(!inst.mayLeave, "may_leave violation");
226
+ const h = inst.handles.remove(i);
227
+ trapIf(!(h instanceof ResourceHandle), "not a resource handle");
228
+ const rh = h;
229
+ trapIf(rh.rt !== rt, "resource type mismatch");
230
+ trapIf(rh.numLends !== 0, "handle still lent out");
231
+ if (rh.own) {
232
+ assert_(rh.borrowScope === null);
233
+ // definitions.py line 2326-2333: the dtor runs through the store's
234
+ // lift/lower bracket. SCOPE NOTE (#85): the call below is a JS frame
235
+ // inside the drop trampoline, so a *guest*-initiated drop whose dtor
236
+ // suspends traps under the JSPI frame rule. That is deterministic and
237
+ // loud, and routing guest-initiated dtor calls through generated wasm is
238
+ // explicitly out of scope for #85 (docs/architecture.md §5/§7 carry the
239
+ // known-limitation note).
240
+ callDtorGated(rt, rh.rep, inst);
241
+ }
242
+ else {
243
+ assert_(rh.borrowScope !== null);
244
+ rh.borrowScope.numBorrows -= 1;
245
+ }
246
+ }
247
+ export function canonResourceRep(inst, rt, i) {
248
+ const h = inst.handles.get(i);
249
+ trapIf(!(h instanceof ResourceHandle), "not a resource handle");
250
+ const rh = h;
251
+ trapIf(rh.rt !== rt, "resource type mismatch");
252
+ return rh.rep;
253
+ }
@@ -0,0 +1,149 @@
1
+ // Alignment and element size (definitions.py `## Alignment`, `## Element
2
+ // Size`): byte layout of component values in linear memory.
3
+ import { assert_ } from "./trap.js";
4
+ import { ptrSize } from "./memory.js";
5
+ import { despecialize, discriminantType, } from "./types.js";
6
+ export function alignTo(ptr, alignment) {
7
+ return Math.ceil(ptr / alignment) * alignment;
8
+ }
9
+ export function alignment(t, ptrType) {
10
+ const d = despecialize(t);
11
+ switch (d.kind) {
12
+ case "bool":
13
+ case "s8":
14
+ case "u8":
15
+ return 1;
16
+ case "s16":
17
+ case "u16":
18
+ return 2;
19
+ case "s32":
20
+ case "u32":
21
+ case "f32":
22
+ case "char":
23
+ return 4;
24
+ case "s64":
25
+ case "u64":
26
+ case "f64":
27
+ return 8;
28
+ case "string":
29
+ return ptrSize(ptrType);
30
+ case "error-context":
31
+ return 4;
32
+ case "list":
33
+ return alignmentList(d.element, d.length ?? null, ptrType);
34
+ case "record":
35
+ return alignmentRecord(d.fields, ptrType);
36
+ case "variant":
37
+ return alignmentVariant(d.cases, ptrType);
38
+ case "flags":
39
+ return alignmentFlags(d.labels);
40
+ case "own":
41
+ case "borrow":
42
+ case "stream":
43
+ case "future":
44
+ return 4;
45
+ }
46
+ }
47
+ export function alignmentList(elemType, maybeLength, ptrType) {
48
+ if (maybeLength !== null)
49
+ return alignment(elemType, ptrType);
50
+ return ptrSize(ptrType);
51
+ }
52
+ export function alignmentRecord(fields, ptrType) {
53
+ let a = 1;
54
+ for (const f of fields)
55
+ a = Math.max(a, alignment(f.type, ptrType));
56
+ return a;
57
+ }
58
+ export function alignmentVariant(cases, ptrType) {
59
+ return Math.max(alignment(discriminantType(cases), ptrType), maxCaseAlignment(cases, ptrType));
60
+ }
61
+ export function maxCaseAlignment(cases, ptrType) {
62
+ let a = 1;
63
+ for (const c of cases) {
64
+ if (c.type !== null)
65
+ a = Math.max(a, alignment(c.type, ptrType));
66
+ }
67
+ return a;
68
+ }
69
+ export function alignmentFlags(labels) {
70
+ const n = labels.length;
71
+ assert_(0 < n && n <= 32, "flags label count");
72
+ if (n <= 8)
73
+ return 1;
74
+ if (n <= 16)
75
+ return 2;
76
+ return 4;
77
+ }
78
+ export function elemSize(t, ptrType) {
79
+ const d = despecialize(t);
80
+ switch (d.kind) {
81
+ case "bool":
82
+ case "s8":
83
+ case "u8":
84
+ return 1;
85
+ case "s16":
86
+ case "u16":
87
+ return 2;
88
+ case "s32":
89
+ case "u32":
90
+ case "f32":
91
+ case "char":
92
+ return 4;
93
+ case "s64":
94
+ case "u64":
95
+ case "f64":
96
+ return 8;
97
+ case "string":
98
+ return 2 * ptrSize(ptrType);
99
+ case "error-context":
100
+ return 4;
101
+ case "list":
102
+ return elemSizeList(d.element, d.length ?? null, ptrType);
103
+ case "record":
104
+ return elemSizeRecord(d.fields, ptrType);
105
+ case "variant":
106
+ return elemSizeVariant(d.cases, ptrType);
107
+ case "flags":
108
+ return elemSizeFlags(d.labels);
109
+ case "own":
110
+ case "borrow":
111
+ case "stream":
112
+ case "future":
113
+ return 4;
114
+ }
115
+ }
116
+ export function elemSizeList(elemType, maybeLength, ptrType) {
117
+ if (maybeLength !== null)
118
+ return maybeLength * elemSize(elemType, ptrType);
119
+ return 2 * ptrSize(ptrType);
120
+ }
121
+ export function elemSizeRecord(fields, ptrType) {
122
+ let s = 0;
123
+ for (const f of fields) {
124
+ s = alignTo(s, alignment(f.type, ptrType));
125
+ s += elemSize(f.type, ptrType);
126
+ }
127
+ assert_(s > 0, "empty record");
128
+ return alignTo(s, alignmentRecord(fields, ptrType));
129
+ }
130
+ export function elemSizeVariant(cases, ptrType) {
131
+ let s = elemSize(discriminantType(cases), ptrType);
132
+ s = alignTo(s, maxCaseAlignment(cases, ptrType));
133
+ let cs = 0;
134
+ for (const c of cases) {
135
+ if (c.type !== null)
136
+ cs = Math.max(cs, elemSize(c.type, ptrType));
137
+ }
138
+ s += cs;
139
+ return alignTo(s, alignmentVariant(cases, ptrType));
140
+ }
141
+ export function elemSizeFlags(labels) {
142
+ const n = labels.length;
143
+ assert_(0 < n && n <= 32, "flags label count");
144
+ if (n <= 8)
145
+ return 1;
146
+ if (n <= 16)
147
+ return 2;
148
+ return 4;
149
+ }