json-as 1.5.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 +5 -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 +13 -3
- package/transform/lib/index.d.ts +6 -0
- package/transform/lib/index.js +770 -111
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
export { serializeBool } from "../naive/bool";
|
|
1
|
+
export { serializeBool, serializeBoolUnsafe } from "../naive/bool";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { serializeInteger } from "../naive/integer";
|
|
1
|
+
export { serializeInteger, serializeIntegerUnsafe } from "../naive/integer";
|
|
@@ -5,6 +5,23 @@ import { serializeArbitrary } from "./arbitrary";
|
|
|
5
5
|
import { serializeStringRange } from "../naive/string";
|
|
6
6
|
|
|
7
7
|
export function serializeObject(src: JSON.Obj): void {
|
|
8
|
+
const rawStart = src._rawStart;
|
|
9
|
+
if (rawStart != 0) {
|
|
10
|
+
const rawSize = src._rawEnd - rawStart;
|
|
11
|
+
// At top level, `bs.out` can copy the anchored source slice straight into
|
|
12
|
+
// the returned string. Nested objects already have parent bytes in the
|
|
13
|
+
// staging buffer and therefore keep the regular append path below.
|
|
14
|
+
if (bs.offset == bs.buffer && bs.cacheOutput == 0) {
|
|
15
|
+
bs.cacheOutput = rawStart;
|
|
16
|
+
bs.cacheOutputLen = rawSize;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
bs.proposeSize(rawSize);
|
|
20
|
+
memory.copy(bs.offset, rawStart, rawSize);
|
|
21
|
+
bs.offset += rawSize;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
const srcSize = src.size;
|
|
9
26
|
|
|
10
27
|
if (srcSize == 0) {
|
|
@@ -5,6 +5,7 @@ import { serializeBoolUnsafe } from "./bool";
|
|
|
5
5
|
import { serializeFloat32Unsafe, serializeFloat64Unsafe } from "./float";
|
|
6
6
|
import { serializeIntegerUnsafe } from "./integer";
|
|
7
7
|
import { serializeString } from "../index/string";
|
|
8
|
+
import { ensureItoaPairs, itoaFast } from "../../util/itoa-fast";
|
|
8
9
|
import { dtoa_buffered, ftoa_buffered } from "xjb-as";
|
|
9
10
|
|
|
10
11
|
function maxIntegerBytes<T extends number>(): u32 {
|
|
@@ -265,6 +266,59 @@ function serializeI8ArrayFast(src: i8[]): void {
|
|
|
265
266
|
store<u16>(bs.offset - 2, BRACKET_RIGHT);
|
|
266
267
|
}
|
|
267
268
|
|
|
269
|
+
// Integer arrays wider than one byte use the same uniform trailing-comma
|
|
270
|
+
// layout as the specialized float paths. Keeping the output cursor local
|
|
271
|
+
// avoids two global `bs.offset` accesses per value, while reading dataStart
|
|
272
|
+
// directly removes the Array.__uget call left in the generic array loop.
|
|
273
|
+
function serializeIntegerArrayFast<T extends number[]>(src: T): void {
|
|
274
|
+
const len = src.length;
|
|
275
|
+
bs.proposeSize(4 + <u32>len * (maxIntegerBytes<valueof<T>>() + 2));
|
|
276
|
+
store<u16>(bs.offset, BRACKET_LEFT);
|
|
277
|
+
bs.offset += 2;
|
|
278
|
+
if (len == 0) {
|
|
279
|
+
store<u16>(bs.offset, BRACKET_RIGHT);
|
|
280
|
+
bs.offset += 2;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
ensureItoaPairs();
|
|
285
|
+
const dataStart = src.dataStart;
|
|
286
|
+
const stride = sizeof<valueof<T>>();
|
|
287
|
+
let offset = bs.offset;
|
|
288
|
+
for (let i: i32 = 0; i < len; i++) {
|
|
289
|
+
const value = load<valueof<T>>(dataStart + <usize>i * stride);
|
|
290
|
+
offset += (<usize>itoaFast<valueof<T>>(offset, value)) << 1;
|
|
291
|
+
store<u16>(offset, COMMA);
|
|
292
|
+
offset += 2;
|
|
293
|
+
}
|
|
294
|
+
store<u16>(offset - 2, BRACKET_RIGHT);
|
|
295
|
+
bs.offset = offset;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function serializeStringArrayFast(src: string[]): void {
|
|
299
|
+
const len = src.length;
|
|
300
|
+
// String serialization reserves its own payload; reserve delimiters here.
|
|
301
|
+
bs.proposeSize(4 + (len > 0 ? <u32>(len - 1) * 2 : 0));
|
|
302
|
+
store<u16>(bs.offset, BRACKET_LEFT);
|
|
303
|
+
bs.offset += 2;
|
|
304
|
+
if (len == 0) {
|
|
305
|
+
store<u16>(bs.offset, BRACKET_RIGHT);
|
|
306
|
+
bs.offset += 2;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const dataStart = src.dataStart;
|
|
311
|
+
for (let i: i32 = 0; i < len; i++) {
|
|
312
|
+
const value = changetype<string>(
|
|
313
|
+
load<usize>(dataStart + ((<usize>i) << 2)),
|
|
314
|
+
);
|
|
315
|
+
serializeString(value);
|
|
316
|
+
store<u16>(bs.offset, COMMA);
|
|
317
|
+
bs.offset += 2;
|
|
318
|
+
}
|
|
319
|
+
store<u16>(bs.offset - 2, BRACKET_RIGHT);
|
|
320
|
+
}
|
|
321
|
+
|
|
268
322
|
export function serializeArray<T extends any[]>(src: T): void {
|
|
269
323
|
// Specialized fast paths fold the per-element comma into the element write,
|
|
270
324
|
// saving one `store<u16>` + advance per iteration. AS folds the type checks
|
|
@@ -292,6 +346,16 @@ export function serializeArray<T extends any[]>(src: T): void {
|
|
|
292
346
|
serializeI8ArrayFast(changetype<i8[]>(src));
|
|
293
347
|
return;
|
|
294
348
|
}
|
|
349
|
+
if (isInteger<valueof<T>>()) {
|
|
350
|
+
// u8[] and i8[] returned through their lookup-table paths above.
|
|
351
|
+
serializeIntegerArrayFast<T>(src);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (isString<valueof<T>>()) {
|
|
355
|
+
// @ts-expect-error: T is string[]
|
|
356
|
+
serializeStringArrayFast(changetype<string[]>(src));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
295
359
|
if (isFloat<valueof<T>>() && sizeof<valueof<T>>() == 8) {
|
|
296
360
|
// @ts-expect-error: T is f64[]
|
|
297
361
|
serializeF64ArrayFast(changetype<f64[]>(src));
|
|
@@ -27,8 +27,3 @@ export function serializeFloat64(data: f64): void {
|
|
|
27
27
|
bs.stackSize += bytes;
|
|
28
28
|
bs.offset += bytes;
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
export function serializeFloat<T extends number>(data: T): void {
|
|
32
|
-
if (sizeof<T>() == 4) serializeFloat32(<f32>data);
|
|
33
|
-
else serializeFloat64(<f64>data);
|
|
34
|
-
}
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { bs } from "../../../lib/as-bs";
|
|
2
2
|
import { JSON } from "../..";
|
|
3
3
|
import { BRACE_LEFT, BRACE_RIGHT, COLON, COMMA } from "../../custom/chars";
|
|
4
|
+
import { serializeString } from "../index/string";
|
|
5
|
+
import { serializeRaw } from "./raw";
|
|
6
|
+
|
|
7
|
+
function serializeRawMapFast<T extends Map<any, any>>(
|
|
8
|
+
keys: Array<indexof<T>>,
|
|
9
|
+
values: Array<valueof<T>>,
|
|
10
|
+
): void {
|
|
11
|
+
const len = keys.length;
|
|
12
|
+
bs.proposeSize(4 + <u32>len * 4);
|
|
13
|
+
store<u16>(bs.offset, BRACE_LEFT);
|
|
14
|
+
bs.offset += 2;
|
|
15
|
+
|
|
16
|
+
const keyData = keys.dataStart;
|
|
17
|
+
const valueData = values.dataStart;
|
|
18
|
+
for (let i = 0; i < len; i++) {
|
|
19
|
+
const key = changetype<string>(load<usize>(keyData + ((<usize>i) << 2)));
|
|
20
|
+
const value = changetype<JSON.Raw>(
|
|
21
|
+
load<usize>(valueData + ((<usize>i) << 2)),
|
|
22
|
+
);
|
|
23
|
+
serializeString(key);
|
|
24
|
+
store<u16>(bs.offset, COLON);
|
|
25
|
+
bs.offset += 2;
|
|
26
|
+
serializeRaw(value);
|
|
27
|
+
store<u16>(bs.offset, COMMA);
|
|
28
|
+
bs.offset += 2;
|
|
29
|
+
}
|
|
30
|
+
store<u16>(bs.offset - 2, BRACE_RIGHT);
|
|
31
|
+
}
|
|
4
32
|
|
|
5
33
|
export function serializeMap<T extends Map<any, any>>(src: T): void {
|
|
6
34
|
const srcSize = src.size;
|
|
@@ -17,6 +45,15 @@ export function serializeMap<T extends Map<any, any>>(src: T): void {
|
|
|
17
45
|
let values = src.values();
|
|
18
46
|
const keyIsString = isString<indexof<T>>();
|
|
19
47
|
|
|
48
|
+
if (keyIsString && isReference<valueof<T>>()) {
|
|
49
|
+
const valueType = changetype<nonnull<valueof<T>>>(0);
|
|
50
|
+
// @ts-ignore: instanceof on the reference value type
|
|
51
|
+
if (valueType instanceof JSON.Raw) {
|
|
52
|
+
serializeRawMapFast<T>(keys, values);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
20
57
|
bs.proposeSize(4 + <u32>(srcSize - 1) * 2 + <u32>srcSize * 2);
|
|
21
58
|
|
|
22
59
|
store<u16>(bs.offset, BRACE_LEFT);
|
|
@@ -13,7 +13,6 @@ function maxIntegerBytes<T extends number>(): u32 {
|
|
|
13
13
|
return isSigned<T>() ? 42 : 40;
|
|
14
14
|
}
|
|
15
15
|
function reservePrimitiveStaticArray<T>(len: i32): void {
|
|
16
|
-
if (len <= 0) return;
|
|
17
16
|
if (isBoolean<T>()) {
|
|
18
17
|
bs.proposeSize(4 + <u32>len * 12);
|
|
19
18
|
} else if (isInteger<T>()) {
|
|
@@ -85,9 +85,3 @@ export function serializeStringRange(srcPtr: usize, srcSize: usize): void {
|
|
|
85
85
|
store<u16>(bs.offset, QUOTE);
|
|
86
86
|
bs.offset += 2;
|
|
87
87
|
}
|
|
88
|
-
function write_u_escape(code: u16): void {
|
|
89
|
-
bs.growSize(10);
|
|
90
|
-
store<u32>(bs.offset, U_MARKER); // "\u"
|
|
91
|
-
store<u64>(bs.offset, u16_to_hex4_swar(code), 4);
|
|
92
|
-
bs.offset += 12;
|
|
93
|
-
}
|
|
@@ -11,7 +11,6 @@ function maxIntegerBytes<T extends number>(): u32 {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function reserveTypedArray<T extends ArrayLike<number>>(len: i32): void {
|
|
14
|
-
if (len <= 0) return;
|
|
15
14
|
if (isFloat<valueof<T>>()) {
|
|
16
15
|
bs.proposeSize(4 + <u32>len * (sizeof<valueof<T>>() == 4 ? 34 : 66));
|
|
17
16
|
} else {
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Fast, correctly-rounded conversion of `mantissa * 10^power` for the common
|
|
2
|
+
// decimal-exponent range [-22, 22]. This is the Eisel-Lemire conversion used by
|
|
3
|
+
// simdjson's number parser, specialized to the range where json-as previously
|
|
4
|
+
// fell into `scientific()` after the exact-Clinger path rejected mantissas above
|
|
5
|
+
// 2^53. Keeping only 45 cached powers costs 720 bytes instead of simdjson's
|
|
6
|
+
// general 10 KiB table; callers retain `scientific()` as the wide-range fallback.
|
|
7
|
+
|
|
8
|
+
// Consecutive {primary, secondary} truncated 128-bit powers of five for decimal
|
|
9
|
+
// exponents -22 through 22. Sourced from simdjson's power_of_five_128 table.
|
|
10
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
11
|
+
@lazy const POWERS_OF_FIVE_128: usize = memory.data<u64>([
|
|
12
|
+
0xf1c90080baf72cb1, 0x5324c68b12dd6800, 0x971da05074da7bee,
|
|
13
|
+
0xd3f6fc16ebca8000, 0xbce5086492111aea, 0x88f4bb1ca6bd0000,
|
|
14
|
+
0xec1e4a7db69561a5, 0x2b31e9e3d0700000, 0x9392ee8e921d5d07,
|
|
15
|
+
0x3aff322e62600000, 0xb877aa3236a4b449, 0x09befeb9fad487c3,
|
|
16
|
+
0xe69594bec44de15b, 0x4c2ebe687989a9b4, 0x901d7cf73ab0acd9,
|
|
17
|
+
0x0f9d37014bf60a11, 0xb424dc35095cd80f, 0x538484c19ef38c95,
|
|
18
|
+
0xe12e13424bb40e13, 0x2865a5f206b06fba, 0x8cbccc096f5088cb,
|
|
19
|
+
0xf93f87b7442e45d4, 0xafebff0bcb24aafe, 0xf78f69a51539d749,
|
|
20
|
+
0xdbe6fecebdedd5be, 0xb573440e5a884d1c, 0x89705f4136b4a597,
|
|
21
|
+
0x31680a88f8953031, 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e,
|
|
22
|
+
0xd6bf94d5e57a42bc, 0x3d32907604691b4d, 0x8637bd05af6c69b5,
|
|
23
|
+
0xa63f9a49c2c1b110, 0xa7c5ac471b478423, 0x0fcf80dc33721d54,
|
|
24
|
+
0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x83126e978d4fdf3b,
|
|
25
|
+
0x645a1cac083126ea, 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4,
|
|
26
|
+
0xcccccccccccccccc, 0xcccccccccccccccd, 0x8000000000000000,
|
|
27
|
+
0x0000000000000000, 0xa000000000000000, 0x0000000000000000,
|
|
28
|
+
0xc800000000000000, 0x0000000000000000, 0xfa00000000000000,
|
|
29
|
+
0x0000000000000000, 0x9c40000000000000, 0x0000000000000000,
|
|
30
|
+
0xc350000000000000, 0x0000000000000000, 0xf424000000000000,
|
|
31
|
+
0x0000000000000000, 0x9896800000000000, 0x0000000000000000,
|
|
32
|
+
0xbebc200000000000, 0x0000000000000000, 0xee6b280000000000,
|
|
33
|
+
0x0000000000000000, 0x9502f90000000000, 0x0000000000000000,
|
|
34
|
+
0xba43b74000000000, 0x0000000000000000, 0xe8d4a51000000000,
|
|
35
|
+
0x0000000000000000, 0x9184e72a00000000, 0x0000000000000000,
|
|
36
|
+
0xb5e620f480000000, 0x0000000000000000, 0xe35fa931a0000000,
|
|
37
|
+
0x0000000000000000, 0x8e1bc9bf04000000, 0x0000000000000000,
|
|
38
|
+
0xb1a2bc2ec5000000, 0x0000000000000000, 0xde0b6b3a76400000,
|
|
39
|
+
0x0000000000000000, 0x8ac7230489e80000, 0x0000000000000000,
|
|
40
|
+
0xad78ebc5ac620000, 0x0000000000000000, 0xd8d726b7177a8000,
|
|
41
|
+
0x0000000000000000, 0x878678326eac9000, 0x0000000000000000,
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// High half of a full 64x64 -> 128 multiplication, using four 32-bit partial
|
|
45
|
+
// products. WebAssembly MVP has no i128 or mul-high instruction.
|
|
46
|
+
function mul64High(a: u64, b: u64): u64 {
|
|
47
|
+
const aLo = <u64>(<u32>a);
|
|
48
|
+
const aHi = a >> 32;
|
|
49
|
+
const bLo = <u64>(<u32>b);
|
|
50
|
+
const bHi = b >> 32;
|
|
51
|
+
const lowLow = aLo * bLo;
|
|
52
|
+
const highLow = aHi * bLo;
|
|
53
|
+
const middle = highLow + aLo * bHi;
|
|
54
|
+
const middleCarry = <u64>(middle < highLow);
|
|
55
|
+
const low = lowLow + (middle << 32);
|
|
56
|
+
return aHi * bHi + (middle >> 32) + (middleCarry << 32) + <u64>(low < lowLow);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Fixed-power Eisel-Lemire conversion for GeoJSON-style coordinates with 14
|
|
61
|
+
* fractional digits. This is the `power == -14` specialization of
|
|
62
|
+
* {@link eiselLemire22}: the cached-power address and binary-exponent base are
|
|
63
|
+
* constants, and the exact-halfway correction is unreachable for this power.
|
|
64
|
+
*/
|
|
65
|
+
export function eiselLemireMinus14(significand: u64): f64 {
|
|
66
|
+
let i = significand;
|
|
67
|
+
let leading = <i32>clz(i);
|
|
68
|
+
i <<= leading;
|
|
69
|
+
|
|
70
|
+
let lower = i * 0xb424dc35095cd80f;
|
|
71
|
+
let upper = mul64High(i, 0xb424dc35095cd80f);
|
|
72
|
+
|
|
73
|
+
if ((upper & 0x1ff) == 0x1ff) {
|
|
74
|
+
const secondUpper = mul64High(i, 0x538484c19ef38c95);
|
|
75
|
+
const oldLower = lower;
|
|
76
|
+
lower += secondUpper;
|
|
77
|
+
if (lower < oldLower) upper++;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const upperBit = upper >> 63;
|
|
81
|
+
let mantissa = upper >> (<i32>(upperBit + 9));
|
|
82
|
+
leading += <i32>(1 ^ upperBit);
|
|
83
|
+
let realExponent = 1040 - leading;
|
|
84
|
+
|
|
85
|
+
mantissa += mantissa & 1;
|
|
86
|
+
mantissa >>= 1;
|
|
87
|
+
|
|
88
|
+
if (mantissa >= (<u64>1) << 53) {
|
|
89
|
+
mantissa = (<u64>1) << 52;
|
|
90
|
+
realExponent++;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const bits = (mantissa & ~((<u64>1) << 52)) | ((<u64>realExponent) << 52);
|
|
94
|
+
return reinterpret<f64>(bits);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Fixed -14 conversion for significands in [2^53, 2^54). */
|
|
98
|
+
export function eiselLemireMinus14_54Bit(significand: u64): f64 {
|
|
99
|
+
const i = significand << 10;
|
|
100
|
+
|
|
101
|
+
let lower = i * 0xb424dc35095cd80f;
|
|
102
|
+
let upper = mul64High(i, 0xb424dc35095cd80f);
|
|
103
|
+
|
|
104
|
+
if ((upper & 0x1ff) == 0x1ff) {
|
|
105
|
+
const secondUpper = mul64High(i, 0x538484c19ef38c95);
|
|
106
|
+
const oldLower = lower;
|
|
107
|
+
lower += secondUpper;
|
|
108
|
+
if (lower < oldLower) upper++;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const upperBit = upper >> 63;
|
|
112
|
+
let mantissa = upper >> (<i32>(upperBit + 9));
|
|
113
|
+
let realExponent = 1030 - <i32>(1 ^ upperBit);
|
|
114
|
+
|
|
115
|
+
mantissa += mantissa & 1;
|
|
116
|
+
mantissa >>= 1;
|
|
117
|
+
|
|
118
|
+
if (mantissa >= (<u64>1) << 53) {
|
|
119
|
+
mantissa = (<u64>1) << 52;
|
|
120
|
+
realExponent++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const bits = (mantissa & ~((<u64>1) << 52)) | ((<u64>realExponent) << 52);
|
|
124
|
+
return reinterpret<f64>(bits);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Convert a non-zero u64 significand at a decimal exponent in [-22, 22].
|
|
129
|
+
* The result is correctly rounded to IEEE-754 binary64.
|
|
130
|
+
*/
|
|
131
|
+
export function eiselLemire22(significand: u64, power: i32): f64 {
|
|
132
|
+
let i = significand;
|
|
133
|
+
let leading = <i32>clz(i);
|
|
134
|
+
i <<= leading;
|
|
135
|
+
|
|
136
|
+
const tableIndex = (<usize>(power + 22)) << 4;
|
|
137
|
+
const factor = load<u64>(POWERS_OF_FIVE_128 + tableIndex);
|
|
138
|
+
let lower = i * factor;
|
|
139
|
+
let upper = mul64High(i, factor);
|
|
140
|
+
|
|
141
|
+
// A primary product ending in nine uncertain bits needs the second table
|
|
142
|
+
// limb. This is rare, but removes Eisel-Lemire's historical fallback.
|
|
143
|
+
if ((upper & 0x1ff) == 0x1ff) {
|
|
144
|
+
const secondUpper = mul64High(
|
|
145
|
+
i,
|
|
146
|
+
load<u64>(POWERS_OF_FIVE_128 + tableIndex, 8),
|
|
147
|
+
);
|
|
148
|
+
const oldLower = lower;
|
|
149
|
+
lower += secondUpper;
|
|
150
|
+
if (lower < oldLower) upper++;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const upperBit = upper >> 63;
|
|
154
|
+
let mantissa = upper >> (<i32>(upperBit + 9));
|
|
155
|
+
leading += <i32>(1 ^ upperBit);
|
|
156
|
+
let realExponent = <i32>(((<i64>217706 * power) >> 16) + 1087) - leading;
|
|
157
|
+
const halfwayShift = <i32>(upperBit + 9);
|
|
158
|
+
|
|
159
|
+
// Round exact halfway cases to even.
|
|
160
|
+
if (
|
|
161
|
+
lower <= 1 &&
|
|
162
|
+
power >= -4 &&
|
|
163
|
+
power <= 22 &&
|
|
164
|
+
(mantissa & 3) == 1 &&
|
|
165
|
+
mantissa << halfwayShift == upper
|
|
166
|
+
) {
|
|
167
|
+
mantissa &= ~(<u64>1);
|
|
168
|
+
}
|
|
169
|
+
mantissa += mantissa & 1;
|
|
170
|
+
mantissa >>= 1;
|
|
171
|
+
|
|
172
|
+
if (mantissa >= (<u64>1) << 53) {
|
|
173
|
+
mantissa = (<u64>1) << 52;
|
|
174
|
+
realExponent++;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const bits = (mantissa & ~((<u64>1) << 52)) | ((<u64>realExponent) << 52);
|
|
178
|
+
return reinterpret<f64>(bits);
|
|
179
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Whitespace-run scanner selected once for large pretty JSON documents.
|
|
2
|
+
|
|
3
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
4
|
+
@lazy const SPLAT_SPACE = i16x8.splat(0x20);
|
|
5
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
6
|
+
@lazy const SPLAT_WS_LO = i16x8.splat(9);
|
|
7
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
8
|
+
@lazy const SPLAT_WS_SPAN = i16x8.splat(4);
|
|
9
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
10
|
+
@lazy const SPLAT_LF = i16x8.splat(10);
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@inline
|
|
14
|
+
function whitespaceMask(block: v128): i32 {
|
|
15
|
+
return i16x8.bitmask(
|
|
16
|
+
v128.or(
|
|
17
|
+
i16x8.eq(block, SPLAT_SPACE),
|
|
18
|
+
i16x8.le_u(i16x8.sub(block, SPLAT_WS_LO), SPLAT_WS_SPAN),
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Detect an eight-space indentation run near the document head. */
|
|
24
|
+
export function hasLongPrettyIndent_SIMD(srcStart: usize, srcEnd: usize): bool {
|
|
25
|
+
const sampleEnd = min(srcEnd, srcStart + 4096);
|
|
26
|
+
const vectorEnd = sampleEnd >= 16 ? sampleEnd - 16 : 0;
|
|
27
|
+
while (srcStart <= vectorEnd) {
|
|
28
|
+
let mask = i16x8.bitmask(i16x8.eq(load<v128>(srcStart), SPLAT_LF));
|
|
29
|
+
while (mask != 0) {
|
|
30
|
+
const newline = srcStart + ((<usize>ctz(mask)) << 1);
|
|
31
|
+
if (
|
|
32
|
+
newline + 18 <= srcEnd &&
|
|
33
|
+
!v128.any_true(v128.xor(load<v128>(newline, 2), i16x8.splat(0x20)))
|
|
34
|
+
)
|
|
35
|
+
return true;
|
|
36
|
+
mask &= mask - 1;
|
|
37
|
+
}
|
|
38
|
+
srcStart += 16;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** `srcStart` is known to point at JSON whitespace. */
|
|
44
|
+
export function skipPrettyWhitespace_SIMD(
|
|
45
|
+
srcStart: usize,
|
|
46
|
+
srcEnd: usize,
|
|
47
|
+
): usize {
|
|
48
|
+
srcStart += 2;
|
|
49
|
+
if (srcStart >= srcEnd) return srcStart;
|
|
50
|
+
let code = load<u16>(srcStart);
|
|
51
|
+
if (code != 0x20 && code - 9 > 4) return srcStart;
|
|
52
|
+
|
|
53
|
+
const vectorEnd = srcEnd >= 16 ? srcEnd - 16 : 0;
|
|
54
|
+
while (srcStart <= vectorEnd) {
|
|
55
|
+
const mask = whitespaceMask(load<v128>(srcStart));
|
|
56
|
+
if (mask == 0xff) {
|
|
57
|
+
srcStart += 16;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
srcStart += (<usize>ctz(~mask & 0xff)) << 1;
|
|
61
|
+
return srcStart;
|
|
62
|
+
}
|
|
63
|
+
do {
|
|
64
|
+
srcStart += 2;
|
|
65
|
+
if (srcStart >= srcEnd) return srcStart;
|
|
66
|
+
code = load<u16>(srcStart);
|
|
67
|
+
} while (code == 0x20 || code - 9 <= 4);
|
|
68
|
+
return srcStart;
|
|
69
|
+
}
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
BRACE_RIGHT,
|
|
5
5
|
BRACKET_LEFT,
|
|
6
6
|
BRACKET_RIGHT,
|
|
7
|
-
COLON,
|
|
8
7
|
COMMA,
|
|
9
8
|
QUOTE,
|
|
10
9
|
} from "../custom/chars";
|
|
@@ -104,49 +103,73 @@ function scanQuotedValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
function scanCompositeValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
106
|
+
// Walk every structural event in a loaded block before advancing. This is
|
|
107
|
+
// substantially cheaper for object-heavy values than restarting a SIMD hunt
|
|
108
|
+
// after every short key: one v128 load now covers all quotes and brackets in
|
|
109
|
+
// its eight UTF-16 lanes. Brackets inside strings are ignored, and a closing
|
|
110
|
+
// quote is active only when preceded by an even-length backslash run.
|
|
112
111
|
let depth: i32 = 1;
|
|
113
112
|
let ptr = srcStart + 2;
|
|
114
113
|
const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
|
|
114
|
+
let inString = false;
|
|
115
|
+
|
|
116
|
+
while (ptr <= srcEnd16) {
|
|
117
|
+
let mask = structuralOrQuoteMask(load<v128>(ptr));
|
|
118
|
+
while (mask != 0) {
|
|
119
|
+
const lane = usize(ctz(mask) << 1);
|
|
120
|
+
mask &= mask - 1;
|
|
121
|
+
const eventPtr = ptr + lane;
|
|
122
|
+
const code = load<u16>(eventPtr);
|
|
123
|
+
|
|
124
|
+
if (code == QUOTE) {
|
|
125
|
+
if (!inString) {
|
|
126
|
+
inString = true;
|
|
127
|
+
} else {
|
|
128
|
+
let slash = eventPtr - 2;
|
|
129
|
+
let escaped = false;
|
|
130
|
+
while (slash > srcStart && load<u16>(slash) == BACK_SLASH) {
|
|
131
|
+
escaped = !escaped;
|
|
132
|
+
slash -= 2;
|
|
133
|
+
}
|
|
134
|
+
if (!escaped) inString = false;
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (inString) continue;
|
|
139
|
+
|
|
140
|
+
const folded = code & 0xffdf;
|
|
141
|
+
if (folded == BRACKET_LEFT) {
|
|
142
|
+
depth++;
|
|
143
|
+
} else if (folded == BRACKET_RIGHT && --depth == 0) {
|
|
144
|
+
return eventPtr + 2;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
ptr += 16;
|
|
148
|
+
}
|
|
149
|
+
|
|
115
150
|
while (ptr < srcEnd) {
|
|
116
151
|
const code = load<u16>(ptr);
|
|
117
152
|
if (code == QUOTE) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
ptr += 2;
|
|
136
|
-
// `,` and `:` sit one byte from the next token, so vectorizing them only
|
|
137
|
-
// adds SIMD setup on string-dense objects - stay scalar. Other fillers
|
|
138
|
-
// (number digits, whitespace, true/false/null) can run long; vectorize past
|
|
139
|
-
// them to the next `"`/`{`/`}`/`[`/`]`.
|
|
140
|
-
if (code == COMMA || code == COLON) continue;
|
|
141
|
-
while (ptr <= srcEnd16) {
|
|
142
|
-
const mask = structuralOrQuoteMask(load<v128>(ptr));
|
|
143
|
-
if (mask == 0) {
|
|
144
|
-
ptr += 16;
|
|
145
|
-
continue;
|
|
153
|
+
if (!inString) {
|
|
154
|
+
inString = true;
|
|
155
|
+
} else {
|
|
156
|
+
let slash = ptr - 2;
|
|
157
|
+
let escaped = false;
|
|
158
|
+
while (slash > srcStart && load<u16>(slash) == BACK_SLASH) {
|
|
159
|
+
escaped = !escaped;
|
|
160
|
+
slash -= 2;
|
|
161
|
+
}
|
|
162
|
+
if (!escaped) inString = false;
|
|
163
|
+
}
|
|
164
|
+
} else if (!inString) {
|
|
165
|
+
const folded = code & 0xffdf;
|
|
166
|
+
if (folded == BRACKET_LEFT) {
|
|
167
|
+
depth++;
|
|
168
|
+
} else if (folded == BRACKET_RIGHT && --depth == 0) {
|
|
169
|
+
return ptr + 2;
|
|
146
170
|
}
|
|
147
|
-
ptr += usize(ctz(mask) << 1);
|
|
148
|
-
break;
|
|
149
171
|
}
|
|
172
|
+
ptr += 2;
|
|
150
173
|
}
|
|
151
174
|
return 0;
|
|
152
175
|
}
|