json-as 1.3.6 → 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.
Files changed (155) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +1 -1
  3. package/assembly/deserialize/helpers/uint.ts +4 -1
  4. package/assembly/deserialize/index/arbitrary.ts +7 -3
  5. package/assembly/deserialize/index/array.ts +42 -17
  6. package/assembly/deserialize/index/bool.ts +1 -1
  7. package/assembly/deserialize/index/date.ts +1 -1
  8. package/assembly/deserialize/index/float.ts +40 -1
  9. package/assembly/deserialize/index/integer.ts +68 -1
  10. package/assembly/deserialize/index/map.ts +1 -1
  11. package/assembly/deserialize/index/object.ts +1 -1
  12. package/assembly/deserialize/index/raw.ts +1 -1
  13. package/assembly/deserialize/index/set.ts +1 -1
  14. package/assembly/deserialize/index/staticarray.ts +4 -1
  15. package/assembly/deserialize/index/string.ts +32 -4
  16. package/assembly/deserialize/index/struct.ts +1 -1
  17. package/assembly/deserialize/index/typedarray.ts +30 -10
  18. package/assembly/deserialize/index/unsigned.ts +78 -1
  19. package/assembly/deserialize/index.ts +1 -0
  20. package/assembly/deserialize/{simple → naive}/array/arbitrary.ts +24 -5
  21. package/assembly/deserialize/{simple → naive}/array/array.ts +8 -2
  22. package/assembly/deserialize/naive/array/bool.ts +68 -0
  23. package/assembly/deserialize/{simple → naive}/array/box.ts +8 -2
  24. package/assembly/deserialize/naive/array/float.ts +63 -0
  25. package/assembly/deserialize/{simple → naive}/array/generic.ts +14 -7
  26. package/assembly/deserialize/naive/array/integer.ts +86 -0
  27. package/assembly/deserialize/naive/array/map.ts +47 -0
  28. package/assembly/deserialize/naive/array/object.ts +47 -0
  29. package/assembly/deserialize/{simple → naive}/array/raw.ts +34 -7
  30. package/assembly/deserialize/naive/array/string.ts +69 -0
  31. package/assembly/deserialize/naive/array/struct.ts +47 -0
  32. package/assembly/deserialize/{simple → naive}/array.ts +15 -10
  33. package/assembly/deserialize/{simple → naive}/bool.ts +6 -2
  34. package/assembly/deserialize/naive/float.ts +135 -0
  35. package/assembly/deserialize/{simple → naive}/integer.ts +10 -2
  36. package/assembly/deserialize/{simple → naive}/map.ts +106 -27
  37. package/assembly/deserialize/{simple → naive}/object.ts +65 -19
  38. package/assembly/deserialize/{simple → naive}/raw.ts +4 -1
  39. package/assembly/deserialize/{simple → naive}/set.ts +49 -19
  40. package/assembly/deserialize/{simple → naive}/staticarray/array.ts +1 -1
  41. package/assembly/deserialize/{simple → naive}/staticarray/bool.ts +1 -1
  42. package/assembly/deserialize/{simple → naive}/staticarray/float.ts +1 -1
  43. package/assembly/deserialize/{simple → naive}/staticarray/integer.ts +1 -1
  44. package/assembly/deserialize/{simple → naive}/staticarray/string.ts +11 -3
  45. package/assembly/deserialize/{simple → naive}/staticarray/struct.ts +1 -2
  46. package/assembly/deserialize/{simple → naive}/staticarray.ts +68 -18
  47. package/assembly/deserialize/naive/string.ts +199 -0
  48. package/assembly/deserialize/{simple → naive}/struct.ts +5 -1
  49. package/assembly/deserialize/{simple → naive}/typedarray.ts +17 -4
  50. package/assembly/deserialize/{simple → naive}/unsigned.ts +10 -15
  51. package/assembly/deserialize/simd/array/integer.ts +339 -62
  52. package/assembly/deserialize/simd/float.ts +303 -0
  53. package/assembly/deserialize/simd/integer.ts +233 -0
  54. package/assembly/deserialize/simd/string.ts +266 -107
  55. package/assembly/deserialize/swar/array/arbitrary.ts +11 -3
  56. package/assembly/deserialize/swar/array/array.ts +40 -9
  57. package/assembly/deserialize/swar/array/bool.ts +28 -5
  58. package/assembly/deserialize/swar/array/box.ts +11 -3
  59. package/assembly/deserialize/swar/array/float.ts +295 -7
  60. package/assembly/deserialize/swar/array/generic.ts +28 -7
  61. package/assembly/deserialize/swar/array/integer.ts +363 -112
  62. package/assembly/deserialize/swar/array/map.ts +11 -3
  63. package/assembly/deserialize/swar/array/object.ts +37 -25
  64. package/assembly/deserialize/swar/array/raw.ts +11 -3
  65. package/assembly/deserialize/swar/array/shared.ts +63 -14
  66. package/assembly/deserialize/swar/array/string.ts +140 -7
  67. package/assembly/deserialize/swar/array/struct.ts +66 -12
  68. package/assembly/deserialize/swar/array.ts +12 -51
  69. package/assembly/deserialize/swar/float.ts +304 -0
  70. package/assembly/deserialize/swar/integer.ts +246 -0
  71. package/assembly/deserialize/swar/string.ts +213 -294
  72. package/assembly/deserialize/swar/typedarray.ts +224 -0
  73. package/assembly/index.d.ts +3 -1
  74. package/assembly/index.ts +402 -261
  75. package/assembly/serialize/index/array.ts +1 -1
  76. package/assembly/serialize/index/bool.ts +1 -1
  77. package/assembly/serialize/index/date.ts +1 -1
  78. package/assembly/serialize/index/float.ts +5 -1
  79. package/assembly/serialize/index/integer.ts +1 -1
  80. package/assembly/serialize/index/map.ts +1 -1
  81. package/assembly/serialize/index/raw.ts +1 -1
  82. package/assembly/serialize/index/set.ts +1 -1
  83. package/assembly/serialize/index/staticarray.ts +1 -1
  84. package/assembly/serialize/index/string.ts +1 -1
  85. package/assembly/serialize/index/struct.ts +1 -1
  86. package/assembly/serialize/index/typedarray.ts +21 -12
  87. package/assembly/serialize/index.ts +1 -0
  88. package/assembly/serialize/naive/array.ts +351 -0
  89. package/assembly/serialize/{simple → naive}/float.ts +4 -1
  90. package/assembly/serialize/naive/integer.ts +19 -0
  91. package/assembly/serialize/{simple → naive}/map.ts +6 -2
  92. package/assembly/serialize/{simple → naive}/raw.ts +5 -1
  93. package/assembly/serialize/{simple → naive}/set.ts +6 -1
  94. package/assembly/serialize/{simple → naive}/staticarray.ts +6 -1
  95. package/assembly/serialize/{simple → naive}/string.ts +1 -2
  96. package/assembly/serialize/{simple → naive}/typedarray.ts +10 -3
  97. package/assembly/serialize/simd/string.ts +6 -2
  98. package/assembly/serialize/swar/string.ts +15 -141
  99. package/assembly/util/atoi-fast.ts +81 -0
  100. package/assembly/util/concat.ts +5 -1
  101. package/assembly/util/dragonbox-cache.ts +443 -2
  102. package/assembly/util/dragonbox.ts +53 -17
  103. package/assembly/util/itoa-fast.ts +241 -0
  104. package/assembly/util/masks.ts +18 -1
  105. package/assembly/util/parsefloat-fast.ts +167 -0
  106. package/assembly/util/scanValueEnd.ts +78 -0
  107. package/assembly/util/scientific.ts +132 -0
  108. package/assembly/util/simd-int.ts +191 -0
  109. package/assembly/util/snp.ts +4 -1
  110. package/assembly/util/swar-int.ts +248 -0
  111. package/assembly/util/swar.ts +13 -3
  112. package/lib/as-bs.ts +27 -6
  113. package/package.json +15 -11
  114. package/transform/lib/builder.d.ts.map +1 -1
  115. package/transform/lib/builder.js +13 -5
  116. package/transform/lib/builder.js.map +1 -1
  117. package/transform/lib/index.d.ts +5 -0
  118. package/transform/lib/index.d.ts.map +1 -1
  119. package/transform/lib/index.js +1046 -340
  120. package/transform/lib/index.js.map +1 -1
  121. package/transform/lib/linkers/alias.d.ts.map +1 -1
  122. package/transform/lib/linkers/alias.js.map +1 -1
  123. package/transform/lib/linkers/custom.d.ts.map +1 -1
  124. package/transform/lib/linkers/custom.js +3 -2
  125. package/transform/lib/linkers/custom.js.map +1 -1
  126. package/transform/lib/linkers/imports.d.ts.map +1 -1
  127. package/transform/lib/linkers/imports.js.map +1 -1
  128. package/transform/lib/types.d.ts.map +1 -1
  129. package/transform/lib/types.js +54 -16
  130. package/transform/lib/types.js.map +1 -1
  131. package/transform/lib/util.d.ts.map +1 -1
  132. package/transform/lib/util.js +1 -1
  133. package/transform/lib/util.js.map +1 -1
  134. package/transform/lib/visitor.d.ts.map +1 -1
  135. package/transform/lib/visitor.js +2 -1
  136. package/transform/lib/visitor.js.map +1 -1
  137. package/assembly/custom/util.ts +0 -310
  138. package/assembly/deserialize/simple/arbitrary.ts +0 -23
  139. package/assembly/deserialize/simple/array/bool.ts +0 -17
  140. package/assembly/deserialize/simple/array/float.ts +0 -28
  141. package/assembly/deserialize/simple/array/integer.ts +0 -27
  142. package/assembly/deserialize/simple/array/map.ts +0 -28
  143. package/assembly/deserialize/simple/array/object.ts +0 -28
  144. package/assembly/deserialize/simple/array/string.ts +0 -23
  145. package/assembly/deserialize/simple/array/struct.ts +0 -28
  146. package/assembly/deserialize/simple/float.ts +0 -201
  147. package/assembly/deserialize/simple/string.ts +0 -132
  148. package/assembly/serialize/simple/arbitrary.ts +0 -79
  149. package/assembly/serialize/simple/array.ts +0 -86
  150. package/assembly/serialize/simple/integer.ts +0 -20
  151. package/assembly/serialize/simple/object.ts +0 -42
  152. /package/assembly/deserialize/{simple → naive}/date.ts +0 -0
  153. /package/assembly/serialize/{simple → naive}/bool.ts +0 -0
  154. /package/assembly/serialize/{simple → naive}/date.ts +0 -0
  155. /package/assembly/serialize/{simple → naive}/struct.ts +0 -0
