@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,186 @@
1
+ // Storing component values into linear memory (definitions.py `## Storing`).
2
+ import { assert_, trapIf } from "./trap.js";
3
+ import { bytesOf, storeInt, storePtr } from "./memory.js";
4
+ import { tryStoreNumericList } from "./bulk_lists.js";
5
+ import { encodeFloatAsI32, encodeFloatAsI64 } from "./float.js";
6
+ import { alignment, alignTo, elemSize, elemSizeFlags, maxCaseAlignment, } from "./layout.js";
7
+ import { charToI32, REALLOC_I32_MAX, REALLOC_MISALIGNED, REALLOC_OOB, storeString, } from "./strings.js";
8
+ import { requireMemory } from "./context.js";
9
+ import { lowerBorrow, lowerOwn } from "./handles.js";
10
+ import { despecialize, discriminantType, } from "./types.js";
11
+ import { lowerErrorContext, lowerFuture, lowerStream, } from "./async_values.js";
12
+ export function store(cx, v, t, ptr) {
13
+ const mem = requireMemory(cx.opts);
14
+ assert_(ptr === alignTo(ptr, alignment(t, mem.ptrType())), "store misaligned");
15
+ assert_(ptr + elemSize(t, mem.ptrType()) <= mem.length, "store OOB");
16
+ const d = despecialize(t);
17
+ switch (d.kind) {
18
+ case "bool":
19
+ storeInt(mem, Number(Boolean(v)), ptr, 1);
20
+ return;
21
+ case "u8":
22
+ storeInt(mem, v, ptr, 1);
23
+ return;
24
+ case "u16":
25
+ storeInt(mem, v, ptr, 2);
26
+ return;
27
+ case "u32":
28
+ storeInt(mem, v, ptr, 4);
29
+ return;
30
+ case "u64":
31
+ storeInt(mem, v, ptr, 8);
32
+ return;
33
+ case "s8":
34
+ storeInt(mem, v, ptr, 1, true);
35
+ return;
36
+ case "s16":
37
+ storeInt(mem, v, ptr, 2, true);
38
+ return;
39
+ case "s32":
40
+ storeInt(mem, v, ptr, 4, true);
41
+ return;
42
+ case "s64":
43
+ storeInt(mem, v, ptr, 8, true);
44
+ return;
45
+ case "f32":
46
+ storeInt(mem, encodeFloatAsI32(v), ptr, 4);
47
+ return;
48
+ case "f64":
49
+ storeInt(mem, encodeFloatAsI64(v), ptr, 8);
50
+ return;
51
+ case "char":
52
+ storeInt(mem, charToI32(v), ptr, 4);
53
+ return;
54
+ case "string":
55
+ storeString(cx, v, ptr);
56
+ return;
57
+ case "error-context":
58
+ storeInt(mem, lowerErrorContext(cx, v), ptr, 4);
59
+ return;
60
+ case "list":
61
+ storeList(cx, v, ptr, d.element, d.length ?? null);
62
+ return;
63
+ case "record":
64
+ storeRecord(cx, v, ptr, d.fields);
65
+ return;
66
+ case "variant":
67
+ storeVariant(cx, v, ptr, d.cases);
68
+ return;
69
+ case "flags":
70
+ storeFlags(cx, v, ptr, d.labels);
71
+ return;
72
+ case "own":
73
+ storeInt(mem, lowerOwn(cx, v, d), ptr, 4);
74
+ return;
75
+ case "borrow":
76
+ storeInt(mem, lowerBorrow(cx, v, d), ptr, 4);
77
+ return;
78
+ case "stream":
79
+ storeInt(mem, lowerStream(cx, v, d), ptr, 4);
80
+ return;
81
+ case "future":
82
+ storeInt(mem, lowerFuture(cx, v, d), ptr, 4);
83
+ return;
84
+ }
85
+ }
86
+ export function storeList(cx, v, ptr, elemType, maybeLength) {
87
+ if (maybeLength !== null) {
88
+ assert_(maybeLength === v.length, "fixed-length list length mismatch");
89
+ storeListIntoValidRange(cx, v, ptr, elemType);
90
+ return;
91
+ }
92
+ const mem = requireMemory(cx.opts);
93
+ const [begin, length] = storeListIntoRange(cx, v, elemType);
94
+ storePtr(mem, begin, ptr);
95
+ storePtr(mem, length, ptr + mem.ptrSize());
96
+ }
97
+ export function storeListIntoRange(cx, v, elemType) {
98
+ const mem = requireMemory(cx.opts);
99
+ const byteLength = v.length * elemSize(elemType, mem.ptrType());
100
+ assert_(byteLength <= REALLOC_I32_MAX);
101
+ const align = alignment(elemType, mem.ptrType());
102
+ const ptr = cx.allocate(align, byteLength);
103
+ trapIf(ptr !== alignTo(ptr, align), REALLOC_MISALIGNED);
104
+ trapIf(ptr + byteLength > mem.length, REALLOC_OOB);
105
+ storeListIntoValidRange(cx, v, ptr, elemType);
106
+ return [ptr, v.length];
107
+ }
108
+ export function storeListIntoValidRange(cx, v, ptr, elemType) {
109
+ const mem = requireMemory(cx.opts);
110
+ const kind = despecialize(elemType).kind;
111
+ // docs/architecture.md §7: list<u8> is Uint8Array-shaped on the host, and
112
+ // both directions are bulk copies — this is the store-side mirror of
113
+ // load.ts `loadListFromValidRange`'s u8 fast path (issue #54: the
114
+ // per-element interpreted store cost ~45 ns/byte, capping async imports
115
+ // returning list<u8> at ~22 MB/s while the lift ran at memcpy speed).
116
+ if (kind === "u8") {
117
+ const dst = bytesOf(mem, ptr, v.length);
118
+ if (v instanceof Uint8Array) {
119
+ dst.set(v);
120
+ return;
121
+ }
122
+ // Plain-array sources (raw-layer embedders) keep the exact per-element
123
+ // semantics of `storeInt(…, 1)`: assert integer-ness, then mask mod 256
124
+ // (a Uint8Array element write and DataView.setUint8 wrap identically).
125
+ for (let i = 0; i < v.length; i++) {
126
+ const x = v[i];
127
+ assert_(typeof x === "number" && Number.isInteger(x), "int store");
128
+ dst[i] = x;
129
+ }
130
+ return;
131
+ }
132
+ // Other flat element types store bulk too (issue #67), preserving the
133
+ // per-element semantics exactly (same asserts, same wrap, canonical-NaN
134
+ // floats); falls through for compound types, char, and non-little-endian
135
+ // platforms.
136
+ if (tryStoreNumericList(mem, v, ptr, kind))
137
+ return;
138
+ const size = elemSize(elemType, mem.ptrType());
139
+ for (let i = 0; i < v.length; i++) {
140
+ store(cx, v[i], elemType, ptr + i * size);
141
+ }
142
+ }
143
+ export function storeRecord(cx, v, ptr, fields) {
144
+ const mem = requireMemory(cx.opts);
145
+ let p = ptr;
146
+ for (const f of fields) {
147
+ p = alignTo(p, alignment(f.type, mem.ptrType()));
148
+ store(cx, v[f.label], f.type, p);
149
+ p += elemSize(f.type, mem.ptrType());
150
+ }
151
+ }
152
+ /** definitions.py match_case: the value is a single-key object. */
153
+ export function matchCase(v, cases) {
154
+ const keys = Object.keys(v);
155
+ assert_(keys.length === 1, "variant value must have exactly one case");
156
+ const label = keys[0];
157
+ const matches = cases.flatMap((c, i) => (c.label === label ? [i] : []));
158
+ assert_(matches.length === 1, `variant case '${label}' not found`);
159
+ return [matches[0], v[label]];
160
+ }
161
+ export function storeVariant(cx, v, ptr, cases) {
162
+ const mem = requireMemory(cx.opts);
163
+ const [caseIndex, caseValue] = matchCase(v, cases);
164
+ const discSize = elemSize(discriminantType(cases), mem.ptrType());
165
+ storeInt(mem, caseIndex, ptr, discSize);
166
+ let p = ptr + discSize;
167
+ p = alignTo(p, maxCaseAlignment(cases, mem.ptrType()));
168
+ const c = cases[caseIndex];
169
+ if (c.type !== null) {
170
+ store(cx, caseValue, c.type, p);
171
+ }
172
+ }
173
+ export function storeFlags(cx, v, ptr, labels) {
174
+ const mem = requireMemory(cx.opts);
175
+ const i = packFlagsIntoInt(v, labels);
176
+ storeInt(mem, i, ptr, elemSizeFlags(labels));
177
+ }
178
+ export function packFlagsIntoInt(v, labels) {
179
+ let i = 0;
180
+ let shift = 0;
181
+ for (const l of labels) {
182
+ i = (i | ((v[l] ? 1 : 0) << shift)) >>> 0;
183
+ shift += 1;
184
+ }
185
+ return i;
186
+ }
@@ -0,0 +1,336 @@
1
+ // String and char lift/lower (definitions.py `load_string*`,
2
+ // `store_string*`, `convert_i32_to_char`, `char_to_i32`).
3
+ //
4
+ // Host-side strings are plain JS strings (docs/architecture.md §7). Two deliberate
5
+ // deviations from definitions.py, both recorded in runtime/README.md:
6
+ //
7
+ // 1. No encoding provenance. The reference represents a lifted string as
8
+ // (str, src_encoding, tagged_code_units) so that lowering can pick a
9
+ // same-encoding copy fast path. In this host, cross-component calls (and
10
+ // hence transcode fast paths) belong to FACT adapters (docs/architecture.md §4.1); the
11
+ // host boundary deals in JS strings only. Lowering therefore always treats
12
+ // the source as a UTF-16 code-unit sequence — exactly the reference's
13
+ // behavior for src_encoding='utf16' — because that is what a JS string is.
14
+ //
15
+ // 2. USVString replacement semantics (docs/architecture.md §7): a JS string containing
16
+ // lone surrogates is lowered as if each unpaired surrogate were U+FFFD
17
+ // (WebIDL USVString). The reference never sees unpaired surrogates because
18
+ // lifted Python strings are always well-formed; this only affects
19
+ // host-constructed strings.
20
+ import { assert_, trap, trapIf } from "./trap.js";
21
+ import { bytesOf, loadPtr, storeInt, trapIfRangeExceedsMemory, writeBytes, } from "./memory.js";
22
+ import { alignTo } from "./layout.js";
23
+ import { requireMemory } from "./context.js";
24
+ export const REALLOC_I32_MAX = 2 ** 32 - 1;
25
+ // Trap wording for realloc-return validation, matching wasmtime
26
+ // (`src/runtime/component/func/options.rs:175,185`) so the official suite's
27
+ // `values/realloc.wast` expectations match. Semantics are unchanged; only the
28
+ // text differs from the earlier hand-written wording.
29
+ export const REALLOC_MISALIGNED = "realloc return: result not aligned";
30
+ export const REALLOC_OOB = "realloc return: beyond end of memory";
31
+ export const MAX_STRING_BYTE_LENGTH = (1 << 28) - 1;
32
+ /** definitions.py utf16_tag: the high bit of a pointer-sized integer. */
33
+ export function utf16TagBig(ptrType) {
34
+ return 1n << BigInt((ptrType === "i32" ? 4 : 8) * 8 - 1);
35
+ }
36
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
37
+ const utf16Decoder = new TextDecoder("utf-16le", {
38
+ fatal: true,
39
+ ignoreBOM: true,
40
+ });
41
+ const utf8Encoder = new TextEncoder();
42
+ /** ISO-8859-1 (true latin1) decode. TextDecoder cannot be used: the WHATWG
43
+ * "latin1"/"iso-8859-1" labels alias windows-1252, which differs in
44
+ * 0x80..0x9F. Identity byte -> code point mapping, chunked. */
45
+ function latin1Decode(bytes) {
46
+ const chunkSize = 8192;
47
+ let s = "";
48
+ for (let i = 0; i < bytes.length; i += chunkSize) {
49
+ const chunk = bytes.subarray(i, Math.min(i + chunkSize, bytes.length));
50
+ s += String.fromCharCode(...chunk);
51
+ }
52
+ return s;
53
+ }
54
+ /** WebIDL USVString conversion: unpaired surrogates -> U+FFFD. */
55
+ export function toWellFormed(s) {
56
+ return s.toWellFormed();
57
+ }
58
+ export function encodeUtf16Le(s) {
59
+ const wf = toWellFormed(s);
60
+ const out = new Uint8Array(2 * wf.length);
61
+ const view = new DataView(out.buffer);
62
+ for (let i = 0; i < wf.length; i++) {
63
+ view.setUint16(2 * i, wf.charCodeAt(i), true);
64
+ }
65
+ return out;
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // Loading (guest memory -> JS string)
69
+ // ---------------------------------------------------------------------------
70
+ export function loadString(cx, ptr) {
71
+ const mem = requireMemory(cx.opts);
72
+ const begin = loadPtr(mem, ptr);
73
+ const taggedCodeUnits = loadPtr(mem, ptr + mem.ptrSize());
74
+ return loadStringFromRange(cx, begin, taggedCodeUnits);
75
+ }
76
+ export function loadStringFromRange(cx, ptr, taggedCodeUnits) {
77
+ const mem = requireMemory(cx.opts);
78
+ const tag = utf16TagBig(mem.ptrType());
79
+ const units = BigInt(taggedCodeUnits);
80
+ let alignment;
81
+ let byteLengthBig;
82
+ let encoding;
83
+ switch (cx.opts.stringEncoding) {
84
+ case "utf8":
85
+ alignment = 1;
86
+ byteLengthBig = units;
87
+ encoding = "utf-8";
88
+ break;
89
+ case "utf16":
90
+ alignment = 2;
91
+ byteLengthBig = 2n * units;
92
+ encoding = "utf-16-le";
93
+ break;
94
+ case "latin1+utf16":
95
+ alignment = 2;
96
+ if ((units & tag) !== 0n) {
97
+ byteLengthBig = 2n * (units ^ tag);
98
+ encoding = "utf-16-le";
99
+ }
100
+ else {
101
+ byteLengthBig = units;
102
+ encoding = "latin-1";
103
+ }
104
+ break;
105
+ }
106
+ trapIf(byteLengthBig > BigInt(MAX_STRING_BYTE_LENGTH), "string too long");
107
+ const byteLength = Number(byteLengthBig);
108
+ const ptrBig = BigInt(ptr);
109
+ trapIf(ptrBig % BigInt(alignment) !== 0n, "misaligned string pointer");
110
+ trapIfRangeExceedsMemory(mem, ptrBig, byteLengthBig, "string pointer/length out of bounds of memory");
111
+ const p = Number(ptrBig);
112
+ const bytes = bytesOf(mem, p, byteLength);
113
+ try {
114
+ switch (encoding) {
115
+ case "utf-8":
116
+ return utf8Decoder.decode(bytes);
117
+ case "utf-16-le":
118
+ return utf16Decoder.decode(bytes);
119
+ case "latin-1":
120
+ return latin1Decode(bytes);
121
+ }
122
+ }
123
+ catch {
124
+ // Message only: the trap condition is unchanged. wasmtime lifts strings
125
+ // with `core::str::from_utf8` and surfaces Rust's `Utf8Error`, whose two
126
+ // shapes the official suite asserts on separately
127
+ // (`values/strings.wast:85` vs `:101`): a byte sequence that can never be
128
+ // valid, versus one that is a valid prefix cut short by the end of the
129
+ // string.
130
+ trap(encoding === "utf-8"
131
+ ? utf8ErrorMessage(bytes)
132
+ : "invalid string encoding");
133
+ }
134
+ }
135
+ /**
136
+ * Classify a UTF-8 decode failure the way `core::str::from_utf8` does:
137
+ * `Utf8Error::error_len() == None` (input ended mid-sequence) is reported as
138
+ * "incomplete utf-8 byte sequence", anything else as "invalid utf-8".
139
+ * Diagnostics only — callers have already decided to trap.
140
+ */
141
+ function utf8ErrorMessage(bytes) {
142
+ const INVALID = "invalid utf-8";
143
+ const INCOMPLETE = "incomplete utf-8 byte sequence";
144
+ let i = 0;
145
+ while (i < bytes.length) {
146
+ const b = bytes[i];
147
+ if (b < 0x80) {
148
+ i += 1;
149
+ continue;
150
+ }
151
+ // Sequence length and the permitted range of the first continuation byte
152
+ // (the second byte carries the overlong/surrogate/range constraints).
153
+ let width;
154
+ let loMin = 0x80;
155
+ let loMax = 0xbf;
156
+ if (b >= 0xc2 && b <= 0xdf) {
157
+ width = 2;
158
+ }
159
+ else if (b === 0xe0) {
160
+ width = 3;
161
+ loMin = 0xa0;
162
+ }
163
+ else if (b >= 0xe1 && b <= 0xec) {
164
+ width = 3;
165
+ }
166
+ else if (b === 0xed) {
167
+ width = 3;
168
+ loMax = 0x9f; // no surrogates
169
+ }
170
+ else if (b >= 0xee && b <= 0xef) {
171
+ width = 3;
172
+ }
173
+ else if (b === 0xf0) {
174
+ width = 4;
175
+ loMin = 0x90;
176
+ }
177
+ else if (b >= 0xf1 && b <= 0xf3) {
178
+ width = 4;
179
+ }
180
+ else if (b === 0xf4) {
181
+ width = 4;
182
+ loMax = 0x8f; // <= U+10FFFF
183
+ }
184
+ else {
185
+ return INVALID; // continuation byte in leading position, or 0xC0/C1/F5+
186
+ }
187
+ for (let k = 1; k < width; k++) {
188
+ if (i + k >= bytes.length)
189
+ return INCOMPLETE;
190
+ const c = bytes[i + k];
191
+ const min = k === 1 ? loMin : 0x80;
192
+ const max = k === 1 ? loMax : 0xbf;
193
+ if (c < min || c > max)
194
+ return INVALID;
195
+ }
196
+ i += width;
197
+ }
198
+ // The decoder rejected input this scan considers well-formed: report the
199
+ // generic verdict rather than claiming a shape we did not find.
200
+ return INVALID;
201
+ }
202
+ // ---------------------------------------------------------------------------
203
+ // Storing (JS string -> guest memory)
204
+ // ---------------------------------------------------------------------------
205
+ export function storeString(cx, v, ptr) {
206
+ const mem = requireMemory(cx.opts);
207
+ const [begin, taggedCodeUnits] = storeStringIntoRange(cx, v);
208
+ // Write order matches the reference (store_string, definitions.py:1613-1616):
209
+ // begin pointer first, then tagged length. Unobservable here (no trap can
210
+ // intervene between the two writes), but kept in step for parity.
211
+ storeInt(mem, mem.ptrSize() === 4 ? begin : BigInt(begin), ptr, mem.ptrSize());
212
+ storeInt(mem, mem.ptrSize() === 4 ? Number(taggedCodeUnits) : taggedCodeUnits, ptr + mem.ptrSize(), mem.ptrSize());
213
+ }
214
+ /**
215
+ * definitions.py store_string_into_range, specialized to a JS-string source
216
+ * (src_encoding = 'utf16', src_code_units = s.length — see module comment).
217
+ * Returns [ptr, tagged_code_units]; tagged units as bigint because the
218
+ * latin1+utf16 tag bit exceeds Number.MAX_SAFE_INTEGER on i64 memories.
219
+ */
220
+ export function storeStringIntoRange(cx, src) {
221
+ const srcCodeUnits = src.length;
222
+ switch (cx.opts.stringEncoding) {
223
+ case "utf8":
224
+ return storeUtf16ToUtf8(cx, src, srcCodeUnits);
225
+ case "utf16":
226
+ return storeStringCopyUtf16(cx, src, srcCodeUnits);
227
+ case "latin1+utf16":
228
+ return storeStringToLatin1OrUtf16(cx, src, srcCodeUnits);
229
+ }
230
+ }
231
+ /** definitions.py store_string_copy for a utf16 destination. */
232
+ function storeStringCopyUtf16(cx, src, srcCodeUnits) {
233
+ const mem = requireMemory(cx.opts);
234
+ const dstByteLength = 2 * srcCodeUnits;
235
+ assert_(dstByteLength <= REALLOC_I32_MAX);
236
+ const ptr = cx.allocate(2, dstByteLength);
237
+ trapIf(ptr !== alignTo(ptr, 2), REALLOC_MISALIGNED);
238
+ trapIfRangeExceedsMemory(mem, ptr, dstByteLength, REALLOC_OOB);
239
+ const encoded = encodeUtf16Le(src);
240
+ assert_(dstByteLength === encoded.length);
241
+ writeBytes(mem, ptr, encoded);
242
+ return [ptr, BigInt(srcCodeUnits)];
243
+ }
244
+ /** definitions.py store_utf16_to_utf8 -> store_string_to_utf8. */
245
+ function storeUtf16ToUtf8(cx, src, srcCodeUnits) {
246
+ const worstCaseSize = srcCodeUnits * 3;
247
+ return storeStringToUtf8(cx, src, srcCodeUnits, worstCaseSize);
248
+ }
249
+ function storeStringToUtf8(cx, src, srcCodeUnits, worstCaseSize) {
250
+ const mem = requireMemory(cx.opts);
251
+ assert_(srcCodeUnits <= REALLOC_I32_MAX);
252
+ let ptr = cx.allocate(1, srcCodeUnits);
253
+ trapIfRangeExceedsMemory(mem, ptr, srcCodeUnits, REALLOC_OOB);
254
+ // Optimistic ASCII copy; on the first non-ASCII code unit, realloc to the
255
+ // worst case, bulk-encode, then shrink.
256
+ for (let i = 0; i < src.length; i++) {
257
+ const cu = src.charCodeAt(i);
258
+ if (cu < 0x80) {
259
+ mem.bytes[ptr + i] = cu;
260
+ }
261
+ else {
262
+ assert_(worstCaseSize <= REALLOC_I32_MAX);
263
+ ptr = cx.reallocate(ptr, srcCodeUnits, 1, worstCaseSize);
264
+ trapIfRangeExceedsMemory(mem, ptr, worstCaseSize, REALLOC_OOB);
265
+ const encoded = utf8Encoder.encode(src); // USVString: replaces lone surrogates
266
+ writeBytes(mem, ptr + i, encoded.subarray(i));
267
+ if (worstCaseSize > encoded.length) {
268
+ ptr = cx.reallocate(ptr, worstCaseSize, 1, encoded.length);
269
+ trapIfRangeExceedsMemory(mem, ptr, encoded.length, REALLOC_OOB);
270
+ }
271
+ return [ptr, BigInt(encoded.length)];
272
+ }
273
+ }
274
+ return [ptr, BigInt(srcCodeUnits)];
275
+ }
276
+ /** definitions.py store_string_to_latin1_or_utf16 (latin1+utf16 dst). */
277
+ function storeStringToLatin1OrUtf16(cx, src, srcCodeUnits) {
278
+ const mem = requireMemory(cx.opts);
279
+ const wf = toWellFormed(src);
280
+ assert_(srcCodeUnits <= REALLOC_I32_MAX);
281
+ let ptr = cx.allocate(2, srcCodeUnits);
282
+ trapIf(ptr !== alignTo(ptr, 2), REALLOC_MISALIGNED);
283
+ trapIfRangeExceedsMemory(mem, ptr, srcCodeUnits, REALLOC_OOB);
284
+ let dstByteLength = 0;
285
+ for (let i = 0; i < wf.length; i++) {
286
+ const cu = wf.charCodeAt(i);
287
+ if (cu < 1 << 8) {
288
+ mem.bytes[ptr + dstByteLength] = cu;
289
+ dstByteLength += 1;
290
+ }
291
+ else {
292
+ // Widen everything written so far to utf16 and continue as utf16.
293
+ const worstCaseSize = 2 * srcCodeUnits;
294
+ assert_(worstCaseSize <= REALLOC_I32_MAX);
295
+ ptr = cx.reallocate(ptr, srcCodeUnits, 2, worstCaseSize);
296
+ trapIf(ptr !== alignTo(ptr, 2), REALLOC_MISALIGNED);
297
+ trapIfRangeExceedsMemory(mem, ptr, worstCaseSize, REALLOC_OOB);
298
+ for (let j = dstByteLength - 1; j >= 0; j--) {
299
+ mem.bytes[ptr + 2 * j] = mem.bytes[ptr + j];
300
+ mem.bytes[ptr + 2 * j + 1] = 0;
301
+ }
302
+ const encoded = encodeUtf16Le(wf);
303
+ writeBytes(mem, ptr + 2 * dstByteLength, encoded.subarray(2 * dstByteLength));
304
+ if (worstCaseSize > encoded.length) {
305
+ ptr = cx.reallocate(ptr, worstCaseSize, 2, encoded.length);
306
+ trapIf(ptr !== alignTo(ptr, 2), REALLOC_MISALIGNED);
307
+ trapIfRangeExceedsMemory(mem, ptr, encoded.length, REALLOC_OOB);
308
+ }
309
+ const taggedCodeUnits = BigInt(encoded.length / 2) |
310
+ utf16TagBig(mem.ptrType());
311
+ return [ptr, taggedCodeUnits];
312
+ }
313
+ }
314
+ if (dstByteLength < srcCodeUnits) {
315
+ ptr = cx.reallocate(ptr, srcCodeUnits, 2, dstByteLength);
316
+ trapIf(ptr !== alignTo(ptr, 2), REALLOC_MISALIGNED);
317
+ trapIfRangeExceedsMemory(mem, ptr, dstByteLength, REALLOC_OOB);
318
+ }
319
+ return [ptr, BigInt(dstByteLength)];
320
+ }
321
+ // ---------------------------------------------------------------------------
322
+ // Char (definitions.py convert_i32_to_char / char_to_i32)
323
+ // ---------------------------------------------------------------------------
324
+ export function convertI32ToChar(i) {
325
+ assert_(i >= 0);
326
+ trapIf(i >= 0x110000, "char out of range");
327
+ trapIf(0xd800 <= i && i <= 0xdfff, "char is a surrogate");
328
+ return String.fromCodePoint(i);
329
+ }
330
+ export function charToI32(c) {
331
+ const i = c.codePointAt(0);
332
+ assert_(i !== undefined, "empty char");
333
+ assert_(c.length === (i > 0xffff ? 2 : 1), "char must be one code point");
334
+ assert_((0 <= i && i <= 0xd7ff) || (0xe000 <= i && i <= 0x10ffff), "char must be a Unicode scalar value");
335
+ return i;
336
+ }
@@ -0,0 +1,38 @@
1
+ // Trap and assertion machinery (definitions.py `Trap`, `trap`, `trap_if`).
2
+ //
3
+ // `Trap`'s canonical definition lives in `@polyengine/protocol` since amendment
4
+ // A9 (it is an embedder-contract value and must be recognizable across
5
+ // runtime copies, issue #83); it is re-exported here so every existing
6
+ // `from "../cabi/trap.ts"` import path is unchanged. The protocol package is
7
+ // dependency-free, so this import introduces no cycle.
8
+ //
9
+ // `Trap` models a Component Model trap — a deterministic guest-visible fault.
10
+ // `AssertionError` models the reference's Python `assert`s: internal
11
+ // invariants that callers are supposed to make unviolable. Tests treat only
12
+ // `Trap` as an expected outcome.
13
+ import { Trap } from "@polyengine/protocol";
14
+ export { isTrap, Trap } from "@polyengine/protocol";
15
+ export function trap(message) {
16
+ throw new Trap(message);
17
+ }
18
+ export function trapIf(cond, message) {
19
+ if (cond)
20
+ trap(message);
21
+ }
22
+ export class AssertionError extends Error {
23
+ constructor(message = "internal assertion failed") {
24
+ super(message);
25
+ this.name = "AssertionError";
26
+ }
27
+ }
28
+ export function assert_(cond, message) {
29
+ if (!cond)
30
+ throw new AssertionError(message);
31
+ }
32
+ /** Marks a definitions.py code path this v1 interpreter does not port yet. */
33
+ export class NotImplemented extends Error {
34
+ constructor(what) {
35
+ super(`not implemented in cabi v1: ${what}`);
36
+ this.name = "NotImplemented";
37
+ }
38
+ }