json-as 1.3.9 → 1.5.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 +60 -19
- package/README.md +120 -21
- package/assembly/custom/chars.ts +39 -78
- package/assembly/deserialize/index/arbitrary.ts +28 -10
- package/assembly/deserialize/index/float.ts +2 -4
- package/assembly/deserialize/index/integer.ts +2 -4
- package/assembly/deserialize/index/object.ts +6 -1
- package/assembly/deserialize/index/string.ts +2 -7
- package/assembly/deserialize/index/unsigned.ts +2 -4
- package/assembly/deserialize/naive/array/arbitrary.ts +3 -136
- package/assembly/deserialize/naive/array/array.ts +30 -1
- package/assembly/deserialize/naive/array/integer.ts +2 -7
- package/assembly/deserialize/naive/array/map.ts +10 -14
- package/assembly/deserialize/naive/array/object.ts +10 -14
- package/assembly/deserialize/naive/array/struct.ts +19 -1
- package/assembly/deserialize/naive/bool.ts +1 -5
- package/assembly/deserialize/naive/date.ts +1 -2
- package/assembly/deserialize/naive/float.ts +4 -11
- package/assembly/deserialize/naive/integer.ts +2 -4
- package/assembly/deserialize/naive/map.ts +42 -205
- package/assembly/deserialize/naive/object.ts +291 -174
- package/assembly/deserialize/naive/raw.ts +1 -5
- package/assembly/deserialize/naive/set.ts +3 -6
- package/assembly/deserialize/naive/staticarray.ts +2 -4
- package/assembly/deserialize/naive/string.ts +68 -24
- package/assembly/deserialize/naive/typedarray.ts +1 -2
- package/assembly/deserialize/naive/unsigned.ts +2 -4
- package/assembly/deserialize/simd/array/integer.ts +5 -13
- package/assembly/deserialize/simd/float.ts +5 -12
- package/assembly/deserialize/simd/integer.ts +6 -15
- package/assembly/deserialize/simd/string.ts +21 -43
- package/assembly/deserialize/swar/array/arbitrary.ts +1 -2
- package/assembly/deserialize/swar/array/array.ts +2 -4
- package/assembly/deserialize/swar/array/bool.ts +2 -4
- package/assembly/deserialize/swar/array/box.ts +1 -2
- package/assembly/deserialize/swar/array/float.ts +8 -21
- package/assembly/deserialize/swar/array/generic.ts +2 -4
- package/assembly/deserialize/swar/array/integer.ts +13 -27
- package/assembly/deserialize/swar/array/map.ts +1 -2
- package/assembly/deserialize/swar/array/object.ts +2 -4
- package/assembly/deserialize/swar/array/raw.ts +1 -2
- package/assembly/deserialize/swar/array/shared.ts +9 -21
- package/assembly/deserialize/swar/array/string.ts +4 -10
- package/assembly/deserialize/swar/array/struct.ts +3 -9
- package/assembly/deserialize/swar/array.ts +1 -3
- package/assembly/deserialize/swar/float.ts +7 -17
- package/assembly/deserialize/swar/integer.ts +6 -15
- package/assembly/deserialize/swar/string.ts +40 -54
- package/assembly/deserialize/swar/typedarray.ts +4 -4
- package/assembly/index.d.ts +259 -21
- package/assembly/index.ts +1704 -266
- package/assembly/serialize/index/arbitrary.ts +70 -4
- package/assembly/serialize/index/jsonarray.ts +51 -0
- package/assembly/serialize/index/object.ts +39 -14
- 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 +23 -34
- package/assembly/serialize/naive/bool.ts +0 -1
- package/assembly/serialize/naive/float.ts +16 -25
- package/assembly/serialize/naive/integer.ts +1 -5
- package/assembly/serialize/naive/raw.ts +1 -2
- package/assembly/serialize/naive/set.ts +0 -4
- package/assembly/serialize/naive/staticarray.ts +0 -5
- package/assembly/serialize/naive/string.ts +11 -7
- package/assembly/serialize/naive/typedarray.ts +0 -6
- package/assembly/serialize/simd/string.ts +1 -3
- package/assembly/serialize/swar/string.ts +2 -4
- package/assembly/util/atoi-fast.ts +4 -14
- package/assembly/util/atoi.ts +1 -2
- package/assembly/util/bytes.ts +1 -2
- package/assembly/util/idofd.ts +1 -2
- package/assembly/util/isSpace.ts +1 -2
- package/assembly/util/itoa-fast.ts +9 -15
- package/assembly/util/nextPowerOf2.ts +1 -2
- package/assembly/util/parsefloat-fast.ts +4 -7
- package/assembly/util/ptrToStr.ts +1 -2
- package/assembly/util/scanValueEnd.ts +1 -2
- package/assembly/util/scanValueEndSimd.ts +198 -0
- package/assembly/util/scanValueEndSwar.ts +184 -0
- package/assembly/util/scientific.ts +8 -14
- package/assembly/util/simd-int.ts +4 -8
- package/assembly/util/snp.ts +2 -7
- package/assembly/util/stringScan.ts +2 -4
- package/assembly/util/swar-int.ts +8 -16
- package/assembly/util/swar.ts +2 -4
- package/lib/as-bs.ts +57 -42
- package/package.json +27 -10
- package/transform/lib/builder.d.ts +0 -1
- package/transform/lib/builder.js +0 -1
- package/transform/lib/index.d.ts +0 -1
- package/transform/lib/index.js +617 -326
- package/transform/lib/linkers/alias.d.ts +0 -1
- package/transform/lib/linkers/alias.js +0 -1
- package/transform/lib/linkers/custom.d.ts +0 -1
- package/transform/lib/linkers/custom.js +0 -1
- package/transform/lib/linkers/imports.d.ts +0 -1
- package/transform/lib/linkers/imports.js +0 -1
- package/transform/lib/types.d.ts +4 -2
- package/transform/lib/types.js +5 -1
- package/transform/lib/util.d.ts +0 -1
- package/transform/lib/util.js +0 -1
- package/transform/lib/visitor.d.ts +0 -1
- package/transform/lib/visitor.js +0 -1
- package/assembly/util/dragonbox-cache.ts +0 -445
- package/assembly/util/dragonbox.ts +0 -660
- package/transform/lib/builder.d.ts.map +0 -1
- package/transform/lib/builder.js.map +0 -1
- package/transform/lib/index.d.ts.map +0 -1
- package/transform/lib/index.js.map +0 -1
- package/transform/lib/linkers/alias.d.ts.map +0 -1
- package/transform/lib/linkers/alias.js.map +0 -1
- package/transform/lib/linkers/custom.d.ts.map +0 -1
- package/transform/lib/linkers/custom.js.map +0 -1
- package/transform/lib/linkers/imports.d.ts.map +0 -1
- package/transform/lib/linkers/imports.js.map +0 -1
- package/transform/lib/types.d.ts.map +0 -1
- package/transform/lib/types.js.map +0 -1
- package/transform/lib/util.d.ts.map +0 -1
- package/transform/lib/util.js.map +0 -1
- package/transform/lib/visitor.d.ts.map +0 -1
- package/transform/lib/visitor.js.map +0 -1
|
@@ -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/atoi.ts
CHANGED
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>()) {
|
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
|
|
|
@@ -59,8 +57,7 @@ function initPairs(): void {
|
|
|
59
57
|
* a byte offset). Caller must ensure the buffer has at least 20 bytes
|
|
60
58
|
* available (max 10 chars).
|
|
61
59
|
*/
|
|
62
|
-
|
|
63
|
-
@inline export function itoaU32(buf: usize, v: u32): u32 {
|
|
60
|
+
export function itoaU32(buf: usize, v: u32): u32 {
|
|
64
61
|
if (v < 10) {
|
|
65
62
|
store<u16>(buf, <u16>(v + 0x30));
|
|
66
63
|
return 1;
|
|
@@ -165,8 +162,7 @@ function initPairs(): void {
|
|
|
165
162
|
* Writes a u32 in the range 0..99_999_999 as exactly 8 UTF-16 chars with
|
|
166
163
|
* leading zeros. Used by the u64 path to emit trailing groups of 8 digits.
|
|
167
164
|
*/
|
|
168
|
-
|
|
169
|
-
@inline function writeU32Padded8(buf: usize, v: u32): void {
|
|
165
|
+
function writeU32Padded8(buf: usize, v: u32): void {
|
|
170
166
|
const a = v / 1_000_000;
|
|
171
167
|
let rest = v - a * 1_000_000;
|
|
172
168
|
const b = rest / 10_000;
|
|
@@ -187,8 +183,7 @@ function initPairs(): void {
|
|
|
187
183
|
* For 17+ digit values (which still fit in u64 < 1.8e19), repeat.
|
|
188
184
|
* Caller must ensure the buffer has at least 40 bytes available.
|
|
189
185
|
*/
|
|
190
|
-
|
|
191
|
-
@inline export function itoaU64(buf: usize, v: u64): u32 {
|
|
186
|
+
export function itoaU64(buf: usize, v: u64): u32 {
|
|
192
187
|
if (v <= <u64>u32.MAX_VALUE) {
|
|
193
188
|
return itoaU32(buf, <u32>v);
|
|
194
189
|
}
|
|
@@ -216,8 +211,7 @@ function initPairs(): void {
|
|
|
216
211
|
*
|
|
217
212
|
* Returns the number of UTF-16 chars written.
|
|
218
213
|
*/
|
|
219
|
-
|
|
220
|
-
@inline export function itoaFast<T extends number>(buf: usize, value: T): u32 {
|
|
214
|
+
export function itoaFast<T extends number>(buf: usize, value: T): u32 {
|
|
221
215
|
if (sizeof<T>() <= 4) {
|
|
222
216
|
if (isSigned<T>()) {
|
|
223
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;
|
|
@@ -64,8 +62,7 @@ const MAX_EXACT_MANTISSA: u64 = 1 << 53;
|
|
|
64
62
|
* exact through accumulation and only loses precision at the final
|
|
65
63
|
* `<f64>` cast.
|
|
66
64
|
*/
|
|
67
|
-
|
|
68
|
-
@inline export function parseFloatFast<T>(srcStart: usize, srcEnd: usize): T {
|
|
65
|
+
export function parseFloatFast<T>(srcStart: usize, srcEnd: usize): T {
|
|
69
66
|
const origStart = srcStart;
|
|
70
67
|
let p = srcStart;
|
|
71
68
|
let negative = false;
|
|
@@ -27,8 +27,7 @@ import { scanStringEnd } from "./stringScan";
|
|
|
27
27
|
* but stays scalar so `naive/` callers don't pull SWAR into the correctness
|
|
28
28
|
* baseline.
|
|
29
29
|
*/
|
|
30
|
-
|
|
31
|
-
@inline export function scanValueEnd(srcStart: usize, srcEnd: usize): usize {
|
|
30
|
+
export function scanValueEnd<T = usize>(srcStart: usize, srcEnd: usize): usize {
|
|
32
31
|
if (srcStart >= srcEnd) return 0;
|
|
33
32
|
const first = load<u16>(srcStart);
|
|
34
33
|
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACK_SLASH,
|
|
3
|
+
BRACE_LEFT,
|
|
4
|
+
BRACE_RIGHT,
|
|
5
|
+
BRACKET_LEFT,
|
|
6
|
+
BRACKET_RIGHT,
|
|
7
|
+
COLON,
|
|
8
|
+
COMMA,
|
|
9
|
+
QUOTE,
|
|
10
|
+
} from "../custom/chars";
|
|
11
|
+
import { isSpace } from "./isSpace";
|
|
12
|
+
|
|
13
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
14
|
+
@lazy const SPLAT_QUOTE = i16x8.splat(<i16>QUOTE);
|
|
15
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
16
|
+
@lazy const SPLAT_BACK_SLASH = i16x8.splat(<i16>BACK_SLASH);
|
|
17
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
18
|
+
@lazy const SPLAT_BRACE_RIGHT = i16x8.splat(<i16>BRACE_RIGHT);
|
|
19
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
20
|
+
@lazy const SPLAT_BRACKET_RIGHT = i16x8.splat(<i16>BRACKET_RIGHT);
|
|
21
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
22
|
+
@lazy const SPLAT_COMMA = i16x8.splat(<i16>COMMA);
|
|
23
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
24
|
+
@lazy const SPLAT_SPACE = i16x8.splat(0x20);
|
|
25
|
+
// JSON whitespace besides space is the contiguous range 0x09..0x0d
|
|
26
|
+
// (tab/LF/VT/FF/CR), matched as `(c - 9) u<= 4` - one sub + one unsigned
|
|
27
|
+
// compare instead of five equality tests. Exact: matches `isSpace` with no
|
|
28
|
+
// false positives.
|
|
29
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
30
|
+
@lazy const SPLAT_WS_LO = i16x8.splat(0x09);
|
|
31
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
32
|
+
@lazy const SPLAT_WS_SPAN = i16x8.splat(0x04);
|
|
33
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
34
|
+
@lazy const SPLAT_BRACKET_LEFT = i16x8.splat(<i16>BRACKET_LEFT);
|
|
35
|
+
// Clears bit 5 (0x20), folding `{`/`}` onto `[`/`]` so one pair of compares
|
|
36
|
+
// matches either bracket flavor. No ASCII char besides `[{`/`]}` folds onto
|
|
37
|
+
// `[`/`]`, so the structural mask stays exact.
|
|
38
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
39
|
+
@lazy const SPLAT_BRACKET_FOLD = i16x8.splat(<i16>0xffdf);
|
|
40
|
+
|
|
41
|
+
function quoteOrBackslashMask(block: v128): i32 {
|
|
42
|
+
return i16x8.bitmask(
|
|
43
|
+
v128.or(i16x8.eq(block, SPLAT_QUOTE), i16x8.eq(block, SPLAT_BACK_SLASH)),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Lanes equal to `"`, `{`, `}`, `[`, or `]` - the only bytes that, outside a
|
|
48
|
+
// string, change depth or open a string. Everything else (digits, `:`, `,`,
|
|
49
|
+
// whitespace, true/false/null) can be bulk-skipped between them.
|
|
50
|
+
function structuralOrQuoteMask(block: v128): i32 {
|
|
51
|
+
const folded = v128.and(block, SPLAT_BRACKET_FOLD);
|
|
52
|
+
const brackets = v128.or(
|
|
53
|
+
i16x8.eq(folded, SPLAT_BRACKET_LEFT),
|
|
54
|
+
i16x8.eq(folded, SPLAT_BRACKET_RIGHT),
|
|
55
|
+
);
|
|
56
|
+
return i16x8.bitmask(v128.or(brackets, i16x8.eq(block, SPLAT_QUOTE)));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function scalarTerminatorMask(block: v128): i32 {
|
|
60
|
+
const structural = v128.or(
|
|
61
|
+
v128.or(i16x8.eq(block, SPLAT_COMMA), i16x8.eq(block, SPLAT_BRACE_RIGHT)),
|
|
62
|
+
i16x8.eq(block, SPLAT_BRACKET_RIGHT),
|
|
63
|
+
);
|
|
64
|
+
// (c - 9) u<= 4 covers tab/LF/VT/FF/CR; space handled separately.
|
|
65
|
+
const whitespace = v128.or(
|
|
66
|
+
i16x8.le_u(i16x8.sub(block, SPLAT_WS_LO), SPLAT_WS_SPAN),
|
|
67
|
+
i16x8.eq(block, SPLAT_SPACE),
|
|
68
|
+
);
|
|
69
|
+
return i16x8.bitmask(v128.or(structural, whitespace));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function scanQuotedValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
73
|
+
srcStart += 2;
|
|
74
|
+
const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
|
|
75
|
+
|
|
76
|
+
while (srcStart <= srcEnd16) {
|
|
77
|
+
const block = load<v128>(srcStart);
|
|
78
|
+
const mask = quoteOrBackslashMask(block);
|
|
79
|
+
if (mask == 0) {
|
|
80
|
+
srcStart += 16;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const laneIdx = usize(ctz(mask) << 1);
|
|
85
|
+
const srcIdx = srcStart + laneIdx;
|
|
86
|
+
const code = load<u16>(srcIdx);
|
|
87
|
+
if (code == QUOTE) return srcIdx + 2;
|
|
88
|
+
if (srcIdx + 2 >= srcEnd) return 0;
|
|
89
|
+
srcStart = srcIdx + 4;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
while (srcStart < srcEnd) {
|
|
93
|
+
const code = load<u16>(srcStart);
|
|
94
|
+
if (code == QUOTE) return srcStart + 2;
|
|
95
|
+
if (code == BACK_SLASH) {
|
|
96
|
+
if (srcStart + 2 >= srcEnd) return 0;
|
|
97
|
+
srcStart += 4;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
srcStart += 2;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function scanCompositeValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
107
|
+
// Process structural tokens scalar-side (cheap, and token-dense regions stay
|
|
108
|
+
// in a tight loop), but bulk-skip the bytes between them: nested string VALUES
|
|
109
|
+
// via the vectorized quoted scan (URLs, base64, prose), and runs of digits /
|
|
110
|
+
// punctuation / whitespace (numeric arrays like coordinate lists) via a
|
|
111
|
+
// vectorized hunt for the next `"`/`{`/`}`/`[`/`]`.
|
|
112
|
+
let depth: i32 = 1;
|
|
113
|
+
let ptr = srcStart + 2;
|
|
114
|
+
const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
|
|
115
|
+
while (ptr < srcEnd) {
|
|
116
|
+
const code = load<u16>(ptr);
|
|
117
|
+
if (code == QUOTE) {
|
|
118
|
+
ptr = scanQuotedValueEnd_SIMD(ptr, srcEnd);
|
|
119
|
+
if (!ptr) return 0;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const folded = code & 0xffdf;
|
|
123
|
+
if (folded == BRACKET_LEFT) {
|
|
124
|
+
// `[` or `{`
|
|
125
|
+
depth++;
|
|
126
|
+
ptr += 2;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (folded == BRACKET_RIGHT) {
|
|
130
|
+
// `]` or `}`
|
|
131
|
+
if (--depth == 0) return ptr + 2;
|
|
132
|
+
ptr += 2;
|
|
133
|
+
continue;
|
|
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;
|
|
146
|
+
}
|
|
147
|
+
ptr += usize(ctz(mask) << 1);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function scanScalarValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
155
|
+
const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
|
|
156
|
+
while (srcStart <= srcEnd16) {
|
|
157
|
+
const mask = scalarTerminatorMask(load<v128>(srcStart));
|
|
158
|
+
if (mask == 0) {
|
|
159
|
+
srcStart += 16;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
return srcStart + usize(ctz(mask) << 1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
while (srcStart < srcEnd) {
|
|
166
|
+
const code = load<u16>(srcStart);
|
|
167
|
+
if (
|
|
168
|
+
code == COMMA ||
|
|
169
|
+
code == BRACKET_RIGHT ||
|
|
170
|
+
code == BRACE_RIGHT ||
|
|
171
|
+
isSpace(code)
|
|
172
|
+
)
|
|
173
|
+
return srcStart;
|
|
174
|
+
srcStart += 2;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return srcStart;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function scanValueEnd_SIMD<T>(srcStart: usize, srcEnd: usize): usize {
|
|
181
|
+
if (srcStart >= srcEnd) return 0;
|
|
182
|
+
const first = load<u16>(srcStart);
|
|
183
|
+
|
|
184
|
+
if (isString<nonnull<T>>() && first == QUOTE)
|
|
185
|
+
return scanQuotedValueEnd_SIMD(srcStart, srcEnd);
|
|
186
|
+
if (isArray<nonnull<T>>() && first == BRACKET_LEFT)
|
|
187
|
+
return scanCompositeValueEnd_SIMD(srcStart, srcEnd);
|
|
188
|
+
if (
|
|
189
|
+
(isManaged<nonnull<T>>() || isReference<nonnull<T>>()) &&
|
|
190
|
+
first == BRACE_LEFT
|
|
191
|
+
)
|
|
192
|
+
return scanCompositeValueEnd_SIMD(srcStart, srcEnd);
|
|
193
|
+
|
|
194
|
+
if (first == QUOTE) return scanQuotedValueEnd_SIMD(srcStart, srcEnd);
|
|
195
|
+
if (first == BRACE_LEFT || first == BRACKET_LEFT)
|
|
196
|
+
return scanCompositeValueEnd_SIMD(srcStart, srcEnd);
|
|
197
|
+
return scanScalarValueEnd_SIMD(srcStart, srcEnd);
|
|
198
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACK_SLASH,
|
|
3
|
+
BRACE_LEFT,
|
|
4
|
+
BRACE_RIGHT,
|
|
5
|
+
BRACKET_LEFT,
|
|
6
|
+
BRACKET_RIGHT,
|
|
7
|
+
COLON,
|
|
8
|
+
COMMA,
|
|
9
|
+
QUOTE,
|
|
10
|
+
} from "../custom/chars";
|
|
11
|
+
import { isSpace } from "./isSpace";
|
|
12
|
+
|
|
13
|
+
// SWAR analogue of `scanValueEndSimd.ts`, processing four UTF-16 lanes per
|
|
14
|
+
// 64-bit word for the SWAR build mode (no SIMD feature). Each mask is a fast
|
|
15
|
+
// FILTER - a matched lane is re-checked with a real `load<u16>` before acting -
|
|
16
|
+
// so the masks may over-match non-ASCII lanes whose low byte equals a target
|
|
17
|
+
// (the verify rejects them). Lane byte offset within a hit word is
|
|
18
|
+
// `ctz(mask) >> 3` (detection bit sits at lane*16 + 7).
|
|
19
|
+
|
|
20
|
+
const ONES: u64 = 0x0001_0001_0001_0001;
|
|
21
|
+
const HI: u64 = 0x0080_0080_0080_0080;
|
|
22
|
+
|
|
23
|
+
// 16-bit-lane "equals" partials (pre-`& HI`); OR several, then `& HI` once.
|
|
24
|
+
function eqPart(block: u64, splat: u64): u64 {
|
|
25
|
+
const t = block ^ splat;
|
|
26
|
+
return (t - ONES) & ~t;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const S_QUOTE: u64 = 0x0022_0022_0022_0022;
|
|
30
|
+
const S_BACK_SLASH: u64 = 0x005c_005c_005c_005c;
|
|
31
|
+
const S_BRACKET_LEFT: u64 = 0x005b_005b_005b_005b;
|
|
32
|
+
const S_BRACKET_RIGHT: u64 = 0x005d_005d_005d_005d;
|
|
33
|
+
// Clears bit 5 (0x20) of each lane, folding `{`/`}` onto `[`/`]`.
|
|
34
|
+
const FOLD: u64 = 0xffdf_ffdf_ffdf_ffdf;
|
|
35
|
+
|
|
36
|
+
function quoteOrBackslashMask(block: u64): u64 {
|
|
37
|
+
return (eqPart(block, S_QUOTE) | eqPart(block, S_BACK_SLASH)) & HI;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Filter for lanes equal to `"`, `{`, `}`, `[`, or `]` - the only bytes that,
|
|
41
|
+
// outside a string, change depth or open a string. As with the other SWAR
|
|
42
|
+
// masks, a hit is a candidate to verify with a real load (it may over-match a
|
|
43
|
+
// non-ASCII lane whose low byte collides).
|
|
44
|
+
function structuralOrQuoteMask(block: u64): u64 {
|
|
45
|
+
const folded = block & FOLD;
|
|
46
|
+
return (
|
|
47
|
+
(eqPart(folded, S_BRACKET_LEFT) |
|
|
48
|
+
eqPart(folded, S_BRACKET_RIGHT) |
|
|
49
|
+
eqPart(block, S_QUOTE)) &
|
|
50
|
+
HI
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function scanQuotedValueEnd_SWAR(srcStart: usize, srcEnd: usize): usize {
|
|
55
|
+
srcStart += 2;
|
|
56
|
+
const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
|
|
57
|
+
|
|
58
|
+
// Fast-skip 8-byte windows until a real quote (return) or a backslash, then
|
|
59
|
+
// hand off to the precise scalar tail (which resolves escape runs). The mask
|
|
60
|
+
// is a filter, so each candidate lane is verified with a real `load<u16>`;
|
|
61
|
+
// non-ASCII lanes that spuriously match are skipped (neither quote nor
|
|
62
|
+
// backslash), and `srcStart` is left at the window start for the tail.
|
|
63
|
+
while (srcStart <= srcEnd8) {
|
|
64
|
+
let mask = quoteOrBackslashMask(load<u64>(srcStart));
|
|
65
|
+
if (mask == 0) {
|
|
66
|
+
srcStart += 8;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
do {
|
|
70
|
+
const srcIdx = srcStart + (usize(ctz(mask)) >> 3);
|
|
71
|
+
mask &= mask - 1;
|
|
72
|
+
const code = load<u16>(srcIdx);
|
|
73
|
+
if (code == QUOTE) return srcIdx + 2;
|
|
74
|
+
if (code == BACK_SLASH) break;
|
|
75
|
+
} while (mask != 0);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Resolve escapes by consuming a backslash *and the char it escapes* together,
|
|
80
|
+
// so escape parity is tracked exactly. A look-back `prev != BACK_SLASH` test is
|
|
81
|
+
// wrong for an escaped backslash: in `"x\\"` the closing quote follows a `\`
|
|
82
|
+
// (the second of the pair) yet still closes the string.
|
|
83
|
+
while (srcStart < srcEnd) {
|
|
84
|
+
const code = load<u16>(srcStart);
|
|
85
|
+
if (code == BACK_SLASH) {
|
|
86
|
+
srcStart += 4;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (code == QUOTE) return srcStart + 2;
|
|
90
|
+
srcStart += 2;
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function scanCompositeValueEnd_SWAR(srcStart: usize, srcEnd: usize): usize {
|
|
96
|
+
// Process structural tokens scalar-side, but bulk-skip the bytes between them:
|
|
97
|
+
// nested string VALUES via the SWAR quoted scan, and runs of digits /
|
|
98
|
+
// punctuation / whitespace (numeric arrays) via a SWAR hunt for the next
|
|
99
|
+
// `"`/`{`/`}`/`[`/`]`.
|
|
100
|
+
let depth: i32 = 1;
|
|
101
|
+
let ptr = srcStart + 2;
|
|
102
|
+
const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
|
|
103
|
+
while (ptr < srcEnd) {
|
|
104
|
+
const code = load<u16>(ptr);
|
|
105
|
+
if (code == QUOTE) {
|
|
106
|
+
ptr = scanQuotedValueEnd_SWAR(ptr, srcEnd);
|
|
107
|
+
if (!ptr) return 0;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const folded = code & 0xffdf;
|
|
111
|
+
if (folded == BRACKET_LEFT) {
|
|
112
|
+
depth++;
|
|
113
|
+
ptr += 2;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (folded == BRACKET_RIGHT) {
|
|
117
|
+
if (--depth == 0) return ptr + 2;
|
|
118
|
+
ptr += 2;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
ptr += 2;
|
|
122
|
+
// `,`/`:` sit one byte from the next token - stay scalar (string-dense
|
|
123
|
+
// objects); other fillers can run long, so SWAR-skip past them.
|
|
124
|
+
if (code == COMMA || code == COLON) continue;
|
|
125
|
+
while (ptr <= srcEnd8) {
|
|
126
|
+
const mask = structuralOrQuoteMask(load<u64>(ptr));
|
|
127
|
+
if (mask == 0) {
|
|
128
|
+
ptr += 8;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const idx = ptr + (usize(ctz(mask)) >> 3);
|
|
132
|
+
const c = load<u16>(idx);
|
|
133
|
+
const f = c & 0xffdf;
|
|
134
|
+
if (c == QUOTE || f == BRACKET_LEFT || f == BRACKET_RIGHT) {
|
|
135
|
+
ptr = idx; // real token - the outer loop processes it
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
ptr = idx + 2; // spurious lane match - keep scanning
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function scanScalarValueEnd_SWAR(srcStart: usize, srcEnd: usize): usize {
|
|
145
|
+
// Scalars (number/true/false/null) are short, so a plain scalar terminator
|
|
146
|
+
// scan beats setting up SWAR masks per word.
|
|
147
|
+
while (srcStart < srcEnd) {
|
|
148
|
+
const code = load<u16>(srcStart);
|
|
149
|
+
if (
|
|
150
|
+
code == COMMA ||
|
|
151
|
+
code == BRACKET_RIGHT ||
|
|
152
|
+
code == BRACE_RIGHT ||
|
|
153
|
+
isSpace(code)
|
|
154
|
+
)
|
|
155
|
+
return srcStart;
|
|
156
|
+
srcStart += 2;
|
|
157
|
+
}
|
|
158
|
+
return srcStart;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* SWAR `scanValueEnd`: position just past the value at `srcStart`. Strings and
|
|
163
|
+
* objects/arrays use the SWAR token scans above; scalars use a short scalar
|
|
164
|
+
* loop. Returns 0 on empty input or an unterminated string/composite.
|
|
165
|
+
*/
|
|
166
|
+
export function scanValueEnd_SWAR<T>(srcStart: usize, srcEnd: usize): usize {
|
|
167
|
+
if (srcStart >= srcEnd) return 0;
|
|
168
|
+
const first = load<u16>(srcStart);
|
|
169
|
+
|
|
170
|
+
if (isString<nonnull<T>>() && first == QUOTE)
|
|
171
|
+
return scanQuotedValueEnd_SWAR(srcStart, srcEnd);
|
|
172
|
+
if (isArray<nonnull<T>>() && first == BRACKET_LEFT)
|
|
173
|
+
return scanCompositeValueEnd_SWAR(srcStart, srcEnd);
|
|
174
|
+
if (
|
|
175
|
+
(isManaged<nonnull<T>>() || isReference<nonnull<T>>()) &&
|
|
176
|
+
first == BRACE_LEFT
|
|
177
|
+
)
|
|
178
|
+
return scanCompositeValueEnd_SWAR(srcStart, srcEnd);
|
|
179
|
+
|
|
180
|
+
if (first == QUOTE) return scanQuotedValueEnd_SWAR(srcStart, srcEnd);
|
|
181
|
+
if (first == BRACE_LEFT || first == BRACKET_LEFT)
|
|
182
|
+
return scanCompositeValueEnd_SWAR(srcStart, srcEnd);
|
|
183
|
+
return scanScalarValueEnd_SWAR(srcStart, srcEnd);
|
|
184
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// `scientific` directly skips both costs.
|
|
11
11
|
//
|
|
12
12
|
// scientific() is correctly rounded for all u64 mantissas and decimal
|
|
13
|
-
// exponents that fit in IEEE-754 f64's range
|
|
13
|
+
// exponents that fit in IEEE-754 f64's range - including the [2^53, 2^64)
|
|
14
14
|
// mantissa range that breaks Lemire's single-fmul fast path.
|
|
15
15
|
|
|
16
16
|
const POWERS10: usize = memory.data<f64>([
|
|
@@ -25,13 +25,11 @@ const POWERS5: usize = memory.data<i32>([
|
|
|
25
25
|
244140625, 1220703125,
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
@inline function pow10(n: i32): f64 {
|
|
28
|
+
function pow10(n: i32): f64 {
|
|
30
29
|
return load<f64>(POWERS10 + ((<usize>n) << alignof<f64>()));
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
@inline function pow5_32(n: i32): i32 {
|
|
32
|
+
function pow5_32(n: i32): i32 {
|
|
35
33
|
return load<i32>(POWERS5 + ((<usize>n) << alignof<i32>()));
|
|
36
34
|
}
|
|
37
35
|
|
|
@@ -40,8 +38,7 @@ const POWERS5: usize = memory.data<i32>([
|
|
|
40
38
|
// @ts-ignore: lazy decorator
|
|
41
39
|
@lazy let __fixmulShift: u64 = 0;
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
@inline function fixmul(a: u64, b: u32): u64 {
|
|
41
|
+
function fixmul(a: u64, b: u32): u64 {
|
|
45
42
|
const low = (a & 0xffffffff) * b;
|
|
46
43
|
const high = (a >> 32) * b + (low >> 32);
|
|
47
44
|
const overflow = <u32>(high >> 32);
|
|
@@ -54,8 +51,7 @@ const POWERS5: usize = memory.data<i32>([
|
|
|
54
51
|
);
|
|
55
52
|
}
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
@inline function scaledown(significand: u64, exp: i32): f64 {
|
|
54
|
+
function scaledown(significand: u64, exp: i32): f64 {
|
|
59
55
|
const denom: u64 = 6103515625; // 1e14 * 0x1p-14
|
|
60
56
|
const scale = reinterpret<f64>(0x3f06849b86a12b9b); // 1e-14 * 0x1p32
|
|
61
57
|
|
|
@@ -82,8 +78,7 @@ const POWERS5: usize = memory.data<i32>([
|
|
|
82
78
|
return NativeMath.scalbn(<f64>significand, <i32>shift);
|
|
83
79
|
}
|
|
84
80
|
|
|
85
|
-
|
|
86
|
-
@inline function scaleup(significand: u64, exp: i32): f64 {
|
|
81
|
+
function scaleup(significand: u64, exp: i32): f64 {
|
|
87
82
|
const coeff: u32 = 1220703125; // 1e13 * 0x1p-13;
|
|
88
83
|
let shift = ctz(significand);
|
|
89
84
|
significand >>= shift;
|
|
@@ -100,7 +95,7 @@ const POWERS5: usize = memory.data<i32>([
|
|
|
100
95
|
|
|
101
96
|
/**
|
|
102
97
|
* Construct an f64 from a u64 mantissa and decimal exponent. Result is
|
|
103
|
-
* correctly rounded
|
|
98
|
+
* correctly rounded - bit-identical to `f64.parse` for any input the SWAR
|
|
104
99
|
* float deserializer can pre-parse into this form.
|
|
105
100
|
*
|
|
106
101
|
* Caller guarantees the digit run that produced `significand` was already
|
|
@@ -111,8 +106,7 @@ const POWERS5: usize = memory.data<i32>([
|
|
|
111
106
|
* @param exp Decimal exponent (e.g. for "12.34" pass 1234 and -2)
|
|
112
107
|
* @returns The correctly rounded f64, or 0 / Infinity at the extremes.
|
|
113
108
|
*/
|
|
114
|
-
|
|
115
|
-
@inline export function scientific(significand: u64, exp: i32): f64 {
|
|
109
|
+
export function scientific(significand: u64, exp: i32): f64 {
|
|
116
110
|
if (!significand || exp < -342) return 0;
|
|
117
111
|
if (exp > 308) return Infinity;
|
|
118
112
|
let significandf = <f64>significand;
|