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
@@ -3,9 +3,8 @@ import { OBJECT, TOTAL_OVERHEAD } from "rt/common";
3
3
  import { __heap_base } from "memory";
4
4
  import { QUOTE } from "../../custom/chars";
5
5
  import { BACK_SLASH } from "../../custom/chars";
6
- import { DESERIALIZE_ESCAPE_TABLE, ESCAPE_HEX_TABLE } from "../../globals/tables";
6
+ import { DESERIALIZE_ESCAPE_TABLE } from "../../globals/tables";
7
7
  import { hex4_to_u16_swar } from "../../util/swar";
8
- import { deserializeStringField_SWAR } from "../swar/string";
9
8
 
10
9
  // @ts-expect-error: @lazy is a valid decorator
11
10
  @lazy const SPLAT_5C = i16x8.splat(0x5c); // \
@@ -65,7 +64,10 @@ import { deserializeStringField_SWAR } from "../swar/string";
65
64
  * @returns number of bytes written
66
65
  */
67
66
  // @ts-expect-error: @inline is a valid decorator
68
- @inline function copyStringFromSource_SIMD(srcStart: usize, byteLength: usize): string {
67
+ @inline function copyStringFromSource_SIMD(
68
+ srcStart: usize,
69
+ byteLength: usize,
70
+ ): string {
69
71
  if (byteLength == 0) return changetype<string>("");
70
72
  // @ts-expect-error: __new is a runtime builtin
71
73
  const out = __new(byteLength, idof<string>());
@@ -74,7 +76,11 @@ import { deserializeStringField_SWAR } from "../swar/string";
74
76
  }
75
77
 
76
78
  // @ts-expect-error: @inline is a valid decorator
77
- @inline function writeStringToField_SIMD(dstFieldPtr: usize, srcStart: usize, byteLength: u32): void {
79
+ @inline function writeStringToField_SIMD(
80
+ dstFieldPtr: usize,
81
+ srcStart: usize,
82
+ byteLength: u32,
83
+ ): void {
78
84
  if (byteLength == 0) {
79
85
  store<usize>(dstFieldPtr, changetype<usize>(""));
80
86
  return;
@@ -96,106 +102,95 @@ import { deserializeStringField_SWAR } from "../swar/string";
96
102
  memory.copy(stringPtr, srcStart, byteLength);
97
103
  }
98
104
 
99
- // @ts-expect-error: @inline is a valid decorator
100
-
101
- // todo: optimize and stuff. it works, its not pretty. ideally, i'd like this to be (nearly) branchless
102
- // @ts-expect-error: @inline is a valid decorator
103
- @inline function deserializeEscapedString_SIMD(payloadStart: usize, escapeStart: usize, srcEnd: usize): string {
105
+ // Vectorized escaped scanner for the standalone (whole-value) path. Quotes are
106
+ // already stripped, so `srcEnd` is the payload end and only `\` escapes need
107
+ // handling (no closing-quote search). Same HYBRID strategy as the field path
108
+ // (see deserializeEscapedStringField_SIMD): escape blocks use a free
109
+ // whole-block v128 store for the plain prefix; clean runs stream the first
110
+ // block then bulk-memcpy the remainder. Output is sliced out of `bs` scratch.
111
+ //
112
+ // Deliberately NOT @inline: cold escape path. Inlining the nested-loop v128
113
+ // body at every call site explodes `--converge` compile time on large schemas
114
+ // for no runtime gain (one call per escaped string).
115
+ function deserializeEscapedString_SIMD(
116
+ payloadStart: usize,
117
+ escapeStart: usize,
118
+ srcEnd: usize,
119
+ ): string {
104
120
  const prefixLen = <u32>(escapeStart - payloadStart);
105
- let srcStart = escapeStart;
106
- const srcEnd16 = srcEnd - 16;
107
121
  const outStart = bs.offset - bs.buffer;
108
- bs.ensureSize(u32(srcEnd - srcStart));
122
+ bs.ensureSize(<u32>(srcEnd - payloadStart) + 16); // +16 slack for overcopy
109
123
  if (prefixLen != 0) {
110
124
  memory.copy(bs.offset, payloadStart, prefixLen);
111
125
  bs.offset += prefixLen;
112
126
  }
113
127
 
114
- while (srcStart < srcEnd16) {
115
- const block = load<v128>(srcStart);
116
- store<v128>(bs.offset, block);
117
-
118
- const eq5C = i16x8.eq(block, SPLAT_5C);
119
- let mask = i16x8.bitmask(eq5C);
128
+ let srcStart = escapeStart;
129
+ const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
120
130
 
131
+ while (srcStart <= srcEnd16) {
132
+ const block = load<v128>(srcStart);
133
+ const mask = i16x8.bitmask(i16x8.eq(block, SPLAT_5C)); // backslash only
121
134
  if (mask == 0) {
122
- srcStart += 16;
135
+ // Stream the first clean block cheaply.
136
+ store<v128>(bs.offset, block);
123
137
  bs.offset += 16;
138
+ srcStart += 16;
139
+ // If the clean run continues, bulk-copy the remainder in one shot.
140
+ if (srcStart <= srcEnd16) {
141
+ const b2 = load<v128>(srcStart);
142
+ if (i16x8.bitmask(i16x8.eq(b2, SPLAT_5C)) == 0) {
143
+ const runStart = srcStart;
144
+ srcStart += 16;
145
+ while (srcStart <= srcEnd16) {
146
+ if (i16x8.bitmask(i16x8.eq(load<v128>(srcStart), SPLAT_5C)) != 0)
147
+ break;
148
+ srcStart += 16;
149
+ }
150
+ const runLen = <u32>(srcStart - runStart);
151
+ memory.copy(bs.offset, runStart, runLen);
152
+ bs.offset += runLen;
153
+ }
154
+ }
124
155
  continue;
125
156
  }
126
157
 
127
- let srcChg: usize = 0;
128
- let lastLane: usize = 0;
129
- do {
130
- const laneIdx = usize(ctz(mask) << 1); // 0 2 4 6 8 10 12 14
131
- mask &= mask - 1;
132
- const srcIdx = srcStart + laneIdx;
133
- const code = load<u16>(srcIdx, 2);
134
-
135
- bs.offset += laneIdx - lastLane;
136
-
137
- // Hot path (negative bias)
138
- if (code !== 0x75) {
139
- // Short escapes (\n \t \" \\)
140
- const escaped = load<u16>(DESERIALIZE_ESCAPE_TABLE + code);
141
- mask &= mask - i32(escaped === 0x5c);
142
- store<u16>(bs.offset, escaped);
143
- store<v128>(bs.offset, load<v128>(srcIdx, 4), 2);
144
-
145
- const l6 = usize(laneIdx === 14);
146
- // bs.offset -= (1 - l6) << 1;
147
- bs.offset += 2;
148
- srcStart += l6 << 1;
149
- lastLane = laneIdx + 4;
150
- continue;
151
- }
152
-
153
- // Unicode escape (\uXXXX)
154
- const block = load<u64>(srcIdx, 4); // XXXX
155
- const escaped = hex4_to_u16_swar(block);
156
-
157
- store<u16>(bs.offset, escaped);
158
- store<u64>(bs.offset, load<u64>(srcIdx, 12), 2);
159
-
158
+ // Escape block: one whole-block store covers the plain prefix.
159
+ store<v128>(bs.offset, block);
160
+ const laneIdx = usize(ctz(mask) << 1);
161
+ bs.offset += laneIdx;
162
+ const srcIdx = srcStart + laneIdx;
163
+ const code = load<u16>(srcIdx, 2);
164
+ if (code !== 0x75) {
165
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
160
166
  bs.offset += 2;
161
- if (laneIdx >= 6) {
162
- srcStart += laneIdx - 4;
163
- }
164
- lastLane = laneIdx + 12;
165
- } while (mask !== 0);
166
-
167
- if (lastLane < 16) {
168
- bs.offset += 16 - lastLane;
167
+ srcStart = srcIdx + 4;
168
+ } else {
169
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
170
+ bs.offset += 2;
171
+ srcStart = srcIdx + 12;
169
172
  }
170
-
171
- srcStart += 16 + srcChg;
172
173
  }
173
174
 
175
+ // scalar tail (< 16 bytes remaining)
174
176
  while (srcStart < srcEnd) {
175
- const block = load<u16>(srcStart);
176
- store<u16>(bs.offset, block);
177
- srcStart += 2;
178
-
179
- // Early exit
180
- if (block !== 0x5c) {
177
+ const char = load<u16>(srcStart);
178
+ if (char != BACK_SLASH) {
179
+ store<u16>(bs.offset, char);
181
180
  bs.offset += 2;
181
+ srcStart += 2;
182
182
  continue;
183
183
  }
184
-
185
- const code = load<u16>(srcStart);
184
+ const code = load<u16>(srcStart, 2);
186
185
  if (code !== 0x75) {
187
- // Short escapes (\n \t \" \\)
188
- const escape = load<u16>(DESERIALIZE_ESCAPE_TABLE + code);
189
- store<u16>(bs.offset, escape);
190
- srcStart += 2;
186
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
187
+ bs.offset += 2;
188
+ srcStart += 4;
191
189
  } else {
192
- // Unicode escape (\uXXXX)
193
- const block = load<u64>(srcStart, 2); // XXXX
194
- const escaped = hex4_to_u16_swar(block);
195
- store<u16>(bs.offset, escaped);
196
- srcStart += 10;
190
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
191
+ bs.offset += 2;
192
+ srcStart += 12;
197
193
  }
198
- bs.offset += 2;
199
194
  }
200
195
 
201
196
  return bs.sliceOut<string>(outStart);
@@ -238,12 +233,16 @@ export function deserializeString_SIMD(srcStart: usize, srcEnd: usize): string {
238
233
  }
239
234
 
240
235
  const laneIdx = usize(ctz(mask) << 1);
241
- return inline.always(deserializeEscapedString_SIMD(payloadStart, srcStart + laneIdx, srcEnd));
236
+ return inline.always(
237
+ deserializeEscapedString_SIMD(payloadStart, srcStart + laneIdx, srcEnd),
238
+ );
242
239
  }
243
240
 
244
241
  while (srcStart < srcEnd) {
245
242
  if (load<u16>(srcStart) == BACK_SLASH) {
246
- return inline.always(deserializeEscapedString_SIMD(payloadStart, srcStart, srcEnd));
243
+ return inline.always(
244
+ deserializeEscapedString_SIMD(payloadStart, srcStart, srcEnd),
245
+ );
247
246
  }
248
247
  srcStart += 2;
249
248
  }
@@ -251,52 +250,212 @@ export function deserializeString_SIMD(srcStart: usize, srcEnd: usize): string {
251
250
  return copyStringFromSource_SIMD(payloadStart, srcEnd - payloadStart);
252
251
  }
253
252
 
254
- // @ts-expect-error: @inline is a valid decorator
255
- @inline export function deserializeStringField_SIMD<T extends string | null>(srcStart: usize, srcEnd: usize, dstObj: usize, dstOffset: usize = 0): usize {
256
- const dstFieldPtr = dstObj + dstOffset;
257
- if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE) abort("Expected leading quote");
253
+ // Vectorized escaped scanner for the field path. `escapeStart` points at the
254
+ // first `\` located by the caller's v128 scan. Output is assembled in the
255
+ // reused `bs` scratch buffer, then written once via writeStringToField_SIMD.
256
+ //
257
+ // Strategy (validated against run-copy and pure-stream variants across escape
258
+ // densities — see __benches__/custom/simd-string-deser-variants-h2h):
259
+ // * Escape-bearing block: a single whole-block v128 store copies the plain
260
+ // prefix for free; then the escape is decoded scalar.
261
+ // * Clean block: stream the first one cheaply, but if the clean run keeps
262
+ // going, switch to one bulk memory.copy for the remainder — bandwidth-
263
+ // optimal on long sparse runs, avoiding a per-block-store cliff on large
264
+ // inputs. This dominates both alternatives: stream-cheap on dense escapes,
265
+ // memcpy-fast on long runs.
266
+ //
267
+ // Deliberately NOT @inline: cold escape path. As an @inline it was inlined into
268
+ // deserializeStringField_SIMD at every struct string-field site, exploding
269
+ // `--converge` compile time ~24x on large schemas (4s → 99s) for no runtime
270
+ // gain. The hot no-escape scan + writeStringToField stay inline in the caller.
271
+ function deserializeEscapedStringField_SIMD(
272
+ payloadStart: usize,
273
+ escapeStart: usize,
274
+ srcEnd: usize,
275
+ dstFieldPtr: usize,
276
+ ): usize {
277
+ const prefixLen = <u32>(escapeStart - payloadStart);
278
+ bs.offset = bs.buffer;
279
+ bs.ensureSize(<u32>(srcEnd - payloadStart) + 16); // +16 slack for overcopy
280
+ if (prefixLen != 0) {
281
+ memory.copy(bs.buffer, payloadStart, prefixLen);
282
+ bs.offset += prefixLen;
283
+ }
258
284
 
259
- const quotedStart = srcStart;
260
- const payloadStart = srcStart + 2;
285
+ let srcStart = escapeStart;
261
286
  const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
262
- srcStart = payloadStart;
263
287
 
264
288
  while (srcStart <= srcEnd16) {
265
289
  const block = load<v128>(srcStart);
266
- let mask = i16x8.bitmask(v128.or(i16x8.eq(block, SPLAT_5C), i16x8.eq(block, SPLAT_22)));
267
-
290
+ const mask = i16x8.bitmask(
291
+ v128.or(i16x8.eq(block, SPLAT_5C), i16x8.eq(block, SPLAT_22)),
292
+ );
268
293
  if (mask == 0) {
294
+ // Stream the first clean block cheaply.
295
+ store<v128>(bs.offset, block);
296
+ bs.offset += 16;
269
297
  srcStart += 16;
298
+ // If the clean run continues, bulk-copy the remainder in one shot.
299
+ if (srcStart <= srcEnd16) {
300
+ const b2 = load<v128>(srcStart);
301
+ if (
302
+ i16x8.bitmask(
303
+ v128.or(i16x8.eq(b2, SPLAT_5C), i16x8.eq(b2, SPLAT_22)),
304
+ ) == 0
305
+ ) {
306
+ const runStart = srcStart;
307
+ srcStart += 16;
308
+ while (srcStart <= srcEnd16) {
309
+ const b3 = load<v128>(srcStart);
310
+ if (
311
+ i16x8.bitmask(
312
+ v128.or(i16x8.eq(b3, SPLAT_5C), i16x8.eq(b3, SPLAT_22)),
313
+ ) != 0
314
+ )
315
+ break;
316
+ srcStart += 16;
317
+ }
318
+ const runLen = <u32>(srcStart - runStart);
319
+ memory.copy(bs.offset, runStart, runLen);
320
+ bs.offset += runLen;
321
+ }
322
+ }
270
323
  continue;
271
324
  }
272
325
 
273
- do {
274
- const laneIdx = usize(ctz(mask) << 1);
275
- mask &= mask - 1;
276
- const srcIdx = srcStart + laneIdx;
277
- const char = load<u16>(srcIdx);
326
+ // Escape/quote block: one whole-block store covers the plain prefix.
327
+ store<v128>(bs.offset, block);
328
+ const laneIdx = usize(ctz(mask) << 1);
329
+ bs.offset += laneIdx;
330
+ const srcIdx = srcStart + laneIdx;
331
+ const char = load<u16>(srcIdx);
332
+ if (char == QUOTE) {
333
+ writeStringToField_SIMD(
334
+ dstFieldPtr,
335
+ bs.buffer,
336
+ <u32>(bs.offset - bs.buffer),
337
+ );
338
+ bs.offset = bs.buffer;
339
+ return srcIdx + 2;
340
+ }
278
341
 
279
- if (char == QUOTE) {
280
- writeStringToField_SIMD(dstFieldPtr, payloadStart, <u32>(srcIdx - payloadStart));
281
- return srcIdx + 2;
282
- }
342
+ const code = load<u16>(srcIdx, 2);
343
+ if (code !== 0x75) {
344
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
345
+ bs.offset += 2;
346
+ srcStart = srcIdx + 4;
347
+ } else {
348
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcIdx, 4)));
349
+ bs.offset += 2;
350
+ srcStart = srcIdx + 12;
351
+ }
352
+ }
283
353
 
284
- if (char == BACK_SLASH) {
285
- return deserializeStringField_SWAR<T>(quotedStart, srcEnd, dstFieldPtr);
286
- }
287
- } while (mask != 0);
354
+ // scalar tail (< 16 bytes remaining): emit chars directly.
355
+ while (srcStart < srcEnd) {
356
+ const char = load<u16>(srcStart);
357
+ if (char == QUOTE) {
358
+ writeStringToField_SIMD(
359
+ dstFieldPtr,
360
+ bs.buffer,
361
+ <u32>(bs.offset - bs.buffer),
362
+ );
363
+ bs.offset = bs.buffer;
364
+ return srcStart + 2;
365
+ }
366
+ if (char != BACK_SLASH) {
367
+ store<u16>(bs.offset, char);
368
+ bs.offset += 2;
369
+ srcStart += 2;
370
+ continue;
371
+ }
372
+
373
+ const code = load<u16>(srcStart, 2);
374
+ if (code !== 0x75) {
375
+ store<u16>(bs.offset, load<u16>(DESERIALIZE_ESCAPE_TABLE + code));
376
+ bs.offset += 2;
377
+ srcStart += 4;
378
+ } else {
379
+ store<u16>(bs.offset, hex4_to_u16_swar(load<u64>(srcStart, 4)));
380
+ bs.offset += 2;
381
+ srcStart += 12;
382
+ }
383
+ }
384
+
385
+ bs.offset = bs.buffer;
386
+ abort("Unterminated string literal");
387
+ return srcStart;
388
+ }
389
+
390
+ // NOT @inline: as an @inline entry, binaryen inlined the (loop-bearing) escaped
391
+ // scanner into every per-field copy, exploding `large` SIMD compile ~24x
392
+ // (4s→99s, 221KB→555KB wasm). Kept as a single shared function — matches
393
+ // deserializeStringField_SWAR (also non-inline) and costs only one call/field.
394
+ export function deserializeStringField_SIMD<T extends string | null>(
395
+ srcStart: usize,
396
+ srcEnd: usize,
397
+ dstObj: usize,
398
+ dstOffset: usize = 0,
399
+ ): usize {
400
+ const dstFieldPtr = dstObj + dstOffset;
401
+ if (srcStart + 2 > srcEnd || load<u16>(srcStart) != QUOTE)
402
+ abort("Expected leading quote");
403
+
404
+ const payloadStart = srcStart + 2;
405
+ const srcEnd16 = srcEnd >= 16 ? srcEnd - 16 : 0;
406
+ srcStart = payloadStart;
407
+
408
+ while (srcStart <= srcEnd16) {
409
+ const block = load<v128>(srcStart);
410
+ const mask = i16x8.bitmask(
411
+ v128.or(i16x8.eq(block, SPLAT_5C), i16x8.eq(block, SPLAT_22)),
412
+ );
413
+ if (mask == 0) {
414
+ srcStart += 16;
415
+ continue;
416
+ }
288
417
 
289
- srcStart += 16;
418
+ const laneIdx = usize(ctz(mask) << 1);
419
+ const srcIdx = srcStart + laneIdx;
420
+ const char = load<u16>(srcIdx);
421
+ if (char == QUOTE) {
422
+ writeStringToField_SIMD(
423
+ dstFieldPtr,
424
+ payloadStart,
425
+ <u32>(srcIdx - payloadStart),
426
+ );
427
+ return srcIdx + 2;
428
+ }
429
+ // backslash → vectorized escaped scan (no more SWAR fallback)
430
+ return inline.always(
431
+ deserializeEscapedStringField_SIMD(
432
+ payloadStart,
433
+ srcIdx,
434
+ srcEnd,
435
+ dstFieldPtr,
436
+ ),
437
+ );
290
438
  }
291
439
 
292
440
  while (srcStart < srcEnd) {
293
441
  const char = load<u16>(srcStart);
294
442
  if (char == QUOTE) {
295
- writeStringToField_SIMD(dstFieldPtr, payloadStart, <u32>(srcStart - payloadStart));
443
+ writeStringToField_SIMD(
444
+ dstFieldPtr,
445
+ payloadStart,
446
+ <u32>(srcStart - payloadStart),
447
+ );
296
448
  return srcStart + 2;
297
449
  }
298
450
  if (char == BACK_SLASH) {
299
- return deserializeStringField_SWAR<T>(quotedStart, srcEnd, dstFieldPtr);
451
+ return inline.always(
452
+ deserializeEscapedStringField_SIMD(
453
+ payloadStart,
454
+ srcStart,
455
+ srcEnd,
456
+ dstFieldPtr,
457
+ ),
458
+ );
300
459
  }
301
460
  srcStart += 2;
302
461
  }
@@ -1,8 +1,16 @@
1
1
  import { JSON } from "../../..";
2
+ import { deserializeGenericArrayBody } from "./generic";
2
3
  import { ensureArrayField } from "./shared";
3
4
 
4
5
 
5
- @inline export function deserializeArbitraryArrayField(srcStart: usize, srcEnd: usize, fieldPtr: usize): usize {
6
- ensureArrayField<JSON.Value[]>(fieldPtr);
7
- throw new Error("Failed to parse JSON!");
6
+ @inline export function deserializeArbitraryArrayField(
7
+ srcStart: usize,
8
+ srcEnd: usize,
9
+ fieldPtr: usize,
10
+ ): usize {
11
+ return deserializeGenericArrayBody<JSON.Value[]>(
12
+ srcStart,
13
+ srcEnd,
14
+ ensureArrayField<JSON.Value[]>(fieldPtr),
15
+ );
8
16
  }
@@ -1,15 +1,20 @@
1
1
  import { JSON } from "../../..";
2
2
  import { BRACKET_LEFT, BRACKET_RIGHT, COMMA } from "../../../custom/chars";
3
- import { deserializeFloatArrayInto } from "./float";
4
- import { ensureArrayField, scanValueEnd } from "./shared";
3
+ import { deserializeFloatArrayBody } from "./float";
4
+ import { ensureArrayField, scanValueEnd, skipWhitespace } from "./shared";
5
5
 
6
6
 
7
- @inline export function deserializeArrayArrayInto<T extends unknown[][]>(srcStart: usize, srcEnd: usize, out: T): usize {
7
+ @inline export function deserializeArrayArrayBody<T extends unknown[][]>(
8
+ srcStart: usize,
9
+ srcEnd: usize,
10
+ out: T,
11
+ ): usize {
8
12
  let index = 0;
9
13
 
10
14
  do {
11
15
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
12
16
  srcStart += 2;
17
+ srcStart = skipWhitespace(srcStart, srcEnd);
13
18
  if (srcStart >= srcEnd) break;
14
19
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
15
20
  out.length = 0;
@@ -25,7 +30,11 @@ import { ensureArrayField, scanValueEnd } from "./shared";
25
30
  value = changetype<valueof<T>>(instantiate<valueof<T>>());
26
31
  out.push(value);
27
32
  }
28
- srcStart = deserializeFloatArrayInto<valueof<T>>(srcStart, srcEnd, value);
33
+ srcStart = deserializeFloatArrayBody<valueof<T>>(
34
+ srcStart,
35
+ srcEnd,
36
+ value,
37
+ );
29
38
  if (!srcStart || srcStart >= srcEnd) break;
30
39
  } else if (isArray<valueof<valueof<T>>>()) {
31
40
  let value: valueof<T>;
@@ -35,7 +44,11 @@ import { ensureArrayField, scanValueEnd } from "./shared";
35
44
  value = changetype<valueof<T>>(instantiate<valueof<T>>());
36
45
  out.push(value);
37
46
  }
38
- srcStart = deserializeArrayArrayInto<valueof<T>>(srcStart, srcEnd, value);
47
+ srcStart = deserializeArrayArrayBody<valueof<T>>(
48
+ srcStart,
49
+ srcEnd,
50
+ value,
51
+ );
39
52
  if (!srcStart || srcStart >= srcEnd) break;
40
53
  } else {
41
54
  const valueEnd = scanValueEnd(srcStart, srcEnd);
@@ -45,20 +58,30 @@ import { ensureArrayField, scanValueEnd } from "./shared";
45
58
  if (index < out.length) {
46
59
  valuePtr = changetype<usize>(unchecked(out[index]));
47
60
  }
48
- const value = JSON.__deserialize<valueof<T>>(srcStart, valueEnd, valuePtr);
61
+ const value = JSON.__deserialize<valueof<T>>(
62
+ srcStart,
63
+ valueEnd,
64
+ valuePtr,
65
+ );
49
66
  if (index < out.length) unchecked((out[index] = value));
50
67
  else out.push(value);
51
68
  srcStart = valueEnd;
52
69
  }
53
70
 
71
+ srcStart = skipWhitespace(srcStart, srcEnd);
54
72
  const code = load<u16>(srcStart);
55
73
  if (code == COMMA) {
56
74
  srcStart += 2;
75
+ srcStart = skipWhitespace(srcStart, srcEnd);
57
76
  index++;
58
77
  continue;
59
78
  }
60
79
  if (code == BRACKET_RIGHT) {
61
- out.length = index + 1;
80
+ // Skip the runtime `ensureCapacity` call when the length is already
81
+ // correct (the array is being reused with the same shape, e.g.
82
+ // canada's geometry rings across repeated parses).
83
+ const nextLen = index + 1;
84
+ if (out.length != nextLen) out.length = nextLen;
62
85
  return srcStart + 2;
63
86
  }
64
87
  break;
@@ -69,6 +92,14 @@ import { ensureArrayField, scanValueEnd } from "./shared";
69
92
  }
70
93
 
71
94
 
72
- @inline export function deserializeArrayArrayField<T extends unknown[][]>(srcStart: usize, srcEnd: usize, fieldPtr: usize): usize {
73
- return deserializeArrayArrayInto<T>(srcStart, srcEnd, ensureArrayField<T>(fieldPtr));
95
+ @inline export function deserializeArrayArrayField<T extends unknown[][]>(
96
+ srcStart: usize,
97
+ srcEnd: usize,
98
+ fieldPtr: usize,
99
+ ): usize {
100
+ return deserializeArrayArrayBody<T>(
101
+ srcStart,
102
+ srcEnd,
103
+ ensureArrayField<T>(fieldPtr),
104
+ );
74
105
  }
@@ -1,13 +1,25 @@
1
- import { BRACKET_LEFT, BRACKET_RIGHT, COMMA, FALSE_WORD_U64, TRUE_WORD_U64 } from "../../../custom/chars";
1
+ import {
2
+ BRACKET_LEFT,
3
+ BRACKET_RIGHT,
4
+ COMMA,
5
+ FALSE_WORD_U64,
6
+ TRUE_WORD_U64,
7
+ } from "../../../custom/chars";
8
+ import { isSpace } from "../../../util";
2
9
  import { ensureArrayElementSlot, ensureArrayField } from "./shared";
3
10
 
4
11
 
5
- @inline export function deserializeBooleanArrayInto<T extends boolean[]>(srcStart: usize, srcEnd: usize, out: T): usize {
12
+ @inline function deserializeBooleanArrayBody<T extends boolean[]>(
13
+ srcStart: usize,
14
+ srcEnd: usize,
15
+ out: T,
16
+ ): usize {
6
17
  let index = 0;
7
18
 
8
19
  do {
9
20
  if (srcStart >= srcEnd || load<u16>(srcStart) != BRACKET_LEFT) break;
10
21
  srcStart += 2;
22
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
11
23
  if (srcStart >= srcEnd) break;
12
24
  if (load<u16>(srcStart) == BRACKET_RIGHT) {
13
25
  out.length = 0;
@@ -27,15 +39,18 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
27
39
  break;
28
40
  }
29
41
 
42
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
30
43
  if (srcStart >= srcEnd) break;
31
44
  const code = load<u16>(srcStart);
32
45
  if (code == COMMA) {
33
46
  srcStart += 2;
47
+ while (srcStart < srcEnd && isSpace(load<u16>(srcStart))) srcStart += 2;
34
48
  index++;
35
49
  continue;
36
50
  }
37
51
  if (code == BRACKET_RIGHT) {
38
- out.length = index + 1;
52
+ const nextLen = index + 1;
53
+ if (out.length != nextLen) out.length = nextLen;
39
54
  return srcStart + 2;
40
55
  }
41
56
  break;
@@ -46,6 +61,14 @@ import { ensureArrayElementSlot, ensureArrayField } from "./shared";
46
61
  }
47
62
 
48
63
 
49
- @inline export function deserializeBooleanArrayField<T extends boolean[]>(srcStart: usize, srcEnd: usize, fieldPtr: usize): usize {
50
- return deserializeBooleanArrayInto<T>(srcStart, srcEnd, ensureArrayField<T>(fieldPtr));
64
+ @inline export function deserializeBooleanArrayField<T extends boolean[]>(
65
+ srcStart: usize,
66
+ srcEnd: usize,
67
+ fieldPtr: usize,
68
+ ): usize {
69
+ return deserializeBooleanArrayBody<T>(
70
+ srcStart,
71
+ srcEnd,
72
+ ensureArrayField<T>(fieldPtr),
73
+ );
51
74
  }
@@ -1,8 +1,16 @@
1
1
  import { JSON } from "../../..";
2
+ import { deserializeGenericArrayBody } from "./generic";
2
3
  import { ensureArrayField } from "./shared";
3
4
 
4
5
 
5
- @inline export function deserializeBoxArrayField<T extends JSON.Box<any>[]>(srcStart: usize, srcEnd: usize, fieldPtr: usize): usize {
6
- ensureArrayField<T>(fieldPtr);
7
- throw new Error("Failed to parse JSON!");
6
+ @inline export function deserializeBoxArrayField<T extends JSON.Box<any>[]>(
7
+ srcStart: usize,
8
+ srcEnd: usize,
9
+ fieldPtr: usize,
10
+ ): usize {
11
+ return deserializeGenericArrayBody<T>(
12
+ srcStart,
13
+ srcEnd,
14
+ ensureArrayField<T>(fieldPtr),
15
+ );
8
16
  }