@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,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
+ }