Section 10: Verification
Status: Complete
Goal: Re-run all 12 code journeys and verify that every finding has been resolved. The emitted LLVM IR at -O0 should produce assembly that a skilled C programmer would recognize as their own work — no redundant blocks, no dead loads, no missing attributes, no correctness bugs.
Depends on: ALL sections 01–09 must be complete before verification.
10.0 Preflight & Environment Lock
Before running verification, lock down the environment so results are reproducible:
- Confirm required tools exist:
ori(LLVM-enabled),rg,objdump,valgrind,opt-21 - Record current commit and date in
build/codegen-purity/current/verification-meta.txt - Record tool versions:
ori --version,opt-21 --version,valgrind --version - Build compiler:
cargo b(debug) ANDcargo b --release(release) - Run
./test-all.shBEFORE starting verification to confirm clean baseline - Use a clean output root for this run:
build/codegen-purity/current/ - Verify target mode is
-O0for IR/asm quality checks - Preserve prior baseline artifacts at
build/codegen-purity/baseline/for before/after comparison - Verify all sections §01-§09 frontmatter shows
status: complete(ordeferredwith rationale in §10.8) - Verify
__recursesentinel is resolved (no unresolved__recursein ARC IR) — this is a §09 prerequisite that fixes a latent AOT bug
set -euo pipefail
mkdir -p build/codegen-purity/current
{
echo "Date: $(date -u)"
echo "Commit: $(git rev-parse HEAD)"
echo "Branch: $(git branch --show-current)"
echo "ori: $(ori --version 2>&1 || echo 'not found')"
echo "opt-21: $(opt-21 --version 2>&1 | head -1 || echo 'not found')"
echo "valgrind: $(valgrind --version 2>&1 || echo 'not found')"
command -v ori rg objdump valgrind opt-21
} > build/codegen-purity/current/verification-meta.txt
10.0 Completion Checklist
- All required tools confirmed present and versioned
-
build/codegen-purity/current/verification-meta.txtcreated with commit hash and tool versions - Baseline artifacts preserved at
build/codegen-purity/baseline/ -
-O0mode confirmed for IR/asm checks -
./test-all.shgreen before starting verification - All sections §01-§09 marked complete (or deferred with rationale)
10.1 Re-Run All 12 Code Journeys
Capture fresh IR/asm/audit artifacts for all 12 journey fixtures.
set -euo pipefail
mkdir -p build/codegen-purity/current/{ir,asm,audit}
for f in plans/code-journeys/journey{1..12}.ori; do
base="$(basename "$f" .ori)"
diagnostics/ir-dump.sh --raw "$f" > "build/codegen-purity/current/ir/${base}.ll"
diagnostics/disasm-ori.sh "$f" > "build/codegen-purity/current/asm/${base}.s"
diagnostics/codegen-audit.sh --strict "$f" > "build/codegen-purity/current/audit/${base}.txt"
done
- Journey 1 (arithmetic): 0 findings (was: M-1, M-2, L-1, L-11)
- Journey 2 (branching/unary negation): 0 findings (was: M-1, M-1b, M-5, L-4)
- Journey 3 (recursion/gcd): 0 findings (was: L-4, L-10)
- Journey 4 (structs/rect): 0 findings (was: L-4, L-5)
- Journey 5 (closures/make_adder): 0 findings (was: M-1, M-2, M-3, L-1, L-12)
- Journey 6 (sum types/extract): 0 findings (was: M-1, M-4, L-4)
- Journey 7 (loops/ranges): 0 findings (was: M-1, M-1c, L-4, L-6, L-7, L-9)
- Journey 8 (generics): 0 findings (was: M-1, L-4)
- Journey 9 (strings): 0 findings (was: L-3, L-4)
- Journey 10 (lists): 0 findings (was: M-1, L-4, L-5, L-8)
- Journey 11 (derived traits/Shape): 0 findings (was: L-2, L-4, M-4)
- Journey 12 (Option/match): 0 findings (was: M-1, L-4)
10.1 Completion Checklist
- All 12 journey IR/asm/audit artifacts captured in
build/codegen-purity/current/ - All 12 journeys produce 0 findings at all severity levels
- Each journey’s audit output reviewed and confirmed clean
10.2 Assembly Quality Audit
For each journey, dump the -O0 assembly and verify it reads like hand-written C:
- No
jmpto the immediately next instruction (redundant blocks eliminated) -
selectused for trivial conditionals (no unnecessary branch+merge) - Only accessed struct fields are loaded from memory
- Panic paths have no normal continuation after panic call (IR:
call+unreachable; asm may appear as trap or fallthrough-free sequence) - String constants: each unique string appears exactly once in
.rodata - Tail-recursive functions compiled as loops (no
callto self) - Loop bodies: no duplicate arithmetic (§08.1 — count checked_add calls in body block)
- Loop headers: no invariant block params / phi nodes (§08.2 — verify all phis change across iterations)
- Range loops: specialized bounds checks where applicable (§08.3 —
icmp slt/sle/sgt/sgefor known step/inclusive)
Use build/codegen-purity/current/asm/*.s from 10.1 plus targeted checks:
rg -n "musttail|tail call|select|ori_panic|unreachable" build/codegen-purity/current/ir
rg -n "jmp" build/codegen-purity/current/asm
rg -n ' = .*c"integer overflow on (addition|subtraction|multiplication|negation)\\00"' build/codegen-purity/current/ir
10.2 Completion Checklist
- No redundant
jmpto next instruction in any journey assembly -
selectpresent where expected; diamonds only where required - Only used struct fields loaded
- Panic paths terminate cleanly
- No duplicate string constants in
.rodata - Tail-recursive functions use loops
- Loop bodies have no duplicate arithmetic
10.3 Dual Execution Verification
Verify eval and AOT paths produce identical results for all 12 journeys:
- Run backend comparison on each journey fixture:
for f in plans/code-journeys/journey{1..12}.ori; do
diagnostics/dual-exec-debug.sh "$f"
done
- Run whole-test parity check:
diagnostics/dual-exec-verify.sh --json=build/codegen-purity/current/dual-exec-report.json tests/spec/
- 0 mismatches between eval and AOT output
- Both paths panic on
-INT_MIN(§03 parity) - Both paths free closure environments (§04 parity)
10.3 Completion Checklist
- All 12 journey dual-exec checks pass (0 mismatches)
- Whole-test dual-exec report shows 0 mismatches
-
-INT_MINparity confirmed (eval and AOT both panic) - Closure environment parity confirmed (both free correctly)
10.4 Pre-Existing IR Quality Tests
Un-ignore the 4 tests in compiler/ori_llvm/tests/aot/ir_quality.rs that document the exact issues this plan fixes:
-
test_nounwind_program_has_no_unreachable_blocks— remove#[ignore]after §02 -
test_nounwind_generic_call_no_unreachable— remove#[ignore]after §02 -
test_mixed_calls_no_dead_unreachable— remove#[ignore]after §02 -
test_constant_main_minimal_ir— remove#[ignore]after §01 + §02 - Un-ignore progressively: do not remove
#[ignore]until owning section exit criteria are actually satisfied
10.4 Completion Checklist
- All 4
#[ignore]tests un-ignored - All 4 tests passing in
cargo test -p ori_llvm --test aot -- ir_quality - Each test was un-ignored only after its owning section was complete
10.5 Permanent Regression Tests
Test file convention:
ir_quality.rsis currently 534 lines and will grow significantly with these additions. While test files are exempt from the 500-line limit, consider splitting into focused test modules (e.g.,ir_quality_attrs.rs,ir_quality_blocks.rs,ir_quality_loops.rs) if it exceeds ~800 lines. Tests must be in thecompiler/ori_llvm/tests/aot/directory (integration tests), not inline in source files.
Test naming convention: Use
test_X_when_Y_then_Zformat per hygiene rules. Each test tests one thing.
Convert key findings into permanent ir_quality.rs tests to prevent regressions:
- Add test: overflow message string appears exactly once per unique message in IR (§07) —
test_overflow_string_dedup_single_global_per_messageinir_quality_codegen.rs - Add test:
ori_panic_cstrdeclaration hasnoreturnattribute (§02) —test_panic_declarations_have_noreturninir_quality_attributes.rs - Add test: derived
$eqmethod hasnounwindattribute (§02) —test_pure_derived_methods_have_nounwindinir_quality_attributes.rs - Add test: payload extraction uses
extractvalue, notalloca+store+GEP+load(§05) —test_enum_payload_uses_extractvalueinir_quality_codegen.rs - Add test:
-INT_MINpanics (AOT parity with eval) (§03) —test_op_mul_int_min_overflowinoperators.rs - Add test: closure env freed with
ORI_CHECK_LEAKS=1(§04) —test_arc_closure_loop_no_leakinarc.rs - Add test: no instructions after
ori_panic_cstrcall on normal path (§06) —test_noreturn_panic_has_unreachable_no_cleanupinir_quality_codegen.rs - Add test: struct parameter loading — only referenced fields loaded (§06) —
test_struct_selective_field_loadinginir_quality_codegen.rs - Add test: no single-predecessor phi nodes in simple programs (§01) — 5 tests in
ir_quality_block_merge.rs - Add test:
selectfor trivial if/else (§01) —test_trivial_if_else_emits_selectinir_quality_block_merge.rs - Add test: loop body computes
i+1once — count@llvm.sadd.with.overflow.i64calls in body block (§08.1) —test_cse_loop_duplicate_add_eliminatedinir_quality_loops.rs - Add test: loop with pre-loop mutable binding not modified in body — no block param / no phi for that binding (§08.2) —
test_loop_invariant_binding_no_phiinir_quality_loops.rs - Add test:
for i in 0..nemitsicmp sltonly (§08.3) —test_range_ascending_exclusive_single_icmpinir_quality_loops.rs - Add test:
for i in 0..=nemitsicmp sleonly (§08.3) —test_range_ascending_inclusive_single_icmpinir_quality_loops.rs - Add test:
for i in n..0 by -1emitsicmp sgtonly (§08.3) —test_range_descending_exclusive_single_icmpinir_quality_loops.rs - Add test:
for i in 0..n by k(variable step) uses general check (§08.3 negative test) —test_range_variable_step_general_conditioninir_quality_loops.rs - Add test: tail-recursive function compiles to loop (§09) —
test_tail_recursive_gcd_has_no_self_callinir_quality_codegen.rs - Add test: tail-recursive function with RC-managed args — no leak (§09) —
test_tail_rec_with_list_paraminrecursion.rs - Add test:
recurse()pattern compiles to loop (§09) —test_tail_rec_recurse_patterninrecursion.rs - Add test: deep tail recursion (>= 100,000 depth) without stack overflow (§09) —
test_tail_rec_countdown_deepinrecursion.rs - Add test: C
mainwrapper hasnounwind(§02) —test_trivial_main_wrapper_has_nounwindinir_quality_attributes.rs - Add test:
noundefon integer parameters (§02) —test_scalar_params_have_noundefinir_quality_attributes.rs
10.5 Completion Checklist
- At least 10 new regression tests added (some may already exist from section work)
- All new tests passing
- Tests are specific enough to catch regressions (assert on IR patterns, not just “compiles”)
- Existing tests from §01.2, §01.3, §03, §04 counted toward regression coverage
10.6 Regression Safety
-
./test-all.shgreen (full test suite) -
./clippy-all.shgreen -
cargo test -p ori_llvmgreen (all LLVM/AOT tests) -
cargo test -p ori_llvm --test aot -- ir_qualitygreen -
diagnostics/valgrind-aot.sh plans/code-journeys/journey{1..12}.ori— 0 memory errors -
ORI_CHECK_LEAKS=1on all journey programs — 0 leaks -
opt-21 -passes=verifyclean on all 12 journey IR files
for ll in build/codegen-purity/current/ir/*.ll; do
opt-21 -passes=verify -disable-output "$ll"
done
10.6 Completion Checklist
-
./test-all.shgreen -
./clippy-all.shgreen -
cargo test -p ori_llvmgreen -
cargo test -p ori_llvm --test aot -- ir_qualitygreen - Valgrind: 0 memory errors on all 12 journey programs
- Leak check: 0 leaks on all 12 journey programs
-
opt-21 -passes=verifyclean on all 12 journey IR files
10.7 Finding-Closure Matrix
Populate this table during final verification. Every finding ID from 00-overview.md must map to concrete evidence.
| Finding ID | Owner Section | Status (fixed/deferred) | Primary Evidence Artifact(s) | Regression Test |
|---|---|---|---|---|
| M-1 | §01 | fixed | block_merge Phase 4 merge jump chains, Phase 1 compact | test_sequential_calls_no_bridge_blocks, test_main_with_call_no_bridge_blocks |
| M-1b | §01 | fixed | block_merge Phase 3 select-fold for trivial diamonds | test_trivial_if_else_emits_select, test_match_pure_values_no_bridge_blocks |
| M-1c | §01 | fixed | block_merge Phase 5 single-pred phi elimination, Phase 6 dead param elimination | test_single_break_loop_clean_exit, test_multi_break_loop_no_dead_phis |
| M-2 | §02 | fixed | runtime_functions.rs: Attr::Noreturn on ori_panic/ori_panic_cstr | test_panic_declarations_have_noreturn |
| M-3 | §04 | fixed | rc_insert/block_rc.rs: RcDec at end of closure live range | test_arc_closure_loop_no_leak, test_arc_closure_passed_and_freed |
| M-4 | §05 | fixed | instr_dispatch.rs: extractvalue chains for SSA values | test_enum_payload_uses_extractvalue, test_enum_int_payload_extractvalue |
| M-5 | §03 | fixed | arithmetic.rs: checked_neg() via @llvm.ssub.with.overflow.i64 | test_checked_neg_overflow, tests/spec/types/integer_safety.ori |
| L-1 | §02 | fixed | entry_point.rs: C main wrapper inherits nounwind from _ori_main | test_trivial_main_wrapper_has_nounwind, test_panicking_main_wrapper_lacks_nounwind |
| L-2 | §02 | fixed | derive_codegen/mod.rs: is_nounwind_derived() on pure derives | test_pure_derived_methods_have_nounwind, test_impure_derived_methods_lack_nounwind |
| L-3 | §02 | fixed | runtime_functions.rs: 131/141 runtime fns now have Attr::Nounwind | (covered by runtime function audit) |
| L-4 | §07 | fixed | constants.rs: global_strings cache deduplicates by content | test_overflow_string_dedup_single_global_per_message |
| L-5 | §06 | fixed | emit_function.rs: scan_used_fields() + load_struct_selective() | test_struct_selective_field_loading, test_struct_selective_two_fields |
| L-6 | §08.1 | fixed | checked_ops.rs: CSE cache per block (CseOperand → ValueId) | test_cse_loop_duplicate_add_eliminated, test_cse_different_operands_not_eliminated |
| L-7 | §06 | fixed | apply.rs: unreachable after noreturn calls, skip remaining block | test_noreturn_panic_has_unreachable_no_cleanup, test_checked_binop_overflow_still_has_unreachable |
| L-8 | §08.2 | fixed | block_merge/invariant_param.rs: Phase 7 invariant param elimination | test_loop_invariant_binding_no_phi, test_multiple_invariant_bindings_no_phi |
| L-9 | §08.3 | fixed | for_range.rs: single icmp for known step/inclusive patterns | test_range_ascending_inclusive_single_icmp, test_range_descending_exclusive_single_icmp |
| L-10 | §09 | fixed | tail_call/rewrite.rs: loop lowering replaces Apply with back-edge | test_tail_recursive_gcd_has_no_self_call, test_tail_rec_countdown_deep |
| L-11 | §02 | fixed | define_phase.rs: noundef on all scalar params via is_llvm_scalar() | test_scalar_params_have_noundef, test_aggregate_params_lack_noundef |
| L-12 | §02 | deferred | Conservative: interprocedural proof too complex for LOW severity | nounwind_indirect_call_is_not_nounwind (guards conservative behavior) |
10.7 Completion Checklist
- Every finding ID (M-1 through L-12) has a status entry (
fixedordeferred) - Every
fixedentry has a primary evidence artifact and regression test - Every
deferredentry has rationale in §10.8
10.8 Unresolved-ID Ledger
Track any finding ID that is not fully closed at verification time. Empty table is the target.
| Finding ID | Status (fixed/deferred) | Rationale | Owner Section | Follow-up |
|---|---|---|---|---|
| L-12 | deferred | Indirect (closure) calls lack interprocedural nounwind proof — requires whole-program escape analysis to determine that no closure can unwind. LOW severity: no correctness impact, only prevents LLVM from optimizing unwind paths for indirect calls. Conservative behavior is correct and safe. | §02 | Future: interprocedural nounwind analysis when closure escape tracking is implemented. Guards in place via nounwind_indirect_call_is_not_nounwind test. |
10.8 Completion Checklist
- Ledger is empty (all findings resolved), OR
- Every deferred entry has: concrete rationale, owner section, and follow-up plan with timeline
Realistic Verification Strategy
Not all sections may complete in a single cycle. The verification strategy should accommodate partial completion:
- Sections that are safe to defer: §08 (Loop IR Quality) is a quality improvement with no correctness implications. §09 (Tail Call Optimization) is a spec conformance requirement (Annex E guarantees TCO, Clause 15.3 guarantees
recursecompiles to O(1) stack) — deferral is acceptable only with explicit rationale in §10.8. Both can be deferred to a follow-up cycle if needed. - Sections that MUST complete: §02 (Function Attributes) — noreturn is needed by §06. §06 (Dead Code Pruning) depends on §02. §05 and §07 are independent and low-risk.
- Partial verification is acceptable if the §10.8 unresolved ledger is fully populated with concrete follow-up plans.
If §08 and/or §09 are deferred:
- L-6, L-8, L-9 (§08 findings) go to §10.8 with “deferred to loop quality follow-up”
- L-10 (§09 finding) goes to §10.8 with “deferred to tail call follow-up”
- §10 verification can still close with these deferrals documented
Section 10 Exit Criteria
Re-running all journey fixtures produces zero unresolved findings at any severity, or only explicitly deferred IDs in §10.8 with concrete rationale and follow-up. The assembly output at -O0 is structurally clean and consistent with hand-written C quality. ./test-all.sh, ./clippy-all.sh, cargo test -p ori_llvm --test aot -- ir_quality, diagnostics/dual-exec-verify.sh, diagnostics/valgrind-aot.sh, and leak checks all pass. Pre-existing IR quality tests un-ignored and green. All sections 01–09 marked complete (or explicitly deferred in §10.8) in their frontmatter. Verification artifacts stored under build/codegen-purity/current/.