json-as 1.3.7 → 1.3.9
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 +36 -0
- package/README.md +1 -1
- package/assembly/deserialize/index/arbitrary.ts +2 -2
- package/assembly/deserialize/index/array.ts +29 -14
- package/assembly/deserialize/index/bool.ts +1 -1
- package/assembly/deserialize/index/date.ts +1 -1
- package/assembly/deserialize/index/float.ts +40 -1
- package/assembly/deserialize/index/integer.ts +3 -3
- package/assembly/deserialize/index/map.ts +1 -1
- package/assembly/deserialize/index/object.ts +1 -1
- package/assembly/deserialize/index/raw.ts +1 -1
- package/assembly/deserialize/index/set.ts +1 -1
- package/assembly/deserialize/index/staticarray.ts +4 -1
- package/assembly/deserialize/index/string.ts +28 -3
- package/assembly/deserialize/index/struct.ts +1 -1
- package/assembly/deserialize/index/typedarray.ts +25 -15
- package/assembly/deserialize/index/unsigned.ts +3 -3
- package/assembly/deserialize/index.ts +1 -0
- package/assembly/deserialize/naive/array/bool.ts +68 -0
- package/assembly/deserialize/naive/array/float.ts +63 -0
- package/assembly/deserialize/{simple → naive}/array/generic.ts +1 -2
- package/assembly/deserialize/naive/array/integer.ts +86 -0
- package/assembly/deserialize/{simple → naive}/array/map.ts +0 -1
- package/assembly/deserialize/{simple → naive}/array/object.ts +0 -1
- package/assembly/deserialize/naive/array/string.ts +69 -0
- package/assembly/deserialize/{simple → naive}/array/struct.ts +0 -1
- package/assembly/deserialize/{simple → naive}/array.ts +6 -11
- package/assembly/deserialize/naive/float.ts +135 -0
- package/assembly/deserialize/{simple → naive}/integer.ts +2 -2
- package/assembly/deserialize/{simple → naive}/map.ts +12 -6
- package/assembly/deserialize/{simple → naive}/object.ts +4 -7
- package/assembly/deserialize/{simple → naive}/set.ts +12 -27
- package/assembly/deserialize/{simple → naive}/staticarray/array.ts +1 -1
- package/assembly/deserialize/{simple → naive}/staticarray/bool.ts +1 -1
- package/assembly/deserialize/{simple → naive}/staticarray/float.ts +1 -1
- package/assembly/deserialize/{simple → naive}/staticarray/integer.ts +1 -1
- package/assembly/deserialize/{simple → naive}/staticarray/struct.ts +1 -2
- package/assembly/deserialize/{simple → naive}/staticarray.ts +4 -4
- package/assembly/deserialize/naive/string.ts +199 -0
- package/assembly/deserialize/{simple → naive}/typedarray.ts +4 -4
- package/assembly/deserialize/{simple → naive}/unsigned.ts +2 -2
- package/assembly/deserialize/simd/array/integer.ts +19 -19
- package/assembly/deserialize/simd/float.ts +303 -0
- package/assembly/deserialize/simd/string.ts +233 -108
- package/assembly/deserialize/swar/array/arbitrary.ts +6 -2
- package/assembly/deserialize/swar/array/array.ts +14 -7
- package/assembly/deserialize/swar/array/bool.ts +8 -3
- package/assembly/deserialize/swar/array/box.ts +6 -2
- package/assembly/deserialize/swar/array/float.ts +282 -6
- package/assembly/deserialize/swar/array/generic.ts +6 -2
- package/assembly/deserialize/swar/array/integer.ts +81 -74
- package/assembly/deserialize/swar/array/map.ts +6 -2
- package/assembly/deserialize/swar/array/object.ts +24 -32
- package/assembly/deserialize/swar/array/raw.ts +6 -2
- package/assembly/deserialize/swar/array/shared.ts +32 -8
- package/assembly/deserialize/swar/array/string.ts +127 -10
- package/assembly/deserialize/swar/array/struct.ts +45 -11
- package/assembly/deserialize/swar/array.ts +2 -56
- package/assembly/deserialize/swar/float.ts +304 -0
- package/assembly/deserialize/swar/string.ts +119 -104
- package/assembly/deserialize/swar/typedarray.ts +224 -0
- package/assembly/index.ts +203 -293
- package/assembly/serialize/index/array.ts +1 -1
- package/assembly/serialize/index/bool.ts +1 -1
- package/assembly/serialize/index/date.ts +1 -1
- package/assembly/serialize/index/float.ts +1 -1
- package/assembly/serialize/index/integer.ts +1 -1
- package/assembly/serialize/index/map.ts +1 -1
- package/assembly/serialize/index/raw.ts +1 -1
- package/assembly/serialize/index/set.ts +1 -1
- package/assembly/serialize/index/staticarray.ts +1 -1
- package/assembly/serialize/index/string.ts +1 -1
- package/assembly/serialize/index/struct.ts +1 -1
- package/assembly/serialize/index/typedarray.ts +2 -11
- package/assembly/serialize/index.ts +1 -0
- package/assembly/serialize/{simple → naive}/array.ts +87 -0
- package/assembly/serialize/{simple → naive}/string.ts +1 -1
- package/assembly/serialize/swar/string.ts +0 -139
- package/assembly/util/dragonbox.ts +10 -3
- package/assembly/util/itoa-fast.ts +29 -18
- package/assembly/util/scanValueEnd.ts +78 -0
- package/assembly/util/scientific.ts +132 -0
- package/lib/as-bs.ts +14 -1
- package/package.json +14 -13
- package/transform/lib/index.d.ts +4 -0
- package/transform/lib/index.d.ts.map +1 -1
- package/transform/lib/index.js +155 -238
- package/transform/lib/index.js.map +1 -1
- package/assembly/deserialize/simple/arbitrary.ts +0 -30
- package/assembly/deserialize/simple/array/bool.ts +0 -48
- package/assembly/deserialize/simple/array/float.ts +0 -55
- package/assembly/deserialize/simple/array/integer.ts +0 -33
- package/assembly/deserialize/simple/array/string.ts +0 -29
- package/assembly/deserialize/simple/float.ts +0 -206
- package/assembly/deserialize/simple/string.ts +0 -45
- package/assembly/serialize/simple/arbitrary.ts +0 -79
- package/assembly/serialize/simple/object.ts +0 -42
- /package/assembly/deserialize/{simple → naive}/array/arbitrary.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/array/array.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/array/box.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/array/raw.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/bool.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/date.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/raw.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/staticarray/string.ts +0 -0
- /package/assembly/deserialize/{simple → naive}/struct.ts +0 -0
- /package/assembly/serialize/{simple → naive}/bool.ts +0 -0
- /package/assembly/serialize/{simple → naive}/date.ts +0 -0
- /package/assembly/serialize/{simple → naive}/float.ts +0 -0
- /package/assembly/serialize/{simple → naive}/integer.ts +0 -0
- /package/assembly/serialize/{simple → naive}/map.ts +0 -0
- /package/assembly/serialize/{simple → naive}/raw.ts +0 -0
- /package/assembly/serialize/{simple → naive}/set.ts +0 -0
- /package/assembly/serialize/{simple → naive}/staticarray.ts +0 -0
- /package/assembly/serialize/{simple → naive}/struct.ts +0 -0
- /package/assembly/serialize/{simple → naive}/typedarray.ts +0 -0
|
@@ -3,11 +3,11 @@ import { JSON } from "../..";
|
|
|
3
3
|
export {
|
|
4
4
|
serializeArrayBufferUnsafe,
|
|
5
5
|
serializeTypedArray,
|
|
6
|
-
} from "../
|
|
6
|
+
} from "../naive/typedarray";
|
|
7
7
|
import {
|
|
8
8
|
serializeArrayBufferUnsafe,
|
|
9
9
|
serializeTypedArray,
|
|
10
|
-
} from "../
|
|
10
|
+
} from "../naive/typedarray";
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@inline export function serializeDynamic(type: u16, data: usize): void {
|
|
@@ -73,12 +73,3 @@ import {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@inline export function serializeArrayBuffer(data: ArrayBuffer): void {
|
|
79
|
-
const dataStart = changetype<usize>(data);
|
|
80
|
-
serializeArrayBufferUnsafe(
|
|
81
|
-
dataStart,
|
|
82
|
-
changetype<OBJECT>(dataStart - TOTAL_OVERHEAD).rtSize,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
@@ -5,6 +5,10 @@ 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
|
+
dragonbox_f32_buffered,
|
|
10
|
+
dragonbox_f64_buffered,
|
|
11
|
+
} from "../../util/dragonbox";
|
|
8
12
|
|
|
9
13
|
|
|
10
14
|
@inline
|
|
@@ -50,6 +54,16 @@ function serializeArrayElement<T>(value: T): void {
|
|
|
50
54
|
else serializeFloat64Unsafe(<f64>value);
|
|
51
55
|
return;
|
|
52
56
|
}
|
|
57
|
+
if (isManaged<T>() || isReference<T>()) {
|
|
58
|
+
// Preserve runtime custom serializers for subclass instances stored in
|
|
59
|
+
// parent-typed arrays before falling back to the static dispatcher.
|
|
60
|
+
// @ts-ignore: transform-defined at runtime when present
|
|
61
|
+
if (isDefined(value.__SERIALIZE_CUSTOM)) {
|
|
62
|
+
// @ts-ignore: transform-defined at runtime when present
|
|
63
|
+
value.__SERIALIZE_CUSTOM();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
53
67
|
JSON.__serialize<T>(value);
|
|
54
68
|
}
|
|
55
69
|
|
|
@@ -167,6 +181,69 @@ function serializeU8ArrayFast(src: u8[]): void {
|
|
|
167
181
|
store<u16>(bs.offset - 2, BRACKET_RIGHT);
|
|
168
182
|
}
|
|
169
183
|
|
|
184
|
+
// Specialized float-array serializer: dragonbox + trailing comma in a
|
|
185
|
+
// uniform per-iteration body, then overwrite the final comma with `]`. The
|
|
186
|
+
// generic dispatcher splits the loop into "N-1 elements with comma, then
|
|
187
|
+
// last element without, then `]`" — the branch on each `i < end` check
|
|
188
|
+
// stalls the loop's tight bs.offset advance pattern. This variant runs the
|
|
189
|
+
// same number of stores per iteration (dragonbox output + COMMA), but the
|
|
190
|
+
// uniform loop body inlines better and the trailing `]` is a single fixed
|
|
191
|
+
// overwrite outside the loop.
|
|
192
|
+
function serializeF64ArrayFast(src: f64[]): void {
|
|
193
|
+
const len = src.length;
|
|
194
|
+
// Worst case per element: ~24 chars for f64 + comma = 50 bytes.
|
|
195
|
+
// Slight over-reserve (66) matches the existing `reservePrimitiveArray`
|
|
196
|
+
// budget and keeps a safety margin for any NaN/Inf spelling.
|
|
197
|
+
bs.proposeSize(4 + <u32>len * 66);
|
|
198
|
+
store<u16>(bs.offset, BRACKET_LEFT);
|
|
199
|
+
bs.offset += 2;
|
|
200
|
+
if (len == 0) {
|
|
201
|
+
store<u16>(bs.offset, BRACKET_RIGHT);
|
|
202
|
+
bs.offset += 2;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Hoist `bs.offset` into a local so the loop body has a single
|
|
207
|
+
// monotonically-advancing pointer instead of two reads + two writes back
|
|
208
|
+
// to the global per iteration.
|
|
209
|
+
const dataStart = src.dataStart;
|
|
210
|
+
let offset = bs.offset;
|
|
211
|
+
for (let i: i32 = 0; i < len; i++) {
|
|
212
|
+
const v = load<f64>(dataStart + ((<usize>i) << 3));
|
|
213
|
+
const size = dragonbox_f64_buffered(offset, v) << 1;
|
|
214
|
+
store<u16>(offset + size, COMMA);
|
|
215
|
+
offset += size + 2;
|
|
216
|
+
}
|
|
217
|
+
// Overwrite the final trailing comma with `]`.
|
|
218
|
+
store<u16>(offset - 2, BRACKET_RIGHT);
|
|
219
|
+
bs.offset = offset;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function serializeF32ArrayFast(src: f32[]): void {
|
|
223
|
+
const len = src.length;
|
|
224
|
+
// Worst case for f32 is ~16 chars + comma = ~34 bytes; mirror the budget
|
|
225
|
+
// used by `reservePrimitiveArray`.
|
|
226
|
+
bs.proposeSize(4 + <u32>len * 34);
|
|
227
|
+
store<u16>(bs.offset, BRACKET_LEFT);
|
|
228
|
+
bs.offset += 2;
|
|
229
|
+
if (len == 0) {
|
|
230
|
+
store<u16>(bs.offset, BRACKET_RIGHT);
|
|
231
|
+
bs.offset += 2;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const dataStart = src.dataStart;
|
|
236
|
+
let offset = bs.offset;
|
|
237
|
+
for (let i: i32 = 0; i < len; i++) {
|
|
238
|
+
const v = load<f32>(dataStart + ((<usize>i) << 2));
|
|
239
|
+
const size = dragonbox_f32_buffered(offset, v) << 1;
|
|
240
|
+
store<u16>(offset + size, COMMA);
|
|
241
|
+
offset += size + 2;
|
|
242
|
+
}
|
|
243
|
+
store<u16>(offset - 2, BRACKET_RIGHT);
|
|
244
|
+
bs.offset = offset;
|
|
245
|
+
}
|
|
246
|
+
|
|
170
247
|
function serializeI8ArrayFast(src: i8[]): void {
|
|
171
248
|
const len = src.length;
|
|
172
249
|
// Worst case: every element is `-DDD,` = 5 chars = 10 bytes; plus 4 for `[]`.
|
|
@@ -226,6 +303,16 @@ export function serializeArray<T extends any[]>(src: T): void {
|
|
|
226
303
|
serializeI8ArrayFast(changetype<i8[]>(src));
|
|
227
304
|
return;
|
|
228
305
|
}
|
|
306
|
+
if (isFloat<valueof<T>>() && sizeof<valueof<T>>() == 8) {
|
|
307
|
+
// @ts-expect-error: T is f64[]
|
|
308
|
+
serializeF64ArrayFast(changetype<f64[]>(src));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (isFloat<valueof<T>>() && sizeof<valueof<T>>() == 4) {
|
|
312
|
+
// @ts-expect-error: T is f32[]
|
|
313
|
+
serializeF32ArrayFast(changetype<f32[]>(src));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
229
316
|
|
|
230
317
|
const len = src.length;
|
|
231
318
|
const end = len - 1;
|
|
@@ -16,7 +16,7 @@ import { serializeStruct } from "./struct";
|
|
|
16
16
|
* @returns void
|
|
17
17
|
*/
|
|
18
18
|
// @ts-ignore: inline
|
|
19
|
-
@inline export function
|
|
19
|
+
@inline export function serializeString_NAIVE(src: string): void {
|
|
20
20
|
const srcSize = bytes(src);
|
|
21
21
|
bs.proposeSize(srcSize + 4);
|
|
22
22
|
let srcPtr = changetype<usize>(src);
|
|
@@ -174,132 +174,6 @@ export function serializeString_SWAR(src: string): void {
|
|
|
174
174
|
bs.offset += 2;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
export function serializeString_SWAR_ExperimentalTableEscapes(
|
|
178
|
-
src: string,
|
|
179
|
-
): void {
|
|
180
|
-
let srcStart = changetype<usize>(src);
|
|
181
|
-
const srcSize = changetype<OBJECT>(srcStart - TOTAL_OVERHEAD).rtSize;
|
|
182
|
-
const srcEnd = srcStart + srcSize;
|
|
183
|
-
const srcEnd8 = srcEnd - 8;
|
|
184
|
-
|
|
185
|
-
bs.proposeSize(srcSize + 4);
|
|
186
|
-
store<u16>(bs.offset, 34); // "
|
|
187
|
-
bs.offset += 2;
|
|
188
|
-
|
|
189
|
-
while (srcStart < srcEnd8) {
|
|
190
|
-
const block = load<u64>(srcStart);
|
|
191
|
-
let mask = detect_escapable_u64_swar_safe(block);
|
|
192
|
-
store<u64>(bs.offset, block);
|
|
193
|
-
|
|
194
|
-
if (mask === 0) {
|
|
195
|
-
srcStart += 8;
|
|
196
|
-
bs.offset += 8;
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
do {
|
|
201
|
-
const laneIdx = usize(ctz(mask) >> 3);
|
|
202
|
-
const srcIdx = srcStart + laneIdx;
|
|
203
|
-
// Even (0 2 4 6) -> Confirmed ASCII Escape
|
|
204
|
-
// Odd (1 3 5 7) -> Possibly a Unicode code unit or surrogate
|
|
205
|
-
if ((laneIdx & 1) === 0) {
|
|
206
|
-
mask &= mask - 1;
|
|
207
|
-
const code = load<u16>(srcIdx);
|
|
208
|
-
const escaped = load<u32>(SERIALIZE_ESCAPE_TABLE + (code << 2));
|
|
209
|
-
|
|
210
|
-
if ((escaped & 0xffff) != BACK_SLASH) {
|
|
211
|
-
bs.growSize(10);
|
|
212
|
-
const dstIdx = bs.offset + laneIdx;
|
|
213
|
-
store<u64>(dstIdx, U00_MARKER);
|
|
214
|
-
store<u32>(dstIdx, escaped, 8);
|
|
215
|
-
store<u64>(dstIdx, load<u64>(srcIdx, 2), 12);
|
|
216
|
-
bs.offset += 10;
|
|
217
|
-
} else {
|
|
218
|
-
bs.growSize(2);
|
|
219
|
-
const dstIdx = bs.offset + laneIdx;
|
|
220
|
-
store<u32>(dstIdx, escaped);
|
|
221
|
-
store<u64>(dstIdx, load<u64>(srcIdx, 2), 4);
|
|
222
|
-
bs.offset += 2;
|
|
223
|
-
}
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
mask &= mask - 1;
|
|
227
|
-
|
|
228
|
-
const code = load<u16>(srcIdx - 1);
|
|
229
|
-
if (code < 0xd800 || code > 0xdfff) continue;
|
|
230
|
-
|
|
231
|
-
if (code <= 0xdbff && srcIdx + 2 < srcEnd) {
|
|
232
|
-
const next = load<u16>(srcIdx, 1);
|
|
233
|
-
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
234
|
-
// paired surrogate
|
|
235
|
-
// mask &= ~(0xFF << ((laneIdx+2) << 3));
|
|
236
|
-
mask &= mask - 1;
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
bs.growSize(10);
|
|
242
|
-
|
|
243
|
-
// unpaired high/low surrogate
|
|
244
|
-
const dstIdx = bs.offset + laneIdx - 1;
|
|
245
|
-
store<u32>(dstIdx, U_MARKER); // \u
|
|
246
|
-
store<u64>(dstIdx, u16_to_hex4_swar(code), 4);
|
|
247
|
-
store<u64>(dstIdx, load<u64>(srcIdx, 1), 12);
|
|
248
|
-
bs.offset += 10;
|
|
249
|
-
} while (mask !== 0);
|
|
250
|
-
|
|
251
|
-
srcStart += 8;
|
|
252
|
-
bs.offset += 8;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
while (srcStart <= srcEnd - 2) {
|
|
256
|
-
const code = load<u16>(srcStart);
|
|
257
|
-
|
|
258
|
-
if (code == BACK_SLASH || code == QUOTE || code < 32) {
|
|
259
|
-
const escaped = load<u32>(SERIALIZE_ESCAPE_TABLE + (code << 2));
|
|
260
|
-
if ((escaped & 0xffff) != BACK_SLASH) {
|
|
261
|
-
bs.growSize(10);
|
|
262
|
-
store<u64>(bs.offset, U00_MARKER);
|
|
263
|
-
store<u32>(bs.offset, escaped, 8);
|
|
264
|
-
bs.offset += 12;
|
|
265
|
-
} else {
|
|
266
|
-
bs.growSize(2);
|
|
267
|
-
store<u32>(bs.offset, escaped);
|
|
268
|
-
bs.offset += 4;
|
|
269
|
-
}
|
|
270
|
-
srcStart += 2;
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (code < 0xd800 || code > 0xdfff) {
|
|
275
|
-
store<u16>(bs.offset, code);
|
|
276
|
-
bs.offset += 2;
|
|
277
|
-
srcStart += 2;
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (code <= 0xdbff && srcStart + 2 <= srcEnd - 2) {
|
|
282
|
-
const next = load<u16>(srcStart, 2);
|
|
283
|
-
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
284
|
-
// valid surrogate pair
|
|
285
|
-
store<u16>(bs.offset, code);
|
|
286
|
-
store<u16>(bs.offset + 2, next);
|
|
287
|
-
bs.offset += 4;
|
|
288
|
-
srcStart += 4;
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// unpaired high/low surrogate
|
|
294
|
-
write_u_escape(code);
|
|
295
|
-
srcStart += 2;
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
store<u16>(bs.offset, 34); // "
|
|
300
|
-
bs.offset += 2;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
177
|
// @ts-expect-error: @inline is a valid decorator
|
|
304
178
|
@inline function write_u_escape(code: u16): void {
|
|
305
179
|
bs.growSize(10);
|
|
@@ -326,16 +200,3 @@ export function serializeString_SWAR_ExperimentalTableEscapes(
|
|
|
326
200
|
0x8000_8000_8000_8000;
|
|
327
201
|
return (ascii_mask & (~hi_mask >> 8)) | hi_mask;
|
|
328
202
|
}
|
|
329
|
-
|
|
330
|
-
// @ts-expect-error: @inline is a valid decorator
|
|
331
|
-
@inline export function detect_escapable_u64_swar_unsafe(block: u64): u64 {
|
|
332
|
-
const lo = block & 0x00ff_00ff_00ff_00ff;
|
|
333
|
-
const loSafe = lo | 0x0100_0100_0100_0100;
|
|
334
|
-
const ascii_mask =
|
|
335
|
-
((loSafe - 0x0020_0020_0020_0020) | // lt 0x20
|
|
336
|
-
((loSafe ^ 0x0022_0022_0022_0022) - 0x0001_0001_0001_0001) | // eq 0x22
|
|
337
|
-
((loSafe ^ 0x005c_005c_005c_005c) - 0x0001_0001_0001_0001)) & // eq 0x5C
|
|
338
|
-
(0x0080_0080_0080_0080 & ~lo); // replace each lane with 0x80
|
|
339
|
-
const hi = block & 0xff00_ff00_ff00_ff00; // add possible non-ascii code units
|
|
340
|
-
return ascii_mask | hi;
|
|
341
|
-
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DRAGONBOX_F32_CACHE, DRAGONBOX_F64_CACHE } from "./dragonbox-cache";
|
|
2
|
-
import { decimalCount32,
|
|
2
|
+
import { decimalCount32, utoa32_dec_core } from "util/number";
|
|
3
|
+
import { ensureItoaPairs, itoaU32, itoaU64 } from "./itoa-fast";
|
|
3
4
|
|
|
4
5
|
const CHAR_MINUS: u16 = 45;
|
|
5
6
|
const CHAR_DOT: u16 = 46;
|
|
@@ -572,7 +573,11 @@ function dragonboxCoreF32(buffer: usize, value: f32): u32 {
|
|
|
572
573
|
reinterpret<u32>(value) & 0x7fffff,
|
|
573
574
|
(reinterpret<u32>(value) >>> 23) & 0xff,
|
|
574
575
|
);
|
|
575
|
-
|
|
576
|
+
// Use the jeaiii-style itoa (forward write + 2-digit-pair LUT) instead
|
|
577
|
+
// of AS stdlib's `itoa_buffered`, which runs a separate width classifier
|
|
578
|
+
// + backward div-by-10000 loop. Saves a function call and ~half the
|
|
579
|
+
// per-digit work for the typical 7-17 digit dragonbox output.
|
|
580
|
+
let len = itoaU32(buffer + ((<usize>sign) << 1), digits);
|
|
576
581
|
return <u32>(prettify(buffer + ((<usize>sign) << 1), len, _dbK) + sign);
|
|
577
582
|
}
|
|
578
583
|
|
|
@@ -588,7 +593,7 @@ function dragonboxCoreF64(buffer: usize, value: f64): u32 {
|
|
|
588
593
|
bits & 0x000fffffffffffff,
|
|
589
594
|
<i32>((bits >>> 52) & 0x7ff),
|
|
590
595
|
);
|
|
591
|
-
let len =
|
|
596
|
+
let len = itoaU64(buffer + ((<usize>sign) << 1), digits);
|
|
592
597
|
return <u32>(prettify(buffer + ((<usize>sign) << 1), len, _dbK) + sign);
|
|
593
598
|
}
|
|
594
599
|
|
|
@@ -615,6 +620,7 @@ export function dragonbox_f32_buffered(buffer: usize, value: f32): u32 {
|
|
|
615
620
|
store<u64>(buffer + 8, 0x7900740069006e);
|
|
616
621
|
return 8 + (sign ? 1 : 0);
|
|
617
622
|
}
|
|
623
|
+
ensureItoaPairs();
|
|
618
624
|
return dragonboxCoreF32(buffer, value);
|
|
619
625
|
}
|
|
620
626
|
|
|
@@ -641,6 +647,7 @@ export function dragonbox_f64_buffered(buffer: usize, value: f64): u32 {
|
|
|
641
647
|
store<u64>(buffer + 8, 0x7900740069006e);
|
|
642
648
|
return 8 + (sign ? 1 : 0);
|
|
643
649
|
}
|
|
650
|
+
ensureItoaPairs();
|
|
644
651
|
return dragonboxCoreF64(buffer, value);
|
|
645
652
|
}
|
|
646
653
|
|
|
@@ -1,20 +1,31 @@
|
|
|
1
|
-
// Fast integer -> UTF-16 stringification
|
|
1
|
+
// Fast integer -> UTF-16 stringification.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// otherwise tiny.
|
|
3
|
+
// We tried the "real" jeaiii algorithm (fixed-point magic-multiplication
|
|
4
|
+
// per bucket; `f0 -> f2 -> f4 -> f6` chained fractional-part extractions)
|
|
5
|
+
// and it ran ~5-7% slower on V8/wasm than the div-by-constant variant
|
|
6
|
+
// below. Two reasons:
|
|
8
7
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
8
|
+
// 1. V8/wasm lowers `v / 100` (and other `/ <const>`s) to a single
|
|
9
|
+
// multiply-shift, so jeaiii's main selling point — avoiding division
|
|
10
|
+
// hardware — gives no win on this target. The op counts come out
|
|
11
|
+
// roughly equal.
|
|
12
|
+
//
|
|
13
|
+
// 2. The div-by-const variant computes each digit pair independently
|
|
14
|
+
// from `v` (`h = v / 100`, `l = v - h*100`, etc), so V8 schedules
|
|
15
|
+
// the LUT loads + stores for all pairs in parallel. The jeaiii
|
|
16
|
+
// chain forces them serial.
|
|
17
|
+
//
|
|
18
|
+
// What we keep from jeaiii here:
|
|
19
|
+
//
|
|
20
|
+
// - Width-ladder dispatch (`if v < 100 / 10_000 / 1_000_000 / ...`) so
|
|
21
|
+
// the same comparisons that would drive a separate `decimalCount`
|
|
22
|
+
// pass become the bucket pick.
|
|
23
|
+
//
|
|
24
|
+
// - A 100-entry digit-pair LUT keyed on `value % 100`. One `store<u32>`
|
|
25
|
+
// emits a UTF-16 pair.
|
|
26
|
+
//
|
|
27
|
+
// - Forward write in one pass — no `decimalCount32` precomputation, no
|
|
28
|
+
// backward write.
|
|
18
29
|
//
|
|
19
30
|
// Reference H2H bench: `__benches__/custom/itoa-h2h.bench.ts`.
|
|
20
31
|
|
|
@@ -43,7 +54,7 @@ function initPairs(): void {
|
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
/**
|
|
46
|
-
*
|
|
57
|
+
* u32 -> UTF-16 stringification, forward write.
|
|
47
58
|
* Returns the number of UTF-16 chars written (caller multiplies by 2 for
|
|
48
59
|
* a byte offset). Caller must ensure the buffer has at least 20 bytes
|
|
49
60
|
* available (max 10 chars).
|
|
@@ -169,7 +180,7 @@ function initPairs(): void {
|
|
|
169
180
|
}
|
|
170
181
|
|
|
171
182
|
/**
|
|
172
|
-
*
|
|
183
|
+
* u64 -> UTF-16 stringification.
|
|
173
184
|
* Small values delegate to `itoaU32`. For 11+ digit values, peel 8 digits
|
|
174
185
|
* from the bottom (always fits in u32), emit the remaining top via the
|
|
175
186
|
* u32 path, then emit the 8 trailing digits with leading-zero padding.
|
|
@@ -224,7 +235,7 @@ function initPairs(): void {
|
|
|
224
235
|
store<u16>(buf, 0x2d); // '-'
|
|
225
236
|
return 1 + itoaU64(buf + 2, <u64>-v);
|
|
226
237
|
}
|
|
227
|
-
return itoaU64(buf, <u64>
|
|
238
|
+
return itoaU64(buf, <u64>value);
|
|
228
239
|
}
|
|
229
240
|
return itoaU64(buf, <u64>value);
|
|
230
241
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BRACE_LEFT,
|
|
3
|
+
BRACE_RIGHT,
|
|
4
|
+
BRACKET_LEFT,
|
|
5
|
+
BRACKET_RIGHT,
|
|
6
|
+
COMMA,
|
|
7
|
+
QUOTE,
|
|
8
|
+
} from "../custom/chars";
|
|
9
|
+
import { isSpace } from "./isSpace";
|
|
10
|
+
import { scanStringEnd } from "./stringScan";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pure-scalar value-end scanner used by the NAIVE container deserializers.
|
|
14
|
+
*
|
|
15
|
+
* Returns the position immediately after the value that begins at `srcStart`:
|
|
16
|
+
*
|
|
17
|
+
* - For a quoted string: position past the closing `"` (uses scalar
|
|
18
|
+
* {@link scanStringEnd}).
|
|
19
|
+
* - For an object/array: position past the matching `}`/`]`, tracking depth
|
|
20
|
+
* and skipping nested quoted strings.
|
|
21
|
+
* - For anything else: position of the first `,`, `]`, or `}` (the value's
|
|
22
|
+
* structural terminator).
|
|
23
|
+
*
|
|
24
|
+
* Returns `0` when the input is empty or no terminator is found.
|
|
25
|
+
*
|
|
26
|
+
* Mirrors the semantics of `deserialize/swar/array/shared.ts:scanValueEnd`
|
|
27
|
+
* but stays scalar so `naive/` callers don't pull SWAR into the correctness
|
|
28
|
+
* baseline.
|
|
29
|
+
*/
|
|
30
|
+
// @ts-ignore: inline
|
|
31
|
+
@inline export function scanValueEnd(srcStart: usize, srcEnd: usize): usize {
|
|
32
|
+
if (srcStart >= srcEnd) return 0;
|
|
33
|
+
const first = load<u16>(srcStart);
|
|
34
|
+
|
|
35
|
+
if (first == QUOTE) {
|
|
36
|
+
const end = scanStringEnd(srcStart, srcEnd);
|
|
37
|
+
return end >= srcEnd ? 0 : end + 2;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (first == BRACE_LEFT || first == BRACKET_LEFT) {
|
|
41
|
+
let depth: i32 = 1;
|
|
42
|
+
let ptr = srcStart + 2;
|
|
43
|
+
while (ptr < srcEnd) {
|
|
44
|
+
const code = load<u16>(ptr);
|
|
45
|
+
if (code == QUOTE) {
|
|
46
|
+
const end = scanStringEnd(ptr, srcEnd);
|
|
47
|
+
if (end >= srcEnd) return 0;
|
|
48
|
+
ptr = end + 2;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (code == BRACE_LEFT || code == BRACKET_LEFT) {
|
|
52
|
+
depth++;
|
|
53
|
+
} else if (code == BRACE_RIGHT || code == BRACKET_RIGHT) {
|
|
54
|
+
if (--depth == 0) return ptr + 2;
|
|
55
|
+
}
|
|
56
|
+
ptr += 2;
|
|
57
|
+
}
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
while (srcStart < srcEnd) {
|
|
62
|
+
const code = load<u16>(srcStart);
|
|
63
|
+
// Stop at the structural terminator OR trailing whitespace, so the returned
|
|
64
|
+
// range is the exact value (scalar parsers assume [srcStart,srcEnd) is the
|
|
65
|
+
// value with no trailing whitespace). Callers skip whitespace to reach the
|
|
66
|
+
// following `,`/`]`/`}`.
|
|
67
|
+
if (
|
|
68
|
+
code == COMMA ||
|
|
69
|
+
code == BRACKET_RIGHT ||
|
|
70
|
+
code == BRACE_RIGHT ||
|
|
71
|
+
isSpace(code)
|
|
72
|
+
)
|
|
73
|
+
return srcStart;
|
|
74
|
+
srcStart += 2;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Direct decimal-to-f64 conversion from a `(u64 mantissa, i32 decimal exp)`
|
|
2
|
+
// pair. Bit-identical to `f64.parse` / `f32.parse` for any input the SWAR
|
|
3
|
+
// float deserializer can produce.
|
|
4
|
+
//
|
|
5
|
+
// Ported from AssemblyScript std's `util/string.ts` (which itself is adapted
|
|
6
|
+
// from the "metallic" library). The reason we duplicate it: AS std exposes
|
|
7
|
+
// `strtod(str)` but the underlying `scientific(mantissa, exp)` helper is
|
|
8
|
+
// module-private. Going through `strtod` requires a string allocation and a
|
|
9
|
+
// re-parse of digits we've already accumulated in the SWAR loop. Calling
|
|
10
|
+
// `scientific` directly skips both costs.
|
|
11
|
+
//
|
|
12
|
+
// scientific() is correctly rounded for all u64 mantissas and decimal
|
|
13
|
+
// exponents that fit in IEEE-754 f64's range — including the [2^53, 2^64)
|
|
14
|
+
// mantissa range that breaks Lemire's single-fmul fast path.
|
|
15
|
+
|
|
16
|
+
const POWERS10: usize = memory.data<f64>([
|
|
17
|
+
1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14,
|
|
18
|
+
1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
// 5^i for i in [0, 13]. ipow32(5, e) for the exponent ranges scaledown
|
|
22
|
+
// and scaleup actually call it with.
|
|
23
|
+
const POWERS5: usize = memory.data<i32>([
|
|
24
|
+
1, 5, 25, 125, 625, 3125, 15625, 78125, 390625, 1953125, 9765625, 48828125,
|
|
25
|
+
244140625, 1220703125,
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// @ts-ignore: inline
|
|
29
|
+
@inline function pow10(n: i32): f64 {
|
|
30
|
+
return load<f64>(POWERS10 + ((<usize>n) << alignof<f64>()));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// @ts-ignore: inline
|
|
34
|
+
@inline function pow5_32(n: i32): i32 {
|
|
35
|
+
return load<i32>(POWERS5 + ((<usize>n) << alignof<i32>()));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// __fixmulShift is mutated by `fixmul` and read by `scaleup`. AS std uses a
|
|
39
|
+
// module-level @lazy variable for the same reason; matching that.
|
|
40
|
+
// @ts-ignore: lazy decorator
|
|
41
|
+
@lazy let __fixmulShift: u64 = 0;
|
|
42
|
+
|
|
43
|
+
// @ts-ignore: inline
|
|
44
|
+
@inline function fixmul(a: u64, b: u32): u64 {
|
|
45
|
+
const low = (a & 0xffffffff) * b;
|
|
46
|
+
const high = (a >> 32) * b + (low >> 32);
|
|
47
|
+
const overflow = <u32>(high >> 32);
|
|
48
|
+
const space = clz(overflow);
|
|
49
|
+
const revspace: u64 = 32 - space;
|
|
50
|
+
__fixmulShift += revspace;
|
|
51
|
+
return (
|
|
52
|
+
((high << space) | ((low & 0xffffffff) >> revspace)) +
|
|
53
|
+
(((low << space) >> 31) & 1)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// @ts-ignore: inline
|
|
58
|
+
@inline function scaledown(significand: u64, exp: i32): f64 {
|
|
59
|
+
const denom: u64 = 6103515625; // 1e14 * 0x1p-14
|
|
60
|
+
const scale = reinterpret<f64>(0x3f06849b86a12b9b); // 1e-14 * 0x1p32
|
|
61
|
+
|
|
62
|
+
let shift = clz(significand);
|
|
63
|
+
significand <<= shift;
|
|
64
|
+
shift = exp - shift;
|
|
65
|
+
|
|
66
|
+
for (; exp <= -14; exp += 14) {
|
|
67
|
+
const q = significand / denom;
|
|
68
|
+
const r = significand % denom;
|
|
69
|
+
const s = clz(q);
|
|
70
|
+
significand = (q << s) + <u64>nearest(scale * <f64>(r << (s - 18)));
|
|
71
|
+
shift -= s;
|
|
72
|
+
}
|
|
73
|
+
const b = <u64>pow5_32(-exp);
|
|
74
|
+
const q = significand / b;
|
|
75
|
+
const r = significand % b;
|
|
76
|
+
const s = clz(q);
|
|
77
|
+
significand =
|
|
78
|
+
(q << s) +
|
|
79
|
+
<u64>(reinterpret<f64>(reinterpret<u64>(<f64>r) + (s << 52)) / <f64>b);
|
|
80
|
+
shift -= s;
|
|
81
|
+
|
|
82
|
+
return NativeMath.scalbn(<f64>significand, <i32>shift);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// @ts-ignore: inline
|
|
86
|
+
@inline function scaleup(significand: u64, exp: i32): f64 {
|
|
87
|
+
const coeff: u32 = 1220703125; // 1e13 * 0x1p-13;
|
|
88
|
+
let shift = ctz(significand);
|
|
89
|
+
significand >>= shift;
|
|
90
|
+
shift += exp;
|
|
91
|
+
|
|
92
|
+
__fixmulShift = shift;
|
|
93
|
+
for (; exp >= 13; exp -= 13) {
|
|
94
|
+
significand = fixmul(significand, coeff);
|
|
95
|
+
}
|
|
96
|
+
significand = fixmul(significand, <u32>pow5_32(exp));
|
|
97
|
+
shift = __fixmulShift;
|
|
98
|
+
return NativeMath.scalbn(<f64>significand, <i32>shift);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Construct an f64 from a u64 mantissa and decimal exponent. Result is
|
|
103
|
+
* correctly rounded — bit-identical to `f64.parse` for any input the SWAR
|
|
104
|
+
* float deserializer can pre-parse into this form.
|
|
105
|
+
*
|
|
106
|
+
* Caller guarantees the digit run that produced `significand` was already
|
|
107
|
+
* scanned and validated; this function only handles the value computation,
|
|
108
|
+
* not the lexing.
|
|
109
|
+
*
|
|
110
|
+
* @param significand u64 mantissa (any value from 0 to U64.MAX_VALUE)
|
|
111
|
+
* @param exp Decimal exponent (e.g. for "12.34" pass 1234 and -2)
|
|
112
|
+
* @returns The correctly rounded f64, or 0 / Infinity at the extremes.
|
|
113
|
+
*/
|
|
114
|
+
// @ts-ignore: inline
|
|
115
|
+
@inline export function scientific(significand: u64, exp: i32): f64 {
|
|
116
|
+
if (!significand || exp < -342) return 0;
|
|
117
|
+
if (exp > 308) return Infinity;
|
|
118
|
+
let significandf = <f64>significand;
|
|
119
|
+
if (!exp) return significandf;
|
|
120
|
+
if (exp > 22 && exp <= 22 + 15) {
|
|
121
|
+
significandf *= pow10(exp - 22);
|
|
122
|
+
exp = 22;
|
|
123
|
+
}
|
|
124
|
+
if (significand <= 9007199254740991 && abs(exp) <= 22) {
|
|
125
|
+
if (exp > 0) return significandf * pow10(exp);
|
|
126
|
+
return significandf / pow10(-exp);
|
|
127
|
+
} else if (exp < 0) {
|
|
128
|
+
return scaledown(significand, exp);
|
|
129
|
+
} else {
|
|
130
|
+
return scaleup(significand, exp);
|
|
131
|
+
}
|
|
132
|
+
}
|
package/lib/as-bs.ts
CHANGED
|
@@ -21,7 +21,7 @@ export namespace bs {
|
|
|
21
21
|
export let offset: usize = buffer;
|
|
22
22
|
|
|
23
23
|
/** Byte length of the buffer. */
|
|
24
|
-
let bufferSize: usize = MIN_BUFFER_SIZE;
|
|
24
|
+
export let bufferSize: usize = MIN_BUFFER_SIZE;
|
|
25
25
|
|
|
26
26
|
/** Proposed size of output */
|
|
27
27
|
export let stackSize: usize = 0;
|
|
@@ -104,6 +104,19 @@ export namespace bs {
|
|
|
104
104
|
pauseStackSizes.length = index;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Resets the buffer to a clean, empty state. Call this after a throw aborts a
|
|
109
|
+
* serialize/deserialize op mid-flight: a partial run can leave `offset`
|
|
110
|
+
* advanced and the pause stacks non-empty, which would corrupt the next op.
|
|
111
|
+
*/
|
|
112
|
+
// @ts-expect-error: @inline is a valid decorator
|
|
113
|
+
@inline export function reset(): void {
|
|
114
|
+
offset = buffer;
|
|
115
|
+
stackSize = 0;
|
|
116
|
+
pauseOffsets.length = 0;
|
|
117
|
+
pauseStackSizes.length = 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
/**
|
|
108
121
|
* Proposes that the buffer size is should be greater than or equal to the proposed size.
|
|
109
122
|
* If necessary, reallocates the buffer to the exact new size.
|