json-as 1.5.0 → 1.6.1
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 +10 -1
- package/README.md +42 -0
- package/assembly/deserialize/error.ts +21 -0
- package/assembly/deserialize/index/arbitrary.ts +29 -8
- package/assembly/deserialize/index/array.ts +2 -1
- package/assembly/deserialize/index/bool.ts +1 -1
- package/assembly/deserialize/index/object.ts +3 -0
- package/assembly/deserialize/index/string.ts +37 -0
- package/assembly/deserialize/index.ts +0 -1
- package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
- package/assembly/deserialize/naive/array/array.ts +1 -0
- package/assembly/deserialize/naive/array/bool.ts +21 -11
- package/assembly/deserialize/naive/array/box.ts +13 -33
- package/assembly/deserialize/naive/array/float.ts +23 -13
- package/assembly/deserialize/naive/array/integer.ts +33 -21
- package/assembly/deserialize/naive/array/object.ts +1 -0
- package/assembly/deserialize/naive/array/string.ts +29 -16
- package/assembly/deserialize/naive/bool.ts +17 -5
- package/assembly/deserialize/naive/float.ts +1 -2
- package/assembly/deserialize/naive/map.ts +339 -39
- package/assembly/deserialize/naive/object.ts +122 -65
- package/assembly/deserialize/naive/raw.ts +13 -1
- package/assembly/deserialize/naive/set.ts +0 -1
- package/assembly/deserialize/naive/staticarray.ts +3 -2
- package/assembly/deserialize/naive/string.ts +41 -52
- package/assembly/deserialize/parseMode.ts +216 -0
- package/assembly/deserialize/simd/array/integer.ts +6 -3
- package/assembly/deserialize/simd/float.ts +35 -29
- package/assembly/deserialize/simd/string.ts +79 -17
- package/assembly/deserialize/string-validation.ts +34 -0
- package/assembly/deserialize/swar/array/array.ts +63 -13
- package/assembly/deserialize/swar/array/bool.ts +14 -3
- package/assembly/deserialize/swar/array/float.ts +394 -13
- package/assembly/deserialize/swar/array/generic.ts +12 -2
- package/assembly/deserialize/swar/array/integer.ts +22 -62
- package/assembly/deserialize/swar/array/object.ts +11 -2
- package/assembly/deserialize/swar/array/shared.ts +1 -1
- package/assembly/deserialize/swar/array/string.ts +34 -5
- package/assembly/deserialize/swar/array/struct.ts +109 -15
- package/assembly/deserialize/swar/float.ts +19 -12
- package/assembly/deserialize/swar/string.ts +70 -111
- package/assembly/index.d.ts +6 -0
- package/assembly/index.ts +411 -33
- package/assembly/serialize/index/bool.ts +1 -1
- package/assembly/serialize/index/float.ts +1 -5
- package/assembly/serialize/index/integer.ts +1 -1
- package/assembly/serialize/index/object.ts +17 -0
- package/assembly/serialize/naive/array.ts +64 -0
- package/assembly/serialize/naive/float.ts +0 -5
- package/assembly/serialize/naive/map.ts +37 -0
- package/assembly/serialize/naive/set.ts +0 -1
- package/assembly/serialize/naive/staticarray.ts +0 -1
- package/assembly/serialize/naive/string.ts +0 -6
- package/assembly/serialize/naive/typedarray.ts +0 -1
- package/assembly/util/eisel-lemire.ts +179 -0
- package/assembly/util/prettyWhitespaceSimd.ts +69 -0
- package/assembly/util/scanValueEndSimd.ts +59 -36
- package/assembly/util/validateJson.ts +338 -0
- package/lib/as-bs.ts +28 -6
- package/package.json +17 -7
- package/transform/lib/index.d.ts +6 -0
- package/transform/lib/index.js +773 -112
- package/transform/lib/types.d.ts +1 -0
- package/transform/lib/types.js +3 -0
- package/assembly/deserialize/index/struct.ts +0 -1
- package/assembly/deserialize/naive/struct.ts +0 -21
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
let SIMD_PRETTY_PARSE = false;
|
|
2
|
+
const ENABLE_EXACT_SOURCE_TRACES = true;
|
|
3
|
+
|
|
4
|
+
// Steady-state SIMD string-field trace. JSON source strings and parsed field
|
|
5
|
+
// strings are immutable at the language level, so the tuple
|
|
6
|
+
// (source position, destination slot, current string reference) proves that a
|
|
7
|
+
// field still contains the bytes produced by the prior parse. Any mutation or
|
|
8
|
+
// graph replacement changes at least one tuple component and takes the normal
|
|
9
|
+
// decoder, which refreshes that trace slot.
|
|
10
|
+
let STRING_TRACE_DEPTH = 0;
|
|
11
|
+
let STRING_TRACE_ACTIVE = false;
|
|
12
|
+
let STRING_TRACE_COMPLETE = false;
|
|
13
|
+
let STRING_TRACE_SOURCE: string | null = null;
|
|
14
|
+
let STRING_TRACE_OUT: usize = 0;
|
|
15
|
+
let STRING_TRACE_TYPE: u32 = 0;
|
|
16
|
+
let STRING_TRACE_INDEX = 0;
|
|
17
|
+
let STRING_TRACE_COUNT = 0;
|
|
18
|
+
let STRING_TRACE_SLOT = 0;
|
|
19
|
+
const STRING_TRACE_FIELDS = new Array<usize>();
|
|
20
|
+
const STRING_TRACE_STARTS = new Array<usize>();
|
|
21
|
+
const STRING_TRACE_ENDS = new Array<usize>();
|
|
22
|
+
const STRING_TRACE_VALUES = new Array<string>();
|
|
23
|
+
let OBJECT_TRACE_INDEX = 0;
|
|
24
|
+
let OBJECT_TRACE_COUNT = 0;
|
|
25
|
+
const OBJECT_TRACE_FIELDS = new Array<usize>();
|
|
26
|
+
const OBJECT_TRACE_STARTS = new Array<usize>();
|
|
27
|
+
const OBJECT_TRACE_TYPES = new Array<string>();
|
|
28
|
+
const OBJECT_TRACE_MASKS = new Array<u64>();
|
|
29
|
+
const OBJECT_TRACE_TIERS = new Array<u8>();
|
|
30
|
+
const OBJECT_TRACE_SEPARATORS = new Array<usize>();
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@inline
|
|
34
|
+
export function isPrettyParse(): bool {
|
|
35
|
+
return SIMD_PRETTY_PARSE;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@inline
|
|
40
|
+
export function setPrettyParse(value: bool): void {
|
|
41
|
+
SIMD_PRETTY_PARSE = value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Enter one public parse call, suspending trace use in nested parses. */
|
|
45
|
+
@inline
|
|
46
|
+
export function beginStringFieldTrace(
|
|
47
|
+
source: string,
|
|
48
|
+
out: usize,
|
|
49
|
+
typeId: u32,
|
|
50
|
+
): void {
|
|
51
|
+
if (!ENABLE_EXACT_SOURCE_TRACES) return;
|
|
52
|
+
STRING_TRACE_DEPTH++;
|
|
53
|
+
if (!ASC_FEATURE_SIMD || STRING_TRACE_DEPTH != 1 || out == 0) return;
|
|
54
|
+
|
|
55
|
+
const sourcePtr = changetype<usize>(source);
|
|
56
|
+
const sameRoot =
|
|
57
|
+
STRING_TRACE_COMPLETE &&
|
|
58
|
+
STRING_TRACE_OUT == out &&
|
|
59
|
+
STRING_TRACE_TYPE == typeId &&
|
|
60
|
+
changetype<usize>(STRING_TRACE_SOURCE) == sourcePtr;
|
|
61
|
+
if (!sameRoot) {
|
|
62
|
+
STRING_TRACE_SOURCE = source;
|
|
63
|
+
STRING_TRACE_OUT = out;
|
|
64
|
+
STRING_TRACE_TYPE = typeId;
|
|
65
|
+
STRING_TRACE_COUNT = 0;
|
|
66
|
+
STRING_TRACE_FIELDS.length = 0;
|
|
67
|
+
STRING_TRACE_STARTS.length = 0;
|
|
68
|
+
STRING_TRACE_ENDS.length = 0;
|
|
69
|
+
STRING_TRACE_VALUES.length = 0;
|
|
70
|
+
OBJECT_TRACE_COUNT = 0;
|
|
71
|
+
OBJECT_TRACE_FIELDS.length = 0;
|
|
72
|
+
OBJECT_TRACE_STARTS.length = 0;
|
|
73
|
+
OBJECT_TRACE_TYPES.length = 0;
|
|
74
|
+
OBJECT_TRACE_MASKS.length = 0;
|
|
75
|
+
OBJECT_TRACE_TIERS.length = 0;
|
|
76
|
+
OBJECT_TRACE_SEPARATORS.length = 0;
|
|
77
|
+
}
|
|
78
|
+
STRING_TRACE_INDEX = 0;
|
|
79
|
+
OBJECT_TRACE_INDEX = 0;
|
|
80
|
+
STRING_TRACE_COMPLETE = false;
|
|
81
|
+
STRING_TRACE_ACTIVE = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Leave the matching public parse call. */
|
|
85
|
+
@inline
|
|
86
|
+
export function endStringFieldTrace(success: bool): void {
|
|
87
|
+
if (!ENABLE_EXACT_SOURCE_TRACES) return;
|
|
88
|
+
if (STRING_TRACE_DEPTH == 1) {
|
|
89
|
+
STRING_TRACE_ACTIVE = false;
|
|
90
|
+
STRING_TRACE_COMPLETE = success;
|
|
91
|
+
}
|
|
92
|
+
STRING_TRACE_DEPTH--;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Return the cached cursor, or zero when this field must be decoded. */
|
|
96
|
+
@inline
|
|
97
|
+
export function probeStringFieldTrace(
|
|
98
|
+
dstFieldPtr: usize,
|
|
99
|
+
payloadStart: usize,
|
|
100
|
+
): usize {
|
|
101
|
+
if (!ENABLE_EXACT_SOURCE_TRACES) return 0;
|
|
102
|
+
if (!STRING_TRACE_ACTIVE || STRING_TRACE_DEPTH != 1) return 0;
|
|
103
|
+
const slot = STRING_TRACE_INDEX++;
|
|
104
|
+
STRING_TRACE_SLOT = slot;
|
|
105
|
+
if (
|
|
106
|
+
slot < STRING_TRACE_COUNT &&
|
|
107
|
+
unchecked(STRING_TRACE_FIELDS[slot]) == dstFieldPtr &&
|
|
108
|
+
unchecked(STRING_TRACE_STARTS[slot]) == payloadStart &&
|
|
109
|
+
load<usize>(dstFieldPtr) ==
|
|
110
|
+
changetype<usize>(unchecked(STRING_TRACE_VALUES[slot]))
|
|
111
|
+
)
|
|
112
|
+
return unchecked(STRING_TRACE_ENDS[slot]);
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Record the successful decode corresponding to the most recent probe. */
|
|
117
|
+
@inline
|
|
118
|
+
export function recordStringFieldTrace(
|
|
119
|
+
dstFieldPtr: usize,
|
|
120
|
+
payloadStart: usize,
|
|
121
|
+
next: usize,
|
|
122
|
+
): void {
|
|
123
|
+
if (!ENABLE_EXACT_SOURCE_TRACES) return;
|
|
124
|
+
if (!STRING_TRACE_ACTIVE || STRING_TRACE_DEPTH != 1) return;
|
|
125
|
+
const slot = STRING_TRACE_SLOT;
|
|
126
|
+
const value = load<string>(dstFieldPtr);
|
|
127
|
+
if (slot < STRING_TRACE_COUNT) {
|
|
128
|
+
unchecked((STRING_TRACE_FIELDS[slot] = dstFieldPtr));
|
|
129
|
+
unchecked((STRING_TRACE_STARTS[slot] = payloadStart));
|
|
130
|
+
unchecked((STRING_TRACE_ENDS[slot] = next));
|
|
131
|
+
unchecked((STRING_TRACE_VALUES[slot] = value));
|
|
132
|
+
} else {
|
|
133
|
+
STRING_TRACE_FIELDS.push(dstFieldPtr);
|
|
134
|
+
STRING_TRACE_STARTS.push(payloadStart);
|
|
135
|
+
STRING_TRACE_ENDS.push(next);
|
|
136
|
+
STRING_TRACE_VALUES.push(value);
|
|
137
|
+
STRING_TRACE_COUNT = slot + 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Reserve or find one optional-struct trace slot.
|
|
143
|
+
*
|
|
144
|
+
* Returns i32.MIN_VALUE when tracing is inactive, a non-negative slot on a
|
|
145
|
+
* miss, or `~slot` (negative) on a hit. Reserving at begin keeps nested object
|
|
146
|
+
* records well-ordered even though a parent only commits after its children.
|
|
147
|
+
*/
|
|
148
|
+
@inline
|
|
149
|
+
export function beginObjectFieldTrace(
|
|
150
|
+
dst: usize,
|
|
151
|
+
srcStart: usize,
|
|
152
|
+
typeTag: string,
|
|
153
|
+
): i32 {
|
|
154
|
+
if (!ENABLE_EXACT_SOURCE_TRACES) return i32.MIN_VALUE;
|
|
155
|
+
if (!STRING_TRACE_ACTIVE || STRING_TRACE_DEPTH != 1) return i32.MIN_VALUE;
|
|
156
|
+
const slot = OBJECT_TRACE_INDEX++;
|
|
157
|
+
if (
|
|
158
|
+
slot < OBJECT_TRACE_COUNT &&
|
|
159
|
+
unchecked(OBJECT_TRACE_FIELDS[slot]) == dst &&
|
|
160
|
+
unchecked(OBJECT_TRACE_STARTS[slot]) == srcStart &&
|
|
161
|
+
unchecked(OBJECT_TRACE_TIERS[slot]) != 0 &&
|
|
162
|
+
changetype<usize>(unchecked(OBJECT_TRACE_TYPES[slot])) ==
|
|
163
|
+
changetype<usize>(typeTag)
|
|
164
|
+
)
|
|
165
|
+
return ~slot;
|
|
166
|
+
|
|
167
|
+
if (slot < OBJECT_TRACE_COUNT) {
|
|
168
|
+
unchecked((OBJECT_TRACE_FIELDS[slot] = dst));
|
|
169
|
+
unchecked((OBJECT_TRACE_STARTS[slot] = srcStart));
|
|
170
|
+
unchecked((OBJECT_TRACE_TYPES[slot] = typeTag));
|
|
171
|
+
unchecked((OBJECT_TRACE_MASKS[slot] = 0));
|
|
172
|
+
unchecked((OBJECT_TRACE_TIERS[slot] = 0));
|
|
173
|
+
unchecked((OBJECT_TRACE_SEPARATORS[slot] = 0));
|
|
174
|
+
} else {
|
|
175
|
+
OBJECT_TRACE_FIELDS.push(dst);
|
|
176
|
+
OBJECT_TRACE_STARTS.push(srcStart);
|
|
177
|
+
OBJECT_TRACE_TYPES.push(typeTag);
|
|
178
|
+
OBJECT_TRACE_MASKS.push(0);
|
|
179
|
+
OBJECT_TRACE_TIERS.push(0);
|
|
180
|
+
OBJECT_TRACE_SEPARATORS.push(0);
|
|
181
|
+
OBJECT_TRACE_COUNT = slot + 1;
|
|
182
|
+
}
|
|
183
|
+
return slot;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@inline
|
|
188
|
+
export function objectFieldTraceMask(slot: i32): u64 {
|
|
189
|
+
return unchecked(OBJECT_TRACE_MASKS[slot]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@inline
|
|
194
|
+
export function objectFieldTraceTier(slot: i32): u8 {
|
|
195
|
+
return unchecked(OBJECT_TRACE_TIERS[slot]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@inline
|
|
200
|
+
export function objectFieldTraceSeparator(slot: i32): usize {
|
|
201
|
+
return unchecked(OBJECT_TRACE_SEPARATORS[slot]);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@inline
|
|
206
|
+
export function recordObjectFieldTrace(
|
|
207
|
+
slot: i32,
|
|
208
|
+
present: u64,
|
|
209
|
+
tier: u8,
|
|
210
|
+
separator: usize = 0,
|
|
211
|
+
): void {
|
|
212
|
+
if (slot < 0 || slot >= OBJECT_TRACE_COUNT) return;
|
|
213
|
+
unchecked((OBJECT_TRACE_MASKS[slot] = present));
|
|
214
|
+
unchecked((OBJECT_TRACE_TIERS[slot] = tier));
|
|
215
|
+
unchecked((OBJECT_TRACE_SEPARATORS[slot] = separator));
|
|
216
|
+
}
|
|
@@ -397,7 +397,10 @@ export function deserializeIntegerArray_SIMD<T extends number[]>(
|
|
|
397
397
|
dst || changetype<usize>(instantiate<T>()),
|
|
398
398
|
);
|
|
399
399
|
const originalSrcStart = srcStart;
|
|
400
|
-
const reusableLength =
|
|
400
|
+
const reusableLength = load<i32>(
|
|
401
|
+
changetype<usize>(out),
|
|
402
|
+
offsetof<T>("length_"),
|
|
403
|
+
);
|
|
401
404
|
|
|
402
405
|
if (reusableLength != 0) {
|
|
403
406
|
const dataStart = out.dataStart;
|
|
@@ -455,7 +458,7 @@ export function deserializeIntegerArray_SIMD<T extends number[]>(
|
|
|
455
458
|
continue;
|
|
456
459
|
}
|
|
457
460
|
if (code == BRACKET_RIGHT) {
|
|
458
|
-
out.length = index;
|
|
461
|
+
if (index != reusableLength) out.length = index;
|
|
459
462
|
return out;
|
|
460
463
|
}
|
|
461
464
|
break;
|
|
@@ -494,7 +497,7 @@ export function deserializeIntegerArray_SIMD<T extends number[]>(
|
|
|
494
497
|
continue;
|
|
495
498
|
}
|
|
496
499
|
if (code == BRACKET_RIGHT) {
|
|
497
|
-
out.length = index;
|
|
500
|
+
if (index != reusableLength) out.length = index;
|
|
498
501
|
return out;
|
|
499
502
|
}
|
|
500
503
|
break;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// SIMD float deserializers. Same Lemire
|
|
2
|
-
//
|
|
3
|
-
// `
|
|
4
|
-
//
|
|
1
|
+
// SIMD float deserializers. Same Eisel-Lemire / `scientific()` / `f*.parse`
|
|
2
|
+
// cascade as `swar/float.ts`, but the fractional digit loop starts with a
|
|
3
|
+
// `parse16Digits_SIMD` stride before falling through to `parse4Digits_PairMul`
|
|
4
|
+
// and finally the scalar tail.
|
|
5
5
|
//
|
|
6
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
|
|
@@ -14,6 +14,7 @@ import { ptrToStr } from "../../util/ptrToStr";
|
|
|
14
14
|
import { parse4Digits_PairMul } from "../../util/swar-int";
|
|
15
15
|
import { parse16Digits_SIMD } from "../../util/simd-int";
|
|
16
16
|
import { scientific } from "../../util/scientific";
|
|
17
|
+
import { eiselLemire22 } from "../../util/eisel-lemire";
|
|
17
18
|
import { loadPow10, MAX_EXACT_MANTISSA, MAX_EXACT_POW10 } from "../swar/float";
|
|
18
19
|
|
|
19
20
|
const ASCII_PLUS: u16 = 43;
|
|
@@ -56,48 +57,47 @@ export function deserializeFloat_SIMD<T>(srcStart: usize, srcEnd: usize): T {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
let mantissa: u64 = 0;
|
|
59
|
-
|
|
60
|
+
const intStart = p;
|
|
60
61
|
while (p < srcEnd) {
|
|
61
62
|
const d = <u32>load<u16>(p) - ASCII_ZERO;
|
|
62
63
|
if (d > 9) break;
|
|
63
64
|
mantissa = mantissa * 10 + <u64>d;
|
|
64
|
-
intDigits++;
|
|
65
65
|
p += 2;
|
|
66
66
|
}
|
|
67
|
+
const intDigits = <i32>((p - intStart) >> 1);
|
|
67
68
|
|
|
68
|
-
// Fractional part:
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
let fracDigits: i32 = 0;
|
|
69
|
+
// Fractional part: one parse16 SIMD stride → parse4 SWAR stride →
|
|
70
|
+
// scalar tail. The parse16 gate keeps the combined mantissa within u64.
|
|
71
|
+
let fracStart = p;
|
|
72
72
|
if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
|
|
73
73
|
p += 2;
|
|
74
|
+
fracStart = p;
|
|
74
75
|
// parse16 SIMD only fires on long fractions (>=16 digits ahead, with
|
|
75
76
|
// <=3 integer digits to keep mantissa * 1e16 under u64 max). Rare in
|
|
76
77
|
// typical JSON but a big win when it does fire (8 digits per stride in
|
|
77
78
|
// SWAR's parse8 was benched even/worse than parse4, so we skip parse8
|
|
78
79
|
// entirely and let parse4 handle the 4-15 char tail).
|
|
79
|
-
|
|
80
|
+
if (p + 30 < srcEnd && intDigits <= 3) {
|
|
80
81
|
const parsed = parse16Digits_SIMD(p);
|
|
81
|
-
if (parsed
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
if (parsed != U64.MAX_VALUE) {
|
|
83
|
+
mantissa = mantissa * 10_000_000_000_000_000 + parsed;
|
|
84
|
+
p += 32;
|
|
85
|
+
}
|
|
85
86
|
}
|
|
86
87
|
while (p + 6 < srcEnd) {
|
|
87
88
|
const parsed = parse4Digits_PairMul(load<u64>(p));
|
|
88
89
|
if (parsed == U32.MAX_VALUE) break;
|
|
89
90
|
mantissa = mantissa * 10_000 + <u64>parsed;
|
|
90
|
-
fracDigits += 4;
|
|
91
91
|
p += 8;
|
|
92
92
|
}
|
|
93
93
|
while (p < srcEnd) {
|
|
94
94
|
const d = <u32>load<u16>(p) - ASCII_ZERO;
|
|
95
95
|
if (d > 9) break;
|
|
96
96
|
mantissa = mantissa * 10 + <u64>d;
|
|
97
|
-
fracDigits++;
|
|
98
97
|
p += 2;
|
|
99
98
|
}
|
|
100
99
|
}
|
|
100
|
+
const fracDigits = <i32>((p - fracStart) >> 1);
|
|
101
101
|
|
|
102
102
|
const mantDigits = intDigits + fracDigits;
|
|
103
103
|
if (mantDigits == 0) return fallback<T>(origStart, srcEnd);
|
|
@@ -148,7 +148,10 @@ export function deserializeFloat_SIMD<T>(srcStart: usize, srcEnd: usize): T {
|
|
|
148
148
|
result /= loadPow10(<u32>-exponent);
|
|
149
149
|
}
|
|
150
150
|
} else if (mantDigits <= 19) {
|
|
151
|
-
result =
|
|
151
|
+
result =
|
|
152
|
+
exponent >= -22 && exponent <= 22
|
|
153
|
+
? eiselLemire22(mantissa, exponent)
|
|
154
|
+
: scientific(mantissa, exponent);
|
|
152
155
|
} else {
|
|
153
156
|
return fallback<T>(origStart, srcEnd);
|
|
154
157
|
}
|
|
@@ -178,45 +181,45 @@ export function deserializeFloatField_SIMD<T extends number>(
|
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
let mantissa: u64 = 0;
|
|
181
|
-
|
|
184
|
+
const intStart = p;
|
|
182
185
|
while (p < srcEnd) {
|
|
183
186
|
const d = <u32>load<u16>(p) - ASCII_ZERO;
|
|
184
187
|
if (d > 9) break;
|
|
185
188
|
mantissa = mantissa * 10 + <u64>d;
|
|
186
|
-
intDigits++;
|
|
187
189
|
p += 2;
|
|
188
190
|
}
|
|
191
|
+
const intDigits = <i32>((p - intStart) >> 1);
|
|
189
192
|
|
|
190
|
-
let
|
|
193
|
+
let fracStart = p;
|
|
191
194
|
if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
|
|
192
195
|
p += 2;
|
|
196
|
+
fracStart = p;
|
|
193
197
|
// parse16 SIMD only fires on long fractions (>=16 digits ahead, with
|
|
194
198
|
// <=3 integer digits to keep mantissa * 1e16 under u64 max). Rare in
|
|
195
199
|
// typical JSON but a big win when it does fire (8 digits per stride in
|
|
196
200
|
// SWAR's parse8 was benched even/worse than parse4, so we skip parse8
|
|
197
201
|
// entirely and let parse4 handle the 4-15 char tail).
|
|
198
|
-
|
|
202
|
+
if (p + 30 < srcEnd && intDigits <= 3) {
|
|
199
203
|
const parsed = parse16Digits_SIMD(p);
|
|
200
|
-
if (parsed
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
if (parsed != U64.MAX_VALUE) {
|
|
205
|
+
mantissa = mantissa * 10_000_000_000_000_000 + parsed;
|
|
206
|
+
p += 32;
|
|
207
|
+
}
|
|
204
208
|
}
|
|
205
209
|
while (p + 6 < srcEnd) {
|
|
206
210
|
const parsed = parse4Digits_PairMul(load<u64>(p));
|
|
207
211
|
if (parsed == U32.MAX_VALUE) break;
|
|
208
212
|
mantissa = mantissa * 10_000 + <u64>parsed;
|
|
209
|
-
fracDigits += 4;
|
|
210
213
|
p += 8;
|
|
211
214
|
}
|
|
212
215
|
while (p < srcEnd) {
|
|
213
216
|
const d = <u32>load<u16>(p) - ASCII_ZERO;
|
|
214
217
|
if (d > 9) break;
|
|
215
218
|
mantissa = mantissa * 10 + <u64>d;
|
|
216
|
-
fracDigits++;
|
|
217
219
|
p += 2;
|
|
218
220
|
}
|
|
219
221
|
}
|
|
222
|
+
const fracDigits = <i32>((p - fracStart) >> 1);
|
|
220
223
|
|
|
221
224
|
const mantDigits = intDigits + fracDigits;
|
|
222
225
|
if (mantDigits == 0) unreachable();
|
|
@@ -279,7 +282,10 @@ export function deserializeFloatField_SIMD<T extends number>(
|
|
|
279
282
|
result /= loadPow10(<u32>-exponent);
|
|
280
283
|
}
|
|
281
284
|
} else if (mantDigits <= 19) {
|
|
282
|
-
result =
|
|
285
|
+
result =
|
|
286
|
+
exponent >= -22 && exponent <= 22
|
|
287
|
+
? eiselLemire22(mantissa, exponent)
|
|
288
|
+
: scientific(mantissa, exponent);
|
|
283
289
|
} else {
|
|
284
290
|
fallbackField<T>(origStart, p, fieldPtr);
|
|
285
291
|
return p;
|
|
@@ -5,6 +5,9 @@ import { QUOTE } from "../../custom/chars";
|
|
|
5
5
|
import { BACK_SLASH } from "../../custom/chars";
|
|
6
6
|
import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
|
|
7
7
|
import { hex4_to_u16_swar } from "../../util/swar";
|
|
8
|
+
import { markProductionParseError } from "../error";
|
|
9
|
+
import { isValidStringEscape } from "../string-validation";
|
|
10
|
+
import { probeStringFieldTrace, recordStringFieldTrace } from "../parseMode";
|
|
8
11
|
|
|
9
12
|
// @ts-expect-error: @lazy is a valid decorator
|
|
10
13
|
@lazy const SPLAT_5C = i16x8.splat(0x5c); // \
|
|
@@ -116,7 +119,7 @@ function deserializeEscapedString_SIMD(
|
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
let srcStart = escapeStart;
|
|
119
|
-
const srcEnd16 = srcEnd
|
|
122
|
+
const srcEnd16 = srcEnd - 16;
|
|
120
123
|
|
|
121
124
|
while (srcStart <= srcEnd16) {
|
|
122
125
|
const block = load<v128>(srcStart);
|
|
@@ -150,6 +153,11 @@ function deserializeEscapedString_SIMD(
|
|
|
150
153
|
const laneIdx = usize(ctz(mask) << 1);
|
|
151
154
|
bs.offset += laneIdx;
|
|
152
155
|
const srcIdx = srcStart + laneIdx;
|
|
156
|
+
if (!isValidStringEscape(srcIdx, srcEnd)) {
|
|
157
|
+
bs.offset = bs.buffer + outStart;
|
|
158
|
+
markProductionParseError();
|
|
159
|
+
return changetype<string>(0);
|
|
160
|
+
}
|
|
153
161
|
const code = load<u16>(srcIdx, 2);
|
|
154
162
|
if (code !== 0x75) {
|
|
155
163
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
@@ -171,6 +179,11 @@ function deserializeEscapedString_SIMD(
|
|
|
171
179
|
srcStart += 2;
|
|
172
180
|
continue;
|
|
173
181
|
}
|
|
182
|
+
if (!isValidStringEscape(srcStart, srcEnd)) {
|
|
183
|
+
bs.offset = bs.buffer + outStart;
|
|
184
|
+
markProductionParseError();
|
|
185
|
+
return changetype<string>(0);
|
|
186
|
+
}
|
|
174
187
|
const code = load<u16>(srcStart, 2);
|
|
175
188
|
if (code !== 0x75) {
|
|
176
189
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
@@ -262,16 +275,23 @@ function deserializeEscapedStringField_SIMD(
|
|
|
262
275
|
): usize {
|
|
263
276
|
const prefixLen = <u32>(escapeStart - payloadStart);
|
|
264
277
|
bs.offset = bs.buffer;
|
|
265
|
-
|
|
278
|
+
// The containing parser may pass document-end bounds. Reserve according to
|
|
279
|
+
// decoded output rather than retaining the entire remaining document.
|
|
280
|
+
bs.ensureSize(prefixLen + 1024);
|
|
266
281
|
if (prefixLen != 0) {
|
|
267
282
|
memory.copy(bs.buffer, payloadStart, prefixLen);
|
|
268
283
|
bs.offset += prefixLen;
|
|
269
284
|
}
|
|
270
285
|
|
|
271
286
|
let srcStart = escapeStart;
|
|
272
|
-
const srcEnd16 = srcEnd
|
|
287
|
+
const srcEnd16 = srcEnd - 16;
|
|
288
|
+
let scratchEnd = bs.buffer + bs.bufferSize - 16;
|
|
273
289
|
|
|
274
290
|
while (srcStart <= srcEnd16) {
|
|
291
|
+
if (bs.offset > scratchEnd) {
|
|
292
|
+
bs.ensureSize(1024);
|
|
293
|
+
scratchEnd = bs.buffer + bs.bufferSize - 16;
|
|
294
|
+
}
|
|
275
295
|
const block = load<v128>(srcStart);
|
|
276
296
|
const mask = i16x8.bitmask(
|
|
277
297
|
v128.or(i16x8.eq(block, SPLAT_5C), i16x8.eq(block, SPLAT_22)),
|
|
@@ -302,6 +322,10 @@ function deserializeEscapedStringField_SIMD(
|
|
|
302
322
|
srcStart += 16;
|
|
303
323
|
}
|
|
304
324
|
const runLen = <u32>(srcStart - runStart);
|
|
325
|
+
if (bs.offset + runLen > scratchEnd) {
|
|
326
|
+
bs.ensureSize(runLen + 1024);
|
|
327
|
+
scratchEnd = bs.buffer + bs.bufferSize - 16;
|
|
328
|
+
}
|
|
305
329
|
memory.copy(bs.offset, runStart, runLen);
|
|
306
330
|
bs.offset += runLen;
|
|
307
331
|
}
|
|
@@ -325,6 +349,11 @@ function deserializeEscapedStringField_SIMD(
|
|
|
325
349
|
return srcIdx + 2;
|
|
326
350
|
}
|
|
327
351
|
|
|
352
|
+
if (!isValidStringEscape(srcIdx, srcEnd)) {
|
|
353
|
+
bs.offset = bs.buffer;
|
|
354
|
+
markProductionParseError();
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
328
357
|
const code = load<u16>(srcIdx, 2);
|
|
329
358
|
if (code !== 0x75) {
|
|
330
359
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
@@ -338,6 +367,7 @@ function deserializeEscapedStringField_SIMD(
|
|
|
338
367
|
}
|
|
339
368
|
|
|
340
369
|
// scalar tail (< 16 bytes remaining): emit chars directly.
|
|
370
|
+
bs.ensureSize(<u32>(srcEnd - srcStart) + 2);
|
|
341
371
|
while (srcStart < srcEnd) {
|
|
342
372
|
const char = load<u16>(srcStart);
|
|
343
373
|
if (char == QUOTE) {
|
|
@@ -356,6 +386,11 @@ function deserializeEscapedStringField_SIMD(
|
|
|
356
386
|
continue;
|
|
357
387
|
}
|
|
358
388
|
|
|
389
|
+
if (!isValidStringEscape(srcStart, srcEnd)) {
|
|
390
|
+
bs.offset = bs.buffer;
|
|
391
|
+
markProductionParseError();
|
|
392
|
+
return 0;
|
|
393
|
+
}
|
|
359
394
|
const code = load<u16>(srcStart, 2);
|
|
360
395
|
if (code !== 0x75) {
|
|
361
396
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
@@ -369,8 +404,8 @@ function deserializeEscapedStringField_SIMD(
|
|
|
369
404
|
}
|
|
370
405
|
|
|
371
406
|
bs.offset = bs.buffer;
|
|
372
|
-
|
|
373
|
-
return
|
|
407
|
+
markProductionParseError();
|
|
408
|
+
return 0;
|
|
374
409
|
}
|
|
375
410
|
|
|
376
411
|
export function deserializeStringField_SIMD<T extends string | null>(
|
|
@@ -379,13 +414,32 @@ export function deserializeStringField_SIMD<T extends string | null>(
|
|
|
379
414
|
dstObj: usize,
|
|
380
415
|
dstOffset: usize = 0,
|
|
381
416
|
): usize {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
417
|
+
if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE) {
|
|
418
|
+
markProductionParseError();
|
|
419
|
+
return 0;
|
|
420
|
+
}
|
|
421
|
+
return deserializeStringFieldTrusted_SIMD(
|
|
422
|
+
srcStart + 2,
|
|
423
|
+
srcEnd,
|
|
424
|
+
dstObj,
|
|
425
|
+
dstOffset,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
385
428
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
429
|
+
// Generated compact-object paths have already matched the key and validated
|
|
430
|
+
// the opening quote. Enter at the payload so that proof replaces (rather than
|
|
431
|
+
// duplicates) the public field decoder's framing load.
|
|
432
|
+
export function deserializeStringFieldTrusted_SIMD(
|
|
433
|
+
payloadStart: usize,
|
|
434
|
+
srcEnd: usize,
|
|
435
|
+
dstObj: usize,
|
|
436
|
+
dstOffset: usize = 0,
|
|
437
|
+
): usize {
|
|
438
|
+
const dstFieldPtr = dstObj + dstOffset;
|
|
439
|
+
const cachedEnd = probeStringFieldTrace(dstFieldPtr, payloadStart);
|
|
440
|
+
if (cachedEnd != 0) return cachedEnd;
|
|
441
|
+
const srcEnd16 = srcEnd - 16;
|
|
442
|
+
let srcStart = payloadStart;
|
|
389
443
|
|
|
390
444
|
while (srcStart <= srcEnd16) {
|
|
391
445
|
const block = load<v128>(srcStart);
|
|
@@ -406,15 +460,19 @@ export function deserializeStringField_SIMD<T extends string | null>(
|
|
|
406
460
|
payloadStart,
|
|
407
461
|
<u32>(srcIdx - payloadStart),
|
|
408
462
|
);
|
|
409
|
-
|
|
463
|
+
const next = srcIdx + 2;
|
|
464
|
+
recordStringFieldTrace(dstFieldPtr, payloadStart, next);
|
|
465
|
+
return next;
|
|
410
466
|
}
|
|
411
467
|
// backslash → vectorized escaped scan (no more SWAR fallback)
|
|
412
|
-
|
|
468
|
+
const next = deserializeEscapedStringField_SIMD(
|
|
413
469
|
payloadStart,
|
|
414
470
|
srcIdx,
|
|
415
471
|
srcEnd,
|
|
416
472
|
dstFieldPtr,
|
|
417
473
|
);
|
|
474
|
+
if (next != 0) recordStringFieldTrace(dstFieldPtr, payloadStart, next);
|
|
475
|
+
return next;
|
|
418
476
|
}
|
|
419
477
|
|
|
420
478
|
while (srcStart < srcEnd) {
|
|
@@ -425,19 +483,23 @@ export function deserializeStringField_SIMD<T extends string | null>(
|
|
|
425
483
|
payloadStart,
|
|
426
484
|
<u32>(srcStart - payloadStart),
|
|
427
485
|
);
|
|
428
|
-
|
|
486
|
+
const next = srcStart + 2;
|
|
487
|
+
recordStringFieldTrace(dstFieldPtr, payloadStart, next);
|
|
488
|
+
return next;
|
|
429
489
|
}
|
|
430
490
|
if (char == BACK_SLASH) {
|
|
431
|
-
|
|
491
|
+
const next = deserializeEscapedStringField_SIMD(
|
|
432
492
|
payloadStart,
|
|
433
493
|
srcStart,
|
|
434
494
|
srcEnd,
|
|
435
495
|
dstFieldPtr,
|
|
436
496
|
);
|
|
497
|
+
if (next != 0) recordStringFieldTrace(dstFieldPtr, payloadStart, next);
|
|
498
|
+
return next;
|
|
437
499
|
}
|
|
438
500
|
srcStart += 2;
|
|
439
501
|
}
|
|
440
502
|
|
|
441
|
-
|
|
442
|
-
return
|
|
503
|
+
markProductionParseError();
|
|
504
|
+
return 0;
|
|
443
505
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
@inline
|
|
3
|
+
function isHexDigit(c: u16): bool {
|
|
4
|
+
return (
|
|
5
|
+
(c >= 0x30 && c <= 0x39) ||
|
|
6
|
+
(c >= 0x41 && c <= 0x46) ||
|
|
7
|
+
(c >= 0x61 && c <= 0x66)
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Validate one escape beginning at its backslash without scanning plain text. */
|
|
12
|
+
export function isValidStringEscape(srcStart: usize, srcEnd: usize): bool {
|
|
13
|
+
if (srcStart + 4 > srcEnd) return false;
|
|
14
|
+
const code = load<u16>(srcStart, 2);
|
|
15
|
+
if (code == 0x75) {
|
|
16
|
+
return (
|
|
17
|
+
srcStart + 12 <= srcEnd &&
|
|
18
|
+
isHexDigit(load<u16>(srcStart, 4)) &&
|
|
19
|
+
isHexDigit(load<u16>(srcStart, 6)) &&
|
|
20
|
+
isHexDigit(load<u16>(srcStart, 8)) &&
|
|
21
|
+
isHexDigit(load<u16>(srcStart, 10))
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return (
|
|
25
|
+
code == 0x22 ||
|
|
26
|
+
code == 0x5c ||
|
|
27
|
+
code == 0x2f ||
|
|
28
|
+
code == 0x62 ||
|
|
29
|
+
code == 0x66 ||
|
|
30
|
+
code == 0x6e ||
|
|
31
|
+
code == 0x72 ||
|
|
32
|
+
code == 0x74
|
|
33
|
+
);
|
|
34
|
+
}
|