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
@@ -1,27 +1,25 @@
1
- import { deserializeIntegerField } from "../../index/integer";
2
- import { deserializeUnsignedField } from "../../index/unsigned";
3
- import { deserializeIntegerArray as deserializeIntegerArray_NAIVE } from "../../simple/array/integer";
1
+ import { deserializeIntegerArray_NAIVE } from "../../naive/array/integer";
4
2
  import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
5
3
  import { isSpace } from "../../../util";
6
4
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
7
-
8
- const ASCII_LANE_MASK_4: u64 = 0x00ff00ff00ff00ff;
9
- const ASCII_ZERO_4: u64 = 0x0030003000300030;
10
- const ASCII_RANGE_MASK_4: u64 = 0xfff0fff0fff0fff0;
11
- const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
12
-
13
-
14
- @inline function storeSignedInteger<T extends number[]>(
5
+ import { parse4Digits_PairMul } from "../../../util/swar-int";
6
+
7
+ // Store helpers parameterised on the element type `E` directly, so they
8
+ // serve both `Array<E>` and `TypedArray<E>` callers. The integer-array
9
+ // callers below all pass `valueof<T>` and AS folds the resulting tower of
10
+ // `sizeof<E>` comparisons at compile time — same codegen as the prior
11
+ // `T extends number[]` version, but reusable from `swar/typedarray.ts`.
12
+ @inline function storeSignedIntegerE<E extends number>(
15
13
  slot: usize,
16
14
  value: i64,
17
15
  ): void {
18
- if (sizeof<valueof<T>>() == sizeof<i8>()) {
16
+ if (sizeof<E>() == sizeof<i8>()) {
19
17
  store<i8>(slot, <i8>value);
20
- } else if (sizeof<valueof<T>>() == sizeof<i16>()) {
18
+ } else if (sizeof<E>() == sizeof<i16>()) {
21
19
  store<i16>(slot, <i16>value);
22
- } else if (sizeof<valueof<T>>() == sizeof<i32>()) {
20
+ } else if (sizeof<E>() == sizeof<i32>()) {
23
21
  store<i32>(slot, <i32>value);
24
- } else if (sizeof<valueof<T>>() == sizeof<isize>()) {
22
+ } else if (sizeof<E>() == sizeof<isize>()) {
25
23
  store<isize>(slot, <isize>value);
26
24
  } else {
27
25
  store<i64>(slot, value);
@@ -29,45 +27,33 @@ const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
29
27
  }
30
28
 
31
29
 
32
- @inline function storeUnsignedInteger<T extends number[]>(
30
+ @inline function storeUnsignedIntegerE<E extends number>(
33
31
  slot: usize,
34
32
  value: u64,
35
33
  ): void {
36
- if (sizeof<valueof<T>>() == sizeof<u8>()) {
34
+ if (sizeof<E>() == sizeof<u8>()) {
37
35
  store<u8>(slot, <u8>value);
38
- } else if (sizeof<valueof<T>>() == sizeof<u16>()) {
36
+ } else if (sizeof<E>() == sizeof<u16>()) {
39
37
  store<u16>(slot, <u16>value);
40
- } else if (sizeof<valueof<T>>() == sizeof<u32>()) {
38
+ } else if (sizeof<E>() == sizeof<u32>()) {
41
39
  store<u32>(slot, <u32>value);
42
- } else if (sizeof<valueof<T>>() == sizeof<usize>()) {
40
+ } else if (sizeof<E>() == sizeof<usize>()) {
43
41
  store<usize>(slot, <usize>value);
44
42
  } else {
45
43
  store<u64>(slot, value);
46
44
  }
47
45
  }
48
46
 
49
-
50
- @inline function parse4DigitsASCII(block: u64): u32 {
51
- const digits = (block & ASCII_LANE_MASK_4) - ASCII_ZERO_4;
52
- if (((digits | (digits + ASCII_RANGE_ADD_4)) & ASCII_RANGE_MASK_4) != 0)
53
- return U32.MAX_VALUE;
54
-
55
- return <u32>(
56
- (<u32>(digits & 0xffff) * 1000 +
57
- <u32>((digits >> 16) & 0xffff) * 100 +
58
- <u32>((digits >> 32) & 0xffff) * 10 +
59
- <u32>(digits >> 48))
60
- );
61
- }
62
-
63
47
  // The four parse helpers below take a `slot` pointer (`writePtr`) and store
64
48
  // the value directly via `store<valueof<T>>(slot, ...)`. The outer dispatcher
65
49
  // owns the array's `out.length = maxElements` pre-allocation and the
66
50
  // `writePtr` advance, so the per-element `Array.push` capacity check and
67
51
  // length write are eliminated for every integer width, not just the u8/i8
68
52
  // narrow-lane path.
69
-
70
- @inline function parseSignedIntegerScalar<T extends number[]>(
53
+ // Parsers are also E-parameterised so they're shareable with
54
+ // `swar/typedarray.ts`. The body is byte-identical to the prior version
55
+ // modulo s/valueof<T>/E/.
56
+ @inline export function parseSignedIntegerScalar<E extends number>(
71
57
  srcStart: usize,
72
58
  srcEnd: usize,
73
59
  slot: usize,
@@ -93,12 +79,12 @@ const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
93
79
  srcStart += 2;
94
80
  }
95
81
 
96
- storeSignedInteger<T>(slot, negative ? -(<i64>value) : <i64>value);
82
+ storeSignedIntegerE<E>(slot, negative ? -(<i64>value) : <i64>value);
97
83
  return srcStart;
98
84
  }
99
85
 
100
86
 
101
- @inline function parseUnsignedIntegerScalar<T extends number[]>(
87
+ @inline export function parseUnsignedIntegerScalar<E extends number>(
102
88
  srcStart: usize,
103
89
  srcEnd: usize,
104
90
  slot: usize,
@@ -115,12 +101,12 @@ const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
115
101
  srcStart += 2;
116
102
  }
117
103
 
118
- storeUnsignedInteger<T>(slot, value);
104
+ storeUnsignedIntegerE<E>(slot, value);
119
105
  return srcStart;
120
106
  }
121
107
 
122
108
 
123
- @inline function parseSignedIntegerSWAR<T extends number[]>(
109
+ @inline export function parseSignedIntegerSWAR<E extends number>(
124
110
  srcStart: usize,
125
111
  srcEnd: usize,
126
112
  slot: usize,
@@ -140,12 +126,17 @@ const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
140
126
  let value: u64 = digit;
141
127
  srcStart += 2;
142
128
 
129
+ // Signed scan uses parse4 + scalar only (matches the asymmetric tuning in
130
+ // swar/integer.ts:deserializeIntegerField_SWAR). The leading minus shifts
131
+ // the digit run into parse8's "terminator-in-load" zone where validate-fail
132
+ // is common, so parse4's smaller failure unit wins.
133
+ //
143
134
  // i8 tops out at 3 digits (-128..127), so the 4-digit kernel can never fire
144
135
  // and the failing load + range check just burns cycles. Gate on the lane
145
136
  // width so AS folds the loop away at compile time.
146
- if (sizeof<valueof<T>>() > 1) {
137
+ if (sizeof<E>() > 1) {
147
138
  while (srcStart + 6 < srcEnd) {
148
- const parsed = parse4DigitsASCII(load<u64>(srcStart));
139
+ const parsed = parse4Digits_PairMul(load<u64>(srcStart));
149
140
  if (parsed == U32.MAX_VALUE) break;
150
141
  value = value * 10000 + parsed;
151
142
  srcStart += 8;
@@ -159,12 +150,12 @@ const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
159
150
  srcStart += 2;
160
151
  }
161
152
 
162
- storeSignedInteger<T>(slot, negative ? -(<i64>value) : <i64>value);
153
+ storeSignedIntegerE<E>(slot, negative ? -(<i64>value) : <i64>value);
163
154
  return srcStart;
164
155
  }
165
156
 
166
157
 
167
- @inline function parseUnsignedIntegerSWAR<T extends number[]>(
158
+ @inline export function parseUnsignedIntegerSWAR<E extends number>(
168
159
  srcStart: usize,
169
160
  srcEnd: usize,
170
161
  slot: usize,
@@ -173,7 +164,7 @@ const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
173
164
  // the element terminator, then a fixed-count fold with no per-digit break.
174
165
  // TurboFan tends to schedule this better than a single combined
175
166
  // scan-and-fold loop because the fold has no data-dependent exit.
176
- if (sizeof<valueof<T>>() <= 2) {
167
+ if (sizeof<E>() <= 2) {
177
168
  const first = <u32>load<u16>(srcStart) - 48;
178
169
  if (first > 9) return 0;
179
170
  const lastIndex = srcStart;
@@ -189,7 +180,7 @@ const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
189
180
  value = value * 10 + (<u32>load<u16>(p) - 48);
190
181
  p += 2;
191
182
  }
192
- storeUnsignedInteger<T>(slot, value);
183
+ storeUnsignedIntegerE<E>(slot, value);
193
184
  return srcStart;
194
185
  }
195
186
 
@@ -199,8 +190,14 @@ const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
199
190
  let value: u64 = digit;
200
191
  srcStart += 2;
201
192
 
193
+ // Array unsigned path uses parse4 + scalar (not parse8 + scalar as in the
194
+ // struct-field path swar/integer.ts:deserializeUnsignedField_SWAR). The
195
+ // bench corpus for arrays mixes element widths (e.g. 1/4/7/10 digits in
196
+ // u32-64mib), so most parse8 strides hit the `,` separator mid-load and
197
+ // pay the load+validate cost for a guaranteed miss. parse4 has a smaller
198
+ // failure unit and matches the typical 1-4 digit run between separators.
202
199
  while (srcStart + 6 < srcEnd) {
203
- const parsed = parse4DigitsASCII(load<u64>(srcStart));
200
+ const parsed = parse4Digits_PairMul(load<u64>(srcStart));
204
201
  if (parsed == U32.MAX_VALUE) break;
205
202
  value = value * 10000 + parsed;
206
203
  srcStart += 8;
@@ -213,7 +210,7 @@ const ASCII_RANGE_ADD_4: u64 = 0x0006000600060006;
213
210
  srcStart += 2;
214
211
  }
215
212
 
216
- storeUnsignedInteger<T>(slot, value);
213
+ storeUnsignedIntegerE<E>(slot, value);
217
214
  return srcStart;
218
215
  }
219
216
 
@@ -240,7 +237,6 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
240
237
  let index = 0;
241
238
 
242
239
  out.length = 0;
243
- srcStart = skipIntegerArrayWhitespace(srcStart, srcEnd);
244
240
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) {
245
241
  throw new Error("Failed to parse JSON!");
246
242
  }
@@ -274,7 +270,7 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
274
270
  srcStart += 2;
275
271
  }
276
272
 
277
- storeSignedInteger<T>(
273
+ storeSignedIntegerE<valueof<T>>(
278
274
  ensureArrayElementSlot<T>(out, index),
279
275
  negative ? -(<i64>value) : <i64>value,
280
276
  );
@@ -291,7 +287,10 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
291
287
  srcStart += 2;
292
288
  }
293
289
 
294
- storeUnsignedInteger<T>(ensureArrayElementSlot<T>(out, index), value);
290
+ storeUnsignedIntegerE<valueof<T>>(
291
+ ensureArrayElementSlot<T>(out, index),
292
+ value,
293
+ );
295
294
  }
296
295
 
297
296
  index++;
@@ -355,7 +354,7 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
355
354
 
356
355
  if (sizeof<valueof<T>>() > 1) {
357
356
  while (srcStart + 6 < srcEnd) {
358
- const parsed = parse4DigitsASCII(load<u64>(srcStart));
357
+ const parsed = parse4Digits_PairMul(load<u64>(srcStart));
359
358
  if (parsed == U32.MAX_VALUE) break;
360
359
  value = value * 10000 + parsed;
361
360
  srcStart += 8;
@@ -370,7 +369,7 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
370
369
  }
371
370
 
372
371
  if (index >= reusableLength) break;
373
- storeSignedInteger<T>(
372
+ storeSignedIntegerE<valueof<T>>(
374
373
  dataStart + <usize>index * sizeof<valueof<T>>(),
375
374
  negative ? -(<i64>value) : <i64>value,
376
375
  );
@@ -398,7 +397,7 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
398
397
 
399
398
  if (sizeof<valueof<T>>() > 1) {
400
399
  while (srcStart + 6 < srcEnd) {
401
- const parsed = parse4DigitsASCII(load<u64>(srcStart));
400
+ const parsed = parse4Digits_PairMul(load<u64>(srcStart));
402
401
  if (parsed == U32.MAX_VALUE) break;
403
402
  value = value * 10000 + parsed;
404
403
  srcStart += 8;
@@ -413,7 +412,7 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
413
412
  }
414
413
 
415
414
  if (index >= reusableLength) break;
416
- storeUnsignedInteger<T>(
415
+ storeUnsignedIntegerE<valueof<T>>(
417
416
  dataStart + <usize>index * sizeof<valueof<T>>(),
418
417
  value,
419
418
  );
@@ -462,8 +461,8 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
462
461
  if (isSigned<valueof<T>>()) {
463
462
  while (srcStart < srcEnd) {
464
463
  const next = useSWAR
465
- ? parseSignedIntegerSWAR<T>(srcStart, srcEnd, writePtr)
466
- : parseSignedIntegerScalar<T>(srcStart, srcEnd, writePtr);
464
+ ? parseSignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr)
465
+ : parseSignedIntegerScalar<valueof<T>>(srcStart, srcEnd, writePtr);
467
466
  if (!next) break;
468
467
  writePtr += elementSize;
469
468
  srcStart = next;
@@ -483,8 +482,8 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
483
482
  } else {
484
483
  while (srcStart < srcEnd) {
485
484
  const next = useSWAR
486
- ? parseUnsignedIntegerSWAR<T>(srcStart, srcEnd, writePtr)
487
- : parseUnsignedIntegerScalar<T>(srcStart, srcEnd, writePtr);
485
+ ? parseUnsignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr)
486
+ : parseUnsignedIntegerScalar<valueof<T>>(srcStart, srcEnd, writePtr);
488
487
  if (!next) break;
489
488
  writePtr += elementSize;
490
489
  srcStart = next;
@@ -515,15 +514,6 @@ export function deserializeIntegerArray_SLOW<T extends number[]>(
515
514
  );
516
515
  }
517
516
 
518
- // @ts-ignore: Decorator valid here
519
- export function deserializeIntegerArray<T extends number[]>(
520
- srcStart: usize,
521
- srcEnd: usize,
522
- dst: usize,
523
- ): T {
524
- return deserializeIntegerArrayImpl<T>(srcStart, srcEnd, dst, false);
525
- }
526
-
527
517
  // @ts-ignore: Decorator valid here
528
518
  export function deserializeIntegerArray_SWAR<T extends number[]>(
529
519
  srcStart: usize,
@@ -655,7 +645,7 @@ function deserializeNarrowIntegerArray_SWAR<T extends number[]>(
655
645
  }
656
646
 
657
647
 
658
- @inline export function deserializeIntegerArrayInto<T extends number[]>(
648
+ @inline function deserializeIntegerArrayBody<T extends number[]>(
659
649
  srcStart: usize,
660
650
  srcEnd: usize,
661
651
  out: T,
@@ -665,6 +655,7 @@ function deserializeNarrowIntegerArray_SWAR<T extends number[]>(
665
655
  do {
666
656
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
667
657
  srcStart += 2;
658
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
668
659
  if (srcStart >= srcEnd) break;
669
660
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
670
661
  out.length = 0;
@@ -673,19 +664,35 @@ function deserializeNarrowIntegerArray_SWAR<T extends number[]>(
673
664
 
674
665
  while (srcStart < srcEnd) {
675
666
  const slot = ensureArrayElementSlot<T>(out, index);
667
+ // Inline the array-optimized SWAR parser directly. The top-level
668
+ // (`deserializeIntegerArrayImpl`) and the typed-array path
669
+ // (`swar/typedarray.ts`) already call these — having the field path
670
+ // call them too means the per-element parser is identical across all
671
+ // three call sites (parse4 + scalar fold for both signed and
672
+ // unsigned, narrow-lane special case for i8/u8/i16/u16).
673
+ //
674
+ // Why not `deserializeUnsignedField_SWAR` (which uses parse8 + scalar)?
675
+ // That tuning targets the struct-single-field path where the digit
676
+ // run is one aligned token. In an array, mixed element widths cause
677
+ // parse8 to fail-and-retry at element boundaries — see u32-64mib's
678
+ // 23% regression when parse8 was tried in the array path.
676
679
  srcStart = isSigned<valueof<T>>()
677
- ? deserializeIntegerField<valueof<T>>(srcStart, srcEnd, slot)
678
- : deserializeUnsignedField<valueof<T>>(srcStart, srcEnd, slot);
679
- if (!srcStart || srcStart >= srcEnd) break;
680
+ ? parseSignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, slot)
681
+ : parseUnsignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, slot);
682
+ if (!srcStart) break;
683
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
684
+ if (srcStart >= srcEnd) break;
680
685
 
681
686
  const code = load<u16>(srcStart);
682
687
  if (code == COMMA) {
683
688
  srcStart += 2;
689
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
684
690
  index++;
685
691
  continue;
686
692
  }
687
693
  if (code == BRACKET_RIGHT) {
688
- out.length = index + 1;
694
+ const nextLen = index + 1;
695
+ if (out.length != nextLen) out.length = nextLen;
689
696
  return srcStart + 2;
690
697
  }
691
698
  break;
@@ -701,7 +708,7 @@ function deserializeNarrowIntegerArray_SWAR<T extends number[]>(
701
708
  srcEnd: usize,
702
709
  fieldPtr: usize,
703
710
  ): usize {
704
- return deserializeIntegerArrayInto<T>(
711
+ return deserializeIntegerArrayBody<T>(
705
712
  srcStart,
706
713
  srcEnd,
707
714
  ensureArrayField<T>(fieldPtr),
@@ -1,3 +1,4 @@
1
+ import { deserializeGenericArrayBody } from "./generic";
1
2
  import { ensureArrayField } from "./shared";
2
3
 
3
4
 
@@ -6,6 +7,9 @@ import { ensureArrayField } from "./shared";
6
7
  srcEnd: usize,
7
8
  fieldPtr: usize,
8
9
  ): usize {
9
- ensureArrayField<T>(fieldPtr);
10
- throw new Error("Failed to parse JSON!");
10
+ return deserializeGenericArrayBody<T>(
11
+ srcStart,
12
+ srcEnd,
13
+ ensureArrayField<T>(fieldPtr),
14
+ );
11
15
  }
@@ -1,8 +1,14 @@
1
+ import { JSON } from "../../..";
1
2
  import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
2
- import { ensureArrayElementSlot, ensureArrayField } from "./shared";
3
+ import {
4
+ ensureArrayElementSlot,
5
+ ensureArrayField,
6
+ scanValueEnd,
7
+ skipWhitespace,
8
+ } from "./shared";
3
9
 
4
10
 
5
- @inline export function deserializeObjectArrayInto<T extends unknown[]>(
11
+ @inline function deserializeObjectArrayBody<T extends unknown[]>(
6
12
  srcStart: usize,
7
13
  srcEnd: usize,
8
14
  out: T,
@@ -12,6 +18,7 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
12
18
  do {
13
19
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
14
20
  srcStart += 2;
21
+ srcStart = skipWhitespace(srcStart, srcEnd);
15
22
  if (srcStart >= srcEnd) break;
16
23
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
17
24
  out.length = 0;
@@ -20,44 +27,29 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
20
27
 
21
28
  while (srcStart < srcEnd) {
22
29
  const slot = ensureArrayElementSlot<T>(out, index);
23
- let value = load<valueof<T>>(slot);
24
- if (changetype<usize>(value) == 0) {
25
- value = changetype<valueof<T>>(
26
- __new(offsetof<nonnull<valueof<T>>>(), idof<nonnull<valueof<T>>>()),
27
- );
28
- // @ts-ignore: supplied by transform
29
- if (isDefined(changetype<nonnull<valueof<T>>>(value).__INITIALIZE)) {
30
- // @ts-ignore: supplied by transform
31
- changetype<nonnull<valueof<T>>>(value).__INITIALIZE();
32
- }
33
- store<valueof<T>>(slot, value);
34
- }
35
-
36
30
  const valueStart = srcStart;
37
- // @ts-ignore: supplied by transform
38
- if (
39
- isDefined(changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST)
40
- ) {
41
- // @ts-ignore: supplied by transform
42
- srcStart = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST<
43
- valueof<T>
44
- >(valueStart, srcEnd, value);
45
- } else {
46
- // @ts-ignore: supplied by transform
47
- srcStart = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_SLOW<
48
- valueof<T>
49
- >(valueStart, srcEnd, value);
50
- }
51
- if (!srcStart || srcStart >= srcEnd) break;
31
+ const valueEnd = scanValueEnd(valueStart, srcEnd);
32
+ if (!valueEnd) break;
33
+
34
+ const value = JSON.__deserialize<valueof<T>>(
35
+ valueStart,
36
+ valueEnd,
37
+ changetype<usize>(load<valueof<T>>(slot)),
38
+ );
39
+ store<valueof<T>>(slot, value);
40
+ srcStart = valueEnd;
52
41
 
42
+ srcStart = skipWhitespace(srcStart, srcEnd);
53
43
  const code = load<u16>(srcStart);
54
44
  if (code == COMMA) {
55
45
  srcStart += 2;
46
+ srcStart = skipWhitespace(srcStart, srcEnd);
56
47
  index++;
57
48
  continue;
58
49
  }
59
50
  if (code == BRACKET_RIGHT) {
60
- out.length = index + 1;
51
+ const nextLen = index + 1;
52
+ if (out.length != nextLen) out.length = nextLen;
61
53
  return srcStart + 2;
62
54
  }
63
55
  break;
@@ -73,7 +65,7 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
73
65
  srcEnd: usize,
74
66
  fieldPtr: usize,
75
67
  ): usize {
76
- return deserializeObjectArrayInto<T>(
68
+ return deserializeObjectArrayBody<T>(
77
69
  srcStart,
78
70
  srcEnd,
79
71
  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<JSON.Raw[]>(fieldPtr);
11
- throw new Error("Failed to parse JSON!");
11
+ return deserializeGenericArrayBody<JSON.Raw[]>(
12
+ srcStart,
13
+ srcEnd,
14
+ ensureArrayField<JSON.Raw[]>(fieldPtr),
15
+ );
12
16
  }
@@ -7,6 +7,14 @@ import {
7
7
  COMMA,
8
8
  QUOTE,
9
9
  } from "../../../custom/chars";
10
+ import { isSpace } from "../../../util";
11
+
12
+ /** Advance past JSON whitespace (space, tab, LF, CR). */
13
+ // @ts-expect-error: @inline is a valid decorator
14
+ @inline export function skipWhitespace(srcStart: usize, srcEnd: usize): usize {
15
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
16
+ return srcStart;
17
+ }
10
18
 
11
19
 
12
20
  @inline export function ensureArrayField<T extends Array<any>>(
@@ -50,13 +58,21 @@ import {
50
58
  ): usize {
51
59
  const nextLength = index + 1;
52
60
  if (out.length < nextLength) {
53
- out.length = nextLength;
54
- const slot = out.dataStart + <usize>index * sizeof<valueof<T>>();
55
- // Reference arrays can allocate recursively before the caller stores the new element.
56
- // Zero the newly exposed slot immediately so incremental GC never observes a garbage pointer.
57
- if (isManaged<valueof<T>>() || isReference<valueof<T>>())
58
- store<usize>(slot, 0);
59
- return slot;
61
+ // Grow via `push`, not `out.length = nextLength`. AS's `length=`
62
+ // setter calls `ensureCapacity(canGrow=false)` which reallocates to
63
+ // *exactly* the requested size fine for a one-shot resize, but
64
+ // catastrophic in the per-element loop (every push triggers a full
65
+ // copy of the array, giving O() growth cost). `push` goes through
66
+ // `canGrow=true`, doubling capacity geometrically as needed.
67
+ //
68
+ // We push a zero-bit default: `0` for primitives, the null reference
69
+ // for managed/reference element types. The caller overwrites this
70
+ // slot immediately, so the placeholder is never observed.
71
+ if (isManaged<valueof<T>>() || isReference<valueof<T>>()) {
72
+ out.push(changetype<valueof<T>>(0));
73
+ } else {
74
+ out.push(<valueof<T>>0);
75
+ }
60
76
  }
61
77
  return out.dataStart + <usize>index * sizeof<valueof<T>>();
62
78
  }
@@ -127,7 +143,15 @@ import {
127
143
 
128
144
  while (srcStart < srcEnd) {
129
145
  const code = load<u16>(srcStart);
130
- if (code == COMMA || code == BRACKET_RIGHT || code == BRACE_RIGHT)
146
+ // Stop at the structural terminator OR trailing whitespace, so the returned
147
+ // range is the exact value (scalar parsers assume no trailing whitespace).
148
+ // Callers skip whitespace to reach the following `,`/`]`/`}`.
149
+ if (
150
+ code == COMMA ||
151
+ code == BRACKET_RIGHT ||
152
+ code == BRACE_RIGHT ||
153
+ isSpace(code)
154
+ )
131
155
  return srcStart;
132
156
  srcStart += 2;
133
157
  }