refactor(joinir): Phase 287 P0.4 - Extract header_phi_prebuild

Extract header PHI pre-build orchestration to header_phi_prebuild.rs
(~220 lines). This is orchestration logic that coordinates entry
selection, block remapping, carrier extraction, and PHI building.

Changes:
- NEW: merge/header_phi_prebuild.rs (220 lines)
- MOD: merge/mod.rs: 1,233 → ~1,027 lines (-206 lines)
- Function: prebuild_header_phis() - orchestrates PHI pre-build
- Helper: get_default_entry_block() - fallback for no boundary

Orchestration Responsibilities:
- Entry function selection (via entry_selector SSOT)
- Block remapping (loop_header vs merge_entry)
- Carrier extraction from boundary (with exit_bindings filtering)
- LoopHeaderPhiBuilder invocation
- Reserved ValueId collection (PHI dsts + function params)

Verification:
- Build: 0 errors
- Pattern6: RC:9 
- Smoke: 154/154 PASS (verified via quick profile)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-27 10:21:28 +09:00
parent fb2ac627da
commit 5ee3b62042
2 changed files with 230 additions and 192 deletions

View File

@ -0,0 +1,219 @@
//! Phase 287 P0.4: Loop header PHI pre-build orchestration
//!
//! Coordinates the pre-building of loop header PHIs BEFORE value remapping.
//! This prevents ValueId conflicts where a Const instruction could get a
//! ValueId that will later be used as a PHI dst, causing carrier corruption.
//!
//! This is orchestration logic, not a pure function - it coordinates:
//! - Entry function selection (via entry_selector)
//! - Block remapping
//! - Carrier extraction from boundary
//! - LoopHeaderPhiBuilder invocation
//! - Reserved ValueId collection
use super::{entry_selector, LoopHeaderPhiBuilder, LoopHeaderPhiInfo};
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
use crate::mir::{BasicBlockId, MirModule, ValueId};
use std::collections::{BTreeMap, BTreeSet, HashSet};
/// Pre-build loop header PHIs and reserve ValueIds
///
/// Returns:
/// - LoopHeaderPhiInfo: Pre-built PHI metadata with allocated dst ValueIds
/// - BasicBlockId: Merge entry block (where host will jump to)
/// - HashSet<ValueId>: All reserved ValueIds (PHI dsts + function params)
pub(super) fn prebuild_header_phis(
builder: &mut crate::mir::builder::MirBuilder,
mir_module: &MirModule,
boundary: Option<&JoinInlineBoundary>,
remapper: &crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper,
function_params: &BTreeMap<String, Vec<ValueId>>,
debug: bool,
) -> Result<(LoopHeaderPhiInfo, BasicBlockId, HashSet<ValueId>), String> {
let trace = super::trace::trace();
// Build loop header PHI info if we have a boundary with loop_var_name
let (mut loop_header_phi_info, merge_entry_block) = if let Some(boundary) = boundary {
if let Some(loop_var_name) = &boundary.loop_var_name {
// Phase 287 P0.4: Delegate entry selection to entry_selector (SSOT)
let loop_step_func_name =
entry_selector::select_loop_step_func_name(mir_module, boundary)?;
let loop_step_func =
entry_selector::get_function(&mir_module.functions, loop_step_func_name)?;
let (entry_func_name, entry_func) =
entry_selector::select_merge_entry_func(mir_module, boundary, loop_step_func_name)?;
// Loop header block (for PHI placement)
let loop_header_block = remapper
.get_block(loop_step_func_name, loop_step_func.entry_block)
.ok_or_else(|| {
format!(
"Loop header block not found for {} (Phase 287 P0.4)",
loop_step_func_name
)
})?;
// Merge entry block (for Jump target and PHI entry edge)
let entry_block_remapped = remapper
.get_block(entry_func_name, entry_func.entry_block)
.ok_or_else(|| {
format!(
"Entry block not found for {} (Phase 287 P0.4)",
entry_func_name
)
})?;
// Get host's current block as the entry edge
let _host_entry_block = builder
.current_block
.ok_or("Phase 287 P0.4: No current block when building header PHIs")?;
// Phase 256.7-fix: Get loop variable's initial value from carrier_info if available
let loop_var_init = if let Some(ref carrier_info) = boundary.carrier_info {
carrier_info.loop_var_id
} else {
boundary
.host_inputs
.first()
.copied()
.ok_or("Phase 287 P0.4: No host_inputs or carrier_info in boundary for loop_var_init")?
};
// Extract carriers with their initialization strategy
let exit_carrier_names: BTreeSet<&str> = boundary
.exit_bindings
.iter()
.map(|b| b.carrier_name.as_str())
.collect();
let other_carriers: Vec<(
String,
ValueId,
crate::mir::join_ir::lowering::carrier_info::CarrierInit,
crate::mir::join_ir::lowering::carrier_info::CarrierRole,
)> = if let Some(ref carrier_info) = boundary.carrier_info {
carrier_info
.carriers
.iter()
.filter(|c| c.name != *loop_var_name)
.filter(|c| exit_carrier_names.contains(c.name.as_str()))
.map(|c| (c.name.clone(), c.host_id, c.init, c.role))
.collect()
} else {
boundary
.exit_bindings
.iter()
.filter(|b| b.carrier_name != *loop_var_name)
.map(|b| {
(
b.carrier_name.clone(),
b.host_slot,
crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost,
crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
)
})
.collect()
};
// Log entry function and block separation
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 287 P0.4: merge_entry_func='{}', merge_entry_block={:?}, loop_header_block={:?}",
entry_func_name, entry_block_remapped, loop_header_block
),
debug,
);
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 287 P0.4: Pre-building header PHIs for loop_var='{}' at {:?}",
loop_var_name, loop_header_block
),
debug,
);
trace.stderr_if(
&format!(
"[cf_loop/joinir] loop_var_init={:?}, carriers={:?}",
loop_var_init,
other_carriers
.iter()
.map(|(n, _, _, _)| n.as_str())
.collect::<Vec<_>>()
),
debug,
);
// Build PHI info (this allocates PHI dst ValueIds)
let phi_info = LoopHeaderPhiBuilder::build(
builder,
loop_header_block,
entry_block_remapped,
loop_var_name,
loop_var_init,
&other_carriers,
&boundary.loop_invariants,
boundary.expr_result.is_some(),
debug,
)?;
(phi_info, entry_block_remapped)
} else {
let default_block = get_default_entry_block(mir_module, remapper)?;
(LoopHeaderPhiInfo::empty(default_block), default_block)
}
} else {
let default_block = get_default_entry_block(mir_module, remapper)?;
(LoopHeaderPhiInfo::empty(default_block), default_block)
};
// Collect reserved PHI dst ValueIds
let reserved_phi_dsts = loop_header_phi_info.reserved_value_ids();
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 287 P0.4: Reserved PHI dsts: {:?}",
reserved_phi_dsts
),
debug && !reserved_phi_dsts.is_empty(),
);
// Also reserve JoinIR parameter ValueIds to avoid collisions
let mut reserved_value_ids = reserved_phi_dsts.clone();
for params in function_params.values() {
for &param in params {
reserved_value_ids.insert(param);
}
}
// Set reserved IDs in MirBuilder so next_value_id() skips them
builder.comp_ctx.reserved_value_ids = reserved_value_ids.clone();
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 287 P0.4: Set builder.comp_ctx.reserved_value_ids = {:?}",
builder.comp_ctx.reserved_value_ids
),
debug && !builder.comp_ctx.reserved_value_ids.is_empty(),
);
Ok((loop_header_phi_info, merge_entry_block, reserved_value_ids))
}
/// Get default entry block when no boundary or loop_var_name is present
fn get_default_entry_block(
mir_module: &MirModule,
remapper: &crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper,
) -> Result<BasicBlockId, String> {
let (first_func_name, first_func) = mir_module
.functions
.iter()
.next()
.ok_or("JoinIR module has no functions (Phase 287 P0.4)")?;
remapper
.get_block(first_func_name, first_func.entry_block)
.ok_or_else(|| {
format!(
"Entry block not found for {} (Phase 287 P0.4)",
first_func_name
)
})
}

