Implementation: - Add make_pattern2_scope_manager() helper for DRY - Header conditions use ExprLowerer for supported patterns - Legacy fallback for unsupported patterns - Fail-Fast on supported patterns that fail Tests: - 4 new tests (all pass) - test_expr_lowerer_supports_simple_header_condition_i_less_literal - test_expr_lowerer_supports_header_condition_var_less_var - test_expr_lowerer_header_condition_generates_expected_instructions - test_pattern2_header_condition_via_exprlowerer Also: Archive old phase documentation (34k lines removed) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.7 KiB
JoinIR→MIR Merge Processing PHI Loss Analysis
Executive Summary
Problem: Pattern 4 (loop_continue_pattern4.hako) loses PHI instructions during JoinIR→MIR merge, while Pattern 3 (loop_if_phi.hako) preserves them correctly.
Critical Finding: Both patterns successfully create PHI instructions during JoinIR→MIR conversion, but Pattern 4's PHI disappears during the merge phase.
Test Cases
Pattern 3: loop_if_phi.hako ✅ PHI Preserved
loop(i <= 5) {
if (i % 2 == 1) { sum = sum + i } else { sum = sum + 0 }
i = i + 1
}
Lowerer: loop_with_if_phi_minimal.rs
JoinIR: Uses Select instruction for if-else PHI in loop body
Pattern 4: loop_continue_pattern4.hako ❌ PHI Lost
loop(i < 10) {
i = i + 1
if (i % 2 == 0) { continue }
sum = sum + i
}
Lowerer: loop_with_continue_minimal.rs
JoinIR: Uses Select instruction for continue vs. normal path (line 304)
PHI Creation Phase (JoinIR→MIR Conversion)
Pattern 3 - Select→PHI Conversion ✅
[joinir_block/handle_select] Created merge_block BasicBlockId(5) with 1 instructions
[joinir_block/handle_select] After insert: merge_block BasicBlockId(5) has 1 instructions
[joinir_block/finalize_block] Preserving 1 PHI instructions in block BasicBlockId(5)
[joinir/meta] Block BasicBlockId(5): 5 instructions (1 PHI)
Result: %23 = phi [%20, bb8], [%22, bb9] appears in final MIR bb10
Pattern 4 - Select→PHI Conversion ✅
[joinir_block/handle_select] Created merge_block BasicBlockId(5) with 1 instructions
[joinir_block/handle_select] After insert: merge_block BasicBlockId(5) has 1 instructions
[joinir_block/finalize_block] Preserving 1 PHI instructions in block BasicBlockId(5)
[joinir/meta] Block BasicBlockId(5): 3 instructions (1 PHI)
Result: PHI is created but does NOT appear in final MIR bb10!
Key Difference: Instruction Count
- Pattern 3: BasicBlockId(5) has 5 instructions (1 PHI + 4 others)
- Pattern 4: BasicBlockId(5) has 3 instructions (1 PHI + 2 others)
Final MIR Comparison
Pattern 3 - bb10 (from BasicBlockId(5))
bb10:
1: %23 = phi [%20, bb8], [%22, bb9] ← PHI PRESENT ✅
1: %24 = const 1
1: %25 = %11 Add %24
1: %11 = copy %25
1: %12 = copy %23
1: br label bb5
Pattern 4 - bb10 (from BasicBlockId(5))
bb10:
1: %8 = copy %14 ← NO PHI! ❌
1: br label bb5
Hypothesis: Merge Processing Difference
The merge processing in instruction_rewriter.rs (lines 117-213) should handle PHI instructions correctly:
for inst in &old_block.instructions {
// ... skip conditions ...
let remapped = remapper.remap_instruction(inst);
let remapped_with_blocks = match remapped {
MirInstruction::Phi { dst, inputs, type_hint: None } => {
// PHI block remapping (lines 183-196)
MirInstruction::Phi {
dst,
inputs: inputs.iter()
.map(|(bb, val)| (local_block_map.get(bb).copied().unwrap_or(*bb), *val))
.collect(),
type_hint: None,
}
}
other => other,
};
new_block.instructions.push(remapped_with_blocks); // Line 212
}
Possible Causes
Theory 1: Block Replacement
- Pattern 4's bb10 might be getting completely replaced instead of merged
- Check if
finalize_block()injoinir_block_converter.rs(lines 691-727) is being called differently
Theory 2: Tail Call Handling
- Pattern 4 has a tail call that might trigger different merge logic
- The tail call detection (lines 146-167) might affect how blocks are merged
- Pattern 3 may not have a tail call in the same position
Theory 3: Return Conversion
- BasicBlockId(5) has terminator
Return { value: Some(ValueId(99991)) } - This gets converted to
Jumpto exit block (lines 302-320) - Something in this conversion might be dropping instructions
Theory 4: Block Overwrite
- Pattern 4's shorter instruction count (3 vs 5) suggests instructions are being lost
- Check if the merge process overwrites the block instead of preserving PHI
Investigation Path
- Enable debug output for instruction_rewriter.rs merge processing
- Check finalize_block calls - are they different between patterns?
- Trace BasicBlockId(5) - what happens to it during merge?
- Compare Return terminator handling - does the conversion lose instructions?
- Check local_block_map - is BasicBlockId(5) being mapped correctly?
Next Steps
- Add detailed logging to
instruction_rewriter.rsaround line 122-213 - Trace the exact path Pattern 4's BasicBlockId(5) takes during merge
- Compare with Pattern 3's path to find the divergence point
- Check if the problem is in the initial block creation or later overwriting
Code Locations
- JoinIR Block Converter:
src/mir/join_ir_vm_bridge/joinir_block_converter.rshandle_select(): lines 407-484 (creates PHI)finalize_block(): lines 691-727 (preserves existing PHI)
- Merge Processing:
src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rsmerge_and_rewrite(): lines 18-405- PHI remapping: lines 183-196
- Pattern Lowerers:
- Pattern 3:
src/mir/join_ir/lowering/loop_with_if_phi_minimal.rs - Pattern 4:
src/mir/join_ir/lowering/loop_with_continue_minimal.rs(Select at line 304)
- Pattern 3:
Expected Outcome
Pattern 4's bb10 should contain:
bb10:
1: %XX = phi [%YY, bb8], [%ZZ, bb9] ← Missing PHI!
1: %8 = copy %14
1: br label bb5
The PHI instruction for sum_merged = Select(continue_cond, sum_param, sum_next) (line 304 in loop_with_continue_minimal.rs) is being lost during merge.
Status: Historical