feat(joinir): Phase 33-16 Loop Header PHI SSOT with TailCallKind refactoring
## Problem - joinir_min_loop.hako returned 0 instead of expected 2 - Entry block's tail call was redirected to itself (bb4 → bb4 self-loop) - JoinIR function parameters lack SSA definition when inlined ## Solution - Distinguish entry calls from back edges using TailCallKind enum - Entry block (LoopEntry) stays at target, back edges redirect to header PHI - Added explicit classification: LoopEntry, BackEdge, ExitJump ## Key Changes - instruction_rewriter.rs: TailCallKind enum + classify_tail_call() - Renamed is_entry_func_entry_block → is_loop_entry_point (clearer intent) - loop_header_phi_builder.rs: New module for header PHI generation - joinir_inline_boundary_injector.rs: Skip loop var Copy when header PHI handles it ## Verified - joinir_min_loop.hako: returns 2 ✅ - NYASH_SSA_UNDEF_DEBUG: no undefined errors ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -17,8 +17,9 @@ mod value_collector;
|
||||
mod instruction_rewriter;
|
||||
mod exit_phi_builder;
|
||||
pub mod exit_line;
|
||||
pub mod loop_header_phi_builder;
|
||||
|
||||
use crate::mir::{MirModule, ValueId};
|
||||
use crate::mir::{BasicBlockId, MirModule, ValueId};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||||
|
||||
/// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function
|
||||
@ -105,7 +106,84 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
// Phase 3: Remap ValueIds
|
||||
remap_values(builder, &used_values, &mut remapper, debug)?;
|
||||
|
||||
// Phase 3.5: Build loop header PHIs (if loop pattern with loop_var_name)
|
||||
//
|
||||
// We need to know PHI dsts before instruction_rewriter runs, so that:
|
||||
// 1. Tail call handling can set latch_incoming
|
||||
// 2. Return handling can use PHI dsts for exit values
|
||||
let (entry_func_name, entry_func) = mir_module
|
||||
.functions
|
||||
.iter()
|
||||
.next()
|
||||
.ok_or("JoinIR module has no functions (Phase 3.5)")?;
|
||||
let entry_block_remapped = remapper
|
||||
.get_block(entry_func_name, entry_func.entry_block)
|
||||
.ok_or_else(|| format!("Entry block not found for {} (Phase 3.5)", entry_func_name))?;
|
||||
|
||||
// Phase 33-16: Get host's current block as the entry edge (the block that jumps INTO the loop)
|
||||
let host_entry_block = builder.current_block.ok_or(
|
||||
"Phase 33-16: No current block when building header PHIs"
|
||||
)?;
|
||||
|
||||
let mut loop_header_phi_info = if let Some(boundary) = boundary {
|
||||
if let Some(loop_var_name) = &boundary.loop_var_name {
|
||||
// Phase 33-16: Get loop variable's initial value from HOST (not JoinIR's ValueId(0))
|
||||
// boundary.host_inputs[0] is the host ValueId that holds the initial loop var value
|
||||
let loop_var_init = boundary.host_inputs.first().copied().ok_or(
|
||||
"Phase 33-16: No host_inputs in boundary for loop_var_init"
|
||||
)?;
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 3.5: Building header PHIs for loop_var='{}' at {:?}",
|
||||
loop_var_name, entry_block_remapped
|
||||
);
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] loop_var_init={:?} (from boundary.host_inputs[0])",
|
||||
loop_var_init
|
||||
);
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] host_entry_block={:?} (where initial value comes from)",
|
||||
host_entry_block
|
||||
);
|
||||
}
|
||||
|
||||
let phi_info = loop_header_phi_builder::LoopHeaderPhiBuilder::build(
|
||||
builder,
|
||||
entry_block_remapped, // header_block (JoinIR's entry block = loop header)
|
||||
host_entry_block, // entry_block (host's block that jumps to loop header)
|
||||
loop_var_name,
|
||||
loop_var_init,
|
||||
&[], // No other carriers for Pattern 2 (loop var is the only carrier)
|
||||
boundary.expr_result.is_some(), // expr_result_is_loop_var
|
||||
debug,
|
||||
)?;
|
||||
|
||||
// Phase 33-16: Override remapper so that JoinIR's loop var parameter (ValueId(0))
|
||||
// references the PHI dst instead of a separate remapped value.
|
||||
// This ensures all uses of the loop variable in the loop body use the PHI result.
|
||||
if let Some(phi_dst) = phi_info.get_carrier_phi(loop_var_name) {
|
||||
// JoinIR uses ValueId(0) for loop var in loop_step's first parameter
|
||||
// After remapping, we want those uses to refer to the header PHI dst
|
||||
remapper.set_value(ValueId(0), phi_dst);
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-16: Override remap ValueId(0) → {:?} (PHI dst)",
|
||||
phi_dst
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
phi_info
|
||||
} else {
|
||||
loop_header_phi_builder::LoopHeaderPhiInfo::empty(entry_block_remapped)
|
||||
}
|
||||
} else {
|
||||
loop_header_phi_builder::LoopHeaderPhiInfo::empty(entry_block_remapped)
|
||||
};
|
||||
|
||||
// Phase 4: Merge blocks and rewrite instructions
|
||||
// Phase 33-16: Pass mutable loop_header_phi_info for latch_incoming tracking
|
||||
let merge_result = instruction_rewriter::merge_and_rewrite(
|
||||
builder,
|
||||
mir_module,
|
||||
@ -113,9 +191,28 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
&value_to_func_name,
|
||||
&function_params,
|
||||
boundary,
|
||||
&mut loop_header_phi_info,
|
||||
debug,
|
||||
)?;
|
||||
|
||||
// Phase 4.5: Finalize loop header PHIs (insert into header block)
|
||||
//
|
||||
// By now, instruction_rewriter has set latch_incoming for all carriers.
|
||||
// We can finalize the PHIs and insert them into the header block.
|
||||
if !loop_header_phi_info.carrier_phis.is_empty() {
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 4.5: Finalizing {} header PHIs",
|
||||
loop_header_phi_info.carrier_phis.len()
|
||||
);
|
||||
}
|
||||
loop_header_phi_builder::LoopHeaderPhiBuilder::finalize(
|
||||
builder,
|
||||
&loop_header_phi_info,
|
||||
debug,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Phase 5: Build exit PHI (expr result and carrier PHIs)
|
||||
// Phase 33-13: Now also builds carrier PHIs and returns their mapping
|
||||
let (exit_phi_result_id, carrier_phis) = exit_phi_builder::build_exit_phi(
|
||||
@ -137,14 +234,8 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
let exit_block_id = merge_result.exit_block_id;
|
||||
|
||||
// Jump from current block to entry function's entry block
|
||||
let (entry_func_name, entry_func) = mir_module
|
||||
.functions
|
||||
.iter()
|
||||
.next()
|
||||
.ok_or("JoinIR module has no functions")?;
|
||||
let entry_block = remapper
|
||||
.get_block(entry_func_name, entry_func.entry_block)
|
||||
.ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?;
|
||||
// (Reuse entry_func_name and entry_block_remapped from Phase 3.5)
|
||||
let entry_block = entry_block_remapped;
|
||||
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Entry function name: {}", entry_func_name);
|
||||
|
||||
Reference in New Issue
Block a user