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
@@ -0,0 +1,199 @@
1
+ import { bs } from "../../../lib/as-bs";
2
+ import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
3
+ import { __heap_base } from "memory";
4
+ import { BACK_SLASH, QUOTE } from "../../custom/chars";
5
+ import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
6
+
7
+ // @ts-ignore: inline
8
+ @inline function hexDigit(c: u16): u32 {
9
+ if (c <= 0x39) return c - 0x30; // '0'-'9'
10
+ if (c <= 0x46) return c - 0x37; // 'A'-'F'
11
+ return c - 0x57; // 'a'-'f'
12
+ }
13
+
14
+ // @ts-ignore: inline
15
+ @inline function hex4ToU16(srcStart: usize): u16 {
16
+ return <u16>(
17
+ ((hexDigit(load<u16>(srcStart)) << 12) |
18
+ (hexDigit(load<u16>(srcStart, 2)) << 8) |
19
+ (hexDigit(load<u16>(srcStart, 4)) << 4) |
20
+ hexDigit(load<u16>(srcStart, 6)))
21
+ );
22
+ }
23
+
24
+ // @ts-ignore: inline
25
+ @inline function isHexDigit(c: u16): bool {
26
+ return (
27
+ (c >= 0x30 && c <= 0x39) ||
28
+ (c >= 0x41 && c <= 0x46) ||
29
+ (c >= 0x61 && c <= 0x66)
30
+ );
31
+ }
32
+
33
+ // Strict RFC 8259 check for the char following a backslash, at [escPtr, srcEnd).
34
+ // Legal escapes: " \ / b f n r t and \uXXXX (4 hex digits). Throws otherwise:
35
+ // unknown escape letter, a trailing backslash, or a short / non-hex \u.
36
+ // @ts-ignore: inline
37
+ @inline function validateEscape(escPtr: usize, srcEnd: usize): void {
38
+ if (escPtr >= srcEnd)
39
+ throw new Error("Invalid JSON string: incomplete escape");
40
+ const code = load<u16>(escPtr);
41
+ if (code == 0x75) {
42
+ // \uXXXX
43
+ if (escPtr + 10 > srcEnd)
44
+ throw new Error("Invalid JSON string: incomplete \\u escape");
45
+ if (
46
+ !isHexDigit(load<u16>(escPtr, 2)) ||
47
+ !isHexDigit(load<u16>(escPtr, 4)) ||
48
+ !isHexDigit(load<u16>(escPtr, 6)) ||
49
+ !isHexDigit(load<u16>(escPtr, 8))
50
+ )
51
+ throw new Error("Invalid JSON string: \\u escape needs 4 hex digits");
52
+ return;
53
+ }
54
+ // short escapes: " \ / b f n r t
55
+ if (
56
+ code != 0x22 &&
57
+ code != 0x5c &&
58
+ code != 0x2f &&
59
+ code != 0x62 &&
60
+ code != 0x66 &&
61
+ code != 0x6e &&
62
+ code != 0x72 &&
63
+ code != 0x74
64
+ )
65
+ throw new Error("Invalid JSON string: illegal escape");
66
+ }
67
+
68
+ // @ts-ignore: inline
69
+ @inline export function deserializeString_NAIVE(
70
+ srcStart: usize,
71
+ srcEnd: usize,
72
+ ): string {
73
+ // RFC 8259: a string is quote-framed. All callers pass quote-inclusive
74
+ // bounds, so the first and last chars must be `"` (rejects `"` alone and
75
+ // trailing garbage like `""x`).
76
+ if (
77
+ srcEnd - srcStart < 4 ||
78
+ load<u16>(srcStart) != QUOTE ||
79
+ load<u16>(srcEnd - 2) != QUOTE
80
+ )
81
+ throw new Error("Invalid JSON string: missing surrounding quotes");
82
+ // Strip quotes
83
+ srcStart += 2;
84
+ srcEnd -= 2;
85
+ const outStart = bs.offset - bs.buffer;
86
+ bs.ensureSize(u32(srcEnd - srcStart));
87
+
88
+ while (srcStart < srcEnd) {
89
+ const block = load<u16>(srcStart);
90
+ store<u16>(bs.offset, block);
91
+ srcStart += 2;
92
+
93
+ // Early exit
94
+ if (block !== 0x5c) {
95
+ // RFC 8259: literal control chars (U+0000..U+001F) must be escaped.
96
+ if (block < 0x20)
97
+ throw new Error("Invalid JSON string: unescaped control character");
98
+ bs.offset += 2;
99
+ continue;
100
+ }
101
+
102
+ validateEscape(srcStart, srcEnd);
103
+ const code = load<u16>(srcStart);
104
+ if (code !== 0x75) {
105
+ // Short escapes (\n \t \" \\)
106
+ const block = load<u16>(srcStart);
107
+ const escape = load<u16>(DESERIALIZE_ESCAPE_TABLE + block);
108
+ store<u16>(bs.offset, escape);
109
+ srcStart += 2;
110
+ } else {
111
+ // Unicode escape (\uXXXX)
112
+ const escaped = hex4ToU16(srcStart + 2);
113
+ store<u16>(bs.offset, escaped);
114
+ srcStart += 10;
115
+ }
116
+
117
+ bs.offset += 2;
118
+ }
119
+ return bs.sliceOut<string>(outStart);
120
+ }
121
+
122
+ // Writes into the destination field, reusing or resizing the backing string.
123
+ // Mirrors `writeStringToField` in ../swar/string.ts.
124
+ // @ts-ignore: inline
125
+ @inline function writeStringToField(
126
+ dstFieldPtr: usize,
127
+ srcStart: usize,
128
+ byteLength: u32,
129
+ ): void {
130
+ if (byteLength == 0) {
131
+ store<usize>(dstFieldPtr, changetype<usize>(""));
132
+ return;
133
+ }
134
+
135
+ const current = load<usize>(dstFieldPtr);
136
+ let stringPtr: usize;
137
+ if (current >= __heap_base) {
138
+ if (changetype<OBJECT>(current - TOTAL_OVERHEAD).rtSize == byteLength) {
139
+ stringPtr = current;
140
+ } else {
141
+ stringPtr = __renew(current, byteLength);
142
+ store<usize>(dstFieldPtr, stringPtr);
143
+ }
144
+ } else {
145
+ stringPtr = __new(byteLength, idof<string>());
146
+ store<usize>(dstFieldPtr, stringPtr);
147
+ }
148
+ memory.copy(stringPtr, srcStart, byteLength);
149
+ }
150
+
151
+ // @ts-ignore: inline
152
+ @inline export function deserializeStringField_NAIVE<T extends string | null>(
153
+ srcStart: usize,
154
+ srcEnd: usize,
155
+ dstObj: usize,
156
+ dstOffset: usize = 0,
157
+ ): usize {
158
+ const dstFieldPtr = dstObj + dstOffset;
159
+ if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
160
+ abort("Expected leading quote");
161
+
162
+ const payloadStart = srcStart + 2;
163
+ srcStart = payloadStart;
164
+
165
+ bs.offset = bs.buffer;
166
+ bs.ensureSize(<u32>(srcEnd - payloadStart));
167
+
168
+ while (srcStart < srcEnd) {
169
+ const block = load<u16>(srcStart);
170
+
171
+ if (block == QUOTE) {
172
+ writeStringToField(dstFieldPtr, bs.buffer, <u32>(bs.offset - bs.buffer));
173
+ bs.offset = bs.buffer;
174
+ return srcStart + 2;
175
+ }
176
+
177
+ if (block != BACK_SLASH) {
178
+ store<u16>(bs.offset, block);
179
+ bs.offset += 2;
180
+ srcStart += 2;
181
+ continue;
182
+ }
183
+
184
+ const code = load<u16>(srcStart, 2);
185
+ if (code !== 0x75) {
186
+ // Short escapes (\n \t \" \\)
187
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
188
+ srcStart += 4;
189
+ } else {
190
+ // Unicode escape (\uXXXX)
191
+ store<u16>(bs.offset, hex4ToU16(srcStart + 4));
192
+ srcStart += 12;
193
+ }
194
+ bs.offset += 2;
195
+ }
196
+
197
+ abort("Expected closing quote");
198
+ return 0;
199
+ }
@@ -1,5 +1,5 @@
1
1
  import { COMMA, BRACKET_RIGHT } from "../../custom/chars";
