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
|
@@ -1,21 +1,77 @@
|
|
|
1
1
|
import { JSON } from "../..";
|
|
2
2
|
import { bs } from "../../../lib/as-bs";
|
|
3
|
-
import {
|
|
3
|
+
import { bytes } from "../../util/bytes";
|
|
4
|
+
import { QUOTE } from "../../custom/chars";
|
|
4
5
|
import { serializeBool } from "./bool";
|
|
5
6
|
import { serializeFloat32, serializeFloat64 } from "./float";
|
|
6
7
|
import { serializeInteger } from "./integer";
|
|
8
|
+
import { serializeJsonArray } from "./jsonarray";
|
|
7
9
|
import { serializeMap } from "./map";
|
|
8
10
|
import { serializeObject } from "./object";
|
|
11
|
+
import { serializeRaw } from "../naive/raw";
|
|
9
12
|
import { serializeString } from "./string";
|
|
10
13
|
import { serializeDynamic } from "./typedarray";
|
|
11
14
|
|
|
15
|
+
// True if any code unit would need JSON-escaping on serialize: a quote,
|
|
16
|
+
// backslash, control char (< 0x20), or a surrogate (handled conservatively -
|
|
17
|
+
// even valid pairs route to the full path, since they can't be bulk-copied as a
|
|
18
|
+
// plain run). A clean string is none of these, so it emits as a verbatim memcpy.
|
|
19
|
+
// @ts-ignore: decorator
|
|
20
|
+
@inline function stringNeedsEscape(src: string): bool {
|
|
21
|
+
let ptr = changetype<usize>(src);
|
|
22
|
+
const end = ptr + <usize>bytes(src);
|
|
23
|
+
while (ptr < end) {
|
|
24
|
+
const code = load<u16>(ptr);
|
|
25
|
+
if (
|
|
26
|
+
code == 0x22 ||
|
|
27
|
+
code == 0x5c ||
|
|
28
|
+
code < 0x20 ||
|
|
29
|
+
(code >= 0xd800 && code <= 0xdfff)
|
|
30
|
+
)
|
|
31
|
+
return true;
|
|
32
|
+
ptr += 2;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Fast path for a string already known to need no escaping: quote + a single
|
|
38
|
+
// bulk copy of the UTF-16 bytes + quote. Skips the per-char escape scan that
|
|
39
|
+
// serializeString would otherwise do (and its trailing second pass).
|
|
40
|
+
function serializeStringClean(src: string): void {
|
|
41
|
+
const size = <usize>bytes(src);
|
|
42
|
+
bs.proposeSize(size + 4);
|
|
43
|
+
store<u16>(bs.offset, QUOTE);
|
|
44
|
+
bs.offset += 2;
|
|
45
|
+
memory.copy(bs.offset, changetype<usize>(src), size);
|
|
46
|
+
bs.offset += size;
|
|
47
|
+
store<u16>(bs.offset, QUOTE);
|
|
48
|
+
bs.offset += 2;
|
|
49
|
+
}
|
|
50
|
+
|
|
12
51
|
export function serializeArbitrary(src: JSON.Value): void {
|
|
52
|
+
// Verbatim passthrough: an untouched (still-deferred) value emits its original
|
|
53
|
+
// source bytes directly - no materialization, no re-encoding. Peek the slice
|
|
54
|
+
// without reading `src.type` (which would force materialization).
|
|
55
|
+
const lz = src.__lazySlice();
|
|
56
|
+
if (lz != 0) {
|
|
57
|
+
const start = <usize>(lz >>> 32);
|
|
58
|
+
const size = <usize>(<u32>lz) - start;
|
|
59
|
+
bs.proposeSize(size);
|
|
60
|
+
memory.copy(bs.offset, start, size);
|
|
61
|
+
bs.offset += size;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
13
64
|
switch (src.type) {
|
|
14
65
|
case JSON.Types.Null:
|
|
15
66
|
bs.proposeSize(8);
|
|
16
67
|
store<u64>(bs.offset, 30399761348886638);
|
|
17
68
|
bs.offset += 8;
|
|
18
69
|
break;
|
|
70
|
+
case JSON.Types.Raw:
|
|
71
|
+
// A materialized JSON.Raw value (e.g. set into a JSON.Obj/Arr) emits its
|
|
72
|
+
// pre-formatted bytes as-is - without this it fell into the struct default.
|
|
73
|
+
serializeRaw(src.get<JSON.Raw>());
|
|
74
|
+
break;
|
|
19
75
|
case JSON.Types.U8:
|
|
20
76
|
serializeInteger<u8>(src.get<u8>());
|
|
21
77
|
break;
|
|
@@ -46,14 +102,24 @@ export function serializeArbitrary(src: JSON.Value): void {
|
|
|
46
102
|
case JSON.Types.F64:
|
|
47
103
|
serializeFloat64(src.get<f64>());
|
|
48
104
|
break;
|
|
49
|
-
case JSON.Types.String:
|
|
50
|
-
|
|
105
|
+
case JSON.Types.String: {
|
|
106
|
+
const str = src.get<string>();
|
|
107
|
+
// Reuse the cached escape class; classify once on first serialize. Clean
|
|
108
|
+
// strings (the common case) then emit as a single memcpy on every reuse.
|
|
109
|
+
let cls = src.__strClass();
|
|
110
|
+
if (cls == 0) {
|
|
111
|
+
cls = stringNeedsEscape(str) ? 2 : 1;
|
|
112
|
+
src.__setStrClass(cls);
|
|
113
|
+
}
|
|
114
|
+
if (cls == 1) serializeStringClean(str);
|
|
115
|
+
else serializeString(str);
|
|
51
116
|
break;
|
|
117
|
+
}
|
|
52
118
|
case JSON.Types.Bool:
|
|
53
119
|
serializeBool(src.get<bool>());
|
|
54
120
|
break;
|
|
55
121
|
case JSON.Types.Array:
|
|
56
|
-
|
|
122
|
+
serializeJsonArray(src.get<JSON.Arr>());
|
|
57
123
|
break;
|
|
58
124
|
case JSON.Types.Object:
|
|
59
125
|
serializeObject(src.get<JSON.Obj>());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { serializeBool } from "../naive/bool";
|
|
1
|
+
export { serializeBool, serializeBoolUnsafe } from "../naive/bool";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { serializeInteger } from "../naive/integer";
|
|
1
|
+
export { serializeInteger, serializeIntegerUnsafe } from "../naive/integer";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { bs } from "../../../lib/as-bs";
|
|
2
|
+
import { JSON } from "../..";
|
|
3
|
+
import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../custom/chars";
|
|
4
|
+
import { serializeArbitrary } from "./arbitrary";
|
|
5
|
+
|
|
6
|
+
export function serializeJsonArray(src: JSON.Arr): void {
|
|
7
|
+
const n = src.length;
|
|
8
|
+
|
|
9
|
+
if (n == 0) {
|
|
10
|
+
bs.proposeSize(4);
|
|
11
|
+
store<u32>(bs.offset, 6094939); // "[]"
|
|
12
|
+
bs.offset += 4;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const vals = src._vals;
|
|
17
|
+
// Source base + upper bound for resolving a deferred slot's value range.
|
|
18
|
+
const srcBase = changetype<usize>(src._src);
|
|
19
|
+
const srcEnd = srcBase + ((<usize>src._src.length) << 1);
|
|
20
|
+
|
|
21
|
+
bs.proposeSize(4 + <u32>(n - 1) * 2);
|
|
22
|
+
store<u16>(bs.offset, BRACKET_LEFT);
|
|
23
|
+
bs.offset += 2;
|
|
24
|
+
|
|
25
|
+
// A still-deferred slot is copied out verbatim from the source; an
|
|
26
|
+
// eager/materialized slot is serialized from its boxed bits.
|
|
27
|
+
for (let i = 0; i < n; i++) {
|
|
28
|
+
if (i != 0) {
|
|
29
|
+
store<u16>(bs.offset, COMMA);
|
|
30
|
+
bs.offset += 2;
|
|
31
|
+
}
|
|
32
|
+
const slot = unchecked(vals[i]);
|
|
33
|
+
if (JSON.Value.slotIsLazy(slot)) {
|
|
34
|
+
const start = JSON.Value.slotPtr(slot, srcBase);
|
|
35
|
+
const end = JSON.Value.slotEnd(slot, srcBase, srcEnd);
|
|
36
|
+
const size = end - start;
|
|
37
|
+
bs.proposeSize(size);
|
|
38
|
+
memory.copy(bs.offset, start, size);
|
|
39
|
+
bs.offset += size;
|
|
40
|
+
} else {
|
|
41
|
+
const v = JSON.Value.fromBits(slot);
|
|
42
|
+
serializeArbitrary(v);
|
|
43
|
+
// Persist a cached String escape-class back into the slot (see object.ts).
|
|
44
|
+
const nb = v.__bits();
|
|
45
|
+
if (nb != slot) unchecked((vals[i] = nb));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
store<u16>(bs.offset, BRACKET_RIGHT);
|
|
50
|
+
bs.offset += 2;
|
|
51
|
+
}
|
|
@@ -5,6 +5,23 @@ import { serializeArbitrary } from "./arbitrary";
|
|
|
5
5
|
import { serializeStringRange } from "../naive/string";
|
|
6
6
|
|
|
7
7
|
export function serializeObject(src: JSON.Obj): void {
|
|
8
|
+
const rawStart = src._rawStart;
|
|
9
|
+
if (rawStart != 0) {
|
|
10
|
+
const rawSize = src._rawEnd - rawStart;
|
|
11
|
+
// At top level, `bs.out` can copy the anchored source slice straight into
|
|
12
|
+
// the returned string. Nested objects already have parent bytes in the
|
|
13
|
+
// staging buffer and therefore keep the regular append path below.
|
|
14
|
+
if (bs.offset == bs.buffer && bs.cacheOutput == 0) {
|
|
15
|
+
bs.cacheOutput = rawStart;
|
|
16
|
+
bs.cacheOutputLen = rawSize;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
bs.proposeSize(rawSize);
|
|
20
|
+
memory.copy(bs.offset, rawStart, rawSize);
|
|
21
|
+
bs.offset += rawSize;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
const srcSize = src.size;
|
|
9
26
|
|
|
10
27
|
if (srcSize == 0) {
|
|
@@ -17,13 +34,18 @@ export function serializeObject(src: JSON.Obj): void {
|
|
|
17
34
|
const vals = src._vals;
|
|
18
35
|
const kbuf = changetype<usize>(src._kbuf);
|
|
19
36
|
const kused = src._kused;
|
|
37
|
+
// Source base + upper bound for resolving a deferred slot's value range.
|
|
38
|
+
const srcBase = changetype<usize>(src._src);
|
|
39
|
+
const srcEnd = srcBase + ((<usize>src._src.length) << 1);
|
|
20
40
|
|
|
21
41
|
bs.proposeSize(4 + <u32>(srcSize - 1) * 2 + <u32>srcSize * 2);
|
|
22
42
|
store<u16>(bs.offset, BRACE_LEFT);
|
|
23
43
|
bs.offset += 2;
|
|
24
44
|
|
|
25
|
-
// Walk the length-prefixed key buffer in lockstep with the
|
|
26
|
-
// each key straight from its slice (no per-key string
|
|
45
|
+
// Walk the length-prefixed key buffer in lockstep with the value slots,
|
|
46
|
+
// emitting each key straight from its slice (no per-key string
|
|
47
|
+
// materialization). A still-deferred slot is copied out verbatim from the
|
|
48
|
+
// source; an eager/materialized slot is serialized from its boxed bits.
|
|
27
49
|
let pos = 0;
|
|
28
50
|
let i = 0;
|
|
29
51
|
while (pos < kused) {
|
|
@@ -35,7 +57,24 @@ export function serializeObject(src: JSON.Obj): void {
|
|
|
35
57
|
serializeStringRange(kbuf + ((<usize>(pos + 1)) << 1), (<usize>len) << 1);
|
|
36
58
|
store<u16>(bs.offset, COLON);
|
|
37
59
|
bs.offset += 2;
|
|
38
|
-
|
|
60
|
+
|
|
61
|
+
const slot = unchecked(vals[i]);
|
|
62
|
+
if (JSON.Value.slotIsLazy(slot)) {
|
|
63
|
+
const start = JSON.Value.slotPtr(slot, srcBase);
|
|
64
|
+
const end = JSON.Value.slotEnd(slot, srcBase, srcEnd);
|
|
65
|
+
const size = end - start;
|
|
66
|
+
bs.proposeSize(size);
|
|
67
|
+
memory.copy(bs.offset, start, size);
|
|
68
|
+
bs.offset += size;
|
|
69
|
+
} else {
|
|
70
|
+
const v = JSON.Value.fromBits(slot);
|
|
71
|
+
serializeArbitrary(v);
|
|
72
|
+
// Persist any escape-class the serializer cached on a materialized String
|
|
73
|
+
// slot back into the flat slot, so re-serializing this object reuses it
|
|
74
|
+
// (the memcpy fast path) instead of re-scanning. No-op for other types.
|
|
75
|
+
const nb = v.__bits();
|
|
76
|
+
if (nb != slot) unchecked((vals[i] = nb));
|
|
77
|
+
}
|
|
39
78
|
pos += 1 + len;
|
|
40
79
|
i++;
|
|
41
80
|
}
|
|
@@ -3,8 +3,7 @@ import { serializeString_NAIVE } from "../naive/string";
|
|
|
3
3
|
import { serializeString_SIMD } from "../simd/string";
|
|
4
4
|
import { serializeString_SWAR } from "../swar/string";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
@inline export function serializeString(src: string): void {
|
|
6
|
+
export function serializeString(src: string): void {
|
|
8
7
|
if (JSON_MODE == JSONMode.SIMD) {
|
|
9
8
|
serializeString_SIMD(src);
|
|
10
9
|
} else if (JSON_MODE == JSONMode.NAIVE) {
|
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
serializeTypedArray,
|
|
10
10
|
} from "../naive/typedarray";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
@inline export function serializeDynamic(type: u16, data: usize): void {
|
|
12
|
+
export function serializeDynamic(type: u16, data: usize): void {
|
|
14
13
|
if (type == JSON.Types.ArrayBuffer) {
|
|
15
14
|
serializeArrayBufferUnsafe(
|
|
16
15
|
data,
|
|
@@ -5,13 +5,9 @@ import { serializeBoolUnsafe } from "./bool";
|
|
|
5
5
|
import { serializeFloat32Unsafe, serializeFloat64Unsafe } from "./float";
|
|
6
6
|
import { serializeIntegerUnsafe } from "./integer";
|
|
7
7
|
import { serializeString } from "../index/string";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
dragonbox_f64_buffered,
|
|
11
|
-
} from "../../util/dragonbox";
|
|
8
|
+
import { ensureItoaPairs, itoaFast } from "../../util/itoa-fast";
|
|
9
|
+
import { dtoa_buffered, ftoa_buffered } from "xjb-as";
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
@inline
|
|
15
11
|
function maxIntegerBytes<T extends number>(): u32 {
|
|
16
12
|
if (sizeof<T>() == 1) return isSigned<T>() ? 8 : 6;
|
|
17
13
|
if (sizeof<T>() == 2) return isSigned<T>() ? 12 : 10;
|
|
@@ -19,8 +15,6 @@ function maxIntegerBytes<T extends number>(): u32 {
|
|
|
19
15
|
return isSigned<T>() ? 42 : 40;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
@inline
|
|
24
18
|
function reservePrimitiveArray<T>(len: i32): void {
|
|
25
19
|
if (len <= 0) return;
|
|
26
20
|
if (isBoolean<T>()) {
|
|
@@ -34,8 +28,6 @@ function reservePrimitiveArray<T>(len: i32): void {
|
|
|
34
28
|
}
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
@inline
|
|
39
31
|
function serializeArrayElement<T>(value: T): void {
|
|
40
32
|
if (isString<T>()) {
|
|
41
33
|
serializeString(value as string);
|
|
@@ -78,15 +70,13 @@ function serializeArrayElement<T>(value: T): void {
|
|
|
78
70
|
// comma-store + advance per element that the generic `serializeArray` does.
|
|
79
71
|
|
|
80
72
|
// `"true,"` packed UTF-16: `t,r,u,e,,` lanes 0..4 = bytes 0..9.
|
|
81
|
-
|
|
73
|
+
const TRUE_COMMA_LO: u64 = 0x0065_0075_0072_0074;
|
|
82
74
|
|
|
83
|
-
|
|
84
|
-
@inline const TRUE_COMMA_HI: u16 = 0x002c;
|
|
75
|
+
const TRUE_COMMA_HI: u16 = 0x002c;
|
|
85
76
|
// `"false,"` packed UTF-16: `f,a,l,s,e,,` lanes 0..5 = bytes 0..11.
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
const FALSE_COMMA_LO: u64 = 0x0073_006c_0061_0066;
|
|
88
78
|
|
|
89
|
-
|
|
79
|
+
const FALSE_COMMA_HI: u32 = 0x002c_0065;
|
|
90
80
|
|
|
91
81
|
function serializeBoolArrayFast(src: bool[]): void {
|
|
92
82
|
const len = src.length;
|
|
@@ -152,8 +142,7 @@ function initU8Lut(): void {
|
|
|
152
142
|
_u8LutInited = true;
|
|
153
143
|
}
|
|
154
144
|
|
|
155
|
-
|
|
156
|
-
@inline function ensureU8Lut(): void {
|
|
145
|
+
function ensureU8Lut(): void {
|
|
157
146
|
if (!_u8LutInited) initU8Lut();
|
|
158
147
|
}
|
|
159
148
|
|
|
@@ -181,20 +170,20 @@ function serializeU8ArrayFast(src: u8[]): void {
|
|
|
181
170
|
store<u16>(bs.offset - 2, BRACKET_RIGHT);
|
|
182
171
|
}
|
|
183
172
|
|
|
184
|
-
// Specialized float-array serializer:
|
|
173
|
+
// Specialized float-array serializer: xjb writer + trailing comma in a
|
|
185
174
|
// uniform per-iteration body, then overwrite the final comma with `]`. The
|
|
186
175
|
// generic dispatcher splits the loop into "N-1 elements with comma, then
|
|
187
|
-
// last element without, then `]`"
|
|
176
|
+
// last element without, then `]`" - the branch on each `i < end` check
|
|
188
177
|
// stalls the loop's tight bs.offset advance pattern. This variant runs the
|
|
189
|
-
// same number of stores per iteration (
|
|
178
|
+
// same number of stores per iteration (xjb output + COMMA), but the
|
|
190
179
|
// uniform loop body inlines better and the trailing `]` is a single fixed
|
|
191
180
|
// overwrite outside the loop.
|
|
192
181
|
function serializeF64ArrayFast(src: f64[]): void {
|
|
193
182
|
const len = src.length;
|
|
194
|
-
// Worst case per element:
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
bs.proposeSize(4 + <u32>len *
|
|
183
|
+
// Worst case per element: a 21-char fixed integer (e.g. 1e20) or a ~24-char
|
|
184
|
+
// exponent form, + comma = ~52 bytes; plus the writer's SIMD stores can
|
|
185
|
+
// overshoot the logical end by up to 16 bytes. 80 covers both comfortably.
|
|
186
|
+
bs.proposeSize(4 + <u32>len * 80);
|
|
198
187
|
store<u16>(bs.offset, BRACKET_LEFT);
|
|
199
188
|
bs.offset += 2;
|
|
200
189
|
if (len == 0) {
|
|
@@ -210,9 +199,9 @@ function serializeF64ArrayFast(src: f64[]): void {
|
|
|
210
199
|
let offset = bs.offset;
|
|
211
200
|
for (let i: i32 = 0; i < len; i++) {
|
|
212
201
|
const v = load<f64>(dataStart + ((<usize>i) << 3));
|
|
213
|
-
|
|
214
|
-
store<u16>(offset
|
|
215
|
-
offset +=
|
|
202
|
+
offset += dtoa_buffered(offset, v) << 1;
|
|
203
|
+
store<u16>(offset, COMMA);
|
|
204
|
+
offset += 2;
|
|
216
205
|
}
|
|
217
206
|
// Overwrite the final trailing comma with `]`.
|
|
218
207
|
store<u16>(offset - 2, BRACKET_RIGHT);
|
|
@@ -221,9 +210,10 @@ function serializeF64ArrayFast(src: f64[]): void {
|
|
|
221
210
|
|
|
222
211
|
function serializeF32ArrayFast(src: f32[]): void {
|
|
223
212
|
const len = src.length;
|
|
224
|
-
// Worst case for f32 is
|
|
225
|
-
//
|
|
226
|
-
|
|
213
|
+
// Worst case for f32 is a 21-char fixed integer (e.g. 1e20 rounds to a
|
|
214
|
+
// 21-digit value), + comma = ~44 bytes, plus up to 16 bytes of SIMD store
|
|
215
|
+
// overshoot. 64 covers it.
|
|
216
|
+
bs.proposeSize(4 + <u32>len * 64);
|
|
227
217
|
store<u16>(bs.offset, BRACKET_LEFT);
|
|
228
218
|
bs.offset += 2;
|
|
229
219
|
if (len == 0) {
|
|
@@ -236,9 +226,9 @@ function serializeF32ArrayFast(src: f32[]): void {
|
|
|
236
226
|
let offset = bs.offset;
|
|
237
227
|
for (let i: i32 = 0; i < len; i++) {
|
|
238
228
|
const v = load<f32>(dataStart + ((<usize>i) << 2));
|
|
239
|
-
|
|
240
|
-
store<u16>(offset
|
|
241
|
-
offset +=
|
|
229
|
+
offset += ftoa_buffered(offset, v) << 1;
|
|
230
|
+
store<u16>(offset, COMMA);
|
|
231
|
+
offset += 2;
|
|
242
232
|
}
|
|
243
233
|
store<u16>(offset - 2, BRACKET_RIGHT);
|
|
244
234
|
bs.offset = offset;
|
|
@@ -276,6 +266,59 @@ function serializeI8ArrayFast(src: i8[]): void {
|
|
|
276
266
|
store<u16>(bs.offset - 2, BRACKET_RIGHT);
|
|
277
267
|
}
|
|
278
268
|
|
|
269
|
+
// Integer arrays wider than one byte use the same uniform trailing-comma
|
|
270
|
+
// layout as the specialized float paths. Keeping the output cursor local
|
|
271
|
+
// avoids two global `bs.offset` accesses per value, while reading dataStart
|
|
272
|
+
// directly removes the Array.__uget call left in the generic array loop.
|
|
273
|
+
function serializeIntegerArrayFast<T extends number[]>(src: T): void {
|
|
274
|
+
const len = src.length;
|
|
275
|
+
bs.proposeSize(4 + <u32>len * (maxIntegerBytes<valueof<T>>() + 2));
|
|
276
|
+
store<u16>(bs.offset, BRACKET_LEFT);
|
|
277
|
+
bs.offset += 2;
|
|
278
|
+
if (len == 0) {
|
|
279
|
+
store<u16>(bs.offset, BRACKET_RIGHT);
|
|
280
|
+
bs.offset += 2;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
ensureItoaPairs();
|
|
285
|
+
const dataStart = src.dataStart;
|
|
286
|
+
const stride = sizeof<valueof<T>>();
|
|
287
|
+
let offset = bs.offset;
|
|
288
|
+
for (let i: i32 = 0; i < len; i++) {
|
|
289
|
+
const value = load<valueof<T>>(dataStart + <usize>i * stride);
|
|
290
|
+
offset += (<usize>itoaFast<valueof<T>>(offset, value)) << 1;
|
|
291
|
+
store<u16>(offset, COMMA);
|
|
292
|
+
offset += 2;
|
|
293
|
+
}
|
|
294
|
+
store<u16>(offset - 2, BRACKET_RIGHT);
|
|
295
|
+
bs.offset = offset;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function serializeStringArrayFast(src: string[]): void {
|
|
299
|
+
const len = src.length;
|
|
300
|
+
// String serialization reserves its own payload; reserve delimiters here.
|
|
301
|
+
bs.proposeSize(4 + (len > 0 ? <u32>(len - 1) * 2 : 0));
|
|
302
|
+
store<u16>(bs.offset, BRACKET_LEFT);
|
|
303
|
+
bs.offset += 2;
|
|
304
|
+
if (len == 0) {
|
|
305
|
+
store<u16>(bs.offset, BRACKET_RIGHT);
|
|
306
|
+
bs.offset += 2;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const dataStart = src.dataStart;
|
|
311
|
+
for (let i: i32 = 0; i < len; i++) {
|
|
312
|
+
const value = changetype<string>(
|
|
313
|
+
load<usize>(dataStart + ((<usize>i) << 2)),
|
|
314
|
+
);
|
|
315
|
+
serializeString(value);
|
|
316
|
+
store<u16>(bs.offset, COMMA);
|
|
317
|
+
bs.offset += 2;
|
|
318
|
+
}
|
|
319
|
+
store<u16>(bs.offset - 2, BRACKET_RIGHT);
|
|
320
|
+
}
|
|
321
|
+
|
|
279
322
|
export function serializeArray<T extends any[]>(src: T): void {
|
|
280
323
|
// Specialized fast paths fold the per-element comma into the element write,
|
|
281
324
|
// saving one `store<u16>` + advance per iteration. AS folds the type checks
|
|
@@ -303,6 +346,16 @@ export function serializeArray<T extends any[]>(src: T): void {
|
|
|
303
346
|
serializeI8ArrayFast(changetype<i8[]>(src));
|
|
304
347
|
return;
|
|
305
348
|
}
|
|
349
|
+
if (isInteger<valueof<T>>()) {
|
|
350
|
+
// u8[] and i8[] returned through their lookup-table paths above.
|
|
351
|
+
serializeIntegerArrayFast<T>(src);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (isString<valueof<T>>()) {
|
|
355
|
+
// @ts-expect-error: T is string[]
|
|
356
|
+
serializeStringArrayFast(changetype<string[]>(src));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
306
359
|
if (isFloat<valueof<T>>() && sizeof<valueof<T>>() == 8) {
|
|
307
360
|
// @ts-expect-error: T is f64[]
|
|
308
361
|
serializeF64ArrayFast(changetype<f64[]>(src));
|
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
import { bs } from "../../../lib/as-bs";
|
|
2
|
-
import {
|
|
3
|
-
dragonbox_f32_buffered,
|
|
4
|
-
dragonbox_f64_buffered,
|
|
5
|
-
} from "../../util/dragonbox";
|
|
2
|
+
import { dtoa_buffered, ftoa_buffered } from "xjb-as";
|
|
6
3
|
|
|
4
|
+
// Float serialization is backed by xjb-as (a Schubfach/xjb shortest-decimal
|
|
5
|
+
// writer with SWAR + WASM-SIMD digit kernels). The *_buffered writers emit
|
|
6
|
+
// UTF-16 straight into the `bs` buffer and return the number of characters
|
|
7
|
+
// written, so advancing the offset is `count << 1` bytes.
|
|
7
8
|
|
|
8
|
-
@inline
|
|
9
9
|
export function serializeFloat32Unsafe(data: f32): void {
|
|
10
|
-
|
|
11
|
-
bs.offset += size;
|
|
10
|
+
bs.offset += ftoa_buffered(bs.offset, data) << 1;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
@inline
|
|
16
13
|
export function serializeFloat64Unsafe(data: f64): void {
|
|
17
|
-
|
|
18
|
-
bs.offset += size;
|
|
14
|
+
bs.offset += dtoa_buffered(bs.offset, data) << 1;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
@inline
|
|
23
17
|
export function serializeFloat32(data: f32): void {
|
|
24
|
-
bs.ensureSize(
|
|
25
|
-
const
|
|
26
|
-
bs.stackSize +=
|
|
27
|
-
bs.offset +=
|
|
18
|
+
bs.ensureSize(128);
|
|
19
|
+
const bytes = ftoa_buffered(bs.offset, data) << 1;
|
|
20
|
+
bs.stackSize += bytes;
|
|
21
|
+
bs.offset += bytes;
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
@inline
|
|
32
24
|
export function serializeFloat64(data: f64): void {
|
|
33
|
-
bs.ensureSize(
|
|
34
|
-
const
|
|
35
|
-
bs.stackSize +=
|
|
36
|
-
bs.offset +=
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// @ts-ignore: inline
|
|
40
|
-
@inline export function serializeFloat<T extends number>(data: T): void {
|
|
41
|
-
if (sizeof<T>() == 4) serializeFloat32(<f32>data);
|
|
42
|
-
else serializeFloat64(<f64>data);
|
|
25
|
+
bs.ensureSize(128);
|
|
26
|
+
const bytes = dtoa_buffered(bs.offset, data) << 1;
|
|
27
|
+
bs.stackSize += bytes;
|
|
28
|
+
bs.offset += bytes;
|
|
43
29
|
}
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { bs } from "../../../lib/as-bs";
|
|
2
2
|
import { ensureItoaPairs, itoaFast } from "../../util/itoa-fast";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
@inline
|
|
6
4
|
export function serializeIntegerUnsafe<T extends number>(data: T): void {
|
|
7
5
|
ensureItoaPairs();
|
|
8
6
|
const charsWritten = itoaFast<T>(bs.offset, data);
|
|
9
7
|
bs.offset += (<usize>charsWritten) << 1;
|
|
10
8
|
}
|
|
11
|
-
|
|
12
|
-
// @ts-ignore: inline
|
|
13
|
-
@inline export function serializeInteger<T extends number>(data: T): void {
|
|
9
|
+
export function serializeInteger<T extends number>(data: T): void {
|
|
14
10
|
ensureItoaPairs();
|
|
15
11
|
bs.ensureSize(sizeof<T>() << 3);
|
|
16
12
|
const charsWritten = itoaFast<T>(bs.offset, data);
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { bs } from "../../../lib/as-bs";
|
|
2
2
|
import { JSON } from "../..";
|
|
3
3
|
import { BRACE_LEFT, BRACE_RIGHT, COLON, COMMA } from "../../custom/chars";
|
|
4
|
+
import { serializeString } from "../index/string";
|
|
5
|
+
import { serializeRaw } from "./raw";
|
|
6
|
+
|
|
7
|
+
function serializeRawMapFast<T extends Map<any, any>>(
|
|
8
|
+
keys: Array<indexof<T>>,
|
|
9
|
+
values: Array<valueof<T>>,
|
|
10
|
+
): void {
|
|
11
|
+
const len = keys.length;
|
|
12
|
+
bs.proposeSize(4 + <u32>len * 4);
|
|
13
|
+
store<u16>(bs.offset, BRACE_LEFT);
|
|
14
|
+
bs.offset += 2;
|
|
15
|
+
|
|
16
|
+
const keyData = keys.dataStart;
|
|
17
|
+
const valueData = values.dataStart;
|
|
18
|
+
for (let i = 0; i < len; i++) {
|
|
19
|
+
const key = changetype<string>(load<usize>(keyData + ((<usize>i) << 2)));
|
|
20
|
+
const value = changetype<JSON.Raw>(
|
|
21
|
+
load<usize>(valueData + ((<usize>i) << 2)),
|
|
22
|
+
);
|
|
23
|
+
serializeString(key);
|
|
24
|
+
store<u16>(bs.offset, COLON);
|
|
25
|
+
bs.offset += 2;
|
|
26
|
+
serializeRaw(value);
|
|
27
|
+
store<u16>(bs.offset, COMMA);
|
|
28
|
+
bs.offset += 2;
|
|
29
|
+
}
|
|
30
|
+
store<u16>(bs.offset - 2, BRACE_RIGHT);
|
|
31
|
+
}
|
|
4
32
|
|
|
5
33
|
export function serializeMap<T extends Map<any, any>>(src: T): void {
|
|
6
34
|
const srcSize = src.size;
|
|
@@ -17,6 +45,15 @@ export function serializeMap<T extends Map<any, any>>(src: T): void {
|
|
|
17
45
|
let values = src.values();
|
|
18
46
|
const keyIsString = isString<indexof<T>>();
|
|
19
47
|
|
|
48
|
+
if (keyIsString && isReference<valueof<T>>()) {
|
|
49
|
+
const valueType = changetype<nonnull<valueof<T>>>(0);
|
|
50
|
+
// @ts-ignore: instanceof on the reference value type
|
|
51
|
+
if (valueType instanceof JSON.Raw) {
|
|
52
|
+
serializeRawMapFast<T>(keys, values);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
20
57
|
bs.proposeSize(4 + <u32>(srcSize - 1) * 2 + <u32>srcSize * 2);
|
|
21
58
|
|
|
22
59
|
store<u16>(bs.offset, BRACE_LEFT);
|
|
@@ -7,8 +7,7 @@ import { bytes } from "../../util";
|
|
|
7
7
|
* @param data data to serialize
|
|
8
8
|
* @returns void
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
@inline export function serializeRaw(data: JSON.Raw): void {
|
|
10
|
+
export function serializeRaw(data: JSON.Raw): void {
|
|
12
11
|
const dataSize = bytes(data.data);
|
|
13
12
|
bs.proposeSize(dataSize);
|
|
14
13
|
memory.copy(
|
|
@@ -6,8 +6,6 @@ import { serializeFloat32Unsafe, serializeFloat64Unsafe } from "./float";
|
|
|
6
6
|
import { serializeIntegerUnsafe } from "./integer";
|
|
7
7
|
import { serializeString } from "../index/string";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
@inline
|
|
11
9
|
function maxIntegerBytes<T extends number>(): u32 {
|
|
12
10
|
if (sizeof<T>() == 1) return isSigned<T>() ? 8 : 6;
|
|
13
11
|
if (sizeof<T>() == 2) return isSigned<T>() ? 12 : 10;
|
|
@@ -15,10 +13,7 @@ function maxIntegerBytes<T extends number>(): u32 {
|
|
|
15
13
|
return isSigned<T>() ? 42 : 40;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
@inline
|
|
20
16
|
function reservePrimitiveSet<T>(len: i32): void {
|
|
21
|
-
if (len <= 0) return;
|
|
22
17
|
if (isBoolean<T>()) {
|
|
23
18
|
bs.proposeSize(4 + <u32>len * 12);
|
|
24
19
|
} else if (isInteger<T>()) {
|
|
@@ -6,19 +6,13 @@ import { serializeFloat32Unsafe, serializeFloat64Unsafe } from "./float";
|
|
|
6
6
|
import { serializeIntegerUnsafe } from "./integer";
|
|
7
7
|
import { serializeString } from "../index/string";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
@inline
|
|
11
9
|
function maxIntegerBytes<T extends number>(): u32 {
|
|
12
10
|
if (sizeof<T>() == 1) return isSigned<T>() ? 8 : 6;
|
|
13
11
|
if (sizeof<T>() == 2) return isSigned<T>() ? 12 : 10;
|
|
14
12
|
if (sizeof<T>() == 4) return isSigned<T>() ? 22 : 20;
|
|
15
13
|
return isSigned<T>() ? 42 : 40;
|
|
16
14
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@inline
|
|
20
15
|
function reservePrimitiveStaticArray<T>(len: i32): void {
|
|
21
|
-
if (len <= 0) return;
|
|
22
16
|
if (isBoolean<T>()) {
|
|
23
17
|
bs.proposeSize(4 + <u32>len * 12);
|
|
24
18
|
} else if (isInteger<T>()) {
|