Bootstrap hotpath from engineering spec v0.1

Commit the spec and scaffold the system it describes:

- SPEC.md: the full v0.1 engineering spec
- crates/: trust-layer Rust workspace, unit-tested
  - bench-runner: interleaved ABAB scheduling with discarded warm-ups,
    deterministic bootstrap CIs over the ratio of medians,
    CI-lower-bound accept rule, env fingerprinting, and a CLI with
    compare + A/A self-test modes (A/A verified passing end-to-end)
  - diff-test: gate outcome model, sha256 corpus pin verifier that
    refuses on mismatch, per-target equivalence policy parser
    (byte-identical default, explicit fp-tolerance opt-in)
  - ledger: append-only SQLite attempt schema with UPDATE/DELETE
    refused by triggers, anti-double-attempt query
  - report: cores-saved and dollars/yr ROI math over speedup CIs,
    pricing.toml inputs
- playbook/: classes 1-7 with preconditions, procedure, verification
  notes, and known failure modes per class
- agent/: Python package skeleton — six-tool contract and prompting
  spine, harness IPC deferred to Phase 1
- config/: accept.toml (2% threshold, 30 runs/side), pricing.toml
- targets/, corpora/, results/calibration/: layout and policy docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcKPWQ3MQD4NdEHwar96u2
This commit is contained in:
Claude
2026-07-06 18:34:51 +00:00
parent 836d4c9208
commit 328aafae77
40 changed files with 2468 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
# Class 1 — Build configuration
Zero source changes. Always first: a large fraction of real-world CPU is
lost to shipping defaults.
## Preconditions
- Any CPU-bound target not already using an optimized release profile.
- Profile signature: flat-ish flamegraph with time spread across many
small frames (inlining starved); heavy call overhead into tiny leaf
functions; or a build that visibly lacks `-O2`/`--release`.
- Check before anything else: `opt-level`, `lto`, `codegen-units`,
`panic`, `target-cpu` (Rust); `-O`, `-march`, `-flto` (C/C++).
## Procedure (cheapest first)
1. Confirm release profile: `opt-level=3`, `panic=abort` where the target
permits, `codegen-units=1`.
2. `target-cpu=native` / `-march=native` — only when the deployment
fleet's ISA baseline is known; record the chosen baseline in the
hypothesis.
3. LTO: thin, then fat. Watch build-time cost; fat LTO on huge targets
can be a net loss for the engagement.
4. PGO: instrument with the golden-replay corpus as the training run,
rebuild with the profile. The corpus is representative by
construction.
5. BOLT (post-link layout) where the binary and toolchain permit.
## Verification notes
- Flags must not include any `-ffast-math`-class option — that's an
equivalence change, auto-routed to `needs-human-review` (SPEC §8).
- Byte-identical golden replay still applies: build flags occasionally
change FP contraction on some ISAs (`-march` enabling FMA); if golden
replay diverges on numeric output, stop — this class is not
equivalence-preserving for that target without a tolerance policy.
## Known failure modes
- `target-cpu=native` on the bench machine but not the fleet: a real win
that the customer cannot deploy. Pin the ISA baseline to the fleet.
- PGO trained on a non-representative workload overfits the benchmark —
hold out workloads (SPEC §11).
- Layout changes from LTO/BOLT interact with layout bias (§7): measured
wins here *especially* need the randomize-and-aggregate protocol.
+36
View File
@@ -0,0 +1,36 @@
# Class 2 — Allocator swap
Link-line only. With class 1, forms the "your 30% was a linker flag"
tier that is always exhausted before touching source.
## Preconditions
- Profile signature: `malloc`/`free`/`operator new` or allocator-internal
symbols (`_int_malloc`, `tcache_*`, page-fault handling) ≥5% of
exclusive samples; or high syscall time in `mmap`/`brk`; or
multithreaded target with allocator lock contention visible.
## Procedure
1. Rust: add `mimalloc` (then `jemallocator`) as `#[global_allocator]` —
two-line diff, still effectively link-level.
2. C/C++: `LD_PRELOAD` trial first (free experiment, no rebuild), then
proper linking of the winner.
3. Bench each candidate allocator separately; do not stack with other
changes.
## Verification notes
- Behavior-preserving by contract, so gates should pass trivially — but
run them all anyway: allocator swaps change address-space layout and
can unmask latent UB (use-after-free that "worked" under glibc). A
sanitizer failure here is a pre-existing target bug: record it,
route to `needs-human-review`, do not ship.
- Max RSS is captured per run: an allocator that wins time but doubles
memory is a trade-off the report must state, not hide.
## Known failure modes
- Wins on the bench box's core count that vanish (or invert) at the
fleet's concurrency level.
- `LD_PRELOAD` trial passing but static-link integration subtly failing
(symbol interposition differences) — re-run full gates on the final
link, not the trial.
- Fragmentation pathologies on long-running services that a short bench
window never sees; service-mode targets need soak evidence (Phase 4).
+36
View File
@@ -0,0 +1,36 @@
# Class 3 — Allocation churn
First source-touching class: reduce the *number* of allocations, not the
allocator servicing them.
## Preconditions
- Class 2 helped but allocator symbols still hot, or allocation shows up
as cache misses from pointer-chasing freshly allocated objects.
- Profile signature: allocation inside hot loops; `Vec`/`String`
reallocation churn; many short-lived objects of the same type.
## Procedure (cheapest first)
1. Hoist allocations out of loops; reuse buffers via `clear()` instead
of re-creation.
2. `Vec::with_capacity` / `reserve` where the size is known or bounded.
3. Small-vector types (`smallvec`, fixed inline buffers) for collections
that are almost always tiny — confirm the size distribution from the
profile or a counting instrument first.
4. Arena allocation (`bumpalo`, typed arenas) for phase-scoped object
graphs with a clear reset point.
## Verification notes
- Buffer reuse is where stale-data bugs come from: golden replay must
cover inputs of *decreasing* size across a session so a dirty buffer
would surface. Add such a sequence to the corpus if missing.
- Arenas change drop order; targets with observable `Drop` side effects
(logging, flushing) can diverge on golden replay — that divergence is
a real reject, not noise.
## Known failure modes
- `with_capacity` guesses that overshoot: RSS regression the wall-clock
number won't show — check the captured max RSS.
- smallvec inline sizes chosen by vibes: an inline buffer bigger than
the cache line it saves. Measure, don't guess.
- Reuse introducing borrow-checker contortions that force `unsafe` —
`unsafe` introduction routes to `needs-human-review` (SPEC §8).
+37
View File
@@ -0,0 +1,37 @@
# Class 4 — Data layout
Make the memory hierarchy work for the access pattern.
## Preconditions
- Profile signature: high cache-miss rate in `perf stat` (LLC misses per
instruction), hot loops whose flamegraph frames are memory-bound
(`perf` cycles concentrated on loads), low IPC (<1) on wide cores.
- For false sharing: multithreaded target where per-thread throughput
degrades as threads are added; `perf c2c` confirms.
## Procedure (cheapest first)
1. Struct field reordering / packing: kill padding, group hot fields on
one cache line, split hot/cold (`#[repr(C)]` review, cold-field
box-out).
2. AoS→SoA for loops that touch one field across many elements —
restructure the container, not the algorithm.
3. False-sharing fixes: pad or align per-thread state to 64 bytes
(`#[repr(align(64))]`, `crossbeam_utils::CachePadded`).
4. Cache blocking / loop tiling for nested loops over large matrices or
images; tile to L1/L2, verified empirically, not by datasheet.
## Verification notes
- Layout changes to any type that crosses FFI, serialization, or hashing
boundaries can change observable behavior (struct hash, serialized
bytes, memcmp-based equality) — golden replay catches this; check the
target for `#[repr]`-sensitive traits before patching.
- Differential fuzz the functions whose types changed, not just the
functions whose code changed.
## Known failure modes
- Reordering fields of a type that derives `Hash`/`Ord` used in ordered
iteration → output order changes → replay divergence (correct reject).
- AoS→SoA wins the target loop but loses every other access site; the
end-to-end number is the verdict, not the microbench.
- Alignment padding blowing up per-element size until the working set
falls out of cache — the cure worse than the disease.
+43
View File
@@ -0,0 +1,43 @@
# Class 5 — Algorithmic
Complexity class and primitive selection. Highest variance: the biggest
wins live here, and so do the most seductive wrong ideas.
## Preconditions
- Profile signature: one dominant frame that is *algorithm*, not memory
(high IPC, low miss rate, still slow); superlinear scaling of runtime
with input size; hot `contains`/`find` over linear structures; repeated
recomputation of pure functions on repeated arguments.
## Procedure (cheapest first)
1. Better std/library primitive: `HashMap` for linear scans, `sort_unstable`
over `sort`, `entry()` API over get-then-insert, buffered I/O,
`memchr`-class scanning.
2. Precomputation: hoist invariant work out of loops; build lookup tables
at startup when the domain is small.
3. Memoization for pure hot functions with high argument repeat rates —
confirm the repeat rate from a counting instrument before adding a
cache.
4. Complexity-class replacement (the real prize): O(n²) → O(n log n)
with a known-correct algorithm. Prefer a well-tested library
implementation over a hand-rolled one, always.
## Verification notes
- HashMap iteration order is not deterministic order: if results flow
from iteration order into output, replay diverges — either sort at the
boundary or reject the change.
- Memoization + interior mutability in multithreaded targets touches the
concurrency review tier (SPEC §8).
- Differential fuzz is load-bearing here: algorithmic replacements have
edge-case surfaces (empty input, duplicates, already-sorted, unicode)
where "equivalent" algorithms differ. 10k iterations minimum, seeded
with corpus-derived cases.
## Known failure modes
- Asymptotic win, constant-factor loss at real input sizes — the ledger
records the workload; the verdict is measured at that workload.
- Caches without bounds: memoization that trades a 10% speedup for
unbounded RSS growth. Check max RSS; bound the cache.
- "Equivalent" floating-point reformulations that aren't (different
rounding paths) — FP output changes route to the tolerance policy or
human review, never silently absorbed.
+44
View File
@@ -0,0 +1,44 @@
# Class 6 — SIMD
Autovectorization enablement first, intrinsics last (SPEC §4).
## Preconditions
- Profile signature: hot, trip-count-heavy inner loops over contiguous
numeric/byte data; scalar math or byte-wise processing in the top
frames; disassembly of the hot loop shows scalar ops where vector ops
are plausible.
## Procedure (cheapest first)
1. Read the optimizer's mind: check the hot loop's assembly (or
`-Cremark`/`-Rpass=loop-vectorize`) for *why* vectorization failed.
2. Remove the blocker, in order of cheapness:
- Bounds checks: restructure with slices/iterators or hoist a single
length check (`&x[..n]` pattern) — not `unsafe` indexing.
- Aliasing: split borrows, `split_at_mut`, pass slices instead of
overlapping raw pointers.
- Loop-carried dependencies: reassociate integer reductions, unroll
accumulators.
3. Widen the ISA baseline if class 1 hasn't already (`target-cpu`), since
SSE2-only baselines strangle the vectorizer.
4. `std::simd` / portable-SIMD explicit vectorization.
5. Raw intrinsics only when all of the above fail and the win is large;
isolate behind a scalar-fallback path selected at runtime.
## Verification notes
- **FP flags are a hard line**: any `-ffast-math`-class flag, FP
reassociation, or FMA-contraction change routes to
`needs-human-review` — no exceptions, even for a 2x win (SPEC §8).
Integer/byte SIMD has no such taint and can auto-accept.
- Intrinsics imply `unsafe` → automatic `needs-human-review` + MIRI
where feasible + differential fuzz against the scalar path with
adversarial lengths (0, 1, lane-1, lane, lane+1, unaligned offsets).
## Known failure modes
- Tail handling: the classic SIMD bug class. Fuzz lengths around lane
boundaries explicitly.
- Alignment assumptions that hold on the bench box and fault (or crawl)
elsewhere.
- Runtime feature detection done per-call in the hot loop, eating the
win; hoist detection to init.
- Integer reduction reassociation is safe; float reduction reassociation
changes results — do not confuse the two.
+43
View File
@@ -0,0 +1,43 @@
# Class 7 — Concurrency
Last in the order, highest risk. **Every diff in this class routes to
`needs-human-review`** (SPEC §8); the agent proposes, a human disposes.
## Preconditions
- Profile signature: multithreaded target where wall time ≫ ideal
(cpu-time / cores); lock symbols (`futex`, `pthread_mutex`,
`parking_lot`) hot; run-queue latency; per-thread throughput that
*drops* as threads are added. coz (Phase 3) is the right prioritizer
here — cycle profiles mislead on blocked time.
## Procedure (cheapest first)
1. Shrink critical sections: move allocation, I/O, and computation out
from under locks; drop guards early.
2. Reduce sharing: per-thread accumulation + merge at the end beats a
shared counter; sharding a hot map by key hash beats a fancier lock.
3. Right-size the primitive: `RwLock` for read-mostly, `OnceLock` for
init-once, atomics for simple counters *with explicitly justified
orderings* (start from `SeqCst`; relax only with a written argument).
4. Lock-free structures: last resort, library-only (crossbeam), never
hand-rolled, and only when the profile proves the lock is the
bottleneck after 1–3.
## Verification notes
- TSan gate is mandatory for this class, on both the upstream suite and
the golden replay run (SPEC §3.2).
- Thread-count sweep: gates and bench at 1, 2, N, and 2N threads —
concurrency bugs and wins are both thread-count-dependent.
- Nondeterministic output ordering (e.g. results gathered from threads)
must be canonicalized at the boundary or the change rejected; replay
equivalence is not negotiable to make a parallel win look clean.
## Known failure modes
- Benchmarks too short to hit the contention window: a race that fires
once per million ops passes 60s of fuzz. TSan + long soak, and still
route to a human.
- Relaxed atomics that are correct on x86's strong model and wrong on
ARM — orderings need an argument, not a test pass.
- Sharding that wins the benchmark's uniform key distribution and loses
production's skewed one — holdout workloads (SPEC §11).
- Deadlocks introduced by lock-order changes that no gate exercises;
the human reviewer checks lock ordering explicitly.
+25
View File
@@ -0,0 +1,25 @@
# Optimization playbook v0
Classes are tried in **strict order** (SPEC §4) — cheapest first. Classes
1–2 are the "your 30% was a linker flag" tier and are always exhausted
before any source change. Each class documents:
- **Preconditions** — the profile signature that suggests it applies
- **Procedure** — what to actually try, cheapest variant first
- **Verification notes** — gate interactions specific to this class
- **Known failure modes** — where this class produces fake wins or real bugs
| # | Class | Touches source? | Human review? |
|---|-------|-----------------|---------------|
| 1 | [Build configuration](01-build-config.md) | no | only PGO/BOLT pipeline changes |
| 2 | [Allocator swap](02-allocator.md) | link line only | no |
| 3 | [Allocation churn](03-allocation-churn.md) | yes | no |
| 4 | [Data layout](04-data-layout.md) | yes | no |
| 5 | [Algorithmic](05-algorithmic.md) | yes | no |
| 6 | [SIMD](06-simd.md) | yes | FP-flag changes: always |
| 7 | [Concurrency](07-concurrency.md) | yes | **always** |
The agent's prompting spine (SPEC §3.5): state the hypothesis before
patching; prefer the cheapest untried class; max two iterations per
rejected hypothesis; a rejection with a clean ledger row is a successful
outcome.