@@ -42,104 +42,113 @@ import { hex4_to_u16_swar } from "../../util/swar";
42
42
  * @returns number of bytes written
43
43
  */
44
44
  // @ts-expect-error: @inline is a valid decorator
45
- @inline function copyStringFromSource(srcStart: usize, byteLength: usize): string {
45
+ @inline function copyStringFromSource(
46
+ srcStart: usize,
47
+ byteLength: usize,
48
+ ): string {
46
49
  if (byteLength == 0) return changetype<string>("");
47
50
  const out = __new(byteLength, idof<string>());
48
51
  memory.copy(out, srcStart, byteLength);
49
52
  return changetype<string>(out);
50
53
  }
51
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.
52
64
  // @ts-expect-error: @inline is a valid decorator
53
- @inline function deserializeEscapedString_SWAR(payloadStart: usize, escapeStart: usize, srcEnd: usize): string {
54
- const srcEnd8 = srcEnd - 8;
65
+ @inline function deserializeEscapedString_SWAR(
66
+ payloadStart: usize,
67
+ escapeStart: usize,
68
+ srcEnd: usize,
69
+ ): string {
55
70
  const prefixLen = <u32>(escapeStart - payloadStart);
56
71
  const outStart = bs.offset - bs.buffer;
57
- bs.ensureSize(<u32>(srcEnd - payloadStart));
72
+ bs.ensureSize(<u32>(srcEnd - payloadStart) + 8); // +8 slack for u64 overcopy
58
73
  if (prefixLen != 0) {
59
74
  memory.copy(bs.offset, payloadStart, prefixLen);
60
75
  bs.offset += prefixLen;
61
76
  }
62
77
 
63
78
  let srcStart = escapeStart;
79
+ const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
64
80
 
65
- while (srcStart < srcEnd8) {
81
+ while (srcStart <= srcEnd8) {
66
82
  const block = load<u64>(srcStart);
67
- store<u64>(bs.offset, block);
68
-
69
83
  let mask = inline.always(backslash_mask_unsafe(block));
70
-
71
- // Early exit
72
- if (mask === 0) {
73
- srcStart += 8;
84
+ if (mask == 0) {
85
+ store<u64>(bs.offset, block);
74
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
+ }
75
104
  continue;
76
105
  }
77
106
 
107
+ store<u64>(bs.offset, block);
108
+ let handled = false;
78
109
  do {
79
- const laneIdx = usize(ctz(mask) >> 3); // 0 2 4 6
110
+ const laneIdx = usize(ctz(mask) >> 3);
80
111
  mask &= mask - 1;
81
112
  const srcIdx = srcStart + laneIdx;
82
- const dstIdx = bs.offset + laneIdx;
83
- const header = load<u32>(srcIdx);
84
- const code = <u16>(header >> 16);
85
-
86
- // Detect false positive (code unit where low byte is 0x5C)
87
- if ((header & 0xffff) !== 0x5c) continue;
88
-
89
- // 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);
90
116
  if (code !== 0x75) {
91
- // Short escapes (\n \t \" \\)
92
- const escaped = load<u16>(DESERIALIZE_ESCAPE_TABLE + code);
93
- mask &= mask - usize(escaped === 0x5c);
94
- store<u16>(dstIdx, escaped);
95
- store<u32>(dstIdx, load<u32>(srcIdx, 4), 2);
96
-
97
- const l6 = usize(laneIdx === 6);
98
- bs.offset -= (1 - l6) << 1;
99
- srcStart += l6 << 1;
100
- 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;
101
124
  }
102
-
103
- // Unicode escape (\uXXXX)
104
- const block = load<u64>(srcIdx, 4); // XXXX
105
- const escaped = hex4_to_u16_swar(block);
106
- store<u16>(dstIdx, escaped);
107
- // store<u64>(dstIdx, load<u32>(srcIdx, 12), 2);
108
- srcStart += 4 + laneIdx;
109
- bs.offset -= 6 - laneIdx;
110
- } while (mask !== 0);
111
-
112
- bs.offset += 8;
113
- srcStart += 8;
125
+ handled = true;
126
+ break;
127
+ } while (mask != 0);
128
+ if (!handled) {
129
+ bs.offset += 8;
130
+ srcStart += 8;
131
+ }
114
132
  }
115
133
 
116
134
  while (srcStart < srcEnd) {
117
- const block = load<u16>(srcStart);
118
- store<u16>(bs.offset, block);
119
- srcStart += 2;
120
-
121
- // Early exit
122
- if (block !== 0x5c) {
135
+ const char = load<u16>(srcStart);
136
+ if (char != BACK_SLASH) {
137
+ store<u16>(bs.offset, char);
123
138
  bs.offset += 2;
139
+ srcStart += 2;
124
140
  continue;
125
141
  }
126
-
127
- const code = load<u16>(srcStart);
142
+ const code = load<u16>(srcStart, 2);
128
143
  if (code !== 0x75) {
129
- // Short escapes (\n \t \" \\)
130
- const block = load<u16>(srcStart);
131
- const escape = load<u16>(DESERIALIZE_ESCAPE_TABLE + block);
132
- store<u16>(bs.offset, escape);
133
- srcStart += 2;
144
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
145
+ bs.offset += 2;
146
+ srcStart += 4;
134
147
  } else {
135
- // Unicode escape (\uXXXX)
136
- const block = load<u64>(srcStart, 2); // XXXX
137
- const escaped = hex4_to_u16_swar(block);
138
- store<u16>(bs.offset, escaped);
139
- srcStart += 10;
148
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
149
+ bs.offset += 2;
150
+ srcStart += 12;
140
151
  }
141
-
142
- bs.offset += 2;
143
152
  }
144
153
  return bs.sliceOut<string>(outStart);
145
154
  }
@@ -190,7 +199,9 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
190
199
  // Detect false positive (code unit where low byte is 0x5C)
191
200
  if ((header & 0xffff) !== 0x5c) continue;
192
201
 
193
- return inline.always(deserializeEscapedString_SWAR(payloadStart, srcIdx, srcEnd));
202
+ return inline.always(
203
+ deserializeEscapedString_SWAR(payloadStart, srcIdx, srcEnd),
204
+ );
194
205
  } while (mask !== 0);
195
206
 
196
207
  srcStart += 8;
@@ -198,7 +209,9 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
198
209
 
199
210
  while (srcStart < srcEnd) {
200
211
  if (load<u16>(srcStart) == BACK_SLASH) {
201
- return inline.always(deserializeEscapedString_SWAR(payloadStart, srcStart, srcEnd));
212
+ return inline.always(
213
+ deserializeEscapedString_SWAR(payloadStart, srcStart, srcEnd),
214
+ );
202
215
  }
203
216
  srcStart += 2;
204
217
  }
@@ -208,7 +221,11 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
208
221
 
209
222
  // Writes into the destination field, reusing or resizing the backing string.
210
223
  // @ts-expect-error: @inline is a valid decorator
211
- @inline function writeStringToField(dstFieldPtr: usize, srcStart: usize, byteLength: u32): void {
224
+ @inline function writeStringToField(
225
+ dstFieldPtr: usize,
226
+ srcStart: usize,
227
+ byteLength: u32,
228
+ ): void {
212
229
  if (byteLength == 0) {
213
230
  store<usize>(dstFieldPtr, changetype<usize>(""));
214
231
  return;
@@ -230,184 +247,113 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
230
247
  memory.copy(stringPtr, srcStart, byteLength);
231
248
  }
232
249
 
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.
233
261
  // @ts-expect-error: @inline is a valid decorator
234
- @inline function deserializeEscapedStringContinuation_SWAR(lastPtr: usize, srcStart: usize, srcEnd: usize, dstFieldPtr: usize, outStart: usize): usize {
235
- const srcEnd8 = srcEnd - 8;
236
-
237
- while (srcStart <= srcEnd8) {
238
- const blockStart = srcStart;
239
- let mask = inline.always(backslash_or_quote_mask(load<u64>(srcStart)));
240
- if (mask === 0) {
241
- srcStart += 8;
242
- continue;
243
- }
244
-
245
- do {
246
- const laneIdx = usize(ctz(mask) >> 3);
247
- mask &= mask - 1;
248
- const srcIdx = srcStart + laneIdx;
249
- const char = load<u16>(srcIdx);
250
- if (char == QUOTE) {
251
- const runLen = <u32>(srcIdx - lastPtr);
252
- if (runLen != 0) {
253
- memory.copy(bs.offset, lastPtr, runLen);
254
- bs.offset += runLen;
255
- }
256
- bs.toField(outStart, dstFieldPtr);
257
- return srcIdx + 2;
258
- }
259
- if (char != BACK_SLASH) continue;
260
-
261
- const runLen = <u32>(srcIdx - lastPtr);
262
- if (runLen != 0) {
263
- memory.copy(bs.offset, lastPtr, runLen);
264
- bs.offset += runLen;
265
- }
266
-
267
- const chunk = load<u32>(srcIdx);
268
- const code = <u16>(chunk >> 16);
269
- if (code !== 0x75) {
270
- store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
271
- bs.offset += 2;
272
- lastPtr = srcIdx + 4;
273
- } else {
274
- store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
275
- bs.offset += 2;
276
- lastPtr = srcIdx + 12;
277
- }
278
- srcStart = lastPtr;
279
- break;
280
- } while (mask !== 0);
281
-
282
- if (srcStart == blockStart) srcStart += 8;
283
- }
284
-
285
- while (srcStart < srcEnd) {
286
- const char = load<u16>(srcStart);
287
- if (char == QUOTE) {
288
- const runLen = <u32>(srcStart - lastPtr);
289
- if (runLen != 0) {
290
- memory.copy(bs.offset, lastPtr, runLen);
291
- bs.offset += runLen;
292
- }
293
- bs.toField(outStart, dstFieldPtr);
294
- return srcStart + 2;
295
- }
296
- if (char != BACK_SLASH) {
297
- srcStart += 2;
298
- continue;
299
- }
300
-
301
- const runLen = <u32>(srcStart - lastPtr);
302
- if (runLen != 0) {
303
- memory.copy(bs.offset, lastPtr, runLen);
304
- bs.offset += runLen;
305
- }
306
-
307
- const code = load<u16>(srcStart, 2);
308
- if (code !== 0x75) {
309
- store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
310
- bs.offset += 2;
311
- srcStart += 4;
312
- } else {
313
- store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
314
- bs.offset += 2;
315
- srcStart += 12;
316
- }
317
- lastPtr = srcStart;
318
- }
319
-
320
- bs.offset = bs.buffer + outStart;
321
- abort("Unterminated string literal");
322
- return srcStart;
323
- }
324
-
325
- // Scans a quoted string value, writes into the destination field, and returns next unread src pointer.
326
- // @ts-expect-error: @inline is a valid decorator
327
- @inline function deserializeEscapedStringScan_SWAR_SplitTuned(payloadStart: usize, escapeStart: usize, srcEnd: usize, dstFieldPtr: usize): usize {
262
+ @inline function deserializeEscapedStringField_SWAR(
263
+ payloadStart: usize,
264
+ escapeStart: usize,
265
+ srcEnd: usize,
266
+ dstFieldPtr: usize,
267
+ ): usize {
328
268
  const prefixLen = <u32>(escapeStart - payloadStart);
329
- const srcEnd8 = srcEnd - 8;
330
269
  bs.offset = bs.buffer;
331
- bs.ensureSize(<u32>(srcEnd - payloadStart));
270
+ bs.ensureSize(<u32>(srcEnd - payloadStart) + 8); // +8 slack for u64 overcopy
332
271
  if (prefixLen != 0) {
333
272
  memory.copy(bs.buffer, payloadStart, prefixLen);
334
273
  bs.offset += prefixLen;
335
274
  }
336
275
 
337
- let lastPtr = escapeStart;
338
276
  let srcStart = escapeStart;
277
+ const srcEnd8 = srcEnd >= 8 ? srcEnd - 8 : 0;
339
278
 
340
279
  while (srcStart <= srcEnd8) {
341
- const blockStart = srcStart;
342
- let mask = inline.always(backslash_or_quote_mask(load<u64>(srcStart)));
343
- if (mask === 0) {
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;
344
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
+ }
345
302
  continue;
346
303
  }
347
304
 
305
+ // Escape/quote block (mask may carry high-byte false positives).
306
+ store<u64>(bs.offset, block);
307
+ let handled = false;
348
308
  do {
349
309
  const laneIdx = usize(ctz(mask) >> 3);
350
310
  mask &= mask - 1;
351
311
  const srcIdx = srcStart + laneIdx;
352
312
  const char = load<u16>(srcIdx);
313
+ if (char != QUOTE && char != BACK_SLASH) continue; // false positive
314
+ bs.offset += laneIdx;
353
315
  if (char == QUOTE) {
354
- const runLen = <u32>(srcIdx - lastPtr);
355
- if (runLen != 0) {
356
- memory.copy(bs.offset, lastPtr, runLen);
357
- bs.offset += runLen;
358
- }
359
- writeStringToField(dstFieldPtr, bs.buffer, <u32>(bs.offset - bs.buffer));
316
+ writeStringToField(
317
+ dstFieldPtr,
318
+ bs.buffer,
319
+ <u32>(bs.offset - bs.buffer),
320
+ );
360
321
  bs.offset = bs.buffer;
361
322
  return srcIdx + 2;
362
323
  }
363
- if (char != BACK_SLASH) continue;
364
-
365
- const runLen = <u32>(srcIdx - lastPtr);
366
- if (runLen != 0) {
367
- memory.copy(bs.offset, lastPtr, runLen);
368
- bs.offset += runLen;
369
- }
370
-
371
- const chunk = load<u32>(srcIdx);
372
- const code = <u16>(chunk >> 16);
324
+ const code = load<u16>(srcIdx, 2);
373
325
  if (code !== 0x75) {
374
326
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
375
327
  bs.offset += 2;
376
- lastPtr = srcIdx + 4;
328
+ srcStart = srcIdx + 4;
377
329
  } else {
378
330
  store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
379
331
  bs.offset += 2;
380
- lastPtr = srcIdx + 12;
332
+ srcStart = srcIdx + 12;
381
333
  }
382
- srcStart = lastPtr;
334
+ handled = true;
383
335
  break;
384
- } while (mask !== 0);
385
- if (srcStart == blockStart) srcStart += 8;
336
+ } while (mask != 0);
337
+ if (!handled) {
338
+ bs.offset += 8;
339
+ srcStart += 8;
340
+ }
386
341
  }
387
342
 
343
+ // scalar tail (< 8 bytes remaining)
388
344
  while (srcStart < srcEnd) {
389
345
  const char = load<u16>(srcStart);
390
346
  if (char == QUOTE) {
391
- const runLen = <u32>(srcStart - lastPtr);
392
- if (runLen != 0) {
393
- memory.copy(bs.offset, lastPtr, runLen);
394
- bs.offset += runLen;
395
- }
396
347
  writeStringToField(dstFieldPtr, bs.buffer, <u32>(bs.offset - bs.buffer));
397
348
  bs.offset = bs.buffer;
398
349
  return srcStart + 2;
399
350
  }
400
351
  if (char != BACK_SLASH) {
352
+ store<u16>(bs.offset, char);
353
+ bs.offset += 2;
401
354
  srcStart += 2;
402
355
  continue;
403
356
  }
404
-
405
- const runLen = <u32>(srcStart - lastPtr);
406
- if (runLen != 0) {
407
- memory.copy(bs.offset, lastPtr, runLen);
408
- bs.offset += runLen;
409
- }
410
-
411
357
  const code = load<u16>(srcStart, 2);
412
358
  if (code !== 0x75) {
413
359
  store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
@@ -418,7 +364,6 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
418
364
  bs.offset += 2;
419
365
  srcStart += 12;
420
366
  }
421
- lastPtr = srcStart;
422
367
  }
423
368
 
424
369
  bs.offset = bs.buffer;
@@ -427,7 +372,12 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
427
372
  }
428
373
 
429
374
  // @ts-expect-error: @inline is a valid decorator
430
- @inline function deserializeEscapedStringContinuation_SWAR_MergedTuned(lastPtr: usize, srcStart: usize, srcEnd: usize, dstFieldPtr: usize): usize {
375
+ @inline function deserializeEscapedStringContinuation_SWAR_MergedTuned(
376
+ lastPtr: usize,
377
+ srcStart: usize,
378
+ srcEnd: usize,
379
+ dstFieldPtr: usize,
380
+ ): usize {
431
381
  const srcEnd8 = srcEnd - 8;
432
382
 
433
383
  while (srcStart <= srcEnd8) {
@@ -449,7 +399,11 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
449
399
  memory.copy(bs.offset, lastPtr, runLen);
450
400
  bs.offset += runLen;
451
401
  }
452
- writeStringToField(dstFieldPtr, bs.buffer, <u32>(bs.offset - bs.buffer));
402
+ writeStringToField(
403
+ dstFieldPtr,
404
+ bs.buffer,
405
+ <u32>(bs.offset - bs.buffer),
406
+ );
453
407
  bs.offset = bs.buffer;
454
408
  return srcIdx + 2;
455
409
  }
@@ -518,99 +472,33 @@ export function deserializeString_SWAR(srcStart: usize, srcEnd: usize): string {
518
472
  return srcStart;
519
473
  }
520
474
 
521
- function deserializeStringField_SWAR_MergedTuned(srcStart: usize, srcEnd: usize, dstFieldPtr: usize): usize {
522
- if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE) abort("Expected leading quote");
475
+ export function deserializeStringField_SWAR<T extends string | null>(
476
+ srcStart: usize,
477
+ srcEnd: usize,
478
+ dstObj: usize,
479
+ dstOffset: usize = 0,
480
+ ): usize {
481
+ const dstFieldPtr = dstObj + dstOffset;
482
+ if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
483
+ abort("Expected leading quote");
523
484
 
524
485
  const payloadStart = srcStart + 2;
525
- const srcEnd8 = srcEnd - 8;
526
486
  srcStart = payloadStart;
527
487
 
528
- while (srcStart <= srcEnd8) {
529
- let mask = inline.always(backslash_or_quote_mask(load<u64>(srcStart)));
530
- if (mask === 0) {
531
- srcStart += 8;
532
- continue;
533
- }
534
-
535
- do {
536
- const laneIdx = usize(ctz(mask) >> 3);
537
- mask &= ~(0xffff << (laneIdx << 3));
538
- const srcIdx = srcStart + laneIdx;
539
- const char = load<u16>(srcIdx);
540
-
541
- if (char == QUOTE) {
542
- writeStringToField(dstFieldPtr, payloadStart, <u32>(srcIdx - payloadStart));
543
- return srcIdx + 2;
544
- }
545
- if (char != BACK_SLASH) continue;
546
-
547
- bs.offset = bs.buffer;
548
- bs.ensureSize(<u32>(srcEnd - payloadStart));
549
- const prefixLen = <u32>(srcIdx - payloadStart);
550
- if (prefixLen != 0) {
551
- memory.copy(bs.buffer, payloadStart, prefixLen);
552
- bs.offset += prefixLen;
553
- }
554
-
555
- const chunk = load<u32>(srcIdx);
556
- const code = <u16>(chunk >> 16);
557
- let lastPtr: usize;
558
- if (code !== 0x75) {
559
- store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
560
- bs.offset += 2;
561
- lastPtr = srcIdx + 4;
562
- } else {
563
- store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
564
- bs.offset += 2;
565
- lastPtr = srcIdx + 12;
566
- }
567
- return inline.always(deserializeEscapedStringContinuation_SWAR_MergedTuned(lastPtr, lastPtr, srcEnd, dstFieldPtr));
568
- } while (mask !== 0);
569
-
570
- srcStart += 8;
571
- }
572
-
573
- while (srcStart < srcEnd) {
574
- const char = load<u16>(srcStart);
575
- if (char == QUOTE) {
576
- writeStringToField(dstFieldPtr, payloadStart, <u32>(srcStart - payloadStart));
577
- return srcStart + 2;
578
- }
579
- if (char == BACK_SLASH) {
580
- bs.offset = bs.buffer;
581
- bs.ensureSize(<u32>(srcEnd - payloadStart));
582
- const prefixLen = <u32>(srcStart - payloadStart);
583
- if (prefixLen != 0) {
584
- memory.copy(bs.buffer, payloadStart, prefixLen);
585
- bs.offset += prefixLen;
586
- }
587
-
588
- const code = load<u16>(srcStart, 2);
589
- let lastPtr: usize;
590
- if (code !== 0x75) {
591
- store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
592
- bs.offset += 2;
593
- lastPtr = srcStart + 4;
594
- } else {
595
- store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
596
- bs.offset += 2;
597
- lastPtr = srcStart + 12;
598
- }
599
- return inline.always(deserializeEscapedStringContinuation_SWAR_MergedTuned(lastPtr, lastPtr, srcEnd, dstFieldPtr));
488
+ // Wide pre-scan: skip 16 bytes per iter while both halves are clean. The
489
+ // common case (plain ASCII payloads, no escape) hits this loop exclusively
490
+ // and is bound by load+SWAR throughput, not branch frequency.
491
+ if (srcEnd >= 16) {
492
+ const srcEnd16 = srcEnd - 16;
493
+ while (srcStart <= srcEnd16) {
494
+ const m0 = inline.always(backslash_or_quote_mask(load<u64>(srcStart)));
495
+ const m1 = inline.always(backslash_or_quote_mask(load<u64>(srcStart, 8)));
496
+ if ((m0 | m1) != 0) break;
497
+ srcStart += 16;
600
498
  }
601
- srcStart += 2;
602
499
  }
603
500
 
604
- return srcStart;
605
- }
606
-
607
- export function deserializeStringField_SWAR<T extends string | null>(srcStart: usize, srcEnd: usize, dstFieldPtr: usize): usize {
608
- if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE) abort("Expected leading quote");
609
-
610
- const payloadStart = srcStart + 2;
611
501
  const srcEnd8 = srcEnd - 8;
612
- srcStart = payloadStart;
613
-
614
502
  while (srcStart <= srcEnd8) {
615
503
  let mask = inline.always(backslash_or_quote_mask(load<u64>(srcStart)));
616
504
  if (mask === 0) {
@@ -624,11 +512,22 @@ export function deserializeStringField_SWAR<T extends string | null>(srcStart: u
624
512
  const srcIdx = srcStart + laneIdx;
625
513
  const char = load<u16>(srcIdx);
626
514
  if (char == QUOTE) {
627
- writeStringToField(dstFieldPtr, payloadStart, <u32>(srcIdx - payloadStart));
515
+ writeStringToField(
516
+ dstFieldPtr,
517
+ payloadStart,
518
+ <u32>(srcIdx - payloadStart),
519
+ );
628
520
  return srcIdx + 2;
629
521
  }
630
522
  if (char != BACK_SLASH) continue;
631
- return inline.always(deserializeEscapedStringScan_SWAR_SplitTuned(payloadStart, srcIdx, srcEnd, dstFieldPtr));
523
+ return inline.always(
524
+ deserializeEscapedStringField_SWAR(
525
+ payloadStart,
526
+ srcIdx,
527
+ srcEnd,
528
+ dstFieldPtr,
529
+ ),
530
+ );
632
531
  } while (mask !== 0);
633
532
 
634
533
  srcStart += 8;
@@ -637,11 +536,22 @@ export function deserializeStringField_SWAR<T extends string | null>(srcStart: u
637
536
  while (srcStart < srcEnd) {
638
537
  const char = load<u16>(srcStart);
639
538
  if (char == QUOTE) {
640
- writeStringToField(dstFieldPtr, payloadStart, <u32>(srcStart - payloadStart));
539
+ writeStringToField(
540
+ dstFieldPtr,
541
+ payloadStart,
542
+ <u32>(srcStart - payloadStart),
543
+ );
641
544
  return srcStart + 2;
642
545
  }
643
546
  if (char == BACK_SLASH) {
644
- return inline.always(deserializeEscapedStringScan_SWAR_SplitTuned(payloadStart, srcStart, srcEnd, dstFieldPtr));
547
+ return inline.always(
548
+ deserializeEscapedStringField_SWAR(
549
+ payloadStart,
550
+ srcStart,
551
+ srcEnd,
552
+ dstFieldPtr,
553
+ ),
554
+ );
645
555
  }
646
556
  srcStart += 2;
647
557
  }
@@ -661,7 +571,10 @@ export function deserializeStringField_SWAR<T extends string | null>(srcStart: u
661
571
  @inline function backslash_or_quote_mask(block: u64): u64 {
662
572
  const b = block ^ 0x005c_005c_005c_005c;
663
573
  const q = block ^ 0x0022_0022_0022_0022;
664
- return (((q - 0x0001_0001_0001_0001) & ~q) | ((b - 0x0001_0001_0001_0001) & ~b)) & 0x0080_0080_0080_0080;
574
+ return (
575
+ (((q - 0x0001_0001_0001_0001) & ~q) | ((b - 0x0001_0001_0001_0001) & ~b)) &
576
+ 0x0080_0080_0080_0080
577
+ );
665
578
  }
666
579
  /**
667
580
  * Computes a per-lane mask identifying UTF-16 code units whose **low byte**
@@ -677,8 +590,13 @@ export function deserializeStringField_SWAR<T extends string | null>(srcStart: u
677
590
  // @ts-expect-error: @inline is a valid decorator
678
591
  @inline function backslash_mask(block: u64): u64 {
679
592
  const b = block ^ 0x005c_005c_005c_005c;
680
- const backslash_mask = (b - 0x0001_0001_0001_0001) & ~b & 0x0080_0080_0080_0080;
681
- const high_byte_mask = ~(((block - 0x0100_0100_0100_0100) & ~block & 0x8000_8000_8000_8000) ^ 0x8000_8000_8000_8000) >> 8;
593
+ const backslash_mask =
594
+ (b - 0x0001_0001_0001_0001) & ~b & 0x0080_0080_0080_0080;
595
+ const high_byte_mask =
596
+ ~(
597
+ ((block - 0x0100_0100_0100_0100) & ~block & 0x8000_8000_8000_8000) ^
598
+ 0x8000_8000_8000_8000
599
+ ) >> 8;
682
600
  return backslash_mask & high_byte_mask;
683
601
  }
684
602
 
@@ -694,6 +612,7 @@ export function deserializeStringField_SWAR<T extends string | null>(srcStart: u
694
612
  // @ts-expect-error: @inline is a valid decorator
695
613
  @inline function backslash_mask_unsafe(block: u64): u64 {
696
614
  const b = block ^ 0x005c_005c_005c_005c;
697
- const backslash_mask = (b - 0x0001_0001_0001_0001) & ~b & 0x0080_0080_0080_0080;
615
+ const backslash_mask =
616
+ (b - 0x0001_0001_0001_0001) & ~b & 0x0080_0080_0080_0080;
698
617
  return backslash_mask;
699
618
  }