json-as 1.4.0 → 1.5.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.
- package/CHANGELOG.md +50 -29
- package/README.md +84 -33
- package/assembly/custom/chars.ts +39 -78
- package/assembly/deserialize/index/arbitrary.ts +26 -8
- package/assembly/deserialize/index/float.ts +2 -4
- package/assembly/deserialize/index/integer.ts +2 -4
- package/assembly/deserialize/index/object.ts +6 -1
- package/assembly/deserialize/index/string.ts +2 -7
- package/assembly/deserialize/index/unsigned.ts +2 -4
- package/assembly/deserialize/naive/array/integer.ts +1 -1
- package/assembly/deserialize/naive/array/map.ts +1 -1
- package/assembly/deserialize/naive/array/object.ts +1 -1
- package/assembly/deserialize/naive/array/struct.ts +19 -1
- package/assembly/deserialize/naive/bool.ts +1 -5
- package/assembly/deserialize/naive/date.ts +1 -2
- package/assembly/deserialize/naive/float.ts +2 -7
- package/assembly/deserialize/naive/integer.ts +1 -2
- package/assembly/deserialize/naive/map.ts +5 -6
- package/assembly/deserialize/naive/object.ts +151 -13
- package/assembly/deserialize/naive/raw.ts +1 -5
- package/assembly/deserialize/naive/set.ts +2 -4
- package/assembly/deserialize/naive/staticarray.ts +1 -2
- package/assembly/deserialize/naive/string.ts +6 -9
- package/assembly/deserialize/naive/unsigned.ts +1 -2
- package/assembly/deserialize/simd/array/integer.ts +2 -7
- package/assembly/deserialize/simd/float.ts +3 -5
- package/assembly/deserialize/simd/integer.ts +2 -7
- package/assembly/deserialize/simd/string.ts +5 -22
- package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
- package/assembly/deserialize/swar/array/array.ts +1 -2
- package/assembly/deserialize/swar/array/bool.ts +1 -2
- package/assembly/deserialize/swar/array/box.ts +1 -2
- package/assembly/deserialize/swar/array/float.ts +6 -18
- package/assembly/deserialize/swar/array/generic.ts +1 -2
- package/assembly/deserialize/swar/array/integer.ts +7 -16
- package/assembly/deserialize/swar/array/map.ts +1 -2
- package/assembly/deserialize/swar/array/object.ts +1 -2
- package/assembly/deserialize/swar/array/raw.ts +1 -2
- package/assembly/deserialize/swar/array/shared.ts +6 -13
- package/assembly/deserialize/swar/array/string.ts +3 -8
- package/assembly/deserialize/swar/array/struct.ts +2 -8
- package/assembly/deserialize/swar/array.ts +1 -3
- package/assembly/deserialize/swar/float.ts +4 -9
- package/assembly/deserialize/swar/integer.ts +2 -7
- package/assembly/deserialize/swar/string.ts +13 -15
- package/assembly/deserialize/swar/typedarray.ts +4 -4
- package/assembly/index.d.ts +29 -24
- package/assembly/index.ts +1362 -246
- package/assembly/serialize/index/arbitrary.ts +70 -4
- package/assembly/serialize/index/jsonarray.ts +51 -0
- package/assembly/serialize/index/object.ts +25 -3
- package/assembly/serialize/index/string.ts +1 -2
- package/assembly/serialize/index/typedarray.ts +1 -2
- package/assembly/serialize/index.ts +1 -0
- package/assembly/serialize/naive/array.ts +23 -34
- package/assembly/serialize/naive/bool.ts +0 -1
- package/assembly/serialize/naive/float.ts +16 -25
- package/assembly/serialize/naive/integer.ts +1 -5
- package/assembly/serialize/naive/raw.ts +1 -2
- package/assembly/serialize/naive/set.ts +0 -4
- package/assembly/serialize/naive/staticarray.ts +0 -5
- package/assembly/serialize/naive/string.ts +2 -5
- package/assembly/serialize/naive/typedarray.ts +0 -6
- package/assembly/serialize/simd/string.ts +1 -3
- package/assembly/serialize/swar/string.ts +1 -2
- package/assembly/util/atoi-fast.ts +4 -14
- package/assembly/util/bytes.ts +1 -2
- package/assembly/util/idofd.ts +1 -2
- package/assembly/util/isSpace.ts +1 -2
- package/assembly/util/itoa-fast.ts +6 -9
- package/assembly/util/nextPowerOf2.ts +1 -2
- package/assembly/util/parsefloat-fast.ts +3 -5
- package/assembly/util/ptrToStr.ts +1 -2
- package/assembly/util/scanValueEndSimd.ts +54 -16
- package/assembly/util/scanValueEndSwar.ts +67 -25
- package/assembly/util/scientific.ts +5 -8
- package/assembly/util/snp.ts +1 -2
- package/assembly/util/swar-int.ts +5 -10
- package/assembly/util/swar.ts +2 -4
- package/lib/as-bs.ts +23 -45
- package/package.json +14 -7
- package/transform/lib/index.js +108 -64
- package/transform/lib/types.d.ts +2 -1
- package/transform/lib/types.js +3 -0
- package/assembly/util/dragonbox-cache.ts +0 -445
- package/assembly/util/dragonbox.ts +0 -652
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// `parse8Digits_SIMD` (16 bytes / 8 digits) before falling through to
|
|
4
4
|
// `parse4Digits_PairMul` (8 bytes / 4 digits) and finally the scalar tail.
|
|
5
5
|
//
|
|
6
|
-
// Output is bit-identical to `f64.parse` / `f32.parse` for every input
|
|
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
|
//
|
|
@@ -23,8 +23,7 @@ const ASCII_ZERO: u16 = 48;
|
|
|
23
23
|
const ASCII_E_UP: u16 = 69;
|
|
24
24
|
const ASCII_E_LO: u16 = 101;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
@inline function fallback<T>(srcStart: usize, srcEnd: usize): T {
|
|
26
|
+
function fallback<T>(srcStart: usize, srcEnd: usize): T {
|
|
28
27
|
const s = ptrToStr(srcStart, srcEnd);
|
|
29
28
|
// @ts-ignore
|
|
30
29
|
const type: T = 0;
|
|
@@ -34,8 +33,7 @@ const ASCII_E_LO: u16 = 101;
|
|
|
34
33
|
return <T>(<f32>f32.parse(s));
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
@inline function fallbackField<T extends number>(
|
|
36
|
+
function fallbackField<T extends number>(
|
|
39
37
|
origStart: usize,
|
|
40
38
|
end: usize,
|
|
41
39
|
fieldPtr: usize,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
65
|
-
@inline function storeUnsignedToField<T extends number>(
|
|
60
|
+
function storeUnsignedToField<T extends number>(
|
|
66
61
|
dstPtr: usize,
|
|
67
62
|
value: u64,
|
|
68
63
|
): void {
|
|
@@ -63,13 +63,8 @@ import { hex4_to_u16_swar } from "../../util/swar";
|
|
|
63
63
|
* @param dst buffer to write to
|
|
64
64
|
* @returns number of bytes written
|
|
65
65
|
*/
|
|
66
|
-
|
|
67
|
-
@inline function copyStringFromSource_SIMD(
|
|
68
|
-
srcStart: usize,
|
|
69
|
-
byteLength: usize,
|
|
70
|
-
): string {
|
|
66
|
+
function copyStringFromSource_SIMD(srcStart: usize, byteLength: usize): string {
|
|
71
67
|
if (byteLength == 0) return changetype<string>("");
|
|
72
|
-
// @ts-expect-error: __new is a runtime builtin
|
|
73
68
|
const out = __new(byteLength, idof<string>());
|
|
74
69
|
memory.copy(out, srcStart, byteLength);
|
|
75
70
|
return changetype<string>(out);
|
|
@@ -106,11 +101,7 @@ function writeStringToField_SIMD(
|
|
|
106
101
|
// handling (no closing-quote search). Same HYBRID strategy as the field path
|
|
107
102
|
// (see deserializeEscapedStringField_SIMD): escape blocks use a free
|
|
108
103
|
// whole-block v128 store for the plain prefix; clean runs stream the first
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
// Deliberately NOT @inline: cold escape path. Inlining the nested-loop v128
|
|
112
|
-
// body at every call site explodes `--converge` compile time on large schemas
|
|
113
|
-
// for no runtime gain (one call per escaped string).
|
|
104
|
+
|
|
114
105
|
function deserializeEscapedString_SIMD(
|
|
115
106
|
payloadStart: usize,
|
|
116
107
|
escapeStart: usize,
|
|
@@ -254,18 +245,14 @@ export function deserializeString_SIMD(srcStart: usize, srcEnd: usize): string {
|
|
|
254
245
|
// reused `bs` scratch buffer, then written once via writeStringToField_SIMD.
|
|
255
246
|
//
|
|
256
247
|
// Strategy (validated against run-copy and pure-stream variants across escape
|
|
257
|
-
// densities
|
|
248
|
+
// densities - see __benches__/custom/simd-string-deser-variants-h2h):
|
|
258
249
|
// * Escape-bearing block: a single whole-block v128 store copies the plain
|
|
259
250
|
// prefix for free; then the escape is decoded scalar.
|
|
260
251
|
// * Clean block: stream the first one cheaply, but if the clean run keeps
|
|
261
|
-
// going, switch to one bulk memory.copy for the remainder
|
|
252
|
+
// going, switch to one bulk memory.copy for the remainder - bandwidth-
|
|
262
253
|
// optimal on long sparse runs, avoiding a per-block-store cliff on large
|
|
263
254
|
// inputs. This dominates both alternatives: stream-cheap on dense escapes,
|
|
264
|
-
|
|
265
|
-
//
|
|
266
|
-
// Deliberately NOT @inline: cold escape path. As an @inline it was inlined into
|
|
267
|
-
// deserializeStringField_SIMD at every struct string-field site, exploding
|
|
268
|
-
// `--converge` compile time ~24x on large schemas (4s → 99s) for no runtime
|
|
255
|
+
|
|
269
256
|
// gain. The hot no-escape scan + writeStringToField stay inline in the caller.
|
|
270
257
|
function deserializeEscapedStringField_SIMD(
|
|
271
258
|
payloadStart: usize,
|
|
@@ -386,10 +373,6 @@ function deserializeEscapedStringField_SIMD(
|
|
|
386
373
|
return srcStart;
|
|
387
374
|
}
|
|
388
375
|
|
|
389
|
-
// NOT @inline: as an @inline entry, binaryen inlined the (loop-bearing) escaped
|
|
390
|
-
// scanner into every per-field copy, exploding `large` SIMD compile ~24x
|
|
391
|
-
// (4s→99s, 221KB→555KB wasm). Kept as a single shared function — matches
|
|
392
|
-
// deserializeStringField_SWAR (also non-inline) and costs only one call/field.
|
|
393
376
|
export function deserializeStringField_SIMD<T extends string | null>(
|
|
394
377
|
srcStart: usize,
|
|
395
378
|
srcEnd: usize,
|
|
@@ -2,8 +2,7 @@ import { JSON } from "../../..";
|
|
|
2
2
|
import { deserializeGenericArrayBody } from "./generic";
|
|
3
3
|
import { ensureArrayField } from "./shared";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
@inline export function deserializeArbitraryArrayField(
|
|
5
|
+
export function deserializeArbitraryArrayField(
|
|
7
6
|
srcStart: usize,
|
|
8
7
|
srcEnd: usize,
|
|
9
8
|
fieldPtr: usize,
|
|
@@ -90,8 +90,7 @@ export function deserializeArrayArrayBody<T extends unknown[][]>(
|
|
|
90
90
|
throw new Error("Failed to parse JSON!");
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
@inline export function deserializeArrayArrayField<T extends unknown[][]>(
|
|
93
|
+
export function deserializeArrayArrayField<T extends unknown[][]>(
|
|
95
94
|
srcStart: usize,
|
|
96
95
|
srcEnd: usize,
|
|
97
96
|
fieldPtr: usize,
|
|
@@ -59,8 +59,7 @@ function deserializeBooleanArrayBody<T extends boolean[]>(
|
|
|
59
59
|
throw new Error("Failed to parse JSON!");
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
@inline export function deserializeBooleanArrayField<T extends boolean[]>(
|
|
62
|
+
export function deserializeBooleanArrayField<T extends boolean[]>(
|
|
64
63
|
srcStart: usize,
|
|
65
64
|
srcEnd: usize,
|
|
66
65
|
fieldPtr: usize,
|
|
@@ -2,8 +2,7 @@ import { JSON } from "../../..";
|
|
|
2
2
|
import { deserializeGenericArrayBody } from "./generic";
|
|
3
3
|
import { ensureArrayField } from "./shared";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
@inline export function deserializeBoxArrayField<T extends JSON.Box<any>[]>(
|
|
5
|
+
export function deserializeBoxArrayField<T extends JSON.Box<any>[]>(
|
|
7
6
|
srcStart: usize,
|
|
8
7
|
srcEnd: usize,
|
|
9
8
|
fieldPtr: usize,
|
|
@@ -8,21 +8,11 @@ import { parse4Digits_PairMul } from "../../../util/swar-int";
|
|
|
8
8
|
import { loadPow10, MAX_EXACT_MANTISSA, MAX_EXACT_POW10 } from "../float";
|
|
9
9
|
import { isSpace } from "../../../util";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
@inline function skipFloatArrayWhitespace(
|
|
13
|
-
srcStart: usize,
|
|
14
|
-
srcEnd: usize,
|
|
15
|
-
): usize {
|
|
11
|
+
function skipFloatArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
|
|
16
12
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
17
13
|
return srcStart;
|
|
18
14
|
}
|
|
19
|
-
|
|
20
|
-
// @ts-ignore: inline
|
|
21
|
-
@inline function fallbackStore<E>(
|
|
22
|
-
origStart: usize,
|
|
23
|
-
end: usize,
|
|
24
|
-
slot: usize,
|
|
25
|
-
): void {
|
|
15
|
+
function fallbackStore<E>(origStart: usize, end: usize, slot: usize): void {
|
|
26
16
|
const s = ptrToStr(origStart, end);
|
|
27
17
|
if (sizeof<E>() == sizeof<f32>()) {
|
|
28
18
|
store<f32>(slot, f32.parse(s));
|
|
@@ -77,7 +67,7 @@ export function parseFloatElementSWAR<E>(
|
|
|
77
67
|
}
|
|
78
68
|
|
|
79
69
|
// Fractional mantissa: parse4 SWAR stride + scalar tail. Same u64
|
|
80
|
-
// accumulator as the integer part
|
|
70
|
+
// accumulator as the integer part - exponent compensates for fracDigits.
|
|
81
71
|
let fracDigits: i32 = 0;
|
|
82
72
|
if (p < srcEnd && load<u16>(p) == 46) {
|
|
83
73
|
p += 2;
|
|
@@ -164,7 +154,7 @@ export function parseFloatElementSWAR<E>(
|
|
|
164
154
|
// the `ptrToStr` allocation + strtod re-parse.
|
|
165
155
|
result = scientific(mantissa, exponent);
|
|
166
156
|
} else {
|
|
167
|
-
// >19 mantissa digits
|
|
157
|
+
// >19 mantissa digits - beyond u64 capacity, may need strtod's sticky-bit
|
|
168
158
|
// pattern. Hand off to f*.parse on the float's substring.
|
|
169
159
|
fallbackStore<E>(origStart, p, slot);
|
|
170
160
|
return p;
|
|
@@ -255,7 +245,7 @@ export function deserializeFloatArray_SWAR<T extends number[]>(
|
|
|
255
245
|
}
|
|
256
246
|
|
|
257
247
|
/**
|
|
258
|
-
* Field/into variant
|
|
248
|
+
* Field/into variant - parses `[..]` into the existing `out` array and
|
|
259
249
|
* returns the cursor past the closing `]`.
|
|
260
250
|
*
|
|
261
251
|
* Worst-case pre-sizing (`(srcEnd - srcStart) >> 2`) used by the top-level
|
|
@@ -315,9 +305,7 @@ export function deserializeFloatArrayBody<T extends number[]>(
|
|
|
315
305
|
|
|
316
306
|
throw new Error("Failed to parse JSON!");
|
|
317
307
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
@inline export function deserializeFloatArrayField<T extends number[]>(
|
|
308
|
+
export function deserializeFloatArrayField<T extends number[]>(
|
|
321
309
|
srcStart: usize,
|
|
322
310
|
srcEnd: usize,
|
|
323
311
|
fieldPtr: usize,
|
|
@@ -47,8 +47,7 @@ export function deserializeGenericArrayBody<T extends unknown[]>(
|
|
|
47
47
|
throw new Error("Failed to parse JSON!");
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
@inline export function deserializeGenericArrayField<T extends unknown[]>(
|
|
50
|
+
export function deserializeGenericArrayField<T extends unknown[]>(
|
|
52
51
|
srcStart: usize,
|
|
53
52
|
srcEnd: usize,
|
|
54
53
|
fieldPtr: usize,
|
|
@@ -7,12 +7,9 @@ import { parse4Digits_PairMul } from "../../../util/swar-int";
|
|
|
7
7
|
// Store helpers parameterised on the element type `E` directly, so they
|
|
8
8
|
// serve both `Array<E>` and `TypedArray<E>` callers. The integer-array
|
|
9
9
|
// callers below all pass `valueof<T>` and AS folds the resulting tower of
|
|
10
|
-
// `sizeof<E>` comparisons at compile time
|
|
10
|
+
// `sizeof<E>` comparisons at compile time - same codegen as the prior
|
|
11
11
|
// `T extends number[]` version, but reusable from `swar/typedarray.ts`.
|
|
12
|
-
|
|
13
|
-
slot: usize,
|
|
14
|
-
value: i64,
|
|
15
|
-
): void {
|
|
12
|
+
function storeSignedIntegerE<E extends number>(slot: usize, value: i64): void {
|
|
16
13
|
if (sizeof<E>() == sizeof<i8>()) {
|
|
17
14
|
store<i8>(slot, <i8>value);
|
|
18
15
|
} else if (sizeof<E>() == sizeof<i16>()) {
|
|
@@ -26,8 +23,7 @@ import { parse4Digits_PairMul } from "../../../util/swar-int";
|
|
|
26
23
|
}
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
@inline function storeUnsignedIntegerE<E extends number>(
|
|
26
|
+
function storeUnsignedIntegerE<E extends number>(
|
|
31
27
|
slot: usize,
|
|
32
28
|
value: u64,
|
|
33
29
|
): void {
|
|
@@ -211,11 +207,7 @@ export function parseUnsignedIntegerSWAR<E extends number>(
|
|
|
211
207
|
return srcStart;
|
|
212
208
|
}
|
|
213
209
|
|
|
214
|
-
|
|
215
|
-
@inline function skipIntegerArrayWhitespace(
|
|
216
|
-
srcStart: usize,
|
|
217
|
-
srcEnd: usize,
|
|
218
|
-
): usize {
|
|
210
|
+
function skipIntegerArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
|
|
219
211
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) {
|
|
220
212
|
srcStart += 2;
|
|
221
213
|
}
|
|
@@ -661,7 +653,7 @@ function deserializeIntegerArrayBody<T extends number[]>(
|
|
|
661
653
|
const slot = ensureArrayElementSlot<T>(out, index);
|
|
662
654
|
// Inline the array-optimized SWAR parser directly. The top-level
|
|
663
655
|
// (`deserializeIntegerArrayImpl`) and the typed-array path
|
|
664
|
-
// (`swar/typedarray.ts`) already call these
|
|
656
|
+
// (`swar/typedarray.ts`) already call these - having the field path
|
|
665
657
|
// call them too means the per-element parser is identical across all
|
|
666
658
|
// three call sites (parse4 + scalar fold for both signed and
|
|
667
659
|
// unsigned, narrow-lane special case for i8/u8/i16/u16).
|
|
@@ -669,7 +661,7 @@ function deserializeIntegerArrayBody<T extends number[]>(
|
|
|
669
661
|
// Why not `deserializeUnsignedField_SWAR` (which uses parse8 + scalar)?
|
|
670
662
|
// That tuning targets the struct-single-field path where the digit
|
|
671
663
|
// run is one aligned token. In an array, mixed element widths cause
|
|
672
|
-
// parse8 to fail-and-retry at element boundaries
|
|
664
|
+
// parse8 to fail-and-retry at element boundaries - see u32-64mib's
|
|
673
665
|
// 23% regression when parse8 was tried in the array path.
|
|
674
666
|
srcStart = isSigned<valueof<T>>()
|
|
675
667
|
? parseSignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, slot)
|
|
@@ -697,8 +689,7 @@ function deserializeIntegerArrayBody<T extends number[]>(
|
|
|
697
689
|
throw new Error("Failed to parse JSON!");
|
|
698
690
|
}
|
|
699
691
|
|
|
700
|
-
|
|
701
|
-
@inline export function deserializeIntegerArrayField<T extends number[]>(
|
|
692
|
+
export function deserializeIntegerArrayField<T extends number[]>(
|
|
702
693
|
srcStart: usize,
|
|
703
694
|
srcEnd: usize,
|
|
704
695
|
fieldPtr: usize,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { deserializeGenericArrayBody } from "./generic";
|
|
2
2
|
import { ensureArrayField } from "./shared";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
@inline export function deserializeMapArrayField<T extends Map<any, any>[]>(
|
|
4
|
+
export function deserializeMapArrayField<T extends Map<any, any>[]>(
|
|
6
5
|
srcStart: usize,
|
|
7
6
|
srcEnd: usize,
|
|
8
7
|
fieldPtr: usize,
|
|
@@ -58,8 +58,7 @@ function deserializeObjectArrayBody<T extends unknown[]>(
|
|
|
58
58
|
throw new Error("Failed to parse JSON!");
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
@inline export function deserializeObjectArrayField<T extends unknown[]>(
|
|
61
|
+
export function deserializeObjectArrayField<T extends unknown[]>(
|
|
63
62
|
srcStart: usize,
|
|
64
63
|
srcEnd: usize,
|
|
65
64
|
fieldPtr: usize,
|
|
@@ -2,8 +2,7 @@ import { JSON } from "../../..";
|
|
|
2
2
|
import { deserializeGenericArrayBody } from "./generic";
|
|
3
3
|
import { ensureArrayField } from "./shared";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
@inline export function deserializeRawArrayField(
|
|
5
|
+
export function deserializeRawArrayField(
|
|
7
6
|
srcStart: usize,
|
|
8
7
|
srcEnd: usize,
|
|
9
8
|
fieldPtr: usize,
|
|
@@ -10,16 +10,12 @@ import {
|
|
|
10
10
|
import { isSpace } from "../../../util";
|
|
11
11
|
|
|
12
12
|
/** Advance past JSON whitespace (space, tab, LF, CR). */
|
|
13
|
-
|
|
14
|
-
@inline export function skipWhitespace(srcStart: usize, srcEnd: usize): usize {
|
|
13
|
+
export function skipWhitespace(srcStart: usize, srcEnd: usize): usize {
|
|
15
14
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
16
15
|
return srcStart;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
@inline export function ensureArrayField<T extends Array<any>>(
|
|
21
|
-
fieldPtr: usize,
|
|
22
|
-
): T {
|
|
18
|
+
export function ensureArrayField<T extends Array<any>>(fieldPtr: usize): T {
|
|
23
19
|
let out = load<T>(fieldPtr);
|
|
24
20
|
if (!changetype<usize>(out)) {
|
|
25
21
|
out = changetype<T>(instantiate<T>());
|
|
@@ -28,8 +24,7 @@ import { isSpace } from "../../../util";
|
|
|
28
24
|
return out;
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
@inline export function ensureArrayFieldAt<T extends Array<any>>(
|
|
27
|
+
export function ensureArrayFieldAt<T extends Array<any>>(
|
|
33
28
|
dstObj: usize,
|
|
34
29
|
dstOffset: usize,
|
|
35
30
|
): T {
|
|
@@ -41,8 +36,7 @@ import { isSpace } from "../../../util";
|
|
|
41
36
|
return out;
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
@inline function backslashOrQuoteMask(block: u64): u64 {
|
|
39
|
+
function backslashOrQuoteMask(block: u64): u64 {
|
|
46
40
|
const b = block ^ 0x005c_005c_005c_005c;
|
|
47
41
|
const q = block ^ 0x0022_0022_0022_0022;
|
|
48
42
|
return (
|
|
@@ -51,8 +45,7 @@ import { isSpace } from "../../../util";
|
|
|
51
45
|
);
|
|
52
46
|
}
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
@inline export function ensureArrayElementSlot<T extends Array<any>>(
|
|
48
|
+
export function ensureArrayElementSlot<T extends Array<any>>(
|
|
56
49
|
out: T,
|
|
57
50
|
index: i32,
|
|
58
51
|
): usize {
|
|
@@ -60,7 +53,7 @@ import { isSpace } from "../../../util";
|
|
|
60
53
|
if (out.length < nextLength) {
|
|
61
54
|
// Grow via `push`, not `out.length = nextLength`. AS's `length=`
|
|
62
55
|
// setter calls `ensureCapacity(canGrow=false)` which reallocates to
|
|
63
|
-
// *exactly* the requested size
|
|
56
|
+
// *exactly* the requested size - fine for a one-shot resize, but
|
|
64
57
|
// catastrophic in the per-element loop (every push triggers a full
|
|
65
58
|
// copy of the array, giving O(N²) growth cost). `push` goes through
|
|
66
59
|
// `canGrow=true`, doubling capacity geometrically as needed.
|
|
@@ -8,11 +8,7 @@ import { isSpace } from "../../../util";
|
|
|
8
8
|
import { ensureArrayElementSlot, ensureArrayField } from "./shared";
|
|
9
9
|
import { deserializeStringField_SWAR } from "../string";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
@inline function skipStringArrayWhitespace(
|
|
13
|
-
srcStart: usize,
|
|
14
|
-
srcEnd: usize,
|
|
15
|
-
): usize {
|
|
11
|
+
function skipStringArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
|
|
16
12
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
17
13
|
return srcStart;
|
|
18
14
|
}
|
|
@@ -41,7 +37,7 @@ function deserializeStringArrayBody<T extends string[]>(
|
|
|
41
37
|
// and skip 8 bytes.
|
|
42
38
|
//
|
|
43
39
|
// We accept null tokens unconditionally rather than gating on
|
|
44
|
-
// `isNullable<valueof<T>>()`
|
|
40
|
+
// `isNullable<valueof<T>>()` - AS's `valueof` of a nullable-array
|
|
45
41
|
// element type doesn't always preserve the nullable marker through
|
|
46
42
|
// the dispatcher's `<T>` cast, so the gate would mis-fire for the
|
|
47
43
|
// very case it's meant to handle. The runtime cost on plain
|
|
@@ -80,8 +76,7 @@ function deserializeStringArrayBody<T extends string[]>(
|
|
|
80
76
|
throw new Error("Failed to parse JSON!");
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
|
|
84
|
-
@inline export function deserializeStringArrayField<T extends string[]>(
|
|
79
|
+
export function deserializeStringArrayField<T extends string[]>(
|
|
85
80
|
srcStart: usize,
|
|
86
81
|
srcEnd: usize,
|
|
87
82
|
fieldPtr: usize,
|
|
@@ -2,11 +2,7 @@ import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
|
|
|
2
2
|
import { isSpace } from "../../../util";
|
|
3
3
|
import { ensureArrayElementSlot, ensureArrayField } from "./shared";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
@inline function skipStructArrayWhitespace(
|
|
7
|
-
srcStart: usize,
|
|
8
|
-
srcEnd: usize,
|
|
9
|
-
): usize {
|
|
5
|
+
function skipStructArrayWhitespace(srcStart: usize, srcEnd: usize): usize {
|
|
10
6
|
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
11
7
|
return srcStart;
|
|
12
8
|
}
|
|
@@ -100,9 +96,7 @@ function deserializeStructArrayBody<T extends unknown[]>(
|
|
|
100
96
|
|
|
101
97
|
throw new Error("Failed to parse JSON!");
|
|
102
98
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@inline export function deserializeStructArrayField<T extends unknown[]>(
|
|
99
|
+
export function deserializeStructArrayField<T extends unknown[]>(
|
|
106
100
|
srcStart: usize,
|
|
107
101
|
srcEnd: usize,
|
|
108
102
|
fieldPtr: usize,
|
|
@@ -13,9 +13,7 @@ import { deserializeStringArrayField } from "./array/string";
|
|
|
13
13
|
import { deserializeStructArrayField } from "./array/struct";
|
|
14
14
|
|
|
15
15
|
export { deserializeArrayField as deserializeArrayField_SWAR };
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@inline export function deserializeArrayField<T extends unknown[]>(
|
|
16
|
+
export function deserializeArrayField<T extends unknown[]>(
|
|
19
17
|
srcStart: usize,
|
|
20
18
|
srcEnd: usize,
|
|
21
19
|
dstObj: usize,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
//
|
|
13
13
|
// Inspired by Daniel Lemire, "Number parsing at a gigabyte per second" (2021)
|
|
14
14
|
// and the simdjson `fast_float` implementation. The integer-part loop stays
|
|
15
|
-
// scalar
|
|
15
|
+
// scalar - most JSON float payloads have 1-3 digit integer parts, so a parse4
|
|
16
16
|
// stride there pays the wasted-validate cost on every call without saving
|
|
17
17
|
// enough scalar iterations.
|
|
18
18
|
|
|
@@ -34,14 +34,11 @@ const ASCII_DOT: u16 = 46;
|
|
|
34
34
|
const ASCII_ZERO: u16 = 48;
|
|
35
35
|
const ASCII_E_UP: u16 = 69;
|
|
36
36
|
const ASCII_E_LO: u16 = 101;
|
|
37
|
-
|
|
38
|
-
// @ts-ignore: inline
|
|
39
|
-
@inline export function loadPow10(exp: u32): f64 {
|
|
37
|
+
export function loadPow10(exp: u32): f64 {
|
|
40
38
|
return load<f64>(POW10_F64_POS + ((<usize>exp) << 3));
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
@inline function fallback<T>(srcStart: usize, srcEnd: usize): T {
|
|
41
|
+
function fallback<T>(srcStart: usize, srcEnd: usize): T {
|
|
45
42
|
const s = ptrToStr(srcStart, srcEnd);
|
|
46
43
|
// @ts-ignore
|
|
47
44
|
const type: T = 0;
|
|
@@ -50,9 +47,7 @@ const ASCII_E_LO: u16 = 101;
|
|
|
50
47
|
// @ts-ignore
|
|
51
48
|
return <T>(<f32>f32.parse(s));
|
|
52
49
|
}
|
|
53
|
-
|
|
54
|
-
// @ts-ignore: inline
|
|
55
|
-
@inline function fallbackField<T extends number>(
|
|
50
|
+
function fallbackField<T extends number>(
|
|
56
51
|
origStart: usize,
|
|
57
52
|
end: usize,
|
|
58
53
|
fieldPtr: usize,
|
|
@@ -40,11 +40,7 @@ const ASCII_ZERO: u16 = 48;
|
|
|
40
40
|
* @param value The `u64` accumulator, interpreted as a two's-complement
|
|
41
41
|
* signed integer for narrower types.
|
|
42
42
|
*/
|
|
43
|
-
|
|
44
|
-
@inline function storeSignedToField<T extends number>(
|
|
45
|
-
dstPtr: usize,
|
|
46
|
-
value: u64,
|
|
47
|
-
): void {
|
|
43
|
+
function storeSignedToField<T extends number>(dstPtr: usize, value: u64): void {
|
|
48
44
|
if (sizeof<T>() == 1) {
|
|
49
45
|
store<i8>(dstPtr, <i8>value);
|
|
50
46
|
} else if (sizeof<T>() == 2) {
|
|
@@ -63,8 +59,7 @@ const ASCII_ZERO: u16 = 48;
|
|
|
63
59
|
* @param dstPtr Destination pointer (already includes any field offset).
|
|
64
60
|
* @param value The `u64` accumulator.
|
|
65
61
|
*/
|
|
66
|
-
|
|
67
|
-
@inline function storeUnsignedToField<T extends number>(
|
|
62
|
+
function storeUnsignedToField<T extends number>(
|
|
68
63
|
dstPtr: usize,
|
|
69
64
|
value: u64,
|
|
70
65
|
): void {
|
|
@@ -41,11 +41,7 @@ import { hex4_to_u16_swar } from "../../util/swar";
|
|
|
41
41
|
* @param dst buffer to write to
|
|
42
42
|
* @returns number of bytes written
|
|
43
43
|
*/
|
|
44
|
-
|
|
45
|
-
@inline function copyStringFromSource(
|
|
46
|
-
srcStart: usize,
|
|
47
|
-
byteLength: usize,
|
|
48
|
-
): string {
|
|
44
|
+
function copyStringFromSource(srcStart: usize, byteLength: usize): string {
|
|
49
45
|
if (byteLength == 0) return changetype<string>("");
|
|
50
46
|
const out = __new(byteLength, idof<string>());
|
|
51
47
|
memory.copy(out, srcStart, byteLength);
|
|
@@ -245,7 +241,7 @@ function writeStringToField(
|
|
|
245
241
|
// the next unread src pointer.
|
|
246
242
|
//
|
|
247
243
|
// HYBRID strategy (validated against the prior run-copy scanner across escape
|
|
248
|
-
// densities
|
|
244
|
+
// densities - see __benches__/custom/swar-string-deser-hybrid-h2h: +17–70%):
|
|
249
245
|
// * Escape-bearing block: one optimistic whole-block u64 store copies the
|
|
250
246
|
// plain prefix for free, then the (scalar-confirmed) escape is decoded.
|
|
251
247
|
// * Clean block: stream the first one, then if the clean run continues switch
|
|
@@ -483,9 +479,14 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
483
479
|
if (srcEnd >= 16) {
|
|
484
480
|
const srcEnd16 = srcEnd - 16;
|
|
485
481
|
while (srcStart <= srcEnd16) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
482
|
+
// Test the first word before loading the second: short values and keys
|
|
483
|
+
// close (or escape) within the first 8 bytes, so this skips the second
|
|
484
|
+
// load on the common case while still skipping 16 bytes when both clean.
|
|
485
|
+
if (backslash_or_quote_mask(load<u64>(srcStart)) != 0) break;
|
|
486
|
+
if (backslash_or_quote_mask(load<u64>(srcStart, 8)) != 0) {
|
|
487
|
+
srcStart += 8;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
489
490
|
srcStart += 16;
|
|
490
491
|
}
|
|
491
492
|
}
|
|
@@ -555,8 +556,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
555
556
|
* so callers must confirm the hit scalarly.
|
|
556
557
|
* Each matching lane sets itself to 0x80.
|
|
557
558
|
*/
|
|
558
|
-
|
|
559
|
-
@inline function backslash_or_quote_mask(block: u64): u64 {
|
|
559
|
+
function backslash_or_quote_mask(block: u64): u64 {
|
|
560
560
|
const b = block ^ 0x005c_005c_005c_005c;
|
|
561
561
|
const q = block ^ 0x0022_0022_0022_0022;
|
|
562
562
|
return (
|
|
@@ -575,8 +575,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
575
575
|
*
|
|
576
576
|
* Each matching lane sets itself to 0x80.
|
|
577
577
|
*/
|
|
578
|
-
|
|
579
|
-
@inline function backslash_mask(block: u64): u64 {
|
|
578
|
+
function backslash_mask(block: u64): u64 {
|
|
580
579
|
const b = block ^ 0x005c_005c_005c_005c;
|
|
581
580
|
const backslash_mask =
|
|
582
581
|
(b - 0x0001_0001_0001_0001) & ~b & 0x0080_0080_0080_0080;
|
|
@@ -597,8 +596,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
597
596
|
* WARNING: The low byte of a code unit *may* be a backslash, thus triggering false positives!
|
|
598
597
|
* This is useful for a hot path where it is possible to detect the false positive scalarly.
|
|
599
598
|
*/
|
|
600
|
-
|
|
601
|
-
@inline function backslash_mask_unsafe(block: u64): u64 {
|
|
599
|
+
function backslash_mask_unsafe(block: u64): u64 {
|
|
602
600
|
const b = block ^ 0x005c_005c_005c_005c;
|
|
603
601
|
const backslash_mask =
|
|
604
602
|
(b - 0x0001_0001_0001_0001) & ~b & 0x0080_0080_0080_0080;
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
//
|
|
7
7
|
// - **No count pass.** TypedArrays have a fixed length at construction,
|
|
8
8
|
// so the natural approach is to count first then allocate. We tried
|
|
9
|
-
// that with a SWAR comma counter
|
|
9
|
+
// that with a SWAR comma counter - it cut the per-element cost but
|
|
10
10
|
// kept us ~30% below the top-level `f64[]` path because the count
|
|
11
11
|
// scan still touched the whole input twice. Instead we allocate
|
|
12
|
-
// worst-case (`(srcEnd - srcStart) >> 2 + 1` elements
|
|
12
|
+
// worst-case (`(srcEnd - srcStart) >> 2 + 1` elements - each
|
|
13
13
|
// element needs >= "D," = 2 UTF-16 chars = 4 bytes) and `__renew`
|
|
14
14
|
// the underlying buffer down to the exact byte count after parsing.
|
|
15
15
|
// The over-allocation peaks at ~2-3× the final size for typical
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
* Worst-case element count: each element occupies >= 1 digit + 1
|
|
47
47
|
* delimiter = 2 UTF-16 chars = 4 bytes. So `(srcEnd - srcStart) >> 2`
|
|
48
48
|
* upper-bounds the count. Allocating to worst-case lets us skip a
|
|
49
|
-
* full count pass over the input
|
|
49
|
+
* full count pass over the input - at the cost of an over-allocated
|
|
50
50
|
* underlying buffer that we trim via `__renew` once we know the
|
|
51
51
|
* actual element count.
|
|
52
52
|
*
|
|
@@ -125,7 +125,7 @@ export function deserializeTypedArray_SWAR<T extends ArrayLike<number>>(
|
|
|
125
125
|
// directly: `__renew` the buffer to the actual byte length and
|
|
126
126
|
// update the view's `byteLength` and `dataStart`. AS's TypedArray
|
|
127
127
|
// structure has `buffer`, `dataStart` (= buffer), `byteLength`
|
|
128
|
-
// (capacity in bytes) in that order
|
|
128
|
+
// (capacity in bytes) in that order - same layout as ArrayBufferView.
|
|
129
129
|
const actualCount = i32(<usize>(writePtr - dataStart) / elementSize);
|
|
130
130
|
if (actualCount != maxElements) {
|
|
131
131
|
const actualBytes = <usize>actualCount * elementSize;
|