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:
219
src/mir/builder/control_flow/joinir/merge/header_phi_prebuild.rs
Normal file
219
src/mir/builder/control_flow/joinir/merge/header_phi_prebuild.rs
Normal 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 ¶m 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
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -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 ¶m 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
|
||||
|
||||
Reference in New Issue
Block a user