Section 01: Compile-Time ARC Statistics
Status: Not Started
Goal: Every AIMS pipeline pass reports before/after RC operation counts via tracing::info!, providing measurable feedback for optimization development. Running ORI_LOG=ori_arc=info ori build file.ori shows RC operations inserted, coalesced, and eliminated per function.
Context: LLVM’s ARC optimizer uses the STATISTIC() macro extensively — tracking NumRRs (retain/release pairs eliminated), NumNoops (no-op calls removed), and debug-only NumRetainsBeforeOpt/NumRetainsAfterOpt counts. Ori’s SynergyMetrics already tracks cross-dimensional evidence but lacks the most basic measurement: how many RC operations were emitted vs how many survived to final output. Without these numbers, optimization work in Sections 02-05 cannot prove its value.
Reference implementations:
- LLVM
llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp:159-175: Fine-grained per-pattern counters with debug-only before/after counts. Each counter maps to a distinct optimization rule. - Swift
swift/lib/SILOptimizer/ARC/ARCSequenceOpts.cpp:40: Single aggregateNumRefCountOpsRemoved. Simpler but less useful for debugging.
Depends on: None (pure additive instrumentation).
01.1 Extend SynergyMetrics with RC Operation Counts
File(s): compiler/ori_arc/src/aims/realize/metrics.rs
Add before/after RC operation counts to SynergyMetrics. These complement the existing cross-dimensional evidence tracking with the most fundamental measurement: raw RC operation volume.
-
Add fields to
SynergyMetrics:/// RC operations in input IR (before any AIMS processing). pub rc_ops_input: usize, /// RC operations after Phase 1 realization (post-emission, pre-coalesce). pub rc_ops_post_emission: usize, /// RC operations after coalescing. pub rc_ops_post_coalesce: usize, /// Barrier flushes during coalescing (total flush_all + flush_touched calls). pub barrier_flush_count: usize, /// Full-barrier flushes (flush_all at Apply/ApplyIndirect). pub barrier_flush_all_count: usize, -
Add
coalesce_reduction_percent()method:pub fn coalesce_reduction_percent(&self) -> f64 { if self.rc_ops_post_emission == 0 { return 0.0; } let reduced = self.rc_ops_post_emission.saturating_sub(self.rc_ops_post_coalesce); (reduced as f64 / self.rc_ops_post_emission as f64) * 100.0 } -
Update
report()to include new fields:tracing::info!( // ... existing fields ... rc_ops_input = self.rc_ops_input, rc_ops_post_emission = self.rc_ops_post_emission, rc_ops_post_coalesce = self.rc_ops_post_coalesce, coalesce_reduction = format_args!("{:.1}%", self.coalesce_reduction_percent()), barrier_flushes = self.barrier_flush_count, barrier_flush_all = self.barrier_flush_all_count, "AIMS synergy metrics" ); -
Update
merge()to include new fields (additive). -
Unit tests in
realize/tests.rs:- Test
coalesce_reduction_percent()with zero, partial, and full reduction - Test
merge()with new fields
- Test
-
/tpr-reviewpassed — independent review found no critical or major issues (or all findings triaged) -
/impl-hygiene-reviewpassed — hygiene review clean. MUST run AFTER/tpr-reviewis clean. -
Subsection close-out (01.1) — MANDATORY before starting the next subsection. Run
/improve-toolingretrospectively on THIS subsection’s debugging journey (per.claude/skills/improve-tooling/SKILL.md“Per-Subsection Workflow”): whichdiagnostics/scripts you ran, where you addeddbg!/tracingcalls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE/commit-pushusing a valid conventional-commit type (build(diagnostics): ... — surfaced by section-01.1 retrospective—build/test/chore/ci/docsare valid;tools(...)is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 01.1: no tooling gaps”. Update this subsection’sstatusin section frontmatter tocomplete. -
/sync-claudesection-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW. -
Repo hygiene check — run
diagnostics/repo-hygiene.sh --checkand clean any detected temp files.
01.2 Add Pipeline-Level Before/After Instrumentation
File(s): compiler/ori_arc/src/pipeline/aims_pipeline.rs, compiler/ori_arc/src/aims/realize/mod.rs
Count RC operations at each pipeline stage by scanning the IR instruction lists.
-
Reuse existing
count_rc_ops()frompipeline/rc_count/mod.rs:// Already exists in pipeline/rc_count/mod.rs: // pub(crate) fn count_rc_ops(func: &ArcFunction) -> RcOpCount // Returns RcOpCount { inc, dec } with inc summing batched counts. // Use .total() for the combined inc+dec count. -
Fix hygiene: eliminate duplicate
count_rc_ops()—realize/emit_unified.rs:242has a privatecount_rc_ops()that returnsusize(simple count without batching). The pipeline version inpipeline/rc_count/mod.rsreturnsRcOpCount { inc, dec }with batchedRcInccount handling (respectscount > 1). Replace the private copy inemit_unified.rswithcrate::pipeline::rc_count::count_rc_ops(func).total()and delete the private function. This fixes a LEAK:algorithmic-duplication finding. -
Instrument the pipeline — two separate sites:
Site 1:
pipeline/aims_pipeline.rs(per-function pipeline):- Before step 4 (
analyze_function):metrics.rc_ops_input = count_rc_ops(func).total()— this will be 0 for input IR (RC ops haven’t been emitted yet), serving as the baseline. - After step 5 (
realize_rc_reuse) returns: capturerc_ops_post_coalesceviacount_rc_ops(func).total(). Note:realize_rc_reuseinternally callsemit_rc_unifiedwhich callscoalesce_block_rc, so by the time step 5 returns, coalescing has already happened. This count IS the post-coalesce count, not pre-coalesce.
Site 2:
realize/emit_unified.rs(insideemit_rc_unified()):- Capture
rc_ops_post_emission(pre-coalesce) count BEFORE the Phase 3 coalescing loop at line ~106. After the hygiene fix above, usecrate::pipeline::rc_count::count_rc_ops(func).total(). - Return the pre-coalesce count from
emit_rc_unified()alongside the existing return tuple (add it as a 5th element or add it to theSynergyMetricsdirectly). - The coalescing loop at line ~107 then runs, after which
count_rc_ops(func).total()at line ~111 gives the post-coalesce count. - Both counts must reach
SynergyMetrics.
- Before step 4 (
-
Verify with:
ORI_LOG=ori_arc=info ori build tests/spec/traits/iterator/map.orishows per-function metrics.
Matrix testing:
- Types: int (scalar, no RC), str (heap), [int] (collection), Option<str> (enum+heap), closures
- Patterns: simple function, loop with RC, nested calls, COW mutation
Semantic pin: Test that counts str and [int] programs as having rc_ops_post_emission > 0 and int-only programs as having rc_ops_post_emission == 0.
Negative pin: Test that rc_ops_post_emission for a program containing only let $x = 42; x + 1 is exactly 0 — rejects the possibility that the instrumentation introduces spurious non-zero counts for scalar-only code. If this test ever passes with a non-zero value, the counting logic is wrong.
-
/tpr-reviewpassed — independent review found no critical or major issues (or all findings triaged) -
/impl-hygiene-reviewpassed — hygiene review clean. MUST run AFTER/tpr-reviewis clean. - Subsection close-out (01.2) — MANDATORY before starting the next subsection. Run
/improve-toolingretrospectively on THIS subsection’s debugging journey (per.claude/skills/improve-tooling/SKILL.md“Per-Subsection Workflow”): whichdiagnostics/scripts you ran, where you addeddbg!/tracingcalls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE/commit-pushusing a valid conventional-commit type (build(diagnostics): ... — surfaced by section-01.2 retrospective—build/test/chore/ci/docsare valid;tools(...)is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 01.2: no tooling gaps”. Update this subsection’sstatusin section frontmatter tocomplete. -
/sync-claudesection-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW. - Repo hygiene check — run
diagnostics/repo-hygiene.sh --checkand clean any detected temp files.
01.3 Add Coalescing-Specific Statistics
File(s): compiler/ori_arc/src/aims/emit_rc/coalesce/mod.rs
Thread metrics through the coalescing pass to count barrier events.
-
Add
CoalesceStatsstruct:pub(crate) struct CoalesceStats { pub flush_all_count: usize, pub flush_touched_count: usize, pub pairs_cancelled: usize, // inc+dec pairs that netted to zero pub ops_merged: usize, // adjacent inc/inc or dec/dec merged } -
Update
coalesce_block_rc()signature to returnCoalesceStats:pub(crate) fn coalesce_block_rc(body: &mut Vec<ArcInstr>) -> CoalesceStats -
Count events inside the coalescing loop:
- Increment
flush_all_countat theflush_all()call inside theif is_callbranch (line 74 ofcoalesce/mod.rs) - Increment
flush_touched_countat theflush_touched()call in the else branch (line 77) - In
flush_entry()(line 161), countpairs_cancelledwhenincs == decs(net zero — neither the> entry.decsnor> entry.incsbranch fires, so both are cancelled) - In
flush_entry(), countops_mergedwhenincs > 1ordecs > 1(the emitted op represents a merge of multiple individual ops)
Note:
flush_entry()currently takes&PendingRc, not&mut CoalesceStats. Either pass&mut CoalesceStatstoflush_entry(), or count at the call sites inflush_all()andflush_touched()after checking the entry values before removal. - Increment
-
Propagate
CoalesceStatsup through the call chain:coalesce_block_rc()returnsCoalesceStats(already changed above)emit_rc_unified()inrealize/emit_unified.rscallscoalesce_block_rc()per block at line ~108 — accumulate per-block stats into a function-level aggregateemit_rc_unified()returns the aggregate alongside existing return tuple (extend the return type or embed inSynergyMetrics)realize_rc_reuse()inrealize/mod.rscallsemit_rc_unified()— propagate toSynergyMetrics:
metrics.barrier_flush_count = stats.flush_all_count + stats.flush_touched_count; metrics.barrier_flush_all_count = stats.flush_all_count; -
Unit tests:
- Test that a block with no calls has
flush_all_count == 0 - Test that a block with 3 Apply instructions has
flush_all_count == 3 - Test that
RcInc(x); RcDec(x)with no barrier haspairs_cancelled == 1
- Test that a block with no calls has
Semantic pin: Test that flush_all_count equals the number of Apply/ApplyIndirect instructions in a block (before Section 02 changes the barrier behavior).
Negative pin: Test that pairs_cancelled for a block containing RcInc(x); Apply(f, x); RcDec(x) is exactly 0 — the intervening call barrier prevents cancellation. If this ever reports pairs_cancelled > 0, the barrier-respecting logic in coalescing is broken.
-
/tpr-reviewpassed — independent review found no critical or major issues (or all findings triaged) -
/impl-hygiene-reviewpassed — hygiene review clean. MUST run AFTER/tpr-reviewis clean. - Subsection close-out (01.3) — MANDATORY before starting the next subsection. Run
/improve-toolingretrospectively on THIS subsection’s debugging journey (per.claude/skills/improve-tooling/SKILL.md“Per-Subsection Workflow”): whichdiagnostics/scripts you ran, where you addeddbg!/tracingcalls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE/commit-pushusing a valid conventional-commit type (build(diagnostics): ... — surfaced by section-01.3 retrospective—build/test/chore/ci/docsare valid;tools(...)is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 01.3: no tooling gaps”. Update this subsection’sstatusin section frontmatter tocomplete. -
/sync-claudesection-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW. - Repo hygiene check — run
diagnostics/repo-hygiene.sh --checkand clean any detected temp files.
01.R Third Party Review Findings
- None.
01.N Completion Checklist
-
SynergyMetricshasrc_ops_input,rc_ops_post_emission,rc_ops_post_coalesce,barrier_flush_count,barrier_flush_all_countfields -
coalesce_block_rc()returnsCoalesceStatswithflush_all_count,flush_touched_count,pairs_cancelled,ops_merged -
ORI_LOG=ori_arc=info ori build tests/spec/traits/iterator/map.orishows per-function RC metrics - Scalar-only programs show
rc_ops_post_emission == 0 - Programs with heap types show
rc_ops_post_emission > 0 -
coalesce_reduction_percent()returns correct values for test cases - Duplicate
count_rc_ops()inemit_unified.rsdeleted — single canonical version inpipeline/rc_count/mod.rs - All existing tests pass unchanged (
./test-all.shgreen) - No spurious warnings in normal compilation
- Plan annotation cleanup:
bash .claude/skills/impl-hygiene-review/plan-annotations.sh --plan 01returns 0 annotations -
/tpr-reviewpassed — independent Codex review found no critical or major issues -
/impl-hygiene-reviewpassed — hygiene review clean -
/improve-toolingretrospective completed — MANDATORY at section close, after both reviews are clean. Reflect on the section’s debugging journey (whichdiagnostics/scripts you ran, which command sequences you repeated, where you added ad-hocdbg!/tracingcalls, where output was hard to interpret) and identify any tool/log/diagnostic improvement that would have made this section materially easier OR that would help the next section touching this area. Implement every accepted improvement NOW (zero deferral) and commit each via SEPARATE/commit-push. The retrospective is mandatory even when nothing felt painful — that is exactly when blind spots accumulate. See.claude/skills/improve-tooling/SKILL.md“Retrospective Mode” for the full protocol.
Exit Criteria: ORI_LOG=ori_arc=info ori build on any .ori file emits structured tracing events with rc_ops_input, rc_ops_post_emission, rc_ops_post_coalesce, barrier_flush_count fields. ./test-all.sh passes with 0 regressions. CoalesceStats counts match manual inspection of IR for 3+ test programs.