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
@@ -15,8 +15,7 @@ import { serializeStruct } from "./struct";
15
15
  * @param src string
16
16
  * @returns void
17
17
  */
18
- // @ts-ignore: inline
19
- @inline export function serializeString_NAIVE(src: string): void {
18
+ export function serializeString_NAIVE(src: string): void {
20
19
  serializeStringRange(changetype<usize>(src), bytes(src));
21
20
  }
22
21
 
@@ -86,11 +85,3 @@ export function serializeStringRange(srcPtr: usize, srcSize: usize): void {
86
85
  store<u16>(bs.offset, QUOTE);
87
86
  bs.offset += 2;
88
87
  }
89
-
90
- // @ts-ignore: inline
91
- @inline function write_u_escape(code: u16): void {
92
- bs.growSize(10);
93
- store<u32>(bs.offset, U_MARKER); // "\u"
94
- store<u64>(bs.offset, u16_to_hex4_swar(code), 4);
95
- bs.offset += 12;
96
- }
@@ -3,8 +3,6 @@ import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../custom/chars";
3
3
  import { serializeFloat32Unsafe, serializeFloat64Unsafe } from "./float";
4
4
  import { serializeIntegerUnsafe } from "./integer";
5
5
 
6
-
7
- @inline
8
6
  function maxIntegerBytes<T extends number>(): u32 {
9
7
  if (sizeof<T>() == 1) return isSigned<T>() ? 8 : 6;
10
8
  if (sizeof<T>() == 2) return isSigned<T>() ? 12 : 10;
@@ -12,10 +10,7 @@ function maxIntegerBytes<T extends number>(): u32 {
12
10
  return isSigned<T>() ? 42 : 40;
13
11
  }
14
12
 
15
-
16
- @inline
17
13
  function reserveTypedArray<T extends ArrayLike<number>>(len: i32): void {
18
- if (len <= 0) return;
19
14
  if (isFloat<valueof<T>>()) {
20
15
  bs.proposeSize(4 + <u32>len * (sizeof<valueof<T>>() == 4 ? 34 : 66));
21
16
  } else {
@@ -23,8 +18,6 @@ function reserveTypedArray<T extends ArrayLike<number>>(len: i32): void {
23
18
  }
24
19
  }
25
20
 
26
-
27
- @inline
28
21
  function serializeTypedArrayElement<T extends ArrayLike<number>>(
29
22
  src: T,
30
23
  index: i32,
@@ -207,9 +207,7 @@ export function serializeString_SIMD(src: string): void {
207
207
  store<u16>(bs.offset, 34); // "
208
208
  bs.offset += 2;
209
209
  }
210
-
211
- // @ts-expect-error: @inline is a valid decorator
212
- @inline function write_u_escape(code: u16): void {
210
+ function write_u_escape(code: u16): void {
213
211
  bs.growSize(10);
214
212
  store<u32>(bs.offset, U_MARKER); // "\u"
215
213
  store<u64>(bs.offset, u16_to_hex4_swar(code), 4);
@@ -174,8 +174,7 @@ export function serializeString_SWAR(src: string): void {
174
174
  bs.offset += 2;
175
175
  }
176
176
 
177
- // @ts-expect-error: @inline is a valid decorator
178
- @inline function write_u_escape(code: u16): void {
177
+ function write_u_escape(code: u16): void {
179
178
  bs.growSize(10);
180
179
  store<u32>(bs.offset, U_MARKER); // "\u"
181
180
  store<u64>(bs.offset, u16_to_hex4_swar(code), 4);
@@ -18,11 +18,7 @@ import {
18
18
  * @param srcEnd Pointer just past the last code unit.
19
19
  * @returns The parsed value, truncated to `T`.
20
20
  */
21
- // @ts-expect-error: @inline is a valid decorator
22
- @inline export function atou<T extends number>(
23
- srcStart: usize,
24
- srcEnd: usize,
25
- ): T {
21
+ export function atou<T extends number>(srcStart: usize, srcEnd: usize): T {
26
22
  return deserializeUnsigned_SWAR<T>(srcStart, srcEnd);
27
23
  }
28
24
 
@@ -34,11 +30,7 @@ import {
34
30
  * @param srcEnd Pointer just past the last code unit.
35
31
  * @returns The parsed value, truncated to `T`.
36
32
  */
37
- // @ts-expect-error: @inline is a valid decorator
38
- @inline export function atoi<T extends number>(
39
- srcStart: usize,
40
- srcEnd: usize,
41
- ): T {
33
+ export function atoi<T extends number>(srcStart: usize, srcEnd: usize): T {
42
34
  return deserializeInteger_SWAR<T>(srcStart, srcEnd);
43
35
  }
44
36
 
@@ -52,8 +44,7 @@ import {
52
44
  * @param dstPtr Destination pointer for the parsed value.
53
45
  * @returns The source position immediately after the last digit consumed.
54
46
  */
55
- // @ts-expect-error: @inline is a valid decorator
56
- @inline export function atouScan<T extends number>(
47
+ export function atouScan<T extends number>(
57
48
  srcStart: usize,
58
49
  srcEnd: usize,
59
50
  dstPtr: usize,
@@ -71,8 +62,7 @@ import {
71
62
  * @param dstPtr Destination pointer for the parsed value.
72
63
  * @returns The source position immediately after the last digit consumed.
73
64
  */
74
- // @ts-expect-error: @inline is a valid decorator
75
- @inline export function atoiScan<T extends number>(
65
+ export function atoiScan<T extends number>(
76
66
  srcStart: usize,
77
67
  srcEnd: usize,
78
68
  dstPtr: usize,
@@ -1,7 +1,6 @@
1
1
  import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
2
2
 
3
- // @ts-ignore: Decorator valid here
4
- @inline export function bytes<T>(o: T): i32 {
3
+ export function bytes<T>(o: T): i32 {
5
4
  if (isInteger<T>() || isFloat<T>()) {
6
5
  return sizeof<T>();
7
6
  } else if (isManaged<T>() || isReference<T>()) {
@@ -0,0 +1,179 @@
1
+ // Fast, correctly-rounded conversion of `mantissa * 10^power` for the common
2
+ // decimal-exponent range [-22, 22]. This is the Eisel-Lemire conversion used by
3
+ // simdjson's number parser, specialized to the range where json-as previously
4
+ // fell into `scientific()` after the exact-Clinger path rejected mantissas above
5
+ // 2^53. Keeping only 45 cached powers costs 720 bytes instead of simdjson's
6
+ // general 10 KiB table; callers retain `scientific()` as the wide-range fallback.
7
+
8
+ // Consecutive {primary, secondary} truncated 128-bit powers of five for decimal
9
+ // exponents -22 through 22. Sourced from simdjson's power_of_five_128 table.
10
+ // @ts-expect-error: @lazy is a valid decorator
11
+ @lazy const POWERS_OF_FIVE_128: usize = memory.data<u64>([
12
+ 0xf1c90080baf72cb1, 0x5324c68b12dd6800, 0x971da05074da7bee,
13
+ 0xd3f6fc16ebca8000, 0xbce5086492111aea, 0x88f4bb1ca6bd0000,
14
+ 0xec1e4a7db69561a5, 0x2b31e9e3d0700000, 0x9392ee8e921d5d07,
15
+ 0x3aff322e62600000, 0xb877aa3236a4b449, 0x09befeb9fad487c3,
16
+ 0xe69594bec44de15b, 0x4c2ebe687989a9b4, 0x901d7cf73ab0acd9,
17
+ 0x0f9d37014bf60a11, 0xb424dc35095cd80f, 0x538484c19ef38c95,
18
+ 0xe12e13424bb40e13, 0x2865a5f206b06fba, 0x8cbccc096f5088cb,
19
+ 0xf93f87b7442e45d4, 0xafebff0bcb24aafe, 0xf78f69a51539d749,
20
+ 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, 0x89705f4136b4a597,
21
+ 0x31680a88f8953031, 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e,
22
+ 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, 0x8637bd05af6c69b5,
23
+ 0xa63f9a49c2c1b110, 0xa7c5ac471b478423, 0x0fcf80dc33721d54,
24
+ 0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x83126e978d4fdf3b,
25
+ 0x645a1cac083126ea, 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4,
26
+ 0xcccccccccccccccc, 0xcccccccccccccccd, 0x8000000000000000,
27
+ 0x0000000000000000, 0xa000000000000000, 0x0000000000000000,
28
+ 0xc800000000000000, 0x0000000000000000, 0xfa00000000000000,
29
+ 0x0000000000000000, 0x9c40000000000000, 0x0000000000000000,
30
+ 0xc350000000000000, 0x0000000000000000, 0xf424000000000000,
31
+ 0x0000000000000000, 0x9896800000000000, 0x0000000000000000,
32
+ 0xbebc200000000000, 0x0000000000000000, 0xee6b280000000000,
33
+ 0x0000000000000000, 0x9502f90000000000, 0x0000000000000000,
34
+ 0xba43b74000000000, 0x0000000000000000, 0xe8d4a51000000000,
35
+ 0x0000000000000000, 0x9184e72a00000000, 0x0000000000000000,
36
+ 0xb5e620f480000000, 0x0000000000000000, 0xe35fa931a0000000,
37
+ 0x0000000000000000, 0x8e1bc9bf04000000, 0x0000000000000000,
38
+ 0xb1a2bc2ec5000000, 0x0000000000000000, 0xde0b6b3a76400000,
39
+ 0x0000000000000000, 0x8ac7230489e80000, 0x0000000000000000,
40
+ 0xad78ebc5ac620000, 0x0000000000000000, 0xd8d726b7177a8000,
41
+ 0x0000000000000000, 0x878678326eac9000, 0x0000000000000000,
42
+ ]);
43
+
44
+ // High half of a full 64x64 -> 128 multiplication, using four 32-bit partial
45
+ // products. WebAssembly MVP has no i128 or mul-high instruction.
46
+ function mul64High(a: u64, b: u64): u64 {
47
+ const aLo = <u64>(<u32>a);
48
+ const aHi = a >> 32;
49
+ const bLo = <u64>(<u32>b);
50
+ const bHi = b >> 32;
51
+ const lowLow = aLo * bLo;
52
+ const highLow = aHi * bLo;
53
+ const middle = highLow + aLo * bHi;
54
+ const middleCarry = <u64>(middle < highLow);
55
+ const low = lowLow + (middle << 32);
56
+ return aHi * bHi + (middle >> 32) + (middleCarry << 32) + <u64>(low < lowLow);
57
+ }
58
+
59
+ /**
60
+ * Fixed-power Eisel-Lemire conversion for GeoJSON-style coordinates with 14
61
+ * fractional digits. This is the `power == -14` specialization of
62
+ * {@link eiselLemire22}: the cached-power address and binary-exponent base are
63
+ * constants, and the exact-halfway correction is unreachable for this power.
64
+ */
65
+ export function eiselLemireMinus14(significand: u64): f64 {
66
+ let i = significand;
67
+ let leading = <i32>clz(i);
68
+ i <<= leading;
69
+
70
+ let lower = i * 0xb424dc35095cd80f;
71
+ let upper = mul64High(i, 0xb424dc35095cd80f);
72
+
73
+ if ((upper & 0x1ff) == 0x1ff) {
74
+ const secondUpper = mul64High(i, 0x538484c19ef38c95);
75
+ const oldLower = lower;
76
+ lower += secondUpper;
77
+ if (lower < oldLower) upper++;
78
+ }
79
+
80
+ const upperBit = upper >> 63;
81
+ let mantissa = upper >> (<i32>(upperBit + 9));
82
+ leading += <i32>(1 ^ upperBit);
83
+ let realExponent = 1040 - leading;
84
+
85
+ mantissa += mantissa & 1;
86
+ mantissa >>= 1;
87
+
88
+ if (mantissa >= (<u64>1) << 53) {
89
+ mantissa = (<u64>1) << 52;
90
+ realExponent++;
91
+ }
92
+
93
+ const bits = (mantissa & ~((<u64>1) << 52)) | ((<u64>realExponent) << 52);
94
+ return reinterpret<f64>(bits);
95
+ }
96
+
97
+ /** Fixed -14 conversion for significands in [2^53, 2^54). */
98
+ export function eiselLemireMinus14_54Bit(significand: u64): f64 {
99
+ const i = significand << 10;
100
+
101
+ let lower = i * 0xb424dc35095cd80f;
102
+ let upper = mul64High(i, 0xb424dc35095cd80f);
103
+
104
+ if ((upper & 0x1ff) == 0x1ff) {
105
+ const secondUpper = mul64High(i, 0x538484c19ef38c95);
106
+ const oldLower = lower;
107
+ lower += secondUpper;
108
+ if (lower < oldLower) upper++;
109
+ }
110
+
111
+ const upperBit = upper >> 63;
112
+ let mantissa = upper >> (<i32>(upperBit + 9));
113
+ let realExponent = 1030 - <i32>(1 ^ upperBit);
114
+
115
+ mantissa += mantissa & 1;
116
+ mantissa >>= 1;
117
+
118
+ if (mantissa >= (<u64>1) << 53) {
119
+ mantissa = (<u64>1) << 52;
120
+ realExponent++;
121
+ }
122
+
123
+ const bits = (mantissa & ~((<u64>1) << 52)) | ((<u64>realExponent) << 52);
124
+ return reinterpret<f64>(bits);
125
+ }
126
+
127
+ /**
128
+ * Convert a non-zero u64 significand at a decimal exponent in [-22, 22].
129
+ * The result is correctly rounded to IEEE-754 binary64.
130
+ */
131
+ export function eiselLemire22(significand: u64, power: i32): f64 {
132
+ let i = significand;
133
+ let leading = <i32>clz(i);
134
+ i <<= leading;
135
+
136
+ const tableIndex = (<usize>(power + 22)) << 4;
137
+ const factor = load<u64>(POWERS_OF_FIVE_128 + tableIndex);
138
+ let lower = i * factor;
139
+ let upper = mul64High(i, factor);
140
+
141
+ // A primary product ending in nine uncertain bits needs the second table
142
+ // limb. This is rare, but removes Eisel-Lemire's historical fallback.
143
+ if ((upper & 0x1ff) == 0x1ff) {
144
+ const secondUpper = mul64High(
145
+ i,
146
+ load<u64>(POWERS_OF_FIVE_128 + tableIndex, 8),
147
+ );
148
+ const oldLower = lower;
149
+ lower += secondUpper;
150
+ if (lower < oldLower) upper++;
151
+ }
152
+
153
+ const upperBit = upper >> 63;
154
+ let mantissa = upper >> (<i32>(upperBit + 9));
155
+ leading += <i32>(1 ^ upperBit);
156
+ let realExponent = <i32>(((<i64>217706 * power) >> 16) + 1087) - leading;
157
+ const halfwayShift = <i32>(upperBit + 9);
158
+
159
+ // Round exact halfway cases to even.
160
+ if (
161
+ lower <= 1 &&
162
+ power >= -4 &&
163
+ power <= 22 &&
164
+ (mantissa & 3) == 1 &&
165
+ mantissa << halfwayShift == upper
166
+ ) {
167
+ mantissa &= ~(<u64>1);
168
+ }
169
+ mantissa += mantissa & 1;
170
+ mantissa >>= 1;
171
+
172
+ if (mantissa >= (<u64>1) << 53) {
173
+ mantissa = (<u64>1) << 52;
174
+ realExponent++;
175
+ }
176
+
177
+ const bits = (mantissa & ~((<u64>1) << 52)) | ((<u64>realExponent) << 52);
178
+ return reinterpret<f64>(bits);
179
+ }
@@ -1,6 +1,5 @@
1
1
  import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
2
2
 
3
- // @ts-ignore: decorator
4
- @inline export function idofD<T>(value: T): usize {
3
+ export function idofD<T>(value: T): usize {
5
4
  return changetype<OBJECT>(changetype<usize>(value) - TOTAL_OVERHEAD).rtId;
6
5
  }
@@ -1,4 +1,3 @@
1
- // @ts-ignore
2
- @inline export function isSpace(code: u16): boolean {
1
+ export function isSpace(code: u16): boolean {
3
2
  return code == 0x20 || code - 9 <= 4;
4
3
  }
@@ -6,8 +6,8 @@
6
6
  // below. Two reasons:
7
7
  //
8
8
  // 1. V8/wasm lowers `v / 100` (and other `/ <const>`s) to a single
9
- // multiply-shift, so jeaiii's main selling point avoiding division
10
- // hardware gives no win on this target. The op counts come out
9
+ // multiply-shift, so jeaiii's main selling point - avoiding division
10
+ // hardware - gives no win on this target. The op counts come out
11
11
  // roughly equal.
12
12
  //
13
13
  // 2. The div-by-const variant computes each digit pair independently
@@ -24,7 +24,7 @@
24
24
  // - A 100-entry digit-pair LUT keyed on `value % 100`. One `store<u32>`
25
25
  // emits a UTF-16 pair.
26
26
  //
27
- // - Forward write in one pass no `decimalCount32` precomputation, no
27
+ // - Forward write in one pass - no `decimalCount32` precomputation, no
28
28
  // backward write.
29
29
  //
30
30
  // Reference H2H bench: `__benches__/custom/itoa-h2h.bench.ts`.
@@ -43,13 +43,11 @@ function initPairs(): void {
43
43
  _pairsInited = true;
44
44
  }
45
45
 
46
-
47
- @inline export function ensureItoaPairs(): void {
46
+ export function ensureItoaPairs(): void {
48
47
  if (!_pairsInited) initPairs();
49
48
  }
50
49
 
51
- // @ts-expect-error: @inline is a valid decorator
52
- @inline function pair(i: u32): u32 {
50
+ function pair(i: u32): u32 {
53
51
  return load<u32>(DIGIT_PAIRS_UTF16 + ((<usize>i) << 2));
54
52
  }
55
53
 
@@ -213,8 +211,7 @@ export function itoaU64(buf: usize, v: u64): u32 {
213
211
  *
214
212
  * Returns the number of UTF-16 chars written.
215
213
  */
216
- // @ts-expect-error: @inline is a valid decorator
217
- @inline export function itoaFast<T extends number>(buf: usize, value: T): u32 {
214
+ export function itoaFast<T extends number>(buf: usize, value: T): u32 {
218
215
  if (sizeof<T>() <= 4) {
219
216
  if (isSigned<T>()) {
220
217
  let v = <i32>value;
@@ -1,4 +1,3 @@
1
- // @ts-ignore: Decorator valid here
2
- @inline export function nextPowerOf2(n: u32): u32 {
1
+ export function nextPowerOf2(n: u32): u32 {
3
2
  return 1 << (32 - clz(n - 1));
4
3
  }
@@ -3,7 +3,7 @@ import { ptrToStr } from "./ptrToStr";
3
3
  // Lemire-style fast float parser.
4
4
  //
5
5
  // Reference: Daniel Lemire, "Number parsing at a gigabyte per second"
6
- // (2021). https://arxiv.org/abs/2101.11408 implemented in
6
+ // (2021). https://arxiv.org/abs/2101.11408 - implemented in
7
7
  // https://github.com/fastfloat/fast_float.
8
8
  //
9
9
  // The "fast path" applies when:
@@ -35,13 +35,11 @@ const MAX_EXACT_POW10: i32 = 22;
35
35
  // 2^53 = 9_007_199_254_740_992. Any u64 <= this is exact in f64.
36
36
  const MAX_EXACT_MANTISSA: u64 = 1 << 53;
37
37
 
38
-
39
- @inline function loadPow10(exp: u32): f64 {
38
+ function loadPow10(exp: u32): f64 {
40
39
  return load<f64>(POW10_F64_POS + ((<usize>exp) << 3));
41
40
  }
42
41
 
43
-
44
- @inline function fallback<T>(srcStart: usize, srcEnd: usize): T {
42
+ function fallback<T>(srcStart: usize, srcEnd: usize): T {
45
43
  const s = ptrToStr(srcStart, srcEnd);
46
44
  // @ts-ignore: type
47
45
  const type: T = 0;
@@ -0,0 +1,69 @@
1
+ // Whitespace-run scanner selected once for large pretty JSON documents.
2
+
3
+ // @ts-expect-error: @lazy is a valid decorator
4
+ @lazy const SPLAT_SPACE = i16x8.splat(0x20);
5
+ // @ts-expect-error: @lazy is a valid decorator
6
+ @lazy const SPLAT_WS_LO = i16x8.splat(9);
7
+ // @ts-expect-error: @lazy is a valid decorator
8
+ @lazy const SPLAT_WS_SPAN = i16x8.splat(4);
9
+ // @ts-expect-error: @lazy is a valid decorator
10
+ @lazy const SPLAT_LF = i16x8.splat(10);
11
+
12
+
13
+ @inline
14
+ function whitespaceMask(block: v128): i32 {
15
+ return i16x8.bitmask(
16
+ v128.or(
17
+ i16x8.eq(block, SPLAT_SPACE),
18
+ i16x8.le_u(i16x8.sub(block, SPLAT_WS_LO), SPLAT_WS_SPAN),
19
+ ),
20
+ );
21
+ }
22
+
23
+ /** Detect an eight-space indentation run near the document head. */
24
+ export function hasLongPrettyIndent_SIMD(srcStart: usize, srcEnd: usize): bool {
25
+ const sampleEnd = min(srcEnd, srcStart + 4096);
26
+ const vectorEnd = sampleEnd >= 16 ? sampleEnd - 16 : 0;
27
+ while (srcStart <= vectorEnd) {
28
+ let mask = i16x8.bitmask(i16x8.eq(load<v128>(srcStart), SPLAT_LF));
29
+ while (mask != 0) {
30
+ const newline = srcStart + ((<usize>ctz(mask)) << 1);
31
+ if (
32
+ newline + 18 <= srcEnd &&
33
+ !v128.any_true(v128.xor(load<v128>(newline, 2), i16x8.splat(0x20)))
34
+ )
35
+ return true;
36
+ mask &= mask - 1;
37
+ }
38
+ srcStart += 16;
39
+ }
40
+ return false;
41
+ }
42
+
43
+ /** `srcStart` is known to point at JSON whitespace. */
44
+ export function skipPrettyWhitespace_SIMD(
45
+ srcStart: usize,
46
+ srcEnd: usize,
47
+ ): usize {
48
+ srcStart += 2;
49
+ if (srcStart >= srcEnd) return srcStart;
50
+ let code = load<u16>(srcStart);
51
+ if (code != 0x20 && code - 9 > 4) return srcStart;
52
+
53
+ const vectorEnd = srcEnd >= 16 ? srcEnd - 16 : 0;
54
+ while (srcStart <= vectorEnd) {
55
+ const mask = whitespaceMask(load<v128>(srcStart));
56
+ if (mask == 0xff) {
57
+ srcStart += 16;
58
+ continue;
59
+ }
60
+ srcStart += (<usize>ctz(~mask & 0xff)) << 1;
61
+ return srcStart;
62
+ }
63
+ do {
64
+ srcStart += 2;
65
+ if (srcStart >= srcEnd) return srcStart;
66
+ code = load<u16>(srcStart);
67
+ } while (code == 0x20 || code - 9 <= 4);
68
+ return srcStart;
69
+ }
@@ -1,5 +1,4 @@
1
- // @ts-ignore: decorator
2
- @inline export function ptrToStr(start: usize, end: usize): string {
1
+ export function ptrToStr(start: usize, end: usize): string {
3
2
  const size = end - start;
4
3
  const out = __new(size, idof<string>());
5
4
  memory.copy(out, start, size);
@@ -22,23 +22,40 @@ import { isSpace } from "./isSpace";
22
22
  // @ts-expect-error: @lazy is a valid decorator
23
23
  @lazy const SPLAT_SPACE = i16x8.splat(0x20);
24
24
  // JSON whitespace besides space is the contiguous range 0x09..0x0d
25
- // (tab/LF/VT/FF/CR), matched as `(c - 9) u<= 4` one sub + one unsigned
25
+ // (tab/LF/VT/FF/CR), matched as `(c - 9) u<= 4` - one sub + one unsigned
26
26
  // compare instead of five equality tests. Exact: matches `isSpace` with no
27
27
  // false positives.
28
28
  // @ts-expect-error: @lazy is a valid decorator
29
29
  @lazy const SPLAT_WS_LO = i16x8.splat(0x09);
30
30
  // @ts-expect-error: @lazy is a valid decorator
31
31
  @lazy const SPLAT_WS_SPAN = i16x8.splat(0x04);
32
+ // @ts-expect-error: @lazy is a valid decorator
33
+ @lazy const SPLAT_BRACKET_LEFT = i16x8.splat(<i16>BRACKET_LEFT);
34
+ // Clears bit 5 (0x20), folding `{`/`}` onto `[`/`]` so one pair of compares
35
+ // matches either bracket flavor. No ASCII char besides `[{`/`]}` folds onto
36
+ // `[`/`]`, so the structural mask stays exact.
37
+ // @ts-expect-error: @lazy is a valid decorator
38
+ @lazy const SPLAT_BRACKET_FOLD = i16x8.splat(<i16>0xffdf);
32
39
 
33
- // @ts-expect-error: @inline is a valid decorator
34
- @inline function quoteOrBackslashMask(block: v128): i32 {
40
+ function quoteOrBackslashMask(block: v128): i32 {
35
41
  return i16x8.bitmask(
36
42
  v128.or(i16x8.eq(block, SPLAT_QUOTE), i16x8.eq(block, SPLAT_BACK_SLASH)),
37
43
  );
38
44
  }
39
45
 
40
- // @ts-expect-error: @inline is a valid decorator
41
- @inline function scalarTerminatorMask(block: v128): i32 {
46
+ // Lanes equal to `"`, `{`, `}`, `[`, or `]` - the only bytes that, outside a
47
+ // string, change depth or open a string. Everything else (digits, `:`, `,`,
48
+ // whitespace, true/false/null) can be bulk-skipped between them.
49
+ function structuralOrQuoteMask(block: v128): i32 {
50
+ const folded = v128.and(block, SPLAT_BRACKET_FOLD);
51
+ const brackets = v128.or(
52
+ i16x8.eq(folded, SPLAT_BRACKET_LEFT),
53
+ i16x8.eq(folded, SPLAT_BRACKET_RIGHT),
54
+ );
55
+ return i16x8.bitmask(v128.or(brackets, i16x8.eq(block, SPLAT_QUOTE)));
56
+ }
57
+
58
+ function scalarTerminatorMask(block: v128): i32 {
42
59
  const structural = v128.or(
43
60
  v128.or(i16x8.eq(block, SPLAT_COMMA), i16x8.eq(block, SPLAT_BRACE_RIGHT)),
44
61
  i16x8.eq(block, SPLAT_BRACKET_RIGHT),
@@ -86,23 +103,71 @@ function scanQuotedValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
86
103
  }
87
104
 
88
105
  function scanCompositeValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
89
- // Track object/array depth scalar-side (structural tokens are sparse and a
90
- // bulk token-mask scan loses to a tight loop on token-dense objects), but
91
- // skip nested string VALUES with the vectorized quoted scan that's where
92
- // the long runs (URLs, base64, prose) actually are.
106
+ // Walk every structural event in a loaded block before advancing. This is
107
+ // substantially cheaper for object-heavy values than restarting a SIMD hunt
108
+ // after every short key: one v128 load now covers all quotes and brackets in
109
+ // its eight UTF-16 lanes. Brackets inside strings are ignored, and a closing
110
+ // quote is active only when preceded by an even-length backslash run.
93
111
  let depth: i32 = 1;
94
112
  let ptr = srcStart + 2;
113
+ const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
114
+ let inString = false;
115
+
116
+ while (ptr <= srcEnd16) {
117
+ let mask = structuralOrQuoteMask(load<v128>(ptr));
118
+ while (mask != 0) {
119
+ const lane = usize(ctz(mask) << 1);
120
+ mask &= mask - 1;
121
+ const eventPtr = ptr + lane;
122
+ const code = load<u16>(eventPtr);
123
+
124
+ if (code == QUOTE) {
125
+ if (!inString) {
126
+ inString = true;
127
+ } else {
128
+ let slash = eventPtr - 2;
129
+ let escaped = false;
130
+ while (slash > srcStart && load<u16>(slash) == BACK_SLASH) {
131
+ escaped = !escaped;
132
+ slash -= 2;
133
+ }
134
+ if (!escaped) inString = false;
135
+ }
136
+ continue;
137
+ }
138
+ if (inString) continue;
139
+
140
+ const folded = code & 0xffdf;
141
+ if (folded == BRACKET_LEFT) {
142
+ depth++;
143
+ } else if (folded == BRACKET_RIGHT && --depth == 0) {
144
+ return eventPtr + 2;
145
+ }
146
+ }
147
+ ptr += 16;
148
+ }
149
+
95
150
  while (ptr < srcEnd) {
96
151
  const code = load<u16>(ptr);
97
152
  if (code == QUOTE) {
98
- ptr = scanQuotedValueEnd_SIMD(ptr, srcEnd);
99
- if (!ptr) return 0;
100
- continue;
101
- }
102
- if (code == BRACE_LEFT || code == BRACKET_LEFT) {
103
- depth++;
104
- } else if (code == BRACE_RIGHT || code == BRACKET_RIGHT) {
105
- if (--depth == 0) return ptr + 2;
153
+ if (!inString) {
154
+ inString = true;
155
+ } else {
156
+ let slash = ptr - 2;
157
+ let escaped = false;
158
+ while (slash > srcStart && load<u16>(slash) == BACK_SLASH) {
159
+ escaped = !escaped;
160
+ slash -= 2;
161
+ }
162
+ if (!escaped) inString = false;
163
+ }
164
+ } else if (!inString) {
165
+ const folded = code & 0xffdf;
166
+ if (folded == BRACKET_LEFT) {
167
+ depth++;
168
+ } else if (folded == BRACKET_RIGHT && --depth == 0) {
169
+ return ptr + 2;
170
+ }
106
171
  }
107
172
  ptr += 2;
108
173
  }
@@ -135,11 +200,7 @@ function scanScalarValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
135
200
  return srcStart;
136
201
  }
137
202
 
138
- // @ts-expect-error: @inline is a valid decorator
139
- @inline export function scanValueEnd_SIMD<T>(
140
- srcStart: usize,
141
- srcEnd: usize,
142
- ): usize {
203
+ export function scanValueEnd_SIMD<T>(srcStart: usize, srcEnd: usize): usize {
143
204
  if (srcStart >= srcEnd) return 0;
144
205
  const first = load<u16>(srcStart);
145
206