json-as 1.3.8 → 1.4.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 +24 -0
- package/README.md +49 -1
- package/assembly/deserialize/index/arbitrary.ts +2 -2
- 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 +1 -6
- package/assembly/deserialize/naive/array/map.ts +10 -14
- package/assembly/deserialize/naive/array/object.ts +10 -14
- package/assembly/deserialize/naive/float.ts +2 -4
- package/assembly/deserialize/naive/integer.ts +1 -2
- package/assembly/deserialize/naive/map.ts +40 -202
- package/assembly/deserialize/naive/object.ts +153 -174
- package/assembly/deserialize/naive/set.ts +1 -2
- package/assembly/deserialize/naive/staticarray.ts +1 -2
- package/assembly/deserialize/naive/string.ts +65 -18
- package/assembly/deserialize/naive/typedarray.ts +1 -2
- package/assembly/deserialize/naive/unsigned.ts +1 -2
- package/assembly/deserialize/simd/array/integer.ts +3 -6
- package/assembly/deserialize/simd/float.ts +2 -7
- package/assembly/deserialize/simd/integer.ts +4 -8
- package/assembly/deserialize/simd/string.ts +16 -21
- package/assembly/deserialize/swar/array/array.ts +1 -2
- package/assembly/deserialize/swar/array/bool.ts +1 -2
- package/assembly/deserialize/swar/array/float.ts +2 -3
- package/assembly/deserialize/swar/array/generic.ts +1 -2
- package/assembly/deserialize/swar/array/integer.ts +6 -11
- package/assembly/deserialize/swar/array/object.ts +1 -2
- package/assembly/deserialize/swar/array/shared.ts +3 -8
- package/assembly/deserialize/swar/array/string.ts +1 -2
- package/assembly/deserialize/swar/array/struct.ts +1 -1
- package/assembly/deserialize/swar/float.ts +3 -8
- package/assembly/deserialize/swar/integer.ts +4 -8
- package/assembly/deserialize/swar/string.ts +29 -41
- package/assembly/index.d.ts +248 -15
- package/assembly/index.ts +468 -146
- package/assembly/serialize/index/object.ts +18 -15
- package/assembly/serialize/naive/string.ts +9 -2
- package/assembly/serialize/swar/string.ts +1 -2
- package/assembly/util/atoi.ts +1 -2
- package/assembly/util/dragonbox.ts +0 -8
- package/assembly/util/itoa-fast.ts +3 -6
- package/assembly/util/parsefloat-fast.ts +1 -2
- package/assembly/util/scanValueEnd.ts +1 -2
- package/assembly/util/scanValueEndSimd.ts +160 -0
- package/assembly/util/scanValueEndSwar.ts +142 -0
- package/assembly/util/scientific.ts +3 -6
- package/assembly/util/simd-int.ts +4 -8
- package/assembly/util/snp.ts +1 -5
- package/assembly/util/stringScan.ts +2 -4
- package/assembly/util/swar-int.ts +3 -6
- package/lib/as-bs.ts +37 -0
- package/package.json +14 -4
- 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 +537 -290
- 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 +3 -2
- package/transform/lib/types.js +2 -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/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
|
@@ -2,11 +2,10 @@ import { bs } from "../../../lib/as-bs";
|
|
|
2
2
|
import { JSON } from "../..";
|
|
3
3
|
import { BRACE_LEFT, BRACE_RIGHT, COLON, COMMA } from "../../custom/chars";
|
|
4
4
|
import { serializeArbitrary } from "./arbitrary";
|
|
5
|
-
import {
|
|
5
|
+
import { serializeStringRange } from "../naive/string";
|
|
6
6
|
|
|
7
7
|
export function serializeObject(src: JSON.Obj): void {
|
|
8
8
|
const srcSize = src.size;
|
|
9
|
-
const srcEnd = srcSize - 1;
|
|
10
9
|
|
|
11
10
|
if (srcSize == 0) {
|
|
12
11
|
bs.proposeSize(4);
|
|
@@ -15,28 +14,32 @@ export function serializeObject(src: JSON.Obj): void {
|
|
|
15
14
|
return;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
17
|
+
const vals = src._vals;
|
|
18
|
+
const kbuf = changetype<usize>(src._kbuf);
|
|
19
|
+
const kused = src._kused;
|
|
20
20
|
|
|
21
21
|
bs.proposeSize(4 + <u32>(srcSize - 1) * 2 + <u32>srcSize * 2);
|
|
22
22
|
store<u16>(bs.offset, BRACE_LEFT);
|
|
23
23
|
bs.offset += 2;
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// Walk the length-prefixed key buffer in lockstep with the values, emitting
|
|
26
|
+
// each key straight from its slice (no per-key string materialization).
|
|
27
|
+
let pos = 0;
|
|
28
|
+
let i = 0;
|
|
29
|
+
while (pos < kused) {
|
|
30
|
+
const len = <i32>load<u16>(kbuf + ((<usize>pos) << 1));
|
|
31
|
+
if (i != 0) {
|
|
32
|
+
store<u16>(bs.offset, COMMA);
|
|
33
|
+
bs.offset += 2;
|
|
34
|
+
}
|
|
35
|
+
serializeStringRange(kbuf + ((<usize>(pos + 1)) << 1), (<usize>len) << 1);
|
|
27
36
|
store<u16>(bs.offset, COLON);
|
|
28
37
|
bs.offset += 2;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
bs.offset += 2;
|
|
38
|
+
serializeArbitrary(unchecked(vals[i]));
|
|
39
|
+
pos += 1 + len;
|
|
40
|
+
i++;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
serializeString(unchecked(keys[srcEnd]));
|
|
36
|
-
store<u16>(bs.offset, COLON);
|
|
37
|
-
bs.offset += 2;
|
|
38
|
-
serializeArbitrary(unchecked(values[srcEnd]));
|
|
39
|
-
|
|
40
43
|
store<u16>(bs.offset, BRACE_RIGHT);
|
|
41
44
|
bs.offset += 2;
|
|
42
45
|
}
|
|
@@ -17,9 +17,16 @@ import { serializeStruct } from "./struct";
|
|
|
17
17
|
*/
|
|
18
18
|
// @ts-ignore: inline
|
|
19
19
|
@inline export function serializeString_NAIVE(src: string): void {
|
|
20
|
-
|
|
20
|
+
serializeStringRange(changetype<usize>(src), bytes(src));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Serializes a raw UTF-16 range as a quoted, escaped JSON string. Lets callers
|
|
25
|
+
* (e.g. JSON.Obj key serialization) emit a string straight from a buffer slice
|
|
26
|
+
* without first materializing a heap `string`.
|
|
27
|
+
*/
|
|
28
|
+
export function serializeStringRange(srcPtr: usize, srcSize: usize): void {
|
|
21
29
|
bs.proposeSize(srcSize + 4);
|
|
22
|
-
let srcPtr = changetype<usize>(src);
|
|
23
30
|
const srcEnd = srcPtr + srcSize;
|
|
24
31
|
|
|
25
32
|
store<u16>(bs.offset, QUOTE);
|
|
@@ -182,8 +182,7 @@ export function serializeString_SWAR(src: string): void {
|
|
|
182
182
|
bs.offset += 12;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
@inline export function detect_escapable_u64_swar_safe(block: u64): u64 {
|
|
185
|
+
export function detect_escapable_u64_swar_safe(block: u64): u64 {
|
|
187
186
|
const hi = block & 0xff00_ff00_ff00_ff00;
|
|
188
187
|
const lo = block & 0x00ff_00ff_00ff_00ff;
|
|
189
188
|
// Setting bit 8 of each 16-bit lane (high byte LSB) prevents borrow from a
|
package/assembly/util/atoi.ts
CHANGED
|
@@ -118,8 +118,6 @@ function computeRoundUpShorter32(cache: u64, beta: i32): u32 {
|
|
|
118
118
|
return (<u32>(cache >>> (39 - beta)) + 1) >>> 1;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
@inline
|
|
123
121
|
function removeTrailingZeros32(significand: u32): u32 {
|
|
124
122
|
let exponent = 0;
|
|
125
123
|
let r = rotr32(significand * 184254097, 4);
|
|
@@ -161,8 +159,6 @@ function checkDivisibilityAndDivideByPow10_32_1(n: u32): u64 {
|
|
|
161
159
|
return ((<u64>(prod >>> 16)) << 32) | ((prod & 0xffff) < 6554 ? 1 : 0);
|
|
162
160
|
}
|
|
163
161
|
|
|
164
|
-
|
|
165
|
-
@inline
|
|
166
162
|
function computeMul64(u: u64, cacheHigh: u64, cacheLow: u64): void {
|
|
167
163
|
const high = umul128_upper64(u, cacheHigh);
|
|
168
164
|
|
|
@@ -187,8 +183,6 @@ function computeMul64(u: u64, cacheHigh: u64, cacheLow: u64): void {
|
|
|
187
183
|
_dbMulIsInteger = rLow == 0;
|
|
188
184
|
}
|
|
189
185
|
|
|
190
|
-
|
|
191
|
-
@inline
|
|
192
186
|
function computeMulParity64(
|
|
193
187
|
twoF: u64,
|
|
194
188
|
cacheHigh: u64,
|
|
@@ -233,8 +227,6 @@ function computeRoundUpShorter64(cacheHigh: u64, beta: i32): u64 {
|
|
|
233
227
|
return ((cacheHigh >>> (10 - beta)) + 1) >>> 1;
|
|
234
228
|
}
|
|
235
229
|
|
|
236
|
-
|
|
237
|
-
@inline
|
|
238
230
|
function removeTrailingZeros64(significand: u64): u64 {
|
|
239
231
|
let exponent = 0;
|
|
240
232
|
let r = rotr64(significand * 28999941890838049, 8);
|
|
@@ -59,8 +59,7 @@ function initPairs(): void {
|
|
|
59
59
|
* a byte offset). Caller must ensure the buffer has at least 20 bytes
|
|
60
60
|
* available (max 10 chars).
|
|
61
61
|
*/
|
|
62
|
-
|
|
63
|
-
@inline export function itoaU32(buf: usize, v: u32): u32 {
|
|
62
|
+
export function itoaU32(buf: usize, v: u32): u32 {
|
|
64
63
|
if (v < 10) {
|
|
65
64
|
store<u16>(buf, <u16>(v + 0x30));
|
|
66
65
|
return 1;
|
|
@@ -165,8 +164,7 @@ function initPairs(): void {
|
|
|
165
164
|
* Writes a u32 in the range 0..99_999_999 as exactly 8 UTF-16 chars with
|
|
166
165
|
* leading zeros. Used by the u64 path to emit trailing groups of 8 digits.
|
|
167
166
|
*/
|
|
168
|
-
|
|
169
|
-
@inline function writeU32Padded8(buf: usize, v: u32): void {
|
|
167
|
+
function writeU32Padded8(buf: usize, v: u32): void {
|
|
170
168
|
const a = v / 1_000_000;
|
|
171
169
|
let rest = v - a * 1_000_000;
|
|
172
170
|
const b = rest / 10_000;
|
|
@@ -187,8 +185,7 @@ function initPairs(): void {
|
|
|
187
185
|
* For 17+ digit values (which still fit in u64 < 1.8e19), repeat.
|
|
188
186
|
* Caller must ensure the buffer has at least 40 bytes available.
|
|
189
187
|
*/
|
|
190
|
-
|
|
191
|
-
@inline export function itoaU64(buf: usize, v: u64): u32 {
|
|
188
|
+
export function itoaU64(buf: usize, v: u64): u32 {
|
|
192
189
|
if (v <= <u64>u32.MAX_VALUE) {
|
|
193
190
|
return itoaU32(buf, <u32>v);
|
|
194
191
|
}
|
|
@@ -64,8 +64,7 @@ const MAX_EXACT_MANTISSA: u64 = 1 << 53;
|
|
|
64
64
|
* exact through accumulation and only loses precision at the final
|
|
65
65
|
* `<f64>` cast.
|
|
66
66
|
*/
|
|
67
|
-
|
|
68
|
-
@inline export function parseFloatFast<T>(srcStart: usize, srcEnd: usize): T {
|
|
67
|
+
export function parseFloatFast<T>(srcStart: usize, srcEnd: usize): T {
|
|
69
68
|
const origStart = srcStart;
|
|
70
69
|
let p = srcStart;
|
|
71
70
|
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,160 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACK_SLASH,
|
|
3
|
+
BRACE_LEFT,
|
|
4
|
+
BRACE_RIGHT,
|
|
5
|
+
BRACKET_LEFT,
|
|
6
|
+
BRACKET_RIGHT,
|
|
7
|
+
COMMA,
|
|
8
|
+
QUOTE,
|
|
9
|
+
} from "../custom/chars";
|
|
10
|
+
import { isSpace } from "./isSpace";
|
|
11
|
+
|
|
12
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
13
|
+
@lazy const SPLAT_QUOTE = i16x8.splat(<i16>QUOTE);
|
|
14
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
15
|
+
@lazy const SPLAT_BACK_SLASH = i16x8.splat(<i16>BACK_SLASH);
|
|
16
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
17
|
+
@lazy const SPLAT_BRACE_RIGHT = i16x8.splat(<i16>BRACE_RIGHT);
|
|
18
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
19
|
+
@lazy const SPLAT_BRACKET_RIGHT = i16x8.splat(<i16>BRACKET_RIGHT);
|
|
20
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
21
|
+
@lazy const SPLAT_COMMA = i16x8.splat(<i16>COMMA);
|
|
22
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
23
|
+
@lazy const SPLAT_SPACE = i16x8.splat(0x20);
|
|
24
|
+
// JSON whitespace besides space is the contiguous range 0x09..0x0d
|
|
25
|
+
// (tab/LF/VT/FF/CR), matched as `(c - 9) u<= 4` — one sub + one unsigned
|
|
26
|
+
// compare instead of five equality tests. Exact: matches `isSpace` with no
|
|
27
|
+
// false positives.
|
|
28
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
29
|
+
@lazy const SPLAT_WS_LO = i16x8.splat(0x09);
|
|
30
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
31
|
+
@lazy const SPLAT_WS_SPAN = i16x8.splat(0x04);
|
|
32
|
+
|
|
33
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
34
|
+
@inline function quoteOrBackslashMask(block: v128): i32 {
|
|
35
|
+
return i16x8.bitmask(
|
|
36
|
+
v128.or(i16x8.eq(block, SPLAT_QUOTE), i16x8.eq(block, SPLAT_BACK_SLASH)),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
41
|
+
@inline function scalarTerminatorMask(block: v128): i32 {
|
|
42
|
+
const structural = v128.or(
|
|
43
|
+
v128.or(i16x8.eq(block, SPLAT_COMMA), i16x8.eq(block, SPLAT_BRACE_RIGHT)),
|
|
44
|
+
i16x8.eq(block, SPLAT_BRACKET_RIGHT),
|
|
45
|
+
);
|
|
46
|
+
// (c - 9) u<= 4 covers tab/LF/VT/FF/CR; space handled separately.
|
|
47
|
+
const whitespace = v128.or(
|
|
48
|
+
i16x8.le_u(i16x8.sub(block, SPLAT_WS_LO), SPLAT_WS_SPAN),
|
|
49
|
+
i16x8.eq(block, SPLAT_SPACE),
|
|
50
|
+
);
|
|
51
|
+
return i16x8.bitmask(v128.or(structural, whitespace));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function scanQuotedValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
55
|
+
srcStart += 2;
|
|
56
|
+
const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
|
|
57
|
+
|
|
58
|
+
while (srcStart <= srcEnd16) {
|
|
59
|
+
const block = load<v128>(srcStart);
|
|
60
|
+
const mask = quoteOrBackslashMask(block);
|
|
61
|
+
if (mask == 0) {
|
|
62
|
+
srcStart += 16;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const laneIdx = usize(ctz(mask) << 1);
|
|
67
|
+
const srcIdx = srcStart + laneIdx;
|
|
68
|
+
const code = load<u16>(srcIdx);
|
|
69
|
+
if (code == QUOTE) return srcIdx + 2;
|
|
70
|
+
if (srcIdx + 2 >= srcEnd) return 0;
|
|
71
|
+
srcStart = srcIdx + 4;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
while (srcStart < srcEnd) {
|
|
75
|
+
const code = load<u16>(srcStart);
|
|
76
|
+
if (code == QUOTE) return srcStart + 2;
|
|
77
|
+
if (code == BACK_SLASH) {
|
|
78
|
+
if (srcStart + 2 >= srcEnd) return 0;
|
|
79
|
+
srcStart += 4;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
srcStart += 2;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function scanCompositeValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
89
|
+
// Track object/array depth scalar-side (structural tokens are sparse and a
|
|
90
|
+
// bulk token-mask scan loses to a tight loop on token-dense objects), but
|
|
91
|
+
// skip nested string VALUES with the vectorized quoted scan — that's where
|
|
92
|
+
// the long runs (URLs, base64, prose) actually are.
|
|
93
|
+
let depth: i32 = 1;
|
|
94
|
+
let ptr = srcStart + 2;
|
|
95
|
+
while (ptr < srcEnd) {
|
|
96
|
+
const code = load<u16>(ptr);
|
|
97
|
+
if (code == QUOTE) {
|
|
98
|
+
ptr = scanQuotedValueEnd_SIMD(ptr, srcEnd);
|
|
99
|
+
if (!ptr) return 0;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (code == BRACE_LEFT || code == BRACKET_LEFT) {
|
|
103
|
+
depth++;
|
|
104
|
+
} else if (code == BRACE_RIGHT || code == BRACKET_RIGHT) {
|
|
105
|
+
if (--depth == 0) return ptr + 2;
|
|
106
|
+
}
|
|
107
|
+
ptr += 2;
|
|
108
|
+
}
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function scanScalarValueEnd_SIMD(srcStart: usize, srcEnd: usize): usize {
|
|
113
|
+
const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
|
|
114
|
+
while (srcStart <= srcEnd16) {
|
|
115
|
+
const mask = scalarTerminatorMask(load<v128>(srcStart));
|
|
116
|
+
if (mask == 0) {
|
|
117
|
+
srcStart += 16;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
return srcStart + usize(ctz(mask) << 1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
while (srcStart < srcEnd) {
|
|
124
|
+
const code = load<u16>(srcStart);
|
|
125
|
+
if (
|
|
126
|
+
code == COMMA ||
|
|
127
|
+
code == BRACKET_RIGHT ||
|
|
128
|
+
code == BRACE_RIGHT ||
|
|
129
|
+
isSpace(code)
|
|
130
|
+
)
|
|
131
|
+
return srcStart;
|
|
132
|
+
srcStart += 2;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return srcStart;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
139
|
+
@inline export function scanValueEnd_SIMD<T>(
|
|
140
|
+
srcStart: usize,
|
|
141
|
+
srcEnd: usize,
|
|
142
|
+
): usize {
|
|
143
|
+
if (srcStart >= srcEnd) return 0;
|
|
144
|
+
const first = load<u16>(srcStart);
|
|
145
|
+
|
|
146
|
+
if (isString<nonnull<T>>() && first == QUOTE)
|
|
147
|
+
return scanQuotedValueEnd_SIMD(srcStart, srcEnd);
|
|
148
|
+
if (isArray<nonnull<T>>() && first == BRACKET_LEFT)
|
|
149
|
+
return scanCompositeValueEnd_SIMD(srcStart, srcEnd);
|
|
150
|
+
if (
|
|
151
|
+
(isManaged<nonnull<T>>() || isReference<nonnull<T>>()) &&
|
|
152
|
+
first == BRACE_LEFT
|
|
153
|
+
)
|
|
154
|
+
return scanCompositeValueEnd_SIMD(srcStart, srcEnd);
|
|
155
|
+
|
|
156
|
+
if (first == QUOTE) return scanQuotedValueEnd_SIMD(srcStart, srcEnd);
|
|
157
|
+
if (first == BRACE_LEFT || first == BRACKET_LEFT)
|
|
158
|
+
return scanCompositeValueEnd_SIMD(srcStart, srcEnd);
|
|
159
|
+
return scanScalarValueEnd_SIMD(srcStart, srcEnd);
|
|
160
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACK_SLASH,
|
|
3
|
+
BRACE_LEFT,
|
|
4
|
+
BRACE_RIGHT,
|
|
5
|
+
BRACKET_LEFT,
|
|
6
|
+
BRACKET_RIGHT,
|
|
7
|
+
COMMA,
|
|
8
|
+
QUOTE,
|
|
9
|
+
} from "../custom/chars";
|
|
10
|
+
import { isSpace } from "./isSpace";
|
|
11
|
+
|
|
12
|
+
// SWAR analogue of `scanValueEndSimd.ts`, processing four UTF-16 lanes per
|
|
13
|
+
// 64-bit word for the SWAR build mode (no SIMD feature). Each mask is a fast
|
|
14
|
+
// FILTER — a matched lane is re-checked with a real `load<u16>` before acting —
|
|
15
|
+
// so the masks may over-match non-ASCII lanes whose low byte equals a target
|
|
16
|
+
// (the verify rejects them). Lane byte offset within a hit word is
|
|
17
|
+
// `ctz(mask) >> 3` (detection bit sits at lane*16 + 7).
|
|
18
|
+
|
|
19
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
20
|
+
@inline const ONES: u64 = 0x0001_0001_0001_0001;
|
|
21
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
22
|
+
@inline const HI: u64 = 0x0080_0080_0080_0080;
|
|
23
|
+
|
|
24
|
+
// 16-bit-lane "equals" partials (pre-`& HI`); OR several, then `& HI` once.
|
|
25
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
26
|
+
@inline function eqPart(block: u64, splat: u64): u64 {
|
|
27
|
+
const t = block ^ splat;
|
|
28
|
+
return (t - ONES) & ~t;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
32
|
+
@inline const S_QUOTE: u64 = 0x0022_0022_0022_0022;
|
|
33
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
34
|
+
@inline const S_BACK_SLASH: u64 = 0x005c_005c_005c_005c;
|
|
35
|
+
|
|
36
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
37
|
+
@inline function quoteOrBackslashMask(block: u64): u64 {
|
|
38
|
+
return (eqPart(block, S_QUOTE) | eqPart(block, S_BACK_SLASH)) & HI;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function scanQuotedValueEnd_SWAR(srcStart: usize, srcEnd: usize): usize {
|
|
42
|
+
srcStart += 2;
|
|
43
|
+
const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
|
|
44
|
+
|
|
45
|
+
// Fast-skip 8-byte windows until a real quote (return) or a backslash, then
|
|
46
|
+
// hand off to the precise scalar tail (which resolves escape runs). The mask
|
|
47
|
+
// is a filter, so each candidate lane is verified with a real `load<u16>`;
|
|
48
|
+
// non-ASCII lanes that spuriously match are skipped (neither quote nor
|
|
49
|
+
// backslash), and `srcStart` is left at the window start for the tail.
|
|
50
|
+
while (srcStart <= srcEnd8) {
|
|
51
|
+
let mask = quoteOrBackslashMask(load<u64>(srcStart));
|
|
52
|
+
if (mask == 0) {
|
|
53
|
+
srcStart += 8;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
do {
|
|
57
|
+
const srcIdx = srcStart + (usize(ctz(mask)) >> 3);
|
|
58
|
+
mask &= mask - 1;
|
|
59
|
+
const code = load<u16>(srcIdx);
|
|
60
|
+
if (code == QUOTE) return srcIdx + 2;
|
|
61
|
+
if (code == BACK_SLASH) break;
|
|
62
|
+
} while (mask != 0);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
while (srcStart < srcEnd) {
|
|
67
|
+
const code = load<u16>(srcStart);
|
|
68
|
+
if (code == QUOTE && load<u16>(srcStart - 2) != BACK_SLASH)
|
|
69
|
+
return srcStart + 2;
|
|
70
|
+
srcStart += 2;
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function scanCompositeValueEnd_SWAR(srcStart: usize, srcEnd: usize): usize {
|
|
76
|
+
// Scalar depth tracking (structural tokens are sparse; a bulk token-mask scan
|
|
77
|
+
// loses to a tight loop on token-dense objects) with SWAR quoted-skip for
|
|
78
|
+
// nested string values — where the long runs are.
|
|
79
|
+
let depth: i32 = 1;
|
|
80
|
+
let ptr = srcStart + 2;
|
|
81
|
+
while (ptr < srcEnd) {
|
|
82
|
+
const code = load<u16>(ptr);
|
|
83
|
+
if (code == QUOTE) {
|
|
84
|
+
ptr = scanQuotedValueEnd_SWAR(ptr, srcEnd);
|
|
85
|
+
if (!ptr) return 0;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (code == BRACE_LEFT || code == BRACKET_LEFT) {
|
|
89
|
+
depth++;
|
|
90
|
+
} else if (code == BRACE_RIGHT || code == BRACKET_RIGHT) {
|
|
91
|
+
if (--depth == 0) return ptr + 2;
|
|
92
|
+
}
|
|
93
|
+
ptr += 2;
|
|
94
|
+
}
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function scanScalarValueEnd_SWAR(srcStart: usize, srcEnd: usize): usize {
|
|
99
|
+
// Scalars (number/true/false/null) are short, so a plain scalar terminator
|
|
100
|
+
// scan beats setting up SWAR masks per word.
|
|
101
|
+
while (srcStart < srcEnd) {
|
|
102
|
+
const code = load<u16>(srcStart);
|
|
103
|
+
if (
|
|
104
|
+
code == COMMA ||
|
|
105
|
+
code == BRACKET_RIGHT ||
|
|
106
|
+
code == BRACE_RIGHT ||
|
|
107
|
+
isSpace(code)
|
|
108
|
+
)
|
|
109
|
+
return srcStart;
|
|
110
|
+
srcStart += 2;
|
|
111
|
+
}
|
|
112
|
+
return srcStart;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* SWAR `scanValueEnd`: position just past the value at `srcStart`. Strings and
|
|
117
|
+
* objects/arrays use the SWAR token scans above; scalars use a short scalar
|
|
118
|
+
* loop. Returns 0 on empty input or an unterminated string/composite.
|
|
119
|
+
*/
|
|
120
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
121
|
+
@inline export function scanValueEnd_SWAR<T>(
|
|
122
|
+
srcStart: usize,
|
|
123
|
+
srcEnd: usize,
|
|
124
|
+
): usize {
|
|
125
|
+
if (srcStart >= srcEnd) return 0;
|
|
126
|
+
const first = load<u16>(srcStart);
|
|
127
|
+
|
|
128
|
+
if (isString<nonnull<T>>() && first == QUOTE)
|
|
129
|
+
return scanQuotedValueEnd_SWAR(srcStart, srcEnd);
|
|
130
|
+
if (isArray<nonnull<T>>() && first == BRACKET_LEFT)
|
|
131
|
+
return scanCompositeValueEnd_SWAR(srcStart, srcEnd);
|
|
132
|
+
if (
|
|
133
|
+
(isManaged<nonnull<T>>() || isReference<nonnull<T>>()) &&
|
|
134
|
+
first == BRACE_LEFT
|
|
135
|
+
)
|
|
136
|
+
return scanCompositeValueEnd_SWAR(srcStart, srcEnd);
|
|
137
|
+
|
|
138
|
+
if (first == QUOTE) return scanQuotedValueEnd_SWAR(srcStart, srcEnd);
|
|
139
|
+
if (first == BRACE_LEFT || first == BRACKET_LEFT)
|
|
140
|
+
return scanCompositeValueEnd_SWAR(srcStart, srcEnd);
|
|
141
|
+
return scanScalarValueEnd_SWAR(srcStart, srcEnd);
|
|
142
|
+
}
|
|
@@ -54,8 +54,7 @@ const POWERS5: usize = memory.data<i32>([
|
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
@inline function scaledown(significand: u64, exp: i32): f64 {
|
|
57
|
+
function scaledown(significand: u64, exp: i32): f64 {
|
|
59
58
|
const denom: u64 = 6103515625; // 1e14 * 0x1p-14
|
|
60
59
|
const scale = reinterpret<f64>(0x3f06849b86a12b9b); // 1e-14 * 0x1p32
|
|
61
60
|
|
|
@@ -82,8 +81,7 @@ const POWERS5: usize = memory.data<i32>([
|
|
|
82
81
|
return NativeMath.scalbn(<f64>significand, <i32>shift);
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
@inline function scaleup(significand: u64, exp: i32): f64 {
|
|
84
|
+
function scaleup(significand: u64, exp: i32): f64 {
|
|
87
85
|
const coeff: u32 = 1220703125; // 1e13 * 0x1p-13;
|
|
88
86
|
let shift = ctz(significand);
|
|
89
87
|
significand >>= shift;
|
|
@@ -111,8 +109,7 @@ const POWERS5: usize = memory.data<i32>([
|
|
|
111
109
|
* @param exp Decimal exponent (e.g. for "12.34" pass 1234 and -2)
|
|
112
110
|
* @returns The correctly rounded f64, or 0 / Infinity at the extremes.
|
|
113
111
|
*/
|
|
114
|
-
|
|
115
|
-
@inline export function scientific(significand: u64, exp: i32): f64 {
|
|
112
|
+
export function scientific(significand: u64, exp: i32): f64 {
|
|
116
113
|
if (!significand || exp < -342) return 0;
|
|
117
114
|
if (exp > 308) return Infinity;
|
|
118
115
|
let significandf = <f64>significand;
|
|
@@ -82,8 +82,7 @@
|
|
|
82
82
|
* @param srcStart Pointer to 16 source bytes (8 UTF-16 chars).
|
|
83
83
|
* @returns The parsed 8-digit value, or `U32.MAX_VALUE` on invalid input.
|
|
84
84
|
*/
|
|
85
|
-
|
|
86
|
-
@inline export function parse8Digits_SIMD(srcStart: usize): u32 {
|
|
85
|
+
export function parse8Digits_SIMD(srcStart: usize): u32 {
|
|
87
86
|
const block = load<v128>(srcStart);
|
|
88
87
|
const digits = i16x8.sub(block, SPLAT_30);
|
|
89
88
|
if (v128.any_true(i16x8.gt_u(digits, SPLAT_09))) return U32.MAX_VALUE;
|
|
@@ -104,8 +103,7 @@
|
|
|
104
103
|
* @param srcStart Pointer to 16 source bytes (8 UTF-16 chars).
|
|
105
104
|
* @returns The parsed 8-digit value.
|
|
106
105
|
*/
|
|
107
|
-
|
|
108
|
-
@inline export function parse8Digits_SIMD_Unsafe(srcStart: usize): u32 {
|
|
106
|
+
export function parse8Digits_SIMD_Unsafe(srcStart: usize): u32 {
|
|
109
107
|
const block = load<v128>(srcStart);
|
|
110
108
|
const digits = i16x8.sub(block, SPLAT_30);
|
|
111
109
|
const packed = i8x16.narrow_i16x8_u(digits, ZERO_I16X8);
|
|
@@ -132,8 +130,7 @@
|
|
|
132
130
|
* @param srcStart Pointer to 32 source bytes (16 UTF-16 chars).
|
|
133
131
|
* @returns The parsed 16-digit value, or `U64.MAX_VALUE` on invalid input.
|
|
134
132
|
*/
|
|
135
|
-
|
|
136
|
-
@inline export function parse16Digits_SIMD(srcStart: usize): u64 {
|
|
133
|
+
export function parse16Digits_SIMD(srcStart: usize): u64 {
|
|
137
134
|
const block0 = load<v128>(srcStart);
|
|
138
135
|
const block1 = load<v128>(srcStart, 16);
|
|
139
136
|
|
|
@@ -168,8 +165,7 @@
|
|
|
168
165
|
* @param srcStart Pointer to 32 source bytes (16 UTF-16 chars).
|
|
169
166
|
* @returns The parsed 16-digit value.
|
|
170
167
|
*/
|
|
171
|
-
|
|
172
|
-
@inline export function parse16Digits_SIMD_Unsafe(srcStart: usize): u64 {
|
|
168
|
+
export function parse16Digits_SIMD_Unsafe(srcStart: usize): u64 {
|
|
173
169
|
const block0 = load<v128>(srcStart);
|
|
174
170
|
const block1 = load<v128>(srcStart, 16);
|
|
175
171
|
const digits0 = i16x8.sub(block0, SPLAT_30);
|
package/assembly/util/snp.ts
CHANGED
|
@@ -6,11 +6,7 @@
|
|
|
6
6
|
import { POW_TEN_TABLE_32, POW_TEN_TABLE_64 } from "../globals/tables";
|
|
7
7
|
import { atoi } from "./atoi";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
@inline export function snp<T extends number>(
|
|
11
|
-
srcStart: usize,
|
|
12
|
-
srcEnd: usize,
|
|
13
|
-
): T {
|
|
9
|
+
export function snp<T extends number>(srcStart: usize, srcEnd: usize): T {
|
|
14
10
|
// @ts-ignore: type
|
|
15
11
|
let val: T = 0;
|
|
16
12
|
let char = load<u16>(srcStart) - 48;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { BACK_SLASH, QUOTE } from "../custom/chars";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
@inline export function isUnescapedQuote(ptr: usize): bool {
|
|
3
|
+
export function isUnescapedQuote(ptr: usize): bool {
|
|
5
4
|
if (load<u16>(ptr) != QUOTE) return false;
|
|
6
5
|
|
|
7
6
|
let escaped = false;
|
|
@@ -13,8 +12,7 @@ import { BACK_SLASH, QUOTE } from "../custom/chars";
|
|
|
13
12
|
return !escaped;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
@inline export function scanStringEnd(ptr: usize, end: usize): usize {
|
|
15
|
+
export function scanStringEnd(ptr: usize, end: usize): usize {
|
|
18
16
|
ptr += 2;
|
|
19
17
|
while (ptr < end) {
|
|
20
18
|
if (load<u16>(ptr) == QUOTE && isUnescapedQuote(ptr)) return ptr;
|
|
@@ -97,8 +97,7 @@ const FINAL_4_MAGIC: u64 = 0x0000_0064_0000_0001;
|
|
|
97
97
|
* @param hi The second `u64`, four UTF-16 code units.
|
|
98
98
|
* @returns The parsed 8-digit value, or `U32.MAX_VALUE` on invalid input.
|
|
99
99
|
*/
|
|
100
|
-
|
|
101
|
-
@inline export function parse8Digits_PairMul(lo: u64, hi: u64): u32 {
|
|
100
|
+
export function parse8Digits_PairMul(lo: u64, hi: u64): u32 {
|
|
102
101
|
const loDigits = lo - ZERO_4;
|
|
103
102
|
const hiDigits = hi - ZERO_4;
|
|
104
103
|
const bad =
|
|
@@ -174,8 +173,7 @@ const FINAL_4_MAGIC: u64 = 0x0000_0064_0000_0001;
|
|
|
174
173
|
* @param srcStart Pointer to the start of 32 source bytes (16 UTF-16 chars).
|
|
175
174
|
* @returns The parsed 16-digit value, or `U64.MAX_VALUE` on invalid input.
|
|
176
175
|
*/
|
|
177
|
-
|
|
178
|
-
@inline export function parse16Digits_SWAR(srcStart: usize): u64 {
|
|
176
|
+
export function parse16Digits_SWAR(srcStart: usize): u64 {
|
|
179
177
|
const b0 = load<u64>(srcStart);
|
|
180
178
|
const b1 = load<u64>(srcStart, 8);
|
|
181
179
|
const b2 = load<u64>(srcStart, 16);
|
|
@@ -220,8 +218,7 @@ const FINAL_4_MAGIC: u64 = 0x0000_0064_0000_0001;
|
|
|
220
218
|
* @param srcStart Pointer to the start of 32 source bytes (16 UTF-16 chars).
|
|
221
219
|
* @returns The parsed 16-digit value.
|
|
222
220
|
*/
|
|
223
|
-
|
|
224
|
-
@inline export function parse16Digits_SWAR_Unsafe(srcStart: usize): u64 {
|
|
221
|
+
export function parse16Digits_SWAR_Unsafe(srcStart: usize): u64 {
|
|
225
222
|
const b0 = load<u64>(srcStart);
|
|
226
223
|
const b1 = load<u64>(srcStart, 8);
|
|
227
224
|
const b2 = load<u64>(srcStart, 16);
|
package/lib/as-bs.ts
CHANGED
|
@@ -287,6 +287,43 @@ export namespace bs {
|
|
|
287
287
|
}
|
|
288
288
|
return changetype<T>(out);
|
|
289
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Like `out<T>()`, but writes the finished bytes into the existing object
|
|
293
|
+
* `target`, reusing its allocation: an in-place overwrite when the size
|
|
294
|
+
* already matches, `__renew` when it differs. Falls back to a fresh
|
|
295
|
+
* `out<T>()` when `target` is null or a static (`< __heap_base`), so callers
|
|
296
|
+
* can pass an empty/uninitialized target on the first call. Mirrors the reuse
|
|
297
|
+
* policy of `toField`, but for the top-level return value.
|
|
298
|
+
*/
|
|
299
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
300
|
+
@inline export function outTo<T>(target: usize): T {
|
|
301
|
+
if (target < __heap_base) return out<T>();
|
|
302
|
+
let len: usize;
|
|
303
|
+
let src: usize;
|
|
304
|
+
if (cacheOutput === 0) {
|
|
305
|
+
len = offset - buffer;
|
|
306
|
+
src = buffer;
|
|
307
|
+
} else {
|
|
308
|
+
len = cacheOutputLen;
|
|
309
|
+
src = cacheOutput;
|
|
310
|
+
}
|
|
311
|
+
let dst = target;
|
|
312
|
+
if (changetype<OBJECT>(target - TOTAL_OVERHEAD).rtSize != len) {
|
|
313
|
+
// @ts-expect-error: __renew is a runtime builtin
|
|
314
|
+
dst = __renew(target, len);
|
|
315
|
+
}
|
|
316
|
+
memory.copy(dst, src, len);
|
|
317
|
+
if (cacheOutput === 0) {
|
|
318
|
+
finalizeDynamicOutput(len);
|
|
319
|
+
} else {
|
|
320
|
+
cacheOutput = 0;
|
|
321
|
+
cacheOutputLen = 0;
|
|
322
|
+
offset = buffer;
|
|
323
|
+
stackSize = 0;
|
|
324
|
+
}
|
|
325
|
+
return changetype<T>(dst);
|
|
326
|
+
}
|
|
290
327
|
}
|
|
291
328
|
|
|
292
329
|
/**
|