# Phase 33-16: Visual Flow Diagram & Code Map ## Architecture Diagram: Loop Header PHI as SSOT ``` ┌────────────────────────────────────────────────────────────────────────┐ │ merge_joinir_mir_blocks() Pipeline │ │ (7 phases after Phase 33-16) │ └────────────────────────────────────────────────────────────────────────┘ Phase 1: allocate_blocks() ├── Input: JoinIR mir_module.functions └── Output: remapper with block ID mappings Phase 2: collect_values() ├── Input: JoinIR blocks └── Output: used_values set Phase 3: remap_values() ├── Input: used_values set, remapper ├── Action: Allocate new ValueIds for all used values └── Output: remapper with BOTH block AND value mappings ⚡ VALUE ID BOUNDARY: JoinIR (local 0,1,2...) → Host (100,101,102...) ┌──────────────────────────────────────────────────────────────────────────┐ │ Phase 3.5: LoopHeaderPhiBuilder::build() ⭐ NEW in Phase 33-16 │ │ │ │ Input: │ │ - boundary.loop_var_name: "i" │ │ - remapper.get_value(ValueId(0)): ValueId(100) ← Init value (host) │ │ - remapper.get_block(entry_func, entry_block): BasicBlockId(10) │ │ │ │ Action: │ │ - Allocate phi_dst = ValueId(101) │ │ - Create CarrierPhiEntry: │ │ { phi_dst: ValueId(101), │ │ entry_incoming: (BasicBlockId(10), ValueId(100)), │ │ latch_incoming: None } ← Set in Phase 4! │ │ │ │ Output: │ │ - loop_header_phi_info with empty latch_incoming │ │ - PASS TO: instruction_rewriter as mutable reference │ │ │ │ Key: PHI dst (ValueId(101)) is NOW ALLOCATED and KNOWN before we │ │ process instructions. instruction_rewriter will use it! │ └──────────────────────────────────────────────────────────────────────────┘ Phase 4: merge_and_rewrite() ├── Input: loop_header_phi_info (mutable!) ├ │ Subphase 4a: Process instructions in each block │ ├── Copy instructions: Use remapped ValueIds │ └── Other instructions: Standard remapping │ │ Subphase 4b: Process terminators │ ├── Tail call (Jump to loop header): │ │ ├── args = [ValueId(102)] ← i_next (updated loop variable) │ │ ├── Call: loop_header_phi_info.set_latch_incoming( │ │ │ "i", │ │ │ target_block, ← Loop header │ │ │ ValueId(102)) ← Updated value │ │ └── Emit parameter bindings + Jump │ │ │ └── Return { value }: │ ├── OLD (Phase 33-15): Skip exit_phi_inputs │ │ │ └── NEW (Phase 33-16): │ ├── Get phi_dst = loop_header_phi_info.get_carrier_phi("i") │ │ = ValueId(101) ← PHI output, not parameter! │ └── Collect: exit_phi_inputs.push((exit_block, ValueId(101))) │ └── Output: MergeResult with exit_phi_inputs using PHI dsts ⚡ KEY MOMENT: loop_header_phi_info.latch_incoming NOW SET! ┌──────────────────────────────────────────────────────────────────────────┐ │ Phase 5: exit_phi_builder::build_exit_phi() │ │ │ │ Input: │ │ - exit_phi_inputs: [(exit_block, ValueId(101))] │ │ - carrier_inputs: {} (empty in Phase 33-16 minimal) │ │ │ │ Action: │ │ - Create exit block │ │ - If exit_phi_inputs not empty: │ │ { Create PHI: exit_phi_dst = PHI [(exit_block, ValueId(101))] │ │ - For each carrier: Create carrier PHI │ │ │ │ Output: │ │ - exit_phi_result_id: Some(ValueId(103)) ← Exit block's PHI dst │ │ - carrier_phis: { "i" → ValueId(101) } ← Header PHI dsts! │ │ │ │ Key: carrier_phis now contains header PHI dsts, NOT remapped parameters!│ └──────────────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────────────┐ │ Phase 4.5: LoopHeaderPhiBuilder::finalize() ⭐ NEW in Phase 33-16 │ │ │ │ Input: loop_header_phi_info with latch_incoming NOW SET │ │ │ │ Action: │ │ - Validate all latch_incoming are set │ │ - For each carrier: │ │ { entry_incoming = (entry_block, ValueId(100)), │ │ latch_incoming = (header_block, ValueId(102)) ← From Phase 4! │ │ Emit PHI: ValueId(101) = PHI [(entry_block, ValueId(100)), │ │ (header_block, ValueId(102))] │ │ - Prepend PHI instructions to header block │ │ │ │ Output: │ │ - Header block now contains: │ │ [PHI instructions...], [original instructions...] │ │ │ │ Key: PHI is now EMITTED into the MIR! SSA definition complete! │ └──────────────────────────────────────────────────────────────────────────┘ Phase 6: ExitLineOrchestrator::execute() ├── Input: carrier_phis = { "i" → ValueId(101) } ├── Action: Call ExitLineReconnector │ └── For each exit_binding: │ ├── Look up carrier PHI: carrier_phis["i"] = ValueId(101) │ └── Update: variable_map["i"] = ValueId(101) ← PHI dst! └── Output: Updated variable_map with PHI dsts ``` --- ## Code Change Map ### Files to Modify (6 locations) ``` src/mir/builder/control_flow/joinir/merge/ ├── mod.rs (MODIFY) │ └── Between line 107 (after remap_values) │ and line 110 (before instruction_rewriter) │ ✅ ADD: Phase 3.5 - Build loop header PHIs │ ✅ ADD: Phase 4.5 - Finalize loop header PHIs (after exit_phi_builder) │ ├── instruction_rewriter.rs (MODIFY 3 places) │ ├── Line 29-37: Update fn signature │ │ ✅ ADD: loop_header_phi_info parameter │ │ │ ├── ~Line 300 (in tail call section): Track latch incoming │ │ ✅ ADD: loop_header_phi_info.set_latch_incoming(...) │ │ │ └── Lines 354-431 (Return processing): Use PHI dsts │ ✅ MODIFY: Replace skip logic with PHI dst usage │ ✅ MODIFY: Replace carrier skip with PHI dst usage │ └── loop_header_phi_builder.rs (ALREADY EXISTS) ├── ::build() ✅ Ready to use └── ::finalize() ✅ Ready to use ``` ### Optional: Pattern Lowerer Update ``` src/mir/builder/control_flow/joinir/patterns/ └── pattern2_with_break.rs ├── Line 200: ✅ Already sets loop_var_name └── Line 200+: OPTIONAL - Extract other carriers from exit_bindings (For Phase 33-16+, not required for minimal) ``` --- ## Concrete Code Changes (Copy-Paste Ready) ### Change 1: Add mod.rs imports **Location**: Top of `src/mir/builder/control_flow/joinir/merge/mod.rs` ```rust // Already present: mod instruction_rewriter; mod exit_phi_builder; pub mod exit_line; pub mod loop_header_phi_builder; // ✅ Already declared! // Import the types use loop_header_phi_builder::{LoopHeaderPhiBuilder, LoopHeaderPhiInfo}; ``` ### Change 2: Add Phase 3.5 after remap_values **Location**: `mod.rs`, line 107+ (after `remap_values(...)`) ```rust // Phase 3: Remap ValueIds remap_values(builder, &used_values, &mut remapper, debug)?; // ===== Phase 3.5: Build loop header PHIs ===== let mut loop_header_phi_info = if let Some(boundary) = boundary { if let Some(loop_var_name) = &boundary.loop_var_name { // Get entry function and entry block let (entry_func_name, entry_func) = mir_module .functions .iter() .next() .ok_or("JoinIR module has no functions")?; let entry_block_id = remapper .get_block(entry_func_name, entry_func.entry_block) .ok_or_else(|| format!("Entry block not found"))?; // Get loop variable's initial value (remapped) let loop_var_init = remapper .get_value(ValueId(0)) .ok_or("Loop var init not remapped")?; if debug { eprintln!( "[cf_loop/joinir] Phase 3.5: Building header PHIs for loop_var '{}'", loop_var_name ); } // Build header PHIs (allocates PHI dsts, doesn't emit yet) LoopHeaderPhiBuilder::build( builder, entry_block_id, // header_block_id entry_block_id, // entry_block_id loop_var_name, loop_var_init, &[], // No other carriers yet boundary.expr_result.is_some(), debug, )? } else { LoopHeaderPhiInfo::empty(BasicBlockId(0)) } } else { LoopHeaderPhiInfo::empty(BasicBlockId(0)) }; // Phase 4: Merge blocks and rewrite instructions let merge_result = instruction_rewriter::merge_and_rewrite( builder, mir_module, &mut remapper, &value_to_func_name, &function_params, boundary, &mut loop_header_phi_info, // NEW: Pass mutable reference debug, )?; ``` ### Change 3: Update instruction_rewriter::merge_and_rewrite signature **Location**: `instruction_rewriter.rs`, line 29-37 ```rust pub(super) fn merge_and_rewrite( builder: &mut crate::mir::builder::MirBuilder, mir_module: &MirModule, remapper: &mut JoinIrIdRemapper, value_to_func_name: &HashMap, function_params: &HashMap>, boundary: Option<&JoinInlineBoundary>, loop_header_phi_info: &mut super::loop_header_phi_builder::LoopHeaderPhiInfo, // NEW debug: bool, ) -> Result { // ... rest of function unchanged ... } ``` ### Change 4: Add latch tracking in tail call section **Location**: `instruction_rewriter.rs`, after line ~319 (after param bindings) ```rust // Second pass: Insert parameter bindings for tail calls // Phase 188-Impl-3: Use actual parameter ValueIds from target function if let Some((target_block, args)) = tail_call_target { if debug { eprintln!( "[cf_loop/joinir] Inserting param bindings for tail call to {:?}", target_block ); } // ... existing param binding code (unchanged) ... // ===== NEW Phase 33-16: Track latch incoming ===== if let Some(loop_var_name) = &boundary.and_then(|b| b.loop_var_name.as_ref()) { if !args.is_empty() { let latch_value = args[0]; // Updated loop variable loop_header_phi_info.set_latch_incoming( loop_var_name, target_block, // Loop header block latch_value, // i_next value ); if debug { eprintln!( "[cf_loop/joinir] Phase 33-16: Set latch incoming for '{}': {:?}", loop_var_name, latch_value ); } } } // Set terminator to Jump new_block.terminator = Some(MirInstruction::Jump { target: target_block, }); ``` ### Change 5: Replace exit_phi_inputs skip logic **Location**: `instruction_rewriter.rs`, lines 354-398 (replace entire block) ```rust MirInstruction::Return { value } => { // Phase 33-16: Use header PHI dst instead of undefined parameters if let Some(ret_val) = value { let remapped_val = remapper.get_value(*ret_val).unwrap_or(*ret_val); // Try to use header PHI dst (SSA-correct) if let Some(loop_var_name) = &boundary.and_then(|b| b.loop_var_name.as_ref()) { if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(loop_var_name) { if debug { eprintln!( "[cf_loop/joinir] Phase 33-16: Using loop header PHI {:?} for exit value", phi_dst ); } exit_phi_inputs.push((exit_block_id, phi_dst)); } else { // Fallback: use parameter (backward compat) if debug { eprintln!( "[cf_loop/joinir] Phase 33-16: No header PHI, fallback to parameter {:?}", remapped_val ); } exit_phi_inputs.push((exit_block_id, remapped_val)); } } else { // No loop_var_name: use parameter if debug { eprintln!( "[cf_loop/joinir] Phase 33-16: No loop_var_name, using parameter {:?}", remapped_val ); } exit_phi_inputs.push((exit_block_id, remapped_val)); } } MirInstruction::Jump { target: exit_block_id, } } ``` ### Change 6: Replace carrier_inputs skip logic **Location**: `instruction_rewriter.rs`, lines 400-431 (replace entire block) ```rust // Phase 33-13/16: Collect carrier exit values using header PHI dsts if let Some(boundary) = boundary { for binding in &boundary.exit_bindings { // Try to use header PHI dst if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(&binding.carrier_name) { if debug { eprintln!( "[cf_loop/joinir] Phase 33-16: Carrier '{}' using header PHI {:?}", binding.carrier_name, phi_dst ); } carrier_inputs.entry(binding.carrier_name.clone()) .or_insert_with(Vec::new) .push((exit_block_id, phi_dst)); } else if debug { eprintln!( "[cf_loop/joinir] Phase 33-16: No header PHI for carrier '{}', skipping", binding.carrier_name ); } } } ``` ### Change 7: Add Phase 4.5 finalize call **Location**: `mod.rs`, after Phase 5 (exit_phi_builder call) ```rust // Phase 5: Build exit PHI (expr result and carrier PHIs) let (exit_phi_result_id, carrier_phis) = exit_phi_builder::build_exit_phi( builder, merge_result.exit_block_id, &merge_result.exit_phi_inputs, &merge_result.carrier_inputs, debug, )?; // ===== Phase 4.5: Finalize loop header PHIs ===== LoopHeaderPhiBuilder::finalize(builder, &loop_header_phi_info, debug)?; // Phase 6: Reconnect boundary (if specified) // Phase 197-B: Pass remapper to enable per-carrier exit value lookup // Phase 33-10-Refactor-P3: Delegate to ExitLineOrchestrator // Phase 33-13: Pass carrier_phis for proper variable_map update if let Some(boundary) = boundary { exit_line::ExitLineOrchestrator::execute(builder, boundary, &carrier_phis, debug)?; } ``` --- ## Complete Flow Checklist 1. ✅ **Phase 3** (remap_values): ValueIds remapped 2. ✅ **Phase 3.5** (build): - Header PHI dsts allocated - Entry incoming set - Passed to Phase 4 3. ✅ **Phase 4** (instruction_rewriter): - Latch incoming set when processing tail calls - Exit values use header PHI dsts (not parameters) - Carrier exit values use header PHI dsts 4. ✅ **Phase 4.5** (finalize): - PHI instructions emitted into header block - Validation that all latch incoming are set 5. ✅ **Phase 5** (exit_phi_builder): - Exit PHI created from exit_phi_inputs - carrier_phis returned with PHI dsts 6. ✅ **Phase 6** (ExitLineOrchestrator): - variable_map updated with header PHI dsts --- ## Testing Commands ```bash # Build cargo build --release 2>&1 | head -50 # Test with debug output NYASH_JOINIR_DEBUG=1 ./target/release/nyash --dump-mir \ apps/tests/joinir_min_loop.hako 2>&1 | grep "Phase 33-16" # Check MIR structure ./target/release/nyash --emit-mir-json mir.json apps/tests/joinir_min_loop.hako jq '.functions[0].blocks[0].instructions[0:3]' mir.json # First 3 instructions (should be PHIs) ``` --- ## Summary Table | Phase | Component | Input | Output | Size | |-------|-----------|-------|--------|------| | 3 | remap_values | ValueId(0-10) | ValueId(100-110) | existing | | 3.5 | build | ValueId(100) | phi_dst=101 | 50 lines new | | 4 | merge_and_rewrite | loop_header_phi_info | latch_incoming set | +20 lines | | 4.5 | finalize | latch_incoming set | PHI emitted | 30 lines new | | 5 | build_exit_phi | exit_phi_inputs | carrier_phis | existing | | 6 | ExitLineOrchestrator | carrier_phis | var_map updated | existing | **Total changes**: ~6 locations, ~100 lines added/modified, 0 lines removed (backward compat)