json-as 1.3.6 → 1.3.7
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 +13 -0
- package/assembly/deserialize/helpers/uint.ts +4 -1
- package/assembly/deserialize/index/arbitrary.ts +5 -1
- package/assembly/deserialize/index/array.ts +13 -3
- package/assembly/deserialize/index/integer.ts +68 -1
- package/assembly/deserialize/index/string.ts +4 -1
- package/assembly/deserialize/index/typedarray.ts +13 -3
- package/assembly/deserialize/index/unsigned.ts +78 -1
- package/assembly/deserialize/simd/array/integer.ts +327 -50
- package/assembly/deserialize/simd/integer.ts +233 -0
- package/assembly/deserialize/simd/string.ts +45 -11
- package/assembly/deserialize/simple/arbitrary.ts +11 -4
- package/assembly/deserialize/simple/array/arbitrary.ts +24 -5
- package/assembly/deserialize/simple/array/array.ts +8 -2
- package/assembly/deserialize/simple/array/bool.ts +38 -7
- package/assembly/deserialize/simple/array/box.ts +8 -2
- package/assembly/deserialize/simple/array/float.ts +36 -9
- package/assembly/deserialize/simple/array/generic.ts +12 -4
- package/assembly/deserialize/simple/array/integer.ts +8 -2
- package/assembly/deserialize/simple/array/map.ts +26 -6
- package/assembly/deserialize/simple/array/object.ts +26 -6
- package/assembly/deserialize/simple/array/raw.ts +34 -7
- package/assembly/deserialize/simple/array/string.ts +8 -2
- package/assembly/deserialize/simple/array/struct.ts +26 -6
- package/assembly/deserialize/simple/array.ts +13 -3
- package/assembly/deserialize/simple/bool.ts +6 -2
- package/assembly/deserialize/simple/float.ts +6 -1
- package/assembly/deserialize/simple/integer.ts +10 -2
- package/assembly/deserialize/simple/map.ts +95 -22
- package/assembly/deserialize/simple/object.ts +63 -14
- package/assembly/deserialize/simple/raw.ts +4 -1
- package/assembly/deserialize/simple/set.ts +59 -14
- package/assembly/deserialize/simple/staticarray/string.ts +11 -3
- package/assembly/deserialize/simple/staticarray.ts +64 -14
- package/assembly/deserialize/simple/string.ts +5 -92
- package/assembly/deserialize/simple/struct.ts +5 -1
- package/assembly/deserialize/simple/typedarray.ts +16 -3
- package/assembly/deserialize/simple/unsigned.ts +10 -15
- package/assembly/deserialize/swar/array/arbitrary.ts +5 -1
- package/assembly/deserialize/swar/array/array.ts +30 -6
- package/assembly/deserialize/swar/array/bool.ts +22 -4
- package/assembly/deserialize/swar/array/box.ts +5 -1
- package/assembly/deserialize/swar/array/float.ts +15 -3
- package/assembly/deserialize/swar/array/generic.ts +24 -7
- package/assembly/deserialize/swar/array/integer.ts +328 -84
- package/assembly/deserialize/swar/array/map.ts +5 -1
- package/assembly/deserialize/swar/array/object.ts +27 -7
- package/assembly/deserialize/swar/array/raw.ts +5 -1
- package/assembly/deserialize/swar/array/shared.ts +36 -11
- package/assembly/deserialize/swar/array/string.ts +20 -4
- package/assembly/deserialize/swar/array/struct.ts +27 -7
- package/assembly/deserialize/swar/array.ts +19 -4
- package/assembly/deserialize/swar/integer.ts +246 -0
- package/assembly/deserialize/swar/string.ts +98 -194
- package/assembly/index.d.ts +3 -1
- package/assembly/index.ts +312 -81
- package/assembly/serialize/index/float.ts +5 -1
- package/assembly/serialize/index/typedarray.ts +25 -7
- package/assembly/serialize/simd/string.ts +6 -2
- package/assembly/serialize/simple/array.ts +179 -1
- package/assembly/serialize/simple/float.ts +4 -1
- package/assembly/serialize/simple/integer.ts +8 -9
- package/assembly/serialize/simple/map.ts +6 -2
- package/assembly/serialize/simple/raw.ts +5 -1
- package/assembly/serialize/simple/set.ts +6 -1
- package/assembly/serialize/simple/staticarray.ts +6 -1
- package/assembly/serialize/simple/string.ts +0 -1
- package/assembly/serialize/simple/typedarray.ts +10 -3
- package/assembly/serialize/swar/string.ts +18 -5
- package/assembly/util/atoi-fast.ts +81 -0
- package/assembly/util/concat.ts +5 -1
- package/assembly/util/dragonbox-cache.ts +443 -2
- package/assembly/util/dragonbox.ts +43 -14
- package/assembly/util/itoa-fast.ts +230 -0
- package/assembly/util/masks.ts +18 -1
- package/assembly/util/parsefloat-fast.ts +167 -0
- package/assembly/util/simd-int.ts +191 -0
- package/assembly/util/snp.ts +4 -1
- package/assembly/util/swar-int.ts +248 -0
- package/assembly/util/swar.ts +13 -3
- package/lib/as-bs.ts +13 -5
- package/package.json +5 -2
- package/transform/lib/builder.d.ts.map +1 -1
- package/transform/lib/builder.js +13 -5
- package/transform/lib/builder.js.map +1 -1
- package/transform/lib/index.d.ts +1 -0
- package/transform/lib/index.d.ts.map +1 -1
- package/transform/lib/index.js +1030 -241
- package/transform/lib/index.js.map +1 -1
- package/transform/lib/linkers/alias.d.ts.map +1 -1
- package/transform/lib/linkers/alias.js.map +1 -1
- package/transform/lib/linkers/custom.d.ts.map +1 -1
- package/transform/lib/linkers/custom.js +3 -2
- package/transform/lib/linkers/custom.js.map +1 -1
- package/transform/lib/linkers/imports.d.ts.map +1 -1
- package/transform/lib/linkers/imports.js.map +1 -1
- package/transform/lib/types.d.ts.map +1 -1
- package/transform/lib/types.js +54 -16
- package/transform/lib/types.js.map +1 -1
- package/transform/lib/util.d.ts.map +1 -1
- package/transform/lib/util.js +1 -1
- package/transform/lib/util.js.map +1 -1
- package/transform/lib/visitor.d.ts.map +1 -1
- package/transform/lib/visitor.js +2 -1
- package/transform/lib/visitor.js.map +1 -1
- package/assembly/custom/util.ts +0 -310
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Fast integer -> UTF-16 stringification (jeaiii-style).
|
|
2
|
+
//
|
|
3
|
+
// Background: AS std's `itoa_buffered` runs `decimalCount32` (a width
|
|
4
|
+
// classifier) then `utoa32_dec_lut` (a backward-writing div-by-10000
|
|
5
|
+
// loop). On wasm/V8 the function-call overhead between those steps shows
|
|
6
|
+
// up clearly for short widths (2-4 digits) where the per-element work is
|
|
7
|
+
// otherwise tiny.
|
|
8
|
+
//
|
|
9
|
+
// This module ports the jeaiii (James Edward Anhalt III) algorithm:
|
|
10
|
+
// - One `@inline` function per signed/unsigned u32/u64.
|
|
11
|
+
// - Width-ladder of `if (v < 10^k)` checks emits digits forward in a
|
|
12
|
+
// single pass; the same comparisons that would have driven a separate
|
|
13
|
+
// `decimalCount` are reused as the dispatch.
|
|
14
|
+
// - Each digit-pair is written as a single `store<u32>` from a 400-byte
|
|
15
|
+
// LUT (`DIGIT_PAIRS_UTF16`) keyed on `value % 100`.
|
|
16
|
+
// - All `/` and `%` are by compile-time constants (10 / 100 / 10000 /
|
|
17
|
+
// 10^8) so the wasm tier lowers them to multiply-shift.
|
|
18
|
+
//
|
|
19
|
+
// Reference H2H bench: `__benches__/custom/itoa-h2h.bench.ts`.
|
|
20
|
+
|
|
21
|
+
// 100-entry pair LUT: index `i` -> u32 holding UTF-16 chars for the
|
|
22
|
+
// zero-padded two-digit string "DD". One `store<u32>` writes the pair.
|
|
23
|
+
const DIGIT_PAIRS_UTF16: usize = memory.data(100 * 4);
|
|
24
|
+
let _pairsInited: bool = false;
|
|
25
|
+
|
|
26
|
+
function initPairs(): void {
|
|
27
|
+
for (let i: i32 = 0; i < 100; i++) {
|
|
28
|
+
const tens = u32(0x30 + i / 10);
|
|
29
|
+
const units = u32(0x30 + (i % 10));
|
|
30
|
+
store<u32>(DIGIT_PAIRS_UTF16 + ((<usize>i) << 2), tens | (units << 16));
|
|
31
|
+
}
|
|
32
|
+
_pairsInited = true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@inline export function ensureItoaPairs(): void {
|
|
37
|
+
if (!_pairsInited) initPairs();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
41
|
+
@inline function pair(i: u32): u32 {
|
|
42
|
+
return load<u32>(DIGIT_PAIRS_UTF16 + ((<usize>i) << 2));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* jeaiii-style u32 -> UTF-16 stringification, forward write.
|
|
47
|
+
* Returns the number of UTF-16 chars written (caller multiplies by 2 for
|
|
48
|
+
* a byte offset). Caller must ensure the buffer has at least 20 bytes
|
|
49
|
+
* available (max 10 chars).
|
|
50
|
+
*/
|
|
51
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
52
|
+
@inline export function itoaU32(buf: usize, v: u32): u32 {
|
|
53
|
+
if (v < 10) {
|
|
54
|
+
store<u16>(buf, <u16>(v + 0x30));
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
if (v < 100) {
|
|
58
|
+
store<u32>(buf, pair(v));
|
|
59
|
+
return 2;
|
|
60
|
+
}
|
|
61
|
+
if (v < 1_000_000) {
|
|
62
|
+
if (v < 10_000) {
|
|
63
|
+
if (v < 1_000) {
|
|
64
|
+
const h = v / 100;
|
|
65
|
+
const l = v - h * 100;
|
|
66
|
+
store<u16>(buf, <u16>(h + 0x30));
|
|
67
|
+
store<u32>(buf, pair(l), 2);
|
|
68
|
+
return 3;
|
|
69
|
+
}
|
|
70
|
+
const h = v / 100;
|
|
71
|
+
const l = v - h * 100;
|
|
72
|
+
store<u32>(buf, pair(h));
|
|
73
|
+
store<u32>(buf, pair(l), 4);
|
|
74
|
+
return 4;
|
|
75
|
+
}
|
|
76
|
+
if (v < 100_000) {
|
|
77
|
+
const hi = v / 10_000;
|
|
78
|
+
const rest = v - hi * 10_000;
|
|
79
|
+
const m = rest / 100;
|
|
80
|
+
const l = rest - m * 100;
|
|
81
|
+
store<u16>(buf, <u16>(hi + 0x30));
|
|
82
|
+
store<u32>(buf, pair(m), 2);
|
|
83
|
+
store<u32>(buf, pair(l), 6);
|
|
84
|
+
return 5;
|
|
85
|
+
}
|
|
86
|
+
const hi = v / 10_000;
|
|
87
|
+
const rest = v - hi * 10_000;
|
|
88
|
+
const m = rest / 100;
|
|
89
|
+
const l = rest - m * 100;
|
|
90
|
+
store<u32>(buf, pair(hi));
|
|
91
|
+
store<u32>(buf, pair(m), 4);
|
|
92
|
+
store<u32>(buf, pair(l), 8);
|
|
93
|
+
return 6;
|
|
94
|
+
}
|
|
95
|
+
if (v < 100_000_000) {
|
|
96
|
+
if (v < 10_000_000) {
|
|
97
|
+
const top = v / 1_000_000;
|
|
98
|
+
let rest = v - top * 1_000_000;
|
|
99
|
+
const m = rest / 10_000;
|
|
100
|
+
rest = rest - m * 10_000;
|
|
101
|
+
const n = rest / 100;
|
|
102
|
+
const l = rest - n * 100;
|
|
103
|
+
store<u16>(buf, <u16>(top + 0x30));
|
|
104
|
+
store<u32>(buf, pair(m), 2);
|
|
105
|
+
store<u32>(buf, pair(n), 6);
|
|
106
|
+
store<u32>(buf, pair(l), 10);
|
|
107
|
+
return 7;
|
|
108
|
+
}
|
|
109
|
+
const top = v / 1_000_000;
|
|
110
|
+
let rest = v - top * 1_000_000;
|
|
111
|
+
const m = rest / 10_000;
|
|
112
|
+
rest = rest - m * 10_000;
|
|
113
|
+
const n = rest / 100;
|
|
114
|
+
const l = rest - n * 100;
|
|
115
|
+
store<u32>(buf, pair(top));
|
|
116
|
+
store<u32>(buf, pair(m), 4);
|
|
117
|
+
store<u32>(buf, pair(n), 8);
|
|
118
|
+
store<u32>(buf, pair(l), 12);
|
|
119
|
+
return 8;
|
|
120
|
+
}
|
|
121
|
+
if (v < 1_000_000_000) {
|
|
122
|
+
const top = v / 100_000_000;
|
|
123
|
+
let rest = v - top * 100_000_000;
|
|
124
|
+
const a = rest / 1_000_000;
|
|
125
|
+
rest = rest - a * 1_000_000;
|
|
126
|
+
const b = rest / 10_000;
|
|
127
|
+
rest = rest - b * 10_000;
|
|
128
|
+
const c = rest / 100;
|
|
129
|
+
const d = rest - c * 100;
|
|
130
|
+
store<u16>(buf, <u16>(top + 0x30));
|
|
131
|
+
store<u32>(buf, pair(a), 2);
|
|
132
|
+
store<u32>(buf, pair(b), 6);
|
|
133
|
+
store<u32>(buf, pair(c), 10);
|
|
134
|
+
store<u32>(buf, pair(d), 14);
|
|
135
|
+
return 9;
|
|
136
|
+
}
|
|
137
|
+
const top = v / 100_000_000;
|
|
138
|
+
let rest = v - top * 100_000_000;
|
|
139
|
+
const a = rest / 1_000_000;
|
|
140
|
+
rest = rest - a * 1_000_000;
|
|
141
|
+
const b = rest / 10_000;
|
|
142
|
+
rest = rest - b * 10_000;
|
|
143
|
+
const c = rest / 100;
|
|
144
|
+
const d = rest - c * 100;
|
|
145
|
+
store<u32>(buf, pair(top));
|
|
146
|
+
store<u32>(buf, pair(a), 4);
|
|
147
|
+
store<u32>(buf, pair(b), 8);
|
|
148
|
+
store<u32>(buf, pair(c), 12);
|
|
149
|
+
store<u32>(buf, pair(d), 16);
|
|
150
|
+
return 10;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Writes a u32 in the range 0..99_999_999 as exactly 8 UTF-16 chars with
|
|
155
|
+
* leading zeros. Used by the u64 path to emit trailing groups of 8 digits.
|
|
156
|
+
*/
|
|
157
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
158
|
+
@inline function writeU32Padded8(buf: usize, v: u32): void {
|
|
159
|
+
const a = v / 1_000_000;
|
|
160
|
+
let rest = v - a * 1_000_000;
|
|
161
|
+
const b = rest / 10_000;
|
|
162
|
+
rest = rest - b * 10_000;
|
|
163
|
+
const c = rest / 100;
|
|
164
|
+
const d = rest - c * 100;
|
|
165
|
+
store<u32>(buf, pair(a));
|
|
166
|
+
store<u32>(buf, pair(b), 4);
|
|
167
|
+
store<u32>(buf, pair(c), 8);
|
|
168
|
+
store<u32>(buf, pair(d), 12);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* jeaiii-style u64 -> UTF-16 stringification.
|
|
173
|
+
* Small values delegate to `itoaU32`. For 11+ digit values, peel 8 digits
|
|
174
|
+
* from the bottom (always fits in u32), emit the remaining top via the
|
|
175
|
+
* u32 path, then emit the 8 trailing digits with leading-zero padding.
|
|
176
|
+
* For 17+ digit values (which still fit in u64 < 1.8e19), repeat.
|
|
177
|
+
* Caller must ensure the buffer has at least 40 bytes available.
|
|
178
|
+
*/
|
|
179
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
180
|
+
@inline export function itoaU64(buf: usize, v: u64): u32 {
|
|
181
|
+
if (v <= <u64>u32.MAX_VALUE) {
|
|
182
|
+
return itoaU32(buf, <u32>v);
|
|
183
|
+
}
|
|
184
|
+
const lo8 = <u32>(v % 100_000_000);
|
|
185
|
+
const hi = v / 100_000_000;
|
|
186
|
+
if (hi <= <u64>u32.MAX_VALUE) {
|
|
187
|
+
const written = itoaU32(buf, <u32>hi);
|
|
188
|
+
writeU32Padded8(buf + ((<usize>written) << 1), lo8);
|
|
189
|
+
return written + 8;
|
|
190
|
+
}
|
|
191
|
+
// 17-20 digit case: peel a second group of 8.
|
|
192
|
+
const mid8 = <u32>(hi % 100_000_000);
|
|
193
|
+
const top = <u32>(hi / 100_000_000);
|
|
194
|
+
const written = itoaU32(buf, top);
|
|
195
|
+
writeU32Padded8(buf + ((<usize>written) << 1), mid8);
|
|
196
|
+
writeU32Padded8(buf + ((<usize>written) << 1) + 16, lo8);
|
|
197
|
+
return written + 16;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Generic integer -> UTF-16 entry point. Signed types peel `-` and pass
|
|
202
|
+
* the absolute value (via two's complement negation, which works for the
|
|
203
|
+
* minimum-value edge case because `u32(-i32.MIN_VALUE) == 2147483648`
|
|
204
|
+
* and likewise for i64).
|
|
205
|
+
*
|
|
206
|
+
* Returns the number of UTF-16 chars written.
|
|
207
|
+
*/
|
|
208
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
209
|
+
@inline export function itoaFast<T extends number>(buf: usize, value: T): u32 {
|
|
210
|
+
if (sizeof<T>() <= 4) {
|
|
211
|
+
if (isSigned<T>()) {
|
|
212
|
+
let v = <i32>value;
|
|
213
|
+
if (v < 0) {
|
|
214
|
+
store<u16>(buf, 0x2d); // '-'
|
|
215
|
+
return 1 + itoaU32(buf + 2, <u32>-v);
|
|
216
|
+
}
|
|
217
|
+
return itoaU32(buf, <u32>v);
|
|
218
|
+
}
|
|
219
|
+
return itoaU32(buf, <u32>value);
|
|
220
|
+
}
|
|
221
|
+
if (isSigned<T>()) {
|
|
222
|
+
let v = <i64>value;
|
|
223
|
+
if (v < 0) {
|
|
224
|
+
store<u16>(buf, 0x2d); // '-'
|
|
225
|
+
return 1 + itoaU64(buf + 2, <u64>-v);
|
|
226
|
+
}
|
|
227
|
+
return itoaU64(buf, <u64>v);
|
|
228
|
+
}
|
|
229
|
+
return itoaU64(buf, <u64>value);
|
|
230
|
+
}
|
package/assembly/util/masks.ts
CHANGED
|
@@ -25,7 +25,24 @@ export function block_to_string(block: u64): string {
|
|
|
25
25
|
export function mask_to_string_v128(vec: v128): string {
|
|
26
26
|
let result = "0x";
|
|
27
27
|
|
|
28
|
-
const lanes: i8[] = [
|
|
28
|
+
const lanes: i8[] = [
|
|
29
|
+
i8x16.extract_lane_s(vec, 0),
|
|
30
|
+
i8x16.extract_lane_s(vec, 1),
|
|
31
|
+
i8x16.extract_lane_s(vec, 2),
|
|
32
|
+
i8x16.extract_lane_s(vec, 3),
|
|
33
|
+
i8x16.extract_lane_s(vec, 4),
|
|
34
|
+
i8x16.extract_lane_s(vec, 5),
|
|
35
|
+
i8x16.extract_lane_s(vec, 6),
|
|
36
|
+
i8x16.extract_lane_s(vec, 7),
|
|
37
|
+
i8x16.extract_lane_s(vec, 8),
|
|
38
|
+
i8x16.extract_lane_s(vec, 9),
|
|
39
|
+
i8x16.extract_lane_s(vec, 10),
|
|
40
|
+
i8x16.extract_lane_s(vec, 11),
|
|
41
|
+
i8x16.extract_lane_s(vec, 12),
|
|
42
|
+
i8x16.extract_lane_s(vec, 13),
|
|
43
|
+
i8x16.extract_lane_s(vec, 14),
|
|
44
|
+
i8x16.extract_lane_s(vec, 15),
|
|
45
|
+
];
|
|
29
46
|
|
|
30
47
|
for (let i = 15; i >= 0; i--) {
|
|
31
48
|
const byte = lanes[i];
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { ptrToStr } from "./ptrToStr";
|
|
2
|
+
|
|
3
|
+
// Lemire-style fast float parser.
|
|
4
|
+
//
|
|
5
|
+
// Reference: Daniel Lemire, "Number parsing at a gigabyte per second"
|
|
6
|
+
// (2021). https://arxiv.org/abs/2101.11408 — implemented in
|
|
7
|
+
// https://github.com/fastfloat/fast_float.
|
|
8
|
+
//
|
|
9
|
+
// The "fast path" applies when:
|
|
10
|
+
// - the mantissa fits in a u64 (<=19 decimal digits), and
|
|
11
|
+
// - the total decimal exponent is in [-22, 22], so the matching
|
|
12
|
+
// `1e<exp>` power-of-ten is representable exactly in f64.
|
|
13
|
+
//
|
|
14
|
+
// In that regime `value = mantissa * 10^exp` rounds correctly under
|
|
15
|
+
// IEEE-754: both operands are exact in f64 and the single fmul is
|
|
16
|
+
// correctly rounded, so the result is the same as the strictly-rounded
|
|
17
|
+
// reference. This covers the overwhelming majority of JSON float
|
|
18
|
+
// payloads (most fields are <20 significant digits and modest
|
|
19
|
+
// exponents). Out-of-range inputs delegate to AS std's `f64.parse`
|
|
20
|
+
// (Grisu-based; correctly rounded for all f64).
|
|
21
|
+
//
|
|
22
|
+
// Compared to the original digit-by-digit accumulator (`value = value *
|
|
23
|
+
// 10.0 + digit`) this saves both wall-time (fewer fmul/fdiv) and
|
|
24
|
+
// precision (one rounding instead of N).
|
|
25
|
+
|
|
26
|
+
// 23-entry table: 10^0 .. 10^22, all exact in f64. f32 fast-paths can
|
|
27
|
+
// reuse the same table (since 10^k for k <= 22 fits in f32 only up to
|
|
28
|
+
// 10^7, but the multiplication is done in f64 and narrowed at the end).
|
|
29
|
+
const POW10_F64_POS: usize = memory.data<f64>([
|
|
30
|
+
1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14,
|
|
31
|
+
1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const MAX_EXACT_POW10: i32 = 22;
|
|
35
|
+
// 2^53 = 9_007_199_254_740_992. Any u64 <= this is exact in f64.
|
|
36
|
+
const MAX_EXACT_MANTISSA: u64 = 1 << 53;
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@inline function loadPow10(exp: u32): f64 {
|
|
40
|
+
return load<f64>(POW10_F64_POS + ((<usize>exp) << 3));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@inline function fallback<T>(srcStart: usize, srcEnd: usize): T {
|
|
45
|
+
const s = ptrToStr(srcStart, srcEnd);
|
|
46
|
+
// @ts-ignore: type
|
|
47
|
+
const type: T = 0;
|
|
48
|
+
// @ts-ignore: type
|
|
49
|
+
if (type instanceof f64) return <T>f64.parse(s);
|
|
50
|
+
// @ts-ignore: cast
|
|
51
|
+
return <T>(<f32>f32.parse(s));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fast path for `deserializeFloat`. `srcStart..srcEnd` must contain only
|
|
56
|
+
* the float content (no surrounding whitespace, no `null`). Returns the
|
|
57
|
+
* parsed value; on the slow path falls back to `f64.parse` /
|
|
58
|
+
* `f32.parse` over the same range so behavior is preserved for every
|
|
59
|
+
* input the previous parser accepted.
|
|
60
|
+
*
|
|
61
|
+
* Structure mirrors the existing parser's split integer/fraction loops
|
|
62
|
+
* (TurboFan schedules these tighter than a single fused loop) but uses
|
|
63
|
+
* u64 accumulators throughout so a 17-digit "3.141592653589793" stays
|
|
64
|
+
* exact through accumulation and only loses precision at the final
|
|
65
|
+
* `<f64>` cast.
|
|
66
|
+
*/
|
|
67
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
68
|
+
@inline export function parseFloatFast<T>(srcStart: usize, srcEnd: usize): T {
|
|
69
|
+
const origStart = srcStart;
|
|
70
|
+
let p = srcStart;
|
|
71
|
+
let negative = false;
|
|
72
|
+
if (p < srcEnd && load<u16>(p) == 45) {
|
|
73
|
+
negative = true;
|
|
74
|
+
p += 2;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Integer part.
|
|
78
|
+
let mantissa: u64 = 0;
|
|
79
|
+
let intDigits: i32 = 0;
|
|
80
|
+
while (p < srcEnd) {
|
|
81
|
+
const d = <u32>load<u16>(p) - 48;
|
|
82
|
+
if (d > 9) break;
|
|
83
|
+
mantissa = mantissa * 10 + <u64>d;
|
|
84
|
+
intDigits++;
|
|
85
|
+
p += 2;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Optional fractional part.
|
|
89
|
+
let fracDigits: i32 = 0;
|
|
90
|
+
if (p < srcEnd && load<u16>(p) == 46) {
|
|
91
|
+
p += 2;
|
|
92
|
+
while (p < srcEnd) {
|
|
93
|
+
const d = <u32>load<u16>(p) - 48;
|
|
94
|
+
if (d > 9) break;
|
|
95
|
+
mantissa = mantissa * 10 + <u64>d;
|
|
96
|
+
fracDigits++;
|
|
97
|
+
p += 2;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const mantDigits = intDigits + fracDigits;
|
|
102
|
+
if (mantDigits == 0) {
|
|
103
|
+
// No digits seen (e.g. `.5`, `NaN`, `Infinity`) - defer to AS std.
|
|
104
|
+
return fallback<T>(origStart, srcEnd);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let exponent: i32 = -fracDigits;
|
|
108
|
+
|
|
109
|
+
// Optional `e[+-]NNN` suffix.
|
|
110
|
+
if (p < srcEnd) {
|
|
111
|
+
const c = load<u16>(p);
|
|
112
|
+
if (c == 101 || c == 69) {
|
|
113
|
+
p += 2;
|
|
114
|
+
if (p >= srcEnd) return fallback<T>(origStart, srcEnd);
|
|
115
|
+
let expNeg = false;
|
|
116
|
+
const sc = load<u16>(p);
|
|
117
|
+
if (sc == 45) {
|
|
118
|
+
expNeg = true;
|
|
119
|
+
p += 2;
|
|
120
|
+
} else if (sc == 43) {
|
|
121
|
+
p += 2;
|
|
122
|
+
}
|
|
123
|
+
if (p >= srcEnd) return fallback<T>(origStart, srcEnd);
|
|
124
|
+
let exp: i32 = 0;
|
|
125
|
+
let expDigits: i32 = 0;
|
|
126
|
+
while (p < srcEnd) {
|
|
127
|
+
const d = <u32>load<u16>(p) - 48;
|
|
128
|
+
if (d > 9) break;
|
|
129
|
+
exp = exp * 10 + <i32>d;
|
|
130
|
+
expDigits++;
|
|
131
|
+
if (expDigits > 4) {
|
|
132
|
+
// Pathological exponent - fall back for safety.
|
|
133
|
+
return fallback<T>(origStart, srcEnd);
|
|
134
|
+
}
|
|
135
|
+
p += 2;
|
|
136
|
+
}
|
|
137
|
+
if (expDigits == 0) return fallback<T>(origStart, srcEnd);
|
|
138
|
+
exponent += expNeg ? -exp : exp;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Fast path eligibility: mantissa fits exactly in an f64 and exponent
|
|
143
|
+
// is in the exactly-representable pow10 range. Both halves are needed
|
|
144
|
+
// for the result to be correctly rounded. Capping `mantDigits` at 19
|
|
145
|
+
// is a cheaper proxy for "didn't overflow u64".
|
|
146
|
+
if (mantDigits > 19 || mantissa > MAX_EXACT_MANTISSA) {
|
|
147
|
+
return fallback<T>(origStart, srcEnd);
|
|
148
|
+
}
|
|
149
|
+
if (exponent > MAX_EXACT_POW10 || exponent < -MAX_EXACT_POW10) {
|
|
150
|
+
return fallback<T>(origStart, srcEnd);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let result = <f64>mantissa;
|
|
154
|
+
if (exponent > 0) {
|
|
155
|
+
result *= loadPow10(<u32>exponent);
|
|
156
|
+
} else if (exponent < 0) {
|
|
157
|
+
result /= loadPow10(<u32>-exponent);
|
|
158
|
+
}
|
|
159
|
+
if (negative) result = -result;
|
|
160
|
+
|
|
161
|
+
// @ts-ignore: type
|
|
162
|
+
const type: T = 0;
|
|
163
|
+
// @ts-ignore: type
|
|
164
|
+
if (type instanceof f64) return <T>result;
|
|
165
|
+
// @ts-ignore: cast
|
|
166
|
+
return <T>(<f32>result);
|
|
167
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// SIMD (v128) integer-digit parsing kernels over UTF-16 sources.
|
|
2
|
+
//
|
|
3
|
+
// Requires `--enable simd` at compile time. Imported only by the SIMD-mode
|
|
4
|
+
// dispatch paths and dead-code-eliminated when JSON_MODE != SIMD.
|
|
5
|
+
//
|
|
6
|
+
// Algorithm is the Lemire-style narrow-extmul-dot pipeline used by simdjson:
|
|
7
|
+
//
|
|
8
|
+
// 1. `i16x8.sub` subtracts `'0'` from each UTF-16 lane.
|
|
9
|
+
// 2. `i8x16.narrow_i16x8_u` packs two 8-lane u16 vectors into one 16-lane u8
|
|
10
|
+
// vector. This pack is free in SIMD and is the move that makes the SWAR
|
|
11
|
+
// packing problem disappear.
|
|
12
|
+
// 3. `i16x8.extmul_low/high_i8x16_u(packed, (10, 1, ...))` multiplies
|
|
13
|
+
// adjacent bytes by 10 and 1, encoding the first pair-fold step in a
|
|
14
|
+
// vector op.
|
|
15
|
+
// 4. `i32x4.extadd_pairwise_i16x8_u` pairwise-sums adjacent u16 lanes into
|
|
16
|
+
// u32 lanes, completing the first pair-fold.
|
|
17
|
+
// 5. `i16x8.narrow_i32x4_u + i32x4.dot_i16x8_s(_, (100, 1, 100, 1, ...))`
|
|
18
|
+
// folds 4 u32 pair-values into 2 u32 group-values per lane via dot
|
|
19
|
+
// product.
|
|
20
|
+
|
|
21
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
22
|
+
@lazy const SPLAT_30 = i16x8.splat(0x30);
|
|
23
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
24
|
+
@lazy const SPLAT_09 = i16x8.splat(9);
|
|
25
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
26
|
+
@lazy const ZERO_I16X8 = i16x8.splat(0);
|
|
27
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
28
|
+
@lazy const ZERO_I32X4 = i32x4.splat(0);
|
|
29
|
+
|
|
30
|
+
// Weights for the first pair-fold step (`digit_lo * 10 + digit_hi`).
|
|
31
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
32
|
+
@lazy const PACK_WEIGHTS_10_1 = i8x16(
|
|
33
|
+
10,
|
|
34
|
+
1,
|
|
35
|
+
10,
|
|
36
|
+
1,
|
|
37
|
+
10,
|
|
38
|
+
1,
|
|
39
|
+
10,
|
|
40
|
+
1,
|
|
41
|
+
0,
|
|
42
|
+
0,
|
|
43
|
+
0,
|
|
44
|
+
0,
|
|
45
|
+
0,
|
|
46
|
+
0,
|
|
47
|
+
0,
|
|
48
|
+
0,
|
|
49
|
+
);
|
|
50
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
51
|
+
@lazy const PACK_WEIGHTS_10_1_FULL = i8x16(
|
|
52
|
+
10,
|
|
53
|
+
1,
|
|
54
|
+
10,
|
|
55
|
+
1,
|
|
56
|
+
10,
|
|
57
|
+
1,
|
|
58
|
+
10,
|
|
59
|
+
1,
|
|
60
|
+
10,
|
|
61
|
+
1,
|
|
62
|
+
10,
|
|
63
|
+
1,
|
|
64
|
+
10,
|
|
65
|
+
1,
|
|
66
|
+
10,
|
|
67
|
+
1,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Weights for the second fold step (`pair_lo * 100 + pair_hi`).
|
|
71
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
72
|
+
@lazy const PAIR_WEIGHTS_100_1 = i16x8(100, 1, 100, 1, 0, 0, 0, 0);
|
|
73
|
+
// @ts-expect-error: @lazy is a valid decorator
|
|
74
|
+
@lazy const PAIR_WEIGHTS_100_1_FULL = i16x8(100, 1, 100, 1, 100, 1, 100, 1);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse eight UTF-16 ASCII digits (16 source bytes) into the 8-digit `u32`
|
|
78
|
+
* value using SIMD.
|
|
79
|
+
*
|
|
80
|
+
* Returns `U32.MAX_VALUE` on any non-digit lane.
|
|
81
|
+
*
|
|
82
|
+
* @param srcStart Pointer to 16 source bytes (8 UTF-16 chars).
|
|
83
|
+
* @returns The parsed 8-digit value, or `U32.MAX_VALUE` on invalid input.
|
|
84
|
+
*/
|
|
85
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
86
|
+
@inline export function parse8Digits_SIMD(srcStart: usize): u32 {
|
|
87
|
+
const block = load<v128>(srcStart);
|
|
88
|
+
const digits = i16x8.sub(block, SPLAT_30);
|
|
89
|
+
if (v128.any_true(i16x8.gt_u(digits, SPLAT_09))) return U32.MAX_VALUE;
|
|
90
|
+
const packed = i8x16.narrow_i16x8_u(digits, ZERO_I16X8);
|
|
91
|
+
const products = i16x8.extmul_low_i8x16_u(packed, PACK_WEIGHTS_10_1);
|
|
92
|
+
const pairs = i32x4.extadd_pairwise_i16x8_u(products);
|
|
93
|
+
const pairs16 = i16x8.narrow_i32x4_u(pairs, ZERO_I32X4);
|
|
94
|
+
const groups = i32x4.dot_i16x8_s(pairs16, PAIR_WEIGHTS_100_1);
|
|
95
|
+
const lo = i32x4.extract_lane(groups, 0);
|
|
96
|
+
const hi = i32x4.extract_lane(groups, 1);
|
|
97
|
+
return <u32>lo * 10_000 + <u32>hi;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Same as {@link parse8Digits_SIMD} but with the validation step removed.
|
|
102
|
+
* Used in consume-to-end paths.
|
|
103
|
+
*
|
|
104
|
+
* @param srcStart Pointer to 16 source bytes (8 UTF-16 chars).
|
|
105
|
+
* @returns The parsed 8-digit value.
|
|
106
|
+
*/
|
|
107
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
108
|
+
@inline export function parse8Digits_SIMD_Unsafe(srcStart: usize): u32 {
|
|
109
|
+
const block = load<v128>(srcStart);
|
|
110
|
+
const digits = i16x8.sub(block, SPLAT_30);
|
|
111
|
+
const packed = i8x16.narrow_i16x8_u(digits, ZERO_I16X8);
|
|
112
|
+
const products = i16x8.extmul_low_i8x16_u(packed, PACK_WEIGHTS_10_1);
|
|
113
|
+
const pairs = i32x4.extadd_pairwise_i16x8_u(products);
|
|
114
|
+
const pairs16 = i16x8.narrow_i32x4_u(pairs, ZERO_I32X4);
|
|
115
|
+
const groups = i32x4.dot_i16x8_s(pairs16, PAIR_WEIGHTS_100_1);
|
|
116
|
+
const lo = i32x4.extract_lane(groups, 0);
|
|
117
|
+
const hi = i32x4.extract_lane(groups, 1);
|
|
118
|
+
return <u32>lo * 10_000 + <u32>hi;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Parse sixteen UTF-16 ASCII digits (32 source bytes) into one 16-digit
|
|
123
|
+
* `u64` value using SIMD.
|
|
124
|
+
*
|
|
125
|
+
* Two `v128` loads. Combined OR'd validation across both halves means one
|
|
126
|
+
* branch covers all 16 digits. Both halves' `extmul`s feed a single dot
|
|
127
|
+
* product, producing 4 four-digit groups that the final parallel-pair
|
|
128
|
+
* scalar combine merges.
|
|
129
|
+
*
|
|
130
|
+
* Returns `U64.MAX_VALUE` on any non-digit lane.
|
|
131
|
+
*
|
|
132
|
+
* @param srcStart Pointer to 32 source bytes (16 UTF-16 chars).
|
|
133
|
+
* @returns The parsed 16-digit value, or `U64.MAX_VALUE` on invalid input.
|
|
134
|
+
*/
|
|
135
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
136
|
+
@inline export function parse16Digits_SIMD(srcStart: usize): u64 {
|
|
137
|
+
const block0 = load<v128>(srcStart);
|
|
138
|
+
const block1 = load<v128>(srcStart, 16);
|
|
139
|
+
|
|
140
|
+
const digits0 = i16x8.sub(block0, SPLAT_30);
|
|
141
|
+
const digits1 = i16x8.sub(block1, SPLAT_30);
|
|
142
|
+
|
|
143
|
+
const bad0 = i16x8.gt_u(digits0, SPLAT_09);
|
|
144
|
+
const bad1 = i16x8.gt_u(digits1, SPLAT_09);
|
|
145
|
+
if (v128.any_true(v128.or(bad0, bad1))) return U64.MAX_VALUE;
|
|
146
|
+
|
|
147
|
+
const packed = i8x16.narrow_i16x8_u(digits0, digits1);
|
|
148
|
+
const products_lo = i16x8.extmul_low_i8x16_u(packed, PACK_WEIGHTS_10_1_FULL);
|
|
149
|
+
const products_hi = i16x8.extmul_high_i8x16_u(packed, PACK_WEIGHTS_10_1_FULL);
|
|
150
|
+
const pairs_lo = i32x4.extadd_pairwise_i16x8_u(products_lo);
|
|
151
|
+
const pairs_hi = i32x4.extadd_pairwise_i16x8_u(products_hi);
|
|
152
|
+
const pairs16 = i16x8.narrow_i32x4_u(pairs_lo, pairs_hi);
|
|
153
|
+
const groups = i32x4.dot_i16x8_s(pairs16, PAIR_WEIGHTS_100_1_FULL);
|
|
154
|
+
|
|
155
|
+
const g0 = i32x4.extract_lane(groups, 0);
|
|
156
|
+
const g1 = i32x4.extract_lane(groups, 1);
|
|
157
|
+
const g2 = i32x4.extract_lane(groups, 2);
|
|
158
|
+
const g3 = i32x4.extract_lane(groups, 3);
|
|
159
|
+
const pair01 = <u64>g0 * 10_000 + <u64>g1;
|
|
160
|
+
const pair23 = <u64>g2 * 10_000 + <u64>g3;
|
|
161
|
+
return pair01 * 100_000_000 + pair23;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Same as {@link parse16Digits_SIMD} but with the validation step removed.
|
|
166
|
+
* Used in consume-to-end paths.
|
|
167
|
+
*
|
|
168
|
+
* @param srcStart Pointer to 32 source bytes (16 UTF-16 chars).
|
|
169
|
+
* @returns The parsed 16-digit value.
|
|
170
|
+
*/
|
|
171
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
172
|
+
@inline export function parse16Digits_SIMD_Unsafe(srcStart: usize): u64 {
|
|
173
|
+
const block0 = load<v128>(srcStart);
|
|
174
|
+
const block1 = load<v128>(srcStart, 16);
|
|
175
|
+
const digits0 = i16x8.sub(block0, SPLAT_30);
|
|
176
|
+
const digits1 = i16x8.sub(block1, SPLAT_30);
|
|
177
|
+
const packed = i8x16.narrow_i16x8_u(digits0, digits1);
|
|
178
|
+
const products_lo = i16x8.extmul_low_i8x16_u(packed, PACK_WEIGHTS_10_1_FULL);
|
|
179
|
+
const products_hi = i16x8.extmul_high_i8x16_u(packed, PACK_WEIGHTS_10_1_FULL);
|
|
180
|
+
const pairs_lo = i32x4.extadd_pairwise_i16x8_u(products_lo);
|
|
181
|
+
const pairs_hi = i32x4.extadd_pairwise_i16x8_u(products_hi);
|
|
182
|
+
const pairs16 = i16x8.narrow_i32x4_u(pairs_lo, pairs_hi);
|
|
183
|
+
const groups = i32x4.dot_i16x8_s(pairs16, PAIR_WEIGHTS_100_1_FULL);
|
|
184
|
+
const g0 = i32x4.extract_lane(groups, 0);
|
|
185
|
+
const g1 = i32x4.extract_lane(groups, 1);
|
|
186
|
+
const g2 = i32x4.extract_lane(groups, 2);
|
|
187
|
+
const g3 = i32x4.extract_lane(groups, 3);
|
|
188
|
+
const pair01 = <u64>g0 * 10_000 + <u64>g1;
|
|
189
|
+
const pair23 = <u64>g2 * 10_000 + <u64>g3;
|
|
190
|
+
return pair01 * 100_000_000 + pair23;
|
|
191
|
+
}
|
package/assembly/util/snp.ts
CHANGED
|
@@ -7,7 +7,10 @@ import { POW_TEN_TABLE_32, POW_TEN_TABLE_64 } from "../globals/tables";
|
|
|
7
7
|
import { atoi } from "./atoi";
|
|
8
8
|
|
|
9
9
|
// @ts-ignore: Decorator valid here
|
|
10
|
-
@inline export function snp<T extends number>(
|
|
10
|
+
@inline export function snp<T extends number>(
|
|
11
|
+
srcStart: usize,
|
|
12
|
+
srcEnd: usize,
|
|
13
|
+
): T {
|
|
11
14
|
// @ts-ignore: type
|
|
12
15
|
let val: T = 0;
|
|
13
16
|
let char = load<u16>(srcStart) - 48;
|