@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,443 @@
|
|
|
1
|
+
// FACT string-transcoder intrinsics (`wasmtime_environ::component::Trampoline
|
|
2
|
+
// ::Transcoder`, contracts/intrinsics.md §B "M1").
|
|
3
|
+
//
|
|
4
|
+
// FACT emits these when a cross-component call has to move a string between
|
|
5
|
+
// two components whose canonical `string-encoding` options disagree (or, for
|
|
6
|
+
// same-encoding pairs, to *validate* the source). It calls them with raw
|
|
7
|
+
// pointers into two linear memories and expects them to do the encoding work
|
|
8
|
+
// in place; the surrounding allocation/realloc dance stays in the adapter.
|
|
9
|
+
//
|
|
10
|
+
// Authorities used, in order:
|
|
11
|
+
// - signatures: wasmtime-environ 47.0.3 `fact/transcode.rs` (`Transcoder::ty`)
|
|
12
|
+
// - call protocol (argument order, multi-pass retries, what each result is
|
|
13
|
+
// used for): `fact/trampoline.rs` `string_copy` / `string_deflate_to_utf8`
|
|
14
|
+
// / `string_to_utf16` / `string_utf16_to_compact` / `string_to_compact`
|
|
15
|
+
// - operation semantics: wasmtime 47.0.3
|
|
16
|
+
// `runtime/vm/component/libcalls.rs` (the twelve `Transcode` libcalls) —
|
|
17
|
+
// the executable reference for this layer, since definitions.py models
|
|
18
|
+
// transcoding as whole-string `store_string_*` rather than as these
|
|
19
|
+
// partial-progress primitives.
|
|
20
|
+
//
|
|
21
|
+
// Memory64 (`from64`/`to64`) is out of scope (https://github.com/polymorph-components/polyengine/issues/12); the executor
|
|
22
|
+
// rejects those at instantiate time.
|
|
23
|
+
import { trap } from "../cabi/trap.js";
|
|
24
|
+
/** definitions.py `UTF16_TAG` for 32-bit pointers. */
|
|
25
|
+
const UTF16_TAG = 0x8000_0000;
|
|
26
|
+
/**
|
|
27
|
+
* A live view of one `WebAssembly.Memory`. Views are re-derived per access:
|
|
28
|
+
* a transcoder can be called after the guest grew a memory, which detaches
|
|
29
|
+
* the previous `ArrayBuffer`.
|
|
30
|
+
*/
|
|
31
|
+
export class TranscodeMemory {
|
|
32
|
+
#provider;
|
|
33
|
+
#label;
|
|
34
|
+
constructor(provider, label) {
|
|
35
|
+
this.#provider = provider;
|
|
36
|
+
this.#label = label;
|
|
37
|
+
}
|
|
38
|
+
bytes() {
|
|
39
|
+
const m = this.#provider();
|
|
40
|
+
if (m === undefined) {
|
|
41
|
+
throw new Error(`${this.#label} accessed before it was extracted`);
|
|
42
|
+
}
|
|
43
|
+
return new Uint8Array(m.buffer);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export const TRANSCODE_OPS = [
|
|
47
|
+
"utf8-to-utf8",
|
|
48
|
+
"utf16-to-utf16",
|
|
49
|
+
"latin1-to-latin1",
|
|
50
|
+
"latin1-to-utf16",
|
|
51
|
+
"latin1-to-utf8",
|
|
52
|
+
"utf16-to-compact-probably-utf16",
|
|
53
|
+
"utf16-to-compact-utf16",
|
|
54
|
+
"utf16-to-latin1",
|
|
55
|
+
"utf16-to-utf8",
|
|
56
|
+
"utf8-to-compact-utf16",
|
|
57
|
+
"utf8-to-latin1",
|
|
58
|
+
"utf8-to-utf16",
|
|
59
|
+
];
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Encoding helpers
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
const utf8Fatal = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
64
|
+
/**
|
|
65
|
+
* Decode UTF-8, trapping on invalid input the way wasmtime's libcalls do
|
|
66
|
+
* (`core::str::from_utf8(..).map_err(|_| format_err!("invalid utf8
|
|
67
|
+
* encoding"))`).
|
|
68
|
+
*/
|
|
69
|
+
function decodeUtf8OrTrap(bytes) {
|
|
70
|
+
try {
|
|
71
|
+
return utf8Fatal.decode(bytes);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
trap("invalid utf8 encoding");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Decode a little-endian UTF-16 code-unit range into code points, trapping on
|
|
79
|
+
* an unpaired surrogate (wasmtime: `core::char::decode_utf16` +
|
|
80
|
+
* "invalid utf16 encoding").
|
|
81
|
+
*
|
|
82
|
+
* Yields `[codePoint, unitsConsumed]` so callers can report how much of the
|
|
83
|
+
* source they read, which the partial-progress ops need.
|
|
84
|
+
*/
|
|
85
|
+
function* decodeUtf16OrTrap(bytes, ptr, units) {
|
|
86
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
87
|
+
let i = 0;
|
|
88
|
+
while (i < units) {
|
|
89
|
+
const u = view.getUint16(ptr + 2 * i, true);
|
|
90
|
+
if (u < 0xd800 || u > 0xdfff) {
|
|
91
|
+
i += 1;
|
|
92
|
+
yield [u, 1];
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (u >= 0xdc00 || i + 1 >= units)
|
|
96
|
+
trap("invalid utf16 encoding");
|
|
97
|
+
const lo = view.getUint16(ptr + 2 * (i + 1), true);
|
|
98
|
+
if (lo < 0xdc00 || lo > 0xdfff)
|
|
99
|
+
trap("invalid utf16 encoding");
|
|
100
|
+
i += 2;
|
|
101
|
+
yield [0x10000 + ((u - 0xd800) << 10) + (lo - 0xdc00), 2];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** UTF-8 byte length of one code point. */
|
|
105
|
+
function utf8Len(cp) {
|
|
106
|
+
if (cp < 0x80)
|
|
107
|
+
return 1;
|
|
108
|
+
if (cp < 0x800)
|
|
109
|
+
return 2;
|
|
110
|
+
if (cp < 0x10000)
|
|
111
|
+
return 3;
|
|
112
|
+
return 4;
|
|
113
|
+
}
|
|
114
|
+
/** Encode one code point as UTF-8 at `dst[at..]`; returns bytes written. */
|
|
115
|
+
function encodeUtf8At(dst, at, cp) {
|
|
116
|
+
if (cp < 0x80) {
|
|
117
|
+
dst[at] = cp;
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
if (cp < 0x800) {
|
|
121
|
+
dst[at] = 0xc0 | (cp >> 6);
|
|
122
|
+
dst[at + 1] = 0x80 | (cp & 0x3f);
|
|
123
|
+
return 2;
|
|
124
|
+
}
|
|
125
|
+
if (cp < 0x10000) {
|
|
126
|
+
dst[at] = 0xe0 | (cp >> 12);
|
|
127
|
+
dst[at + 1] = 0x80 | ((cp >> 6) & 0x3f);
|
|
128
|
+
dst[at + 2] = 0x80 | (cp & 0x3f);
|
|
129
|
+
return 3;
|
|
130
|
+
}
|
|
131
|
+
dst[at] = 0xf0 | (cp >> 18);
|
|
132
|
+
dst[at + 1] = 0x80 | ((cp >> 12) & 0x3f);
|
|
133
|
+
dst[at + 2] = 0x80 | ((cp >> 6) & 0x3f);
|
|
134
|
+
dst[at + 3] = 0x80 | (cp & 0x3f);
|
|
135
|
+
return 4;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Write one code point as little-endian UTF-16 at u16 index `at`; returns the
|
|
139
|
+
* number of code units written.
|
|
140
|
+
*/
|
|
141
|
+
function encodeUtf16At(view, base, at, cp) {
|
|
142
|
+
if (cp < 0x10000) {
|
|
143
|
+
view.setUint16(base + 2 * at, cp, true);
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
const c = cp - 0x10000;
|
|
147
|
+
view.setUint16(base + 2 * at, 0xd800 + (c >> 10), true);
|
|
148
|
+
view.setUint16(base + 2 * (at + 1), 0xdc00 + (c & 0x3ff), true);
|
|
149
|
+
return 2;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* `encoding_rs::mem::utf8_latin1_up_to`: the byte index of the first byte that
|
|
153
|
+
* does not start a well-formed UTF-8 sequence for a code point below U+0100.
|
|
154
|
+
* Invalid UTF-8 also stops the scan (it is "not latin1"); the caller's later
|
|
155
|
+
* UTF-16 pass is what turns genuinely invalid input into a trap.
|
|
156
|
+
*/
|
|
157
|
+
function utf8Latin1UpTo(bytes, ptr, len) {
|
|
158
|
+
let i = 0;
|
|
159
|
+
while (i < len) {
|
|
160
|
+
const b = bytes[ptr + i];
|
|
161
|
+
if (b < 0x80) {
|
|
162
|
+
i += 1;
|
|
163
|
+
}
|
|
164
|
+
else if (b === 0xc2 || b === 0xc3) {
|
|
165
|
+
const next = i + 1 < len ? bytes[ptr + i + 1] : -1;
|
|
166
|
+
if (next < 0x80 || next > 0xbf)
|
|
167
|
+
break;
|
|
168
|
+
i += 2;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return i;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* `inflate_latin1_bytes`: the first `latin1Bytes` bytes of the destination
|
|
178
|
+
* were written as latin1; widen them in place to little-endian u16 code units
|
|
179
|
+
* (walking backwards so the expansion does not clobber unread input).
|
|
180
|
+
*/
|
|
181
|
+
function inflateLatin1Bytes(dst, dstPtr, latin1Bytes) {
|
|
182
|
+
for (let i = latin1Bytes - 1; i >= 0; i--) {
|
|
183
|
+
dst[dstPtr + 2 * i] = dst[dstPtr + i];
|
|
184
|
+
dst[dstPtr + 2 * i + 1] = 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Guard against the one case where reading and writing through the same
|
|
189
|
+
* `Uint8Array` would corrupt data: FACT freshly allocates every destination,
|
|
190
|
+
* so source and destination never overlap, but they *can* live in the same
|
|
191
|
+
* memory. Callers that read and write interleaved snapshot the source first.
|
|
192
|
+
*/
|
|
193
|
+
function snapshot(bytes, ptr, len) {
|
|
194
|
+
return bytes.slice(ptr, ptr + len);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* O(1) defensive counterpart to wasmtime's `assert_no_overlap`
|
|
198
|
+
* (libcalls.rs:166-177): traps (does not merely assert) because this
|
|
199
|
+
* replaces a guarantee FACT's trampoline construction is supposed to
|
|
200
|
+
* provide — src/dst are always independently-allocated regions — so a hit
|
|
201
|
+
* here means that guarantee broke, which is guest-memory-corruption-class
|
|
202
|
+
* severity, not an internal invariant a caller controls.
|
|
203
|
+
*
|
|
204
|
+
* Applied only where a call reads and writes through the SAME backing
|
|
205
|
+
* `Uint8Array` while interleaving reads and writes (byte-range comparison,
|
|
206
|
+
* not per-element — O(1) per call). Ops that first `snapshot()` the source
|
|
207
|
+
* into an independent copy (transcode.ts's `snapshot`, used by every op
|
|
208
|
+
* above that decodes-then-writes) already break aliasing before the first
|
|
209
|
+
* write, so they are exempt by construction and do not call this.
|
|
210
|
+
*/
|
|
211
|
+
function trapIfOverlap(src, srcPtr, srcLen, dst, dstPtr, dstLen) {
|
|
212
|
+
if (src.buffer !== dst.buffer)
|
|
213
|
+
return; // different memories: cannot overlap
|
|
214
|
+
const srcStart = src.byteOffset + srcPtr;
|
|
215
|
+
const srcEnd = srcStart + srcLen;
|
|
216
|
+
const dstStart = dst.byteOffset + dstPtr;
|
|
217
|
+
const dstEnd = dstStart + dstLen;
|
|
218
|
+
if (srcStart < dstEnd && dstStart < srcEnd) {
|
|
219
|
+
trap("transcode src/dst regions overlap");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// The twelve operations
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
/**
|
|
226
|
+
* Build the JS function backing one `Transcoder` trampoline.
|
|
227
|
+
*
|
|
228
|
+
* Result shape follows the core signature in `fact/transcode.rs`: no result,
|
|
229
|
+
* one result (a number), or two results (a `[srcRead, dstWritten]` pair — the
|
|
230
|
+
* JS API delivers a multi-value return as an array).
|
|
231
|
+
*/
|
|
232
|
+
export function createTranscoder(op, from, to) {
|
|
233
|
+
switch (op) {
|
|
234
|
+
// (srcPtr, srcLen, dstPtr) -> () --------------------------------------
|
|
235
|
+
case "latin1-to-latin1":
|
|
236
|
+
return (srcPtr, srcLen, dstPtr) => {
|
|
237
|
+
const src = from.bytes();
|
|
238
|
+
const dst = to.bytes();
|
|
239
|
+
dst.set(snapshot(src, srcPtr, srcLen), dstPtr);
|
|
240
|
+
};
|
|
241
|
+
case "utf8-to-utf8":
|
|
242
|
+
return (srcPtr, srcLen, dstPtr) => {
|
|
243
|
+
const src = from.bytes();
|
|
244
|
+
const copy = snapshot(src, srcPtr, srcLen);
|
|
245
|
+
decodeUtf8OrTrap(copy); // validation only
|
|
246
|
+
to.bytes().set(copy, dstPtr);
|
|
247
|
+
};
|
|
248
|
+
case "utf16-to-utf16":
|
|
249
|
+
return (srcPtr, srcLen, dstPtr) => {
|
|
250
|
+
const src = from.bytes();
|
|
251
|
+
const copy = snapshot(src, srcPtr, 2 * srcLen);
|
|
252
|
+
// Round-trips the units, but must reject unpaired surrogates.
|
|
253
|
+
for (const _ of decodeUtf16OrTrap(copy, 0, srcLen)) { /* validate */ }
|
|
254
|
+
to.bytes().set(copy, dstPtr);
|
|
255
|
+
};
|
|
256
|
+
case "latin1-to-utf16":
|
|
257
|
+
return (srcPtr, srcLen, dstPtr) => {
|
|
258
|
+
const src = snapshot(from.bytes(), srcPtr, srcLen);
|
|
259
|
+
const dst = to.bytes();
|
|
260
|
+
for (let i = 0; i < srcLen; i++) {
|
|
261
|
+
dst[dstPtr + 2 * i] = src[i];
|
|
262
|
+
dst[dstPtr + 2 * i + 1] = 0;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
// (srcPtr, srcLen, dstPtr) -> dstUnits ---------------------------------
|
|
266
|
+
case "utf8-to-utf16":
|
|
267
|
+
return (srcPtr, srcLen, dstPtr) => {
|
|
268
|
+
const s = decodeUtf8OrTrap(snapshot(from.bytes(), srcPtr, srcLen));
|
|
269
|
+
const dst = to.bytes();
|
|
270
|
+
const view = new DataView(dst.buffer, dst.byteOffset, dst.byteLength);
|
|
271
|
+
// The destination holds `srcLen` code units (UTF-16 units are never
|
|
272
|
+
// more numerous than UTF-8 bytes), matching wasmtime's `zip`.
|
|
273
|
+
let units = 0;
|
|
274
|
+
for (let i = 0; i < s.length && units < srcLen; i++) {
|
|
275
|
+
view.setUint16(dstPtr + 2 * units, s.charCodeAt(i), true);
|
|
276
|
+
units++;
|
|
277
|
+
}
|
|
278
|
+
return units;
|
|
279
|
+
};
|
|
280
|
+
case "utf16-to-compact-probably-utf16":
|
|
281
|
+
return (srcPtr, srcLen, dstPtr) => {
|
|
282
|
+
const src = snapshot(from.bytes(), srcPtr, 2 * srcLen);
|
|
283
|
+
let allLatin1 = true;
|
|
284
|
+
for (const [cp] of decodeUtf16OrTrap(src, 0, srcLen)) {
|
|
285
|
+
if (cp > 0xff)
|
|
286
|
+
allLatin1 = false;
|
|
287
|
+
}
|
|
288
|
+
const dst = to.bytes();
|
|
289
|
+
dst.set(src, dstPtr);
|
|
290
|
+
if (!allLatin1)
|
|
291
|
+
return (srcLen | UTF16_TAG) >>> 0;
|
|
292
|
+
// Compact in place: keep the low byte of each little-endian unit.
|
|
293
|
+
for (let i = 0; i < srcLen; i++)
|
|
294
|
+
dst[dstPtr + i] = dst[dstPtr + 2 * i];
|
|
295
|
+
return srcLen;
|
|
296
|
+
};
|
|
297
|
+
// (srcPtr, srcLen, dstPtr) -> [srcRead, dstWritten] --------------------
|
|
298
|
+
case "utf8-to-latin1":
|
|
299
|
+
return (srcPtr, srcLen, dstPtr) => {
|
|
300
|
+
const src = snapshot(from.bytes(), srcPtr, srcLen);
|
|
301
|
+
const read = utf8Latin1UpTo(src, 0, srcLen);
|
|
302
|
+
const dst = to.bytes();
|
|
303
|
+
let written = 0;
|
|
304
|
+
let i = 0;
|
|
305
|
+
while (i < read) {
|
|
306
|
+
const b = src[i];
|
|
307
|
+
if (b < 0x80) {
|
|
308
|
+
dst[dstPtr + written] = b;
|
|
309
|
+
i += 1;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
dst[dstPtr + written] = ((b & 0x1f) << 6) | (src[i + 1] & 0x3f);
|
|
313
|
+
i += 2;
|
|
314
|
+
}
|
|
315
|
+
written++;
|
|
316
|
+
}
|
|
317
|
+
return [read, written];
|
|
318
|
+
};
|
|
319
|
+
case "utf16-to-latin1":
|
|
320
|
+
return (srcPtr, srcLen, dstPtr) => {
|
|
321
|
+
const src = from.bytes();
|
|
322
|
+
const dst = to.bytes();
|
|
323
|
+
// This op does not call `snapshot()` (unlike its siblings above):
|
|
324
|
+
// it reads the full `out` prefix before writing anything to `dst`,
|
|
325
|
+
// which is the same aliasing-safety property snapshot() buys
|
|
326
|
+
// elsewhere, just via a builder array instead of a byte copy. The
|
|
327
|
+
// overlap guard is still added here (O(1): a byte-range compare, not
|
|
328
|
+
// per-element) as the one op in this file that is safe by algorithm
|
|
329
|
+
// shape rather than by an explicit `snapshot()` call — cheap
|
|
330
|
+
// insurance against that reasoning becoming stale under a future
|
|
331
|
+
// edit (docs/architecture.md §7; wasmtime asserts overlap on every
|
|
332
|
+
// op unconditionally, libcalls.rs:166-177).
|
|
333
|
+
trapIfOverlap(src, srcPtr, 2 * srcLen, dst, dstPtr, srcLen);
|
|
334
|
+
const view = new DataView(src.buffer, src.byteOffset, src.byteLength);
|
|
335
|
+
// Note: no surrogate validation here, matching wasmtime — a surrogate
|
|
336
|
+
// is simply > 0xFF and ends the latin1 prefix.
|
|
337
|
+
const out = [];
|
|
338
|
+
for (let i = 0; i < srcLen; i++) {
|
|
339
|
+
const u = view.getUint16(srcPtr + 2 * i, true);
|
|
340
|
+
if (u > 0xff)
|
|
341
|
+
break;
|
|
342
|
+
out.push(u);
|
|
343
|
+
}
|
|
344
|
+
for (let i = 0; i < out.length; i++)
|
|
345
|
+
dst[dstPtr + i] = out[i];
|
|
346
|
+
return [out.length, out.length];
|
|
347
|
+
};
|
|
348
|
+
// (srcPtr, srcLen, dstPtr, dstLen, firstPass) -> [srcRead, dstWritten] -
|
|
349
|
+
case "utf16-to-utf8":
|
|
350
|
+
return (srcPtr, srcLen, dstPtr, dstLen, firstPass) => {
|
|
351
|
+
const src = snapshot(from.bytes(), srcPtr, 2 * srcLen);
|
|
352
|
+
const dst = to.bytes();
|
|
353
|
+
let srcRead = 0;
|
|
354
|
+
let dstWritten = 0;
|
|
355
|
+
let consumed = 0;
|
|
356
|
+
for (const [cp, units] of decodeUtf16OrTrap(src, 0, srcLen)) {
|
|
357
|
+
consumed += units;
|
|
358
|
+
// The spec requires the first pass to bail on the first
|
|
359
|
+
// non-ASCII code point (wasmtime: `first_pass && ch >= 0x80`).
|
|
360
|
+
if (firstPass !== 0 && cp >= 0x80)
|
|
361
|
+
break;
|
|
362
|
+
const remaining = dstLen - dstWritten;
|
|
363
|
+
if (remaining < 4 && remaining < utf8Len(cp))
|
|
364
|
+
break;
|
|
365
|
+
srcRead = consumed;
|
|
366
|
+
dstWritten += encodeUtf8At(dst, dstPtr + dstWritten, cp);
|
|
367
|
+
}
|
|
368
|
+
return [srcRead, dstWritten];
|
|
369
|
+
};
|
|
370
|
+
case "latin1-to-utf8":
|
|
371
|
+
return (srcPtr, srcLen, dstPtr, dstLen, firstPass) => {
|
|
372
|
+
const src = snapshot(from.bytes(), srcPtr, srcLen);
|
|
373
|
+
// First pass halts at the first byte that is not ASCII, because a
|
|
374
|
+
// latin1 byte >= 0x80 is two bytes of UTF-8.
|
|
375
|
+
let stop = srcLen;
|
|
376
|
+
if (firstPass !== 0) {
|
|
377
|
+
for (let i = 0; i < srcLen; i++) {
|
|
378
|
+
if (src[i] >= 0x80) {
|
|
379
|
+
stop = i;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const dst = to.bytes();
|
|
385
|
+
let read = 0;
|
|
386
|
+
let written = 0;
|
|
387
|
+
while (read < stop) {
|
|
388
|
+
const b = src[read];
|
|
389
|
+
const need = b < 0x80 ? 1 : 2;
|
|
390
|
+
if (written + need > dstLen)
|
|
391
|
+
break; // partial: caller grows and retries
|
|
392
|
+
written += encodeUtf8At(dst, dstPtr + written, b);
|
|
393
|
+
read++;
|
|
394
|
+
}
|
|
395
|
+
return [read, written];
|
|
396
|
+
};
|
|
397
|
+
// (srcPtr, srcLen, dstPtr, dstLen, latin1BytesSoFar) -> dstUnits -------
|
|
398
|
+
case "utf8-to-compact-utf16":
|
|
399
|
+
return (srcPtr, srcLen, dstPtr, dstLen, latin1Bytes) => {
|
|
400
|
+
const s = decodeUtf8OrTrap(snapshot(from.bytes(), srcPtr, srcLen));
|
|
401
|
+
const dst = to.bytes();
|
|
402
|
+
inflateLatin1Bytes(dst, dstPtr, latin1Bytes);
|
|
403
|
+
const view = new DataView(dst.buffer, dst.byteOffset, dst.byteLength);
|
|
404
|
+
// Defensive dst-capacity guard: wasmtime's equivalent
|
|
405
|
+
// (`run_utf8_to_utf16`'s `.zip(dst)`, libcalls.rs:308-312) is bounded
|
|
406
|
+
// by Rust's `Iterator::zip` truncating to the shorter of the two —
|
|
407
|
+
// it can never overrun `dst`. FACT is supposed to size `dstLen` to
|
|
408
|
+
// always have room (a full re-encode of a string that was already
|
|
409
|
+
// partially latin1-encoded never needs more u16 units than
|
|
410
|
+
// `dstLen - latin1Bytes`), so this should be unreachable; trap
|
|
411
|
+
// rather than let a broken caller corrupt guest memory past `dst`'s
|
|
412
|
+
// bound or silently truncate.
|
|
413
|
+
const capacity = dstLen - latin1Bytes;
|
|
414
|
+
if (s.length > capacity) {
|
|
415
|
+
trap("utf8-to-compact-utf16: destination capacity exceeded");
|
|
416
|
+
}
|
|
417
|
+
let units = 0;
|
|
418
|
+
for (let i = 0; i < s.length; i++) {
|
|
419
|
+
view.setUint16(dstPtr + 2 * (latin1Bytes + units), s.charCodeAt(i), true);
|
|
420
|
+
units++;
|
|
421
|
+
}
|
|
422
|
+
return units + latin1Bytes;
|
|
423
|
+
};
|
|
424
|
+
case "utf16-to-compact-utf16":
|
|
425
|
+
return (srcPtr, srcLen, dstPtr, _dstLen, latin1Bytes) => {
|
|
426
|
+
const src = snapshot(from.bytes(), srcPtr, 2 * srcLen);
|
|
427
|
+
const dst = to.bytes();
|
|
428
|
+
inflateLatin1Bytes(dst, dstPtr, latin1Bytes);
|
|
429
|
+
const view = new DataView(dst.buffer, dst.byteOffset, dst.byteLength);
|
|
430
|
+
let at = latin1Bytes;
|
|
431
|
+
for (const [cp] of decodeUtf16OrTrap(src, 0, srcLen)) {
|
|
432
|
+
at += encodeUtf16At(view, dstPtr, at, cp);
|
|
433
|
+
}
|
|
434
|
+
// wasmtime returns `src.len() + latin1_bytes_so_far`: the source unit
|
|
435
|
+
// count is the destination unit count for a utf16->utf16 copy.
|
|
436
|
+
return srcLen + latin1Bytes;
|
|
437
|
+
};
|
|
438
|
+
default: {
|
|
439
|
+
const exhaustive = op;
|
|
440
|
+
throw new Error(`unknown transcode op ${exhaustive}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|