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
@@ -1,9 +1,24 @@
1
- import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
1
+ import {
2
+ BRACKET_LEFT,
3
+ BRACKET_RIGHT,
4
+ COMMA,
5
+ NULL_WORD_U64,
6
+ } from "../../../custom/chars";
7
+ import { isSpace } from "../../../util";
2
8
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
3
9
  import { deserializeStringField_SWAR } from "../string";
4
10
 
5
11
 
6
- @inline export function deserializeStringArrayInto<T extends string[]>(
12
+ @inline function skipStringArrayWhitespace(
13
+ srcStart: usize,
14
+ srcEnd: usize,
15
+ ): usize {
16
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
17
+ return srcStart;
18
+ }
19
+
20
+
21
+ @inline function deserializeStringArrayBody<T extends string[]>(
7
22
  srcStart: usize,
8
23
  srcEnd: usize,
9
24
  out: T,
@@ -13,6 +28,7 @@ import { deserializeStringField_SWAR } from "../string";
13
28
  do {
14
29
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
15
30
  srcStart += 2;
31
+ srcStart = skipStringArrayWhitespace(srcStart, srcEnd);
16
32
  if (srcStart >= srcEnd) break;
17
33
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
18
34
  out.length = 0;
@@ -21,21 +37,41 @@ import { deserializeStringField_SWAR } from "../string";
21
37
 
22
38
  while (srcStart < srcEnd) {
23
39
  const slot = ensureArrayElementSlot<T>(out, index);
24
- srcStart = deserializeStringField_SWAR<valueof<T>>(
25
- srcStart,
26
- srcEnd,
27
- slot,
28
- );
29
- if (!srcStart || srcStart >= srcEnd) break;
40
+ // Null fast path for `(string | null)[]`. "null" is 4 UTF-16 chars
41
+ // = 8 bytes, exactly one u64 compare. Store 0 (the AS null reference)
42
+ // and skip 8 bytes.
43
+ //
44
+ // We accept null tokens unconditionally rather than gating on
45
+ // `isNullable<valueof<T>>()` AS's `valueof` of a nullable-array
46
+ // element type doesn't always preserve the nullable marker through
47
+ // the dispatcher's `<T>` cast, so the gate would mis-fire for the
48
+ // very case it's meant to handle. The runtime cost on plain
49
+ // `string[]` arrays is one extra u64 compare per element; a
50
+ // well-formed `string[]` input never matches it.
51
+ if (srcStart + 8 <= srcEnd && load<u64>(srcStart) == NULL_WORD_U64) {
52
+ store<usize>(slot, 0);
53
+ srcStart += 8;
54
+ } else {
55
+ srcStart = deserializeStringField_SWAR<valueof<T>>(
56
+ srcStart,
57
+ srcEnd,
58
+ slot,
59
+ );
60
+ }
61
+ if (!srcStart) break;
62
+ srcStart = skipStringArrayWhitespace(srcStart, srcEnd);
63
+ if (srcStart >= srcEnd) break;
30
64
 
31
65
  const code = load<u16>(srcStart);
32
66
  if (code == COMMA) {
33
67
  srcStart += 2;
68
+ srcStart = skipStringArrayWhitespace(srcStart, srcEnd);
34
69
  index++;
35
70
  continue;
36
71
  }
37
72
  if (code == BRACKET_RIGHT) {
38
- out.length = index + 1;
73
+ const nextLen = index + 1;
74
+ if (out.length != nextLen) out.length = nextLen;
39
75
  return srcStart + 2;
40
76
  }
41
77
  break;
@@ -51,9 +87,90 @@ import { deserializeStringField_SWAR } from "../string";
51
87
  srcEnd: usize,
52
88
  fieldPtr: usize,
53
89
  ): usize {
54
- return deserializeStringArrayInto<T>(
90
+ return deserializeStringArrayBody<T>(
55
91
  srcStart,
56
92
  srcEnd,
57
93
  ensureArrayField<T>(fieldPtr),
58
94
  );
59
95
  }
96
+
97
+ // Top-level entry for `JSON.parse<string[]>` / `JSON.parse<(string | null)[]>`.
98
+ //
99
+ // Pre-grows the destination array to a worst-case bound in one allocation,
100
+ // then writes elements via `writePtr` direct stores. This sidesteps the
101
+ // per-element growth cost of `deserializeStringArrayBody`'s
102
+ // `ensureArrayElementSlot` path. The incremental runtime zero-initializes
103
+ // new allocations, so `null` slots need no per-element store on cold
104
+ // arrays (we still write 0 to handle reused arrays whose old refs would
105
+ // otherwise leak through).
106
+ export function deserializeStringArray_SWAR<T extends string[]>(
107
+ srcStart: usize,
108
+ srcEnd: usize,
109
+ dst: usize,
110
+ ): T {
111
+ const out = changetype<nonnull<T>>(
112
+ dst || changetype<usize>(instantiate<T>()),
113
+ );
114
+
115
+ // Worst-case sizing: shortest possible element is `""` followed by `,` =
116
+ // 6 UTF-16 bytes, so `(srcLen + 5) / 6` upper-bounds the count. Clamp to
117
+ // AS's BLOCK_MAXSIZE / sizeof<string ref> = (1<<28) - 4 elements; payloads
118
+ // that exceed this would fail the `out.length =` setter anyway. For a
119
+ // 1 GiB UTF-16 source that maxes at ~256M slot allocation = ~1 GiB.
120
+ const elementSize: usize = sizeof<usize>();
121
+ const maxBlockElements: i32 = i32((<usize>0x40000000 - 16) / elementSize);
122
+ let maxElements: i32 = i32((<usize>(srcEnd - srcStart) + 5) / 6);
123
+ if (maxElements < 0 || maxElements > maxBlockElements) {
124
+ maxElements = maxBlockElements;
125
+ }
126
+ if (out.length < maxElements) out.length = maxElements;
127
+
128
+ const dataStart: usize = out.dataStart;
129
+ let writePtr: usize = dataStart;
130
+ const writePtrLimit: usize = dataStart + <usize>maxElements * elementSize;
131
+
132
+ // Caller guarantees srcStart is at the opening `[`.
133
+ if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) {
134
+ out.length = 0;
135
+ return out;
136
+ }
137
+ srcStart += 2;
138
+ srcStart = skipStringArrayWhitespace(srcStart, srcEnd);
139
+ if (srcStart < srcEnd && load<u16>(srcStart) == BRACKET_RIGHT) {
140
+ out.length = 0;
141
+ return out;
142
+ }
143
+
144
+ while (srcStart < srcEnd && writePtr < writePtrLimit) {
145
+ // Null fast path: one u64 compare.
146
+ if (srcStart + 8 <= srcEnd && load<u64>(srcStart) == NULL_WORD_U64) {
147
+ store<usize>(writePtr, 0);
148
+ srcStart += 8;
149
+ } else {
150
+ srcStart = deserializeStringField_SWAR<valueof<T>>(
151
+ srcStart,
152
+ srcEnd,
153
+ writePtr,
154
+ );
155
+ if (!srcStart) break;
156
+ }
157
+ writePtr += elementSize;
158
+ srcStart = skipStringArrayWhitespace(srcStart, srcEnd);
159
+ if (srcStart >= srcEnd) break;
160
+
161
+ const code = load<u16>(srcStart);
162
+ if (code == COMMA) {
163
+ srcStart += 2;
164
+ srcStart = skipStringArrayWhitespace(srcStart, srcEnd);
165
+ continue;
166
+ }
167
+ if (code == BRACKET_RIGHT) {
168
+ const finalLen = i32(<usize>(writePtr - dataStart) / elementSize);
169
+ if (out.length != finalLen) out.length = finalLen;
170
+ return out;
171
+ }
172
+ break;
173
+ }
174
+
175
+ throw new Error("Failed to parse JSON!");
176
+ }
@@ -1,8 +1,32 @@
1
1
  import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
2
+ import { isSpace } from "../../../util";
2
3
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
3
4
 
4
5
 
5
- @inline export function deserializeStructArrayInto<T extends unknown[]>(
6
+ @inline function skipStructArrayWhitespace(
7
+ srcStart: usize,
8
+ srcEnd: usize,
9
+ ): usize {
10
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
11
+ return srcStart;
12
+ }
13
+
14
+ // Per-element worker for `@json class Foo[]` fields. Each element is a
15
+ // managed reference whose value is produced by the transform-generated
16
+ // `__DESERIALIZE_FAST` / `__DESERIALIZE_SLOW` methods, so the loop pattern
17
+ // is structurally different from primitive-array bodies:
18
+ //
19
+ // - The slot stores a *reference*; reused arrays may already hold an
20
+ // allocated instance whose fields we just overwrite. Only allocate +
21
+ // `__INITIALIZE` when the slot is null.
22
+ // - `__DESERIALIZE_FAST` returns the cursor past the closing `}` on
23
+ // success, or `0` to signal "bail to slow path". We mirror the
24
+ // dispatcher in `JSON.__deserialize` here: try FAST first, fall back
25
+ // to SLOW on `0`.
26
+ // - Whitespace is skipped at each separator boundary so struct-array
27
+ // fields tolerate the same `[ {...} , {...} ]` shape that top-level
28
+ // `JSON.parse` does.
29
+ @inline function deserializeStructArrayBody<T extends unknown[]>(
6
30
  srcStart: usize,
7
31
  srcEnd: usize,
8
32
  out: T,
@@ -12,6 +36,7 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
12
36
  do {
13
37
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
14
38
  srcStart += 2;
39
+ srcStart = skipStructArrayWhitespace(srcStart, srcEnd);
15
40
  if (srcStart >= srcEnd) break;
16
41
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
17
42
  out.length = 0;
@@ -33,31 +58,40 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
33
58
  store<valueof<T>>(slot, value);
34
59
  }
35
60
 
36
- const valueStart = srcStart;
37
- // @ts-ignore: supplied by transform
61
+ let next: usize = 0;
38
62
  if (
63
+ // @ts-ignore: supplied by transform
39
64
  isDefined(changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST)
40
65
  ) {
41
66
  // @ts-ignore: supplied by transform
42
- srcStart = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST<
67
+ next = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST<
43
68
  valueof<T>
44
- >(valueStart, srcEnd, value);
45
- } else {
69
+ >(srcStart, srcEnd, value);
70
+ }
71
+ if (!next) {
46
72
  // @ts-ignore: supplied by transform
47
- srcStart = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_SLOW<
73
+ next = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_SLOW<
48
74
  valueof<T>
49
- >(valueStart, srcEnd, value);
75
+ >(srcStart, srcEnd, value);
50
76
  }
51
- if (!srcStart || srcStart >= srcEnd) break;
77
+ if (!next) break;
78
+ srcStart = next;
79
+ srcStart = skipStructArrayWhitespace(srcStart, srcEnd);
80
+ if (srcStart >= srcEnd) break;
52
81
 
53
82
  const code = load<u16>(srcStart);
54
83
  if (code == COMMA) {
55
84
  srcStart += 2;
85
+ srcStart = skipStructArrayWhitespace(srcStart, srcEnd);
56
86
  index++;
57
87
  continue;
58
88
  }
59
89
  if (code == BRACKET_RIGHT) {
60
- out.length = index + 1;
90
+ // Skip `ensureCapacity` when the reused array already has the
91
+ // right length (e.g. canada-style geometry rings whose count
92
+ // matches a previous parse).
93
+ const nextLen = index + 1;
94
+ if (out.length != nextLen) out.length = nextLen;
61
95
  return srcStart + 2;
62
96
  }
63
97
  break;
@@ -73,7 +107,7 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
73
107
  srcEnd: usize,
74
108
  fieldPtr: usize,
75
109
  ): usize {
76
- return deserializeStructArrayInto<T>(
110
+ return deserializeStructArrayBody<T>(
77
111
  srcStart,
78
112
  srcEnd,
79
113
  ensureArrayField<T>(fieldPtr),
@@ -11,14 +11,8 @@ import { deserializeObjectArrayField } from "./array/object";
11
11
  import { deserializeRawArrayField } from "./array/raw";
12
12
  import { deserializeStringArrayField } from "./array/string";
13
13
  import { deserializeStructArrayField } from "./array/struct";
14
- import { deserializeArrayArrayInto } from "./array/array";
15
- import { deserializeBooleanArrayInto } from "./array/bool";
16
- import { deserializeFloatArrayInto } from "./array/float";
17
- import { deserializeGenericArrayInto } from "./array/generic";
18
- import { deserializeIntegerArrayInto } from "./array/integer";
19
- import { deserializeObjectArrayInto } from "./array/object";
20
- import { deserializeStringArrayInto } from "./array/string";
21
- import { deserializeStructArrayInto } from "./array/struct";
14
+
15
+ export { deserializeArrayField as deserializeArrayField_SWAR };
22
16
 
23
17
 
24
18
  @inline export function deserializeArrayField<T extends unknown[]>(
@@ -69,51 +63,3 @@ import { deserializeStructArrayInto } from "./array/struct";
69
63
  throw new Error("Could not parse array field of type " + nameof<T>() + "!");
70
64
  }
71
65
  }
72
-
73
-
74
- @inline export function deserializeArrayInto_SWAR<T extends unknown[]>(
75
- srcStart: usize,
76
- srcEnd: usize,
77
- out: T,
78
- ): usize {
79
- if (isString<valueof<T>>()) {
80
- return deserializeStringArrayInto<T>(srcStart, srcEnd, out);
81
- } else if (isBoolean<valueof<T>>()) {
82
- return deserializeBooleanArrayInto<T>(srcStart, srcEnd, out);
83
- } else if (isInteger<valueof<T>>()) {
84
- return deserializeIntegerArrayInto<T>(srcStart, srcEnd, out);
85
- } else if (isFloat<valueof<T>>()) {
86
- return deserializeFloatArrayInto<T>(srcStart, srcEnd, out);
87
- } else if (isArray<valueof<T>>()) {
88
- return deserializeArrayArrayInto<T>(srcStart, srcEnd, out);
89
- } else if (isManaged<valueof<T>>() || isReference<valueof<T>>()) {
90
- const type = changetype<nonnull<valueof<T>>>(0);
91
- if (type instanceof JSON.Value) {
92
- return deserializeGenericArrayInto<T>(srcStart, srcEnd, out);
93
- } else if (type instanceof JSON.Box) {
94
- throw new Error("Failed to parse JSON!");
95
- } else if (type instanceof JSON.Obj) {
96
- return deserializeObjectArrayInto<T>(srcStart, srcEnd, out);
97
- } else if (type instanceof JSON.Raw) {
98
- throw new Error("Failed to parse JSON!");
99
- } else if (type instanceof Date) {
100
- return deserializeGenericArrayInto<T>(srcStart, srcEnd, out);
101
- } else if (type instanceof Set) {
102
- return deserializeGenericArrayInto<T>(srcStart, srcEnd, out);
103
- } else if (type instanceof Map) {
104
- throw new Error("Failed to parse JSON!");
105
- // @ts-ignore: defined by transform
106
- } else if (isDefined(type.__DESERIALIZE_CUSTOM)) {
107
- return deserializeStructArrayInto<T>(srcStart, srcEnd, out);
108
- // @ts-ignore: defined by transform
109
- } else if (
110
- isDefined(type.__DESERIALIZE_SLOW) ||
111
- isDefined(type.__DESERIALIZE_FAST)
112
- ) {
113
- return deserializeStructArrayInto<T>(srcStart, srcEnd, out);
114
- }
115
- throw new Error("Could not parse array field of type " + nameof<T>() + "!");
116
- } else {
117
- throw new Error("Could not parse array field of type " + nameof<T>() + "!");
118
- }
119
- }
@@ -0,0 +1,304 @@
1
+ // SWAR float deserializers. Lemire-style fast path with parse4 SWAR digit
2
+ // folding on the fractional accumulator. Output is bit-identical to
3
+ // `f64.parse` / `f32.parse` (the NAIVE baseline) on the fast path because:
4
+ //
5
+ // - the u64 mantissa accumulator is exact (no rounding in the digit loop),
6
+ // - `1e0 .. 1e22` are exactly representable in f64, and
7
+ // - a single fmul/fdiv on two exact operands is correctly rounded.
8
+ //
9
+ // Pathological inputs (>19 mantissa digits, mantissa > 2^53, or |exp| > 22)
10
+ // fall through to `f64.parse` / `f32.parse` over the float's own range so the
11
+ // SWAR result matches the NAIVE result for every input.
12
+ //
13
+ // Inspired by Daniel Lemire, "Number parsing at a gigabyte per second" (2021)
14
+ // and the simdjson `fast_float` implementation. The integer-part loop stays
15
+ // scalar — most JSON float payloads have 1-3 digit integer parts, so a parse4
16
+ // stride there pays the wasted-validate cost on every call without saving
17
+ // enough scalar iterations.
18
+
19
+ import { ptrToStr } from "../../util/ptrToStr";
20
+ import { parse4Digits_PairMul } from "../../util/swar-int";
21
+ import { scientific } from "../../util/scientific";
22
+
23
+ export const POW10_F64_POS: usize = memory.data<f64>([
24
+ 1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14,
25
+ 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
26
+ ]);
27
+ export const MAX_EXACT_POW10: i32 = 22;
28
+ // 2^53. Any u64 mantissa <= this is exact in f64.
29
+ export const MAX_EXACT_MANTISSA: u64 = 1 << 53;
30
+
31
+ const ASCII_PLUS: u16 = 43;
32
+ const ASCII_MINUS: u16 = 45;
33
+ const ASCII_DOT: u16 = 46;
34
+ const ASCII_ZERO: u16 = 48;
35
+ const ASCII_E_UP: u16 = 69;
36
+ const ASCII_E_LO: u16 = 101;
37
+
38
+ // @ts-ignore: inline
39
+ @inline export function loadPow10(exp: u32): f64 {
40
+ return load<f64>(POW10_F64_POS + ((<usize>exp) << 3));
41
+ }
42
+
43
+ // @ts-ignore: inline
44
+ @inline function fallback<T>(srcStart: usize, srcEnd: usize): T {
45
+ const s = ptrToStr(srcStart, srcEnd);
46
+ // @ts-ignore
47
+ const type: T = 0;
48
+ // @ts-ignore
49
+ if (type instanceof f64) return <T>f64.parse(s);
50
+ // @ts-ignore
51
+ return <T>(<f32>f32.parse(s));
52
+ }
53
+
54
+ // @ts-ignore: inline
55
+ @inline function fallbackField<T extends number>(
56
+ origStart: usize,
57
+ end: usize,
58
+ fieldPtr: usize,
59
+ ): void {
60
+ const s = ptrToStr(origStart, end);
61
+ if (sizeof<T>() == sizeof<f32>()) {
62
+ store<f32>(fieldPtr, f32.parse(s));
63
+ } else {
64
+ store<f64>(fieldPtr, f64.parse(s));
65
+ }
66
+ }
67
+
68
+ // @ts-ignore: inline
69
+ @inline export function deserializeFloat_SWAR<T>(
70
+ srcStart: usize,
71
+ srcEnd: usize,
72
+ ): T {
73
+ const origStart = srcStart;
74
+ let p = srcStart;
75
+ let negative = false;
76
+ if (p < srcEnd && load<u16>(p) == ASCII_MINUS) {
77
+ negative = true;
78
+ p += 2;
79
+ }
80
+
81
+ // Integer part: scalar. Most JSON integers are 1-3 digits, so parse4 would
82
+ // waste a validate per call.
83
+ let mantissa: u64 = 0;
84
+ let intDigits: i32 = 0;
85
+ while (p < srcEnd) {
86
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
87
+ if (d > 9) break;
88
+ mantissa = mantissa * 10 + <u64>d;
89
+ intDigits++;
90
+ p += 2;
91
+ }
92
+
93
+ // Fractional part: parse4 stride (8 bytes / 4 digits) → scalar tail.
94
+ // parse8 was tried and benchmarked even/worse; the saved mantissa mul
95
+ // didn't outweigh the extra load and combined-validation latency, and the
96
+ // dependency chain is the same length either way.
97
+ let fracDigits: i32 = 0;
98
+ if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
99
+ p += 2;
100
+ while (p + 6 < srcEnd) {
101
+ const parsed = inline.always(parse4Digits_PairMul(load<u64>(p)));
102
+ if (parsed == U32.MAX_VALUE) break;
103
+ mantissa = mantissa * 10_000 + <u64>parsed;
104
+ fracDigits += 4;
105
+ p += 8;
106
+ }
107
+ while (p < srcEnd) {
108
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
109
+ if (d > 9) break;
110
+ mantissa = mantissa * 10 + <u64>d;
111
+ fracDigits++;
112
+ p += 2;
113
+ }
114
+ }
115
+
116
+ const mantDigits = intDigits + fracDigits;
117
+ if (mantDigits == 0) return fallback<T>(origStart, srcEnd);
118
+
119
+ let exponent: i32 = -fracDigits;
120
+
121
+ // Optional `e[+-]NNN` suffix.
122
+ if (p < srcEnd) {
123
+ const c = load<u16>(p);
124
+ if (c == ASCII_E_LO || c == ASCII_E_UP) {
125
+ const expStart = p;
126
+ p += 2;
127
+ if (p >= srcEnd) return fallback<T>(origStart, expStart);
128
+ let expNeg = false;
129
+ const sc = load<u16>(p);
130
+ if (sc == ASCII_MINUS) {
131
+ expNeg = true;
132
+ p += 2;
133
+ } else if (sc == ASCII_PLUS) {
134
+ p += 2;
135
+ }
136
+ if (p >= srcEnd) return fallback<T>(origStart, expStart);
137
+ let exp: i32 = 0;
138
+ let expDigits: i32 = 0;
139
+ while (p < srcEnd) {
140
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
141
+ if (d > 9) break;
142
+ exp = exp * 10 + <i32>d;
143
+ expDigits++;
144
+ if (expDigits > 4) return fallback<T>(origStart, srcEnd);
145
+ p += 2;
146
+ }
147
+ if (expDigits == 0) return fallback<T>(origStart, expStart);
148
+ exponent += expNeg ? -exp : exp;
149
+ }
150
+ }
151
+
152
+ // Lemire fast path when fully in range; `scientific` for u64-fitting
153
+ // mantissas that exceed 2^53 or |exp| > 22 (still correctly rounded, but
154
+ // via the scaledown/scaleup path); `f*.parse` fallback only for >19
155
+ // mantissa digits where the sticky-bit handling matters.
156
+ let result: f64;
157
+ if (
158
+ mantDigits <= 19 &&
159
+ mantissa <= MAX_EXACT_MANTISSA &&
160
+ exponent <= MAX_EXACT_POW10 &&
161
+ exponent >= -MAX_EXACT_POW10
162
+ ) {
163
+ result = <f64>mantissa;
164
+ if (exponent > 0) {
165
+ result *= loadPow10(<u32>exponent);
166
+ } else if (exponent < 0) {
167
+ result /= loadPow10(<u32>-exponent);
168
+ }
169
+ } else if (mantDigits <= 19) {
170
+ result = scientific(mantissa, exponent);
171
+ } else {
172
+ return fallback<T>(origStart, srcEnd);
173
+ }
174
+ if (negative) result = -result;
175
+
176
+ // @ts-ignore
177
+ const type: T = 0;
178
+ // @ts-ignore
179
+ if (type instanceof f64) return <T>result;
180
+ // @ts-ignore
181
+ return <T>(<f32>result);
182
+ }
183
+
184
+ // @ts-ignore: inline
185
+ @inline export function deserializeFloatField_SWAR<T extends number>(
186
+ srcStart: usize,
187
+ srcEnd: usize,
188
+ dstObj: usize,
189
+ dstOffset: usize = 0,
190
+ ): usize {
191
+ const fieldPtr = dstObj + dstOffset;
192
+ const origStart = srcStart;
193
+ let p = srcStart;
194
+ let negative = false;
195
+ if (p < srcEnd && load<u16>(p) == ASCII_MINUS) {
196
+ negative = true;
197
+ p += 2;
198
+ }
199
+
200
+ let mantissa: u64 = 0;
201
+ let intDigits: i32 = 0;
202
+ while (p < srcEnd) {
203
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
204
+ if (d > 9) break;
205
+ mantissa = mantissa * 10 + <u64>d;
206
+ intDigits++;
207
+ p += 2;
208
+ }
209
+
210
+ let fracDigits: i32 = 0;
211
+ if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
212
+ p += 2;
213
+ while (p + 6 < srcEnd) {
214
+ const parsed = parse4Digits_PairMul(load<u64>(p));
215
+ if (parsed == U32.MAX_VALUE) break;
216
+ mantissa = mantissa * 10_000 + <u64>parsed;
217
+ fracDigits += 4;
218
+ p += 8;
219
+ }
220
+ while (p < srcEnd) {
221
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
222
+ if (d > 9) break;
223
+ mantissa = mantissa * 10 + <u64>d;
224
+ fracDigits++;
225
+ p += 2;
226
+ }
227
+ }
228
+
229
+ const mantDigits = intDigits + fracDigits;
230
+ if (mantDigits == 0) unreachable();
231
+
232
+ let exponent: i32 = -fracDigits;
233
+
234
+ if (p < srcEnd) {
235
+ const c = load<u16>(p);
236
+ if (c == ASCII_E_LO || c == ASCII_E_UP) {
237
+ const expStart = p;
238
+ p += 2;
239
+ if (p >= srcEnd) {
240
+ fallbackField<T>(origStart, expStart, fieldPtr);
241
+ return expStart;
242
+ }
243
+ let expNeg = false;
244
+ const sc = load<u16>(p);
245
+ if (sc == ASCII_MINUS) {
246
+ expNeg = true;
247
+ p += 2;
248
+ } else if (sc == ASCII_PLUS) {
249
+ p += 2;
250
+ }
251
+ if (p >= srcEnd) {
252
+ fallbackField<T>(origStart, expStart, fieldPtr);
253
+ return expStart;
254
+ }
255
+ let exp: i32 = 0;
256
+ let expDigits: i32 = 0;
257
+ while (p < srcEnd) {
258
+ const d = <u32>load<u16>(p) - ASCII_ZERO;
259
+ if (d > 9) break;
260
+ exp = exp * 10 + <i32>d;
261
+ expDigits++;
262
+ if (expDigits > 4) {
263
+ fallbackField<T>(origStart, p, fieldPtr);
264
+ return p;
265
+ }
266
+ p += 2;
267
+ }
268
+ if (expDigits == 0) {
269
+ fallbackField<T>(origStart, expStart, fieldPtr);
270
+ return expStart;
271
+ }
272
+ exponent += expNeg ? -exp : exp;
273
+ }
274
+ }
275
+
276
+ let result: f64;
277
+ if (
278
+ mantDigits <= 19 &&
279
+ mantissa <= MAX_EXACT_MANTISSA &&
280
+ exponent <= MAX_EXACT_POW10 &&
281
+ exponent >= -MAX_EXACT_POW10
282
+ ) {
283
+ result = <f64>mantissa;
284
+ if (exponent > 0) {
285
+ result *= loadPow10(<u32>exponent);
286
+ } else if (exponent < 0) {
287
+ result /= loadPow10(<u32>-exponent);
288
+ }
289
+ } else if (mantDigits <= 19) {
290
+ result = scientific(mantissa, exponent);
291
+ } else {
292
+ fallbackField<T>(origStart, p, fieldPtr);
293
+ return p;
294
+ }
295
+ if (negative) result = -result;
296
+
297
+ if (sizeof<T>() == sizeof<f32>()) {
298
+ store<f32>(fieldPtr, <f32>result);
299
+ } else {
300
+ store<f64>(fieldPtr, result);
301
+ }
302
+
303
+ return p;
304
+ }