Files
hakorune/src/mir/builder/control_flow/joinir/merge/mod.rs
nyash-codex 95f3aa429e refactor(joinir): Extract legacy binding path to routing_legacy_binding.rs
Phase 179-A Step 2: Separate LoopFrontendBinding JSON construction logic
into dedicated module for better organization.

Changes:
- New file: routing_legacy_binding.rs (223 lines)
- routing.rs: cf_loop_joinir_impl() simplified to 15 lines (delegates to legacy path)
- Routing now clearly separates pattern-based vs. legacy binding paths

Benefits:
- Clear separation of concerns (pattern router vs. legacy whitelist)
- routing.rs reduced from 364 to 146 lines (60% reduction)
- Legacy path isolated for future deprecation
2025-12-08 18:36:13 +09:00

762 lines
32 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! JoinIR MIR Block Merging Coordinator
//!
//! This module coordinates the merging of JoinIR-generated MIR functions
//! into the host MIR builder. The process is broken into 6 phases:
//!
//! 1. Block ID allocation (block_allocator.rs)
//! 2. Value collection (value_collector.rs)
//! 3. ValueId remapping (uses JoinIrIdRemapper)
//! 4. Instruction rewriting (instruction_rewriter.rs)
//! 5. Exit PHI construction (exit_phi_builder.rs)
//! 6. Boundary reconnection (inline in this file)
//!
//! Phase 4 Refactoring: Breaking down 714-line merge_joinir_mir_blocks() into focused modules
mod block_allocator;
mod value_collector;
mod instruction_rewriter;
mod exit_phi_builder;
pub mod exit_line;
mod loop_header_phi_info;
mod loop_header_phi_builder;
mod tail_call_classifier;
mod merge_result;
// Phase 33-17: Re-export for use by other modules
pub use loop_header_phi_info::LoopHeaderPhiInfo;
pub use loop_header_phi_builder::LoopHeaderPhiBuilder;
use crate::mir::{MirModule, ValueId};
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
/// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function
///
/// # Phase 189: Multi-Function MIR Merge
///
/// This merges JoinIR-generated blocks by:
/// 1. Remapping all block IDs across ALL functions to avoid conflicts
/// 2. Remapping all value IDs across ALL functions to avoid conflicts
/// 3. Adding all blocks from all functions to current_function
/// 4. Jumping from current_block to the entry block
/// 5. Converting Return → Jump to exit block for all functions
///
/// **Multi-Function Support** (Phase 189):
/// - Pattern 1 (Simple While) generates 3 functions: entry + loop_step + k_exit
/// - All functions are flattened into current_function with global ID remapping
/// - Single exit block receives all Return instructions from all functions
///
/// # Phase 188-Impl-3: JoinInlineBoundary Support
///
/// When `boundary` is provided, injects Copy instructions at the entry block
/// to connect host ValueIds to JoinIR local ValueIds:
///
/// ```text
/// entry_block:
/// // Injected by boundary
/// ValueId(100) = Copy ValueId(4) // join_input → host_input
/// // Original JoinIR instructions follow...
/// ```
///
/// This enables clean separation: JoinIR uses local IDs (0,1,2...),
/// host uses its own IDs, and Copy instructions bridge the gap.
///
/// # Returns
///
/// Returns `Ok(Some(exit_phi_id))` if the merged JoinIR functions have return values
/// that were collected into an exit block PHI. さらに、`boundary` に
/// host_outputs が指定されている場合は、exit PHI の結果をホスト側の
/// SSA スロットへ再接続するvariable_map 内の ValueId を更新する)。
pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
builder: &mut crate::mir::builder::MirBuilder,
mir_module: &MirModule,
boundary: Option<&JoinInlineBoundary>,
debug: bool,
) -> Result<Option<ValueId>, String> {
if debug {
eprintln!(
"[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions",
mir_module.functions.len()
);
}
// Phase 1: Allocate block IDs for all functions
// Phase 177-3: block_allocator now returns exit_block_id to avoid conflicts
let (mut remapper, exit_block_id) = block_allocator::allocate_blocks(builder, mir_module, debug)?;
// Phase 2: Collect values from all functions
let (mut used_values, value_to_func_name, function_params) =
value_collector::collect_values(mir_module, &remapper, debug)?;
// Phase 171-fix: Add condition_bindings' join_values to used_values for remapping
if let Some(boundary) = boundary {
for binding in &boundary.condition_bindings {
if debug {
eprintln!(
"[cf_loop/joinir] Phase 171-fix: Adding condition binding '{}' JoinIR {:?} to used_values",
binding.name, binding.join_value
);
}
used_values.insert(binding.join_value);
}
// Phase 172-3: Add exit_bindings' join_exit_values to used_values for remapping
for binding in &boundary.exit_bindings {
if debug {
eprintln!(
"[cf_loop/joinir] Phase 172-3: Adding exit binding '{}' JoinIR {:?} to used_values",
binding.carrier_name, binding.join_exit_value
);
}
used_values.insert(binding.join_exit_value);
}
}
// Phase 3: Remap ValueIds
remap_values(builder, &used_values, &mut remapper, debug)?;
// Phase 177-3 DEBUG: Verify remapper state after Phase 3
eprintln!("[DEBUG-177] === Remapper state after Phase 3 ===");
eprintln!("[DEBUG-177] used_values count: {}", used_values.len());
for value_id in &used_values {
if let Some(remapped) = remapper.get_value(*value_id) {
eprintln!("[DEBUG-177] JoinIR {:?} → Host {:?}", value_id, remapped);
} else {
eprintln!("[DEBUG-177] JoinIR {:?} → NOT FOUND ❌", value_id);
}
}
// Check condition_bindings specifically
if let Some(boundary) = boundary {
eprintln!("[DEBUG-177] === Condition bindings check ===");
for binding in &boundary.condition_bindings {
let lookup_result = remapper.get_value(binding.join_value);
eprintln!(
"[DEBUG-177] '{}': JoinIR {:?}{:?}",
binding.name, binding.join_value, lookup_result
);
}
}
eprintln!("[DEBUG-177] ==============================");
// 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
);
}
// Phase 33-20: Extract other carriers from exit_bindings
// Skip the loop variable (it's handled separately) and collect other carriers
eprintln!(
"[cf_loop/joinir] Phase 33-20 DEBUG: exit_bindings count={}, loop_var_name={:?}",
boundary.exit_bindings.len(),
loop_var_name
);
for b in boundary.exit_bindings.iter() {
eprintln!(
"[cf_loop/joinir] Phase 33-20 DEBUG: exit_binding: carrier_name={:?}, host_slot={:?}",
b.carrier_name, b.host_slot
);
}
let other_carriers: Vec<(String, ValueId)> = boundary.exit_bindings
.iter()
.filter(|b| b.carrier_name != *loop_var_name)
.map(|b| (b.carrier_name.clone(), b.host_slot))
.collect();
if debug && !other_carriers.is_empty() {
eprintln!(
"[cf_loop/joinir] Phase 33-20: Found {} other carriers from exit_bindings: {:?}",
other_carriers.len(),
other_carriers.iter().map(|(n, _)| n.as_str()).collect::<Vec<_>>()
);
}
let phi_info = 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,
&other_carriers, // Phase 33-20: Pass other carriers from exit_bindings
boundary.expr_result.is_some(), // expr_result_is_loop_var
debug,
)?;
// Phase 33-21: Override remapper for loop_step's parameters
//
// JoinIR generates separate parameter ValueIds for each function:
// - main(): ValueId(0), ValueId(1), ... for (i_init, carrier1_init, ...)
// - loop_step(): ValueId(3), ValueId(4), ... for (i_param, carrier1_param, ...)
//
// The loop body uses loop_step's parameters, so we need to remap THOSE
// to the header PHI dsts, not main()'s parameters.
//
// We get loop_step's parameters from function_params collected earlier.
// Phase 33-21: Override remapper for ALL functions' parameters
//
// JoinIR generates separate parameter ValueIds for each function:
// - main (join_func_0): ValueId(0), ValueId(1), ... for (i_init, carrier1_init, ...)
// - loop_step (join_func_1): ValueId(3), ValueId(4), ... for (i_param, carrier1_param, ...)
//
// ALL of these need to be mapped to header PHI dsts so that:
// 1. condition evaluation uses PHI result
// 2. loop body uses PHI result
// 3. tail call args are correctly routed
// Phase 177-3 fix: Protect condition-ONLY bindings from being overridden to PHI dsts
//
// Problem: condition_bindings may contain:
// 1. True condition-only variables (e.g., 'limit' in loop(i < limit)) - NOT carriers
// 2. Body-only carriers added by Phase 176-5 (e.g., 'result') - ARE carriers
//
// We must ONLY protect (1), not (2), because:
// - Condition-only vars should keep their HOST mapping (e.g., limit = %8)
// - Body-only carriers MUST be remapped to PHI dsts (e.g., result = %24)
//
// Solution: Protect condition_bindings that are NOT in exit_bindings (i.e., not carriers)
let carrier_names: std::collections::HashSet<&str> = boundary
.exit_bindings
.iter()
.map(|eb| eb.carrier_name.as_str())
.collect();
let condition_binding_ids: std::collections::HashSet<ValueId> = boundary
.condition_bindings
.iter()
.filter(|cb| !carrier_names.contains(cb.name.as_str()))
.map(|cb| cb.join_value)
.collect();
if !condition_binding_ids.is_empty() {
eprintln!(
"[cf_loop/joinir] Phase 177-3: Protected ValueIds (condition-only, not carriers): {:?}",
condition_binding_ids
);
for cb in &boundary.condition_bindings {
let is_carrier = carrier_names.contains(cb.name.as_str());
eprintln!(
"[cf_loop/joinir] Phase 177-3: '{}': JoinIR {:?} (carrier={})",
cb.name, cb.join_value, is_carrier
);
}
}
let main_func_name = "join_func_0";
let loop_step_func_name = "join_func_1";
if function_params.get(main_func_name).is_none() {
eprintln!(
"[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}",
main_func_name,
function_params.keys().collect::<Vec<_>>()
);
}
if let Some(main_params) = function_params.get(main_func_name) {
eprintln!(
"[DEBUG-177] Phase 33-21: main ({}) params: {:?}",
main_func_name, main_params
);
eprintln!(
"[DEBUG-177] Phase 33-21: carrier_phis count: {}, names: {:?}",
phi_info.carrier_phis.len(),
phi_info.carrier_phis.iter().map(|(n, _)| n.as_str()).collect::<Vec<_>>()
);
// Map main's parameters to header PHI dsts
// main params: [i_init, carrier1_init, ...]
// carrier_phis: [("i", entry), ("sum", entry), ...]
for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() {
if let Some(&main_param) = main_params.get(idx) {
// Phase 177-3: Don't override condition_bindings
if condition_binding_ids.contains(&main_param) {
eprintln!(
"[cf_loop/joinir] Phase 177-3: Skipping override for condition_binding {:?} ('{}')",
main_param, carrier_name
);
continue;
}
eprintln!(
"[DEBUG-177] Phase 33-21: REMAP main param[{}] {:?}{:?} ('{}')",
idx, main_param, entry.phi_dst, carrier_name
);
remapper.set_value(main_param, entry.phi_dst);
}
}
}
// Phase 177-3-B: Handle body-only carriers
// These are carriers in carrier_phis that are NOT in main function params.
// They appear in condition_bindings (added by Phase 176-5) but need PHI remapping.
for (carrier_name, entry) in &phi_info.carrier_phis {
// Check if this carrier has a condition_binding
if let Some(binding) = boundary.condition_bindings.iter().find(|cb| cb.name == *carrier_name) {
// Skip if it's a true condition-only variable (already protected above)
if condition_binding_ids.contains(&binding.join_value) {
continue;
}
// This is a body-only carrier - remap it to PHI dst
eprintln!(
"[cf_loop/joinir] Phase 177-3-B: Body-only carrier '{}': JoinIR {:?} → PHI {:?}",
carrier_name, binding.join_value, entry.phi_dst
);
remapper.set_value(binding.join_value, entry.phi_dst);
}
}
// Map loop_step's parameters
// DEBUG-177: Always log function_params keys to diagnose multi-carrier issue
eprintln!(
"[DEBUG-177] Phase 33-21: function_params keys: {:?}",
function_params.keys().collect::<Vec<_>>()
);
if function_params.get(loop_step_func_name).is_none() {
eprintln!(
"[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}",
loop_step_func_name,
function_params.keys().collect::<Vec<_>>()
);
}
if let Some(loop_step_params) = function_params.get(loop_step_func_name) {
// DEBUG-177: Always log loop_step params
eprintln!(
"[DEBUG-177] Phase 33-21: loop_step ({}) params: {:?}",
loop_step_func_name, loop_step_params
);
// Phase 177-FIX: Process loop_step params but skip if already mapped
//
// We use a name-based approach: for each carrier_phi, check if
// its join_value was already set in Phase 177-3-B (body-only carriers).
// Only process loop_step params for carriers NOT already handled.
for loop_step_param in loop_step_params {
// Phase 177-3: Don't override condition_bindings
if condition_binding_ids.contains(loop_step_param) {
eprintln!(
"[DEBUG-177] Phase 177-FIX: Skipping condition_binding {:?}",
loop_step_param
);
continue;
}
// Find which carrier this param belongs to by matching join_value
// Check if this param was already handled by Phase 177-3-B
let already_mapped = boundary.condition_bindings.iter().any(|cb| {
cb.join_value == *loop_step_param &&
phi_info.carrier_phis.iter().any(|(name, _)| name == &cb.name)
});
if already_mapped {
eprintln!(
"[DEBUG-177] Phase 177-FIX: Skipping {:?} (already mapped by Phase 177-3-B)",
loop_step_param
);
continue;
}
// Phase 177-STRUCT-2: Use carrier_order for index-based matching
//
// Problem: BTreeMap iterates in alphabetical order, but JoinIR
// generates params in exit_bindings order.
//
// Solution: Use carrier_order (Vec<String>) which preserves insertion order.
if let Some(param_idx) = loop_step_params.iter().position(|p| p == loop_step_param) {
// Map params[i] to carrier_order[i]
if let (Some(carrier_name), Some(entry)) = (
phi_info.get_carrier_at_index(param_idx),
phi_info.get_entry_at_index(param_idx),
) {
eprintln!(
"[DEBUG-177] Phase 177-STRUCT-2: REMAP loop_step param[{}] {:?}{:?} (carrier '{}')",
param_idx, loop_step_param, entry.phi_dst, carrier_name
);
remapper.set_value(*loop_step_param, entry.phi_dst);
}
}
}
}
if function_params.get(main_func_name).is_none() && function_params.get(loop_step_func_name).is_none() {
// Fallback: Use old behavior (ValueId(0), ValueId(1), ...)
// This handles patterns that don't have loop_step function
if let Some(phi_dst) = phi_info.get_carrier_phi(loop_var_name) {
// Phase 177-3: Don't override condition_bindings
if !condition_binding_ids.contains(&ValueId(0)) {
remapper.set_value(ValueId(0), phi_dst);
if debug {
eprintln!(
"[cf_loop/joinir] Phase 33-16 fallback: Override remap ValueId(0) → {:?} (PHI dst)",
phi_dst
);
}
} else {
eprintln!(
"[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding ValueId(0)"
);
}
}
// Phase 177-STRUCT-2: Use carrier_order for deterministic iteration
for (idx, carrier_name) in phi_info.carrier_order.iter().enumerate() {
if carrier_name == loop_var_name {
continue;
}
let entry = match phi_info.carrier_phis.get(carrier_name) {
Some(e) => e,
None => continue,
};
let join_value_id = ValueId(idx as u32);
// Phase 177-3: Don't override condition_bindings
if !condition_binding_ids.contains(&join_value_id) {
remapper.set_value(join_value_id, entry.phi_dst);
if debug {
eprintln!(
"[cf_loop/joinir] Phase 33-20 fallback: Override remap {:?}{:?} (carrier '{}' PHI dst)",
join_value_id, entry.phi_dst, carrier_name
);
}
} else {
eprintln!(
"[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding {:?} ('{}')",
join_value_id, carrier_name
);
}
}
}
// Phase 177-3 DEBUG: Check remapper after Phase 33-21 overrides
eprintln!("[DEBUG-177] === Remapper state after Phase 33-21 ===");
for binding in &boundary.condition_bindings {
let lookup_result = remapper.get_value(binding.join_value);
eprintln!(
"[DEBUG-177] '{}': JoinIR {:?}{:?} (after 33-21)",
binding.name, binding.join_value, lookup_result
);
}
phi_info
} else {
LoopHeaderPhiInfo::empty(entry_block_remapped)
}
} else {
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
// Phase 177-3: Pass exit_block_id from allocator to avoid conflicts
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,
exit_block_id,
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()
);
}
LoopHeaderPhiBuilder::finalize(
builder,
&loop_header_phi_info,
debug,
)?;
}
// Phase 5: Build exit PHI (expr result only, not carrier PHIs)
// Phase 33-20: Carrier PHIs are now taken from header PHI info, not exit block
let (exit_phi_result_id, _exit_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 33-20: Build carrier_phis from header PHI info instead of exit PHI builder
// The header PHI dst IS the current value of each carrier, and it's SSA-valid
// because it's defined at the loop header and dominates the exit block.
let carrier_phis: std::collections::BTreeMap<String, ValueId> = loop_header_phi_info
.carrier_phis
.iter()
.map(|(name, entry)| (name.clone(), entry.phi_dst))
.collect();
if debug && !carrier_phis.is_empty() {
eprintln!(
"[cf_loop/joinir] Phase 33-20: Using header PHI dsts for variable_map: {:?}",
carrier_phis.iter().map(|(n, v)| (n.as_str(), v)).collect::<Vec<_>>()
);
}
// 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-20: Now uses header PHI dsts instead of exit PHI dsts
if let Some(boundary) = boundary {
exit_line::ExitLineOrchestrator::execute(builder, boundary, &carrier_phis, debug)?;
}
let exit_block_id = merge_result.exit_block_id;
// Jump from current block to entry function's entry block
// (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);
eprintln!(
"[cf_loop/joinir] Entry function's entry_block (JoinIR local): {:?}",
entry_func.entry_block
);
eprintln!("[cf_loop/joinir] Entry block (remapped): {:?}", entry_block);
eprintln!(
"[cf_loop/joinir] Current block before emit_jump: {:?}",
builder.current_block
);
eprintln!("[cf_loop/joinir] Jumping to entry block: {:?}", entry_block);
}
crate::mir::builder::emission::branch::emit_jump(builder, entry_block)?;
if debug {
eprintln!(
"[cf_loop/joinir] After emit_jump, current_block: {:?}",
builder.current_block
);
}
// Switch to exit block for subsequent code
builder.start_new_block(exit_block_id)?;
if debug {
eprintln!(
"[cf_loop/joinir] Phase 189: Merge complete: {} functions merged, continuing from {:?}",
mir_module.functions.len(),
exit_block_id
);
}
// Phase 200-3: Verify JoinIR contracts (debug only)
#[cfg(debug_assertions)]
{
if let Some(boundary) = boundary {
verify_joinir_contracts(
builder.function(),
entry_block_remapped,
exit_block_id,
&loop_header_phi_info,
boundary,
);
if debug {
eprintln!("[cf_loop/joinir] Phase 200-3: Contract verification passed");
}
}
}
Ok(exit_phi_result_id)
}
/// Phase 3: Allocate new ValueIds for all collected values
fn remap_values(
builder: &mut crate::mir::builder::MirBuilder,
used_values: &std::collections::BTreeSet<ValueId>,
remapper: &mut crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper,
debug: bool,
) -> Result<(), String> {
if debug {
eprintln!("[cf_loop/joinir] Phase 3: Remapping {} ValueIds", used_values.len());
}
for old_value in used_values {
let new_value = builder.next_value_id();
remapper.set_value(*old_value, new_value);
if debug {
eprintln!(
"[cf_loop/joinir] Value remap: {:?}{:?}",
old_value, new_value
);
}
}
Ok(())
}
// ============================================================================
// Phase 200-3: JoinIR Contract Verification
// ============================================================================
/// Verify loop header PHI consistency
///
/// # Checks
///
/// 1. If loop_var_name is Some, header block must have corresponding PHI
/// 2. All carriers in LoopHeaderPhiInfo should have PHIs in the header block
///
/// # Panics
///
/// Panics in debug mode if contract violations are detected.
#[cfg(debug_assertions)]
fn verify_loop_header_phis(
func: &crate::mir::MirFunction,
header_block: crate::mir::BasicBlockId,
loop_info: &LoopHeaderPhiInfo,
boundary: &JoinInlineBoundary,
) {
// Check 1: Loop variable PHI existence
if let Some(ref loop_var_name) = boundary.loop_var_name {
let header_block_data = &func.blocks[header_block.0];
let has_loop_var_phi = header_block_data
.instructions
.iter()
.any(|instr| matches!(instr, crate::mir::MirInstruction::Phi { .. }));
if !has_loop_var_phi && !loop_info.carrier_phis.is_empty() {
panic!(
"[JoinIRVerifier] Loop variable '{}' in boundary but no PHI in header block {} (has {} carrier PHIs)",
loop_var_name, header_block.0, loop_info.carrier_phis.len()
);
}
}
// Check 2: Carrier PHI existence
if !loop_info.carrier_phis.is_empty() {
let header_block_data = &func.blocks[header_block.0];
let phi_count = header_block_data
.instructions
.iter()
.filter(|instr| matches!(instr, crate::mir::MirInstruction::Phi { .. }))
.count();
if phi_count == 0 {
panic!(
"[JoinIRVerifier] LoopHeaderPhiInfo has {} PHIs but header block {} has none",
loop_info.carrier_phis.len(),
header_block.0
);
}
// Verify each carrier has a corresponding PHI
for (carrier_name, entry) in &loop_info.carrier_phis {
let phi_exists = header_block_data.instructions.iter().any(|instr| {
if let crate::mir::MirInstruction::Phi { dst, .. } = instr {
*dst == entry.phi_dst
} else {
false
}
});
if !phi_exists {
panic!(
"[JoinIRVerifier] Carrier '{}' has PHI dst {:?} but PHI not found in header block {}",
carrier_name, entry.phi_dst, header_block.0
);
}
}
}
}
/// Verify exit line consistency
///
/// # Checks
///
/// 1. All exit_bindings in boundary should have corresponding values
/// 2. Exit block should exist and be in range
///
/// # Panics
///
/// Panics in debug mode if contract violations are detected.
#[cfg(debug_assertions)]
fn verify_exit_line(
func: &crate::mir::MirFunction,
exit_block: crate::mir::BasicBlockId,
boundary: &JoinInlineBoundary,
) {
// Check 1: Exit block exists
if exit_block.0 >= func.blocks.len() {
panic!(
"[JoinIRVerifier] Exit block {} out of range (func has {} blocks)",
exit_block.0,
func.blocks.len()
);
}
// Check 2: Exit bindings reference valid values
if !boundary.exit_bindings.is_empty() {
for binding in &boundary.exit_bindings {
// Verify host_slot is reasonable (basic sanity check)
// We can't verify the exact value since it's from the host's value space,
// but we can check it's not obviously invalid
if binding.host_slot.0 >= 1000000 {
// Arbitrary large number check
panic!(
"[JoinIRVerifier] Exit binding '{}' has suspiciously large host_slot {:?}",
binding.carrier_name, binding.host_slot
);
}
}
}
}
/// Verify all loop contracts for a merged JoinIR function
///
/// This is the main entry point for verification. It runs all checks
/// and panics if any contract violation is found.
///
/// # Panics
///
/// Panics in debug mode if any contract violation is detected.
#[cfg(debug_assertions)]
fn verify_joinir_contracts(
func: &crate::mir::MirFunction,
header_block: crate::mir::BasicBlockId,
exit_block: crate::mir::BasicBlockId,
loop_info: &LoopHeaderPhiInfo,
boundary: &JoinInlineBoundary,
) {
verify_loop_header_phis(func, header_block, loop_info, boundary);
verify_exit_line(func, exit_block, boundary);
}