json-as 1.3.7 → 1.3.9
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 +36 -0
- package/README.md +1 -1
- package/assembly/deserialize/index/arbitrary.ts +2 -2
- package/assembly/deserialize/index/array.ts +29 -14
- package/assembly/deserialize/index/bool.ts +1 -1
- package/assembly/deserialize/index/date.ts +1 -1
- package/assembly/deserialize/index/float.ts +40 -1
- package/assembly/deserialize/index/integer.ts +3 -3
- package/assembly/deserialize/index/map.ts +1 -1
- package/assembly/deserialize/index/object.ts +1 -1
- package/assembly/deserialize/index/raw.ts +1 -1
- package/assembly/deserialize/index/set.ts +1 -1
- package/assembly/deserialize/index/staticarray.ts +4 -1
- package/assembly/deserialize/index/string.ts +28 -3
- package/assembly/deserialize/index/struct.ts +1 -1
- package/assembly/deserialize/index/typedarray.ts +25 -15
- package/assembly/deserialize/index/unsigned.ts +3 -3
- package/assembly/deserialize/index.ts +1 -0
- package/assembly/deserialize/naive/array/bool.ts +68 -0
- package/assembly/deserialize/naive/array/float.ts +63 -0
- package/assembly/deserialize/{simple → naive}/array/generic.ts +1 -2
- package/assembly/deserialize/naive/array/integer.ts +86 -0
- package/assembly/deserialize/{simple → naive}/array/map.ts +0 -1
- package/assembly/deserialize/{simple → naive}/array/object.ts +0 -1
- package/assembly/deserialize/naive/array/string.ts +69 -0
- package/assembly/deserialize/{simple → naive}/array/struct.ts +0 -1
- package/assembly/deserialize/{simple → naive}/array.ts +6 -11
- package/assembly/deserialize/naive/float.ts +135 -0
- package/assembly/deserialize/{simple → naive}/integer.ts +2 -2
- package/assembly/deserialize/{simple → naive}/map.ts +12 -6
- package/assembly/deserialize/{simple → naive}/object.ts +4 -7
- package/assembly/deserialize/{simple → naive}/set.ts +12 -27
- package/assembly/deserialize/{simple → naive}/staticarray/array.ts +1 -1
- package/assembly/deserialize/{simple → naive}/staticarray/bool.ts +1 -1
- package/assembly/deserialize/{simple → naive}/staticarray/float.ts +1 -1
- package/assembly/deserialize/{simple → naive}/staticarray/integer.ts +1 -1
- package/assembly/deserialize/{simple → naive}/staticarray/struct.ts +1 -2
- package/assembly/deserialize/{simple → naive}/staticarray.ts +4 -4
- package/assembly/deserialize/naive/string.ts +199 -0
- package/assembly/deserialize/{simple → naive}/typedarray.ts +4 -4
- package/assembly/deserialize/{simple → naive}/unsigned.ts +2 -2
- package/assembly/deserialize/simd/array/integer.ts +19 -19
- package/assembly/deserialize/simd/float.ts +303 -0
- package/assembly/deserialize/simd/string.ts +233 -108
- package/assembly/deserialize/swar/array/arbitrary.ts +6 -2
- package/assembly/deserialize/swar/array/array.ts +14 -7
- package/assembly/deserialize/swar/array/bool.ts +8 -3
- package/assembly/deserialize/swar/array/box.ts +6 -2
- package/assembly/deserialize/swar/array/float.ts +282 -6
- package/assembly/deserialize/swar/array/generic.ts +6 -2
- package/assembly/deserialize/swar/array/integer.ts +81 -74
- package/assembly/deserialize/swar/array/map.ts +6 -2
- package/assembly/deserialize/swar/array/object.ts +24 -32
- package/assembly/deserialize/swar/array/raw.ts +6 -2
- package/assembly/deserialize/swar/array/shared.ts +32 -8
- package/assembly/deserialize/swar/array/string.ts +127 -10
- package/assembly/deserialize/swar/array/struct.ts +45 -11
- package/assembly/deserialize/swar/array.ts +2 -56
- package/assembly/deserialize/swar/float.ts +304 -0
- package/assembly/deserialize/swar/string.ts +119 -104
- package/assembly/deserialize/swar/typedarray.ts +224 -0
- package/assembly/index.ts +203 -293
- package/assembly/serialize/index/array.ts +1 -1
- package/assembly/serialize/index/bool.ts +1 -1
- package/assembly/serialize/index/date.ts +1 -1
- package/assembly/serialize/index/float.ts +1 -1
- package/assembly/serialize/index/integer.ts +1 -1
- package/assembly/serialize/index/map.ts +1 -1
- package/assembly/serialize/index/raw.ts +1 -1
- package/assembly/serialize/index/set.ts +1 -1
- package/assembly/serialize/index/staticarray.ts +1 -1
- package/assembly/serialize/index/string.ts +1 -1
- package/assembly/serialize/index/struct.ts +1 -1
- package/assembly/serialize/index/typedarray.ts +2 -11
- package/assembly/serialize/index.ts +1 -0
- package/assembly/serialize/{simple → naive}/array.ts +87 -0
- package/assembly/serialize/{simple → naive}/string.ts +1 -1
- package/assembly/serialize/swar/string.ts +0 -139
- package/assembly/util/dragonbox.ts +10 -3
- package/assembly/util/itoa-fast.ts +29 -18
- package/assembly/util/scanValueEnd.ts +78 -0
- package/assembly/util/scientific.ts +132 -0
- package/lib/as-bs.ts +14 -1
- package/package.json +14 -13
- package/transform/lib/index.d.ts +4 -0
- package/transform/lib/index.d.ts.map +1 -1
- package/transform/lib/index.js +155 -238
- package/transform/lib/index.js.map +1 -1
- package/assembly/deserialize/simple/arbitrary.ts +0 -30
- package/assembly/deserialize/simple/array/bool.ts +0 -48
- package/assembly/deserialize/simple/array/float.ts +0 -55
- package/assembly/deserialize/simple/array/integer.ts +0 -33
- package/assembly/deserialize/simple/array/string.ts +0 -29
- package/assembly/deserialize/simple/float.ts +0 -206
- package/assembly/deserialize/simple/string.ts +0 -45
- package/assembly/serialize/simple/arbitrary.ts +0 -79
- package/assembly/serialize/simple/object.ts +0 -42
- /package/assembly/deserialize/{simple → naive}/array/arbitrary.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/array/array.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/array/box.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/array/raw.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/bool.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/date.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/raw.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/staticarray/string.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/struct.ts +0 -0
- /package/assembly/serialize/{simple → naive}/bool.ts +0 -0
- /package/assembly/serialize/{simple → naive}/date.ts +0 -0
- /package/assembly/serialize/{simple → naive}/float.ts +0 -0
- /package/assembly/serialize/{simple → naive}/integer.ts +0 -0
- /package/assembly/serialize/{simple → naive}/map.ts +0 -0
- /package/assembly/serialize/{simple → naive}/raw.ts +0 -0
- /package/assembly/serialize/{simple → naive}/set.ts +0 -0
- /package/assembly/serialize/{simple → naive}/staticarray.ts +0 -0
- /package/assembly/serialize/{simple → naive}/struct.ts +0 -0
- /package/assembly/serialize/{simple → naive}/typedarray.ts +0 -0
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
67
|
+
next = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_FAST<
|
|
43
68
|
valueof<T>
|
|
44
|
-
>(
|
|
45
|
-
}
|
|
69
|
+
>(srcStart, srcEnd, value);
|
|
70
|
+
}
|
|
71
|
+
if (!next) {
|
|
46
72
|
// @ts-ignore: supplied by transform
|
|
47
|
-
|
|
73
|
+
next = changetype<nonnull<valueof<T>>>(value).__DESERIALIZE_SLOW<
|
|
48
74
|
valueof<T>
|
|
49
|
-
>(
|
|
75
|
+
>(srcStart, srcEnd, value);
|
|
50
76
|
}
|
|
51
|
-
if (!
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
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
|
+
}
|