2
- import { deserializeFloat } from "./float";
2
+ import { deserializeFloat_NAIVE } from "./float";
3
3
  import { atoi, isSpace } from "../../util";
4
4
 
5
5
 
@@ -25,7 +25,7 @@ import { atoi, isSpace } from "../../util";
25
25
  return count;
26
26
  }
27
27
 
28
- export function deserializeTypedArray<T extends ArrayLike<number>>(
28
+ export function deserializeTypedArray_NAIVE<T extends ArrayLike<number>>(
29
29
  srcStart: usize,
30
30
  srcEnd: usize,
31
31
  dst: usize = 0,
@@ -49,7 +49,7 @@ export function deserializeTypedArray<T extends ArrayLike<number>>(
49
49
  if (code == COMMA || code == BRACKET_RIGHT || isSpace(code)) {
50
50
  if (isFloat<valueof<T>>()) {
51
51
  unchecked(
52
- (out[index++] = deserializeFloat<valueof<T>>(
52
+ (out[index++] = deserializeFloat_NAIVE<valueof<T>>(
53
53
  lastIndex,
54
54
  srcStart,
55
55
  )),
@@ -69,7 +69,7 @@ export function deserializeTypedArray<T extends ArrayLike<number>>(
69
69
  return out;
70
70
  }
71
71
 
72
- export function deserializeArrayBuffer(
72
+ export function deserializeArrayBuffer_NAIVE(
73
73
  srcStart: usize,
74
74
  srcEnd: usize,
75
75
  dst: usize = 0,
@@ -1,7 +1,7 @@
1
1
  import { atoi } from "../../util/atoi";
2
2
 
3
3
  // @ts-ignore: inline
4
- @inline export function deserializeUnsigned<T>(
4
+ @inline export function deserializeUnsigned_NAIVE<T extends number>(
5
5
  srcStart: usize,
6
6
  srcEnd: usize,
7
7
  ): T {
@@ -9,7 +9,7 @@ import { atoi } from "../../util/atoi";
9
9
  }
10
10
 
11
11
  // @ts-ignore: inline
12
- @inline export function deserializeUnsignedField<T extends number>(
12
+ @inline export function deserializeUnsignedField_NAIVE<T extends number>(
13
13
  srcStart: usize,
14
14
  srcEnd: usize,
15
15
  dstObj: usize,
@@ -5,19 +5,19 @@ import { isSpace } from "../../../util";
5
5
  const ASCII_LANE_MASK_4: u64 = 0x00ff00ff00ff00ff;
6
6
  const ASCII_ZERO_4: u64 = 0x0030003000300030;
7
7
 
8
-
8
+ // @ts-expect-error: decorators valid here
9
9
  @lazy const SPLAT_30 = i16x8.splat(0x30);
10
10
 
11
-
11
+ // @ts-expect-error: decorators valid here
12
12
  @lazy const SPLAT_09 = i16x8.splat(9);
13
13
 
14
-
14
+ // @ts-expect-error: decorators valid here
15
15
  @lazy const ZERO_I16X8 = i16x8.splat(0);
16
16
 
17
-
17
+ // @ts-expect-error: decorators valid here
18
18
  @lazy const ZERO_I32X4 = i32x4.splat(0);
19
19
 
20
-
20
+ // @ts-expect-error: decorators valid here
21
21
  @lazy const PACK_WEIGHTS_10_1 = i8x16(
22
22
  10,
23
23
  1,
@@ -37,10 +37,10 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
37
37
  0,
38
38
  );
39
39
 
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
-
43
+ // @ts-expect-error: decorators valid here
44
44
  @inline function storeSignedInteger<T extends number[]>(
45
45
  slot: usize,
46
46
  value: i64,
@@ -58,7 +58,7 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
58
58
  }
59
59
  }
60
60
 
61
-
61
+ // @ts-expect-error: decorators valid here
62
62
  @inline function storeUnsignedInteger<T extends number[]>(
63
63
  slot: usize,
64
64
  value: u64,
@@ -76,7 +76,7 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
76
76
  }
77
77
  }
78
78
 
79
-
79
+ // @ts-expect-error: decorators valid here
80
80
  @inline function tryParseEightDigitsSIMD(srcStart: usize, value: u64): u64 {
81
81
  const block = load<v128>(srcStart);
82
82
  const digits = i16x8.sub(block, SPLAT_30);
@@ -96,8 +96,7 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
96
96
  // As in the SWAR variant: the parse helpers take a `slot` (`writePtr`) and
97
97
  // store directly. The dispatcher owns `out.length = maxElements` and the
98
98
  // per-element `writePtr` advance so `Array.push` is removed for every
99
- // integer width, not just the narrow-lane fast path.
100
-
99
+ // @ts-expect-error: decorators valid here
101
100
  @inline function parseSignedIntegerSIMD<T extends number[]>(
102
101
  srcStart: usize,
103
102
  srcEnd: usize,
@@ -136,7 +135,7 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
136
135
  return srcStart;
137
136
  }
138
137
 
139
-
138
+ // @ts-expect-error: decorators valid here
140
139
  @inline function parseUnsignedIntegerSIMD<T extends number[]>(
141
140
  srcStart: usize,
142
141
  srcEnd: usize,
@@ -166,7 +165,7 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
166
165
  return srcStart;
167
166
  }
168
167
 
169
-
168
+ // @ts-expect-error: decorators valid here
170
169
  @lazy const COMMA_SPLAT_8 = i16x8.splat(<i16>COMMA);
171
170
 
172
171
  // Pair-multiply weights for the common two-element packings in 8-char blocks.
@@ -181,24 +180,25 @@ const ASCII_ZERO_4: u64 = 0x0030003000300030;
181
180
  // 2+2 (`DD,DD,??`) bitmask 0x24, advance 12
182
181
  // 3+1 (`DDD,D,??`) bitmask 0x28, advance 12
183
182
  // 1+3 (`D,DDD,??`) bitmask 0x22, advance 12
183
+ // @ts-expect-error: decorators valid here
184
184
  @lazy const PAIR_WEIGHTS_3_3_8 = i16x8(100, 10, 1, 0, 100, 10, 1, 0);
185
185
 
186
-
186
+ // @ts-expect-error: decorators valid here
187
187
  @lazy const PAIR_WEIGHTS_3_2_8 = i16x8(100, 10, 1, 0, 10, 1, 0, 0);
188
188
 
189
-
189
+ // @ts-expect-error: decorators valid here
190
190
  @lazy const PAIR_WEIGHTS_2_3_8 = i16x8(10, 1, 0, 100, 10, 1, 0, 0);
191
191
 
192
-
192
+ // @ts-expect-error: decorators valid here
193
193
  @lazy const PAIR_WEIGHTS_2_2_8 = i16x8(10, 1, 0, 10, 1, 0, 0, 0);
194
194
 
195
-
195
+ // @ts-expect-error: decorators valid here
196
196
  @lazy const PAIR_WEIGHTS_3_1_8 = i16x8(100, 10, 1, 0, 1, 0, 0, 0);
197
197
 
198
-
198
+ // @ts-expect-error: decorators valid here
199
199
  @lazy const PAIR_WEIGHTS_1_3_8 = i16x8(1, 0, 100, 10, 1, 0, 0, 0);
200
200
 
201
-
201
+ // @ts-expect-error: decorators valid here
202
202
  @lazy const SPLAT_30_8 = i16x8.splat(0x30);
203
203
 
204
204
  /**
@@ -0,0 +1,303 @@
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.
5
+ //
6
+ // Output is bit-identical to `f64.parse` / `f32.parse` for every input — the
7
+ // SIMD strides only change how the u64 mantissa is accumulated, not what it
8
+ // becomes.
9
+ //
10
+ // Requires `--enable simd` at compile time. Dead-code-eliminated when
11
+ // JSON_MODE != SIMD.
12
+
13
+ import { ptrToStr } from "../../util/ptrToStr";
14
+ import { parse4Digits_PairMul } from "../../util/swar-int";
15
+ import { parse16Digits_SIMD } from "../../util/simd-int";
16
+ import { scientific } from "../../util/scientific";
17
+ import { loadPow10, MAX_EXACT_MANTISSA, MAX_EXACT_POW10 } from "../swar/float";
18
+
19
+ const ASCII_PLUS: u16 = 43;
20
+ const ASCII_MINUS: u16 = 45;
21
+ const ASCII_DOT: u16 = 46;
22
+ const ASCII_ZERO: u16 = 48;
23
+ const ASCII_E_UP: u16 = 69;
24
+ const ASCII_E_LO: u16 = 101;
25
+
26
+ // @ts-ignore: inline
27
+ @inline function fallback<T>(srcStart: usize, srcEnd: usize): T {
28
+ const s = ptrToStr(srcStart, srcEnd);
29
+ // @ts-ignore
30
+ const type: T = 0;
31
+ // @ts-ignore
32
+ if (type instanceof f64) return <T>f64.parse(s);
33
+ // @ts-ignore
34
+ return <T>(<f32>f32.parse(s));
35
+ }
36
+
37
+ // @ts-ignore: inline
38
+ @inline function fallbackField<T extends number>(
39
+ origStart: usize,
40
+ end: usize,
41
+ fieldPtr: usize,
42
+ ): void {
43
+ const s = ptrToStr(origStart, end);
44
+ if (sizeof<T>() == sizeof<f32>()) {
45
+ store<f32>(fieldPtr, f32.parse(s));
46
+ } else {
47
+ store<f64>(fieldPtr, f64.parse(s));
48
+ }
49
+ }
50
+
51
+ // @ts-ignore: inline
52
+ @inline export function deserializeFloat_SIMD<T>(
53
+ srcStart: usize,
54
+ srcEnd: usize,
55
+ ): T {
56
+ const origStart = srcStart;
57
+ let p = srcStart;
58
+ let negative = false;
59
+ if (p < srcEnd && load<u16>(p) == ASCII_MINUS) {
60
+ negative = true;
61
+ p += 2;
62
+ }
63
+
64
+ let mantissa: u64 = 0;
65
+ let intDigits: i32 = 0;
66
+ while (p < srcEnd) {
67
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
68
+ if (d > 9) break;
69
+ mantissa = mantissa * 10 + <u64>d;
70
+ intDigits++;
71
+ p += 2;
72
+ }
73
+
74
+ // Fractional part: parse8 SIMD stride → parse4 SWAR stride → scalar tail.
75
+ // `intDigits + fracDigits <= 11` gate keeps `mantissa * 10^8 + parsed8`
76
+ // under u64 max even when the integer part is large.
77
+ let fracDigits: i32 = 0;
78
+ if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
79
+ p += 2;
80
+ // parse16 SIMD only fires on long fractions (>=16 digits ahead, with
81
+ // <=3 integer digits to keep mantissa * 1e16 under u64 max). Rare in
82
+ // typical JSON but a big win when it does fire (8 digits per stride in
83
+ // SWAR's parse8 was benched even/worse than parse4, so we skip parse8
84
+ // entirely and let parse4 handle the 4-15 char tail).
85
+ while (p + 30 < srcEnd && intDigits + fracDigits <= 3) {
86
+ const parsed = parse16Digits_SIMD(p);
87
+ if (parsed == U64.MAX_VALUE) break;
88
+ mantissa = mantissa * 10_000_000_000_000_000 + parsed;
89
+ fracDigits += 16;
90
+ p += 32;
91
+ }
92
+ while (p + 6 < srcEnd) {
93
+ const parsed = parse4Digits_PairMul(load<u64>(p));
94
+ if (parsed == U32.MAX_VALUE) break;
95
+ mantissa = mantissa * 10_000 + <u64>parsed;
96
+ fracDigits += 4;
97
+ p += 8;
98
+ }
99
+ while (p < srcEnd) {
100
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
101
+ if (d > 9) break;
102
+ mantissa = mantissa * 10 + <u64>d;
103
+ fracDigits++;
104
+ p += 2;
105
+ }
106
+ }
107
+
108
+ const mantDigits = intDigits + fracDigits;
109
+ if (mantDigits == 0) return fallback<T>(origStart, srcEnd);
110
+
111
+ let exponent: i32 = -fracDigits;
112
+
113
+ if (p < srcEnd) {
114
+ const c = load<u16>(p);
115
+ if (c == ASCII_E_LO || c == ASCII_E_UP) {
116
+ const expStart = p;
117
+ p += 2;
118
+ if (p >= srcEnd) return fallback<T>(origStart, expStart);
119
+ let expNeg = false;
120
+ const sc = load<u16>(p);
121
+ if (sc == ASCII_MINUS) {
122
+ expNeg = true;
123
+ p += 2;
124
+ } else if (sc == ASCII_PLUS) {
125
+ p += 2;
126
+ }
127
+ if (p >= srcEnd) return fallback<T>(origStart, expStart);
128
+ let exp: i32 = 0;
129
+ let expDigits: i32 = 0;
130
+ while (p < srcEnd) {
131
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
132
+ if (d > 9) break;
133
+ exp = exp * 10 + <i32>d;
134
+ expDigits++;
135
+ if (expDigits > 4) return fallback<T>(origStart, srcEnd);
136
+ p += 2;
137
+ }
138
+ if (expDigits == 0) return fallback<T>(origStart, expStart);
139
+ exponent += expNeg ? -exp : exp;
140
+ }
141
+ }
142
+
143
+ let result: f64;
144
+ if (
145
+ mantDigits <= 19 &&
146
+ mantissa <= MAX_EXACT_MANTISSA &&
147
+ exponent <= MAX_EXACT_POW10 &&
148
+ exponent >= -MAX_EXACT_POW10
149
+ ) {
150
+ result = <f64>mantissa;
151
+ if (exponent > 0) {
152
+ result *= loadPow10(<u32>exponent);
153
+ } else if (exponent < 0) {
154
+ result /= loadPow10(<u32>-exponent);
155
+ }
156
+ } else if (mantDigits <= 19) {
157
+ result = scientific(mantissa, exponent);
158
+ } else {
159
+ return fallback<T>(origStart, srcEnd);
160
+ }
161
+ if (negative) result = -result;
162
+
163
+ // @ts-ignore
164
+ const type: T = 0;
165
+ // @ts-ignore
166
+ if (type instanceof f64) return <T>result;
167
+ // @ts-ignore
168
+ return <T>(<f32>result);
169
+ }
170
+
171
+ // @ts-ignore: inline
172
+ @inline export function deserializeFloatField_SIMD<T extends number>(
173
+ srcStart: usize,
174
+ srcEnd: usize,
175
+ dstObj: usize,
176
+ dstOffset: usize = 0,
177
+ ): usize {
178
+ const fieldPtr = dstObj + dstOffset;
179
+ const origStart = srcStart;
180
+ let p = srcStart;
181
+ let negative = false;
182
+ if (p < srcEnd && load<u16>(p) == ASCII_MINUS) {
183
+ negative = true;
184
+ p += 2;
185
+ }
186
+
187
+ let mantissa: u64 = 0;
188
+ let intDigits: i32 = 0;
189
+ while (p < srcEnd) {
190
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
191
+ if (d > 9) break;
192
+ mantissa = mantissa * 10 + <u64>d;
193
+ intDigits++;
194
+ p += 2;
195
+ }
196
+
197
+ let fracDigits: i32 = 0;
198
+ if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
199
+ p += 2;
200
+ // parse16 SIMD only fires on long fractions (>=16 digits ahead, with
201
+ // <=3 integer digits to keep mantissa * 1e16 under u64 max). Rare in
202
+ // typical JSON but a big win when it does fire (8 digits per stride in
203
+ // SWAR's parse8 was benched even/worse than parse4, so we skip parse8
204
+ // entirely and let parse4 handle the 4-15 char tail).
205
+ while (p + 30 < srcEnd && intDigits + fracDigits <= 3) {
206
+ const parsed = parse16Digits_SIMD(p);
207
+ if (parsed == U64.MAX_VALUE) break;
208
+ mantissa = mantissa * 10_000_000_000_000_000 + parsed;
209
+ fracDigits += 16;
210
+ p += 32;
211
+ }
212
+ while (p + 6 < srcEnd) {
213
+ const parsed = parse4Digits_PairMul(load<u64>(p));
214
+ if (parsed == U32.MAX_VALUE) break;
215
+ mantissa = mantissa * 10_000 + <u64>parsed;
216
+ fracDigits += 4;
217
+ p += 8;
218
+ }
219
+ while (p < srcEnd) {
220
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
221
+ if (d > 9) break;
222
+ mantissa = mantissa * 10 + <u64>d;
223
+ fracDigits++;
224
+ p += 2;
225
+ }
226
+ }
227
+
228
+ const mantDigits = intDigits + fracDigits;
229
+ if (mantDigits == 0) unreachable();
230
+
231
+ let exponent: i32 = -fracDigits;
232
+
233
+ if (p < srcEnd) {
234
+ const c = load<u16>(p);
235
+ if (c == ASCII_E_LO || c == ASCII_E_UP) {
236
+ const expStart = p;
237
+ p += 2;
238
+ if (p >= srcEnd) {
239
+ fallbackField<T>(origStart, expStart, fieldPtr);
240
+ return expStart;
241
+ }
242
+ let expNeg = false;
243
+ const sc = load<u16>(p);
244
+ if (sc == ASCII_MINUS) {
245
+ expNeg = true;
246
+ p += 2;
247
+ } else if (sc == ASCII_PLUS) {
248
+ p += 2;
249
+ }
250
+ if (p >= srcEnd) {
251
+ fallbackField<T>(origStart, expStart, fieldPtr);
252
+ return expStart;
253
+ }
254
+ let exp: i32 = 0;
255
+ let expDigits: i32 = 0;
256
+ while (p < srcEnd) {
257
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
258
+ if (d > 9) break;
259
+ exp = exp * 10 + <i32>d;
260
+ expDigits++;
261
+ if (expDigits > 4) {
262
+ fallbackField<T>(origStart, p, fieldPtr);
263
+ return p;
264
+ }
265
+ p += 2;
266
+ }
267
+ if (expDigits == 0) {
268
+ fallbackField<T>(origStart, expStart, fieldPtr);
269
+ return expStart;
270
+ }
271
+ exponent += expNeg ? -exp : exp;
272
+ }
273
+ }
274
+
275
+ let result: f64;
276
+ if (
277
+ mantDigits <= 19 &&
278
+ mantissa <= MAX_EXACT_MANTISSA &&
279
+ exponent <= MAX_EXACT_POW10 &&
280
+ exponent >= -MAX_EXACT_POW10
281
+ ) {
282
+ result = <f64>mantissa;
283
+ if (exponent > 0) {
284
+ result *= loadPow10(<u32>exponent);
285
+ } else if (exponent < 0) {
286
+ result /= loadPow10(<u32>-exponent);
287
+ }
288
+ } else if (mantDigits <= 19) {
289
+ result = scientific(mantissa, exponent);
290
+ } else {
291
+ fallbackField<T>(origStart, p, fieldPtr);
292
+ return p;
293
+ }
294
+ if (negative) result = -result;
295
+
296
+ if (sizeof<T>() == sizeof<f32>()) {
297
+ store<f32>(fieldPtr, <f32>result);
298
+ } else {
299
+ store<f64>(fieldPtr, result);
300
+ }
301
+
302
+ return p;
303
+ }