json-as 1.4.0 → 1.6.0

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 (108) hide show
  1. package/CHANGELOG.md +54 -29
  2. package/README.md +126 -33
  3. package/assembly/custom/chars.ts +39 -78
  4. package/assembly/deserialize/error.ts +21 -0
  5. package/assembly/deserialize/index/arbitrary.ts +50 -11
  6. package/assembly/deserialize/index/array.ts +2 -1
  7. package/assembly/deserialize/index/bool.ts +1 -1
  8. package/assembly/deserialize/index/float.ts +2 -4
  9. package/assembly/deserialize/index/integer.ts +2 -4
  10. package/assembly/deserialize/index/object.ts +9 -1
  11. package/assembly/deserialize/index/string.ts +39 -7
  12. package/assembly/deserialize/index/unsigned.ts +2 -4
  13. package/assembly/deserialize/index.ts +0 -1
  14. package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
  15. package/assembly/deserialize/naive/array/array.ts +1 -0
  16. package/assembly/deserialize/naive/array/bool.ts +21 -11
  17. package/assembly/deserialize/naive/array/box.ts +13 -33
  18. package/assembly/deserialize/naive/array/float.ts +23 -13
  19. package/assembly/deserialize/naive/array/integer.ts +34 -22
  20. package/assembly/deserialize/naive/array/map.ts +1 -1
  21. package/assembly/deserialize/naive/array/object.ts +2 -1
  22. package/assembly/deserialize/naive/array/string.ts +29 -16
  23. package/assembly/deserialize/naive/array/struct.ts +19 -1
  24. package/assembly/deserialize/naive/bool.ts +18 -10
  25. package/assembly/deserialize/naive/date.ts +1 -2
  26. package/assembly/deserialize/naive/float.ts +3 -9
  27. package/assembly/deserialize/naive/integer.ts +1 -2
  28. package/assembly/deserialize/naive/map.ts +342 -43
  29. package/assembly/deserialize/naive/object.ts +250 -55
  30. package/assembly/deserialize/naive/raw.ts +10 -2
  31. package/assembly/deserialize/naive/set.ts +2 -5
  32. package/assembly/deserialize/naive/staticarray.ts +4 -4
  33. package/assembly/deserialize/naive/string.ts +46 -60
  34. package/assembly/deserialize/naive/unsigned.ts +1 -2
  35. package/assembly/deserialize/parseMode.ts +216 -0
  36. package/assembly/deserialize/simd/array/integer.ts +8 -10
  37. package/assembly/deserialize/simd/float.ts +38 -34
  38. package/assembly/deserialize/simd/integer.ts +2 -7
  39. package/assembly/deserialize/simd/string.ts +84 -39
  40. package/assembly/deserialize/string-validation.ts +34 -0
  41. package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
  42. package/assembly/deserialize/swar/array/array.ts +64 -15
  43. package/assembly/deserialize/swar/array/bool.ts +15 -5
  44. package/assembly/deserialize/swar/array/box.ts +1 -2
  45. package/assembly/deserialize/swar/array/float.ts +399 -30
  46. package/assembly/deserialize/swar/array/generic.ts +13 -4
  47. package/assembly/deserialize/swar/array/integer.ts +29 -78
  48. package/assembly/deserialize/swar/array/map.ts +1 -2
  49. package/assembly/deserialize/swar/array/object.ts +12 -4
  50. package/assembly/deserialize/swar/array/raw.ts +1 -2
  51. package/assembly/deserialize/swar/array/shared.ts +7 -14
  52. package/assembly/deserialize/swar/array/string.ts +37 -13
  53. package/assembly/deserialize/swar/array/struct.ts +109 -21
  54. package/assembly/deserialize/swar/array.ts +1 -3
  55. package/assembly/deserialize/swar/float.ts +23 -21
  56. package/assembly/deserialize/swar/integer.ts +2 -7
  57. package/assembly/deserialize/swar/string.ts +81 -124
  58. package/assembly/deserialize/swar/typedarray.ts +4 -4
  59. package/assembly/index.d.ts +35 -24
  60. package/assembly/index.ts +1775 -281
  61. package/assembly/serialize/index/arbitrary.ts +70 -4
  62. package/assembly/serialize/index/bool.ts +1 -1
  63. package/assembly/serialize/index/float.ts +1 -5
  64. package/assembly/serialize/index/integer.ts +1 -1
  65. package/assembly/serialize/index/jsonarray.ts +51 -0
  66. package/assembly/serialize/index/object.ts +42 -3
  67. package/assembly/serialize/index/string.ts +1 -2
  68. package/assembly/serialize/index/typedarray.ts +1 -2
  69. package/assembly/serialize/index.ts +1 -0
  70. package/assembly/serialize/naive/array.ts +87 -34
  71. package/assembly/serialize/naive/bool.ts +0 -1
  72. package/assembly/serialize/naive/float.ts +15 -29
  73. package/assembly/serialize/naive/integer.ts +1 -5
  74. package/assembly/serialize/naive/map.ts +37 -0
  75. package/assembly/serialize/naive/raw.ts +1 -2
  76. package/assembly/serialize/naive/set.ts +0 -5
  77. package/assembly/serialize/naive/staticarray.ts +0 -6
  78. package/assembly/serialize/naive/string.ts +1 -10
  79. package/assembly/serialize/naive/typedarray.ts +0 -7
  80. package/assembly/serialize/simd/string.ts +1 -3
  81. package/assembly/serialize/swar/string.ts +1 -2
  82. package/assembly/util/atoi-fast.ts +4 -14
  83. package/assembly/util/bytes.ts +1 -2
  84. package/assembly/util/eisel-lemire.ts +179 -0
  85. package/assembly/util/idofd.ts +1 -2
  86. package/assembly/util/isSpace.ts +1 -2
  87. package/assembly/util/itoa-fast.ts +6 -9
  88. package/assembly/util/nextPowerOf2.ts +1 -2
  89. package/assembly/util/parsefloat-fast.ts +3 -5
  90. package/assembly/util/prettyWhitespaceSimd.ts +69 -0
  91. package/assembly/util/ptrToStr.ts +1 -2
  92. package/assembly/util/scanValueEndSimd.ts +83 -22
  93. package/assembly/util/scanValueEndSwar.ts +67 -25
  94. package/assembly/util/scientific.ts +5 -8
  95. package/assembly/util/snp.ts +1 -2
  96. package/assembly/util/swar-int.ts +5 -10
  97. package/assembly/util/swar.ts +2 -4
  98. package/assembly/util/validateJson.ts +338 -0
  99. package/lib/as-bs.ts +51 -51
  100. package/package.json +25 -8
  101. package/transform/lib/index.d.ts +6 -0
  102. package/transform/lib/index.js +874 -171
  103. package/transform/lib/types.d.ts +3 -1
  104. package/transform/lib/types.js +6 -0
  105. package/assembly/deserialize/index/struct.ts +0 -1
  106. package/assembly/deserialize/naive/struct.ts +0 -21
  107. package/assembly/util/dragonbox-cache.ts +0 -445
  108. package/assembly/util/dragonbox.ts +0 -652
