-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathlayout-kinds.js
More file actions
1111 lines (1077 loc) · 78.7 KB
/
Copy pathlayout-kinds.js
File metadata and controls
1111 lines (1077 loc) · 78.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* Heap-kind registry — COMPACT EXECUTABLE metadata only (audit-#16 registry
* finding, .work/research.md §Heap-kind registry Slice 4: production dist
* cost fix). This is the per-tag authority every consumer of a NaN-boxed
* value's KIND derives from — the composition point for carrier boxing ×
* region relocation. What's here is exactly what production READS: enums,
* numbers, short symbolic strings, no prose.
*
* PRODUCTION-CONSUMED: module/core.js's $__eq and module/collection.js's
* $__same_value_zero/$__map_hash import eqIdentityChain/
* sameValueZeroIdentityChain/mapHashStringArm/mapHashBigintArm below —
* every byte here ships in dist/jz.js. Slice 3 (2026-08-08) put this table
* on that path; Slice 4 (this split) is the fix for what Slice 3's own
* landing measured: the ORIGINAL single-file registry carried full per-kind
* prose (allocShape, childPointers, forwarding, interopDecode, typeofArm
* descriptions, cross-consumer findings writeups) in the SAME object the
* generators iterate, and esbuild's minifier only strips JS comments —
* string-literal PROPERTY VALUES survive verbatim, so that prose rode into
* dist/jz.js (+19,613B) and, transitively through the generated WAT text
* size, dist/jz.wasm (+60,511B). The prose itself, plus the FINDINGS array,
* now lives in layout-kinds-doc.js, which imports and EXTENDS this table —
* nothing here is duplicated there. See that file's header for the doc-side
* rationale and test/layout-kinds.js for the shadow checks (import both).
*
* LEAF MODULE — imports only layout.js (the err-codes.js pattern: safe for
* `jz/interop` and tests without pulling the compiler).
*/
import { PTR, LAYOUT, ATOM, STR_INTERN_BIT, STR_HCACHE_BIT } from './layout.js'
// Collection entry strides (module/collection.js) — duplicated here rather than
// imported, matching layout-kinds-doc.js's OWN precedent for the same three
// constants (its header comment: "not re-exported from layout.js today...
// duplicated so this doc module stays a leaf"; layout-kinds.js's leaf-module
// contract is identical — importing module/collection.js would pull the whole
// compiler in). Used here for real (interpolated into the region-arm generators'
// WAT text, not just prose) — keep in sync with module/collection.js's
// SET_ENTRY/MAP_ENTRY/LANE if those ever change; test/layout-kinds.js's shadow
// checks cover drift the same way they already do for the compact table.
const SET_ENTRY = 16 // [hash i64 @0][elem f64 @8]
const MAP_ENTRY = 24 // [hash i64 @0][key f64 @8][value f64 @16]
const LANE = 4 // normal output; self-compile compact profile passes lane=0
// regionArmSetMap's durable short-circuit (.work/research.md §Region arena —
// regionArmSetMap's durable short-circuit): tags whose $__region_copy_rec arm
// actually MOVES the heap block (and therefore changes the value's raw NaN-box
// bits — tag+offset) when the value is ephemeral this round. module/
// collection.js's $__map_hash dereferences PAYLOAD content for STRING/BIGINT
// (hash stable regardless of address — KIND_REGISTRY identity:'content') and
// never even sees NUMBER/ATOM/EXTERNAL move (KIND_REGISTRY relocate:
// 'immediate' — value/exact-bits identity, no heap block to move); every
// OTHER kind hashes the raw bits verbatim, so ITS hash changes iff its own
// address changes. `(1 << tag) & KEY_MOVABLE_MASK` mirrors layout.js's own
// FORWARDING_MASK idiom (same shl+and-membership shape, different tag set).
const KEY_MOVABLE_MASK = (1 << PTR.ARRAY) | (1 << PTR.BUFFER) | (1 << PTR.TYPED) |
(1 << PTR.OBJECT) | (1 << PTR.HASH) | (1 << PTR.SET) | (1 << PTR.MAP) | (1 << PTR.CLOSURE)
/**
* @typedef {Object} KindEntry
* @property {number|null} tag PTR.* (layout.js) or null for the two tagless kinds (NUMBER, and ATOM
* sub-rows share PTR.ATOM=0 with a distinguishing `aux`).
* @property {number|string|null} aux Short aux-semantics symbol: a reserved constant (0, ATOM.*), or a
* short tag naming what the aux bits carry (e.g. 'schemaId'). Full prose is
* layout-kinds-doc.js's `auxNote`.
* @property {'value'|'content'|'pointer-bits'|'exact-bits'} identity Identity-arm family this kind's
* ==/===/Set-Map keying belongs to. Full prose is doc's `identityNote`.
* @property {{kind:'content', order:number}} [identityArm] STRING/BIGINT only — `order` fixes this
* kind's position in the generated content-identity dispatch chain below
* (checked in ascending order; the tags are mutually exclusive so this is a
* byte-match constraint on generated text, not a soundness one).
* @property {'none'|'slots(len@-8)'|'schema-slots(aux)+sidecar'|'hash-entries(kv)'|'hash-entries(elem)'|
* 'env(aux-arity)'|'buffer-edge(raw-i32)'} children __region_copy_rec's tracer input: which
* payload slots hold boxed children (the generic recursion target) vs a raw,
* non-boxed edge (TYPED view's bufferRootOff) vs none (leaf). Full prose is
* doc's `childPointers`.
* @property {'copy'|'copy-forward'|'rebuild'|'value-relocate'|'copy-rebase'|'immediate'|'env-relocate'} relocate
* Heap-kind registry Slice 2 (.work/research.md §Heap-kind registry): how
* __region_copy_rec moves this kind across a region boundary — a DISTINCT
* axis from GROWTH forwarding (FORWARDING_MASK, layout.js): 'copy' leaf
* bytes (no children to rewrite); 'copy-forward' relocate-with-forward-stub,
* recursing into boxed children (durable receivers walk in place instead,
* memo'd at their own address); 'rebuild' reconstruct via __coll_order+
* reinsert (bucket position is a function of KEY BITS, which change on
* relocation — SET/MAP) UNLESS a durable short-circuit applies (table itself
* durable AND every occupied key's hash is invariant this round — see
* regionArmSetMap's own doc), in which case entries are value-patched in
* place instead, no rebuild; 'value-relocate' bucket-preserving
* (KEYS are content-hashed STRINGs, invariant under relocation — only each
* slot's VALUE recurses — HASH, via __region_relocate_props); 'copy-rebase'
* relocate the block AND rewrite a raw (non-boxed) child edge through the
* child's own new address (TYPED view → BUFFER); 'immediate' passthrough,
* no wasm-heap block to move (ATOM/NUMBER, and EXTERNAL — an index into a
* HOST table, not a wasm offset); 'env-relocate' — CLOSURE: env slot COUNT
* and per-slot boxed/raw MODE come from the `$__closure_env_len`/
* `$__closure_env_mask` side table (funcIdx-keyed, built in src/wat/
* assemble.js from facts module/function.js's ctx.closure.make captures at
* its own env-allocation site — not recoverable from a bare CLOSURE box at
* runtime any other way, since `aux` carries the function-table index, not
* the arity).
*/
/** @type {Record<string, KindEntry>} */
export const KIND_REGISTRY = {
NUMBER: { tag: null, aux: null, identity: 'value', children: 'none', relocate: 'immediate' },
STRING: { tag: PTR.STRING, aux: null, identity: 'content', identityArm: { kind: 'content', order: 1 }, children: 'none', relocate: 'copy' },
ARRAY: { tag: PTR.ARRAY, aux: null, identity: 'pointer-bits', children: 'slots(len@-8)', relocate: 'copy-forward' },
OBJECT: { tag: PTR.OBJECT, aux: 'schemaId', identity: 'pointer-bits', children: 'schema-slots(aux)+sidecar', relocate: 'copy-forward' },
HASH: { tag: PTR.HASH, aux: 0, identity: 'pointer-bits', children: 'hash-entries(kv)', relocate: 'value-relocate' },
SET: { tag: PTR.SET, aux: 0, identity: 'pointer-bits', children: 'hash-entries(elem)', relocate: 'rebuild' },
MAP: { tag: PTR.MAP, aux: 0, identity: 'pointer-bits', children: 'hash-entries(kv)', relocate: 'rebuild' },
TYPED: { tag: PTR.TYPED, aux: 'elemTypeCode', identity: 'pointer-bits', children: 'buffer-edge(raw-i32)', relocate: 'copy-rebase' },
BUFFER: { tag: PTR.BUFFER, aux: 0, identity: 'pointer-bits', children: 'none', relocate: 'copy' },
CLOSURE: { tag: PTR.CLOSURE, aux: 'fnTableIndex', identity: 'pointer-bits', children: 'env(funcIdx→len/mask table)', relocate: 'env-relocate' },
EXTERNAL: { tag: PTR.EXTERNAL, aux: 'reserved', identity: 'pointer-bits', children: 'none', relocate: 'immediate' },
BIGINT: { tag: PTR.BIGINT, aux: 0, identity: 'content', identityArm: { kind: 'content', order: 0 }, children: 'none', relocate: 'copy' },
'ATOM.NULL': { tag: PTR.ATOM, aux: ATOM.NULL, identity: 'exact-bits', children: 'none', relocate: 'immediate' },
'ATOM.UNDEFINED': { tag: PTR.ATOM, aux: ATOM.UNDEF, identity: 'exact-bits', children: 'none', relocate: 'immediate' },
'ATOM.BOOLEAN': { tag: PTR.ATOM, aux: `${ATOM.FALSE}|${ATOM.TRUE}`, identity: 'exact-bits', children: 'none', relocate: 'immediate' },
'ATOM.SYMBOL': { tag: PTR.ATOM, aux: 'symbolId', identity: 'exact-bits', children: 'none', relocate: 'immediate' },
}
// ============================================================================
// __region_copy_rec arm generation (Heap-kind registry Slice 2, .work/
// research.md §Heap-kind registry / §Region arena). Every KIND_REGISTRY row's
// `relocate` column above names the STRATEGY; the functions below are the
// EXECUTABLE arms implementing each strategy for module/core.js's
// __region_copy_rec (the region-arena Cheney-copy tracer). Not a single
// generic per-strategy template (rejected for the identical reason Slice 4's
// own header already documents for the eq-identity generators: OBJECT's
// dyn-props sidecar hazard, TYPED's raw-edge rebase, and HASH's bucket-
// stable-key shortcut are each a REAL, individually-shaped mechanism, not
// interchangeable instances of one pattern) — hand-authored, guarded
// functions per kind, composed in dispatch order by regionCopyRecBody().
//
// BIGINT/STRING/ARRAY/SET+MAP are EXTRACTED VERBATIM from the pre-Slice-2
// hand-written __region_copy_rec (module/core.js, git history) — byte-
// identity with that original text is the gate (test/layout-kinds.js pins
// it) before the hand-written switch retires in favor of calling
// regionCopyRecBody(). OBJECT/HASH/TYPED/BUFFER/EXTERNAL are NEWLY authored
// this slice, modeled on the closest existing precedent (OBJECT mirrors
// ARRAY's durable/ephemeral split + __obj_clone's schema-length/static-
// segment guards; HASH delegates to the memo-hardened __region_relocate_props
// — the SAME helper the ARRAY/OBJECT dyn-props sidecar already uses, since a
// bare HASH value is physically identical to a sidecar HASH; TYPED rebases
// its view descriptor's raw bufferRootOff edge through a recursive
// __region_copy_rec call on a synthesized BUFFER box, mirroring
// __sclone_rec's TYPED view arm; BUFFER mirrors __sclone_rec's BUFFER arm
// with a memo added, since — unlike structuredClone — region relocation
// must preserve the "same .buffer" identity multiple views may share).
// CLOSURE (region arena FRONT-BOUNDARY forcing case, .work/research.md
// §Region arena — the front boundary's own wall: "give CLOSURE a real
// region-copy arm — needs a capture-count/env-length side table") gets a
// real arm too: env slot count + per-slot boxed/raw mode come from the
// `$__closure_env_len`/`$__closure_env_mask` side table (funcIdx-keyed,
// src/wat/assemble.js), sourced from facts module/function.js's
// ctx.closure.make already computes at its own env-allocation site — see
// FINDINGS['region-forwarding'] (layout-kinds-doc.js) for the now-RESOLVED
// history (OBJECT/HASH/TYPED/BUFFER/EXTERNAL landed Slice 2; CLOSURE here).
// ============================================================================
/** BIGINT's region arm — verbatim (module/core.js, pre-Slice-2). Flat 8-byte
* payload cell, no header, no children: durable short-circuit / memo / fresh
* copy-with-delta, same shape as STRING's heap-block case below. */
export function regionArmBigint() {
return `(if (i32.eq (local.get $t) (i32.const ${PTR.BIGINT}))
(then
(local.set $off (call $__ptr_offset (local.get $bits)))
(if (i32.lt_u (local.get $off) (local.get $mark)) (then (return (local.get $v))))
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (f64.reinterpret_i64 (local.get $hit)))))
(local.set $newOff (call $__alloc (i32.const 8)))
(i64.store (local.get $newOff) (i64.load (local.get $off)))
(local.set $out (call $__mkptr (i32.const ${PTR.BIGINT}) (i32.const 0) (i32.sub (local.get $newOff) (local.get $delta))))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(return (local.get $out))))`
}
/** STRING's region arm. SSO is immediate; SLICE views are out of scope
* (unreachable — the region program never produces one); plain heap
* strings never forward (module/string.js invariant) so the raw offset
* mask is always canonical.
*
* HCACHE header fix (region-arena front-boundary hunt, .work/research.md
* §Region arena): module/string.js allocates a heap-built (non-SSO,
* STR_HCACHE_BIT) string with an 8-byte `[hash u32][len u32]` header, and
* layout.js's own STR_HCACHE_BIT doc says the lazy-cache design is
* "Sound because heap strings never relocate and die with their arena" —
* an invariant true before this region arm existed and false the instant
* it does. The prior version here allocated/copied only a bare 4-byte
* `[len]` header for EVERY ephemeral string, silently dropping the hash-
* cache word for any HCACHE string (which is most non-trivial runtime-
* built strings, e.g. every prepareModule renameFunc mangled name —
* `${prefix}$${name}` concatenation): the new location's -8 slot is left
* as whatever byte happens to precede it in the bump arena, and the next
* `__str_hash` on that string reads garbage there — either a bogus
* "cached" hash directly, or (the observed failure) a false miss that
* falls through into reading -4 as a length despite it not being where
* this call expects it, walking the FNV loop off the end of memory
* (root-caused via a trap-frame decompile plus a worktree-only debug-
* global probe on `$__str_hash`'s own inputs, the SW-hunt method). Fix: give an HCACHE
* string its real 8-byte header at the new address too, RESETTING the
* cache to 0 (the documented "uncomputed" sentinel — byte-FNV clamps to
* ≥2 so 0 stays unambiguous) instead of copying whatever the old cache
* held. Sound, not a hack: 0 is the exact state a freshly bump-extended
* HCACHE string already starts from, and module/string.js's own in-place
* mutators already reset the cell to 0 on any content change — this is
* one more legitimate "uncomputed" transition, costing one lazy recompute
* on next hash, never a wrong answer. STR_INTERN_BIT also carries a -8
* cached hash (layout.js doc) but is unaffected: every INTERN pointer
* resolves to the static string pool (module/string.js's own intern
* lookup returns the STATIC candidate's offset), which is always below
* `$__heap_start` and therefore always durable (`off < mark`) — it can
* never reach the ephemeral branch below to begin with. */
export function regionArmString() {
return `(if (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
(then
(local.set $aux (call $__ptr_aux (local.get $bits)))
;; SSO: immediate, no separate heap block
(if (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT})) (then (return (local.get $v))))
;; SLICE view (aliases a parent's bytes, no owned storage of its own): out of scope
(if (i32.and (local.get $aux) (i32.const ${LAYOUT.SLICE_BIT})) (then (unreachable)))
;; STRING never forwards (module/string.js invariant) — raw offset is always canonical
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const ${LAYOUT.OFFSET_MASK}))))
(if (i32.lt_u (local.get $off) (local.get $mark)) (then (return (local.get $v))))
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (f64.reinterpret_i64 (local.get $hit)))))
(local.set $len (i32.load (i32.sub (local.get $off) (i32.const 4))))
(if (i32.and (local.get $aux) (i32.const ${STR_HCACHE_BIT}))
(then
(local.set $newOff (i32.add (call $__alloc (i32.add (i32.const 8) (local.get $len))) (i32.const 8)))
(i32.store (i32.sub (local.get $newOff) (i32.const 8)) (i32.const 0))
(i32.store (i32.sub (local.get $newOff) (i32.const 4)) (local.get $len)))
(else
(local.set $newOff (i32.add (call $__alloc (i32.add (i32.const 4) (local.get $len))) (i32.const 4)))
(i32.store (i32.sub (local.get $newOff) (i32.const 4)) (local.get $len))))
(memory.copy (local.get $newOff) (local.get $off) (local.get $len))
(local.set $out (call $__mkptr (i32.const ${PTR.STRING}) (local.get $aux) (i32.sub (local.get $newOff) (local.get $delta))))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(return (local.get $out))))`
}
/** ARRAY's region arm — verbatim (module/core.js, pre-Slice-2), parametrized
* only over `hasDynProps` (was `ctx.scope.globals.has('__dyn_props')` read
* directly — layout-kinds.js stays ctx-free, so the caller resolves the
* flag and passes it in; the WAT text this produces is unchanged either
* way). Durable arrays walk in place (own address never changes); ephemeral
* arrays relocate fresh with a forward stub left at the old site. Both
* branches additionally migrate the off-16 dyn-props sidecar when present
* (kernel-oracle dvnested-mechanism O2/O3 regression fix, already landed). */
export function regionArmArray({ hasDynProps }) {
const durableDynProps = !hasDynProps ? '' : `
(local.set $oldProps (f64.load (i32.sub (local.get $off) (i32.const 16))))
(local.set $oldProps (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $oldProps)) (i64.const -2))))
(local.set $propsF (f64.const 0))
(if (i32.eq
(i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $oldProps)) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
(i32.const ${PTR.HASH}))
(then (local.set $propsF (local.get $oldProps)))
(else
(if (f64.ne (global.get $__dyn_props) (f64.const 0))
(then
(local.set $hit (call $__ihash_get_local (i64.reinterpret_f64 (global.get $__dyn_props)) (i64.reinterpret_f64 (f64.convert_i32_s (local.get $off)))))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (local.set $propsF (f64.reinterpret_i64 (local.get $hit)))))))))
(if (f64.ne (local.get $propsF) (f64.const 0))
(then
(local.set $propsF (call $__region_relocate_props (local.get $propsF) (local.get $memo) (local.get $mark) (local.get $delta)))
(if (i32.eq
(i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $oldProps)) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
(i32.const ${PTR.HASH}))
(then
;; was inline: __region_relocate_props may itself have moved the
;; props container (if it was ephemeral) — write the (possibly
;; new) pointer back to this STABLE off-16 slot.
(f64.store (i32.sub (local.get $off) (i32.const 16)) (local.get $propsF)))
(else
;; was already in $__dyn_props keyed by this stable $off — refile
;; the (possibly-relocated) value under the SAME key.
(local.set $dpRoot (f64.reinterpret_i64 (call $__ihash_set_local
(i64.reinterpret_f64 (global.get $__dyn_props))
(i64.reinterpret_f64 (f64.convert_i32_s (local.get $off)))
(i64.reinterpret_f64 (local.get $propsF)))))
(global.set $__dyn_props (local.get $dpRoot))
(global.set $__enumc_off (i32.const 0))))))
`
const ephemeralDynProps = !hasDynProps ? '' : `
(local.set $newFinal (i32.sub (local.get $newOff) (local.get $delta)))
(local.set $oldProps (f64.load (i32.sub (local.get $off) (i32.const 16))))
(local.set $oldProps (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $oldProps)) (i64.const -2))))
(local.set $propsF (f64.const 0))
(if (i32.eq
(i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $oldProps)) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
(i32.const ${PTR.HASH}))
(then (local.set $propsF (local.get $oldProps)))
(else
;; not inline — an earlier grow/shift/region-round may already have
;; filed it in $__dyn_props keyed by the array's OLD (still-valid-to-
;; read-right-now) offset.
(if (f64.ne (global.get $__dyn_props) (f64.const 0))
(then
(local.set $hit (call $__ihash_get_local (i64.reinterpret_f64 (global.get $__dyn_props)) (i64.reinterpret_f64 (f64.convert_i32_s (local.get $off)))))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (local.set $propsF (f64.reinterpret_i64 (local.get $hit)))))))))
(if (f64.ne (local.get $propsF) (f64.const 0))
(then
(local.set $propsF (call $__region_relocate_props (local.get $propsF) (local.get $memo) (local.get $mark) (local.get $delta)))
(local.set $dpRoot (global.get $__dyn_props))
(if (f64.eq (local.get $dpRoot) (f64.const 0)) (then (local.set $dpRoot (call $__hash_new))))
(local.set $dpRoot (f64.reinterpret_i64 (call $__ihash_set_local
(i64.reinterpret_f64 (local.get $dpRoot))
(i64.reinterpret_f64 (f64.convert_i32_s (local.get $newFinal)))
(i64.reinterpret_f64 (local.get $propsF)))))
(global.set $__dyn_props (local.get $dpRoot))
(global.set $__enumc_off (i32.const 0))
(i64.store (i32.sub (local.get $newOff) (i32.const 16)) (i64.const -1))))
`
return `(if (i32.eq (local.get $t) (i32.const ${PTR.ARRAY}))
(then
(local.set $off (call $__ptr_offset (local.get $bits)))
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (f64.reinterpret_i64 (local.get $hit)))))
(local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
(if (i32.lt_u (local.get $off) (local.get $mark))
(then
;; Durable container — never relocated (its own block stays put forever) —
;; but a durable array can still hold a slot written THIS round (e.g. a
;; compiler-internal registry array durable arrays only ever get PUSHED
;; into, not rebuilt), referencing non-durable data that would otherwise be
;; silently reclaimed by the closing rewind. Walk in place (no relocation of
;; the container itself — memo it at its OWN address — but recurse into
;; every slot and write back whatever comes out, exactly as durable_slot_log
;; recognizes "durable receiver, ephemeral payload" as the hazard needing a
;; write, except here the payload survives via relocation instead of dying).
(local.set $out (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (local.get $off)))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(block $dd (loop $dl
(br_if $dd (i32.ge_s (local.get $i) (local.get $len)))
(local.set $slot (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))
(f64.store (local.get $slot)
(call $__region_copy_rec (f64.load (local.get $slot)) (local.get $memo) (local.get $mark) (local.get $delta)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $dl)))
;; Same "durable receiver, ephemeral payload" hazard applies to the
;; dyn-props sidecar itself, not just element slots: the CONTAINER's
;; own address never changes (durable), so no re-keying is needed, but
;; whatever it points to (inline at off-16, or already filed in
;; $__dyn_props keyed by this stable $off) can still be ephemeral.
${durableDynProps}
(return (local.get $out))))
(local.set $newOff (call $__alloc_hdr (local.get $len) (local.get $len)))
(local.set $out (call $__mkptr (i32.const ${PTR.ARRAY}) (i32.const 0) (i32.sub (local.get $newOff) (local.get $delta))))
;; memo BEFORE recursing into elements — cycles / diamond sharing terminate on revisit
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(block $ad (loop $al
(br_if $ad (i32.ge_s (local.get $i) (local.get $len)))
(local.set $slot (i32.add (local.get $newOff) (i32.shl (local.get $i) (i32.const 3))))
(f64.store (local.get $slot)
(call $__region_copy_rec (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))
(local.get $memo) (local.get $mark) (local.get $delta)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $al)))
;; ARRAY dyn-props migration (audit finding, kernel-oracle dvnested-mechanism
;; O2/O3 regression root cause): the ORIGINAL comment below this function
;; ("watr's own AST/bookkeeping never attaches dynamic properties to its
;; internal arrays") was WRONG — src/compile/index.js's emitFunc stamps
;; fn.cseLoadBases = new Set(...) directly onto the compiled func-node
;; ARRAY during emission (src/optimize/index.js cseScalarLoad's whitelist),
;; and that node IS part of the region root (ast, bundled into region_exit's
;; root by watr's runRounds patch). An ARRAY's dyn-props sidecar lives EITHER
;; inline at off-16 (a HASH pointer, PTR.HASH-tagged) or, once any prior
;; grow/shift/durable-fallthrough/region-round has migrated it, in the
;; global $__dyn_props table keyed by the array's CURRENT offset
;; (module/array.js headerPropsCopyIR/headerPropsToGlobalIR/maybeDynMoveIR —
;; the exact mechanism arrGrow/arrShift already use to survive their OWN
;; relocation). Whichever form it's currently in, find the props-hash
;; pointer, relocate ITS OWN CONTENTS (__region_relocate_props — a bare
;; pointer copy would leave whatever it points to, e.g. cseLoadBases's
;; Set, unreachable from the region root and silently reclaimed), then
;; ALWAYS re-file it into $__dyn_props keyed by $newFinal (the array's
;; FINAL post-region_exit address: memory.copy hasn't landed the bytes
;; yet, so $newOff itself is a T-relative STAGING address, not a valid
;; $__dyn_props key — the SAME $out/$outPhys distinction the SET/MAP
;; branch above already makes). Gated on $__dyn_props existing at all
;; (ctx.scope.globals.has check in this function's deps() entry above) —
;; a build with no array/object dynamic-property support anywhere skips
;; this block entirely.
${ephemeralDynProps}
;; NO old-site forwarding stub (boundary-arithmetic audit, window B —
;; .work/research.md §Region arena: this function's own CALLER,
;; __region_exit, closes with a memory.copy(mark, T, size) — that copy
;; physically overwrites every byte in [mark, mark+size) with the
;; compacted survivors BEFORE any consumer outside this traversal can
;; possibly read a just-written stub there, and the round after that
;; one starts allocating fresh churn from the new heap top, overwriting
;; whatever stub bytes landed in [mark+size, T) the instant that space
;; is reused — a write with no reachable reader, in EVERY case, not a
;; probabilistic one (this was the target-pass-ablation "reshuffle"
;; mechanism: different pass orderings change how much of the dead
;; zone gets clobbered before a stray external reference — if one ever
;; existed — got a chance to chase it, reshuffling WHICH corpus rows
;; happened to trap, never fixing the underlying wall). Every reference
;; reachable from the region root is healed the honest way instead —
;; directly, by this very function returning $out and rewriting each
;; parent slot with it as the walk descends (already done above) — so
;; no chase is needed for anything region_exit is actually responsible
;; for. A holder OUTSIDE the root (watr's runRounds passes exactly
;; [ast, dirty, snapshots] as root, and drains every other known
;; module-scope scratch global before calling in — src/optimize.js)
;; is a root-completeness bug in the CALLER's registration, not
;; something an in-place stub could have fixed anyway (it never
;; survived long enough to be read).
(return (local.get $out))))`
}
/** SET/MAP's region arm. Bucket position is a function of the KEY's hash
* (__map_hash, module/collection.js) — a relocated key's bits change its
* bucket, so in general this arm rebuilds via __coll_order+reinsert
* (below), exactly like the pre-Slice-2 hand-written version.
*
* Durable short-circuit (.work/research.md §Region arena — regionArmSetMap's
* durable short-circuit, the finisher named by the bb493138/e854a8a7
* session pair): when the TABLE itself is durable (`off < mark` — created a
* prior region round, never grown/rebuilt this one, so its own address is
* fixed) AND every OCCUPIED key's hash is invariant across this round's
* relocation, the bucket layout provably cannot have changed — skip the
* rebuild entirely and patch entries in place, exactly the technique
* `__region_relocate_props`'s own durable branch (module/core.js) already
* uses for HASH: walk the full `$cap` slots, relocate each occupied KEY (a
* no-op unless it's an ephemeral content-hashed STRING/BIGINT — safe
* either way, its hash is unaffected either way) and, for MAP, its VALUE,
* writing both back in place. No `__alloc_hdr_n`, no `__coll_order`
* gather, no reinsertion, no rehash.
*
* A key's hash is invariant this round unless it is BOTH (a) a kind whose
* `$__region_copy_rec` arm can actually relocate it (`KEY_MOVABLE_MASK`
* above — content-hashed STRING/BIGINT and immediate NUMBER/ATOM/EXTERNAL
* are excluded, matching `$__map_hash`'s own two-arm split: it dereferences
* STRING/BIGINT payloads, never their address) AND (b) itself ephemeral
* (`off >= mark` — a durable key's own address never changes this round
* either, by the very same argument one level up). The scan below is
* READ-ONLY (no mutation) specifically so a table with even one such
* unstable key can fall straight through to the unconditional rebuild with
* zero partial state to unwind — a fallback after partial in-place
* mutation would risk exactly the memo double-visitation hazard
* `__region_relocate_props`'s own idempotency-self-map fix closed for a
* DIFFERENT case (re-presenting an already-relocated, not-yet-physically-
* landed T-relative staging address as fresh input): this branch never
* produces one — `$out` here is always a durable, already-landed address,
* never a staging one — but the scan-before-mutate structure keeps that
* true by construction rather than by argument.
*
* Once a Map/Set's keys are ALL durable (the steady state `ctx.plans`/
* `ctx.funcs` — pointer-keyed by durable `FunctionPlan`/registry records —
* reach after their first compaction), every later exit that reaches them
* takes this path: the full-table rebuild these two large, monotonically-
* growing Maps drove on EVERY exit collapses to a linear value-patch. */
export function regionArmSetMap({ lane = LANE } = {}) {
return `(if (i32.or (i32.eq (local.get $t) (i32.const ${PTR.SET})) (i32.eq (local.get $t) (i32.const ${PTR.MAP})))
(then
(local.set $off (call $__ptr_offset (local.get $bits)))
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (f64.reinterpret_i64 (local.get $hit)))))
(local.set $stride (select (i32.const ${MAP_ENTRY}) (i32.const ${SET_ENTRY}) (i32.eq (local.get $t) (i32.const ${PTR.MAP}))))
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
(if (i32.lt_u (local.get $off) (local.get $mark))
(then
;; Read-only scan: any occupied key whose kind can relocate (KEY_MOVABLE_MASK)
;; AND is itself ephemeral (>= mark) makes the WHOLE table's bucket layout
;; unstable — fall through to the rebuild below, no mutation has happened yet.
(local.set $stable (i32.const 1))
(local.set $i (i32.const 0))
(block $sd (loop $sl
(br_if $sd (i32.ge_s (local.get $i) (local.get $cap)))
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $i) (local.get $stride))))
(if (i64.ne (i64.load (local.get $slot)) (i64.const 0))
(then
(local.set $keyF (f64.load (i32.add (local.get $slot) (i32.const 8))))
;; tag bits are only meaningful on a real NaN-boxed value — a plain
;; finite NUMBER key can alias mantissa bits onto the type slot
;; ($__map_hash's own gating comment, module/collection.js).
(if (i32.and (f64.ne (local.get $keyF) (local.get $keyF))
(i32.ne (i32.and
(i32.shl (i32.const 1) (call $__ptr_type (i64.reinterpret_f64 (local.get $keyF))))
(i32.const ${KEY_MOVABLE_MASK})) (i32.const 0)))
(then
(local.set $keyOff (call $__ptr_offset (i64.reinterpret_f64 (local.get $keyF))))
(if (i32.ge_u (local.get $keyOff) (local.get $mark))
(then (local.set $stable (i32.const 0)) (br $sd)))))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $sl)))
(if (local.get $stable)
(then
(local.set $out (call $__mkptr (local.get $t) (i32.const 0) (local.get $off)))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(local.set $i (i32.const 0))
(block $vd (loop $vl
(br_if $vd (i32.ge_s (local.get $i) (local.get $cap)))
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $i) (local.get $stride))))
(if (i64.ne (i64.load (local.get $slot)) (i64.const 0))
(then
(f64.store (i32.add (local.get $slot) (i32.const 8))
(call $__region_copy_rec (f64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $memo) (local.get $mark) (local.get $delta)))
(if (i32.eq (local.get $t) (i32.const ${PTR.MAP}))
(then (f64.store (i32.add (local.get $slot) (i32.const 16))
(call $__region_copy_rec (f64.load (i32.add (local.get $slot) (i32.const 16))) (local.get $memo) (local.get $mark) (local.get $delta)))))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $vl)))
(return (local.get $out))))))
;; $i reset (audit fplan-2026-08-17, .work/research.md §CompileSession
;; forensic — "FunctionPlan missing for m6_parse$parse"): the
;; stability scan above (when entered — table durable) shares this
;; same $i local and, on finding an unstable key, branches out of the
;; scan EARLY, leaving $i frozen at that break's raw slot index (0..
;; cap-1, table layout order — NOT 0, NOT $cap). Falling through here
;; means either the table was durable+unstable (scan broke early, $i
;; mid-table) or fully durable+stable (scan ran to completion, $i ==
;; $cap) — never reset for the CODE BELOW, which reuses $i as its OWN
;; loop index into $ord (the insertion-ORDER array __coll_order
;; gathers). A non-zero inherited $i skips $ord[0..$i-1] — the
;; OLDEST-published survivors, unrelated to the raw slot the scan
;; happened to break on — silently dropping them from the rebuilt
;; table (confirmed empirically: a durable table with enough churn
;; to cross __region_exit's own adaptive skip threshold, several
;; durable entries, and one ephemeral pointer-typed entry whose raw
;; slot lands past index 0 rebuilds EMPTY — $i ends up >= $n, so the
;; loop below never executes at all). Reset unconditionally — correct
;; whether the scan ran to completion, broke early, or never ran (the
;; table was ephemeral to begin with, off >= $mark, and $i was never
;; touched by this arm at all).
(local.set $i (i32.const 0))
(local.set $newOff (call $__alloc_hdr_n (i32.const 0) (local.get $cap) (i32.add (local.get $stride) (i32.const ${lane}))))
;; Two addresses for the SAME new table: $outPhys (physical, T-relative — the
;; only form valid to DEREFERENCE right now, since the memmove down to mark
;; hasn't happened yet) drives __map_set/__set_add's OWN internal __ptr_offset
;; below; $out (logical, delta-adjusted) is the value returned/memoized — never
;; dereferenced until after region_exit's closing memory.copy lands it for real.
(local.set $outPhys (call $__mkptr (local.get $t) (i32.const 0) (local.get $newOff)))
(local.set $out (call $__mkptr (local.get $t) (i32.const 0) (i32.sub (local.get $newOff) (local.get $delta))))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
;; walk the source in insertion order (__coll_order), like __sclone_rec's SET/MAP
;; branch — inserting into a fresh cap-sized table never grows, so $outPhys stays canonical,
;; PROVIDED $n is the real live count. Chained-region-round fix (.work/research.md §Region
;; arena, __coll_order/$__dyn_props chain-round defect): $n used to be read from the
;; table's own header count word (i32.load(off-8)) BEFORE ever calling __coll_order — the
;; ONE call site in this codebase that violated __coll_order's own documented contract
;; ("the header and the real gathered count are NOT guaranteed to agree... every caller
;; MUST read $__coll_order_n") — every sibling caller (__sclone_rec, __region_exit's own
;; $__dyn_props rebuild, every genLookup/genUpsertGrow iteration site in
;; module/collection.js) reads $__coll_order_n AFTER the call instead. A header/real-count
;; divergence here doesn't just under/over-count for THIS table — it feeds genUpsertStrictPrehashed
;; (__set_add_h/__map_set_h), which has NO grow path at all ("inserting into a fresh
;; cap-sized table never grows" — true only when $n is the genuine live count, since a
;; real n < cap guarantees open-addressed probing terminates by the pigeonhole principle,
;; independent of hash-bucket clustering from relocated keys' changed bits). An inflated
;; $n reads past __coll_order's own gathered $ord entries (uninitialized bump-allocator
;; bytes decoded as bogus "slot" pointers); a table that genuinely fills under a wrong $n
;; drives __zomb_scan's documented "falls back to slot 0... which the 75%-load grow makes
;; unreachable" escape hatch, which unconditionally increments the header count even
;; though it overwrote (not added) an entry — inflating the REBUILT table's own header for
;; the NEXT round to inherit and compound. Every ADDITIONAL region round is another
;; unconditional rebuild of every reachable Set/Map (this arm has no durable short-circuit
;; — see the comment above), so more rounds mean more chances for the divergence to first
;; appear and then compound round over round — exactly the "any additional region round"
;; trigger and the "round N confuses round N+2" composition the task named. Fixed by
;; reading $n from $__coll_order_n, AFTER the call, matching every other caller in this
;; codebase — no special-casing, just the documented contract finally honored here too.
(local.set $ord (call $__coll_order (local.get $off) (local.get $cap) (local.get $stride)))
(local.set $n (global.get $__coll_order_n))
(block $cd (loop $cl
(br_if $cd (i32.ge_s (local.get $i) (local.get $n)))
(local.set $slot (i32.load (i32.add (local.get $ord) (i32.shl (local.get $i) (i32.const 2)))))
;; Region-arena rebuild fix (.work/research.md §Region arena, front-
;; boundary hunt — full mechanism on __set_add_h/__map_set_h,
;; module/collection.js): a relocated key's stored bits are the
;; LOGICAL (post-move) address — correct to STORE, but its target
;; memory only physically exists at the PRE-move address until
;; region_exit's closing memory.copy. $__map_hash's STRING/BIGINT
;; arms dereference the key's payload (content hash); every other
;; kind hashes the raw bits with no dereference. So: content-hashed
;; keys hash the ORIGINAL bits (content is copied byte-for-byte,
;; identical either way, and the original address stays valid to
;; read all the way to region_exit's own last instruction); every
;; other key hashes the RELOCATED bits (bits-based, must match
;; what a future lookup — which only ever sees the stored, final
;; bits — will compute; no dereference, so the not-yet-moved
;; address is never touched). Then insert with the precomputed
;; hash via the STRICT prehashed sibling (skips $__map_set/
;; $__set_add's OWN internal re-hash of the — for content kinds,
;; still-premature — relocated pointer).
(local.set $propsF (f64.load (i32.add (local.get $slot) (i32.const 8))))
(local.set $newFinal (i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $propsF)) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
(local.set $oldProps (call $__region_copy_rec (local.get $propsF) (local.get $memo) (local.get $mark) (local.get $delta)))
(local.set $len (call $__map_hash (i64.reinterpret_f64
(select (local.get $propsF) (local.get $oldProps)
(i32.or (i32.eq (local.get $newFinal) (i32.const ${PTR.STRING})) (i32.eq (local.get $newFinal) (i32.const ${PTR.BIGINT})))))))
(if (i32.eq (local.get $t) (i32.const ${PTR.MAP}))
(then (drop (call $__map_set_h (i64.reinterpret_f64 (local.get $outPhys))
(i64.reinterpret_f64 (local.get $oldProps))
(local.get $len)
(i64.reinterpret_f64 (call $__region_copy_rec (f64.load (i32.add (local.get $slot) (i32.const 16))) (local.get $memo) (local.get $mark) (local.get $delta))))))
(else (drop (call $__set_add_h (i64.reinterpret_f64 (local.get $outPhys))
(i64.reinterpret_f64 (local.get $oldProps))
(local.get $len)))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $cl)))
;; NO old-site forwarding stub — boundary-arithmetic audit, window B
;; (see regionArmArray's matching comment above for the full
;; mechanism: __region_exit's closing memory.copy destroys any stub
;; written here before an external reader could ever chase it).
(return (local.get $out))))`
}
/** OBJECT's region arm — NEW (Slice 2). Mirrors ARRAY's durable/ephemeral
* split exactly (OBJECT shares ARRAY's own header shape via __alloc_hdr —
* layout-kinds-doc.js OBJECT.allocShape) with two differences: (1) slot
* COUNT comes from the schema table (aux = schemaId indexes it), the same
* __schema_tbl[sid] → __len lookup __obj_clone/__sclone_rec already use,
* not a header length word (OBJECT's header len word is unused — schema
* slot count is compile-time-fixed per shape, never grows); (2) the durable
* branch's off-16 dyn-props peek is guarded by `off >= $__heap_start`
* (__obj_clone's own guard, mirrored here) — a STATIC-SEGMENT object
* literal (compile-time constant, off < heap base) has NO header at all,
* unlike every ARRAY this function ever sees (region roots are always
* heap-resident compiler-internal data, never a bare static array literal
* reached as a schema-less OBJECT would be). */
export function regionArmObject({ hasDynProps }) {
const durableDynProps = !hasDynProps ? '' : `
(if (i32.ge_u (local.get $off) (global.get $__heap_start))
(then
(local.set $oldProps (f64.load (i32.sub (local.get $off) (i32.const 16))))
(local.set $oldProps (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $oldProps)) (i64.const -2))))
(local.set $propsF (f64.const 0))
(if (i32.eq
(i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $oldProps)) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
(i32.const ${PTR.HASH}))
(then (local.set $propsF (local.get $oldProps)))
(else
(if (f64.ne (global.get $__dyn_props) (f64.const 0))
(then
(local.set $hit (call $__ihash_get_local (i64.reinterpret_f64 (global.get $__dyn_props)) (i64.reinterpret_f64 (f64.convert_i32_s (local.get $off)))))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (local.set $propsF (f64.reinterpret_i64 (local.get $hit)))))))))
(if (f64.ne (local.get $propsF) (f64.const 0))
(then
(local.set $propsF (call $__region_relocate_props (local.get $propsF) (local.get $memo) (local.get $mark) (local.get $delta)))
(if (i32.eq
(i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $oldProps)) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
(i32.const ${PTR.HASH}))
(then
(f64.store (i32.sub (local.get $off) (i32.const 16)) (local.get $propsF)))
(else
(local.set $dpRoot (f64.reinterpret_i64 (call $__ihash_set_local
(i64.reinterpret_f64 (global.get $__dyn_props))
(i64.reinterpret_f64 (f64.convert_i32_s (local.get $off)))
(i64.reinterpret_f64 (local.get $propsF)))))
(global.set $__dyn_props (local.get $dpRoot))
(global.set $__enumc_off (i32.const 0))))))))
`
// NOT ARRAY's migrate-to-$__dyn_props-with-a--1-sentinel pattern (module/
// collection.js __dyn_set's ARRAY arm treats a non-zero, non-HASH-tagged
// off-16 word — e.g. -1 — as "look in the global table instead", falling
// through past its inline check). OBJECT's __dyn_set arm has NO such
// fallback (module/collection.js, the "OBJECT: heap-allocated AND ephemeral
// ... writes propsPtr directly at off-16" comment): it treats ANY non-zero
// off-16 word as an already-valid HASH pointer, unconditionally, with no
// tag check. Writing ARRAY's -1 sentinel there would misdirect the next
// dyn-prop access into dereferencing that -1 bit pattern as a real pointer
// — confirmed live (native repro: an ephemeral `{}` given a dynamic key
// then read back after a region boundary, `memory access out of bounds`).
// So OBJECT's ephemeral relocation keeps props INLINE at the object's NEW
// off-16, unconditionally — never migrates to $__dyn_props (matching
// __dyn_set's own policy: an ephemeral OBJECT's props are ALWAYS inline;
// only a static-segment or durable-heap receiver ever uses the global
// table, and this branch is for a freshly-relocated EPHEMERAL object).
const ephemeralDynProps = !hasDynProps ? '' : `
(local.set $oldProps (f64.load (i32.sub (local.get $off) (i32.const 16))))
(local.set $oldProps (f64.reinterpret_i64 (i64.and (i64.reinterpret_f64 (local.get $oldProps)) (i64.const -2))))
(if (i32.eq
(i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $oldProps)) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
(i32.const ${PTR.HASH}))
(then
(f64.store (i32.sub (local.get $newOff) (i32.const 16))
(call $__region_relocate_props (local.get $oldProps) (local.get $memo) (local.get $mark) (local.get $delta)))))
`
return `(if (i32.eq (local.get $t) (i32.const ${PTR.OBJECT}))
(then
(local.set $off (call $__ptr_offset (local.get $bits)))
(local.set $aux (call $__ptr_aux (local.get $bits)))
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (f64.reinterpret_i64 (local.get $hit)))))
(local.set $n (i32.const 0))
(if (i32.ne (global.get $__schema_tbl) (i32.const 0))
(then (local.set $n (call $__len
(i64.load (i32.add (global.get $__schema_tbl) (i32.shl (local.get $aux) (i32.const 3))))))))
(if (i32.lt_u (local.get $off) (local.get $mark))
(then
;; Durable — schema slots never grow (fixed count once allocated), so the
;; container's own address never changes; walk in place exactly like
;; ARRAY's durable branch (a durable object can still hold an ephemeral
;; slot value, e.g. a freshly-built child written into it this round).
(local.set $out (call $__mkptr (i32.const ${PTR.OBJECT}) (local.get $aux) (local.get $off)))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(block $od (loop $ol
(br_if $od (i32.ge_s (local.get $i) (local.get $n)))
(local.set $slot (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))
(f64.store (local.get $slot)
(call $__region_copy_rec (f64.load (local.get $slot)) (local.get $memo) (local.get $mark) (local.get $delta)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $ol)))
;; Same off-16 dyn-props sidecar shape/hazard as ARRAY's (layout-kinds-doc.js
;; OBJECT.childPointers) — guarded against static-segment objects, which have
;; no header at all (__obj_clone's own guard, mirrored here).
${durableDynProps}
(return (local.get $out))))
(local.set $newOff (call $__alloc_hdr (i32.const 0) (i32.add (local.get $n) (i32.eqz (local.get $n)))))
(local.set $out (call $__mkptr (i32.const ${PTR.OBJECT}) (local.get $aux) (i32.sub (local.get $newOff) (local.get $delta))))
;; memo BEFORE recursing into slots — cycles / diamond sharing terminate on revisit
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(block $pd (loop $pl
(br_if $pd (i32.ge_s (local.get $i) (local.get $n)))
(local.set $slot (i32.add (local.get $newOff) (i32.shl (local.get $i) (i32.const 3))))
(f64.store (local.get $slot)
(call $__region_copy_rec (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))
(local.get $memo) (local.get $mark) (local.get $delta)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $pl)))
;; ephemeral OBJECTs are always heap-resident (never static-segment), so no
;; $__heap_start guard is needed here (mirrors ARRAY's ephemeral branch).
${ephemeralDynProps}
;; NO old-site forwarding stub (boundary-arithmetic audit, window A):
;; PTR.OBJECT is not a FORWARDING_MASK member (layout.js) — __ptr_offset's
;; chase never even inspects an OBJECT-tagged pointer's header for one, so
;; a stub written here could never be read by ANY consumer, ever, chase or
;; no chase — a dead write regardless of the closing-memcpy timing that
;; kills every OTHER kind's stub too (window B — see regionArmArray).
;; Consistent with TYPED/BUFFER, neither of which ever wrote one either.
(return (local.get $out))))`
}
/** HASH's region arm — NEW (Slice 2). A bare PTR.HASH value is physically
* identical to the dyn-props sidecar case __region_relocate_props already
* handles (same header/entry shape, same content-hashed-STRING-key bucket-
* stability argument — layout-kinds-doc.js HASH.forwarding) — delegate to
* it directly rather than duplicating the durable-walk/ephemeral-bulk-copy
* logic a second time. */
export function regionArmHash() {
return `(if (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
(then (return (call $__region_relocate_props (local.get $v) (local.get $memo) (local.get $mark) (local.get $delta)))))`
}
/** TYPED's region arm — NEW (Slice 2). OWNED storage is a leaf raw-byte copy
* (byteLen at header -8, no boxed children — mirrors BUFFER's own arm). A
* VIEW's 16B descriptor holds bufferRootOff as a RAW i32 edge (not a boxed
* f64 slot — layout-kinds-doc.js TYPED.childPointers' "structurally
* different edge shape" note): rebased by recursing __region_copy_rec on a
* SYNTHESIZED BUFFER box for the root (mirrors __sclone_rec's TYPED view
* arm), then re-deriving dataOff from the (possibly-relocated) root's new
* offset plus the original byte delta into it. */
export function regionArmTyped() {
return `(if (i32.eq (local.get $t) (i32.const ${PTR.TYPED}))
(then
(local.set $off (call $__ptr_offset (local.get $bits)))
(local.set $aux (call $__ptr_aux (local.get $bits)))
(if (i32.and (local.get $aux) (i32.const 8))
(then
;; VIEW: 16B descriptor [0]byteLen [4]dataOff [8]rootOff [12]reserved.
(if (i32.lt_u (local.get $off) (local.get $mark))
(then
;; durable descriptor (stable address) — its root buffer may still be
;; ephemeral (this round); rebase in place if it moved. Ordering audit
;; (.work/research.md §Region arena): memo-guard the durable branch
;; itself, same fix class as __region_relocate_props's durable branch
;; below (both were the only two "walks/mutates in place, no memo"
;; arms in the whole dispatch — ARRAY/OBJECT's durable branches memo
;; themselves before this). Without it, a diamond-shared durable view
;; (the SAME descriptor object reachable via two root paths) would
;; re-read off+8 on a second visit AFTER the first visit already
;; overwrote it with the FINAL (delta-adjusted, not-yet-physically-
;; valid) buffer address — re-deriving $oldRoot from that final value
;; and recursing into __region_copy_rec on a bogus synthesized BUFFER
;; box, corrupting state exactly like the HASH-durable case this audit
;; found and fixed natively.
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (local.get $v))))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $v))))
(local.set $oldRoot (i32.load (i32.add (local.get $off) (i32.const 8))))
(local.set $rootBox (call $__region_copy_rec
(call $__mkptr (i32.const ${PTR.BUFFER}) (i32.const 0) (local.get $oldRoot))
(local.get $memo) (local.get $mark) (local.get $delta)))
(local.set $newRoot (call $__ptr_offset (i64.reinterpret_f64 (local.get $rootBox))))
(i32.store (i32.add (local.get $off) (i32.const 4))
(i32.add (local.get $newRoot) (i32.sub (i32.load (i32.add (local.get $off) (i32.const 4))) (local.get $oldRoot))))
(i32.store (i32.add (local.get $off) (i32.const 8)) (local.get $newRoot))
(return (local.get $v))))
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (f64.reinterpret_i64 (local.get $hit)))))
(local.set $oldRoot (i32.load (i32.add (local.get $off) (i32.const 8))))
(local.set $rootBox (call $__region_copy_rec
(call $__mkptr (i32.const ${PTR.BUFFER}) (i32.const 0) (local.get $oldRoot))
(local.get $memo) (local.get $mark) (local.get $delta)))
(local.set $newRoot (call $__ptr_offset (i64.reinterpret_f64 (local.get $rootBox))))
(local.set $newOff (call $__alloc (i32.const 16)))
(i32.store (local.get $newOff) (i32.load (local.get $off)))
(i32.store (i32.add (local.get $newOff) (i32.const 4))
(i32.add (local.get $newRoot) (i32.sub (i32.load (i32.add (local.get $off) (i32.const 4))) (local.get $oldRoot))))
(i32.store (i32.add (local.get $newOff) (i32.const 8)) (local.get $newRoot))
(i32.store (i32.add (local.get $newOff) (i32.const 12)) (i32.load (i32.add (local.get $off) (i32.const 12))))
(local.set $out (call $__mkptr (i32.const ${PTR.TYPED}) (local.get $aux) (i32.sub (local.get $newOff) (local.get $delta))))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(return (local.get $out)))
(else
;; OWNED: leaf raw bytes, header byteLen at -8, no boxed children.
(if (i32.lt_u (local.get $off) (local.get $mark)) (then (return (local.get $v))))
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (f64.reinterpret_i64 (local.get $hit)))))
(local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
(local.set $newOff (call $__alloc_hdr_n (local.get $len) (local.get $len) (i32.const 1)))
(memory.copy (local.get $newOff) (local.get $off) (local.get $len))
(local.set $out (call $__mkptr (i32.const ${PTR.TYPED}) (local.get $aux) (i32.sub (local.get $newOff) (local.get $delta))))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(return (local.get $out))))))`
}
/** BUFFER's region arm — NEW (Slice 2). Leaf raw-byte copy (byteLen at
* header -8, no boxed children), memo'd — unlike structuredClone (which
* intentionally makes an independent copy), region relocation MUST
* preserve "same .buffer" identity across multiple typed-array views that
* legitimately share one BUFFER (TYPED's view arm above relies on this: two
* views over the SAME root, relocated in the same round, must land on the
* SAME new BUFFER address). */
export function regionArmBuffer() {
return `(if (i32.eq (local.get $t) (i32.const ${PTR.BUFFER}))
(then
(local.set $off (call $__ptr_offset (local.get $bits)))
(if (i32.lt_u (local.get $off) (local.get $mark)) (then (return (local.get $v))))
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (f64.reinterpret_i64 (local.get $hit)))))
(local.set $len (i32.load (i32.sub (local.get $off) (i32.const 8))))
(local.set $newOff (call $__alloc_hdr_n (local.get $len) (local.get $len) (i32.const 1)))
(memory.copy (local.get $newOff) (local.get $off) (local.get $len))
(local.set $out (call $__mkptr (i32.const ${PTR.BUFFER}) (i32.const 0) (i32.sub (local.get $newOff) (local.get $delta))))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(return (local.get $out))))`
}
/** EXTERNAL's region arm — NEW (Slice 2). Immediate passthrough: the offset
* is an INDEX into the host-side mem._extMap table, not a wasm-heap
* address — there is nothing for the wasm side to relocate at all
* (layout-kinds-doc.js EXTERNAL.allocShape). */
export function regionArmExternal() {
return `(if (i32.eq (local.get $t) (i32.const ${PTR.EXTERNAL})) (then (return (local.get $v))))`
}
/** CLOSURE's region arm — the front-boundary forcing case (.work/research.md
* §Region arena), a real relocation now instead of a trap. Shape mirrors
* OBJECT's durable/ephemeral split (the env block, like OBJECT's schema
* slots, is a fixed-count-once-allocated run with no separate indirect
* backing pointer) with two differences: (1) slot COUNT and per-slot
* boxed/raw MODE come from the `$__closure_env_len`/`$__closure_env_mask`
* side table (funcIdx = aux indexes it — module/function.js's
* ctx.closure.make captures both facts at its own env-allocation site,
* materialized here by src/wat/assemble.js, exactly mirroring
* `$__schema_tbl`'s "build once, index by a stable small int" shape); (2) a
* zero-capture closure's offset is the LITERAL immediate `0` (no heap block
* at all — module/function.js `mkPtrIR(PTR.CLOSURE, tableIdx, 0)`), passed
* through unchanged before ever touching `$memo` (bits never change across
* any relocation, so this is trivially identity-safe, mirroring the
* preamble's ATOM arm). A cell-mode slot (mask bit set — the boxed/mutable-
* capture path, module/function.js's `ctx.func.boxed`) holds a RAW i32
* pointer to a shared, independently-heap-allocated 8-byte payload cell
* (`${T}cell_${name}`) — NOT a NaN-boxed f64 — so it can't route through
* `__region_copy_rec`'s own f64 dispatch; `__region_relocate_cell` (module/
* core.js) is the dedicated helper, memoized by a synthetic (never-NaN,
* never colliding with a real heap pointer's bits) f64 key so a cell shared
* by two closures (the whole point of the boxed-capture mechanism) lands on
* the SAME new address from both env slots — breaking that would silently
* un-alias a mutable capture across the boundary. */
export function regionArmClosure() {
return `(if (i32.eq (local.get $t) (i32.const ${PTR.CLOSURE}))
(then
(local.set $off (call $__ptr_offset (local.get $bits)))
(local.set $aux (call $__ptr_aux (local.get $bits)))
;; zero-capture: no heap block, offset is the literal 0 sentinel — see doc above.
(if (i32.eqz (local.get $off)) (then (return (local.get $v))))
(local.set $hit (call $__region_memo_get (local.get $memo) (local.get $bits)))
(if (i32.eqz (call $__is_nullish (local.get $hit))) (then (return (f64.reinterpret_i64 (local.get $hit)))))
;; Side table absent is impossible once ANY real (non-zero-offset)
;; CLOSURE value reaches here — a program with zero closures never
;; constructs a PTR.CLOSURE box with a real heap block at all.
(if (i32.eqz (global.get $__closure_env_len)) (then (unreachable)))
(local.set $n (i32.load (i32.add (global.get $__closure_env_len) (i32.shl (local.get $aux) (i32.const 2)))))
;; >31 captures can't fit the i32 cell-mode bitmask (module/function.js's
;; own envCellMask cap — unobserved on every measured corpus, .work/
;; closure-plan-design.md §1.5 tops out at 27 captures) — a NAMED trap
;; for that one case, not a silent truncation of which slots are pointers.
(if (i32.gt_s (local.get $n) (i32.const 32)) (then (unreachable)))
(local.set $cellMask (i32.load (i32.add (global.get $__closure_env_mask) (i32.shl (local.get $aux) (i32.const 2)))))
(if (i32.lt_u (local.get $off) (local.get $mark))
(then
;; Durable env block — exclusively owned by this ONE closure box
;; (unlike a boxed cell, which CAN be shared — see
;; __region_relocate_cell), so its address never changes; memo
;; itself, walk slots in place, mirroring ARRAY/OBJECT's own
;; durable branches.
(local.set $out (call $__mkptr (i32.const ${PTR.CLOSURE}) (local.get $aux) (local.get $off)))
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(block $cld (loop $cll
(br_if $cld (i32.ge_s (local.get $i) (local.get $n)))
(local.set $slot (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))
(if (i32.and (i32.shr_u (local.get $cellMask) (local.get $i)) (i32.const 1))
(then (i32.store (local.get $slot)
(call $__region_relocate_cell (i32.load (local.get $slot)) (local.get $memo) (local.get $mark) (local.get $delta))))
(else (f64.store (local.get $slot)
(call $__region_copy_rec (f64.load (local.get $slot)) (local.get $memo) (local.get $mark) (local.get $delta)))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $cll)))
(return (local.get $out))))
(local.set $newOff (call $__alloc (i32.shl (local.get $n) (i32.const 3))))
(local.set $out (call $__mkptr (i32.const ${PTR.CLOSURE}) (local.get $aux) (i32.sub (local.get $newOff) (local.get $delta))))
;; memo BEFORE recursing into slots — cycles / diamond sharing terminate on revisit
(drop (call $__region_memo_set (local.get $memo) (local.get $bits) (i64.reinterpret_f64 (local.get $out))))
(block $ced (loop $cel
(br_if $ced (i32.ge_s (local.get $i) (local.get $n)))
(local.set $slot (i32.add (local.get $newOff) (i32.shl (local.get $i) (i32.const 3))))
(if (i32.and (i32.shr_u (local.get $cellMask) (local.get $i)) (i32.const 1))
(then (i32.store (local.get $slot)
(call $__region_relocate_cell (i32.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))) (local.get $memo) (local.get $mark) (local.get $delta))))
(else (f64.store (local.get $slot)
(call $__region_copy_rec (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))) (local.get $memo) (local.get $mark) (local.get $delta)))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $cel)))
(return (local.get $out))))`
}
/** Extended locals declaration for __region_copy_rec (Slice 2 adds TYPED's
* view-rebase temporaries; every other new arm reuses locals the pre-
* Slice-2 function already declared). */
export function regionCopyRecLocals() {
return `(local $bits i64) (local $t i32) (local $off i32) (local $aux i32) (local $hit i64) (local $out f64)
(local $newOff i32) (local $n i32) (local $i i32) (local $slot i32) (local $len i32) (local $cap i32)
(local $stride i32) (local $ord i32) (local $outPhys f64) (local $oldProps f64) (local $dpRoot f64) (local $newFinal i32) (local $propsF f64)
(local $oldRoot i32) (local $rootBox f64) (local $newRoot i32) (local $cellMask i32)
(local $stable i32) (local $keyF f64) (local $keyOff i32)`
}
/** Preamble — verbatim (module/core.js, pre-Slice-2) plus ONE new immediate
* check (EXTERNAL — Slice 2, same "no wasm heap block at all" shape as
* ATOM, so it sits right beside it). */
export function regionCopyRecPreamble() {
return `;; ordinary numbers (incl. +/-Infinity) are immediate
(if (f64.eq (local.get $v) (local.get $v)) (then (return (local.get $v))))
(local.set $bits (i64.reinterpret_f64 (local.get $v)))
;; negative-NaN bit patterns are numeric NaN, never boxes (__sclone_rec precedent)
(if (i64.eq (i64.and (local.get $bits) (i64.const 0xFFF0000000000000)) (i64.const 0xFFF0000000000000))
(then (return (local.get $v))))
(local.set $t (call $__ptr_type (local.get $bits)))
;; ATOM (null/undefined/bool/canonical-NaN): immediate, passes through
(if (i32.eq (local.get $t) (i32.const ${PTR.ATOM})) (then (return (local.get $v))))
${regionArmExternal()}
`
}
/** Composes the full __region_copy_rec body (Slice 2 + the CLOSURE arm):
* preamble, then every kind's arm in KIND_REGISTRY's own declared order
* (skipping ATOM/EXTERNAL/NUMBER, folded into the preamble already), then
* the trailing backstop — reachable ONLY by a tag value no PTR.*
* enumerates; every real heap kind, CLOSURE included, now has its own arm.
* `hasDynProps` is the caller-resolved `ctx.scope.globals.has('__dyn_props')`
* flag (layout-kinds.js stays ctx-free — see regionArmArray's doc). */
export function regionCopyRecBody({ hasDynProps, lane = LANE }) {
return ` ${regionCopyRecLocals()}
${regionCopyRecPreamble()}
${regionArmBigint()}
${regionArmString()}
${regionArmArray({ hasDynProps })}
${regionArmObject({ hasDynProps })}
${regionArmHash()}
${regionArmSetMap({ lane })}
${regionArmTyped()}
${regionArmBuffer()}
${regionArmClosure()}
;; any tag not one of the above is not a valid PTR.* value — impossible
;; by construction (every real heap kind now has an arm).
(unreachable))`
}
// ============================================================================
// Identity-dispatch arm generation (Heap-kind registry Slice 3, .work/
// research.md §Heap-kind registry — "3 $__eq/$__map_hash arms generated").
// module/core.js's $__eq and module/collection.js's $__same_value_zero/
// $__map_hash each hand-roll a tag-dispatch chain that special-cases the
// CONTENT-identity kinds — every other kind needs no arm at all, relying on
// the caller's own bit-equality fast path (pointer-bits identity IS bit
// equality). Which kinds get an arm, and in what order, is data: the
// `identityArm` column on KIND_REGISTRY.STRING/BIGINT above (every other