@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.
- package/LICENSE +202 -0
- package/README.md +23 -0
- package/esm/cabi/async_values.js +162 -0
- package/esm/cabi/bulk_lists.js +198 -0
- package/esm/cabi/context.js +42 -0
- package/esm/cabi/flatten.js +145 -0
- package/esm/cabi/float.js +67 -0
- package/esm/cabi/handles.js +253 -0
- package/esm/cabi/layout.js +149 -0
- package/esm/cabi/lift.js +196 -0
- package/esm/cabi/load.js +146 -0
- package/esm/cabi/lower.js +141 -0
- package/esm/cabi/memory.js +182 -0
- package/esm/cabi/mod.js +22 -0
- package/esm/cabi/store.js +186 -0
- package/esm/cabi/strings.js +336 -0
- package/esm/cabi/trap.js +38 -0
- package/esm/cabi/types.js +264 -0
- package/esm/cabi/values.js +64 -0
- package/esm/cache/core.js +156 -0
- package/esm/cache/dir.js +170 -0
- package/esm/cache/mod.js +4 -0
- package/esm/cache/web.js +136 -0
- package/esm/digest/digest.js +332 -0
- package/esm/digest/mod.js +3 -0
- package/esm/digest/verify.js +129 -0
- package/esm/embedder/casing.js +56 -0
- package/esm/embedder/copy.js +42 -0
- package/esm/embedder/errors.js +26 -0
- package/esm/embedder/imports.js +63 -0
- package/esm/embedder/instantiate.js +978 -0
- package/esm/embedder/mod.js +40 -0
- package/esm/embedder/resources.js +406 -0
- package/esm/embedder/streams.js +770 -0
- package/esm/embedder/values.js +450 -0
- package/esm/embedder/version.js +273 -0
- package/esm/exec/boundary.js +1902 -0
- package/esm/exec/executor.js +1004 -0
- package/esm/exec/host_streams.js +818 -0
- package/esm/exec/mod.js +4 -0
- package/esm/intrinsics/async_builtins.js +510 -0
- package/esm/intrinsics/context.js +90 -0
- package/esm/intrinsics/errors.js +13 -0
- package/esm/intrinsics/fact_calls.js +865 -0
- package/esm/intrinsics/mod.js +564 -0
- package/esm/intrinsics/stream_builtins.js +578 -0
- package/esm/intrinsics/transcode.js +443 -0
- package/esm/jspi/bridge.js +579 -0
- package/esm/jspi/mechanics.js +89 -0
- package/esm/jspi/mod.js +5 -0
- package/esm/jspi/suspending.js +15 -0
- package/esm/jspi/types.js +29 -0
- package/esm/package.json +3 -0
- package/esm/plan/format.js +5 -0
- package/esm/plan/loader.js +657 -0
- package/esm/plan/mod.js +3 -0
- package/esm/shim/mod.js +2 -0
- package/esm/shim/translator.js +94 -0
- package/esm/task/mod.js +483 -0
- package/esm/task/scheduler.js +1028 -0
- package/esm/task/streams.js +786 -0
- package/esm/task/subtask.js +135 -0
- package/esm/task/thread.js +255 -0
- package/esm/task/waitable.js +144 -0
- package/package.json +91 -0
- package/types/cabi/async_values.d.ts +35 -0
- package/types/cabi/bulk_lists.d.ts +18 -0
- package/types/cabi/context.d.ts +59 -0
- package/types/cabi/flatten.d.ts +14 -0
- package/types/cabi/float.d.ts +14 -0
- package/types/cabi/handles.d.ts +70 -0
- package/types/cabi/layout.d.ts +13 -0
- package/types/cabi/lift.d.ts +25 -0
- package/types/cabi/load.d.ts +14 -0
- package/types/cabi/lower.d.ts +10 -0
- package/types/cabi/memory.d.ts +58 -0
- package/types/cabi/mod.d.ts +15 -0
- package/types/cabi/store.d.ts +12 -0
- package/types/cabi/strings.d.ts +23 -0
- package/types/cabi/trap.d.ts +11 -0
- package/types/cabi/types.d.ts +206 -0
- package/types/cabi/values.d.ts +5 -0
- package/types/cache/core.d.ts +97 -0
- package/types/cache/dir.d.ts +6 -0
- package/types/cache/mod.d.ts +3 -0
- package/types/cache/web.d.ts +10 -0
- package/types/digest/digest.d.ts +17 -0
- package/types/digest/mod.d.ts +2 -0
- package/types/digest/verify.d.ts +48 -0
- package/types/embedder/casing.d.ts +40 -0
- package/types/embedder/copy.d.ts +24 -0
- package/types/embedder/errors.d.ts +11 -0
- package/types/embedder/imports.d.ts +47 -0
- package/types/embedder/instantiate.d.ts +88 -0
- package/types/embedder/mod.d.ts +11 -0
- package/types/embedder/resources.d.ts +158 -0
- package/types/embedder/streams.d.ts +202 -0
- package/types/embedder/values.d.ts +70 -0
- package/types/embedder/version.d.ts +85 -0
- package/types/exec/boundary.d.ts +360 -0
- package/types/exec/executor.d.ts +125 -0
- package/types/exec/host_streams.d.ts +165 -0
- package/types/exec/mod.d.ts +3 -0
- package/types/intrinsics/async_builtins.d.ts +69 -0
- package/types/intrinsics/context.d.ts +28 -0
- package/types/intrinsics/errors.d.ts +5 -0
- package/types/intrinsics/fact_calls.d.ts +120 -0
- package/types/intrinsics/mod.d.ts +187 -0
- package/types/intrinsics/stream_builtins.d.ts +113 -0
- package/types/intrinsics/transcode.d.ts +21 -0
- package/types/jspi/bridge.d.ts +227 -0
- package/types/jspi/mechanics.d.ts +50 -0
- package/types/jspi/mod.d.ts +3 -0
- package/types/jspi/suspending.d.ts +1 -0
- package/types/jspi/types.d.ts +26 -0
- package/types/plan/format.d.ts +369 -0
- package/types/plan/loader.d.ts +113 -0
- package/types/plan/mod.d.ts +2 -0
- package/types/shim/mod.d.ts +1 -0
- package/types/shim/translator.d.ts +55 -0
- package/types/task/mod.d.ts +257 -0
- package/types/task/scheduler.d.ts +421 -0
- package/types/task/streams.d.ts +370 -0
- package/types/task/subtask.d.ts +96 -0
- package/types/task/thread.d.ts +73 -0
- 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
|
+
}
|
package/esm/cabi/trap.js
ADDED
|
@@ -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
|
+
}
|