json-as 1.3.7 → 1.3.8
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 +32 -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 +153 -236
- 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
|
@@ -52,101 +52,103 @@ import { hex4_to_u16_swar } from "../../util/swar";
|
|
|
52
52
|
return changetype<string>(out);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Standalone (whole-value) escaped scanner. Quotes are already stripped, so
|
|
56
|
+
// `srcEnd` is the payload end and only `\` escapes are handled. Same HYBRID
|
|
57
|
+
// strategy as the field scanner: escape blocks use a free optimistic u64 store
|
|
58
|
+
// for the plain prefix; clean runs stream the first block then bulk-memcpy the
|
|
59
|
+
// remainder. The `backslash_mask_unsafe` hits are confirmed scalarly.
|
|
60
|
+
//
|
|
61
|
+
// NOTE: vs the prior overflow scanner this is faster on dense and sparse
|
|
62
|
+
// escaping but ~20% slower on sustained moderate-density escaping (escape
|
|
63
|
+
// every ~20 chars), where multi-escape-per-block had an edge.
|
|
55
64
|
// @ts-expect-error: @inline is a valid decorator
|
|
56
65
|
@inline function deserializeEscapedString_SWAR(
|
|
57
66
|
payloadStart: usize,
|
|
58
67
|
escapeStart: usize,
|
|
59
68
|
srcEnd: usize,
|
|
60
69
|
): string {
|
|
61
|
-
const srcEnd8 = srcEnd - 8;
|
|
62
70
|
const prefixLen = <u32>(escapeStart - payloadStart);
|
|
63
71
|
const outStart = bs.offset - bs.buffer;
|
|
64
|
-
bs.ensureSize(<u32>(srcEnd - payloadStart));
|
|
72
|
+
bs.ensureSize(<u32>(srcEnd - payloadStart) + 8); // +8 slack for u64 overcopy
|
|
65
73
|
if (prefixLen != 0) {
|
|
66
74
|
memory.copy(bs.offset, payloadStart, prefixLen);
|
|
67
75
|
bs.offset += prefixLen;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
let srcStart = escapeStart;
|
|
79
|
+
const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
|
|
71
80
|
|
|
72
|
-
while (srcStart
|
|
81
|
+
while (srcStart <= srcEnd8) {
|
|
73
82
|
const block = load<u64>(srcStart);
|
|
74
|
-
store<u64>(bs.offset, block);
|
|
75
|
-
|
|
76
83
|
let mask = inline.always(backslash_mask_unsafe(block));
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (mask === 0) {
|
|
80
|
-
srcStart += 8;
|
|
84
|
+
if (mask == 0) {
|
|
85
|
+
store<u64>(bs.offset, block);
|
|
81
86
|
bs.offset += 8;
|
|
87
|
+
srcStart += 8;
|
|
88
|
+
if (
|
|
89
|
+
srcStart <= srcEnd8 &&
|
|
90
|
+
inline.always(backslash_mask_unsafe(load<u64>(srcStart))) == 0
|
|
91
|
+
) {
|
|
92
|
+
const runStart = srcStart;
|
|
93
|
+
srcStart += 8;
|
|
94
|
+
while (
|
|
95
|
+
srcStart <= srcEnd8 &&
|
|
96
|
+
inline.always(backslash_mask_unsafe(load<u64>(srcStart))) == 0
|
|
97
|
+
) {
|
|
98
|
+
srcStart += 8;
|
|
99
|
+
}
|
|
100
|
+
const runLen = <u32>(srcStart - runStart);
|
|
101
|
+
memory.copy(bs.offset, runStart, runLen);
|
|
102
|
+
bs.offset += runLen;
|
|
103
|
+
}
|
|
82
104
|
continue;
|
|
83
105
|
}
|
|
84
106
|
|
|
107
|
+
store<u64>(bs.offset, block);
|
|
108
|
+
let handled = false;
|
|
85
109
|
do {
|
|
86
|
-
const laneIdx = usize(ctz(mask) >> 3);
|
|
110
|
+
const laneIdx = usize(ctz(mask) >> 3);
|
|
87
111
|
mask &= mask - 1;
|
|
88
112
|
const srcIdx = srcStart + laneIdx;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const code = <u16>(
|
|
92
|
-
|
|
93
|
-
// Detect false positive (code unit where low byte is 0x5C)
|
|
94
|
-
if ((header & 0xffff) !== 0x5c) continue;
|
|
95
|
-
|
|
96
|
-
// Hot path (negative bias)
|
|
113
|
+
if ((load<u32>(srcIdx) & 0xffff) !== 0x5c) continue; // false positive
|
|
114
|
+
bs.offset += laneIdx;
|
|
115
|
+
const code = load<u16>(srcIdx, 2);
|
|
97
116
|
if (code !== 0x75) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
store<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
bs.offset -= (1 - l6) << 1;
|
|
106
|
-
srcStart += l6 << 1;
|
|
107
|
-
continue;
|
|
117
|
+
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
118
|
+
bs.offset += 2;
|
|
119
|
+
srcStart = srcIdx + 4;
|
|
120
|
+
} else {
|
|
121
|
+
store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
|
|
122
|
+
bs.offset += 2;
|
|
123
|
+
srcStart = srcIdx + 12;
|
|
108
124
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
bs.offset -= 6 - laneIdx;
|
|
117
|
-
} while (mask !== 0);
|
|
118
|
-
|
|
119
|
-
bs.offset += 8;
|
|
120
|
-
srcStart += 8;
|
|
125
|
+
handled = true;
|
|
126
|
+
break;
|
|
127
|
+
} while (mask != 0);
|
|
128
|
+
if (!handled) {
|
|
129
|
+
bs.offset += 8;
|
|
130
|
+
srcStart += 8;
|
|
131
|
+
}
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
while (srcStart < srcEnd) {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Early exit
|
|
129
|
-
if (block !== 0x5c) {
|
|
135
|
+
const char = load<u16>(srcStart);
|
|
136
|
+
if (char != BACK_SLASH) {
|
|
137
|
+
store<u16>(bs.offset, char);
|
|
130
138
|
bs.offset += 2;
|
|
139
|
+
srcStart += 2;
|
|
131
140
|
continue;
|
|
132
141
|
}
|
|
133
|
-
|
|
134
|
-
const code = load<u16>(srcStart);
|
|
142
|
+
const code = load<u16>(srcStart, 2);
|
|
135
143
|
if (code !== 0x75) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
store<u16>(bs.offset, escape);
|
|
140
|
-
srcStart += 2;
|
|
144
|
+
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
145
|
+
bs.offset += 2;
|
|
146
|
+
srcStart += 4;
|
|
141
147
|
} else {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
store<u16>(bs.offset, escaped);
|
|
146
|
-
srcStart += 10;
|
|
148
|
+
store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
|
|
149
|
+
bs.offset += 2;
|
|
150
|
+
srcStart += 12;
|
|
147
151
|
}
|
|
148
|
-
|
|
149
|
-
bs.offset += 2;
|
|
150
152
|
}
|
|
151
153
|
return bs.sliceOut<string>(outStart);
|
|
152
154
|
}
|
|
@@ -245,45 +247,72 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
|
|
|
245
247
|
memory.copy(stringPtr, srcStart, byteLength);
|
|
246
248
|
}
|
|
247
249
|
|
|
248
|
-
// Scans a quoted string value, writes into the destination field, and returns
|
|
250
|
+
// Scans a quoted string value, writes into the destination field, and returns
|
|
251
|
+
// the next unread src pointer.
|
|
252
|
+
//
|
|
253
|
+
// HYBRID strategy (validated against the prior run-copy scanner across escape
|
|
254
|
+
// densities — see __benches__/custom/swar-string-deser-hybrid-h2h: +17–70%):
|
|
255
|
+
// * Escape-bearing block: one optimistic whole-block u64 store copies the
|
|
256
|
+
// plain prefix for free, then the (scalar-confirmed) escape is decoded.
|
|
257
|
+
// * Clean block: stream the first one, then if the clean run continues switch
|
|
258
|
+
// to one bulk memory.copy for the remainder.
|
|
259
|
+
// SWAR masks carry high-byte false positives, so each hit is confirmed
|
|
260
|
+
// scalarly before acting.
|
|
249
261
|
// @ts-expect-error: @inline is a valid decorator
|
|
250
|
-
@inline function
|
|
262
|
+
@inline function deserializeEscapedStringField_SWAR(
|
|
251
263
|
payloadStart: usize,
|
|
252
264
|
escapeStart: usize,
|
|
253
265
|
srcEnd: usize,
|
|
254
266
|
dstFieldPtr: usize,
|
|
255
267
|
): usize {
|
|
256
268
|
const prefixLen = <u32>(escapeStart - payloadStart);
|
|
257
|
-
const srcEnd8 = srcEnd - 8;
|
|
258
269
|
bs.offset = bs.buffer;
|
|
259
|
-
bs.ensureSize(<u32>(srcEnd - payloadStart));
|
|
270
|
+
bs.ensureSize(<u32>(srcEnd - payloadStart) + 8); // +8 slack for u64 overcopy
|
|
260
271
|
if (prefixLen != 0) {
|
|
261
272
|
memory.copy(bs.buffer, payloadStart, prefixLen);
|
|
262
273
|
bs.offset += prefixLen;
|
|
263
274
|
}
|
|
264
275
|
|
|
265
|
-
let lastPtr = escapeStart;
|
|
266
276
|
let srcStart = escapeStart;
|
|
277
|
+
const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
|
|
267
278
|
|
|
268
279
|
while (srcStart <= srcEnd8) {
|
|
269
|
-
const
|
|
270
|
-
let mask = inline.always(backslash_or_quote_mask(
|
|
271
|
-
if (mask
|
|
280
|
+
const block = load<u64>(srcStart);
|
|
281
|
+
let mask = inline.always(backslash_or_quote_mask(block));
|
|
282
|
+
if (mask == 0) {
|
|
283
|
+
store<u64>(bs.offset, block);
|
|
284
|
+
bs.offset += 8;
|
|
272
285
|
srcStart += 8;
|
|
286
|
+
if (
|
|
287
|
+
srcStart <= srcEnd8 &&
|
|
288
|
+
inline.always(backslash_or_quote_mask(load<u64>(srcStart))) == 0
|
|
289
|
+
) {
|
|
290
|
+
const runStart = srcStart;
|
|
291
|
+
srcStart += 8;
|
|
292
|
+
while (
|
|
293
|
+
srcStart <= srcEnd8 &&
|
|
294
|
+
inline.always(backslash_or_quote_mask(load<u64>(srcStart))) == 0
|
|
295
|
+
) {
|
|
296
|
+
srcStart += 8;
|
|
297
|
+
}
|
|
298
|
+
const runLen = <u32>(srcStart - runStart);
|
|
299
|
+
memory.copy(bs.offset, runStart, runLen);
|
|
300
|
+
bs.offset += runLen;
|
|
301
|
+
}
|
|
273
302
|
continue;
|
|
274
303
|
}
|
|
275
304
|
|
|
305
|
+
// Escape/quote block (mask may carry high-byte false positives).
|
|
306
|
+
store<u64>(bs.offset, block);
|
|
307
|
+
let handled = false;
|
|
276
308
|
do {
|
|
277
309
|
const laneIdx = usize(ctz(mask) >> 3);
|
|
278
310
|
mask &= mask - 1;
|
|
279
311
|
const srcIdx = srcStart + laneIdx;
|
|
280
312
|
const char = load<u16>(srcIdx);
|
|
313
|
+
if (char != QUOTE && char != BACK_SLASH) continue; // false positive
|
|
314
|
+
bs.offset += laneIdx;
|
|
281
315
|
if (char == QUOTE) {
|
|
282
|
-
const runLen = <u32>(srcIdx - lastPtr);
|
|
283
|
-
if (runLen != 0) {
|
|
284
|
-
memory.copy(bs.offset, lastPtr, runLen);
|
|
285
|
-
bs.offset += runLen;
|
|
286
|
-
}
|
|
287
316
|
writeStringToField(
|
|
288
317
|
dstFieldPtr,
|
|
289
318
|
bs.buffer,
|
|
@@ -292,54 +321,39 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
|
|
|
292
321
|
bs.offset = bs.buffer;
|
|
293
322
|
return srcIdx + 2;
|
|
294
323
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const runLen = <u32>(srcIdx - lastPtr);
|
|
298
|
-
if (runLen != 0) {
|
|
299
|
-
memory.copy(bs.offset, lastPtr, runLen);
|
|
300
|
-
bs.offset += runLen;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const chunk = load<u32>(srcIdx);
|
|
304
|
-
const code = <u16>(chunk >> 16);
|
|
324
|
+
const code = load<u16>(srcIdx, 2);
|
|
305
325
|
if (code !== 0x75) {
|
|
306
326
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
307
327
|
bs.offset += 2;
|
|
308
|
-
|
|
328
|
+
srcStart = srcIdx + 4;
|
|
309
329
|
} else {
|
|
310
330
|
store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
|
|
311
331
|
bs.offset += 2;
|
|
312
|
-
|
|
332
|
+
srcStart = srcIdx + 12;
|
|
313
333
|
}
|
|
314
|
-
|
|
334
|
+
handled = true;
|
|
315
335
|
break;
|
|
316
|
-
} while (mask
|
|
317
|
-
if (
|
|
336
|
+
} while (mask != 0);
|
|
337
|
+
if (!handled) {
|
|
338
|
+
bs.offset += 8;
|
|
339
|
+
srcStart += 8;
|
|
340
|
+
}
|
|
318
341
|
}
|
|
319
342
|
|
|
343
|
+
// scalar tail (< 8 bytes remaining)
|
|
320
344
|
while (srcStart < srcEnd) {
|
|
321
345
|
const char = load<u16>(srcStart);
|
|
322
346
|
if (char == QUOTE) {
|
|
323
|
-
const runLen = <u32>(srcStart - lastPtr);
|
|
324
|
-
if (runLen != 0) {
|
|
325
|
-
memory.copy(bs.offset, lastPtr, runLen);
|
|
326
|
-
bs.offset += runLen;
|
|
327
|
-
}
|
|
328
347
|
writeStringToField(dstFieldPtr, bs.buffer, <u32>(bs.offset - bs.buffer));
|
|
329
348
|
bs.offset = bs.buffer;
|
|
330
349
|
return srcStart + 2;
|
|
331
350
|
}
|
|
332
351
|
if (char != BACK_SLASH) {
|
|
352
|
+
store<u16>(bs.offset, char);
|
|
353
|
+
bs.offset += 2;
|
|
333
354
|
srcStart += 2;
|
|
334
355
|
continue;
|
|
335
356
|
}
|
|
336
|
-
|
|
337
|
-
const runLen = <u32>(srcStart - lastPtr);
|
|
338
|
-
if (runLen != 0) {
|
|
339
|
-
memory.copy(bs.offset, lastPtr, runLen);
|
|
340
|
-
bs.offset += runLen;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
357
|
const code = load<u16>(srcStart, 2);
|
|
344
358
|
if (code !== 0x75) {
|
|
345
359
|
store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
|
|
@@ -350,7 +364,6 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
|
|
|
350
364
|
bs.offset += 2;
|
|
351
365
|
srcStart += 12;
|
|
352
366
|
}
|
|
353
|
-
lastPtr = srcStart;
|
|
354
367
|
}
|
|
355
368
|
|
|
356
369
|
bs.offset = bs.buffer;
|
|
@@ -462,8 +475,10 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
|
|
|
462
475
|
export function deserializeStringField_SWAR<T extends string | null>(
|
|
463
476
|
srcStart: usize,
|
|
464
477
|
srcEnd: usize,
|
|
465
|
-
|
|
478
|
+
dstObj: usize,
|
|
479
|
+
dstOffset: usize = 0,
|
|
466
480
|
): usize {
|
|
481
|
+
const dstFieldPtr = dstObj + dstOffset;
|
|
467
482
|
if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
|
|
468
483
|
abort("Expected leading quote");
|
|
469
484
|
|
|
@@ -506,7 +521,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
506
521
|
}
|
|
507
522
|
if (char != BACK_SLASH) continue;
|
|
508
523
|
return inline.always(
|
|
509
|
-
|
|
524
|
+
deserializeEscapedStringField_SWAR(
|
|
510
525
|
payloadStart,
|
|
511
526
|
srcIdx,
|
|
512
527
|
srcEnd,
|
|
@@ -530,7 +545,7 @@ export function deserializeStringField_SWAR<T extends string | null>(
|
|
|
530
545
|
}
|
|
531
546
|
if (char == BACK_SLASH) {
|
|
532
547
|
return inline.always(
|
|
533
|
-
|
|
548
|
+
deserializeEscapedStringField_SWAR(
|
|
534
549
|
payloadStart,
|
|
535
550
|
srcStart,
|
|
536
551
|
srcEnd,
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// SWAR-mode TypedArray / ArrayBuffer deserializer.
|
|
2
|
+
//
|
|
3
|
+
// The naive variant in `../naive/typedarray.ts` does two scalar passes
|
|
4
|
+
// (count digit-starts, then call `JSON.__deserialize` per element which
|
|
5
|
+
// re-scans the same digits). This rewrite replaces both passes:
|
|
6
|
+
//
|
|
7
|
+
// - **No count pass.** TypedArrays have a fixed length at construction,
|
|
8
|
+
// so the natural approach is to count first then allocate. We tried
|
|
9
|
+
// that with a SWAR comma counter — it cut the per-element cost but
|
|
10
|
+
// kept us ~30% below the top-level `f64[]` path because the count
|
|
11
|
+
// scan still touched the whole input twice. Instead we allocate
|
|
12
|
+
// worst-case (`(srcEnd - srcStart) >> 2 + 1` elements — each
|
|
13
|
+
// element needs >= "D," = 2 UTF-16 chars = 4 bytes) and `__renew`
|
|
14
|
+
// the underlying buffer down to the exact byte count after parsing.
|
|
15
|
+
// The over-allocation peaks at ~2-3× the final size for typical
|
|
16
|
+
// payloads; the trim is a single `memory.copy` on the GC's terms.
|
|
17
|
+
//
|
|
18
|
+
// - **Inline parse.** The integer parsers come from `./array/integer.ts`
|
|
19
|
+
// (refactored to take element type `E` so the same
|
|
20
|
+
// `parseSignedIntegerSWAR` serves `Array<i32>` and `Int32Array`).
|
|
21
|
+
// The float parser comes from `./array/float.ts`. Stores write
|
|
22
|
+
// directly to `dataStart + index * elementSize`, bypassing the
|
|
23
|
+
// typed-array's bounds-checked `[]=` setter.
|
|
24
|
+
|
|
25
|
+
import { isSpace } from "../../util";
|
|
26
|
+
import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../custom/chars";
|
|
27
|
+
import { parseFloatElementSWAR } from "./array/float";
|
|
28
|
+
import {
|
|
29
|
+
parseSignedIntegerSWAR,
|
|
30
|
+
parseUnsignedIntegerSWAR,
|
|
31
|
+
} from "./array/integer";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* SWAR TypedArray deserializer.
|
|
35
|
+
*
|
|
36
|
+
* Counts commas (with empty-body detection), allocates the typed array
|
|
37
|
+
* at the exact size, then parses each element inline with no per-call
|
|
38
|
+
* function dispatch. Stores write directly to `dataStart + idx *
|
|
39
|
+
* elementSize`, bypassing the typed-array's bounds-checked `[]=` setter.
|
|
40
|
+
*
|
|
41
|
+
* Falls through to the underlying SWAR float / integer parsers; the
|
|
42
|
+
* element type (`f32/f64/u8/i32/...`) is detected via `isFloat<E>()` /
|
|
43
|
+
* `isSigned<E>()` and AS folds the type dispatch at compile time.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Worst-case element count: each element occupies >= 1 digit + 1
|
|
47
|
+
* delimiter = 2 UTF-16 chars = 4 bytes. So `(srcEnd - srcStart) >> 2`
|
|
48
|
+
* upper-bounds the count. Allocating to worst-case lets us skip a
|
|
49
|
+
* full count pass over the input — at the cost of an over-allocated
|
|
50
|
+
* underlying buffer that we trim via `__renew` once we know the
|
|
51
|
+
* actual element count.
|
|
52
|
+
*
|
|
53
|
+
* For a top-level f64[] payload of ~64 MiB JSON encoding 6M floats,
|
|
54
|
+
* worst-case alloc is ~16M f64 = 128 MB temporarily. We trim back
|
|
55
|
+
* to ~48 MB after parse. The trim is a wasm `memory.copy` (or just
|
|
56
|
+
* a length update if the runtime supports in-place shrink), much
|
|
57
|
+
* cheaper than a second 64 MB scan over the input.
|
|
58
|
+
*/
|
|
59
|
+
export function deserializeTypedArray_SWAR<T extends ArrayLike<number>>(
|
|
60
|
+
srcStart: usize,
|
|
61
|
+
srcEnd: usize,
|
|
62
|
+
dst: usize = 0,
|
|
63
|
+
): T {
|
|
64
|
+
// Find the opening `[`, then skip whitespace to the first non-WS char.
|
|
65
|
+
while (srcStart < srcEnd) {
|
|
66
|
+
const ch = load<u16>(srcStart);
|
|
67
|
+
if (ch == BRACKET_LEFT) {
|
|
68
|
+
srcStart += 2;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
srcStart += 2;
|
|
72
|
+
}
|
|
73
|
+
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
74
|
+
|
|
75
|
+
// Empty-array fast path.
|
|
76
|
+
if (srcStart >= srcEnd || load<u16>(srcStart) == BRACKET_RIGHT) {
|
|
77
|
+
let out = changetype<T>(dst || changetype<usize>(instantiate<T>(0)));
|
|
78
|
+
if (out.length != 0) out = changetype<T>(instantiate<T>(0));
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const elementSize = sizeof<valueof<T>>();
|
|
83
|
+
const maxElements = i32((<usize>(srcEnd - srcStart)) >> 2) + 1;
|
|
84
|
+
let out = changetype<T>(
|
|
85
|
+
dst || changetype<usize>(instantiate<T>(maxElements)),
|
|
86
|
+
);
|
|
87
|
+
if (out.length != maxElements) {
|
|
88
|
+
out = changetype<T>(instantiate<T>(maxElements));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const dataStart = out.dataStart;
|
|
92
|
+
let writePtr = dataStart;
|
|
93
|
+
|
|
94
|
+
// Parse loop. Each element parses into the slot at `writePtr`, then
|
|
95
|
+
// the separator (`,` or `]`) is consumed. Whitespace surrounding the
|
|
96
|
+
// separator is skipped to match the naive variant's behaviour.
|
|
97
|
+
while (srcStart < srcEnd) {
|
|
98
|
+
let next: usize = 0;
|
|
99
|
+
if (isFloat<valueof<T>>()) {
|
|
100
|
+
next = parseFloatElementSWAR<valueof<T>>(srcStart, srcEnd, writePtr);
|
|
101
|
+
} else if (isSigned<valueof<T>>()) {
|
|
102
|
+
next = parseSignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr);
|
|
103
|
+
} else {
|
|
104
|
+
next = parseUnsignedIntegerSWAR<valueof<T>>(srcStart, srcEnd, writePtr);
|
|
105
|
+
}
|
|
106
|
+
if (!next) break;
|
|
107
|
+
writePtr += elementSize;
|
|
108
|
+
srcStart = next;
|
|
109
|
+
if (srcStart >= srcEnd) break;
|
|
110
|
+
|
|
111
|
+
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
112
|
+
if (srcStart >= srcEnd) break;
|
|
113
|
+
const ch = load<u16>(srcStart);
|
|
114
|
+
if (ch == COMMA) {
|
|
115
|
+
srcStart += 2;
|
|
116
|
+
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (ch == BRACKET_RIGHT) break;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Trim to actual count. `out.length =` on a typed-array isn't legal
|
|
124
|
+
// (length is read-only), so we shrink the underlying ArrayBufferView
|
|
125
|
+
// directly: `__renew` the buffer to the actual byte length and
|
|
126
|
+
// update the view's `byteLength` and `dataStart`. AS's TypedArray
|
|
127
|
+
// structure has `buffer`, `dataStart` (= buffer), `byteLength`
|
|
128
|
+
// (capacity in bytes) in that order — same layout as ArrayBufferView.
|
|
129
|
+
const actualCount = i32(<usize>(writePtr - dataStart) / elementSize);
|
|
130
|
+
if (actualCount != maxElements) {
|
|
131
|
+
const actualBytes = <usize>actualCount * elementSize;
|
|
132
|
+
const oldBuffer = changetype<ArrayBuffer>(
|
|
133
|
+
load<usize>(changetype<usize>(out)),
|
|
134
|
+
);
|
|
135
|
+
const newBuffer = __renew(changetype<usize>(oldBuffer), actualBytes);
|
|
136
|
+
// Update buffer, dataStart, byteLength on the view.
|
|
137
|
+
store<usize>(changetype<usize>(out), newBuffer);
|
|
138
|
+
store<usize>(
|
|
139
|
+
changetype<usize>(out),
|
|
140
|
+
newBuffer,
|
|
141
|
+
offsetof<ArrayBufferView>("dataStart"),
|
|
142
|
+
);
|
|
143
|
+
store<i32>(
|
|
144
|
+
changetype<usize>(out),
|
|
145
|
+
i32(actualBytes),
|
|
146
|
+
offsetof<ArrayBufferView>("byteLength"),
|
|
147
|
+
);
|
|
148
|
+
__link(changetype<usize>(out), newBuffer, false);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* SWAR ArrayBuffer deserializer. JSON encoding is `[u8, u8, ...]` so
|
|
156
|
+
* elements are always 1-3 ASCII digits (0..255). We can use the same
|
|
157
|
+
* comma-count + inline-parse strategy as above, but since the result
|
|
158
|
+
* is an `ArrayBuffer` rather than a `TypedArray<E>`, we use a plain
|
|
159
|
+
* `store<u8>` directly.
|
|
160
|
+
*/
|
|
161
|
+
export function deserializeArrayBuffer_SWAR(
|
|
162
|
+
srcStart: usize,
|
|
163
|
+
srcEnd: usize,
|
|
164
|
+
dst: usize = 0,
|
|
165
|
+
): ArrayBuffer {
|
|
166
|
+
while (srcStart < srcEnd) {
|
|
167
|
+
const ch = load<u16>(srcStart);
|
|
168
|
+
if (ch == BRACKET_LEFT) {
|
|
169
|
+
srcStart += 2;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
srcStart += 2;
|
|
173
|
+
}
|
|
174
|
+
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
175
|
+
|
|
176
|
+
if (srcStart >= srcEnd || load<u16>(srcStart) == BRACKET_RIGHT) {
|
|
177
|
+
let out = dst ? changetype<ArrayBuffer>(dst) : new ArrayBuffer(0);
|
|
178
|
+
if (out.byteLength != 0) out = new ArrayBuffer(0);
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Worst-case byte count: each element is `D,` minimum = 4 bytes.
|
|
183
|
+
const maxBytes = i32((<usize>(srcEnd - srcStart)) >> 2) + 1;
|
|
184
|
+
let out = dst ? changetype<ArrayBuffer>(dst) : new ArrayBuffer(maxBytes);
|
|
185
|
+
if (out.byteLength != maxBytes) {
|
|
186
|
+
out = new ArrayBuffer(maxBytes);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const dataStart = changetype<usize>(out);
|
|
190
|
+
let writePtr: usize = 0;
|
|
191
|
+
|
|
192
|
+
while (srcStart < srcEnd) {
|
|
193
|
+
const next = parseUnsignedIntegerSWAR<u8>(
|
|
194
|
+
srcStart,
|
|
195
|
+
srcEnd,
|
|
196
|
+
dataStart + writePtr,
|
|
197
|
+
);
|
|
198
|
+
if (!next) break;
|
|
199
|
+
writePtr += 1;
|
|
200
|
+
srcStart = next;
|
|
201
|
+
if (srcStart >= srcEnd) break;
|
|
202
|
+
|
|
203
|
+
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
204
|
+
if (srcStart >= srcEnd) break;
|
|
205
|
+
const ch = load<u16>(srcStart);
|
|
206
|
+
if (ch == COMMA) {
|
|
207
|
+
srcStart += 2;
|
|
208
|
+
while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (ch == BRACKET_RIGHT) break;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Trim to actual byte count via `__renew`.
|
|
216
|
+
const actualBytes = i32(writePtr);
|
|
217
|
+
if (actualBytes != maxBytes) {
|
|
218
|
+
out = changetype<ArrayBuffer>(
|
|
219
|
+
__renew(changetype<usize>(out), <usize>actualBytes),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return out;
|
|
224
|
+
}
|