|
| 1 | +# Packed arena range preflight |
| 2 | + |
| 3 | +## Motivation |
| 4 | + |
| 5 | +The pushed typed-sweep amortization checkpoint `54714887` reduced repeated |
| 6 | +quarantine visits and global certificates, but a fresh loop-only hardware |
| 7 | +profile still measured 405.96 ns per `closures_upval` operation. The remaining |
| 8 | +free-run reconstruction path was the clearest isolated target: |
| 9 | + |
| 10 | +```text |
| 11 | +arena_clear_extent_range 51.42 ns/op |
| 12 | +arena_set_free_run 32.02 ns/op |
| 13 | +lj_arena_scan_free_runs 13.95 ns/op |
| 14 | +total 97.39 ns/op |
| 15 | +``` |
| 16 | + |
| 17 | +The first two functions repeatedly loaded root, recovery, destructor, READY, |
| 18 | +and lifetime state for every cell in a large free span. The same metadata is |
| 19 | +already stored in packed atomic planes, so this was observation overhead rather |
| 20 | +than useful semantic work. |
| 21 | + |
| 22 | +## Exact packed proof |
| 23 | + |
| 24 | +`arena_range_ownership_preflight()` now walks only the bitmap words intersected |
| 25 | +by the requested range. Existing exact converters map every nonzero root, |
| 26 | +recovery, and destructor state and every non-FREE lifetime nibble back to one |
| 27 | +bit per arena cell. Each word is intersected with a partial-range mask, so cells |
| 28 | +before or after the requested span cannot veto it. |
| 29 | + |
| 30 | +This is only an amortized form of the previous acquire observations. It does |
| 31 | +not authorize mutation, replace the owner/open-or-closed generation protocol, |
| 32 | +or remove any later validation. `arena_set_free_run()` still completes its |
| 33 | +entire first preflight before changing metadata, scrubs READY/cdata/destructor |
| 34 | +coverage, and calls the stricter `arena_clear_extent_range()` preflight again |
| 35 | +before removing interior boundaries. |
| 36 | + |
| 37 | +The two historical policies remain distinct: |
| 38 | + |
| 39 | +- `arena_set_free_run()` treats recovery ownership as an ordinary fail-closed |
| 40 | + veto and returns without mutation; |
| 41 | +- `arena_clear_extent_range()` retains its corruption assertion and abort when |
| 42 | + recovery is the first cell-ordered blocker. |
| 43 | + |
| 44 | +The cold fatal path also preserves the old diagnostic order inside a packed |
| 45 | +word. An ordinary blocker in a lower cell returns first; recovery at the same |
| 46 | +or an earlier cell remains fatal. Zero-length free-run publication is now |
| 47 | +rejected explicitly before end arithmetic or structural mutation. Other |
| 48 | +invalid bounds are rejected before an array access. |
| 49 | + |
| 50 | +READY deliberately differs between the two callers. An old typed READY bit |
| 51 | +vetoes direct extent removal, while free-run publication is allowed to scrub |
| 52 | +READY after lifetime FREE and zero destructor identity. Its second interior |
| 53 | +preflight then observes the scrubbed plane exactly as before. |
| 54 | + |
| 55 | +## Race and ordering argument |
| 56 | + |
| 57 | +The packed loads use the same acquire order class as the former per-cell |
| 58 | +accessors. Release publication of `block[]` changes and the atomic relaxed AND |
| 59 | +on concurrently markable `mark[]` are unchanged. A racing allocation, |
| 60 | +recovery, root, or remote-free publication is still governed by its lifetime |
| 61 | +claim and the surrounding arena generation. Grouping reads by word does not |
| 62 | +make a stale snapshot commit authority; later validation and the exact |
| 63 | +open/commit generation continue to reject a conflicting publisher. |
| 64 | + |
| 65 | +For a valid nonzero range, the new predicate is logically identical to the old |
| 66 | +one: |
| 67 | + |
| 68 | +- every root/recovery pair and lifetime nibble is converted exactly; |
| 69 | +- every destructor plane is ORed into cell geometry; |
| 70 | +- READY is included only for the caller which historically required it; and |
| 71 | +- plain arenas continue to omit the traversable-only lifetime plane. |
| 72 | + |
| 73 | +An independent concurrency/source audit found no unsafe-positive race or |
| 74 | +memory-order weakening after the recovery-policy, zero-length, and cold-order |
| 75 | +hardening. |
| 76 | + |
| 77 | +## Deterministic coverage |
| 78 | + |
| 79 | +An `LJ_ARENA_TEST_HELPERS` wrapper calls the actual static free-run function; |
| 80 | +it is absent from production builds and changes no public ABI. The focused |
| 81 | +arena-sweep fixture uses one 20-cell range spanning both packed lifetime |
| 82 | +positions 15/16 and bitmap positions 31/32. Whole-arena snapshots prove that: |
| 83 | + |
| 84 | +- zero-length and invalid-bound calls fail without mutation; |
| 85 | +- root, recovery-PENDING, destructor, and non-FREE lifetime blockers each fail |
| 86 | + without changing any arena byte; |
| 87 | +- recovery remains a nonfatal veto on the set-free path; |
| 88 | +- blockers immediately outside the partial range do not leak through its |
| 89 | + masks; and |
| 90 | +- a valid run scrubs READY/cdata, removes all interior block/mark boundaries, |
| 91 | + and publishes exactly one mark-only free-run start. |
| 92 | + |
| 93 | +The fixture compiles with `-Wall -Wextra -Werror` and passed. |
| 94 | + |
| 95 | +## Validation |
| 96 | + |
| 97 | +All requested forced-clean gates passed: |
| 98 | + |
| 99 | +- `m2_arena_sweep`, before and after the direct packed-boundary fixture; |
| 100 | +- `m2_arena_gcsweep`; |
| 101 | +- `m3_gc2_recovery` in normal and assertions/GC2-paranoia builds; and |
| 102 | +- `m3_gc2_paranoia`, including all C oracles, 509/509 JIT tests, and 387/387 |
| 103 | + no-JIT tests. |
| 104 | + |
| 105 | +Every configuration-changing run restored the default build. The only known |
| 106 | +compiler diagnostic was the pre-existing GCC inlining warning around |
| 107 | +`gc2_root_rescan_later`/`la_load32_acq`. |
| 108 | + |
| 109 | +## Performance |
| 110 | + |
| 111 | +Five fresh independent processes with the valid stopped-GC wrapper measured: |
| 112 | + |
| 113 | +```text |
| 114 | +current active: 319.30, 317.84, 318.53, 320.70, 323.47; median 319.30 ns/op |
| 115 | +current stopped: 94.83, 94.02, 95.16, 94.28, 95.99; median 94.83 ns/op |
| 116 | +stock active: 37.51, 38.45, 36.98, 39.65, 38.28; median 38.28 ns/op |
| 117 | +stock stopped: 18.35, 21.46, 21.04, 20.61, 21.70; median 21.04 ns/op |
| 118 | +``` |
| 119 | + |
| 120 | +The final zero-length/diagnostic hardening was performance-neutral in a |
| 121 | +three-process confirmation: 319.44 ns/op active and 94.14 ns/op stopped. |
| 122 | + |
| 123 | +Against pushed `54714887` (389.19 active, 113.31 stopped), the packed preflight |
| 124 | +improves active time by 17.96%, stopped time by 16.31%, and active-minus-stopped |
| 125 | +cost by 18.63%. It is 34.68% faster than the earlier 488.80 ns/op typed-body |
| 126 | +checkpoint. The main five-process medians remain 8.34x stock active and 4.51x |
| 127 | +stock stopped, so b1.2.0 is still performance-blocked. |
| 128 | + |
| 129 | +The next profile-directed tranche is an independently revalidated terminal |
| 130 | +FREE/FREEING word fast path in quarantine bitmap readiness/application, |
| 131 | +followed by all-free live-cell counting/adoption if the new profile still |
| 132 | +justifies it. |
0 commit comments