@@ -0,0 +1,216 @@
1
+ let SIMD_PRETTY_PARSE = false;
2
+ const ENABLE_EXACT_SOURCE_TRACES = true;
3
+
4
+ // Steady-state SIMD string-field trace. JSON source strings and parsed field
5
+ // strings are immutable at the language level, so the tuple
6
+ // (source position, destination slot, current string reference) proves that a
7
+ // field still contains the bytes produced by the prior parse. Any mutation or
8
+ // graph replacement changes at least one tuple component and takes the normal
9
+ // decoder, which refreshes that trace slot.
10
+ let STRING_TRACE_DEPTH = 0;
11
+ let STRING_TRACE_ACTIVE = false;
12
+ let STRING_TRACE_COMPLETE = false;
13
+ let STRING_TRACE_SOURCE: string | null = null;
14
+ let STRING_TRACE_OUT: usize = 0;
15
+ let STRING_TRACE_TYPE: u32 = 0;
16
+ let STRING_TRACE_INDEX = 0;
17
+ let STRING_TRACE_COUNT = 0;
18
+ let STRING_TRACE_SLOT = 0;
19
+ const STRING_TRACE_FIELDS = new Array<usize>();
20
+ const STRING_TRACE_STARTS = new Array<usize>();
21
+ const STRING_TRACE_ENDS = new Array<usize>();
22
+ const STRING_TRACE_VALUES = new Array<string>();
23
+ let OBJECT_TRACE_INDEX = 0;
24
+ let OBJECT_TRACE_COUNT = 0;
25
+ const OBJECT_TRACE_FIELDS = new Array<usize>();
26
+ const OBJECT_TRACE_STARTS = new Array<usize>();
27
+ const OBJECT_TRACE_TYPES = new Array<string>();
28
+ const OBJECT_TRACE_MASKS = new Array<u64>();
29
+ const OBJECT_TRACE_TIERS = new Array<u8>();
30
+ const OBJECT_TRACE_SEPARATORS = new Array<usize>();
31
+
32
+
33
+ @inline
34
+ export function isPrettyParse(): bool {
35
+ return SIMD_PRETTY_PARSE;
36
+ }
37
+
38
+
39
+ @inline
40
+ export function setPrettyParse(value: bool): void {
41
+ SIMD_PRETTY_PARSE = value;
42
+ }
43
+
44
+ /** Enter one public parse call, suspending trace use in nested parses. */
45
+ @inline
46
+ export function beginStringFieldTrace(
47
+ source: string,
48
+ out: usize,
49
+ typeId: u32,
50
+ ): void {
51
+ if (!ENABLE_EXACT_SOURCE_TRACES) return;
52
+ STRING_TRACE_DEPTH++;
53
+ if (!ASC_FEATURE_SIMD || STRING_TRACE_DEPTH != 1 || out == 0) return;
54
+
55
+ const sourcePtr = changetype<usize>(source);
56
+ const sameRoot =
57
+ STRING_TRACE_COMPLETE &&
58
+ STRING_TRACE_OUT == out &&
59
+ STRING_TRACE_TYPE == typeId &&
60
+ changetype<usize>(STRING_TRACE_SOURCE) == sourcePtr;
61
+ if (!sameRoot) {
62
+ STRING_TRACE_SOURCE = source;
63
+ STRING_TRACE_OUT = out;
64
+ STRING_TRACE_TYPE = typeId;
65
+ STRING_TRACE_COUNT = 0;
66
+ STRING_TRACE_FIELDS.length = 0;
67
+ STRING_TRACE_STARTS.length = 0;
68
+ STRING_TRACE_ENDS.length = 0;
69
+ STRING_TRACE_VALUES.length = 0;
70
+ OBJECT_TRACE_COUNT = 0;
71
+ OBJECT_TRACE_FIELDS.length = 0;
72
+ OBJECT_TRACE_STARTS.length = 0;
73
+ OBJECT_TRACE_TYPES.length = 0;
74
+ OBJECT_TRACE_MASKS.length = 0;
75
+ OBJECT_TRACE_TIERS.length = 0;
76
+ OBJECT_TRACE_SEPARATORS.length = 0;
77
+ }
78
+ STRING_TRACE_INDEX = 0;
79
+ OBJECT_TRACE_INDEX = 0;
80
+ STRING_TRACE_COMPLETE = false;
81
+ STRING_TRACE_ACTIVE = true;
82
+ }
83
+
84
+ /** Leave the matching public parse call. */
85
+ @inline
86
+ export function endStringFieldTrace(success: bool): void {
87
+ if (!ENABLE_EXACT_SOURCE_TRACES) return;
88
+ if (STRING_TRACE_DEPTH == 1) {
89
+ STRING_TRACE_ACTIVE = false;
90
+ STRING_TRACE_COMPLETE = success;
91
+ }
92
+ STRING_TRACE_DEPTH--;
93
+ }
94
+
95
+ /** Return the cached cursor, or zero when this field must be decoded. */
96
+ @inline
97
+ export function probeStringFieldTrace(
98
+ dstFieldPtr: usize,
99
+ payloadStart: usize,
100
+ ): usize {
101
+ if (!ENABLE_EXACT_SOURCE_TRACES) return 0;
102
+ if (!STRING_TRACE_ACTIVE || STRING_TRACE_DEPTH != 1) return 0;
103
+ const slot = STRING_TRACE_INDEX++;
104
+ STRING_TRACE_SLOT = slot;
105
+ if (
106
+ slot < STRING_TRACE_COUNT &&
107
+ unchecked(STRING_TRACE_FIELDS[slot]) == dstFieldPtr &&
108
+ unchecked(STRING_TRACE_STARTS[slot]) == payloadStart &&
109
+ load<usize>(dstFieldPtr) ==
110
+ changetype<usize>(unchecked(STRING_TRACE_VALUES[slot]))
111
+ )
112
+ return unchecked(STRING_TRACE_ENDS[slot]);
113
+ return 0;
114
+ }
115
+
116
+ /** Record the successful decode corresponding to the most recent probe. */
117
+ @inline
118
+ export function recordStringFieldTrace(
119
+ dstFieldPtr: usize,
120
+ payloadStart: usize,
121
+ next: usize,
122
+ ): void {
123
+ if (!ENABLE_EXACT_SOURCE_TRACES) return;
124
+ if (!STRING_TRACE_ACTIVE || STRING_TRACE_DEPTH != 1) return;
125
+ const slot = STRING_TRACE_SLOT;
126
+ const value = load<string>(dstFieldPtr);
127
+ if (slot < STRING_TRACE_COUNT) {
128
+ unchecked((STRING_TRACE_FIELDS[slot] = dstFieldPtr));
129
+ unchecked((STRING_TRACE_STARTS[slot] = payloadStart));
130
+ unchecked((STRING_TRACE_ENDS[slot] = next));
131
+ unchecked((STRING_TRACE_VALUES[slot] = value));
132
+ } else {
133
+ STRING_TRACE_FIELDS.push(dstFieldPtr);
134
+ STRING_TRACE_STARTS.push(payloadStart);
135
+ STRING_TRACE_ENDS.push(next);
136
+ STRING_TRACE_VALUES.push(value);
137
+ STRING_TRACE_COUNT = slot + 1;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Reserve or find one optional-struct trace slot.
143
+ *
144
+ * Returns i32.MIN_VALUE when tracing is inactive, a non-negative slot on a
145
+ * miss, or `~slot` (negative) on a hit. Reserving at begin keeps nested object
146
+ * records well-ordered even though a parent only commits after its children.
147
+ */
148
+ @inline
149
+ export function beginObjectFieldTrace(
150
+ dst: usize,
151
+ srcStart: usize,
152
+ typeTag: string,
153
+ ): i32 {
154
+ if (!ENABLE_EXACT_SOURCE_TRACES) return i32.MIN_VALUE;
155
+ if (!STRING_TRACE_ACTIVE || STRING_TRACE_DEPTH != 1) return i32.MIN_VALUE;
156
+ const slot = OBJECT_TRACE_INDEX++;
157
+ if (
158
+ slot < OBJECT_TRACE_COUNT &&
159
+ unchecked(OBJECT_TRACE_FIELDS[slot]) == dst &&
160
+ unchecked(OBJECT_TRACE_STARTS[slot]) == srcStart &&
161
+ unchecked(OBJECT_TRACE_TIERS[slot]) != 0 &&
162
+ changetype<usize>(unchecked(OBJECT_TRACE_TYPES[slot])) ==
163
+ changetype<usize>(typeTag)
164
+ )
165
+ return ~slot;
166
+
167
+ if (slot < OBJECT_TRACE_COUNT) {
168
+ unchecked((OBJECT_TRACE_FIELDS[slot] = dst));
169
+ unchecked((OBJECT_TRACE_STARTS[slot] = srcStart));
170
+ unchecked((OBJECT_TRACE_TYPES[slot] = typeTag));
171
+ unchecked((OBJECT_TRACE_MASKS[slot] = 0));
172
+ unchecked((OBJECT_TRACE_TIERS[slot] = 0));
173
+ unchecked((OBJECT_TRACE_SEPARATORS[slot] = 0));
174
+ } else {
175
+ OBJECT_TRACE_FIELDS.push(dst);
176
+ OBJECT_TRACE_STARTS.push(srcStart);
177
+ OBJECT_TRACE_TYPES.push(typeTag);
178
+ OBJECT_TRACE_MASKS.push(0);
179
+ OBJECT_TRACE_TIERS.push(0);
180
+ OBJECT_TRACE_SEPARATORS.push(0);
181
+ OBJECT_TRACE_COUNT = slot + 1;
182
+ }
183
+ return slot;
184
+ }
185
+
186
+
187
+ @inline
188
+ export function objectFieldTraceMask(slot: i32): u64 {
189
+ return unchecked(OBJECT_TRACE_MASKS[slot]);
190
+ }
191
+
192
+
193
+ @inline
194
+ export function objectFieldTraceTier(slot: i32): u8 {
195
+ return unchecked(OBJECT_TRACE_TIERS[slot]);
196
+ }
197
+
198
+
199
+ @inline
200
+ export function objectFieldTraceSeparator(slot: i32): usize {
201
+ return unchecked(OBJECT_TRACE_SEPARATORS[slot]);
202
+ }
203
+
204
+
205
+ @inline
206
+ export function recordObjectFieldTrace(
207
+ slot: i32,
208
+ present: u64,
209
+ tier: u8,
210
+ separator: usize = 0,
211
+ ): void {
212
+ if (slot < 0 || slot >= OBJECT_TRACE_COUNT) return;
213
+ unchecked((OBJECT_TRACE_MASKS[slot] = present));
214
+ unchecked((OBJECT_TRACE_TIERS[slot] = tier));
215
+ unchecked((OBJECT_TRACE_SEPARATORS[slot] = separator));
216
+ }
@@ -40,11 +40,7 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
40
40
  // @ts-expect-error: decorators valid here
41
41
  @lazy const PAIR_WEIGHTS_100_1 = i16x8(100, 1, 100, 1, 0, 0, 0, 0);
42
42
 
43
- // @ts-expect-error: decorators valid here
44
- @inline function storeSignedInteger<T extends number[]>(
45
- slot: usize,
46
- value: i64,
47
- ): void {
43
+ function storeSignedInteger<T extends number[]>(slot: usize, value: i64): void {
48
44
  if (sizeof<valueof<T>>() == sizeof<i8>()) {
49
45
  store<i8>(slot, <i8>value);
50
46
  } else if (sizeof<valueof<T>>() == sizeof<i16>()) {
@@ -58,8 +54,7 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
58
54
  }
59
55
  }
60
56
 
61
- // @ts-expect-error: decorators valid here
62
- @inline function storeUnsignedInteger<T extends number[]>(
57
+ function storeUnsignedInteger<T extends number[]>(
63
58
  slot: usize,
64
59
  value: u64,
65
60
  ): void {
@@ -402,7 +397,10 @@ export function deserializeIntegerArray_SIMD<T extends number[]>(
402
397
  dst || changetype<usize>(instantiate<T>()),
403
398
  );
404
399
  const originalSrcStart = srcStart;
405
- const reusableLength = out.length;
400
+ const reusableLength = load<i32>(
401
+ changetype<usize>(out),
402
+ offsetof<T>("length_"),
403
+ );
406
404
 
407
405
  if (reusableLength != 0) {
408
406
  const dataStart = out.dataStart;
@@ -460,7 +458,7 @@ export function deserializeIntegerArray_SIMD<T extends number[]>(
460
458
  continue;
461
459
  }
462
460
  if (code == BRACKET_RIGHT) {
463
- out.length = index;
461
+ if (index != reusableLength) out.length = index;
464
462
  return out;
465
463
  }
466
464
  break;
@@ -499,7 +497,7 @@ export function deserializeIntegerArray_SIMD<T extends number[]>(
499
497
  continue;
500
498
  }
501
499
  if (code == BRACKET_RIGHT) {
502
- out.length = index;
500
+ if (index != reusableLength) out.length = index;
503
501
  return out;
504
502
  }
505
503
  break;
@@ -1,9 +1,9 @@
1
- // SIMD float deserializers. Same Lemire-style fast path / `scientific()` /
2
- // `f*.parse` cascade as `swar/float.ts`, but the fractional digit loop uses
3
- // `parse8Digits_SIMD` (16 bytes / 8 digits) before falling through to
4
- // `parse4Digits_PairMul` (8 bytes / 4 digits) and finally the scalar tail.
1
+ // SIMD float deserializers. Same Eisel-Lemire / `scientific()` / `f*.parse`
2
+ // cascade as `swar/float.ts`, but the fractional digit loop starts with a
3
+ // `parse16Digits_SIMD` stride before falling through to `parse4Digits_PairMul`
4
+ // and finally the scalar tail.
5
5
  //
6
- // Output is bit-identical to `f64.parse` / `f32.parse` for every input the
6
+ // Output is bit-identical to `f64.parse` / `f32.parse` for every input - the
7
7
  // SIMD strides only change how the u64 mantissa is accumulated, not what it
8
8
  // becomes.
9
9
  //
@@ -14,6 +14,7 @@ import { ptrToStr } from "../../util/ptrToStr";
14
14
  import { parse4Digits_PairMul } from "../../util/swar-int";
15
15
  import { parse16Digits_SIMD } from "../../util/simd-int";
16
16
  import { scientific } from "../../util/scientific";
17
+ import { eiselLemire22 } from "../../util/eisel-lemire";
17
18
  import { loadPow10, MAX_EXACT_MANTISSA, MAX_EXACT_POW10 } from "../swar/float";
18
19
 
19
20
  const ASCII_PLUS: u16 = 43;
@@ -23,8 +24,7 @@ const ASCII_ZERO: u16 = 48;
23
24
  const ASCII_E_UP: u16 = 69;
24
25
  const ASCII_E_LO: u16 = 101;
25
26
 
26
- // @ts-ignore: inline
27
- @inline function fallback<T>(srcStart: usize, srcEnd: usize): T {
27
+ function fallback<T>(srcStart: usize, srcEnd: usize): T {
28
28
  const s = ptrToStr(srcStart, srcEnd);
29
29
  // @ts-ignore
30
30
  const type: T = 0;
@@ -34,8 +34,7 @@ const ASCII_E_LO: u16 = 101;
34
34
  return <T>(<f32>f32.parse(s));
35
35
  }
36
36
 
37
- // @ts-ignore: inline
38
- @inline function fallbackField<T extends number>(
37
+ function fallbackField<T extends number>(
39
38
  origStart: usize,
40
39
  end: usize,
41
40
  fieldPtr: usize,
@@ -58,48 +57,47 @@ export function deserializeFloat_SIMD<T>(srcStart: usize, srcEnd: usize): T {
58
57
  }
59
58
 
60
59
  let mantissa: u64 = 0;
61
- let intDigits: i32 = 0;
60
+ const intStart = p;
62
61
  while (p < srcEnd) {
63
62
  const d = <u32>load<u16>(p) - ASCII_ZERO;
64
63
  if (d > 9) break;
65
64
  mantissa = mantissa * 10 + <u64>d;
66
- intDigits++;
67
65
  p += 2;
68
66
  }
67
+ const intDigits = <i32>((p - intStart) >> 1);
69
68
 
70
- // Fractional part: parse8 SIMD stride → parse4 SWAR stride → scalar tail.
71
- // `intDigits + fracDigits <= 11` gate keeps `mantissa * 10^8 + parsed8`
72
- // under u64 max even when the integer part is large.
73
- let fracDigits: i32 = 0;
69
+ // Fractional part: one parse16 SIMD stride → parse4 SWAR stride →
70
+ // scalar tail. The parse16 gate keeps the combined mantissa within u64.
71
+ let fracStart = p;
74
72
  if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
75
73
  p += 2;
74
+ fracStart = p;
76
75
  // parse16 SIMD only fires on long fractions (>=16 digits ahead, with
77
76
  // <=3 integer digits to keep mantissa * 1e16 under u64 max). Rare in
78
77
  // typical JSON but a big win when it does fire (8 digits per stride in
79
78
  // SWAR's parse8 was benched even/worse than parse4, so we skip parse8
80
79
  // entirely and let parse4 handle the 4-15 char tail).
81
- while (p + 30 < srcEnd && intDigits + fracDigits <= 3) {
80
+ if (p + 30 < srcEnd && intDigits <= 3) {
82
81
  const parsed = parse16Digits_SIMD(p);
83
- if (parsed == U64.MAX_VALUE) break;
84
- mantissa = mantissa * 10_000_000_000_000_000 + parsed;
85
- fracDigits += 16;
86
- p += 32;
82
+ if (parsed != U64.MAX_VALUE) {
83
+ mantissa = mantissa * 10_000_000_000_000_000 + parsed;
84
+ p += 32;
85
+ }
87
86
  }
88
87
  while (p + 6 < srcEnd) {
89
88
  const parsed = parse4Digits_PairMul(load<u64>(p));
90
89
  if (parsed == U32.MAX_VALUE) break;
91
90
  mantissa = mantissa * 10_000 + <u64>parsed;
92
- fracDigits += 4;
93
91
  p += 8;
94
92
  }
95
93
  while (p < srcEnd) {
96
94
  const d = <u32>load<u16>(p) - ASCII_ZERO;
97
95
  if (d > 9) break;
98
96
  mantissa = mantissa * 10 + <u64>d;
99
- fracDigits++;
100
97
  p += 2;
101
98
  }
102
99
  }
100
+ const fracDigits = <i32>((p - fracStart) >> 1);
103
101
 
104
102
  const mantDigits = intDigits + fracDigits;
105
103
  if (mantDigits == 0) return fallback<T>(origStart, srcEnd);
@@ -150,7 +148,10 @@ export function deserializeFloat_SIMD<T>(srcStart: usize, srcEnd: usize): T {
150
148
  result /= loadPow10(<u32>-exponent);
151
149
  }
152
150
  } else if (mantDigits <= 19) {
153
- result = scientific(mantissa, exponent);
151
+ result =
152
+ exponent >= -22 && exponent <= 22
153
+ ? eiselLemire22(mantissa, exponent)
154
+ : scientific(mantissa, exponent);
154
155
  } else {
155
156
  return fallback<T>(origStart, srcEnd);
156
157
  }
@@ -180,45 +181,45 @@ export function deserializeFloatField_SIMD<T extends number>(
180
181
  }
181
182
 
182
183
  let mantissa: u64 = 0;
183
- let intDigits: i32 = 0;
184
+ const intStart = p;
184
185
  while (p < srcEnd) {
185
186
  const d = <u32>load<u16>(p) - ASCII_ZERO;
186
187
  if (d > 9) break;
187
188
  mantissa = mantissa * 10 + <u64>d;
188
- intDigits++;
189
189
  p += 2;
190
190
  }
191
+ const intDigits = <i32>((p - intStart) >> 1);
191
192
 
192
- let fracDigits: i32 = 0;
193
+ let fracStart = p;
193
194
  if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
194
195
  p += 2;
196
+ fracStart = p;
195
197
  // parse16 SIMD only fires on long fractions (>=16 digits ahead, with
196
198
  // <=3 integer digits to keep mantissa * 1e16 under u64 max). Rare in
197
199
  // typical JSON but a big win when it does fire (8 digits per stride in
198
200
  // SWAR's parse8 was benched even/worse than parse4, so we skip parse8
199
201
  // entirely and let parse4 handle the 4-15 char tail).
200
- while (p + 30 < srcEnd && intDigits + fracDigits <= 3) {
202
+ if (p + 30 < srcEnd && intDigits <= 3) {
201
203
  const parsed = parse16Digits_SIMD(p);
202
- if (parsed == U64.MAX_VALUE) break;
203
- mantissa = mantissa * 10_000_000_000_000_000 + parsed;
204
- fracDigits += 16;
205
- p += 32;
204
+ if (parsed != U64.MAX_VALUE) {
205
+ mantissa = mantissa * 10_000_000_000_000_000 + parsed;
206
+ p += 32;
207
+ }
206
208
  }
207
209
  while (p + 6 < srcEnd) {
208
210
  const parsed = parse4Digits_PairMul(load<u64>(p));
209
211
  if (parsed == U32.MAX_VALUE) break;
210
212
  mantissa = mantissa * 10_000 + <u64>parsed;
211
- fracDigits += 4;
212
213
  p += 8;
213
214
  }
214
215
  while (p < srcEnd) {
215
216
  const d = <u32>load<u16>(p) - ASCII_ZERO;
216
217
  if (d > 9) break;
217
218
  mantissa = mantissa * 10 + <u64>d;
218
- fracDigits++;
219
219
  p += 2;
220
220
  }
221
221
  }
222
+ const fracDigits = <i32>((p - fracStart) >> 1);
222
223
 
223
224
  const mantDigits = intDigits + fracDigits;
224
225
  if (mantDigits == 0) unreachable();
@@ -281,7 +282,10 @@ export function deserializeFloatField_SIMD<T extends number>(
281
282
  result /= loadPow10(<u32>-exponent);
282
283
  }
283
284
  } else if (mantDigits <= 19) {
284
- result = scientific(mantissa, exponent);
285
+ result =
286
+ exponent >= -22 && exponent <= 22
287
+ ? eiselLemire22(mantissa, exponent)
288
+ : scientific(mantissa, exponent);
285
289
  } else {
286
290
  fallbackField<T>(origStart, p, fieldPtr);
287
291
  return p;
@@ -38,11 +38,7 @@ const ASCII_ZERO: u16 = 48;
38
38
  * @param value The `u64` accumulator, interpreted as a two's-complement
39
39
  * signed integer for narrower types.
40
40
  */
41
- // @ts-expect-error: @inline is a valid decorator
42
- @inline function storeSignedToField<T extends number>(
43
- dstPtr: usize,
44
- value: u64,
45
- ): void {
41
+ function storeSignedToField<T extends number>(dstPtr: usize, value: u64): void {
46
42
  if (sizeof<T>() == 1) {
47
43
  store<i8>(dstPtr, <i8>value);
48
44
  } else if (sizeof<T>() == 2) {
@@ -61,8 +57,7 @@ const ASCII_ZERO: u16 = 48;
61
57
  * @param dstPtr Destination pointer (already includes any field offset).
62
58
  * @param value The `u64` accumulator.
63
59
  */
64
- // @ts-expect-error: decorator valid here
65
- @inline function storeUnsignedToField<T extends number>(
60
+ function storeUnsignedToField<T extends number>(
66
61
  dstPtr: usize,
67
62
  value: u64,
68
63
  ): void {