Section 03: AIMS State Export to Codegen
Status: Not Started
Goal: Thread AIMS’s interprocedural analysis results (MemoryContract, ParamContract, ReturnContract, EffectSummary) through to the LLVM emitter so that standard LLVM optimization passes (LICM, GVN, SROA) have richer aliasing and effect information. Currently, FunctionCompiler holds aims_contracts: FxHashMap<Name, MemoryContract> (line 78 of function_compiler/mod.rs) but never passes it to ArcIrEmitter (line 176 of define_phase.rs). The contracts are computed, stored, and then ignored during LLVM emission.
Success Criteria:
-
ArcIrEmitterhas access to the current function’sMemoryContract - At call sites where callee’s
ReturnContract.uniqueness == Unique, return value getsnoaliasattribute - At call sites where callee’s
EffectSummary.may_allocate == false && may_deallocate == false, the call getsmemory(read)ormemory(none)annotation -
ORI_DUMP_AFTER_LLVM=1shows new attributes on appropriate call sites
Context: AIMS computes rich per-function contracts via SCC fixpoint (compute_aims_contracts()). Each MemoryContract contains: per-parameter access/consumption/cardinality/locality/uniqueness, return value uniqueness/freshness, and function-level effect summary (may_allocate, may_deallocate, may_share, may_throw). These facts are consumed by the ARC pipeline (passed to run_arc_pipeline() at define_phase.rs:317 for RC placement decisions and param ownership) but never reach the LLVM emission layer — ArcIrEmitter::new() at define_phase.rs:176 does NOT receive the contracts map. Threading them to the emitter enables call-site-specific optimization attributes that LLVM’s standard passes (LICM, GVN, SROA) can exploit.
Reference implementations:
- Current pattern:
FunctionCompiler.aims_contractsfield (mod.rs:78) — data exists but isn’t threaded - Nounwind pattern:
is_arc_function_nounwind()(nounwind/analyze.rs:163) — post-analysis attribute application
Depends on: Section 02 (metadata infrastructure for alias.scope).
03.1 Thread MemoryContract to ArcIrEmitter
File(s): compiler/ori_llvm/src/codegen/function_compiler/define_phase.rs, compiler/ori_llvm/src/codegen/arc_emitter/mod.rs
-
Add
callee_contracts: &'a FxHashMap<Name, MemoryContract>field toArcIrEmitter -
Update
ArcIrEmitter::new()signature to accept the contracts map -
Update the construction site at
define_phase.rs:176to pass&self.aims_contracts -
Add
fn callee_contract(&self, name: Name) -> Option<&MemoryContract>helper to ArcIrEmitter -
Verify:
timeout 150 cargo t -p ori_llvmpasses (no behavioral change yet) -
/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 (03.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-03.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 03.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.
03.2 Emit noalias on Fresh Allocations
File(s): compiler/ori_llvm/src/codegen/arc_emitter/apply.rs or equivalent call-site emission
When emitting a function call, if the callee’s ReturnContract.uniqueness == Unique, the return value is guaranteed to be a fresh allocation not aliased by anything. Apply noalias to the call-site return.
- At call-site emission (Apply instruction handling), look up callee in
callee_contracts - Write failing AOT test FIRST (TDD): function that returns a freshly allocated list → before the fix, IR dump does NOT show
noaliason the return. This test will verify the attribute after the fix. - If
return_info.uniqueness == Uniqueness::Unique, addnoaliasreturn attribute to the call instruction - Use
IrBuilder::add_call_site_return_attribute()(may need to add this method if it doesn’t exist — attributes.rs already has parameter-level call-site attributes) - Verify AOT test now passes:
noaliasappears in IR dump on fresh allocation returns - Verify:
ORI_DUMP_AFTER_LLVM=1showsnoaliason return of allocation functions - For functions with disjoint parameters (both borrowed, different allocation domains), emit
!alias.scope+!noaliasmetadata pair on parameter loads: each parameter gets its own scope, and its loads are!noaliaswith respect to the other parameter’s scope. This enables LLVM to prove that loads from parameter A do not alias stores through parameter B. - Verify: no test regressions (noalias/alias.scope are hints, not behavioral)
Matrix dimensions:
-
Types:
[int](list allocation),str(string allocation),{str: int}(map allocation), user struct -
Patterns: direct call returning fresh, call returning borrowed (should NOT get noalias), call returning field projection (should NOT get noalias), two disjoint borrowed params (should get alias.scope pair)
-
Semantic pin: IR dump test showing noalias on fresh allocation return
-
Semantic pin: IR dump test showing
!alias.scopeand!noaliason disjoint parameter loads -
/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 (03.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-03.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 03.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.
03.3 Emit Effect-Based Call Site Annotations
File(s): compiler/ori_llvm/src/codegen/arc_emitter/apply.rs
When emitting a function call, if the callee’s EffectSummary indicates limited effects, annotate the call site with LLVM memory() attributes. This enables LICM (loop-invariant code motion) when calling pure functions in loops.
- At call-site emission, look up callee in
callee_contracts - If
effects.may_allocate == false && effects.may_deallocate == false && effects.may_throw == false:- If also no writes to parameters →
memory(none)(pure function) - If reads but no writes →
memory(read)(readonly function)
- If also no writes to parameters →
- If
effects.may_allocate == trueand also accesses arguments →memory(argmem: readwrite, inaccessiblemem: readwrite)(allocating + arg access). If pure allocator (no arg access) →memory(inaccessiblemem: readwrite). Per aims-rules.md RL-30:memory(argmem: readwrite)ALONE is wrong for allocators — misses global heap state. - Use existing
IrBuilder::add_memory_none_attribute()/add_memory_read_attribute()patterns (attributes.rs already has these for function-level, adapt for call-site level) - Write failing AOT test FIRST (TDD): call a pure function inside a loop → before the fix, the call is NOT hoisted. This test will verify LICM after the fix.
- Verify AOT test now passes: IR shows the pure function call hoisted above the loop header
- Verify: no test regressions
Matrix dimensions:
-
Types: pure int function, allocating function, function that reads but doesn’t allocate
-
Patterns: call outside loop (no change), call inside loop (should be hoistable if pure), call with side effects (should NOT get memory(none))
-
Semantic pin: loop containing pure function call → IR shows the call hoisted above the loop header
-
/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 (03.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-03.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 03.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.
03.R Third Party Review Findings
- None.
03.N Completion Checklist
-
ArcIrEmitterreceives and queriesMemoryContractfor callees - Fresh allocation returns get
noalias - Pure/readonly call sites get
memory(none)/memory(read) -
ORI_DUMP_AFTER_LLVM=1demonstrates new attributes -
timeout 150 ./test-all.shgreen (debug AND release —cargo b --release && timeout 150 ./test-all.sh) - Dual-exec parity:
diagnostics/dual-exec-verify.sh tests/spec/passes (eval and LLVM produce identical results — new attributes are hints, must not change observable behavior) -
ORI_CHECK_LEAKS=1clean on test programs exercising new attributes (noalias/memory() are optimization hints but verify RC ops remain balanced) - No spurious warnings
- Plan annotation cleanup
- Plan sync — update plan metadata
- This section
status→complete -
00-overview.mdupdated -
index.mdupdated
- This section
-
/tpr-reviewpassed -
/impl-hygiene-reviewpassed -
/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: AIMS ownership and effect facts are visible in LLVM IR output. Pure function calls in loops are hoistable. Fresh allocations are marked noalias. All tests pass unchanged.