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
|
@@ -15,8 +15,7 @@ import { serializeStruct } from "./struct";
|
|
|
15
15
|
* @param src string
|
|
16
16
|
* @returns void
|
|
17
17
|
*/
|
|
18
|
-
|
|
19
|
-
@inline export function serializeString_NAIVE(src: string): void {
|
|
18
|
+
export function serializeString_NAIVE(src: string): void {
|
|
20
19
|
serializeStringRange(changetype<usize>(src), bytes(src));
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -86,11 +85,3 @@ export function serializeStringRange(srcPtr: usize, srcSize: usize): void {
|
|
|
86
85
|
store<u16>(bs.offset, QUOTE);
|
|
87
86
|
bs.offset += 2;
|
|
88
87
|
}
|
|
89
|
-
|
|
90
|
-
// @ts-ignore: inline
|
|
91
|
-
@inline function write_u_escape(code: u16): void {
|
|
92
|
-
bs.growSize(10);
|
|
93
|
-
store<u32>(bs.offset, U_MARKER); // "\u"
|
|
94
|
-
store<u64>(bs.offset, u16_to_hex4_swar(code), 4);
|
|
95
|
-
bs.offset += 12;
|
|
96
|
-
}
|
|
@@ -3,8 +3,6 @@ import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../custom/chars";
|
|
|
3
3
|
import { serializeFloat32Unsafe, serializeFloat64Unsafe } from "./float";
|
|
4
4
|
import { serializeIntegerUnsafe } from "./integer";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
@inline
|
|
8
6
|
function maxIntegerBytes<T extends number>(): u32 {
|
|
9
7
|
if (sizeof<T>() == 1) return isSigned<T>() ? 8 : 6;
|
|
10
8
|
if (sizeof<T>() == 2) return isSigned<T>() ? 12 : 10;
|
|
@@ -12,10 +10,7 @@ function maxIntegerBytes<T extends number>(): u32 {
|
|
|
12
10
|
return isSigned<T>() ? 42 : 40;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
@inline
|
|
17
13
|
function reserveTypedArray<T extends ArrayLike<number>>(len: i32): void {
|
|
18
|
-
if (len <= 0) return;
|
|
19
14
|
if (isFloat<valueof<T>>()) {
|
|
20
15
|
bs.proposeSize(4 + <u32>len * (sizeof<valueof<T>>() == 4 ? 34 : 66));
|
|
21
16
|
} else {
|
|
@@ -23,8 +18,6 @@ function reserveTypedArray<T extends ArrayLike<number>>(len: i32): void {
|
|
|
23
18
|
}
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
@inline
|
|
28
21
|
function serializeTypedArrayElement<T extends ArrayLike<number>>(
|
|
29
22
|
src: T,
|
|
30
23
|
index: i32,
|
|
@@ -207,9 +207,7 @@ export function serializeString_SIMD(src: string): void {
|
|
|
207
207
|
store<u16>(bs.offset, 34); // "
|
|
208
208
|
bs.offset += 2;
|
|
209
209
|
}
|
|
210
|
-
|
|
211
|
-
// @ts-expect-error: @inline is a valid decorator
|
|
212
|
-
@inline function write_u_escape(code: u16): void {
|
|
210
|
+
function write_u_escape(code: u16): void {
|
|
213
211
|
bs.growSize(10);
|
|
214
212
|
store<u32>(bs.offset, U_MARKER); // "\u"
|
|
215
213
|
store<u64>(bs.offset, u16_to_hex4_swar(code), 4);
|
|
@@ -174,8 +174,7 @@ export function serializeString_SWAR(src: string): void {
|
|
|
174
174
|
bs.offset += 2;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
@inline function write_u_escape(code: u16): void {
|
|
177
|
+
function write_u_escape(code: u16): void {
|
|
179
178
|
bs.growSize(10);
|
|
180
179
|
store<u32>(bs.offset, U_MARKER); // "\u"
|
|
181
180
|
store<u64>(bs.offset, u16_to_hex4_swar(code), 4);
|
|
@@ -18,11 +18,7 @@ import {
|
|
|
18
18
|
* @param srcEnd Pointer just past the last code unit.
|
|
19
19
|
* @returns The parsed value, truncated to `T`.
|
|
20
20
|
*/
|
|
21
|
-
|
|
22
|
-
@inline export function atou<T extends number>(
|
|
23
|
-
srcStart: usize,
|
|
24
|
-
srcEnd: usize,
|
|
25
|
-
): T {
|
|
21
|
+
export function atou<T extends number>(srcStart: usize, srcEnd: usize): T {
|
|
26
22
|
return deserializeUnsigned_SWAR<T>(srcStart, srcEnd);
|
|
27
23
|
}
|
|
28
24
|
|
|
@@ -34,11 +30,7 @@ import {
|
|
|
34
30
|
* @param srcEnd Pointer just past the last code unit.
|
|
35
31
|
* @returns The parsed value, truncated to `T`.
|
|
36
32
|
*/
|
|
37
|
-
|
|
38
|
-
@inline export function atoi<T extends number>(
|
|
39
|
-
srcStart: usize,
|
|
40
|
-
srcEnd: usize,
|
|
41
|
-
): T {
|
|
33
|
+
export function atoi<T extends number>(srcStart: usize, srcEnd: usize): T {
|
|
42
34
|
return deserializeInteger_SWAR<T>(srcStart, srcEnd);
|
|
43
35
|
}
|
|
44
36
|
|
|
@@ -52,8 +44,7 @@ import {
|
|
|
52
44
|
* @param dstPtr Destination pointer for the parsed value.
|
|
53
45
|
* @returns The source position immediately after the last digit consumed.
|
|
54
46
|
*/
|
|
55
|
-
|
|
56
|
-
@inline export function atouScan<T extends number>(
|
|
47
|
+
export function atouScan<T extends number>(
|
|
57
48
|
srcStart: usize,
|
|
58
49
|
srcEnd: usize,
|
|
59
50
|
dstPtr: usize,
|
|
@@ -71,8 +62,7 @@ import {
|
|
|
71
62
|
* @param dstPtr Destination pointer for the parsed value.
|
|
72
63
|
* @returns The source position immediately after the last digit consumed.
|
|
73
64
|
*/
|
|
74
|
-
|
|
75
|
-
@inline export function atoiScan<T extends number>(
|
|
65
|
+
export function atoiScan<T extends number>(
|
|
76
66
|
srcStart: usize,
|
|
77
67
|
srcEnd: usize,
|
|
78
68
|
dstPtr: usize,
|
package/assembly/util/bytes.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
@inline export function bytes<T>(o: T): i32 {
|
|
3
|
+
export function bytes<T>(o: T): i32 {
|
|
5
4
|
if (isInteger<T>() || isFloat<T>()) {
|
|
6
5
|
return sizeof<T>();
|
|
7
6
|
} else if (isManaged<T>() || isReference<T>()) {
|
|
@@ -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
|
+
}
|
package/assembly/util/idofd.ts
CHANGED
package/assembly/util/isSpace.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// below. Two reasons:
|
|
7
7
|
//
|
|
8
8
|
// 1. V8/wasm lowers `v / 100` (and other `/ <const>`s) to a single
|
|
9
|
-
// multiply-shift, so jeaiii's main selling point
|
|
10
|
-
// hardware
|
|
9
|
+
// multiply-shift, so jeaiii's main selling point - avoiding division
|
|
10
|
+
// hardware - gives no win on this target. The op counts come out
|
|
11
11
|
// roughly equal.
|
|
12
12
|
//
|
|
13
13
|
// 2. The div-by-const variant computes each digit pair independently
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
// - A 100-entry digit-pair LUT keyed on `value % 100`. One `store<u32>`
|
|
25
25
|
// emits a UTF-16 pair.
|
|
26
26
|
//
|
|
27
|
-
// - Forward write in one pass
|
|
27
|
+
// - Forward write in one pass - no `decimalCount32` precomputation, no
|
|
28
28
|
// backward write.
|
|
29
29
|
//
|
|
30
30
|
// Reference H2H bench: `__benches__/custom/itoa-h2h.bench.ts`.
|
|
@@ -43,13 +43,11 @@ function initPairs(): void {
|
|
|
43
43
|
_pairsInited = true;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
@inline export function ensureItoaPairs(): void {
|
|
46
|
+
export function ensureItoaPairs(): void {
|
|
48
47
|
if (!_pairsInited) initPairs();
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
@inline function pair(i: u32): u32 {
|
|
50
|
+
function pair(i: u32): u32 {
|
|
53
51
|
return load<u32>(DIGIT_PAIRS_UTF16 + ((<usize>i) << 2));
|
|
54
52
|
}
|
|
55
53
|
|
|
@@ -213,8 +211,7 @@ export function itoaU64(buf: usize, v: u64): u32 {
|
|
|
213
211
|
*
|
|
214
212
|
* Returns the number of UTF-16 chars written.
|
|
215
213
|
*/
|
|
216
|
-
|
|
217
|
-
@inline export function itoaFast<T extends number>(buf: usize, value: T): u32 {
|
|
214
|
+
export function itoaFast<T extends number>(buf: usize, value: T): u32 {
|
|
218
215
|
if (sizeof<T>() <= 4) {
|
|
219
216
|
if (isSigned<T>()) {
|
|
220
217
|
let v = <i32>value;
|
|
@@ -3,7 +3,7 @@ import { ptrToStr } from "./ptrToStr";
|
|
|
3
3
|
// Lemire-style fast float parser.
|
|
4
4
|
//
|
|
5
5
|
// Reference: Daniel Lemire, "Number parsing at a gigabyte per second"
|
|
6
|
-
// (2021). https://arxiv.org/abs/2101.11408
|
|
6
|
+
// (2021). https://arxiv.org/abs/2101.11408 - implemented in
|
|
7
7
|
// https://github.com/fastfloat/fast_float.
|
|
8
8
|
//
|
|
9
9
|
// The "fast path" applies when:
|
|
@@ -35,13 +35,11 @@ const MAX_EXACT_POW10: i32 = 22;
|
|
|
35
35
|
// 2^53 = 9_007_199_254_740_992. Any u64 <= this is exact in f64.
|
|
36
36
|
const MAX_EXACT_MANTISSA: u64 = 1 << 53;
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
@inline function loadPow10(exp: u32): f64 {
|
|
38
|
+
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: type
|
|
47
45
|
const type: T = 0;
|
|
@@ -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
|
+
}
|
|
@@ -22,23 +22,40 @@ import { isSpace } from "./isSpace";
|
|
|
22
22
|
// @ts-expect-error: @lazy is a valid decorator
|
|
23
23
|
@lazy const SPLAT_SPACE = i16x8.splat(0x20);
|
|
24
24
|
// JSON whitespace besides space is the contiguous range 0x09..0x0d
|
|
25
|
-
// (tab/LF/VT/FF/CR), matched as `(c - 9) u<= 4`
|
|
25
|
+
// (tab/LF/VT/FF/CR), matched as `(c - 9) u<= 4` - one sub + one unsigned
|
|
26
26
|
// compare instead of five equality tests. Exact: matches `isSpace` with no
|
|
27
27
|
// false positives.
|
|
28
28
|
// @ts-expect-error: @lazy is a valid decorator
|
|
29
29
|
@lazy const SPLAT_WS_LO = i16x8.splat(0x09);
|
|
30
30
|
// @ts-expect-error: @lazy is a valid decorator
|
|
31
31
|
@lazy const SPLAT_WS_SPAN = i16x8.splat(0x04);
|
|
32
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
33
|
+
@lazy const SPLAT_BRACKET_LEFT = i16x8.splat(<i16>BRACKET_LEFT);
|
|
34
|
+
// Clears bit 5 (0x20), folding `{`/`}` onto `[`/`]` so one pair of compares
|
|
35
|
+
// matches either bracket flavor. No ASCII char besides `[{`/`]}` folds onto
|
|
36
|
+
// `[`/`]`, so the structural mask stays exact.
|
|
37
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
38
|
+
@lazy const SPLAT_BRACKET_FOLD = i16x8.splat(<i16>0xffdf);
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
@inline function quoteOrBackslashMask(block: v128): i32 {
|
|
40
|
+
function quoteOrBackslashMask(block: v128): i32 {
|
|
35
41
|
return i16x8.bitmask(
|
|
36
42
|
v128.or(i16x8.eq(block, SPLAT_QUOTE), i16x8.eq(block, SPLAT_BACK_SLASH)),
|
|
37
43
|
);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
//
|
|
41
|
-
|
|
46
|
+
// Lanes equal to `"`, `{`, `}`, `[`, or `]` - the only bytes that, outside a
|
|
47
|
+
// string, change depth or open a string. Everything else (digits, `:`, `,`,
|
|
48
|
+
// whitespace, true/false/null) can be bulk-skipped between them.
|
|
49
|
+
function structuralOrQuoteMask(block: v128): i32 {
|
|
50
|
+
const folded = v128.and(block, SPLAT_BRACKET_FOLD);
|
|
51
|
+
const brackets = v128.or(
|
|
52
|
+
i16x8.eq(folded, SPLAT_BRACKET_LEFT),
|
|
53
|
+
i16x8.eq(folded, SPLAT_BRACKET_RIGHT),
|
|
54
|
+
);
|
|
55
|
+
return i16x8.bitmask(v128.or(brackets, i16x8.eq(block, SPLAT_QUOTE)));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function scalarTerminatorMask(block: v128): i32 {
|
|
42
59
|
const structural = v128.or(
|
|
43
60
|
v128.or(i16x8.eq(block, SPLAT_COMMA), i16x8.eq(block, SPLAT_BRACE_RIGHT)),
|
|
44
61
|
i16x8.eq(block, SPLAT_BRACKET_RIGHT),
|
|
@@ -86,23 +103,71 @@ function scanQuotedValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
function scanCompositeValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
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.
|
|
93
111
|
let depth: i32 = 1;
|
|
94
112
|
let ptr = srcStart + 2;
|
|
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
|
+
|
|
95
150
|
while (ptr < srcEnd) {
|
|
96
151
|
const code = load<u16>(ptr);
|
|
97
152
|
if (code == QUOTE) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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;
|
|
170
|
+
}
|
|
106
171
|
}
|
|
107
172
|
ptr += 2;
|
|
108
173
|
}
|
|
@@ -135,11 +200,7 @@ function scanScalarValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
|
135
200
|
return srcStart;
|
|
136
201
|
}
|
|
137
202
|
|
|
138
|
-
|
|
139
|
-
@inline export function scanValueEnd_SIMD<T>(
|
|
140
|
-
srcStart: usize,
|
|
141
|
-
srcEnd: usize,
|
|
142
|
-
): usize {
|
|
203
|
+
export function scanValueEnd_SIMD<T>(srcStart: usize, srcEnd: usize): usize {
|
|
143
204
|
if (srcStart >= srcEnd) return 0;
|
|
144
205
|
const first = load<u16>(srcStart);
|
|
145
206
|
|