json-as 1.3.7 → 1.3.9

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 (116) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +1 -1
  3. package/assembly/deserialize/index/arbitrary.ts +2 -2
  4. package/assembly/deserialize/index/array.ts +29 -14
  5. package/assembly/deserialize/index/bool.ts +1 -1
  6. package/assembly/deserialize/index/date.ts +1 -1
  7. package/assembly/deserialize/index/float.ts +40 -1
  8. package/assembly/deserialize/index/integer.ts +3 -3
  9. package/assembly/deserialize/index/map.ts +1 -1
  10. package/assembly/deserialize/index/object.ts +1 -1
  11. package/assembly/deserialize/index/raw.ts +1 -1
  12. package/assembly/deserialize/index/set.ts +1 -1
  13. package/assembly/deserialize/index/staticarray.ts +4 -1
  14. package/assembly/deserialize/index/string.ts +28 -3
  15. package/assembly/deserialize/index/struct.ts +1 -1
  16. package/assembly/deserialize/index/typedarray.ts +25 -15
  17. package/assembly/deserialize/index/unsigned.ts +3 -3
  18. package/assembly/deserialize/index.ts +1 -0
  19. package/assembly/deserialize/naive/array/bool.ts +68 -0
  20. package/assembly/deserialize/naive/array/float.ts +63 -0
  21. package/assembly/deserialize/{simple → naive}/array/generic.ts +1 -2
  22. package/assembly/deserialize/naive/array/integer.ts +86 -0
  23. package/assembly/deserialize/{simple → naive}/array/map.ts +0 -1
  24. package/assembly/deserialize/{simple → naive}/array/object.ts +0 -1
  25. package/assembly/deserialize/naive/array/string.ts +69 -0
  26. package/assembly/deserialize/{simple → naive}/array/struct.ts +0 -1
  27. package/assembly/deserialize/{simple → naive}/array.ts +6 -11
  28. package/assembly/deserialize/naive/float.ts +135 -0
  29. package/assembly/deserialize/{simple → naive}/integer.ts +2 -2
  30. package/assembly/deserialize/{simple → naive}/map.ts +12 -6
  31. package/assembly/deserialize/{simple → naive}/object.ts +4 -7
  32. package/assembly/deserialize/{simple → naive}/set.ts +12 -27
  33. package/assembly/deserialize/{simple → naive}/staticarray/array.ts +1 -1
  34. package/assembly/deserialize/{simple → naive}/staticarray/bool.ts +1 -1
  35. package/assembly/deserialize/{simple → naive}/staticarray/float.ts +1 -1
  36. package/assembly/deserialize/{simple → naive}/staticarray/integer.ts +1 -1
  37. package/assembly/deserialize/{simple → naive}/staticarray/struct.ts +1 -2
  38. package/assembly/deserialize/{simple → naive}/staticarray.ts +4 -4
  39. package/assembly/deserialize/naive/string.ts +199 -0
  40. package/assembly/deserialize/{simple → naive}/typedarray.ts +4 -4
  41. package/assembly/deserialize/{simple → naive}/unsigned.ts +2 -2
  42. package/assembly/deserialize/simd/array/integer.ts +19 -19
  43. package/assembly/deserialize/simd/float.ts +303 -0
  44. package/assembly/deserialize/simd/string.ts +233 -108
  45. package/assembly/deserialize/swar/array/arbitrary.ts +6 -2
  46. package/assembly/deserialize/swar/array/array.ts +14 -7
  47. package/assembly/deserialize/swar/array/bool.ts +8 -3
  48. package/assembly/deserialize/swar/array/box.ts +6 -2
  49. package/assembly/deserialize/swar/array/float.ts +282 -6
  50. package/assembly/deserialize/swar/array/generic.ts +6 -2
  51. package/assembly/deserialize/swar/array/integer.ts +81 -74
  52. package/assembly/deserialize/swar/array/map.ts +6 -2
  53. package/assembly/deserialize/swar/array/object.ts +24 -32
  54. package/assembly/deserialize/swar/array/raw.ts +6 -2
  55. package/assembly/deserialize/swar/array/shared.ts +32 -8
  56. package/assembly/deserialize/swar/array/string.ts +127 -10
  57. package/assembly/deserialize/swar/array/struct.ts +45 -11
  58. package/assembly/deserialize/swar/array.ts +2 -56
  59. package/assembly/deserialize/swar/float.ts +304 -0
  60. package/assembly/deserialize/swar/string.ts +119 -104
  61. package/assembly/deserialize/swar/typedarray.ts +224 -0
  62. package/assembly/index.ts +203 -293
  63. package/assembly/serialize/index/array.ts +1 -1
  64. package/assembly/serialize/index/bool.ts +1 -1
  65. package/assembly/serialize/index/date.ts +1 -1
  66. package/assembly/serialize/index/float.ts +1 -1
  67. package/assembly/serialize/index/integer.ts +1 -1
  68. package/assembly/serialize/index/map.ts +1 -1
  69. package/assembly/serialize/index/raw.ts +1 -1
  70. package/assembly/serialize/index/set.ts +1 -1
  71. package/assembly/serialize/index/staticarray.ts +1 -1
  72. package/assembly/serialize/index/string.ts +1 -1
  73. package/assembly/serialize/index/struct.ts +1 -1
  74. package/assembly/serialize/index/typedarray.ts +2 -11
  75. package/assembly/serialize/index.ts +1 -0
  76. package/assembly/serialize/{simple → naive}/array.ts +87 -0
  77. package/assembly/serialize/{simple → naive}/string.ts +1 -1
  78. package/assembly/serialize/swar/string.ts +0 -139
  79. package/assembly/util/dragonbox.ts +10 -3
  80. package/assembly/util/itoa-fast.ts +29 -18
  81. package/assembly/util/scanValueEnd.ts +78 -0
  82. package/assembly/util/scientific.ts +132 -0
  83. package/lib/as-bs.ts +14 -1
  84. package/package.json +14 -13
  85. package/transform/lib/index.d.ts +4 -0
  86. package/transform/lib/index.d.ts.map +1 -1
  87. package/transform/lib/index.js +155 -238
  88. package/transform/lib/index.js.map +1 -1
  89. package/assembly/deserialize/simple/arbitrary.ts +0 -30
  90. package/assembly/deserialize/simple/array/bool.ts +0 -48
  91. package/assembly/deserialize/simple/array/float.ts +0 -55
  92. package/assembly/deserialize/simple/array/integer.ts +0 -33
  93. package/assembly/deserialize/simple/array/string.ts +0 -29
  94. package/assembly/deserialize/simple/float.ts +0 -206
  95. package/assembly/deserialize/simple/string.ts +0 -45
  96. package/assembly/serialize/simple/arbitrary.ts +0 -79
  97. package/assembly/serialize/simple/object.ts +0 -42
  98. /package/assembly/deserialize/{simple → naive}/array/arbitrary.ts +0 -0
  99. /package/assembly/deserialize/{simple → naive}/array/array.ts +0 -0
  100. /package/assembly/deserialize/{simple → naive}/array/box.ts +0 -0
  101. /package/assembly/deserialize/{simple → naive}/array/raw.ts +0 -0
  102. /package/assembly/deserialize/{simple → naive}/bool.ts +0 -0
  103. /package/assembly/deserialize/{simple → naive}/date.ts +0 -0
  104. /package/assembly/deserialize/{simple → naive}/raw.ts +0 -0
  105. /package/assembly/deserialize/{simple → naive}/staticarray/string.ts +0 -0
  106. /package/assembly/deserialize/{simple → naive}/struct.ts +0 -0
  107. /package/assembly/serialize/{simple → naive}/bool.ts +0 -0
  108. /package/assembly/serialize/{simple → naive}/date.ts +0 -0
  109. /package/assembly/serialize/{simple → naive}/float.ts +0 -0
  110. /package/assembly/serialize/{simple → naive}/integer.ts +0 -0
  111. /package/assembly/serialize/{simple → naive}/map.ts +0 -0
  112. /package/assembly/serialize/{simple → naive}/raw.ts +0 -0
  113. /package/assembly/serialize/{simple → naive}/set.ts +0 -0
  114. /package/assembly/serialize/{simple → naive}/staticarray.ts +0 -0
  115. /package/assembly/serialize/{simple → naive}/struct.ts +0 -0
  116. /package/assembly/serialize/{simple → naive}/typedarray.ts +0 -0
