Unifies initialization and conversion logic across all 4 loop patterns, eliminating code duplication and establishing single source of truth. ## Changes ### Infrastructure (New) - CommonPatternInitializer (117 lines): Unified loop var extraction + CarrierInfo building - JoinIRConversionPipeline (127 lines): Unified JoinIR→MIR→Merge flow ### Pattern Refactoring - Pattern 1: Uses CommonPatternInitializer + JoinIRConversionPipeline (-25 lines) - Pattern 2: Uses CommonPatternInitializer + JoinIRConversionPipeline (-25 lines) - Pattern 3: Uses CommonPatternInitializer + JoinIRConversionPipeline (-25 lines) - Pattern 4: Uses CommonPatternInitializer + JoinIRConversionPipeline (-40 lines) ### Code Reduction - Total reduction: ~115 lines across all patterns - Zero code duplication in initialization/conversion - Pattern files: 806 lines total (down from ~920) ### Quality Improvements - Single source of truth for initialization - Consistent conversion flow across all patterns - Guaranteed boundary.loop_var_name setting (prevents SSA-undef bugs) - Improved maintainability and testability ### Testing - All 4 patterns tested and passing: - Pattern 1 (Simple While): ✅ - Pattern 2 (With Break): ✅ - Pattern 3 (If-Else PHI): ✅ - Pattern 4 (With Continue): ✅ ### Documentation - Phase 33-22 inventory and results document - Updated joinir-architecture-overview.md with new infrastructure ## Breaking Changes None - pure refactoring with no API changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
8.5 KiB
Phase 33-16: Loop Header PHI SSOT - Documentation Index
Last Updated: 2025-12-07
Status: ✅ Complete implementation design ready
Quick Navigation
For Implementation
Start here: phase33-16-visual-guide.md
- Architecture flow diagram (all 7 phases)
- Code change map with exact line numbers
- 7 complete code changes (copy-paste ready)
- Testing commands
For Understanding
Read first: phase33-16-qa.md
- Answer to all 5 core questions
- Exact code snippets with explanations
- "What you DON'T need" guidance
- Complete flow summary
For Context
Detailed planning: phase33-16-implementation-plan.md
- Executive summary
- Problem analysis
- 6 concrete implementation steps
- Testing strategy
- Risk analysis and mitigation
- Future enhancements
Quick Summary
Reference: PHASE_33_16_SUMMARY.md
- TL;DR answers
- Architecture evolution
- Implementation scope
- Key architectural insight
- Testing roadmap
Original Design
Background: phase33-16-loop-header-phi-design.md
- Original design document
- Problem statement
- Solution overview
Your 5 Questions - Direct Answers
Q1: Where exactly should LoopHeaderPhiBuilder::build() be called?
Location: src/mir/builder/control_flow/joinir/merge/mod.rs, line 107
When: Between Phase 3 (remap_values) and Phase 4 (instruction_rewriter)
Why: Phase 3.5 allocates PHI dsts before instruction_rewriter needs them
Details: phase33-16-qa.md#q1
Q2: How do I get the header_block_id (loop_step's entry block after remapping)?
Code: remapper.get_block(entry_func_name, entry_func.entry_block)?
Key: Entry block is the loop header for Pattern 2
Details: phase33-16-qa.md#q2
Q3: How do I get the loop variable's initial value (host-side)?
Code: remapper.get_value(ValueId(0))?
Key: ValueId(0) is always the loop parameter in JoinIR space
Details: phase33-16-qa.md#q3
Q4: Where should instruction_rewriter record latch_incoming?
Location: src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs, ~line 300
When: In tail call section, after parameter bindings
Code: loop_header_phi_info.set_latch_incoming(loop_var_name, target_block, latch_value)
Details: phase33-16-qa.md#q4
Q5: Should the Phase 33-15 skip logic be removed or modified?
Answer: Modify, NOT remove Strategy: Use header PHI dst when available, fallback to parameter Details: phase33-16-qa.md#q5
Implementation at a Glance
Files to Modify
src/mir/builder/control_flow/joinir/merge/mod.rs(2 locations: Phase 3.5, Phase 4.5)src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs(5 locations: signature, latch tracking, exit logic)
Changes Summary
| Phase | What | Where | Type |
|---|---|---|---|
| 3.5 | Build header PHIs | merge/mod.rs | New |
| 4 | Update signature | instruction_rewriter.rs | Signature |
| 4 | Track latch | instruction_rewriter.rs | New code |
| 4 | Use PHI for exit | instruction_rewriter.rs | Modified |
| 4 | Use PHI for carriers | instruction_rewriter.rs | Modified |
| 4.5 | Finalize PHIs | merge/mod.rs | New |
Scope
- Lines added: ~100
- Lines modified: ~100
- Lines removed: 0 (backward compatible)
- New files: 0 (uses existing LoopHeaderPhiBuilder)
Architecture Diagram
Phase 3: remap_values()
↓ (ValueIds remapped: 0→100, 0→101, etc.)
Phase 3.5: LoopHeaderPhiBuilder::build() ⭐ NEW
└── Allocate phi_dst(101), entry_incoming(init_value)
└── Pass loop_header_phi_info to Phase 4
Phase 4: instruction_rewriter::merge_and_rewrite()
├── When tail call: set_latch_incoming()
└── When Return: use phi_dst (not parameter)
Phase 4.5: LoopHeaderPhiBuilder::finalize() ⭐ NEW
└── Emit PHIs into header block
Phase 5: exit_phi_builder::build_exit_phi()
└── Return carrier_phis with header PHI dsts
Phase 6: ExitLineOrchestrator::execute()
└── Update variable_map with carrier_phis
Testing Checklist
Before implementation:
- Review phase33-16-qa.md (answers to your questions)
- Review phase33-16-visual-guide.md (code locations)
- Baseline compile:
cargo build --release
During implementation:
- Implement Phase 3.5 → compile
- Update signature → compile
- Add latch tracking → compile
- Replace exit_phi_inputs → compile
- Replace carrier_inputs → compile
- Add Phase 4.5 → compile
- Update docs → compile
After implementation:
NYASH_JOINIR_DEBUG=1 ./target/release/nyash --dump-mir test.hako | grep "Phase 33-16"- Verify header block has PHI instructions at start
- Verify exit values reference header PHI dsts
- Test:
joinir_min_loop.hakoproduces correct MIR - Test: Loop variable values correct at exit
Document Quick Links
Complete Guides (in order of use)
-
phase33-16-qa.md (11 KB)
- Start here for understanding
- All 5 questions answered
- Code examples ready to copy-paste
-
phase33-16-visual-guide.md (22 KB)
- Use during implementation
- Architecture diagrams
- Complete code changes with line numbers
- Copy-paste ready code
-
phase33-16-implementation-plan.md (18 KB)
- For detailed planning
- Risk analysis
- Testing strategy
- Future enhancements
-
PHASE_33_16_SUMMARY.md (8.2 KB)
- Quick reference
- TL;DR answers
- Key insights
- Next steps
Reference Documents
- phase33-16-loop-header-phi-design.md (9.0 KB)
- Original design document
- Historical context
Key Concepts
Loop Header PHI as SSOT (Single Source of Truth)
The core idea: Instead of using undefined loop parameters, use PHI nodes at the loop header to track the "current value" of loop variables.
Why it works:
- PHI nodes ARE SSA-defined at the header block
- PHI dsts can safely be referenced in exit values
- Eliminates SSA-undef errors from undefined parameters
Architecture:
- Phase 3.5: Pre-allocate PHI dsts (before instruction processing)
- Phase 4: Set latch incoming during instruction processing
- Phase 4.5: Finalize PHIs with all incoming edges
- Phase 6: Use PHI dsts in exit line reconnection
Two-Phase PHI Construction
Phase 3.5: build() - Allocate PHI dsts
let phi_dst = builder.next_value_id(); // Allocate
entry_incoming = (entry_block, init_value); // Set entry
latch_incoming = None; // Will be set in Phase 4
Phase 4.5: finalize() - Emit PHI instructions
PHI {
dst: phi_dst, // Already allocated
inputs: [
(entry_block, init_value), // From Phase 3.5
(header_block, latch_value), // From Phase 4
]
}
Known Limitations & Future Work
Phase 33-16 (Current)
✅ Minimal scope: Loop variable only, no other carriers ✅ Pattern 2 primary focus (break statements) ✅ Backward compatible with Phase 33-15
Phase 33-16+ (Future)
- Extract multiple carriers from exit_bindings
- Handle Pattern 3+ with multiple PHIs
- Pattern-specific header block identification
- Optimize constant carriers
Support & Debugging
If implementation breaks:
- Compilation error: Check if loop_header_phi_info is properly passed as mutable reference
- SSA-undef error: Check if finalize() is called and all latch_incoming are set
- Wrong values: Check if latch_incoming is set from correct tail call args
- No header PHI: Enable
NYASH_JOINIR_DEBUG=1and check "Phase 33-16" output
Debug commands:
# Check debug output
NYASH_JOINIR_DEBUG=1 ./target/release/nyash --dump-mir test.hako 2>&1 | grep "Phase 33-16"
# Inspect MIR
./target/release/nyash --emit-mir-json mir.json test.hako
jq '.functions[0].blocks[0].instructions[0:3]' mir.json
Next Phase: Phase 34+
After Phase 33-16 is complete:
- Pattern 4 (continue statement) implementation
- Trim patterns with complex carriers
- Loop-as-expression full integration
- Performance optimizations
Ready to implement? Start with phase33-16-qa.md!