Files
hakorune/src/mir/builder/control_flow/joinir/merge/mod.rs
nyash-codex 192620f842 feat(joinir): Phase 228 - CarrierInit for ConditionOnly header PHI initialization
- Add CarrierInit enum (FromHost/BoolConst) for explicit initialization policy
- LoopHeaderPhiBuilder generates Const instruction for BoolConst carriers
- merge/mod.rs uses carrier_info to include ALL carriers in header PHI
- Pattern 2/4 pass carrier_info to boundary builder
- ConditionOnly carriers (is_digit_pos) now included in header PHI

Remaining: latch incoming for ConditionOnly carriers (Phase 228-8)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 21:10:28 +09:00

1068 lines
43 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;
mod expr_result_resolver;
// 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 201-A: Build loop header PHI info 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.
let mut loop_header_phi_info = if let Some(boundary) = boundary {
if let Some(loop_var_name) = &boundary.loop_var_name {
// Get entry function and block for building PHI info
let (entry_func_name, entry_func) = mir_module
.functions
.iter()
.next()
.ok_or("JoinIR module has no functions (Phase 201-A)")?;
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"
)?;
// Get loop variable's initial value from HOST
let loop_var_init = boundary.host_inputs.first().copied().ok_or(
"Phase 201-A: No host_inputs in boundary for loop_var_init"
)?;
// Phase 228-4: Extract carriers with their initialization strategy
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)
carrier_info.carriers
.iter()
.filter(|c| c.name != *loop_var_name)
.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()
};
if debug {
eprintln!(
"[cf_loop/joinir] Phase 201-A: Pre-building header PHIs for loop_var='{}' at {:?}",
loop_var_name, entry_block_remapped
);
eprintln!(
"[cf_loop/joinir] loop_var_init={:?}, carriers={:?}",
loop_var_init,
other_carriers.iter().map(|(n, _, _, _)| n.as_str()).collect::<Vec<_>>()
);
}
// Build PHI info (this allocates PHI dst ValueIds)
LoopHeaderPhiBuilder::build(
builder,
entry_block_remapped,
host_entry_block,
loop_var_name,
loop_var_init,
&other_carriers,
boundary.expr_result.is_some(),
debug,
)?
} else {
LoopHeaderPhiInfo::empty(remapper.get_block(
mir_module.functions.iter().next().unwrap().0,
mir_module.functions.iter().next().unwrap().1.entry_block
).unwrap())
}
} else {
LoopHeaderPhiInfo::empty(remapper.get_block(
mir_module.functions.iter().next().unwrap().0,
mir_module.functions.iter().next().unwrap().1.entry_block
).unwrap())
};
// Phase 201-A: Get reserved PHI dst ValueIds and set in MirBuilder
let reserved_phi_dsts = loop_header_phi_info.reserved_value_ids();
if debug && !reserved_phi_dsts.is_empty() {
eprintln!("[cf_loop/joinir] Phase 201-A: Reserved PHI dsts: {:?}", reserved_phi_dsts);
}
// 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.reserved_value_ids = reserved_phi_dsts.clone();
if debug && !builder.reserved_value_ids.is_empty() {
eprintln!("[cf_loop/joinir] Phase 201-A: Set builder.reserved_value_ids = {:?}", builder.reserved_value_ids);
}
// Phase 3: Remap ValueIds (with reserved PHI dsts protection)
remap_values(builder, &used_values, &mut remapper, &reserved_phi_dsts, 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: Override remapper for function parameters to use PHI dsts
//
// Phase 201-A: This phase now uses the loop_header_phi_info built before Phase 3.
// The PHI dst allocation has been moved earlier to prevent ValueId conflicts.
if let Some(boundary) = boundary {
if let Some(loop_var_name) = &boundary.loop_var_name {
// Phase 201-A: PHI info is already built (before Phase 3) - just use it
// 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: {:?}",
loop_header_phi_info.carrier_phis.len(),
loop_header_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 loop_header_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 &loop_header_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 &&
loop_header_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)) = (
loop_header_phi_info.get_carrier_at_index(param_idx),
loop_header_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) = loop_header_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 loop_header_phi_info.carrier_order.iter().enumerate() {
if carrier_name == loop_var_name {
continue;
}
let entry = match loop_header_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
);
}
// Phase 201-A: loop_header_phi_info already built (no assignment needed)
}
}
// 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;
// Phase 201-A: Get entry block from loop_header_phi_info
// The header_block in loop_header_phi_info is the remapped entry block
let entry_block = loop_header_phi_info.header_block;
if debug {
eprintln!("[cf_loop/joinir] Entry block (from loop_header_phi_info): {:?}", 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 {
if let Some(ref func) = builder.current_function {
verify_joinir_contracts(
func,
entry_block,
exit_block_id,
&loop_header_phi_info,
boundary,
);
}
if debug {
eprintln!("[cf_loop/joinir] Phase 200-3: Contract verification passed");
}
}
}
// Phase 201-A: Clear reserved ValueIds after merge completes
// Future loops will set their own reserved IDs
if !builder.reserved_value_ids.is_empty() {
if debug {
eprintln!("[cf_loop/joinir] Phase 201-A: Clearing reserved_value_ids (was {:?})", builder.reserved_value_ids);
}
builder.reserved_value_ids.clear();
}
// Phase 221-R: Use ExprResultResolver Box
let expr_result_value = expr_result_resolver::ExprResultResolver::resolve(
boundary.and_then(|b| b.expr_result),
boundary.map(|b| b.exit_bindings.as_slice()).unwrap_or(&[]),
&carrier_phis,
&remapper,
debug,
)?;
// Return expr_result if present, otherwise fall back to exit_phi_result_id
if let Some(resolved) = expr_result_value {
if debug {
eprintln!("[cf_loop/joinir] Phase 221-R: Returning expr_result_value {:?}", resolved);
}
Ok(Some(resolved))
} else {
// Fallback: return exit_phi_result_id (for legacy patterns or carrier-only loops)
if debug && exit_phi_result_id.is_some() {
eprintln!(
"[cf_loop/joinir] Phase 221-R: Returning exit_phi_result_id (fallback): {:?}",
exit_phi_result_id
);
}
Ok(exit_phi_result_id)
}
}
/// Phase 3: Allocate new ValueIds for all collected values
///
/// Phase 201-A: Accept reserved ValueIds that must not be reused.
/// These are PHI dst ValueIds that will be created by LoopHeaderPhiBuilder.
/// We must skip these IDs to prevent carrier value corruption.
fn remap_values(
builder: &mut crate::mir::builder::MirBuilder,
used_values: &std::collections::BTreeSet<ValueId>,
remapper: &mut crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper,
reserved_ids: &std::collections::HashSet<ValueId>,
debug: bool,
) -> Result<(), String> {
if debug {
eprintln!("[cf_loop/joinir] Phase 3: Remapping {} ValueIds (reserved: {})",
used_values.len(), reserved_ids.len());
}
for old_value in used_values {
// Phase 201-A: Allocate new ValueId, skipping reserved PHI dsts
let new_value = loop {
let candidate = builder.next_value_id();
if !reserved_ids.contains(&candidate) {
break candidate;
}
// Skip reserved ID - will try next one
if debug {
eprintln!("[cf_loop/joinir] Phase 201-A: Skipping reserved PHI dst {:?}", candidate);
}
};
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.get(&header_block).unwrap_or_else(|| {
panic!(
"[JoinIRVerifier] Header block {} not found ({} blocks in func)",
header_block,
func.blocks.len()
)
});
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.get(&header_block).unwrap_or_else(|| {
panic!(
"[JoinIRVerifier] Header block {} not found ({} blocks in func)",
header_block,
func.blocks.len()
)
});
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 !func.blocks.contains_key(&exit_block) {
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
);
}
}
}
}
/// Phase 205-4: Verify ValueId regions follow JoinValueSpace contracts
///
/// # Checks
///
/// 1. All `boundary.join_inputs` are in Param region (100-999)
/// 2. All `carrier_phis[].phi_dst` are within valid range (<= LOCAL_MAX)
/// 3. All `condition_bindings[].join_value` are in Param region
///
/// # Rationale
///
/// JoinValueSpace enforces disjoint regions (Param: 100-999, Local: 1000+)
/// to prevent ValueId collisions. This verifier ensures that the boundary
/// contracts are respected after JoinIR generation.
///
/// # Panics
///
/// Panics in debug mode if any ValueId is in an unexpected region.
#[cfg(debug_assertions)]
fn verify_valueid_regions(
loop_info: &LoopHeaderPhiInfo,
boundary: &JoinInlineBoundary,
) {
use crate::mir::join_ir::lowering::join_value_space::{PARAM_MIN, PARAM_MAX, LOCAL_MAX};
// Helper to classify region
fn region_name(id: ValueId) -> &'static str {
if id.0 < PARAM_MIN {
"PHI Reserved"
} else if id.0 <= PARAM_MAX {
"Param"
} else if id.0 <= LOCAL_MAX {
"Local"
} else {
"Invalid (> LOCAL_MAX)"
}
}
// Check 1: Boundary join_inputs must be in Param region
for join_id in &boundary.join_inputs {
if join_id.0 < PARAM_MIN || join_id.0 > PARAM_MAX {
panic!(
"[RegionVerifier] Boundary input {:?} is in {} region, expected Param (100-999)",
join_id, region_name(*join_id)
);
}
}
// Check 2: Condition bindings must be in Param region
for binding in &boundary.condition_bindings {
let join_value = binding.join_value;
if join_value.0 < PARAM_MIN || join_value.0 > PARAM_MAX {
panic!(
"[RegionVerifier] Condition binding '{}' join_value {:?} is in {} region, expected Param (100-999)",
binding.name, join_value, region_name(join_value)
);
}
}
// Check 3: PHI dst must be within valid range
for (carrier_name, entry) in &loop_info.carrier_phis {
let phi_dst = entry.phi_dst;
if phi_dst.0 > LOCAL_MAX {
panic!(
"[RegionVerifier] Carrier '{}' PHI dst {:?} exceeds LOCAL_MAX ({})",
carrier_name, phi_dst, LOCAL_MAX
);
}
}
}
/// Verify that PHI dst values are not overwritten by later instructions (Phase 204-2)
///
/// # Checks
///
/// 1. PHI instructions define dst values in header block
/// 2. No subsequent instruction in the same block overwrites these dsts
///
/// # Rationale
///
/// PHI dst overwrite violates SSA invariant (single assignment) and causes undefined behavior.
/// While Phase 201 JoinValueSpace prevents ValueId collisions, manual coding errors can still
/// occur during pattern lowering.
///
/// # Panics
///
/// Panics in debug mode if PHI dst is overwritten by a later instruction.
#[cfg(debug_assertions)]
fn verify_no_phi_dst_overwrite(
func: &crate::mir::MirFunction,
header_block: crate::mir::BasicBlockId,
loop_info: &LoopHeaderPhiInfo,
) {
if loop_info.carrier_phis.is_empty() {
return; // No PHIs to verify
}
let header_block_data = func.blocks.get(&header_block).unwrap_or_else(|| {
panic!(
"[JoinIRVerifier] Header block {} not found ({} blocks in func)",
header_block,
func.blocks.len()
)
});
// 1. Collect all PHI dsts
let phi_dsts: std::collections::HashSet<crate::mir::ValueId> = loop_info
.carrier_phis
.values()
.map(|entry| entry.phi_dst)
.collect();
// 2. Check instructions after PHI definitions
let mut after_phis = false;
for instr in &header_block_data.instructions {
match instr {
crate::mir::MirInstruction::Phi { dst, .. } => {
// PHI instructions come first in basic block
if after_phis {
panic!(
"[JoinIRVerifier] PHI instruction {:?} appears after non-PHI instructions in block {}",
dst, header_block.0
);
}
}
_ => {
after_phis = true;
// Check if this instruction writes to a PHI dst
if let Some(dst) = get_instruction_dst(instr) {
if phi_dsts.contains(&dst) {
panic!(
"[JoinIRVerifier/Phase204] PHI dst {:?} is overwritten by instruction in header block {}: {:?}",
dst, header_block.0, instr
);
}
}
}
}
}
}
/// Helper: Extract dst ValueId from MirInstruction (Phase 204-2)
#[cfg(debug_assertions)]
fn get_instruction_dst(instr: &crate::mir::MirInstruction) -> Option<crate::mir::ValueId> {
use crate::mir::MirInstruction;
match instr {
// Instructions with always-present dst
MirInstruction::Const { dst, .. }
| MirInstruction::Load { dst, .. }
| MirInstruction::UnaryOp { dst, .. }
| MirInstruction::BinOp { dst, .. }
| MirInstruction::Compare { dst, .. }
| MirInstruction::TypeOp { dst, .. }
| MirInstruction::NewBox { dst, .. }
| MirInstruction::NewClosure { dst, .. }
| MirInstruction::Copy { dst, .. }
| MirInstruction::Cast { dst, .. }
| MirInstruction::TypeCheck { dst, .. }
| MirInstruction::Phi { dst, .. }
| MirInstruction::ArrayGet { dst, .. }
| MirInstruction::RefNew { dst, .. }
| MirInstruction::RefGet { dst, .. }
| MirInstruction::WeakNew { dst, .. }
| MirInstruction::WeakLoad { dst, .. }
| MirInstruction::WeakRef { dst, .. }
| MirInstruction::FutureNew { dst, .. }
| MirInstruction::Await { dst, .. } => Some(*dst),
// Instructions with Option<ValueId> dst
MirInstruction::BoxCall { dst, .. }
| MirInstruction::ExternCall { dst, .. }
| MirInstruction::Call { dst, .. }
| MirInstruction::PluginInvoke { dst, .. } => *dst,
// Instructions without dst (side-effects only)
_ => None,
}
}
/// Verify PHI inputs are defined (Phase 204-3 - Conservative sanity checks)
///
/// # Checks
///
/// 1. PHI inputs have reasonable ValueId values (< threshold)
/// 2. No obviously undefined values (e.g., suspiciously large IDs)
///
/// # Note
///
/// Full data-flow analysis (DFA) verification is deferred to Phase 205+.
/// This function only performs conservative sanity checks.
///
/// # Panics
///
/// Panics in debug mode if suspicious PHI inputs are detected.
#[cfg(debug_assertions)]
fn verify_phi_inputs_defined(
func: &crate::mir::MirFunction,
header_block: crate::mir::BasicBlockId,
) {
let header_block_data = func.blocks.get(&header_block).unwrap_or_else(|| {
panic!(
"[JoinIRVerifier] Header block {} not found ({} blocks in func)",
header_block,
func.blocks.len()
)
});
for instr in &header_block_data.instructions {
if let crate::mir::MirInstruction::Phi { dst, inputs, type_hint: _ } = instr {
for (value_id, pred_block) in inputs {
// Conservative sanity check: ValueId should not be suspiciously large
// Phase 201 JoinValueSpace uses regions:
// - PHI Reserved: 0-99
// - Param: 100-999
// - Local: 1000+
// - Reasonable max: 100000 (arbitrary but catches obvious bugs)
if value_id.0 >= 100000 {
panic!(
"[JoinIRVerifier/Phase204-3] PHI {:?} has suspiciously large input {:?} from predecessor block {:?}",
dst, value_id, pred_block
);
}
}
}
}
}
/// 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_no_phi_dst_overwrite(func, header_block, loop_info); // Phase 204-2
verify_phi_inputs_defined(func, header_block); // Phase 204-3
verify_exit_line(func, exit_block, boundary);
verify_valueid_regions(loop_info, boundary); // Phase 205-4
}