json-as 1.4.0 → 1.6.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 +54 -29
- package/README.md +126 -33
- package/assembly/custom/chars.ts +39 -78
- package/assembly/deserialize/error.ts +21 -0
- package/assembly/deserialize/index/arbitrary.ts +50 -11
- package/assembly/deserialize/index/array.ts +2 -1
- package/assembly/deserialize/index/bool.ts +1 -1
- package/assembly/deserialize/index/float.ts +2 -4
- package/assembly/deserialize/index/integer.ts +2 -4
- package/assembly/deserialize/index/object.ts +9 -1
- package/assembly/deserialize/index/string.ts +39 -7
- package/assembly/deserialize/index/unsigned.ts +2 -4
- 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 +34 -22
- package/assembly/deserialize/naive/array/map.ts +1 -1
- package/assembly/deserialize/naive/array/object.ts +2 -1
- package/assembly/deserialize/naive/array/string.ts +29 -16
- package/assembly/deserialize/naive/array/struct.ts +19 -1
- package/assembly/deserialize/naive/bool.ts +18 -10
- package/assembly/deserialize/naive/date.ts +1 -2
- package/assembly/deserialize/naive/float.ts +3 -9
- package/assembly/deserialize/naive/integer.ts +1 -2
- package/assembly/deserialize/naive/map.ts +342 -43
- package/assembly/deserialize/naive/object.ts +250 -55
- package/assembly/deserialize/naive/raw.ts +10 -2
- package/assembly/deserialize/naive/set.ts +2 -5
- package/assembly/deserialize/naive/staticarray.ts +4 -4
- package/assembly/deserialize/naive/string.ts +46 -60
- package/assembly/deserialize/naive/unsigned.ts +1 -2
- package/assembly/deserialize/parseMode.ts +216 -0
- package/assembly/deserialize/simd/array/integer.ts +8 -10
- package/assembly/deserialize/simd/float.ts +38 -34
- package/assembly/deserialize/simd/integer.ts +2 -7
- package/assembly/deserialize/simd/string.ts +84 -39
- package/assembly/deserialize/string-validation.ts +34 -0
- package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
- package/assembly/deserialize/swar/array/array.ts +64 -15
- package/assembly/deserialize/swar/array/bool.ts +15 -5
- package/assembly/deserialize/swar/array/box.ts +1 -2
- package/assembly/deserialize/swar/array/float.ts +399 -30
- package/assembly/deserialize/swar/array/generic.ts +13 -4
- package/assembly/deserialize/swar/array/integer.ts +29 -78
- package/assembly/deserialize/swar/array/map.ts +1 -2
- package/assembly/deserialize/swar/array/object.ts +12 -4
- package/assembly/deserialize/swar/array/raw.ts +1 -2
- package/assembly/deserialize/swar/array/shared.ts +7 -14
- package/assembly/deserialize/swar/array/string.ts +37 -13
- package/assembly/deserialize/swar/array/struct.ts +109 -21
- package/assembly/deserialize/swar/array.ts +1 -3
- package/assembly/deserialize/swar/float.ts +23 -21
- package/assembly/deserialize/swar/integer.ts +2 -7
- package/assembly/deserialize/swar/string.ts +81 -124
- package/assembly/deserialize/swar/typedarray.ts +4 -4
- package/assembly/index.d.ts +35 -24
- package/assembly/index.ts +1775 -281
- package/assembly/serialize/index/arbitrary.ts +70 -4
- 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/jsonarray.ts +51 -0
- package/assembly/serialize/index/object.ts +42 -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 +87 -34
- package/assembly/serialize/naive/bool.ts +0 -1
- package/assembly/serialize/naive/float.ts +15 -29
- package/assembly/serialize/naive/integer.ts +1 -5
- package/assembly/serialize/naive/map.ts +37 -0
- package/assembly/serialize/naive/raw.ts +1 -2
- package/assembly/serialize/naive/set.ts +0 -5
- package/assembly/serialize/naive/staticarray.ts +0 -6
- package/assembly/serialize/naive/string.ts +1 -10
- package/assembly/serialize/naive/typedarray.ts +0 -7
- 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/eisel-lemire.ts +179 -0
- 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/prettyWhitespaceSimd.ts +69 -0
- package/assembly/util/ptrToStr.ts +1 -2
- package/assembly/util/scanValueEndSimd.ts +83 -22
- 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/assembly/util/validateJson.ts +338 -0
- package/lib/as-bs.ts +51 -51
- package/package.json +25 -8
- package/transform/lib/index.d.ts +6 -0
- package/transform/lib/index.js +874 -171
- package/transform/lib/types.d.ts +3 -1
- package/transform/lib/types.js +6 -0
- package/assembly/deserialize/index/struct.ts +0 -1
- package/assembly/deserialize/naive/struct.ts +0 -21
- package/assembly/util/dragonbox-cache.ts +0 -445
- package/assembly/util/dragonbox.ts +0 -652
|
@@ -12,13 +12,14 @@
|
|
|
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
|
|
|
19
19
|
import { ptrToStr } from "../../util/ptrToStr";
|
|
20
20
|
import { parse4Digits_PairMul } from "../../util/swar-int";
|
|
21
21
|
import { scientific } from "../../util/scientific";
|
|
22
|
+
import { eiselLemire22 } from "../../util/eisel-lemire";
|
|
22
23
|
|
|
23
24
|
export const POW10_F64_POS: usize = memory.data<f64>([
|
|
24
25
|
1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14,
|
|
@@ -34,14 +35,11 @@ const ASCII_DOT: u16 = 46;
|
|
|
34
35
|
const ASCII_ZERO: u16 = 48;
|
|
35
36
|
const ASCII_E_UP: u16 = 69;
|
|
36
37
|
const ASCII_E_LO: u16 = 101;
|
|
37
|
-
|
|
38
|
-
// @ts-ignore: inline
|
|
39
|
-
@inline export function loadPow10(exp: u32): f64 {
|
|
38
|
+
export function loadPow10(exp: u32): f64 {
|
|
40
39
|
return load<f64>(POW10_F64_POS + ((<usize>exp) << 3));
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
@inline function fallback<T>(srcStart: usize, srcEnd: usize): T {
|
|
42
|
+
function fallback<T>(srcStart: usize, srcEnd: usize): T {
|
|
45
43
|
const s = ptrToStr(srcStart, srcEnd);
|
|
46
44
|
// @ts-ignore
|
|
47
45
|
const type: T = 0;
|
|
@@ -50,9 +48,7 @@ const ASCII_E_LO: u16 = 101;
|
|
|
50
48
|
// @ts-ignore
|
|
51
49
|
return <T>(<f32>f32.parse(s));
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
// @ts-ignore: inline
|
|
55
|
-
@inline function fallbackField<T extends number>(
|
|
51
|
+
function fallbackField<T extends number>(
|
|
56
52
|
origStart: usize,
|
|
57
53
|
end: usize,
|
|
58
54
|
fieldPtr: usize,
|
|
@@ -77,37 +73,37 @@ export function deserializeFloat_SWAR<T>(srcStart: usize, srcEnd: usize): T {
|
|
|
77
73
|
// Integer part: scalar. Most JSON integers are 1-3 digits, so parse4 would
|
|
78
74
|
// waste a validate per call.
|
|
79
75
|
let mantissa: u64 = 0;
|
|
80
|
-
|
|
76
|
+
const intStart = p;
|
|
81
77
|
while (p < srcEnd) {
|
|
82
78
|
const d = <u32>load<u16>(p) - ASCII_ZERO;
|
|
83
79
|
if (d > 9) break;
|
|
84
80
|
mantissa = mantissa * 10 + <u64>d;
|
|
85
|
-
intDigits++;
|
|
86
81
|
p += 2;
|
|
87
82
|
}
|
|
83
|
+
const intDigits = <i32>((p - intStart) >> 1);
|
|
88
84
|
|
|
89
85
|
// Fractional part: parse4 stride (8 bytes / 4 digits) → scalar tail.
|
|
90
86
|
// parse8 was tried and benchmarked even/worse; the saved mantissa mul
|
|
91
87
|
// didn't outweigh the extra load and combined-validation latency, and the
|
|
92
88
|
// dependency chain is the same length either way.
|
|
93
|
-
let
|
|
89
|
+
let fracStart = p;
|
|
94
90
|
if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
|
|
95
91
|
p += 2;
|
|
92
|
+
fracStart = p;
|
|
96
93
|
while (p + 6 < srcEnd) {
|
|
97
94
|
const parsed = parse4Digits_PairMul(load<u64>(p));
|
|
98
95
|
if (parsed == U32.MAX_VALUE) break;
|
|
99
96
|
mantissa = mantissa * 10_000 + <u64>parsed;
|
|
100
|
-
fracDigits += 4;
|
|
101
97
|
p += 8;
|
|
102
98
|
}
|
|
103
99
|
while (p < srcEnd) {
|
|
104
100
|
const d = <u32>load<u16>(p) - ASCII_ZERO;
|
|
105
101
|
if (d > 9) break;
|
|
106
102
|
mantissa = mantissa * 10 + <u64>d;
|
|
107
|
-
fracDigits++;
|
|
108
103
|
p += 2;
|
|
109
104
|
}
|
|
110
105
|
}
|
|
106
|
+
const fracDigits = <i32>((p - fracStart) >> 1);
|
|
111
107
|
|
|
112
108
|
const mantDigits = intDigits + fracDigits;
|
|
113
109
|
if (mantDigits == 0) return fallback<T>(origStart, srcEnd);
|
|
@@ -163,7 +159,10 @@ export function deserializeFloat_SWAR<T>(srcStart: usize, srcEnd: usize): T {
|
|
|
163
159
|
result /= loadPow10(<u32>-exponent);
|
|
164
160
|
}
|
|
165
161
|
} else if (mantDigits <= 19) {
|
|
166
|
-
result =
|
|
162
|
+
result =
|
|
163
|
+
exponent >= -22 && exponent <= 22
|
|
164
|
+
? eiselLemire22(mantissa, exponent)
|
|
165
|
+
: scientific(mantissa, exponent);
|
|
167
166
|
} else {
|
|
168
167
|
return fallback<T>(origStart, srcEnd);
|
|
169
168
|
}
|
|
@@ -193,33 +192,33 @@ export function deserializeFloatField_SWAR<T extends number>(
|
|
|
193
192
|
}
|
|
194
193
|
|
|
195
194
|
let mantissa: u64 = 0;
|
|
196
|
-
|
|
195
|
+
const intStart = p;
|
|
197
196
|
while (p < srcEnd) {
|
|
198
197
|
const d = <u32>load<u16>(p) - ASCII_ZERO;
|
|
199
198
|
if (d > 9) break;
|
|
200
199
|
mantissa = mantissa * 10 + <u64>d;
|
|
201
|
-
intDigits++;
|
|
202
200
|
p += 2;
|
|
203
201
|
}
|
|
202
|
+
const intDigits = <i32>((p - intStart) >> 1);
|
|
204
203
|
|
|
205
|
-
let
|
|
204
|
+
let fracStart = p;
|
|
206
205
|
if (p < srcEnd && load<u16>(p) == ASCII_DOT) {
|
|
207
206
|
p += 2;
|
|
207
|
+
fracStart = p;
|
|
208
208
|
while (p + 6 < srcEnd) {
|
|
209
209
|
const parsed = parse4Digits_PairMul(load<u64>(p));
|
|
210
210
|
if (parsed == U32.MAX_VALUE) break;
|
|
211
211
|
mantissa = mantissa * 10_000 + <u64>parsed;
|
|
212
|
-
fracDigits += 4;
|
|
213
212
|
p += 8;
|
|
214
213
|
}
|
|
215
214
|
while (p < srcEnd) {
|
|
216
215
|
const d = <u32>load<u16>(p) - ASCII_ZERO;
|
|
217
216
|
if (d > 9) break;
|
|
218
217
|
mantissa = mantissa * 10 + <u64>d;
|
|
219
|
-
fracDigits++;
|
|
220
218
|
p += 2;
|
|
221
219
|
}
|
|
222
220
|
}
|
|
221
|
+
const fracDigits = <i32>((p - fracStart) >> 1);
|
|
223
222
|
|
|
224
223
|
const mantDigits = intDigits + fracDigits;
|
|
225
224
|
if (mantDigits == 0) unreachable();
|
|
@@ -282,7 +281,10 @@ export function deserializeFloatField_SWAR<T extends number>(
|
|
|
282
281
|
result /= loadPow10(<u32>-exponent);
|
|
283
282
|
}
|
|
284
283
|
} else if (mantDigits <= 19) {
|
|
285
|
-
result =
|
|
284
|
+
result =
|
|
285
|
+
exponent >= -22 && exponent <= 22
|
|
286
|
+
? eiselLemire22(mantissa, exponent)
|
|
287
|
+
: scientific(mantissa, exponent);
|
|
286
288
|
} else {
|
|
287
289
|
fallbackField<T>(origStart, p, fieldPtr);
|
|
288
290
|
return p;
|
|
@@ -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 {
|
|
@@ -4,6 +4,8 @@ import { __heap_base } from "memory";
|
|
|
4
4
|
import { BACK_SLASH, QUOTE } from "../../custom/chars";
|
|
5
5
|
import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
|
|
6
6
|
import { hex4_to_u16_swar } from "../../util/swar";
|
|
7
|
+
import { markProductionParseError } from "../error";
|
|
8
|
+
import { isValidStringEscape } from "../string-validation";
|
|
7
9
|
|
|
8
10
|
// Overflow Pattern for Unicode Escapes (READ)
|
|
9
11
|
// \u0001 0 \u00|01__ + 4
|
|
@@ -41,11 +43,7 @@ import { hex4_to_u16_swar } from "../../util/swar";
|
|
|
41
43
|
* @param dst buffer to write to
|
|
42
44
|
* @returns number of bytes written
|
|
43
45
|
*/
|
|
44
|
-
|
|
45
|
-
@inline function copyStringFromSource(
|
|
46
|
-
srcStart: usize,
|
|
47
|
-
byteLength: usize,
|
|
48
|
-
): string {
|
|
46
|
+
function copyStringFromSource(srcStart: usize, byteLength: usize): string {
|
|
49
47
|
if (byteLength == 0) return changetype<string>("");
|
|
50
48
|
const out = __new(byteLength, idof<string>());
|
|
51
49
|
memory.copy(out, srcStart, byteLength);
|
|
@@ -75,7 +73,7 @@ function deserializeEscapedString_SWAR(
|
|
|
75
73
|
}
|
|
76
74
|
|
|
77
75
|
let srcStart = escapeStart;
|
|
78
|
-
const srcEnd8 = srcEnd
|
|
76
|
+
const srcEnd8 = srcEnd - 8;
|
|
79
77
|
|
|
80
78
|
while (srcStart <= srcEnd8) {
|
|
81
79
|
const block = load<u64>(srcStart);
|
|
@@ -111,6 +109,11 @@ function deserializeEscapedString_SWAR(
|
|
|
111
109
|
const srcIdx = srcStart + laneIdx;
|
|
112
110
|
if ((load<u32>(srcIdx) & 0xffff) !== 0x5c) continue; // false positive
|
|
113
111
|
bs.offset += laneIdx;
|
|
112
|
+
if (!isValidStringEscape(srcIdx, srcEnd)) {
|
|
113
|
+
bs.offset = bs.buffer + outStart;
|
|
114
|
+
markProductionParseError();
|
|
115
|
+
return changetype<string>(0);
|
|
116
|
+
}
|
|
114
117
|
const code = load<u16>(srcIdx, 2);
|
|
115
118
|
if (code !== 0x75) {
|
|
116
119
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
@@ -138,6 +141,11 @@ function deserializeEscapedString_SWAR(
|
|
|
138
141
|
srcStart += 2;
|
|
139
142
|
continue;
|
|
140
143
|
}
|
|
144
|
+
if (!isValidStringEscape(srcStart, srcEnd)) {
|
|
145
|
+
bs.offset = bs.buffer + outStart;
|
|
146
|
+
markProductionParseError();
|
|
147
|
+
return changetype<string>(0);
|
|
148
|
+
}
|
|
141
149
|
const code = load<u16>(srcStart, 2);
|
|
142
150
|
if (code !== 0x75) {
|
|
143
151
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
@@ -245,7 +253,7 @@ function writeStringToField(
|
|
|
245
253
|
// the next unread src pointer.
|
|
246
254
|
//
|
|
247
255
|
// HYBRID strategy (validated against the prior run-copy scanner across escape
|
|
248
|
-
// densities
|
|
256
|
+
// densities - see __benches__/custom/swar-string-deser-hybrid-h2h: +17–70%):
|
|
249
257
|
// * Escape-bearing block: one optimistic whole-block u64 store copies the
|
|
250
258
|
// plain prefix for free, then the (scalar-confirmed) escape is decoded.
|
|
251
259
|
// * Clean block: stream the first one, then if the clean run continues switch
|
|
@@ -260,16 +268,25 @@ function deserializeEscapedStringField_SWAR(
|
|
|
260
268
|
): usize {
|
|
261
269
|
const prefixLen = <u32>(escapeStart - payloadStart);
|
|
262
270
|
bs.offset = bs.buffer;
|
|
263
|
-
|
|
271
|
+
// `srcEnd` may be the end of a containing map/document when a generated
|
|
272
|
+
// parser is cursor-driven. Reserving that entire remainder for one escaped
|
|
273
|
+
// field retained multi-megabyte scratch buffers. Grow with the decoded
|
|
274
|
+
// string instead; escapes only shrink relative to the source.
|
|
275
|
+
bs.ensureSize(prefixLen + 1024);
|
|
264
276
|
if (prefixLen != 0) {
|
|
265
277
|
memory.copy(bs.buffer, payloadStart, prefixLen);
|
|
266
278
|
bs.offset += prefixLen;
|
|
267
279
|
}
|
|
268
280
|
|
|
269
281
|
let srcStart = escapeStart;
|
|
270
|
-
const srcEnd8 = srcEnd
|
|
282
|
+
const srcEnd8 = srcEnd - 8;
|
|
283
|
+
let scratchEnd = bs.buffer + bs.bufferSize - 8;
|
|
271
284
|
|
|
272
285
|
while (srcStart <= srcEnd8) {
|
|
286
|
+
if (bs.offset > scratchEnd) {
|
|
287
|
+
bs.ensureSize(1024);
|
|
288
|
+
scratchEnd = bs.buffer + bs.bufferSize - 8;
|
|
289
|
+
}
|
|
273
290
|
const block = load<u64>(srcStart);
|
|
274
291
|
let mask = backslash_or_quote_mask(block);
|
|
275
292
|
if (mask == 0) {
|
|
@@ -289,6 +306,10 @@ function deserializeEscapedStringField_SWAR(
|
|
|
289
306
|
srcStart += 8;
|
|
290
307
|
}
|
|
291
308
|
const runLen = <u32>(srcStart - runStart);
|
|
309
|
+
if (bs.offset + runLen > scratchEnd) {
|
|
310
|
+
bs.ensureSize(runLen + 1024);
|
|
311
|
+
scratchEnd = bs.buffer + bs.bufferSize - 8;
|
|
312
|
+
}
|
|
292
313
|
memory.copy(bs.offset, runStart, runLen);
|
|
293
314
|
bs.offset += runLen;
|
|
294
315
|
}
|
|
@@ -314,6 +335,11 @@ function deserializeEscapedStringField_SWAR(
|
|
|
314
335
|
bs.offset = bs.buffer;
|
|
315
336
|
return srcIdx + 2;
|
|
316
337
|
}
|
|
338
|
+
if (!isValidStringEscape(srcIdx, srcEnd)) {
|
|
339
|
+
bs.offset = bs.buffer;
|
|
340
|
+
markProductionParseError();
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
317
343
|
const code = load<u16>(srcIdx, 2);
|
|
318
344
|
if (code !== 0x75) {
|
|
319
345
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
@@ -334,6 +360,7 @@ function deserializeEscapedStringField_SWAR(
|
|
|
334
360
|
}
|
|
335
361
|
|
|
336
362
|
// scalar tail (< 8 bytes remaining)
|
|
363
|
+
bs.ensureSize(<u32>(srcEnd - srcStart) + 2);
|
|
337
364
|
while (srcStart < srcEnd) {
|
|
338
365
|
const char = load<u16>(srcStart);
|
|
339
366
|
if (char == QUOTE) {
|
|
@@ -347,6 +374,11 @@ function deserializeEscapedStringField_SWAR(
|
|
|
347
374
|
srcStart += 2;
|
|
348
375
|
continue;
|
|
349
376
|
}
|
|
377
|
+
if (!isValidStringEscape(srcStart, srcEnd)) {
|
|
378
|
+
bs.offset = bs.buffer;
|
|
379
|
+
markProductionParseError();
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
350
382
|
const code = load<u16>(srcStart, 2);
|
|
351
383
|
if (code !== 0x75) {
|
|
352
384
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
@@ -360,122 +392,37 @@ function deserializeEscapedStringField_SWAR(
|
|
|
360
392
|
}
|
|
361
393
|
|
|
362
394
|
bs.offset = bs.buffer;
|
|
363
|
-
|
|
364
|
-
return
|
|
395
|
+
markProductionParseError();
|
|
396
|
+
return 0;
|
|
365
397
|
}
|
|
366
398
|
|
|
367
|
-
function
|
|
368
|
-
lastPtr: usize,
|
|
399
|
+
export function deserializeStringField_SWAR<T extends string | null>(
|
|
369
400
|
srcStart: usize,
|
|
370
401
|
srcEnd: usize,
|
|
371
|
-
|
|
402
|
+
dstObj: usize,
|
|
403
|
+
dstOffset: usize = 0,
|
|
372
404
|
): usize {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const blockStart = srcStart;
|
|
377
|
-
let mask = backslash_or_quote_mask(load<u64>(srcStart));
|
|
378
|
-
if (mask === 0) {
|
|
379
|
-
srcStart += 8;
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
do {
|
|
384
|
-
const laneIdx = usize(ctz(mask) >> 3);
|
|
385
|
-
mask &= mask - 1;
|
|
386
|
-
const srcIdx = srcStart + laneIdx;
|
|
387
|
-
const char = load<u16>(srcIdx);
|
|
388
|
-
if (char == QUOTE) {
|
|
389
|
-
const runLen = <u32>(srcIdx - lastPtr);
|
|
390
|
-
if (runLen != 0) {
|
|
391
|
-
memory.copy(bs.offset, lastPtr, runLen);
|
|
392
|
-
bs.offset += runLen;
|
|
393
|
-
}
|
|
394
|
-
writeStringToField(
|
|
395
|
-
dstFieldPtr,
|
|
396
|
-
bs.buffer,
|
|
397
|
-
<u32>(bs.offset - bs.buffer),
|
|
398
|
-
);
|
|
399
|
-
bs.offset = bs.buffer;
|
|
400
|
-
return srcIdx + 2;
|
|
401
|
-
}
|
|
402
|
-
if (char != BACK_SLASH) continue;
|
|
403
|
-
|
|
404
|
-
const runLen = <u32>(srcIdx - lastPtr);
|
|
405
|
-
if (runLen != 0) {
|
|
406
|
-
memory.copy(bs.offset, lastPtr, runLen);
|
|
407
|
-
bs.offset += runLen;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const chunk = load<u32>(srcIdx);
|
|
411
|
-
const code = <u16>(chunk >> 16);
|
|
412
|
-
if (code !== 0x75) {
|
|
413
|
-
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
414
|
-
bs.offset += 2;
|
|
415
|
-
lastPtr = srcIdx + 4;
|
|
416
|
-
} else {
|
|
417
|
-
store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
|
|
418
|
-
bs.offset += 2;
|
|
419
|
-
lastPtr = srcIdx + 12;
|
|
420
|
-
}
|
|
421
|
-
srcStart = lastPtr;
|
|
422
|
-
break;
|
|
423
|
-
} while (mask !== 0);
|
|
424
|
-
|
|
425
|
-
if (srcStart == blockStart) srcStart += 8;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
while (srcStart < srcEnd) {
|
|
429
|
-
const tailChar = load<u16>(srcStart);
|
|
430
|
-
if (tailChar == QUOTE) {
|
|
431
|
-
const runLen = <u32>(srcStart - lastPtr);
|
|
432
|
-
if (runLen != 0) {
|
|
433
|
-
memory.copy(bs.offset, lastPtr, runLen);
|
|
434
|
-
bs.offset += runLen;
|
|
435
|
-
}
|
|
436
|
-
writeStringToField(dstFieldPtr, bs.buffer, <u32>(bs.offset - bs.buffer));
|
|
437
|
-
bs.offset = bs.buffer;
|
|
438
|
-
return srcStart + 2;
|
|
439
|
-
}
|
|
440
|
-
if (tailChar != BACK_SLASH) {
|
|
441
|
-
srcStart += 2;
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const runLen = <u32>(srcStart - lastPtr);
|
|
446
|
-
if (runLen != 0) {
|
|
447
|
-
memory.copy(bs.offset, lastPtr, runLen);
|
|
448
|
-
bs.offset += runLen;
|
|
449
|
-
}
|
|
450
|
-
const tailCode = load<u16>(srcStart, 2);
|
|
451
|
-
if (tailCode !== 0x75) {
|
|
452
|
-
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + tailCode));
|
|
453
|
-
bs.offset += 2;
|
|
454
|
-
srcStart += 4;
|
|
455
|
-
} else {
|
|
456
|
-
store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
|
|
457
|
-
bs.offset += 2;
|
|
458
|
-
srcStart += 12;
|
|
459
|
-
}
|
|
460
|
-
lastPtr = srcStart;
|
|
405
|
+
if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE) {
|
|
406
|
+
markProductionParseError();
|
|
407
|
+
return 0;
|
|
461
408
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
409
|
+
return deserializeStringFieldTrusted_SWAR(
|
|
410
|
+
srcStart + 2,
|
|
411
|
+
srcEnd,
|
|
412
|
+
dstObj,
|
|
413
|
+
dstOffset,
|
|
414
|
+
);
|
|
465
415
|
}
|
|
466
416
|
|
|
467
|
-
export function
|
|
468
|
-
|
|
417
|
+
export function deserializeStringFieldTrusted_SWAR(
|
|
418
|
+
payloadStart: usize,
|
|
469
419
|
srcEnd: usize,
|
|
470
420
|
dstObj: usize,
|
|
471
421
|
dstOffset: usize = 0,
|
|
472
422
|
): usize {
|
|
473
423
|
const dstFieldPtr = dstObj + dstOffset;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const payloadStart = srcStart + 2;
|
|
478
|
-
srcStart = payloadStart;
|
|
424
|
+
let srcStart = payloadStart;
|
|
425
|
+
let pendingMask: u64 = 0;
|
|
479
426
|
|
|
480
427
|
// Wide pre-scan: skip 16 bytes per iter while both halves are clean. The
|
|
481
428
|
// common case (plain ASCII payloads, no escape) hits this loop exclusively
|
|
@@ -483,16 +430,29 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
483
430
|
if (srcEnd >= 16) {
|
|
484
431
|
const srcEnd16 = srcEnd - 16;
|
|
485
432
|
while (srcStart <= srcEnd16) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
433
|
+
// Test the first word before loading the second: short values and keys
|
|
434
|
+
// close (or escape) within the first 8 bytes, so this skips the second
|
|
435
|
+
// load on the common case while still skipping 16 bytes when both clean.
|
|
436
|
+
const firstMask = backslash_or_quote_mask(load<u64>(srcStart));
|
|
437
|
+
if (firstMask != 0) {
|
|
438
|
+
pendingMask = firstMask;
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
const secondMask = backslash_or_quote_mask(load<u64>(srcStart, 8));
|
|
442
|
+
if (secondMask != 0) {
|
|
443
|
+
srcStart += 8;
|
|
444
|
+
pendingMask = secondMask;
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
489
447
|
srcStart += 16;
|
|
490
448
|
}
|
|
491
449
|
}
|
|
492
450
|
|
|
493
451
|
const srcEnd8 = srcEnd - 8;
|
|
494
452
|
while (srcStart <= srcEnd8) {
|
|
495
|
-
let mask =
|
|
453
|
+
let mask = pendingMask;
|
|
454
|
+
if (mask == 0) mask = backslash_or_quote_mask(load<u64>(srcStart));
|
|
455
|
+
pendingMask = 0;
|
|
496
456
|
if (mask === 0) {
|
|
497
457
|
srcStart += 8;
|
|
498
458
|
continue;
|
|
@@ -544,8 +504,8 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
544
504
|
srcStart += 2;
|
|
545
505
|
}
|
|
546
506
|
|
|
547
|
-
|
|
548
|
-
return
|
|
507
|
+
markProductionParseError();
|
|
508
|
+
return 0;
|
|
549
509
|
}
|
|
550
510
|
|
|
551
511
|
/**
|
|
@@ -555,8 +515,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
555
515
|
* so callers must confirm the hit scalarly.
|
|
556
516
|
* Each matching lane sets itself to 0x80.
|
|
557
517
|
*/
|
|
558
|
-
|
|
559
|
-
@inline function backslash_or_quote_mask(block: u64): u64 {
|
|
518
|
+
function backslash_or_quote_mask(block: u64): u64 {
|
|
560
519
|
const b = block ^ 0x005c_005c_005c_005c;
|
|
561
520
|
const q = block ^ 0x0022_0022_0022_0022;
|
|
562
521
|
return (
|
|
@@ -575,8 +534,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
575
534
|
*
|
|
576
535
|
* Each matching lane sets itself to 0x80.
|
|
577
536
|
*/
|
|
578
|
-
|
|
579
|
-
@inline function backslash_mask(block: u64): u64 {
|
|
537
|
+
function backslash_mask(block: u64): u64 {
|
|
580
538
|
const b = block ^ 0x005c_005c_005c_005c;
|
|
581
539
|
const backslash_mask =
|
|
582
540
|
(b - 0x0001_0001_0001_0001) & ~b & 0x0080_0080_0080_0080;
|
|
@@ -597,8 +555,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
597
555
|
* WARNING: The low byte of a code unit *may* be a backslash, thus triggering false positives!
|
|
598
556
|
* This is useful for a hot path where it is possible to detect the false positive scalarly.
|
|
599
557
|
*/
|
|
600
|
-
|
|
601
|
-
@inline function backslash_mask_unsafe(block: u64): u64 {
|
|
558
|
+
function backslash_mask_unsafe(block: u64): u64 {
|
|
602
559
|
const b = block ^ 0x005c_005c_005c_005c;
|
|
603
560
|
const backslash_mask =
|
|
604
561
|
(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;
|
package/assembly/index.d.ts
CHANGED
|
@@ -8,12 +8,12 @@ declare class JSONConfig {
|
|
|
8
8
|
* access; untouched fields pass their original bytes straight through on
|
|
9
9
|
* serialize.
|
|
10
10
|
*
|
|
11
|
-
* - `"none"` *(default)*
|
|
12
|
-
* - `"auto"`
|
|
11
|
+
* - `"none"` *(default)* - every field is parsed eagerly, up-front.
|
|
12
|
+
* - `"auto"` - the transform defers fields whose estimated parse cost is high
|
|
13
13
|
* (nested structs, arrays, maps, long strings) and keeps cheap fields
|
|
14
14
|
* (primitives, enums, `Date`) eager. Use `@eager` to force a field back to
|
|
15
15
|
* eager, or `@lazy` to force one on.
|
|
16
|
-
* - `"all"`
|
|
16
|
+
* - `"all"` - every field is deferred. Best for proxy / filter / forward
|
|
17
17
|
* workloads over large payloads; note it generates a getter and a serialize
|
|
18
18
|
* branch per field, so module size grows with very wide schemas.
|
|
19
19
|
*
|
|
@@ -50,9 +50,9 @@ declare class JSONConfig {
|
|
|
50
50
|
* JSON.parse<Vec3>('{"x":1,"y":2,"z":3}');
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
53
|
-
// @ts-ignore: type
|
|
54
53
|
declare function json(config?: JSONConfig): Function;
|
|
55
|
-
|
|
54
|
+
// @ts-expect-error: type
|
|
55
|
+
declare function json(..._): void;
|
|
56
56
|
/**
|
|
57
57
|
* Alias for {@link json}. `@serializable` and `@json` are interchangeable.
|
|
58
58
|
*/
|
|
@@ -96,7 +96,7 @@ declare function omit(..._): void;
|
|
|
96
96
|
* The field is still parsed normally; the condition only affects serialization.
|
|
97
97
|
*
|
|
98
98
|
* @param condition - A predicate that receives the **instance** and returns
|
|
99
|
-
* `true` to omit the field
|
|
99
|
+
* `true` to omit the field - `(self: T) => boolean` - or a string expression
|
|
100
100
|
* evaluated in the instance's scope (reference fields via `this`).
|
|
101
101
|
*
|
|
102
102
|
* @example
|
|
@@ -124,13 +124,31 @@ declare function omitif(condition: string | ((self: any) => boolean)): Function;
|
|
|
124
124
|
* ```
|
|
125
125
|
*/
|
|
126
126
|
// @ts-ignore: type
|
|
127
|
-
declare function omitnull(..._):
|
|
127
|
+
declare function omitnull(..._): void;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Field decorator that marks a property as optional for deserialization: the
|
|
131
|
+
* key may be absent from (or appear anywhere in) the input, and the field keeps
|
|
132
|
+
* its default. Unlike `@omitnull`/`@omitif` it does NOT omit the field on
|
|
133
|
+
* serialize and has no nullability requirement - it only opts the field into
|
|
134
|
+
* the order-tolerant fast path.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* @json
|
|
139
|
+
* class Tweet {
|
|
140
|
+
* @optional retweeted_status: Retweet | null = null; // key may be absent
|
|
141
|
+
* }
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
// @ts-ignore: type
|
|
145
|
+
declare function optional(..._): void;
|
|
128
146
|
|
|
129
147
|
/**
|
|
130
148
|
* Field decorator that defers parsing of a property until it is first read
|
|
131
149
|
* (on-demand / lazy parsing). The raw JSON slice is stored at parse time and
|
|
132
150
|
* materialized into the field's type on first access, then cached; an untouched
|
|
133
|
-
* field round-trips by copying its original bytes
|
|
151
|
+
* field round-trips by copying its original bytes - never re-parsed or
|
|
134
152
|
* re-serialized.
|
|
135
153
|
*
|
|
136
154
|
* Equivalent to the `JSON.Lazy<T>` type-wrapper form. Pays off for fields you
|
|
@@ -170,7 +188,7 @@ declare function eager(..._): void;
|
|
|
170
188
|
* the generated serialization. The method receives the instance and must return
|
|
171
189
|
* a **valid JSON string**. Pair with {@link deserializer}.
|
|
172
190
|
*
|
|
173
|
-
* @param shape - Optional JSON value shape the output conforms to
|
|
191
|
+
* @param shape - Optional JSON value shape the output conforms to - one of
|
|
174
192
|
* `"any"` (default), `"string"`, `"number"`, `"object"`, `"array"`,
|
|
175
193
|
* `"boolean"`, or `"null"`.
|
|
176
194
|
*
|
|
@@ -212,11 +230,11 @@ declare function serializer(
|
|
|
212
230
|
/**
|
|
213
231
|
* Method decorator marking a member as the class's custom deserializer,
|
|
214
232
|
* replacing the generated deserialization. The method receives the raw JSON
|
|
215
|
-
* string and must return a **new** instance
|
|
233
|
+
* string and must return a **new** instance - never assume an existing
|
|
216
234
|
* destination is reused. Pair with {@link serializer} (see it for a full,
|
|
217
235
|
* round-tripping example).
|
|
218
236
|
*
|
|
219
|
-
* @param shape - Optional JSON value shape the input conforms to
|
|
237
|
+
* @param shape - Optional JSON value shape the input conforms to - one of
|
|
220
238
|
* `"any"` (default), `"string"`, `"number"`, `"object"`, `"array"`,
|
|
221
239
|
* `"boolean"`, or `"null"`.
|
|
222
240
|
*
|
|
@@ -235,19 +253,6 @@ declare function deserializer(
|
|
|
235
253
|
shape?: "any" | "string" | "number" | "object" | "array" | "boolean" | "null",
|
|
236
254
|
): any;
|
|
237
255
|
|
|
238
|
-
/**
|
|
239
|
-
* Parsing/serialization strategy selected at build time via the `JSON_MODE`
|
|
240
|
-
* environment variable and exposed as {@link JSON_MODE}.
|
|
241
|
-
*/
|
|
242
|
-
declare const enum JSONMode {
|
|
243
|
-
/** Scalar/word-at-a-time (SWAR) scanning. The default; no extra flags. */
|
|
244
|
-
SWAR = 0,
|
|
245
|
-
/** 128-bit SIMD scanning. Fastest on larger payloads; needs `--enable simd`. */
|
|
246
|
-
SIMD = 1,
|
|
247
|
-
/** Straightforward byte-at-a-time scanning. Smallest code, slowest. */
|
|
248
|
-
NAIVE = 2,
|
|
249
|
-
}
|
|
250
|
-
|
|
251
256
|
/**
|
|
252
257
|
* The active {@link JSONMode}, injected by the transform from the `JSON_MODE`
|
|
253
258
|
* build-time environment variable (default `SWAR`). Set it on the `asc`
|
|
@@ -262,6 +267,12 @@ declare const enum JSONMode {
|
|
|
262
267
|
*/
|
|
263
268
|
declare const JSON_MODE: JSONMode;
|
|
264
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Whether strict RFC 8259 validation is enabled. Injected by the transform from
|
|
272
|
+
* the `JSON_STRICT` build-time environment variable (default `false`).
|
|
273
|
+
*/
|
|
274
|
+
declare const JSON_STRICT: bool;
|
|
275
|
+
|
|
265
276
|
/**
|
|
266
277
|
* Whether the string cache is enabled (default off). Injected from the
|
|
267
278
|
* `JSON_CACHE` build-time environment variable. When on, repeated strings are
|