jz 0.9.0 → 0.9.2
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/README.md +19 -7
- package/bench/bench.svg +18 -18
- package/dist/interop.js +1 -1
- package/dist/jz.js +2026 -1429
- package/interop.js +73 -6
- package/jzify/index.js +7 -1
- package/jzify/transform.js +21 -2
- package/jzify/webrt.js +145 -0
- package/layout.js +13 -3
- package/module/array.js +49 -42
- package/module/collection.js +222 -111
- package/module/console.js +4 -0
- package/module/core.js +86 -7
- package/module/crypto.js +124 -0
- package/module/index.js +3 -1
- package/module/math.js +5 -0
- package/module/navigator.js +26 -0
- package/module/schema.js +11 -3
- package/module/string.js +433 -28
- package/module/timer.js +42 -1
- package/module/typedarray.js +370 -60
- package/package.json +3 -3
- package/src/abi/array.js +24 -16
- package/src/abi/index.js +2 -2
- package/src/abi/object.js +17 -0
- package/src/ast.js +53 -0
- package/src/autoload.js +19 -3
- package/src/compile/analyze.js +190 -21
- package/src/compile/emit-assign.js +111 -7
- package/src/compile/emit.js +84 -20
- package/src/compile/index.js +37 -4
- package/src/compile/inplace-store.js +10 -9
- package/src/compile/narrow.js +71 -1
- package/src/compile/plan/index.js +5 -0
- package/src/compile/plan/literals.js +11 -2
- package/src/ctx.js +14 -0
- package/src/ir.js +26 -1
- package/src/optimize/index.js +1 -0
- package/src/optimize/vectorize.js +80 -6
- package/src/prepare/index.js +6 -0
- package/src/static.js +31 -0
- package/src/type.js +356 -47
package/module/collection.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Collection module — Set, Map, HASH (dynamic string-keyed objects).
|
|
3
3
|
*
|
|
4
|
-
* Set: type=8, open addressing hash table. Entries: [hash:
|
|
5
|
-
* Map: type=9, same but entries: [hash:
|
|
4
|
+
* Set: type=8, open addressing hash table. Entries: [hash|seq:8, key:8] (16B each).
|
|
5
|
+
* Map: type=9, same but entries: [hash|seq:8, key:8, val:8] (24B each).
|
|
6
6
|
* HASH: type=7, same layout as Map but uses content-based string hash + equality.
|
|
7
|
+
* Every table additionally carries an i32 HASH LANE (cap × 4 B) AFTER the entry
|
|
8
|
+
* region — the only thing probes walk (see probeStart) — so allocations are
|
|
9
|
+
* cap × (entrySize + 4) bytes; entry offsets and iteration are unchanged.
|
|
7
10
|
*
|
|
8
11
|
* @module collection
|
|
9
12
|
*/
|
|
@@ -229,46 +232,56 @@ const litKeyHash = (key) => {
|
|
|
229
232
|
return null
|
|
230
233
|
}
|
|
231
234
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
// walking shared-prefix bytes through __str_eq (31M unequal content-compares
|
|
242
|
-
// per self-host bench run before this). If-expression for short-circuit:
|
|
243
|
-
// i32.and would still evaluate the call.
|
|
244
|
-
//
|
|
245
|
-
// On a hash hit, an inline `storedKey == queryKey` (one i64.eq on the slot's key
|
|
246
|
-
// word) decides the overwhelmingly-common identity case — interned/SSO literals and
|
|
247
|
-
// the same heap pointer are bit-equal — WITHOUT the __str_eq / __same_value_zero call
|
|
248
|
-
// frame. Sound for both: bit-equality implies string-equality and SameValueZero (the
|
|
249
|
-
// only cross-bit-pattern equals — +0/-0, distinct NaN payloads — fall through to the
|
|
250
|
-
// full compare, never the reverse). Only a content-equal-but-bit-distinct key (a heap
|
|
251
|
-
// string vs a literal) still pays the call. This attacks the __str_eq call tax directly
|
|
252
|
-
// and helps the runtimes that DON'T inline tiny callees (wasmtime, wasm2c).
|
|
253
|
-
const hashGuard = (eqExpr) =>
|
|
254
|
-
`(if (result i32) (i32.eq (i32.load (local.get $slot)) (local.get $h))
|
|
255
|
-
(then (if (result i32)
|
|
235
|
+
// Key-equality expressions for probe templates — run only after a LANE hash hit
|
|
236
|
+
// (the probe skeleton compares hashes; these decide the hit). The inline
|
|
237
|
+
// `storedKey == queryKey` bit-eq decides the overwhelmingly-common identity case
|
|
238
|
+
// — interned/SSO literals and the same heap pointer are bit-equal — WITHOUT the
|
|
239
|
+
// __str_eq / __same_value_zero call frame. Sound for both: bit-equality implies
|
|
240
|
+
// string-equality and SameValueZero (the only cross-bit-pattern equals — +0/-0,
|
|
241
|
+
// distinct NaN payloads — fall through to the full compare, never the reverse).
|
|
242
|
+
const keyEq = (fullEq) =>
|
|
243
|
+
`(if (result i32)
|
|
256
244
|
(i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))
|
|
257
245
|
(then (i32.const 1))
|
|
258
|
-
(else ${
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
246
|
+
(else ${fullEq}))`
|
|
247
|
+
const strEqG = keyEq('(call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))')
|
|
248
|
+
const sameValueZeroEqG = keyEq('(call $__same_value_zero (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))')
|
|
249
|
+
const bitEq = '(i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
250
|
+
|
|
251
|
+
// HASH-LANE probe. Entries keep the classic layout ([hash|seq:8][key:8][val:8] —
|
|
252
|
+
// iteration, heal, durable logs, delete-shift and clones are untouched), but a
|
|
253
|
+
// parallel i32 HASH LANE (cap × 4 B, zero-filled, AFTER the entry region) is what
|
|
254
|
+
// probes WALK: one 4-byte load per step, 16 hash checks per cache line where the
|
|
255
|
+
// 24-byte entry stride gave 2-3, and a miss chain touches an 8 kB lane instead of
|
|
256
|
+
// sweeping a 48 kB table through L1 (the wordcount-vs-C probe-footprint gap).
|
|
257
|
+
// Empty ⇔ lane word 0 (hash clamp keeps real hashes ≥ 2); healed zombies KEEP
|
|
258
|
+
// their stale hash in the lane and are passed by the key compare exactly as the
|
|
259
|
+
// entry-walk passed them. $ls walks the lane ($lb/$end its bounds); $slot (the
|
|
260
|
+
// entry address) derives only on a hash hit / at the insert slot. Every table
|
|
261
|
+
// alloc pays entrySize+4 per slot; the entry region offsets are unchanged.
|
|
262
|
+
const LANE = 4
|
|
266
263
|
const probeStart = (entrySize, idxExpr = '(i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1)))') =>
|
|
267
|
-
`(local.set $
|
|
268
|
-
(local.set $
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
264
|
+
`(local.set $lb (i32.add (local.get $off) (i32.mul (local.get $cap) (i32.const ${entrySize}))))
|
|
265
|
+
(local.set $end (i32.add (local.get $lb) (i32.shl (local.get $cap) (i32.const 2))))
|
|
266
|
+
(local.set $ls (i32.add (local.get $lb) (i32.shl ${idxExpr} (i32.const 2))))`
|
|
267
|
+
const probeNext = () =>
|
|
268
|
+
`(local.set $ls (i32.add (local.get $ls) (i32.const 4)))
|
|
269
|
+
(if (i32.ge_u (local.get $ls) (local.get $end)) (then (local.set $ls (local.get $lb))))`
|
|
270
|
+
// entry address of the lane cursor's slot
|
|
271
|
+
const slotFromLane = (entrySize) =>
|
|
272
|
+
`(local.set $slot (i32.add (local.get $off)
|
|
273
|
+
(i32.mul (i32.shr_u (i32.sub (local.get $ls) (local.get $lb)) (i32.const 2)) (i32.const ${entrySize}))))`
|
|
274
|
+
// probe-loop locals shared by every template
|
|
275
|
+
const laneLocals = '(local $lb i32) (local $ls i32) (local $hw i32)'
|
|
276
|
+
// cap-tries exhausted with no remembered zombie: rescan for any TOMB key via
|
|
277
|
+
// the shared cold helper (an all-zombies-with-foreign-hashes table —
|
|
278
|
+
// durable-heal-heavy warm embedders only; the lane probe only notices zombies
|
|
279
|
+
// on a hash hit). $__zomb_scan falls back to slot 0 when the table is truly
|
|
280
|
+
// full of live keys, which the 75%-load grow makes unreachable.
|
|
281
|
+
const zombieRescan = (entrySize) => `(if (i32.eqz (local.get $zb)) (then
|
|
282
|
+
(local.set $zb (call $__zomb_scan (local.get $off) (local.get $cap) (i32.const ${entrySize})))
|
|
283
|
+
(local.set $zbl (i32.add (local.get $lb)
|
|
284
|
+
(i32.shl (i32.div_u (i32.sub (local.get $zb) (local.get $off)) (i32.const ${entrySize})) (i32.const 2))))))`
|
|
272
285
|
|
|
273
286
|
// Store a fresh entry's hash word, packing a monotonic insertion sequence
|
|
274
287
|
// (global $__seq) into its free high 32 bits. The hash itself only ever occupies
|
|
@@ -313,6 +326,7 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
313
326
|
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32)
|
|
314
327
|
(local $size i32) (local $newptr i32) (local $newcap i32) (local $i i32)
|
|
315
328
|
(local $oldslot i32) (local $newidx i32) (local $newslot i32) (local $zb i32) (local $ztr i32)
|
|
329
|
+
${laneLocals} (local $zbl i32) (local $nlb i32)
|
|
316
330
|
${typeGuard}
|
|
317
331
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
318
332
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
@@ -327,7 +341,8 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
327
341
|
(if (i32.ge_s (i32.mul (local.get $size) (i32.const 4)) (i32.mul (local.get $cap) (i32.const 3)))
|
|
328
342
|
(then
|
|
329
343
|
(local.set $newcap (i32.shl (local.get $cap) (i32.const 1)))
|
|
330
|
-
(local.set $newptr (call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${entrySize})))
|
|
344
|
+
(local.set $newptr (call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${entrySize + LANE})))
|
|
345
|
+
(local.set $nlb (i32.add (local.get $newptr) (i32.mul (local.get $newcap) (i32.const ${entrySize}))))
|
|
331
346
|
(i64.store (i32.sub (local.get $newptr) (i32.const 16)) (i64.load (i32.sub (local.get $off) (i32.const 16))))
|
|
332
347
|
(local.set $i (i32.const 0))
|
|
333
348
|
(block $rd (loop $rl
|
|
@@ -344,6 +359,7 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
344
359
|
(br $probe2)))
|
|
345
360
|
(i64.store (local.get $newslot) (i64.load (local.get $oldslot)))
|
|
346
361
|
(i64.store (i32.add (local.get $newslot) (i32.const 8)) (i64.load (i32.add (local.get $oldslot) (i32.const 8))))${rehashVal}
|
|
362
|
+
(i32.store (i32.add (local.get $nlb) (i32.shl (local.get $newidx) (i32.const 2))) (local.get $h))
|
|
347
363
|
(i32.store (i32.sub (local.get $newptr) (i32.const 8))
|
|
348
364
|
(i32.add (i32.load (i32.sub (local.get $newptr) (i32.const 8))) (i32.const 1)))))
|
|
349
365
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
@@ -355,27 +371,39 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
355
371
|
(local.set $cap (local.get $newcap))))
|
|
356
372
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
357
373
|
${probeStart(entrySize)}
|
|
358
|
-
;; zombie-aware probe (durable-slot heal, TOMB_NAN keys):
|
|
359
|
-
;;
|
|
360
|
-
;;
|
|
374
|
+
;; zombie-aware LANE probe (durable-slot heal, TOMB_NAN keys): a zombie keeps
|
|
375
|
+
;; its stale hash in the lane, so it is only NOTICED on a hash hit (key reads
|
|
376
|
+
;; TOMB) — reuse still catches the dominant re-insert-same-key case, and the
|
|
377
|
+
;; cap-tries fallback rescans for any zombie before giving up.
|
|
361
378
|
(block $done (loop $probe
|
|
362
|
-
(
|
|
379
|
+
(local.set $hw (i32.load (local.get $ls)))
|
|
380
|
+
(if (i32.eqz (local.get $hw))
|
|
363
381
|
(then
|
|
364
|
-
(if (local.get $zb)
|
|
382
|
+
(if (local.get $zb)
|
|
383
|
+
(then (local.set $slot (local.get $zb)) (local.set $ls (local.get $zbl)))
|
|
384
|
+
(else ${slotFromLane(entrySize)}))
|
|
365
385
|
${seqStore}
|
|
386
|
+
(i32.store (local.get $ls) (local.get $h))
|
|
366
387
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${durableEntryLogIR('slot', 'off')}${storeVal}
|
|
367
388
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
368
389
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
369
390
|
(br $done)))
|
|
370
|
-
(if (
|
|
371
|
-
(then
|
|
372
|
-
|
|
373
|
-
|
|
391
|
+
(if (i32.eq (local.get $hw) (local.get $h))
|
|
392
|
+
(then
|
|
393
|
+
${slotFromLane(entrySize)}
|
|
394
|
+
(if (i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (i64.const ${TOMB_NAN}))
|
|
395
|
+
(then (if (i32.eqz (local.get $zb))
|
|
396
|
+
(then (local.set $zb (local.get $slot)) (local.set $zbl (local.get $ls)))))
|
|
397
|
+
(else (if ${eqExpr} ${onMatch})))))
|
|
398
|
+
${probeNext()}
|
|
374
399
|
(local.set $ztr (i32.add (local.get $ztr) (i32.const 1)))
|
|
375
400
|
(if (i32.ge_s (local.get $ztr) (local.get $cap))
|
|
376
401
|
(then
|
|
402
|
+
${zombieRescan(entrySize)}
|
|
377
403
|
(local.set $slot (local.get $zb))
|
|
404
|
+
(local.set $ls (local.get $zbl))
|
|
378
405
|
${seqStore}
|
|
406
|
+
(i32.store (local.get $ls) (local.get $h))
|
|
379
407
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${durableEntryLogIR('slot', 'off')}${storeVal}
|
|
380
408
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
381
409
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
@@ -414,15 +442,20 @@ function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, has
|
|
|
414
442
|
|
|
415
443
|
return `(func $${name} (param $coll i64) (param $key i64) (result ${rt})
|
|
416
444
|
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
445
|
+
${laneLocals}
|
|
417
446
|
${typeGuard}
|
|
418
447
|
(local.set $off ${offExpr})
|
|
419
448
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
420
449
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
421
450
|
${probeStart(entrySize)}
|
|
422
451
|
(block $done (loop $probe
|
|
423
|
-
(
|
|
424
|
-
(if $
|
|
425
|
-
$
|
|
452
|
+
(local.set $hw (i32.load (local.get $ls)))
|
|
453
|
+
(if (i32.eqz (local.get $hw)) (then ${onEmpty}))
|
|
454
|
+
(if (i32.eq (local.get $hw) (local.get $h))
|
|
455
|
+
(then
|
|
456
|
+
${slotFromLane(entrySize)}
|
|
457
|
+
(if ${eqExpr} (then ${onFound}))))
|
|
458
|
+
${probeNext()}
|
|
426
459
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
427
460
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
428
461
|
(br $probe)))
|
|
@@ -450,6 +483,7 @@ function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
|
450
483
|
return `(func $${name} (param $coll i64) (param $key i64) (result i32)
|
|
451
484
|
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
452
485
|
(local $i i32) (local $j i32) (local $k i32) (local $n i32)
|
|
486
|
+
${laneLocals} (local $li i32) (local $lj i32)
|
|
453
487
|
(if (i32.ne (call $__ptr_type (local.get $coll)) (i32.const ${expectedType})) (then (return (i32.const 0))))
|
|
454
488
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
455
489
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
@@ -463,20 +497,29 @@ function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
|
463
497
|
${probeStart(entrySize)}
|
|
464
498
|
(block $found
|
|
465
499
|
(block $absent (loop $probe
|
|
466
|
-
(
|
|
467
|
-
(if $
|
|
468
|
-
$
|
|
500
|
+
(local.set $hw (i32.load (local.get $ls)))
|
|
501
|
+
(if (i32.eqz (local.get $hw)) (then (br $absent)))
|
|
502
|
+
(if (i32.eq (local.get $hw) (local.get $h))
|
|
503
|
+
(then
|
|
504
|
+
${slotFromLane(entrySize)}
|
|
505
|
+
(if ${eqExpr} (then (br $found)))))
|
|
506
|
+
${probeNext()}
|
|
469
507
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
470
508
|
(br_if $absent (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
471
509
|
(br $probe)))
|
|
472
510
|
(return (i32.const 0)))
|
|
473
|
-
;; $slot holds the entry to remove. Walk forward; move back
|
|
474
|
-
;; is not cyclically within (i, j], else it would become
|
|
511
|
+
;; $slot holds the entry to remove ($ls its lane word). Walk forward; move back
|
|
512
|
+
;; any entry whose home is not cyclically within (i, j], else it would become
|
|
513
|
+
;; unreachable from its home. The lane word travels with each moved entry.
|
|
475
514
|
(local.set $i (local.get $slot))
|
|
476
515
|
(local.set $j (local.get $slot))
|
|
516
|
+
(local.set $li (local.get $ls))
|
|
517
|
+
(local.set $lj (local.get $ls))
|
|
477
518
|
(block $stop (loop $shift
|
|
478
519
|
(local.set $j (i32.add (local.get $j) (i32.const ${entrySize})))
|
|
479
|
-
(
|
|
520
|
+
(local.set $lj (i32.add (local.get $lj) (i32.const 4)))
|
|
521
|
+
(if (i32.ge_u (local.get $lj) (local.get $end))
|
|
522
|
+
(then (local.set $j (local.get $off)) (local.set $lj (local.get $lb))))
|
|
480
523
|
(br_if $stop (i64.eqz (i64.load (local.get $j))))
|
|
481
524
|
;; Empty slot ends the cluster (load < 100%). A 100%-full table has none — lookups
|
|
482
525
|
;; tolerate that via the $tries<cap bound, so delete must too: after $cap advances $j
|
|
@@ -489,10 +532,13 @@ function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
|
489
532
|
(then (br_if $shift (i32.and (i32.lt_u (local.get $i) (local.get $k)) (i32.le_u (local.get $k) (local.get $j)))))
|
|
490
533
|
(else (br_if $shift (i32.or (i32.lt_u (local.get $i) (local.get $k)) (i32.le_u (local.get $k) (local.get $j))))))
|
|
491
534
|
(memory.copy (local.get $i) (local.get $j) (i32.const ${entrySize}))
|
|
535
|
+
(i32.store (local.get $li) (i32.load (local.get $lj)))
|
|
492
536
|
(local.set $i (local.get $j))
|
|
537
|
+
(local.set $li (local.get $lj))
|
|
493
538
|
(br $shift)))
|
|
494
539
|
(i64.store (local.get $i) (i64.const 0))
|
|
495
540
|
(i64.store (i32.add (local.get $i) (i32.const 8)) (i64.const 0))
|
|
541
|
+
(i32.store (local.get $li) (i32.const 0))
|
|
496
542
|
${enumcInval}(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
497
543
|
(i32.sub (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
498
544
|
(i32.const 1))`
|
|
@@ -520,6 +566,7 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
520
566
|
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32)
|
|
521
567
|
(local $size i32) (local $newptr i32) (local $newcap i32) (local $i i32)
|
|
522
568
|
(local $oldslot i32) (local $newidx i32) (local $newslot i32) (local $zb i32) (local $ztr i32)
|
|
569
|
+
${laneLocals} (local $zbl i32) (local $nlb i32)
|
|
523
570
|
${typeGuard}
|
|
524
571
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
525
572
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
@@ -534,7 +581,8 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
534
581
|
(if (i32.ge_s (i32.mul (local.get $size) (i32.const 4)) (i32.mul (local.get $cap) (i32.const 3)))
|
|
535
582
|
(then
|
|
536
583
|
(local.set $newcap (i32.shl (local.get $cap) (i32.const 1)))
|
|
537
|
-
(local.set $newptr (call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${entrySize})))
|
|
584
|
+
(local.set $newptr (call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${entrySize + LANE})))
|
|
585
|
+
(local.set $nlb (i32.add (local.get $newptr) (i32.mul (local.get $newcap) (i32.const ${entrySize}))))
|
|
538
586
|
(local.set $i (i32.const 0))
|
|
539
587
|
(block $rd (loop $rl
|
|
540
588
|
(br_if $rd (i32.ge_s (local.get $i) (local.get $cap)))
|
|
@@ -551,6 +599,7 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
551
599
|
(i64.store (local.get $newslot) (i64.load (local.get $oldslot)))
|
|
552
600
|
(i64.store (i32.add (local.get $newslot) (i32.const 8)) (i64.load (i32.add (local.get $oldslot) (i32.const 8))))
|
|
553
601
|
(i64.store (i32.add (local.get $newslot) (i32.const 16)) (i64.load (i32.add (local.get $oldslot) (i32.const 16))))
|
|
602
|
+
(i32.store (i32.add (local.get $nlb) (i32.shl (local.get $newidx) (i32.const 2))) (local.get $h))
|
|
554
603
|
(i32.store (i32.sub (local.get $newptr) (i32.const 8))
|
|
555
604
|
(i32.add (i32.load (i32.sub (local.get $newptr) (i32.const 8))) (i32.const 1)))))
|
|
556
605
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
@@ -576,29 +625,40 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
576
625
|
;; Insert/update
|
|
577
626
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
578
627
|
${probeStart(entrySize)}
|
|
579
|
-
;; zombie-aware probe (durable-slot heal, TOMB_NAN keys) — see genUpsert.
|
|
628
|
+
;; zombie-aware LANE probe (durable-slot heal, TOMB_NAN keys) — see genUpsert.
|
|
580
629
|
(block $done (loop $probe
|
|
581
|
-
(
|
|
630
|
+
(local.set $hw (i32.load (local.get $ls)))
|
|
631
|
+
(if (i32.eqz (local.get $hw))
|
|
582
632
|
(then
|
|
583
|
-
(if (local.get $zb)
|
|
633
|
+
(if (local.get $zb)
|
|
634
|
+
(then (local.set $slot (local.get $zb)) (local.set $ls (local.get $zbl)))
|
|
635
|
+
(else ${slotFromLane(entrySize)}))
|
|
584
636
|
${seqStore}
|
|
637
|
+
(i32.store (local.get $ls) (local.get $h))
|
|
585
638
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${durableEntryLogIR('slot', 'off')}
|
|
586
639
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))${durableSlotLogIR('slot', 16, 'val')}
|
|
587
640
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
588
641
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
589
642
|
(br $done)))
|
|
590
|
-
(if (
|
|
591
|
-
(then
|
|
592
|
-
|
|
593
|
-
(
|
|
594
|
-
(
|
|
595
|
-
|
|
596
|
-
|
|
643
|
+
(if (i32.eq (local.get $hw) (local.get $h))
|
|
644
|
+
(then
|
|
645
|
+
${slotFromLane(entrySize)}
|
|
646
|
+
(if (i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (i64.const ${TOMB_NAN}))
|
|
647
|
+
(then (if (i32.eqz (local.get $zb))
|
|
648
|
+
(then (local.set $zb (local.get $slot)) (local.set $zbl (local.get $ls)))))
|
|
649
|
+
(else (if ${eqExpr}
|
|
650
|
+
(then
|
|
651
|
+
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))${durableSlotLogIR('slot', 16, 'val')}
|
|
652
|
+
(br $done)))))))
|
|
653
|
+
${probeNext()}
|
|
597
654
|
(local.set $ztr (i32.add (local.get $ztr) (i32.const 1)))
|
|
598
655
|
(if (i32.ge_s (local.get $ztr) (local.get $cap))
|
|
599
656
|
(then
|
|
657
|
+
${zombieRescan(entrySize)}
|
|
600
658
|
(local.set $slot (local.get $zb))
|
|
659
|
+
(local.set $ls (local.get $zbl))
|
|
601
660
|
${seqStore}
|
|
661
|
+
(i32.store (local.get $ls) (local.get $h))
|
|
602
662
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${durableEntryLogIR('slot', 'off')}
|
|
603
663
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))${durableSlotLogIR('slot', 16, 'val')}
|
|
604
664
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
@@ -624,6 +684,7 @@ function genSlotUpsert(name, entrySize, hashFn, eqExpr) {
|
|
|
624
684
|
(local $size i32) (local $newptr i32) (local $newcap i32) (local $i i32)
|
|
625
685
|
(local $oldslot i32) (local $newidx i32) (local $newslot i32) (local $zb i32) (local $ztr i32)
|
|
626
686
|
(local $kaux i32) (local $koff i32)
|
|
687
|
+
${laneLocals} (local $zbl i32) (local $nlb i32)
|
|
627
688
|
(if (i32.ne (call $__ptr_type (local.get $obj)) (i32.const ${PTR.HASH}))
|
|
628
689
|
(then (return (i32.const 0))))
|
|
629
690
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
@@ -638,7 +699,8 @@ function genSlotUpsert(name, entrySize, hashFn, eqExpr) {
|
|
|
638
699
|
(if (i32.ge_s (i32.mul (local.get $size) (i32.const 4)) (i32.mul (local.get $cap) (i32.const 3)))
|
|
639
700
|
(then
|
|
640
701
|
(local.set $newcap (i32.shl (local.get $cap) (i32.const 1)))
|
|
641
|
-
(local.set $newptr (call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${entrySize})))
|
|
702
|
+
(local.set $newptr (call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${entrySize + LANE})))
|
|
703
|
+
(local.set $nlb (i32.add (local.get $newptr) (i32.mul (local.get $newcap) (i32.const ${entrySize}))))
|
|
642
704
|
(local.set $i (i32.const 0))
|
|
643
705
|
(block $rd (loop $rl
|
|
644
706
|
(br_if $rd (i32.ge_s (local.get $i) (local.get $cap)))
|
|
@@ -655,6 +717,7 @@ function genSlotUpsert(name, entrySize, hashFn, eqExpr) {
|
|
|
655
717
|
(i64.store (local.get $newslot) (i64.load (local.get $oldslot)))
|
|
656
718
|
(i64.store (i32.add (local.get $newslot) (i32.const 8)) (i64.load (i32.add (local.get $oldslot) (i32.const 8))))
|
|
657
719
|
(i64.store (i32.add (local.get $newslot) (i32.const 16)) (i64.load (i32.add (local.get $oldslot) (i32.const 16))))
|
|
720
|
+
(i32.store (i32.add (local.get $nlb) (i32.shl (local.get $newidx) (i32.const 2))) (local.get $h))
|
|
658
721
|
(i32.store (i32.sub (local.get $newptr) (i32.const 8))
|
|
659
722
|
(i32.add (i32.load (i32.sub (local.get $newptr) (i32.const 8))) (i32.const 1)))))
|
|
660
723
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
@@ -694,24 +757,35 @@ function genSlotUpsert(name, entrySize, hashFn, eqExpr) {
|
|
|
694
757
|
: `(local.set $h (call ${hashFn} (local.get $key)))`}
|
|
695
758
|
${probeStart(entrySize)}
|
|
696
759
|
(block $done (loop $probe
|
|
697
|
-
(
|
|
760
|
+
(local.set $hw (i32.load (local.get $ls)))
|
|
761
|
+
(if (i32.eqz (local.get $hw))
|
|
698
762
|
(then
|
|
699
|
-
(if (local.get $zb)
|
|
763
|
+
(if (local.get $zb)
|
|
764
|
+
(then (local.set $slot (local.get $zb)) (local.set $ls (local.get $zbl)))
|
|
765
|
+
(else ${slotFromLane(entrySize)}))
|
|
700
766
|
${seqStore}
|
|
767
|
+
(i32.store (local.get $ls) (local.get $h))
|
|
701
768
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${durableEntryLogIR('slot', 'off')}
|
|
702
769
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (i64.const ${UNDEF_NAN}))
|
|
703
770
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
704
771
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
705
772
|
(br $done)))
|
|
706
|
-
(if (
|
|
707
|
-
(then
|
|
708
|
-
|
|
709
|
-
|
|
773
|
+
(if (i32.eq (local.get $hw) (local.get $h))
|
|
774
|
+
(then
|
|
775
|
+
${slotFromLane(entrySize)}
|
|
776
|
+
(if (i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (i64.const ${TOMB_NAN}))
|
|
777
|
+
(then (if (i32.eqz (local.get $zb))
|
|
778
|
+
(then (local.set $zb (local.get $slot)) (local.set $zbl (local.get $ls)))))
|
|
779
|
+
(else (if ${eqExpr} (then (br $done)))))))
|
|
780
|
+
${probeNext()}
|
|
710
781
|
(local.set $ztr (i32.add (local.get $ztr) (i32.const 1)))
|
|
711
782
|
(if (i32.ge_s (local.get $ztr) (local.get $cap))
|
|
712
783
|
(then
|
|
784
|
+
${zombieRescan(entrySize)}
|
|
713
785
|
(local.set $slot (local.get $zb))
|
|
786
|
+
(local.set $ls (local.get $zbl))
|
|
714
787
|
${seqStore}
|
|
788
|
+
(i32.store (local.get $ls) (local.get $h))
|
|
715
789
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${durableEntryLogIR('slot', 'off')}
|
|
716
790
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (i64.const ${UNDEF_NAN}))
|
|
717
791
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
@@ -724,6 +798,7 @@ function genSlotUpsert(name, entrySize, hashFn, eqExpr) {
|
|
|
724
798
|
function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing = UNDEF_NAN) {
|
|
725
799
|
return `(func $${name} (param $coll i64) (param $key i64) (result i64)
|
|
726
800
|
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
801
|
+
${laneLocals}
|
|
727
802
|
(if (i32.ne
|
|
728
803
|
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
729
804
|
(i32.const ${expectedType}))
|
|
@@ -739,11 +814,15 @@ function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing
|
|
|
739
814
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
740
815
|
${probeStart(entrySize)}
|
|
741
816
|
(block $done (loop $probe
|
|
742
|
-
(
|
|
817
|
+
(local.set $hw (i32.load (local.get $ls)))
|
|
818
|
+
(if (i32.eqz (local.get $hw))
|
|
743
819
|
(then (return (i64.const ${missing}))))
|
|
744
|
-
(if $
|
|
745
|
-
(then
|
|
746
|
-
|
|
820
|
+
(if (i32.eq (local.get $hw) (local.get $h))
|
|
821
|
+
(then
|
|
822
|
+
${slotFromLane(entrySize)}
|
|
823
|
+
(if ${eqExpr}
|
|
824
|
+
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))))
|
|
825
|
+
${probeNext()}
|
|
747
826
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
748
827
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
749
828
|
(br $probe)))
|
|
@@ -769,6 +848,7 @@ function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing
|
|
|
769
848
|
(then ${onEmpty}))`
|
|
770
849
|
return `(func $${name} (param $coll i64) (param $key i64) (param $h i32) (result ${rt})
|
|
771
850
|
(local $off i32) (local $cap i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
851
|
+
${laneLocals}
|
|
772
852
|
${typeGuard}
|
|
773
853
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
774
854
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
@@ -780,9 +860,13 @@ function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing
|
|
|
780
860
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))))
|
|
781
861
|
${probeStart(entrySize)}
|
|
782
862
|
(block $done (loop $probe
|
|
783
|
-
(
|
|
784
|
-
(if $
|
|
785
|
-
$
|
|
863
|
+
(local.set $hw (i32.load (local.get $ls)))
|
|
864
|
+
(if (i32.eqz (local.get $hw)) (then ${onEmpty}))
|
|
865
|
+
(if (i32.eq (local.get $hw) (local.get $h))
|
|
866
|
+
(then
|
|
867
|
+
${slotFromLane(entrySize)}
|
|
868
|
+
(if ${eqExpr} (then ${onFound}))))
|
|
869
|
+
${probeNext()}
|
|
786
870
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
787
871
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
788
872
|
(br $probe)))
|
|
@@ -792,6 +876,7 @@ function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing
|
|
|
792
876
|
function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
793
877
|
return `(func $${name} (param $obj i64) (param $key i64) (param $h i32) (param $val i64) (result i64)
|
|
794
878
|
(local $off i32) (local $cap i32) (local $end i32) (local $slot i32) (local $zb i32) (local $ztr i32)
|
|
879
|
+
${laneLocals} (local $zbl i32)
|
|
795
880
|
(if (i32.ne
|
|
796
881
|
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
797
882
|
(i32.const ${expectedType}))
|
|
@@ -805,29 +890,40 @@ function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
|
805
890
|
(local.set $off (call $__ptr_offset_fwd (local.get $off)))
|
|
806
891
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))))
|
|
807
892
|
${probeStart(entrySize)}
|
|
808
|
-
;; zombie-aware probe (durable-slot heal, TOMB_NAN keys) — see genUpsert.
|
|
893
|
+
;; zombie-aware LANE probe (durable-slot heal, TOMB_NAN keys) — see genUpsert.
|
|
809
894
|
(block $done (loop $probe
|
|
810
|
-
(
|
|
895
|
+
(local.set $hw (i32.load (local.get $ls)))
|
|
896
|
+
(if (i32.eqz (local.get $hw))
|
|
811
897
|
(then
|
|
812
|
-
(if (local.get $zb)
|
|
898
|
+
(if (local.get $zb)
|
|
899
|
+
(then (local.set $slot (local.get $zb)) (local.set $ls (local.get $zbl)))
|
|
900
|
+
(else ${slotFromLane(entrySize)}))
|
|
813
901
|
${seqStore}
|
|
902
|
+
(i32.store (local.get $ls) (local.get $h))
|
|
814
903
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${durableEntryLogIR('slot', 'off')}
|
|
815
904
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))${durableSlotLogIR('slot', 16, 'val')}
|
|
816
905
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
817
906
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
818
907
|
(br $done)))
|
|
819
|
-
(if (
|
|
820
|
-
(then
|
|
821
|
-
|
|
822
|
-
(
|
|
823
|
-
(
|
|
824
|
-
|
|
825
|
-
|
|
908
|
+
(if (i32.eq (local.get $hw) (local.get $h))
|
|
909
|
+
(then
|
|
910
|
+
${slotFromLane(entrySize)}
|
|
911
|
+
(if (i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (i64.const ${TOMB_NAN}))
|
|
912
|
+
(then (if (i32.eqz (local.get $zb))
|
|
913
|
+
(then (local.set $zb (local.get $slot)) (local.set $zbl (local.get $ls)))))
|
|
914
|
+
(else (if ${eqExpr}
|
|
915
|
+
(then
|
|
916
|
+
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))${durableSlotLogIR('slot', 16, 'val')}
|
|
917
|
+
(br $done)))))))
|
|
918
|
+
${probeNext()}
|
|
826
919
|
(local.set $ztr (i32.add (local.get $ztr) (i32.const 1)))
|
|
827
920
|
(if (i32.ge_s (local.get $ztr) (local.get $cap))
|
|
828
921
|
(then
|
|
922
|
+
${zombieRescan(entrySize)}
|
|
829
923
|
(local.set $slot (local.get $zb))
|
|
924
|
+
(local.set $ls (local.get $zbl))
|
|
830
925
|
${seqStore}
|
|
926
|
+
(i32.store (local.get $ls) (local.get $h))
|
|
831
927
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (local.get $key))${durableEntryLogIR('slot', 'off')}
|
|
832
928
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))${durableSlotLogIR('slot', 16, 'val')}
|
|
833
929
|
(i32.store (i32.sub (local.get $off) (i32.const 8))
|
|
@@ -865,7 +961,7 @@ export default (ctx) => {
|
|
|
865
961
|
// log (durableEntryLogIR) still calls $__durable_slot_log — without the
|
|
866
962
|
// explicit edge the kernel leg drops the helper (auto-scan divergence, the
|
|
867
963
|
// selfhost-includes class) and every `new Set(...)` fails to compile there.
|
|
868
|
-
__set_add: () => [...(ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__alloc_hdr_n', '__ext_set'] : ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__alloc_hdr_n']), ...(needsDurableFwdLog() ? ['__durable_fwd_log'] : []), ...slotLogDeps()],
|
|
964
|
+
__set_add: () => [...(ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__alloc_hdr_n', '__zomb_scan', '__ext_set'] : ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__alloc_hdr_n', '__zomb_scan']), ...(needsDurableFwdLog() ? ['__durable_fwd_log'] : []), ...slotLogDeps()],
|
|
869
965
|
__set_has: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__ext_has'] : ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd'],
|
|
870
966
|
__set_delete: ['__map_hash', '__same_value_zero'],
|
|
871
967
|
__set_add_all: ['__ptr_offset', '__ptr_offset_fwd', '__cap', '__len', '__coll_order', '__set_add'],
|
|
@@ -874,7 +970,7 @@ export default (ctx) => {
|
|
|
874
970
|
__sclone: ['__sclone_rec', '__mkptr', '__alloc_hdr_n'],
|
|
875
971
|
__sclone_rec: ['__ptr_type', '__ptr_offset', '__ptr_offset_fwd', '__ptr_aux', '__is_nullish', '__len', '__alloc', '__alloc_hdr_n', '__mkptr', '__map_get', '__map_set', '__set_add', '__coll_order', '__arr_from', '__obj_clone', '__sclone_hash_vals'],
|
|
876
972
|
__sclone_hash_vals: ['__sclone_rec'],
|
|
877
|
-
__map_set: () => [...(ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__alloc_hdr_n', '__ext_set'] : ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__alloc_hdr_n']), ...(needsDurableFwdLog() ? ['__durable_fwd_log'] : []), ...slotLogDeps()],
|
|
973
|
+
__map_set: () => [...(ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__alloc_hdr_n', '__zomb_scan', '__ext_set'] : ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__alloc_hdr_n', '__zomb_scan']), ...(needsDurableFwdLog() ? ['__durable_fwd_log'] : []), ...slotLogDeps()],
|
|
878
974
|
__map_get: () => ctx.features.external ? ['__ext_prop', '__map_set', '__ptr_offset', '__ptr_offset_fwd'] : ['__map_set', '__ptr_offset', '__ptr_offset_fwd'],
|
|
879
975
|
__map_get_h: () => ctx.features.external ? ['__ext_prop', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd'] : ['__same_value_zero', '__ptr_offset', '__ptr_offset_fwd'],
|
|
880
976
|
__map_has: () => ctx.features.external ? ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd', '__ext_has'] : ['__map_hash', '__same_value_zero', '__ptr_offset', '__ptr_offset_fwd'],
|
|
@@ -888,6 +984,7 @@ export default (ctx) => {
|
|
|
888
984
|
__map_new: ['__alloc_hdr_n'],
|
|
889
985
|
__hash_set: () => [
|
|
890
986
|
...(ctx.features.external ? ['__str_hash', '__str_eq', '__ptr_type', '__ext_set', '__dyn_set'] : ['__str_hash', '__str_eq', '__ptr_type', '__dyn_set']),
|
|
987
|
+
'__zomb_scan',
|
|
891
988
|
...(needsDurableFwdLog() ? ['__durable_fwd_log'] : []),
|
|
892
989
|
...slotLogDeps(),
|
|
893
990
|
],
|
|
@@ -901,12 +998,12 @@ export default (ctx) => {
|
|
|
901
998
|
__hash_new_small: ['__alloc_hdr_n', '__mkptr'],
|
|
902
999
|
__hash_get_local: ['__str_hash', '__str_eq'],
|
|
903
1000
|
__hash_get_local_h: ['__str_eq'],
|
|
904
|
-
__hash_set_local_h: () => ['__str_eq', ...slotLogDeps()],
|
|
905
|
-
__hash_set_local: () => ['__str_hash', '__str_eq', '__alloc_hdr_n', '__mkptr', ...(needsDurableFwdLog() ? ['__durable_fwd_log'] : []), ...slotLogDeps()],
|
|
906
|
-
__hash_slot: () => ['__str_hash', '__str_eq', '__alloc_hdr_n', '__ptr_type', '__ptr_offset', '__ptr_offset_fwd', ...(needsDurableFwdLog() ? ['__durable_fwd_log'] : []), ...slotLogDeps()],
|
|
1001
|
+
__hash_set_local_h: () => ['__str_eq', '__zomb_scan', ...slotLogDeps()],
|
|
1002
|
+
__hash_set_local: () => ['__str_hash', '__str_eq', '__alloc_hdr_n', '__mkptr', '__zomb_scan', ...(needsDurableFwdLog() ? ['__durable_fwd_log'] : []), ...slotLogDeps()],
|
|
1003
|
+
__hash_slot: () => ['__str_hash', '__str_eq', '__alloc_hdr_n', '__ptr_type', '__ptr_offset', '__ptr_offset_fwd', '__zomb_scan', ...(needsDurableFwdLog() ? ['__durable_fwd_log'] : []), ...slotLogDeps()],
|
|
907
1004
|
__slot_write: () => slotLogDeps(),
|
|
908
1005
|
__ihash_get_local: ['__map_hash'],
|
|
909
|
-
__ihash_set_local: () => ['__map_hash', '__alloc_hdr_n', '__mkptr', ...slotLogDeps()],
|
|
1006
|
+
__ihash_set_local: () => ['__map_hash', '__alloc_hdr_n', '__mkptr', '__zomb_scan', ...slotLogDeps()],
|
|
910
1007
|
__dyn_get_t: ['__dyn_get_t_h', '__str_hash', '__is_str_key', '__to_str'],
|
|
911
1008
|
__dyn_get_t_h: ['__ihash_get_local', '__str_eq', '__is_nullish', '__hash_get_local_h', '__str_arr_idx', '__str_byteLen'],
|
|
912
1009
|
__dyn_get: ['__dyn_get_t', '__ptr_type'],
|
|
@@ -1038,14 +1135,14 @@ export default (ctx) => {
|
|
|
1038
1135
|
// __map_new() → f64 — allocate empty Map (for JSON.parse, runtime creation)
|
|
1039
1136
|
ctx.core.stdlib['__map_new'] = `(func $__map_new (result f64)
|
|
1040
1137
|
(call $__mkptr (i32.const ${PTR.MAP}) (i32.const 0)
|
|
1041
|
-
(call $__alloc_hdr_n (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY}))))`
|
|
1138
|
+
(call $__alloc_hdr_n (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY + LANE}))))`
|
|
1042
1139
|
|
|
1043
1140
|
// === Set ===
|
|
1044
1141
|
|
|
1045
1142
|
ctx.core.emit['new.Set'] = (iterExpr) => {
|
|
1046
1143
|
ctx.features.set = true
|
|
1047
1144
|
if (iterExpr == null) {
|
|
1048
|
-
const out = allocPtr({ type: PTR.SET, len: 0, cap: INIT_CAP, stride: SET_ENTRY, tag: 'set' })
|
|
1145
|
+
const out = allocPtr({ type: PTR.SET, len: 0, cap: INIT_CAP, stride: SET_ENTRY + LANE, tag: 'set' })
|
|
1049
1146
|
return typed(['block', ['result', 'f64'], out.init, out.ptr], 'f64')
|
|
1050
1147
|
}
|
|
1051
1148
|
// new Set(iterable): __iter_arr normalizes any iterable to an index-iterable
|
|
@@ -1067,7 +1164,7 @@ export default (ctx) => {
|
|
|
1067
1164
|
['i32.sub',
|
|
1068
1165
|
['i32.add', ['i32.shl', ['local.get', `$${lenL}`], ['i32.const', 1]], ['i32.const', INIT_CAP]],
|
|
1069
1166
|
['i32.const', 1]]]]]
|
|
1070
|
-
const out = allocPtr({ type: PTR.SET, len: 0, cap: capExpr, stride: SET_ENTRY, tag: 'set' })
|
|
1167
|
+
const out = allocPtr({ type: PTR.SET, len: 0, cap: capExpr, stride: SET_ENTRY + LANE, tag: 'set' })
|
|
1071
1168
|
return typed(['block', ['result', 'f64'],
|
|
1072
1169
|
['local.set', `$${arrL}`, asF64(emit(['()', '__iter_arr', iterExpr]))],
|
|
1073
1170
|
['local.set', `$${lenL}`, ['i32.const', 0]],
|
|
@@ -1148,7 +1245,7 @@ export default (ctx) => {
|
|
|
1148
1245
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
1149
1246
|
(memory.fill (local.get $off) (i32.const 0)
|
|
1150
1247
|
(i32.mul (local.get $cap)
|
|
1151
|
-
(i32.add (i32.const ${SET_ENTRY})
|
|
1248
|
+
(i32.add (i32.const ${SET_ENTRY + LANE})
|
|
1152
1249
|
(i32.shl (i32.eq (local.get $t) (i32.const ${PTR.MAP})) (i32.const 3)))))
|
|
1153
1250
|
(i32.store (i32.sub (local.get $off) (i32.const 8)) (i32.const 0))))
|
|
1154
1251
|
(f64.reinterpret_i64 (i64.const ${UNDEF_NAN})))`
|
|
@@ -1271,7 +1368,7 @@ export default (ctx) => {
|
|
|
1271
1368
|
ctx.core.emit['new.Map'] = (iterExpr) => {
|
|
1272
1369
|
ctx.features.map = true
|
|
1273
1370
|
if (iterExpr == null) {
|
|
1274
|
-
const out = allocPtr({ type: PTR.MAP, len: 0, cap: INIT_CAP, stride: MAP_ENTRY, tag: 'map' })
|
|
1371
|
+
const out = allocPtr({ type: PTR.MAP, len: 0, cap: INIT_CAP, stride: MAP_ENTRY + LANE, tag: 'map' })
|
|
1275
1372
|
return typed(['block', ['result', 'f64'], out.init, out.ptr], 'f64')
|
|
1276
1373
|
}
|
|
1277
1374
|
// new Map(iterable): seed from another Map or an array of [key, value] pairs.
|
|
@@ -1415,7 +1512,7 @@ export default (ctx) => {
|
|
|
1415
1512
|
}
|
|
1416
1513
|
// Build the fresh-dst + threaded-walker-call sequence, returning the dst temp.
|
|
1417
1514
|
const buildSet = (steps, tag) => {
|
|
1418
|
-
const dst = allocPtr({ type: PTR.SET, len: 0, cap: INIT_CAP, stride: SET_ENTRY, tag })
|
|
1515
|
+
const dst = allocPtr({ type: PTR.SET, len: 0, cap: INIT_CAP, stride: SET_ENTRY + LANE, tag })
|
|
1419
1516
|
const dstT = temp('sopd')
|
|
1420
1517
|
const dI = () => ['i64.reinterpret_f64', ['local.get', `$${dstT}`]]
|
|
1421
1518
|
const seq = ['block', ['result', 'f64'], dst.init, ['local.set', `$${dstT}`, dst.ptr]]
|
|
@@ -1486,7 +1583,7 @@ export default (ctx) => {
|
|
|
1486
1583
|
const resI64 = ['i64.reinterpret_f64', ['local.get', `$${result}`]]
|
|
1487
1584
|
const nb = allocPtr({ type: PTR.ARRAY, len: 0, cap: 0, tag: 'gbn' })
|
|
1488
1585
|
const initResult = isMap
|
|
1489
|
-
? (() => { const out = allocPtr({ type: PTR.MAP, len: 0, cap: INIT_CAP, stride: MAP_ENTRY, tag: 'gbm' })
|
|
1586
|
+
? (() => { const out = allocPtr({ type: PTR.MAP, len: 0, cap: INIT_CAP, stride: MAP_ENTRY + LANE, tag: 'gbm' })
|
|
1490
1587
|
return ['block', ['result', 'f64'], out.init, out.ptr] })()
|
|
1491
1588
|
: ['call', '$__hash_new']
|
|
1492
1589
|
const keyOf = (cbResult) => isMap ? asI64(cbResult) : ['call', '$__to_str', asI64(cbResult)]
|
|
@@ -1546,7 +1643,7 @@ export default (ctx) => {
|
|
|
1546
1643
|
ctx.core.stdlib['__sclone'] = `(func $__sclone (param $v f64) (result f64)
|
|
1547
1644
|
(call $__sclone_rec (local.get $v)
|
|
1548
1645
|
(i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.MAP}) (i32.const 0)
|
|
1549
|
-
(call $__alloc_hdr_n (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY}))))))`
|
|
1646
|
+
(call $__alloc_hdr_n (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY + LANE}))))))`
|
|
1550
1647
|
|
|
1551
1648
|
// Deep-clone the values of a freshly copied HASH table, in place.
|
|
1552
1649
|
ctx.core.stdlib['__sclone_hash_vals'] = `(func $__sclone_hash_vals (param $off i32) (param $memo i64)
|
|
@@ -1639,7 +1736,7 @@ export default (ctx) => {
|
|
|
1639
1736
|
(local.set $src (call $__ptr_offset (local.get $bits)))
|
|
1640
1737
|
(local.set $cap (i32.load (i32.sub (local.get $src) (i32.const 4))))
|
|
1641
1738
|
(local.set $out (call $__mkptr (local.get $t) (i32.const 0)
|
|
1642
|
-
(call $__alloc_hdr_n (i32.const 0) (local.get $cap) (local.get $stride))))
|
|
1739
|
+
(call $__alloc_hdr_n (i32.const 0) (local.get $cap) (i32.add (local.get $stride) (i32.const ${LANE})))))
|
|
1643
1740
|
(drop (call $__map_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
|
|
1644
1741
|
;; walk the source in insertion order; ≤len inserts into cap slots never grow,
|
|
1645
1742
|
;; so $out's bits stay canonical (the memo entry above remains the pointer)
|
|
@@ -1739,7 +1836,7 @@ export default (ctx) => {
|
|
|
1739
1836
|
(i32.sub (i32.const 32) (i32.clz
|
|
1740
1837
|
(i32.sub (i32.add (i32.shl (local.get $n) (i32.const 1)) (i32.const ${INIT_CAP})) (i32.const 1))))))
|
|
1741
1838
|
(local.set $map (i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.MAP}) (i32.const 0)
|
|
1742
|
-
(call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${MAP_ENTRY})))))
|
|
1839
|
+
(call $__alloc_hdr_n (i32.const 0) (local.get $newcap) (i32.const ${MAP_ENTRY + LANE})))))
|
|
1743
1840
|
(if (i32.eq (local.get $t) (i32.const ${PTR.MAP}))
|
|
1744
1841
|
(then
|
|
1745
1842
|
;; Copy in source insertion order so the new map enumerates identically.
|
|
@@ -1834,9 +1931,23 @@ export default (ctx) => {
|
|
|
1834
1931
|
(if (local.get $cs) (then (i32.store (local.get $cs) (local.get $h))))
|
|
1835
1932
|
(local.get $h))`
|
|
1836
1933
|
|
|
1934
|
+
// Cold shared rescan for the upsert templates' cap-tries fallback: first
|
|
1935
|
+
// TOMB-keyed (zombied) slot of the table, or slot 0 when none exists (a
|
|
1936
|
+
// truly-full-of-live-keys table — unreachable behind the 75%-load grow).
|
|
1937
|
+
ctx.core.stdlib['__zomb_scan'] = `(func $__zomb_scan (param $off i32) (param $cap i32) (param $es i32) (result i32)
|
|
1938
|
+
(local $slot i32) (local $end i32)
|
|
1939
|
+
(local.set $slot (local.get $off))
|
|
1940
|
+
(local.set $end (i32.add (local.get $off) (i32.mul (local.get $cap) (local.get $es))))
|
|
1941
|
+
(block $d (loop $l
|
|
1942
|
+
(br_if $d (i32.ge_u (local.get $slot) (local.get $end)))
|
|
1943
|
+
(br_if $d (i64.eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (i64.const ${TOMB_NAN})))
|
|
1944
|
+
(local.set $slot (i32.add (local.get $slot) (local.get $es)))
|
|
1945
|
+
(br $l)))
|
|
1946
|
+
(select (local.get $off) (local.get $slot) (i32.ge_u (local.get $slot) (local.get $end))))`
|
|
1947
|
+
|
|
1837
1948
|
ctx.core.stdlib['__hash_new'] = `(func $__hash_new (result f64)
|
|
1838
1949
|
(call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0)
|
|
1839
|
-
(call $__alloc_hdr_n (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY}))))`
|
|
1950
|
+
(call $__alloc_hdr_n (i32.const 0) (i32.const ${INIT_CAP}) (i32.const ${MAP_ENTRY + LANE}))))`
|
|
1840
1951
|
|
|
1841
1952
|
// Small initial capacity for propsPtr-style hashes (per-object dyn props).
|
|
1842
1953
|
// Most receivers in real code carry 0-2 dyn props; paying 8-slot up-front
|
|
@@ -1846,7 +1957,7 @@ export default (ctx) => {
|
|
|
1846
1957
|
const smallCap = Math.max(ctx.transform.optimize?.hashSmallInitCap | 0, 2)
|
|
1847
1958
|
ctx.core.stdlib['__hash_new_small'] = `(func $__hash_new_small (result f64)
|
|
1848
1959
|
(call $__mkptr (i32.const ${PTR.HASH}) (i32.const 0)
|
|
1849
|
-
(call $__alloc_hdr_n (i32.const 0) (i32.const ${smallCap}) (i32.const ${MAP_ENTRY}))))`
|
|
1960
|
+
(call $__alloc_hdr_n (i32.const 0) (i32.const ${smallCap}) (i32.const ${MAP_ENTRY + LANE}))))`
|
|
1850
1961
|
|
|
1851
1962
|
ctx.core.stdlib['__hash_get_local'] = genLookupStrict('__hash_get_local', MAP_ENTRY, '$__str_hash', strEqG, PTR.HASH)
|
|
1852
1963
|
ctx.core.stdlib['__hash_get_local_h'] = genLookupStrictPrehashed('__hash_get_local_h', MAP_ENTRY, strEqG, PTR.HASH)
|