#8277 added a per-arena-block object-start bitmap to fix #8256. It is the right fix for that bug — an exact recorded allocation boundary is a real invariant where #8251's size == 24 was a heuristic a correct-size fabrication defeats. It shipped with its cost unmeasured, and I flagged that at merge time. The 2026-08-17 four-engine sweep now measures it.
The measurement
Both arms are clean sweeps on the quiet M1 mini, best-of-five interleaved, spreads 0.3–2.2%. Baseline is 38cf15336 (2026-08-15); current is 14468dcbc (2026-08-17).
Split by whether the workload allocates:
| workload class |
instructions retired, geomean |
rows |
| allocation-heavy |
+3.69% |
12 |
| everything else |
−48.3% |
7 |
The concentration matches the mechanism, and allocation-light rows inside the same group are flat — so this is not a broad slowdown that happens to touch allocation:
| benchmark |
wall Δ |
instructions Δ |
churn_alloc |
+8.4% |
+10.5% |
push_cls |
+8.7% |
+10.4% |
retain_wide |
+9.6% |
+8.7% |
retain_wide1 |
+9.3% |
+7.1% |
churn |
+8.2% |
+5.9% |
cycles |
+1.4% |
+1.8% |
shapes |
+1.7% |
-0.5% |
retain |
+1.8% |
-1.6% |
retain1 |
+1.9% |
-2.9% |
push_num |
+0.2% |
+0.6% |
Mechanism
lower_call/new_alloc.rs emits this on the inline fast path, per allocation:
load(current_data) · ptrtoint(raw) · ptrtoint(data) · sub · lshr · lshr · and
gep · load(bitmap) · gep · load(prior_starts) · shl · or · store
Three loads, one store, six ALU ops — and the bitmap word is on a different cache line from the object being allocated, so the real cost is plausibly a miss rather than the op count. There is also 1.5625% permanent metadata per arena block (one bit per 8-byte slot), which is RSS that never comes back.
Why this is worth work rather than acceptance
The project rule is that RSS and compute are both minimized, always, not traded. A 8–10% wall regression on the allocation path is the kind of cost that compounds: churn, churn_alloc, push_cls and retain_wide are the shapes real programs hit constantly.
Directions, cheapest first
- Stamp fewer bits. The classifier needs an exact boundary only for objects a fabricated header could impersonate.
GC_TYPE_SET (12) is already unreachable because it is not 8-aligned; if the reachable set is genuinely just Map-like fixed-layout types, the bitmap only needs stamping for those, not for every inline allocation.
- Hoist the block base.
current_data is reloaded per allocation. In a loop that allocates repeatedly into the same block it is invariant until the slow arm installs a new block — that is a phi, not a reload.
- Fold the bit-set into the bump. The slot index is derivable from the bump pointer the allocator already computed; the two
lshr plus and are recomputing what the allocation arithmetic knows.
- Byte map instead of bitmap. Trades 8× the metadata (12.5%) for dropping the read-modify-write to a single store — worse on the RSS axis, so probably the wrong trade here, but worth pricing.
Acceptance
Re-run the four-engine sweep and require the allocation-heavy geomean back under +1.0% instructions against 38cf15336, with no regression on the −48.3% cohort and no loss of the #8256 fixture (fabricated_map_rejection). Report instructions and peak RSS together.
Sweep evidence: gc-handoff/current-sweep-2026-08-17/ (RESULTS.md, results-clean.json).
#8277added a per-arena-block object-start bitmap to fix #8256. It is the right fix for that bug — an exact recorded allocation boundary is a real invariant where #8251'ssize == 24was a heuristic a correct-size fabrication defeats. It shipped with its cost unmeasured, and I flagged that at merge time. The 2026-08-17 four-engine sweep now measures it.The measurement
Both arms are clean sweeps on the quiet M1 mini, best-of-five interleaved, spreads 0.3–2.2%. Baseline is
38cf15336(2026-08-15); current is14468dcbc(2026-08-17).Split by whether the workload allocates:
The concentration matches the mechanism, and allocation-light rows inside the same group are flat — so this is not a broad slowdown that happens to touch allocation:
churn_allocpush_clsretain_wideretain_wide1churncyclesshapesretainretain1push_numMechanism
lower_call/new_alloc.rsemits this on the inline fast path, per allocation:Three loads, one store, six ALU ops — and the bitmap word is on a different cache line from the object being allocated, so the real cost is plausibly a miss rather than the op count. There is also 1.5625% permanent metadata per arena block (one bit per 8-byte slot), which is RSS that never comes back.
Why this is worth work rather than acceptance
The project rule is that RSS and compute are both minimized, always, not traded. A 8–10% wall regression on the allocation path is the kind of cost that compounds:
churn,churn_alloc,push_clsandretain_wideare the shapes real programs hit constantly.Directions, cheapest first
GC_TYPE_SET(12) is already unreachable because it is not 8-aligned; if the reachable set is genuinely just Map-like fixed-layout types, the bitmap only needs stamping for those, not for every inline allocation.current_datais reloaded per allocation. In a loop that allocates repeatedly into the same block it is invariant until the slow arm installs a new block — that is a phi, not a reload.lshrplusandare recomputing what the allocation arithmetic knows.Acceptance
Re-run the four-engine sweep and require the allocation-heavy geomean back under +1.0% instructions against
38cf15336, with no regression on the−48.3%cohort and no loss of the #8256 fixture (fabricated_map_rejection). Report instructions and peak RSS together.Sweep evidence:
gc-handoff/current-sweep-2026-08-17/(RESULTS.md,results-clean.json).