@@ -3,12 +3,8 @@ import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
3
3
  import { __heap_base } from "memory";
4
4
  import { QUOTE } from "../../custom/chars";
5
5
  import { BACK_SLASH } from "../../custom/chars";
6
- import {
7
- DESERIALIZE_ESCAPE_TABLE,
8
- ESCAPE_HEX_TABLE,
9
- } from "../../globals/tables";
6
+ import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
10
7
  import { hex4_to_u16_swar } from "../../util/swar";
11
- import { deserializeStringField_SWAR } from "../swar/string";
12
8
 
13
9
  // @ts-expect-error: @lazy is a valid decorator
14
10
  @lazy const SPLAT_5C = i16x8.splat(0x5c); // \
@@ -106,110 +102,95 @@ import { deserializeStringField_SWAR } from "../swar/string";
106
102
  memory.copy(stringPtr, srcStart, byteLength);
107
103
  }
108
104
 
109
- // @ts-expect-error: @inline is a valid decorator
110
-
111
- // todo: optimize and stuff. it works, its not pretty. ideally, i'd like this to be (nearly) branchless
112
- // @ts-expect-error: @inline is a valid decorator
113
- @inline function deserializeEscapedString_SIMD(
105
+ // Vectorized escaped scanner for the standalone (whole-value) path. Quotes are
106
+ // already stripped, so `srcEnd` is the payload end and only `\` escapes need
107
+ // handling (no closing-quote search). Same HYBRID strategy as the field path
108
+ // (see deserializeEscapedStringField_SIMD): escape blocks use a free
109
+ // whole-block v128 store for the plain prefix; clean runs stream the first
110
+ // block then bulk-memcpy the remainder. Output is sliced out of `bs` scratch.
111
+ //
112
+ // Deliberately NOT @inline: cold escape path. Inlining the nested-loop v128
113
+ // body at every call site explodes `--converge` compile time on large schemas
114
+ // for no runtime gain (one call per escaped string).
115
+ function deserializeEscapedString_SIMD(
114
116
  payloadStart: usize,
115
117
  escapeStart: usize,
116
118
  srcEnd: usize,
117
119
  ): string {
118
120
  const prefixLen = <u32>(escapeStart - payloadStart);
119
- let srcStart = escapeStart;
120
- const srcEnd16 = srcEnd - 16;
121
121
  const outStart = bs.offset - bs.buffer;
122
- bs.ensureSize(u32(srcEnd - srcStart));
122
+ bs.ensureSize(<u32>(srcEnd - payloadStart) + 16); // +16 slack for overcopy
123
123
  if (prefixLen != 0) {
124
124
  memory.copy(bs.offset, payloadStart, prefixLen);
125
125
  bs.offset += prefixLen;
126
126
  }
127
127
 
128
- while (srcStart < srcEnd16) {
129
- const block = load<v128>(srcStart);
130
- store<v128>(bs.offset, block);
131
-
132
- const eq5C = i16x8.eq(block, SPLAT_5C);
133
- let mask = i16x8.bitmask(eq5C);
128
+ let srcStart = escapeStart;
129
+ const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
134
130
 
131
+ while (srcStart <= srcEnd16) {
132
+ const block = load<v128>(srcStart);
133
+ const mask = i16x8.bitmask(i16x8.eq(block, SPLAT_5C)); // backslash only
135
134
  if (mask == 0) {
136
- srcStart += 16;
135
+ // Stream the first clean block cheaply.
136
+ store<v128>(bs.offset, block);
137
137
  bs.offset += 16;
138
+ srcStart += 16;
139
+ // If the clean run continues, bulk-copy the remainder in one shot.
140
+ if (srcStart <= srcEnd16) {
141
+ const b2 = load<v128>(srcStart);
142
+ if (i16x8.bitmask(i16x8.eq(b2, SPLAT_5C)) == 0) {
143
+ const runStart = srcStart;
144
+ srcStart += 16;
145
+ while (srcStart <= srcEnd16) {
146
+ if (i16x8.bitmask(i16x8.eq(load<v128>(srcStart), SPLAT_5C)) != 0)
147
+ break;
148
+ srcStart += 16;
149
+ }
150
+ const runLen = <u32>(srcStart - runStart);
151
+ memory.copy(bs.offset, runStart, runLen);
152
+ bs.offset += runLen;
153
+ }
154
+ }
138
155
  continue;
139
156
  }
140
157
 
141
- let srcChg: usize = 0;
142
- let lastLane: usize = 0;
143
- do {
144
- const laneIdx = usize(ctz(mask) << 1); // 0 2 4 6 8 10 12 14
145
- mask &= mask - 1;
146
- const srcIdx = srcStart + laneIdx;
147
- const code = load<u16>(srcIdx, 2);
148
-
149
- bs.offset += laneIdx - lastLane;
150
-
151
- // Hot path (negative bias)
152
- if (code !== 0x75) {
153
- // Short escapes (\n \t \" \\)
154
- const escaped = load<u16>(DESERIALIZE_ESCAPE_TABLE + code);
155
- mask &= mask - i32(escaped === 0x5c);
156
- store<u16>(bs.offset, escaped);
157
- store<v128>(bs.offset, load<v128>(srcIdx, 4), 2);
158
-
159
- const l6 = usize(laneIdx === 14);
160
- // bs.offset -= (1 - l6) << 1;
161
- bs.offset += 2;
162
- srcStart += l6 << 1;
163
- lastLane = laneIdx + 4;
164
- continue;
165
- }
166
-
167
- // Unicode escape (\uXXXX)
168
- const block = load<u64>(srcIdx, 4); // XXXX
169
- const escaped = hex4_to_u16_swar(block);
170
-
171
- store<u16>(bs.offset, escaped);
172
- store<u64>(bs.offset, load<u64>(srcIdx, 12), 2);
173
-
158
+ // Escape block: one whole-block store covers the plain prefix.
159
+ store<v128>(bs.offset, block);
160
+ const laneIdx = usize(ctz(mask) << 1);
161
+ bs.offset += laneIdx;
162
+ const srcIdx = srcStart + laneIdx;
163
+ const code = load<u16>(srcIdx, 2);
164
+ if (code !== 0x75) {
165
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
174
166
  bs.offset += 2;
175
- if (laneIdx >= 6) {
176
- srcStart += laneIdx - 4;
177
- }
178
- lastLane = laneIdx + 12;
179
- } while (mask !== 0);
180
-
181
- if (lastLane < 16) {
182
- bs.offset += 16 - lastLane;
167
+ srcStart = srcIdx + 4;
168
+ } else {
169
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
170
+ bs.offset += 2;
171
+ srcStart = srcIdx + 12;
183
172
  }
184
-
185
- srcStart += 16 + srcChg;
186
173
  }
187
174
 
175
+ // scalar tail (< 16 bytes remaining)
188
176
  while (srcStart < srcEnd) {
189
- const block = load<u16>(srcStart);
190
- store<u16>(bs.offset, block);
191
- srcStart += 2;
192
-
193
- // Early exit
194
- if (block !== 0x5c) {
177
+ const char = load<u16>(srcStart);
178
+ if (char != BACK_SLASH) {
179
+ store<u16>(bs.offset, char);
195
180
  bs.offset += 2;
181
+ srcStart += 2;
196
182
  continue;
197
183
  }
198
-
199
- const code = load<u16>(srcStart);
184
+ const code = load<u16>(srcStart, 2);
200
185
  if (code !== 0x75) {
201
- // Short escapes (\n \t \" \\)
202
- const escape = load<u16>(DESERIALIZE_ESCAPE_TABLE + code);
203
- store<u16>(bs.offset, escape);
204
- srcStart += 2;
186
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
187
+ bs.offset += 2;
188
+ srcStart += 4;
205
189
  } else {
206
- // Unicode escape (\uXXXX)
207
- const block = load<u64>(srcStart, 2); // XXXX
208
- const escaped = hex4_to_u16_swar(block);
209
- store<u16>(bs.offset, escaped);
210
- srcStart += 10;
190
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
191
+ bs.offset += 2;
192
+ srcStart += 12;
211
193
  }
212
- bs.offset += 2;
213
194
  }
214
195
 
215
196
  return bs.sliceOut<string>(outStart);
@@ -269,8 +250,148 @@ export function deserializeString_SIMD(srcStart: usize, srcEnd: usize): string {
269
250
  return copyStringFromSource_SIMD(payloadStart, srcEnd - payloadStart);
270
251
  }
271
252
 
272
- // @ts-expect-error: @inline is a valid decorator
273
- @inline export function deserializeStringField_SIMD<T extends string | null>(
253
+ // Vectorized escaped scanner for the field path. `escapeStart` points at the
254
+ // first `\` located by the caller's v128 scan. Output is assembled in the
255
+ // reused `bs` scratch buffer, then written once via writeStringToField_SIMD.
256
+ //
257
+ // Strategy (validated against run-copy and pure-stream variants across escape
258
+ // densities — see __benches__/custom/simd-string-deser-variants-h2h):
259
+ // * Escape-bearing block: a single whole-block v128 store copies the plain
260
+ // prefix for free; then the escape is decoded scalar.
261
+ // * Clean block: stream the first one cheaply, but if the clean run keeps
262
+ // going, switch to one bulk memory.copy for the remainder — bandwidth-
263
+ // optimal on long sparse runs, avoiding a per-block-store cliff on large
264
+ // inputs. This dominates both alternatives: stream-cheap on dense escapes,
265
+ // memcpy-fast on long runs.
266
+ //
267
+ // Deliberately NOT @inline: cold escape path. As an @inline it was inlined into
268
+ // deserializeStringField_SIMD at every struct string-field site, exploding
269
+ // `--converge` compile time ~24x on large schemas (4s → 99s) for no runtime
270
+ // gain. The hot no-escape scan + writeStringToField stay inline in the caller.
271
+ function deserializeEscapedStringField_SIMD(
272
+ payloadStart: usize,
273
+ escapeStart: usize,
274
+ srcEnd: usize,
275
+ dstFieldPtr: usize,
276
+ ): usize {
277
+ const prefixLen = <u32>(escapeStart - payloadStart);
278
+ bs.offset = bs.buffer;
279
+ bs.ensureSize(<u32>(srcEnd - payloadStart) + 16); // +16 slack for overcopy
280
+ if (prefixLen != 0) {
281
+ memory.copy(bs.buffer, payloadStart, prefixLen);
282
+ bs.offset += prefixLen;
283
+ }
284
+
285
+ let srcStart = escapeStart;
286
+ const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
287
+
288
+ while (srcStart <= srcEnd16) {
289
+ const block = load<v128>(srcStart);
290
+ const mask = i16x8.bitmask(
291
+ v128.or(i16x8.eq(block, SPLAT_5C), i16x8.eq(block, SPLAT_22)),
292
+ );
293
+ if (mask == 0) {
294
+ // Stream the first clean block cheaply.
295
+ store<v128>(bs.offset, block);
296
+ bs.offset += 16;
297
+ srcStart += 16;
298
+ // If the clean run continues, bulk-copy the remainder in one shot.
299
+ if (srcStart <= srcEnd16) {
300
+ const b2 = load<v128>(srcStart);
301
+ if (
302
+ i16x8.bitmask(
303
+ v128.or(i16x8.eq(b2, SPLAT_5C), i16x8.eq(b2, SPLAT_22)),
304
+ ) == 0
305
+ ) {
306
+ const runStart = srcStart;
307
+ srcStart += 16;
308
+ while (srcStart <= srcEnd16) {
309
+ const b3 = load<v128>(srcStart);
310
+ if (
311
+ i16x8.bitmask(
312
+ v128.or(i16x8.eq(b3, SPLAT_5C), i16x8.eq(b3, SPLAT_22)),
313
+ ) != 0
314
+ )
315
+ break;
316
+ srcStart += 16;
317
+ }
318
+ const runLen = <u32>(srcStart - runStart);
319
+ memory.copy(bs.offset, runStart, runLen);
320
+ bs.offset += runLen;
321
+ }
322
+ }
323
+ continue;
324
+ }
325
+
326
+ // Escape/quote block: one whole-block store covers the plain prefix.
327
+ store<v128>(bs.offset, block);
328
+ const laneIdx = usize(ctz(mask) << 1);
329
+ bs.offset += laneIdx;
330
+ const srcIdx = srcStart + laneIdx;
331
+ const char = load<u16>(srcIdx);
332
+ if (char == QUOTE) {
333
+ writeStringToField_SIMD(
334
+ dstFieldPtr,
335
+ bs.buffer,
336
+ <u32>(bs.offset - bs.buffer),
337
+ );
338
+ bs.offset = bs.buffer;
339
+ return srcIdx + 2;
340
+ }
341
+
342
+ const code = load<u16>(srcIdx, 2);
343
+ if (code !== 0x75) {
344
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
345
+ bs.offset += 2;
346
+ srcStart = srcIdx + 4;
347
+ } else {
348
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
349
+ bs.offset += 2;
350
+ srcStart = srcIdx + 12;
351
+ }
352
+ }
353
+
354
+ // scalar tail (< 16 bytes remaining): emit chars directly.
355
+ while (srcStart < srcEnd) {
356
+ const char = load<u16>(srcStart);
357
+ if (char == QUOTE) {
358
+ writeStringToField_SIMD(
359
+ dstFieldPtr,
360
+ bs.buffer,
361
+ <u32>(bs.offset - bs.buffer),
362
+ );
363
+ bs.offset = bs.buffer;
364
+ return srcStart + 2;
365
+ }
366
+ if (char != BACK_SLASH) {
367
+ store<u16>(bs.offset, char);
368
+ bs.offset += 2;
369
+ srcStart += 2;
370
+ continue;
371
+ }
372
+
373
+ const code = load<u16>(srcStart, 2);
374
+ if (code !== 0x75) {
375
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
376
+ bs.offset += 2;
377
+ srcStart += 4;
378
+ } else {
379
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
380
+ bs.offset += 2;
381
+ srcStart += 12;
382
+ }
383
+ }
384
+
385
+ bs.offset = bs.buffer;
386
+ abort("Unterminated string literal");
387
+ return srcStart;
388
+ }
389
+
390
+ // NOT @inline: as an @inline entry, binaryen inlined the (loop-bearing) escaped
391
+ // scanner into every per-field copy, exploding `large` SIMD compile ~24x
392
+ // (4s→99s, 221KB→555KB wasm). Kept as a single shared function — matches
393
+ // deserializeStringField_SWAR (also non-inline) and costs only one call/field.
394
+ export function deserializeStringField_SIMD<T extends string | null>(
274
395
  srcStart: usize,
275
396
  srcEnd: usize,
276
397
  dstObj: usize,
@@ -280,43 +401,40 @@ export function deserializeString_SIMD(srcStart: usize, srcEnd: usize): string {
280
401
  if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
281
402
  abort("Expected leading quote");
282
403
 
283
- const quotedStart = srcStart;
284
404
  const payloadStart = srcStart + 2;
285
405
  const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
286
406
  srcStart = payloadStart;
287
407
 
288
408
  while (srcStart <= srcEnd16) {
289
409
  const block = load<v128>(srcStart);
290
- let mask = i16x8.bitmask(
410
+ const mask = i16x8.bitmask(
291
411
  v128.or(i16x8.eq(block, SPLAT_5C), i16x8.eq(block, SPLAT_22)),
292
412
  );
293
-
294
413
  if (mask == 0) {
295
414
  srcStart += 16;
296
415
  continue;
297
416
  }
298
417
 
299
- do {
300
- const laneIdx = usize(ctz(mask) << 1);
301
- mask &= mask - 1;
302
- const srcIdx = srcStart + laneIdx;
303
- const char = load<u16>(srcIdx);
304
-
305
- if (char == QUOTE) {
306
- writeStringToField_SIMD(
307
- dstFieldPtr,
308
- payloadStart,
309
- <u32>(srcIdx - payloadStart),
310
- );
311
- return srcIdx + 2;
312
- }
313
-
314
- if (char == BACK_SLASH) {
315
- return deserializeStringField_SWAR<T>(quotedStart, srcEnd, dstFieldPtr);
316
- }
317
- } while (mask != 0);
318
-
319
- srcStart += 16;
418
+ const laneIdx = usize(ctz(mask) << 1);
419
+ const srcIdx = srcStart + laneIdx;
420
+ const char = load<u16>(srcIdx);
421
+ if (char == QUOTE) {
422
+ writeStringToField_SIMD(
423
+ dstFieldPtr,
424
+ payloadStart,
425
+ <u32>(srcIdx - payloadStart),
426
+ );
427
+ return srcIdx + 2;
428
+ }
429
+ // backslash → vectorized escaped scan (no more SWAR fallback)
430
+ return inline.always(
431
+ deserializeEscapedStringField_SIMD(
432
+ payloadStart,
433
+ srcIdx,
434
+ srcEnd,
435
+ dstFieldPtr,
436
+ ),
437
+ );
320
438
  }
321
439
 
322
440
  while (srcStart < srcEnd) {
@@ -330,7 +448,14 @@ export function deserializeString_SIMD(srcStart: usize, srcEnd: usize): string {
330
448
  return srcStart + 2;
331
449
  }
332
450
  if (char == BACK_SLASH) {
333
- return deserializeStringField_SWAR<T>(quotedStart, srcEnd, dstFieldPtr);
451
+ return inline.always(
452
+ deserializeEscapedStringField_SIMD(
453
+ payloadStart,
454
+ srcStart,
455
+ srcEnd,
456
+ dstFieldPtr,
457
+ ),
458
+ );
334
459
  }
335
460
  srcStart += 2;
336
461
  }
@@ -1,4 +1,5 @@
1
1
  import { JSON } from "../../..";
2
+ import { deserializeGenericArrayBody } from "./generic";
2
3
  import { ensureArrayField } from "./shared";
3
4
 
4
5
 
@@ -7,6 +8,9 @@ import { ensureArrayField } from "./shared";
7
8
  srcEnd: usize,
8
9
  fieldPtr: usize,
9
10
  ): usize {
10
- ensureArrayField<JSON.Value[]>(fieldPtr);
11
- throw new Error("Failed to parse JSON!");
11
+ return deserializeGenericArrayBody<JSON.Value[]>(
12
+ srcStart,
13
+ srcEnd,
14
+ ensureArrayField<JSON.Value[]>(fieldPtr),
15
+ );
12
16
  }
@@ -1,10 +1,10 @@
1
1
  import { JSON } from "../../..";
2
2
  import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
3
- import { deserializeFloatArrayInto } from "./float";
4
- import { ensureArrayField, scanValueEnd } from "./shared";
3
+ import { deserializeFloatArrayBody } from "./float";
4
+ import { ensureArrayField, scanValueEnd, skipWhitespace } from "./shared";
5
5
 
6
6
 
7
- @inline export function deserializeArrayArrayInto<T extends unknown[][]>(
7
+ @inline export function deserializeArrayArrayBody<T extends unknown[][]>(
8
8
  srcStart: usize,
9
9
  srcEnd: usize,
10
10
  out: T,
@@ -14,6 +14,7 @@ import { ensureArrayField, scanValueEnd } from "./shared";
14
14
  do {
15
15
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
16
16
  srcStart += 2;
17
+ srcStart = skipWhitespace(srcStart, srcEnd);
17
18
  if (srcStart >= srcEnd) break;
18
19
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
19
20
  out.length = 0;
@@ -29,7 +30,7 @@ import { ensureArrayField, scanValueEnd } from "./shared";
29
30
  value = changetype<valueof<T>>(instantiate<valueof<T>>());
30
31
  out.push(value);
31
32
  }
32
- srcStart = deserializeFloatArrayInto<valueof<T>>(
33
+ srcStart = deserializeFloatArrayBody<valueof<T>>(
33
34
  srcStart,
34
35
  srcEnd,
35
36
  value,
@@ -43,7 +44,7 @@ import { ensureArrayField, scanValueEnd } from "./shared";
43
44
  value = changetype<valueof<T>>(instantiate<valueof<T>>());
44
45
  out.push(value);
45
46
  }
46
- srcStart = deserializeArrayArrayInto<valueof<T>>(
47
+ srcStart = deserializeArrayArrayBody<valueof<T>>(
47
48
  srcStart,
48
49
  srcEnd,
49
50
  value,
@@ -67,14 +68,20 @@ import { ensureArrayField, scanValueEnd } from "./shared";
67
68
  srcStart = valueEnd;
68
69
  }
69
70
 
71
+ srcStart = skipWhitespace(srcStart, srcEnd);
70
72
  const code = load<u16>(srcStart);
71
73
  if (code == COMMA) {
72
74
  srcStart += 2;
75
+ srcStart = skipWhitespace(srcStart, srcEnd);
73
76
  index++;
74
77
  continue;
75
78
  }
76
79
  if (code == BRACKET_RIGHT) {
77
- out.length = index + 1;
80
+ // Skip the runtime `ensureCapacity` call when the length is already
81
+ // correct (the array is being reused with the same shape, e.g.
82
+ // canada's geometry rings across repeated parses).
83
+ const nextLen = index + 1;
84
+ if (out.length != nextLen) out.length = nextLen;
78
85
  return srcStart + 2;
79
86
  }
80
87
  break;
@@ -90,7 +97,7 @@ import { ensureArrayField, scanValueEnd } from "./shared";
90
97
  srcEnd: usize,
91
98
  fieldPtr: usize,
92
99
  ): usize {
93
- return deserializeArrayArrayInto<T>(
100
+ return deserializeArrayArrayBody<T>(
94
101
  srcStart,
95
102
  srcEnd,
96
103
  ensureArrayField<T>(fieldPtr),
@@ -5,10 +5,11 @@ import {
5
5
  FALSE_WORD_U64,
6
6
  TRUE_WORD_U64,
7
7
  } from "../../../custom/chars";
8
+ import { isSpace } from "../../../util";
8
9
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
9
10
 
10
11
 
11
- @inline export function deserializeBooleanArrayInto<T extends boolean[]>(
12
+ @inline function deserializeBooleanArrayBody<T extends boolean[]>(
12
13
  srcStart: usize,
13
14
  srcEnd: usize,
14
15
  out: T,
@@ -18,6 +19,7 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
18
19
  do {
19
20
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
20
21
  srcStart += 2;
22
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
21
23
  if (srcStart >= srcEnd) break;
22
24
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
23
25
  out.length = 0;
@@ -37,15 +39,18 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
37
39
  break;
38
40
  }
39
41
 
42
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
40
43
  if (srcStart >= srcEnd) break;
41
44
  const code = load<u16>(srcStart);
42
45
  if (code == COMMA) {
43
46
  srcStart += 2;
47
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
44
48
  index++;
45
49
  continue;
46
50
  }
47
51
  if (code == BRACKET_RIGHT) {
48
- out.length = index + 1;
52
+ const nextLen = index + 1;
53
+ if (out.length != nextLen) out.length = nextLen;
49
54
  return srcStart + 2;
50
55
  }
51
56
  break;
@@ -61,7 +66,7 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
61
66
  srcEnd: usize,
62
67
  fieldPtr: usize,
63
68
  ): usize {
64
- return deserializeBooleanArrayInto<T>(
69
+ return deserializeBooleanArrayBody<T>(
65
70
  srcStart,
66
71
  srcEnd,
67
72
  ensureArrayField<T>(fieldPtr),
@@ -1,4 +1,5 @@
1
1
  import { JSON } from "../../..";
2
+ import { deserializeGenericArrayBody } from "./generic";
2
3
  import { ensureArrayField } from "./shared";
3
4
 
4
5
 
@@ -7,6 +8,9 @@ import { ensureArrayField } from "./shared";
7
8
  srcEnd: usize,
8
9
  fieldPtr: usize,
9
10
  ): usize {
10
- ensureArrayField<T>(fieldPtr);
11
- throw new Error("Failed to parse JSON!");
11
+ return deserializeGenericArrayBody<T>(
12
+ srcStart,
13
+ srcEnd,
14
+ ensureArrayField<T>(fieldPtr),
15
+ );
12
16
  }