json-as 1.3.7 → 1.3.8

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 +32 -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 +153 -236
  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
@@ -52,101 +52,103 @@ import { hex4_to_u16_swar } from "../../util/swar";
52
52
  return changetype<string>(out);
53
53
  }
54
54
 
55
+ // Standalone (whole-value) escaped scanner. Quotes are already stripped, so
56
+ // `srcEnd` is the payload end and only `\` escapes are handled. Same HYBRID
57
+ // strategy as the field scanner: escape blocks use a free optimistic u64 store
58
+ // for the plain prefix; clean runs stream the first block then bulk-memcpy the
59
+ // remainder. The `backslash_mask_unsafe` hits are confirmed scalarly.
60
+ //
61
+ // NOTE: vs the prior overflow scanner this is faster on dense and sparse
62
+ // escaping but ~20% slower on sustained moderate-density escaping (escape
63
+ // every ~20 chars), where multi-escape-per-block had an edge.
55
64
  // @ts-expect-error: @inline is a valid decorator
56
65
  @inline function deserializeEscapedString_SWAR(
57
66
  payloadStart: usize,
58
67
  escapeStart: usize,
59
68
  srcEnd: usize,
60
69
  ): string {
61
- const srcEnd8 = srcEnd - 8;
62
70
  const prefixLen = <u32>(escapeStart - payloadStart);
63
71
  const outStart = bs.offset - bs.buffer;
64
- bs.ensureSize(<u32>(srcEnd - payloadStart));
72
+ bs.ensureSize(<u32>(srcEnd - payloadStart) + 8); // +8 slack for u64 overcopy
65
73
  if (prefixLen != 0) {
66
74
  memory.copy(bs.offset, payloadStart, prefixLen);
67
75
  bs.offset += prefixLen;
68
76
  }
69
77
 
70
78
  let srcStart = escapeStart;
79
+ const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
71
80
 
72
- while (srcStart < srcEnd8) {
81
+ while (srcStart <= srcEnd8) {
73
82
  const block = load<u64>(srcStart);
74
- store<u64>(bs.offset, block);
75
-
76
83
  let mask = inline.always(backslash_mask_unsafe(block));
77
-
78
- // Early exit
79
- if (mask === 0) {
80
- srcStart += 8;
84
+ if (mask == 0) {
85
+ store<u64>(bs.offset, block);
81
86
  bs.offset += 8;
87
+ srcStart += 8;
88
+ if (
89
+ srcStart <= srcEnd8 &&
90
+ inline.always(backslash_mask_unsafe(load<u64>(srcStart))) == 0
91
+ ) {
92
+ const runStart = srcStart;
93
+ srcStart += 8;
94
+ while (
95
+ srcStart <= srcEnd8 &&
96
+ inline.always(backslash_mask_unsafe(load<u64>(srcStart))) == 0
97
+ ) {
98
+ srcStart += 8;
99
+ }
100
+ const runLen = <u32>(srcStart - runStart);
101
+ memory.copy(bs.offset, runStart, runLen);
102
+ bs.offset += runLen;
103
+ }
82
104
  continue;
83
105
  }
84
106
 
107
+ store<u64>(bs.offset, block);
108
+ let handled = false;
85
109
  do {
86
- const laneIdx = usize(ctz(mask) >> 3); // 0 2 4 6
110
+ const laneIdx = usize(ctz(mask) >> 3);
87
111
  mask &= mask - 1;
88
112
  const srcIdx = srcStart + laneIdx;
89
- const dstIdx = bs.offset + laneIdx;
90
- const header = load<u32>(srcIdx);
91
- const code = <u16>(header >> 16);
92
-
93
- // Detect false positive (code unit where low byte is 0x5C)
94
- if ((header & 0xffff) !== 0x5c) continue;
95
-
96
- // Hot path (negative bias)
113
+ if ((load<u32>(srcIdx) & 0xffff) !== 0x5c) continue; // false positive
114
+ bs.offset += laneIdx;
115
+ const code = load<u16>(srcIdx, 2);
97
116
  if (code !== 0x75) {
98
- // Short escapes (\n \t \" \\)
99
- const escaped = load<u16>(DESERIALIZE_ESCAPE_TABLE + code);
100
- mask &= mask - usize(escaped === 0x5c);
101
- store<u16>(dstIdx, escaped);
102
- store<u32>(dstIdx, load<u32>(srcIdx, 4), 2);
103
-
104
- const l6 = usize(laneIdx === 6);
105
- bs.offset -= (1 - l6) << 1;
106
- srcStart += l6 << 1;
107
- continue;
117
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
118
+ bs.offset += 2;
119
+ srcStart = srcIdx + 4;
120
+ } else {
121
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
122
+ bs.offset += 2;
123
+ srcStart = srcIdx + 12;
108
124
  }
109
-
110
- // Unicode escape (\uXXXX)
111
- const block = load<u64>(srcIdx, 4); // XXXX
112
- const escaped = hex4_to_u16_swar(block);
113
- store<u16>(dstIdx, escaped);
114
- // store<u64>(dstIdx, load<u32>(srcIdx, 12), 2);
115
- srcStart += 4 + laneIdx;
116
- bs.offset -= 6 - laneIdx;
117
- } while (mask !== 0);
118
-
119
- bs.offset += 8;
120
- srcStart += 8;
125
+ handled = true;
126
+ break;
127
+ } while (mask != 0);
128
+ if (!handled) {
129
+ bs.offset += 8;
130
+ srcStart += 8;
131
+ }
121
132
  }
122
133
 
123
134
  while (srcStart < srcEnd) {
124
- const block = load<u16>(srcStart);
125
- store<u16>(bs.offset, block);
126
- srcStart += 2;
127
-
128
- // Early exit
129
- if (block !== 0x5c) {
135
+ const char = load<u16>(srcStart);
136
+ if (char != BACK_SLASH) {
137
+ store<u16>(bs.offset, char);
130
138
  bs.offset += 2;
139
+ srcStart += 2;
131
140
  continue;
132
141
  }
133
-
134
- const code = load<u16>(srcStart);
142
+ const code = load<u16>(srcStart, 2);
135
143
  if (code !== 0x75) {
136
- // Short escapes (\n \t \" \\)
137
- const block = load<u16>(srcStart);
138
- const escape = load<u16>(DESERIALIZE_ESCAPE_TABLE + block);
139
- store<u16>(bs.offset, escape);
140
- srcStart += 2;
144
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
145
+ bs.offset += 2;
146
+ srcStart += 4;
141
147
  } else {
142
- // Unicode escape (\uXXXX)
143
- const block = load<u64>(srcStart, 2); // XXXX
144
- const escaped = hex4_to_u16_swar(block);
145
- store<u16>(bs.offset, escaped);
146
- srcStart += 10;
148
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
149
+ bs.offset += 2;
150
+ srcStart += 12;
147
151
  }
148
-
149
- bs.offset += 2;
150
152
  }
151
153
  return bs.sliceOut<string>(outStart);
152
154
  }
@@ -245,45 +247,72 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
245
247
  memory.copy(stringPtr, srcStart, byteLength);
246
248
  }
247
249
 
248
- // Scans a quoted string value, writes into the destination field, and returns next unread src pointer.
250
+ // Scans a quoted string value, writes into the destination field, and returns
251
+ // the next unread src pointer.
252
+ //
253
+ // HYBRID strategy (validated against the prior run-copy scanner across escape
254
+ // densities — see __benches__/custom/swar-string-deser-hybrid-h2h: +17–70%):
255
+ // * Escape-bearing block: one optimistic whole-block u64 store copies the
256
+ // plain prefix for free, then the (scalar-confirmed) escape is decoded.
257
+ // * Clean block: stream the first one, then if the clean run continues switch
258
+ // to one bulk memory.copy for the remainder.
259
+ // SWAR masks carry high-byte false positives, so each hit is confirmed
260
+ // scalarly before acting.
249
261
  // @ts-expect-error: @inline is a valid decorator
250
- @inline function deserializeEscapedStringScan_SWAR_SplitTuned(
262
+ @inline function deserializeEscapedStringField_SWAR(
251
263
  payloadStart: usize,
252
264
  escapeStart: usize,
253
265
  srcEnd: usize,
254
266
  dstFieldPtr: usize,
255
267
  ): usize {
256
268
  const prefixLen = <u32>(escapeStart - payloadStart);
257
- const srcEnd8 = srcEnd - 8;
258
269
  bs.offset = bs.buffer;
259
- bs.ensureSize(<u32>(srcEnd - payloadStart));
270
+ bs.ensureSize(<u32>(srcEnd - payloadStart) + 8); // +8 slack for u64 overcopy
260
271
  if (prefixLen != 0) {
261
272
  memory.copy(bs.buffer, payloadStart, prefixLen);
262
273
  bs.offset += prefixLen;
263
274
  }
264
275
 
265
- let lastPtr = escapeStart;
266
276
  let srcStart = escapeStart;
277
+ const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
267
278
 
268
279
  while (srcStart <= srcEnd8) {
269
- const blockStart = srcStart;
270
- let mask = inline.always(backslash_or_quote_mask(load<u64>(srcStart)));
271
- if (mask === 0) {
280
+ const block = load<u64>(srcStart);
281
+ let mask = inline.always(backslash_or_quote_mask(block));
282
+ if (mask == 0) {
283
+ store<u64>(bs.offset, block);
284
+ bs.offset += 8;
272
285
  srcStart += 8;
286
+ if (
287
+ srcStart <= srcEnd8 &&
288
+ inline.always(backslash_or_quote_mask(load<u64>(srcStart))) == 0
289
+ ) {
290
+ const runStart = srcStart;
291
+ srcStart += 8;
292
+ while (
293
+ srcStart <= srcEnd8 &&
294
+ inline.always(backslash_or_quote_mask(load<u64>(srcStart))) == 0
295
+ ) {
296
+ srcStart += 8;
297
+ }
298
+ const runLen = <u32>(srcStart - runStart);
299
+ memory.copy(bs.offset, runStart, runLen);
300
+ bs.offset += runLen;
301
+ }
273
302
  continue;
274
303
  }
275
304
 
305
+ // Escape/quote block (mask may carry high-byte false positives).
306
+ store<u64>(bs.offset, block);
307
+ let handled = false;
276
308
  do {
277
309
  const laneIdx = usize(ctz(mask) >> 3);
278
310
  mask &= mask - 1;
279
311
  const srcIdx = srcStart + laneIdx;
280
312
  const char = load<u16>(srcIdx);
313
+ if (char != QUOTE && char != BACK_SLASH) continue; // false positive
314
+ bs.offset += laneIdx;
281
315
  if (char == QUOTE) {
282
- const runLen = <u32>(srcIdx - lastPtr);
283
- if (runLen != 0) {
284
- memory.copy(bs.offset, lastPtr, runLen);
285
- bs.offset += runLen;
286
- }
287
316
  writeStringToField(
288
317
  dstFieldPtr,
289
318
  bs.buffer,
@@ -292,54 +321,39 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
292
321
  bs.offset = bs.buffer;
293
322
  return srcIdx + 2;
294
323
  }
295
- if (char != BACK_SLASH) continue;
296
-
297
- const runLen = <u32>(srcIdx - lastPtr);
298
- if (runLen != 0) {
299
- memory.copy(bs.offset, lastPtr, runLen);
300
- bs.offset += runLen;
301
- }
302
-
303
- const chunk = load<u32>(srcIdx);
304
- const code = <u16>(chunk >> 16);
324
+ const code = load<u16>(srcIdx, 2);
305
325
  if (code !== 0x75) {
306
326
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
307
327
  bs.offset += 2;
308
- lastPtr = srcIdx + 4;
328
+ srcStart = srcIdx + 4;
309
329
  } else {
310
330
  store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
311
331
  bs.offset += 2;
312
- lastPtr = srcIdx + 12;
332
+ srcStart = srcIdx + 12;
313
333
  }
314
- srcStart = lastPtr;
334
+ handled = true;
315
335
  break;
316
- } while (mask !== 0);
317
- if (srcStart == blockStart) srcStart += 8;
336
+ } while (mask != 0);
337
+ if (!handled) {
338
+ bs.offset += 8;
339
+ srcStart += 8;
340
+ }
318
341
  }
319
342
 
343
+ // scalar tail (< 8 bytes remaining)
320
344
  while (srcStart < srcEnd) {
321
345
  const char = load<u16>(srcStart);
322
346
  if (char == QUOTE) {
323
- const runLen = <u32>(srcStart - lastPtr);
324
- if (runLen != 0) {
325
- memory.copy(bs.offset, lastPtr, runLen);
326
- bs.offset += runLen;
327
- }
328
347
  writeStringToField(dstFieldPtr, bs.buffer, <u32>(bs.offset - bs.buffer));
329
348
  bs.offset = bs.buffer;
330
349
  return srcStart + 2;
331
350
  }
332
351
  if (char != BACK_SLASH) {
352
+ store<u16>(bs.offset, char);
353
+ bs.offset += 2;
333
354
  srcStart += 2;
334
355
  continue;
335
356
  }
336
-
337
- const runLen = <u32>(srcStart - lastPtr);
338
- if (runLen != 0) {
339
- memory.copy(bs.offset, lastPtr, runLen);
340
- bs.offset += runLen;
341
- }
342
-
343
357
  const code = load<u16>(srcStart, 2);
344
358
  if (code !== 0x75) {
345
359
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -350,7 +364,6 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
350
364
  bs.offset += 2;
351
365
  srcStart += 12;
352
366
  }
353
- lastPtr = srcStart;
354
367
  }
355
368
 
356
369
  bs.offset = bs.buffer;
@@ -462,8 +475,10 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
462
475
  export function deserializeStringField_SWAR<T extends string | null>(
463
476
  srcStart: usize,
464
477
  srcEnd: usize,
465
- dstFieldPtr: usize,
478
+ dstObj: usize,
479
+ dstOffset: usize = 0,
466
480
  ): usize {
481
+ const dstFieldPtr = dstObj + dstOffset;
467
482
  if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
468
483
  abort("Expected leading quote");
469
484
 
@@ -506,7 +521,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
506
521
  }
507
522
  if (char != BACK_SLASH) continue;
508
523
  return inline.always(
509
- deserializeEscapedStringScan_SWAR_SplitTuned(
524
+ deserializeEscapedStringField_SWAR(
510
525
  payloadStart,
511
526
  srcIdx,
512
527
  srcEnd,
@@ -530,7 +545,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
530
545
  }
531
546
  if (char == BACK_SLASH) {
532
547
  return inline.always(
533
- deserializeEscapedStringScan_SWAR_SplitTuned(
548
+ deserializeEscapedStringField_SWAR(
534
549
  payloadStart,
535
550
  srcStart,
536
551
  srcEnd,
@@ -0,0 +1,224 @@
1
+ // SWAR-mode TypedArray / ArrayBuffer deserializer.
2
+ //
3
+ // The naive variant in `../naive/typedarray.ts` does two scalar passes
4
+ // (count digit-starts, then call `JSON.__deserialize` per element which
5
+ // re-scans the same digits). This rewrite replaces both passes:
6
+ //
7
+ // - **No count pass.** TypedArrays have a fixed length at construction,
8
+ // so the natural approach is to count first then allocate. We tried
9
+ // that with a SWAR comma counter — it cut the per-element cost but
10
+ // kept us ~30% below the top-level `f64[]` path because the count
11
+ // scan still touched the whole input twice. Instead we allocate
12
+ // worst-case (`(srcEnd - srcStart) >> 2 + 1` elements — each
13
+ // element needs >= "D," = 2 UTF-16 chars = 4 bytes) and `__renew`
14
+ // the underlying buffer down to the exact byte count after parsing.
15
+ // The over-allocation peaks at ~2-3× the final size for typical
16
+ // payloads; the trim is a single `memory.copy` on the GC's terms.
17
+ //
18
+ // - **Inline parse.** The integer parsers come from `./array/integer.ts`
19
+ // (refactored to take element type `E` so the same
20
+ // `parseSignedIntegerSWAR` serves `Array<i32>` and `Int32Array`).
21
+ // The float parser comes from `./array/float.ts`. Stores write
22
+ // directly to `dataStart + index * elementSize`, bypassing the
23
+ // typed-array's bounds-checked `[]=` setter.
24
+
25
+ import { isSpace } from "../../util";
26
+ import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../custom/chars";
27
+ import { parseFloatElementSWAR } from "./array/float";
28
+ import {
29
+ parseSignedIntegerSWAR,
30
+ parseUnsignedIntegerSWAR,
31
+ } from "./array/integer";
32
+
33
+ /**
34
+ * SWAR TypedArray deserializer.
35
+ *
36
+ * Counts commas (with empty-body detection), allocates the typed array
37
+ * at the exact size, then parses each element inline with no per-call
38
+ * function dispatch. Stores write directly to `dataStart + idx *
39
+ * elementSize`, bypassing the typed-array's bounds-checked `[]=` setter.
40
+ *
41
+ * Falls through to the underlying SWAR float / integer parsers; the
42
+ * element type (`f32/f64/u8/i32/...`) is detected via `isFloat<E>()` /
43
+ * `isSigned<E>()` and AS folds the type dispatch at compile time.
44
+ */
45
+ /**
46
+ * Worst-case element count: each element occupies >= 1 digit + 1
47
+ * delimiter = 2 UTF-16 chars = 4 bytes. So `(srcEnd - srcStart) >> 2`
48
+ * upper-bounds the count. Allocating to worst-case lets us skip a
49
+ * full count pass over the input — at the cost of an over-allocated
50
+ * underlying buffer that we trim via `__renew` once we know the
51
+ * actual element count.
52
+ *
53
+ * For a top-level f64[] payload of ~64 MiB JSON encoding 6M floats,
54
+ * worst-case alloc is ~16M f64 = 128 MB temporarily. We trim back
55
+ * to ~48 MB after parse. The trim is a wasm `memory.copy` (or just
56
+ * a length update if the runtime supports in-place shrink), much
57
+ * cheaper than a second 64 MB scan over the input.
58
+ */
59
+ export function deserializeTypedArray_SWAR<T extends ArrayLike<number>>(
60
+ srcStart: usize,
61
+ srcEnd: usize,
62
+ dst: usize = 0,
63
+ ): T {
64
+ // Find the opening `[`, then skip whitespace to the first non-WS char.
65
+ while (srcStart < srcEnd) {
66
+ const ch = load<u16>(srcStart);
67
+ if (ch == BRACKET_LEFT) {
68
+ srcStart += 2;
69
+ break;
70
+ }
71
+ srcStart += 2;
72
+ }
73
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
74
+
75
+ // Empty-array fast path.
76
+ if (srcStart >= srcEnd || load<u16>(srcStart) == BRACKET_RIGHT) {
77
+ let out = changetype<T>(dst || changetype<usize>(instantiate<T>(0)));
78
+ if (out.length != 0) out = changetype<T>(instantiate<T>(0));
79
+ return out;
80
+ }
81
+
82
+ const elementSize = sizeof<valueof<T>>();
83
+ const maxElements = i32((<usize>(srcEnd - srcStart)) >> 2) + 1;
84
+ let out = changetype<T>(
85
+ dst || changetype<usize>(instantiate<T>(maxElements)),
86
+ );
87
+ if (out.length != maxElements) {
88
+ out = changetype<T>(instantiate<T>(maxElements));
89
+ }
90
+
91
+ const dataStart = out.dataStart;
92
+ let writePtr = dataStart;
93
+
94
+ // Parse loop. Each element parses into the slot at `writePtr`, then
95
+ // the separator (`,` or `]`) is consumed. Whitespace surrounding the
96
+ // separator is skipped to match the naive variant's behaviour.
97
+ while (srcStart < srcEnd) {
98
+ let next: usize = 0;
99
+ if (isFloat<valueof<T>>()) {
100
+ next = parseFloatElementSWAR<valueof<T>>(srcStart, srcEnd, writePtr);
101
+ } else if (isSigned<valueof<T>>()) {
102
+ next = parseSignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr);
103
+ } else {
104
+ next = parseUnsignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr);
105
+ }
106
+ if (!next) break;
107
+ writePtr += elementSize;
108
+ srcStart = next;
109
+ if (srcStart >= srcEnd) break;
110
+
111
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
112
+ if (srcStart >= srcEnd) break;
113
+ const ch = load<u16>(srcStart);
114
+ if (ch == COMMA) {
115
+ srcStart += 2;
116
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
117
+ continue;
118
+ }
119
+ if (ch == BRACKET_RIGHT) break;
120
+ break;
121
+ }
122
+
123
+ // Trim to actual count. `out.length =` on a typed-array isn't legal
124
+ // (length is read-only), so we shrink the underlying ArrayBufferView
125
+ // directly: `__renew` the buffer to the actual byte length and
126
+ // update the view's `byteLength` and `dataStart`. AS's TypedArray
127
+ // structure has `buffer`, `dataStart` (= buffer), `byteLength`
128
+ // (capacity in bytes) in that order — same layout as ArrayBufferView.
129
+ const actualCount = i32(<usize>(writePtr - dataStart) / elementSize);
130
+ if (actualCount != maxElements) {
131
+ const actualBytes = <usize>actualCount * elementSize;
132
+ const oldBuffer = changetype<ArrayBuffer>(
133
+ load<usize>(changetype<usize>(out)),
134
+ );
135
+ const newBuffer = __renew(changetype<usize>(oldBuffer), actualBytes);
136
+ // Update buffer, dataStart, byteLength on the view.
137
+ store<usize>(changetype<usize>(out), newBuffer);
138
+ store<usize>(
139
+ changetype<usize>(out),
140
+ newBuffer,
141
+ offsetof<ArrayBufferView>("dataStart"),
142
+ );
143
+ store<i32>(
144
+ changetype<usize>(out),
145
+ i32(actualBytes),
146
+ offsetof<ArrayBufferView>("byteLength"),
147
+ );
148
+ __link(changetype<usize>(out), newBuffer, false);
149
+ }
150
+
151
+ return out;
152
+ }
153
+
154
+ /**
155
+ * SWAR ArrayBuffer deserializer. JSON encoding is `[u8, u8, ...]` so
156
+ * elements are always 1-3 ASCII digits (0..255). We can use the same
157
+ * comma-count + inline-parse strategy as above, but since the result
158
+ * is an `ArrayBuffer` rather than a `TypedArray<E>`, we use a plain
159
+ * `store<u8>` directly.
160
+ */
161
+ export function deserializeArrayBuffer_SWAR(
162
+ srcStart: usize,
163
+ srcEnd: usize,
164
+ dst: usize = 0,
165
+ ): ArrayBuffer {
166
+ while (srcStart < srcEnd) {
167
+ const ch = load<u16>(srcStart);
168
+ if (ch == BRACKET_LEFT) {
169
+ srcStart += 2;
170
+ break;
171
+ }
172
+ srcStart += 2;
173
+ }
174
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
175
+
176
+ if (srcStart >= srcEnd || load<u16>(srcStart) == BRACKET_RIGHT) {
177
+ let out = dst ? changetype<ArrayBuffer>(dst) : new ArrayBuffer(0);
178
+ if (out.byteLength != 0) out = new ArrayBuffer(0);
179
+ return out;
180
+ }
181
+
182
+ // Worst-case byte count: each element is `D,` minimum = 4 bytes.
183
+ const maxBytes = i32((<usize>(srcEnd - srcStart)) >> 2) + 1;
184
+ let out = dst ? changetype<ArrayBuffer>(dst) : new ArrayBuffer(maxBytes);
185
+ if (out.byteLength != maxBytes) {
186
+ out = new ArrayBuffer(maxBytes);
187
+ }
188
+
189
+ const dataStart = changetype<usize>(out);
190
+ let writePtr: usize = 0;
191
+
192
+ while (srcStart < srcEnd) {
193
+ const next = parseUnsignedIntegerSWAR<u8>(
194
+ srcStart,
195
+ srcEnd,
196
+ dataStart + writePtr,
197
+ );
198
+ if (!next) break;
199
+ writePtr += 1;
200
+ srcStart = next;
201
+ if (srcStart >= srcEnd) break;
202
+
203
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
204
+ if (srcStart >= srcEnd) break;
205
+ const ch = load<u16>(srcStart);
206
+ if (ch == COMMA) {
207
+ srcStart += 2;
208
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
209
+ continue;
210
+ }
211
+ if (ch == BRACKET_RIGHT) break;
212
+ break;
213
+ }
214
+
215
+ // Trim to actual byte count via `__renew`.
216
+ const actualBytes = i32(writePtr);
217
+ if (actualBytes != maxBytes) {
218
+ out = changetype<ArrayBuffer>(
219
+ __renew(changetype<usize>(out), <usize>actualBytes),
220
+ );
221
+ }
222
+
223
+ return out;
224
+ }