View File

@ -19,6 +19,7 @@ pub(super) mod contract_checks; // Phase 256 P1.5-DBG: Exposed for patterns to a
mod debug_assertions; // Phase 286C-4.3: Debug-only assertions (split from contract_checks)
mod entry_selector; // Phase 287 P0.3: Entry function selection (SSOT)
pub mod exit_args_collector; // Phase 118: Exit args collection box
mod header_phi_prebuild; // Phase 287 P0.4: Loop header PHI pre-build orchestration
pub mod exit_line;
mod exit_phi_builder;
mod expr_result_resolver;
@ -351,202 +352,20 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
}
}
// Phase 201-A: Build loop header PHI info BEFORE Phase 3
// Phase 201-A + Phase 287 P0.4: Pre-build loop header PHIs BEFORE Phase 3
//
// We need to allocate PHI dst ValueIds before remap_values() runs,
// to prevent conflicts where a Const instruction gets a ValueId that
// will later be used as a PHI dst, causing carrier value corruption.
//
// This is a reordering of Phase 3 and Phase 3.5 logic.
//
// Phase 256.7-fix: Track two blocks separately:
// - loop_header_block: where PHIs are placed (loop_step's entry)
// - merge_entry_block: where host Jumps to and PHI entry edge comes from
// (main's entry when condition_bindings exist, otherwise same as loop_header)
let (mut loop_header_phi_info, merge_entry_block) = if let Some(boundary) = boundary {
if let Some(loop_var_name) = &boundary.loop_var_name {
// Phase 287 P0.3: Delegate to entry_selector (SSOT)
let loop_step_func_name = entry_selector::select_loop_step_func_name(mir_module, boundary)?;
let loop_step_func = entry_selector::get_function(&mir_module.functions, loop_step_func_name)?;
// Phase 256.7-fix + Phase 287 P0.3: Determine merge_entry_block
// When main has condition_bindings as params, we enter through main first
// (for boundary Copies), then main's tail call jumps to loop_step.
let (entry_func_name, entry_func) =
entry_selector::select_merge_entry_func(mir_module, boundary, loop_step_func_name)?;
// Loop header block (for PHI placement)
let loop_header_block = remapper
.get_block(loop_step_func_name, loop_step_func.entry_block)
.ok_or_else(|| {
format!(
"Loop header block not found for {} (Phase 256.7-fix)",
loop_step_func_name
)
})?;
// Merge entry block (for Jump target and PHI entry edge)
let entry_block_remapped = remapper
.get_block(entry_func_name, entry_func.entry_block)
.ok_or_else(|| {
format!(
"Entry block not found for {} (Phase 201-A)",
entry_func_name
)
})?;
// Get host's current block as the entry edge
let host_entry_block = builder
.current_block
.ok_or("Phase 201-A: No current block when building header PHIs")?;
// Phase 256.7-fix: Get loop variable's initial value from carrier_info if available
// For if-sum patterns, host_inputs contains condition_bindings, not loop_var init
let loop_var_init = if let Some(ref carrier_info) = boundary.carrier_info {
// Use carrier_info.loop_var_id (Phase 256.7-fix)
carrier_info.loop_var_id
} else {
// Fallback: legacy patterns use host_inputs[0]
boundary
.host_inputs
.first()
.copied()
.ok_or("Phase 201-A: No host_inputs or carrier_info in boundary for loop_var_init")?
};
// Phase 228-4: Extract carriers with their initialization strategy
// Phase 256.7-fix: Build set of exit_binding carrier names for filtering
// Only include carriers that are actually modified in the loop (have exit_bindings)
let exit_carrier_names: std::collections::BTreeSet<&str> = boundary
.exit_bindings
.iter()
.map(|b| b.carrier_name.as_str())
.collect();
let other_carriers: Vec<(
String,
ValueId,
crate::mir::join_ir::lowering::carrier_info::CarrierInit,
crate::mir::join_ir::lowering::carrier_info::CarrierRole,
)> = if let Some(ref carrier_info) = boundary.carrier_info {
// Use carrier_info if available (Phase 228)
// Phase 256.7-fix: Filter to only include carriers that are in exit_bindings
// This excludes function parameters that are in carrier_info but not modified in loop
carrier_info
.carriers
.iter()
.filter(|c| c.name != *loop_var_name)
.filter(|c| exit_carrier_names.contains(c.name.as_str()))
.map(|c| (c.name.clone(), c.host_id, c.init, c.role))
.collect()
} else {
// Fallback: exit_bindings から取得(既存動作)
boundary
.exit_bindings
.iter()
.filter(|b| b.carrier_name != *loop_var_name)
.map(|b| {
(
b.carrier_name.clone(),
b.host_slot,
crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost,
crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
)
})
.collect()
};
// Phase 256.7-fix: Log entry function and block separation
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 256.7-fix: merge_entry_func='{}', merge_entry_block={:?}, loop_header_block={:?}",
entry_func_name, entry_block_remapped, loop_header_block
),
debug,
);
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 201-A: Pre-building header PHIs for loop_var='{}' at {:?}",
loop_var_name, loop_header_block
),
debug,
);
trace.stderr_if(
&format!(
"[cf_loop/joinir] loop_var_init={:?}, carriers={:?}",
loop_var_init,
other_carriers
.iter()
.map(|(n, _, _, _)| n.as_str())
.collect::<Vec<_>>()
),
debug,
);
// Build PHI info (this allocates PHI dst ValueIds)
// Phase 256.7-fix:
// - loop_header_block: where PHIs are placed (loop_step's entry)
// - entry_block_remapped: PHI entry edge source AND Jump target (main's entry when condition_bindings exist)
let phi_info = LoopHeaderPhiBuilder::build(
builder,
loop_header_block,
entry_block_remapped,
loop_var_name,
loop_var_init,
&other_carriers,
&boundary.loop_invariants, // Phase 255 P2: Add loop invariants
boundary.expr_result.is_some(),
debug,
)?;
// Phase 256.7-fix: Return merge_entry_block for the final Jump
(phi_info, entry_block_remapped)
} else {
let default_block = remapper
.get_block(
mir_module.functions.iter().next().unwrap().0,
mir_module.functions.iter().next().unwrap().1.entry_block,
)
.unwrap();
(LoopHeaderPhiInfo::empty(default_block), default_block)
}
} else {
let default_block = remapper
.get_block(
mir_module.functions.iter().next().unwrap().0,
mir_module.functions.iter().next().unwrap().1.entry_block,
)
.unwrap();
(LoopHeaderPhiInfo::empty(default_block), default_block)
};
// Phase 201-A: Get reserved PHI dst ValueIds and set in MirBuilder
let reserved_phi_dsts = loop_header_phi_info.reserved_value_ids();
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 201-A: Reserved PHI dsts: {:?}",
reserved_phi_dsts
),
debug && !reserved_phi_dsts.is_empty(),
);
// Phase 201-A: Also reserve JoinIR parameter ValueIds to avoid collisions
let mut reserved_value_ids = reserved_phi_dsts.clone();
for params in function_params.values() {
for &param in params {
reserved_value_ids.insert(param);
}
}
// Phase 201-A: Set reserved IDs in MirBuilder so next_value_id() skips them
// This protects against carrier corruption when break conditions emit Const instructions
builder.comp_ctx.reserved_value_ids = reserved_value_ids.clone();
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 201-A: Set builder.comp_ctx.reserved_value_ids = {:?}",
builder.comp_ctx.reserved_value_ids
),
debug && !builder.comp_ctx.reserved_value_ids.is_empty(),
);
let (mut loop_header_phi_info, merge_entry_block, reserved_value_ids) =
header_phi_prebuild::prebuild_header_phis(
builder,
mir_module,
boundary,
&remapper,
&function_params,
debug,
)?;
// Phase 3: Remap ValueIds (with reserved PHI dsts protection)
// Phase 287 P0.2: Delegated to value_remapper module