Files
hakorune/src/mir/builder/control_flow/joinir/merge/mod.rs
tomoaki e51777b448 refactor(joinir): Phase 287 P0.6 - Final mod.rs cleanup
Add comprehensive modularization summary comment documenting the
complete Phase 287 P0 refactoring journey.

Changes:
- MOD: merge/mod.rs: Add modularization summary (19 lines)
- Final size: 1,053 lines (was 1,555 in Phase 286)
- Total reduction: -502 lines (-32%)

Modularization Summary:
- P0.1: debug_assertions.rs (verification functions)
- P0.2: value_remapper.rs (ValueId remapping helper)
- P0.3: entry_selector.rs (SSOT entry function selection)
- P0.4: header_phi_prebuild.rs (PHI pre-build orchestration)
- P0.5: boundary_logging.rs (consolidated logging)

Remaining in mod.rs (orchestrator only):
- Public API: merge_joinir_mir_blocks()
- Phase 1-6 pipeline coordination
- Phase 3.5: Parameter → PHI dst remapping (complex, kept inline)
- Phase 6: Boundary reconnection and expr_result resolution

SSOT Principles Enforced:
 Entry selection: boundary.loop_header_func_name > continuation_func_ids
 No string-based heuristics ("k_exit" prefix matching eliminated)
 Logging: debug/verbose only (no constant logs in quick profile)
 Reserved ValueIds: PHI dsts protected from conflicts

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>
2025-12-27 10:27:44 +09:00

1054 lines
44 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 block_remapper; // Phase 284 P1: Block ID remap SSOT
mod boundary_logging; // Phase 287 P0.5: Boundary logging consolidation
mod carrier_init_builder;
pub(super) mod contract_checks; // Phase 256 P1.5-DBG: Exposed for patterns to access verify_boundary_entry_params
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;
mod instruction_rewriter; // Phase 260 P0.1: Keep for gradual migration
mod rewriter; // Phase 260 P0.1: New modularized rewriter (forwards to instruction_rewriter)
mod loop_header_phi_builder;
mod loop_header_phi_info;
mod merge_result;
mod phi_block_remapper; // Phase 94: Phi block-id remap box
mod tail_call_classifier;
mod tail_call_lowering_policy; // Phase 131 Task 2: k_exit exit edge normalization
mod value_collector;
mod value_remapper; // Phase 287 P0.2: ValueId remapping helper
#[cfg(test)]
mod tests; // Phase 132-R0 Task 3: Continuation contract tests
// Phase 33-17: Re-export for use by other modules
pub use loop_header_phi_builder::LoopHeaderPhiBuilder;
pub use loop_header_phi_info::LoopHeaderPhiInfo;
// Phase 131 P1 Task 1: Re-export MergeContracts for SSOT visibility
pub use merge_result::MergeContracts;
// Phase 131 P1 Task 6: MergeConfig is defined in this module (no re-export needed)
use super::trace;
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
use crate::mir::join_ir::lowering::error_tags;
use crate::mir::{MirModule, ValueId};
use std::collections::BTreeMap;
/// Phase 131 P1 Task 6: Merge configuration consolidation
///
/// Consolidates all merge-related configuration into a single structure
/// to reduce parameter clutter and improve maintainability.
#[derive(Debug, Clone)]
pub struct MergeConfig {
/// Enable detailed trace logs (dev mode)
pub dev_log: bool,
/// Enable strict contract verification (fail-fast on violations)
pub strict_mode: bool,
/// Exit reconnection mode (Phi or DirectValue)
pub exit_reconnect_mode: Option<crate::mir::join_ir::lowering::carrier_info::ExitReconnectMode>,
/// Allow missing exit block in contract checks (typically exit_block_id before insertion)
pub allow_missing_exit_block: bool,
}
impl MergeConfig {
/// Default configuration for normal operation
pub fn default() -> Self {
Self {
dev_log: crate::config::env::joinir_dev_enabled(),
strict_mode: crate::config::env::joinir_strict_enabled(),
exit_reconnect_mode: None,
allow_missing_exit_block: true,
}
}
/// Strict configuration for development/debugging (all checks enabled)
pub fn strict() -> Self {
Self {
dev_log: true,
strict_mode: true,
exit_reconnect_mode: None,
allow_missing_exit_block: true,
}
}
/// Configuration for specific debug session
pub fn with_debug(debug: bool) -> Self {
let mut config = Self::default();
config.dev_log = debug || config.dev_log;
config
}
}
/// 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> {
// Phase 131 Task 6: Use MergeConfig for consolidated configuration
let config = MergeConfig::with_debug(debug);
let verbose = config.dev_log;
let trace = trace::trace();
trace.stderr_if(
&format!(
"[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions",
mir_module.functions.len()
),
debug,
);
if let Some(boundary) = boundary {
if let Err(msg) = boundary.validate_jump_args_layout() {
return Err(error_tags::freeze_with_hint(
"phase256/jump_args_layout",
&msg,
"set JoinInlineBoundary.jump_args_layout via builder and avoid expr_result/carrier mismatch",
));
}
}
// Phase 286 P3: Validate boundary contract BEFORE merge begins
// This catches boundary construction bugs early with clear diagnostics
if let Some(boundary) = boundary {
// Enrich context with host_fn and join-side info for better error diagnostics
let host_fn = builder
.scope_ctx
.current_function
.as_ref()
.map(|f| f.signature.name.as_str())
.unwrap_or("<unknown>");
// Join-side info: continuation count + boundary summary
let cont_count = boundary.continuation_func_ids.len();
let join_summary = format!(
"conts={} exits={} conds={}",
cont_count,
boundary.exit_bindings.len(),
boundary.condition_bindings.len()
);
let context = format!(
"merge_joinir_mir_blocks host={} join={} phase=<unknown> [{}]",
host_fn, cont_count, join_summary
);
if let Err(msg) = contract_checks::verify_boundary_contract_at_creation(boundary, &context) {
return Err(msg); // Fail-Fast: [joinir/contract:B*] error
}
}
// Phase 287 P0.5: Delegated to boundary_logging module
boundary_logging::log_boundary_info(boundary, &trace, verbose);
// 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 + Phase 256.7-fix: Add condition_bindings' join_values to used_values for remapping
// UNLESS they are function params. Params should NOT be remapped (they're defined
// by boundary Copies and used directly in JoinIR body).
if let Some(boundary) = boundary {
// Build all_params set for checking (moved before condition_bindings loop)
let all_params: std::collections::HashSet<ValueId> = function_params
.values()
.flat_map(|params| params.iter().copied())
.collect();
// Phase 283 P0 DEBUG: Log condition_bindings count
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 283 P0 DEBUG: Processing {} condition_bindings",
boundary.condition_bindings.len()
),
debug,
);
for binding in &boundary.condition_bindings {
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 283 P0 DEBUG: Checking binding '{}' join={:?}",
binding.name, binding.join_value
),
debug,
);
if all_params.contains(&binding.join_value) {
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 256.7-fix: Skipping condition binding '{}' (JoinIR {:?} is a param)",
binding.name, binding.join_value
),
debug,
);
} else {
// Phase 283 P0 FIX: Ensure remapper has valid mapping (Fail-Fast)
if let Some(host_id) = builder.variable_ctx.variable_map.get(&binding.name) {
// Variable exists in host context - map join_value to existing host_id
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 283 P0: ✅ Condition binding '{}' JoinIR {:?} → host {:?}",
binding.name, binding.join_value, host_id
),
debug,
);
remapper.set_value(binding.join_value, *host_id);
used_values.insert(binding.join_value);
} else {
// Fail-Fast: No host ValueId found → surface root cause immediately
return Err(format!(
"[merge/phase2.1] Condition variable '{}' (join={:?}) has no host ValueId in variable_map. \
This indicates the value was not properly supplied by boundary builder or cond_env. \
Check: (1) boundary builder supplies all condition vars, (2) cond_env correctly tracks host ValueIds.",
binding.name, binding.join_value
));
}
}
}
// Phase 172-3 + Phase 256 P1.10: Add exit_bindings' join_exit_values to used_values
// UNLESS they are function params. Params should NOT be remapped (they're defined
// by call site Copies and used directly in k_exit body).
// Note: all_params was already built above for condition_bindings check.
for binding in &boundary.exit_bindings {
if all_params.contains(&binding.join_exit_value) {
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 256 P1.10: Skipping exit binding '{}' (JoinIR {:?} is a param)",
binding.carrier_name, binding.join_exit_value
),
debug,
);
} else {
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 172-3: Adding exit binding '{}' JoinIR {:?} to used_values",
binding.carrier_name, binding.join_exit_value
),
debug,
);
used_values.insert(binding.join_exit_value);
}
}
}
// 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.
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
value_remapper::remap_values(
builder,
&used_values,
&mut remapper,
&reserved_value_ids,
debug,
)?;
// Phase 177-3 DEBUG: Verify remapper state after Phase 3
trace.stderr_if("[DEBUG-177] === Remapper state after Phase 3 ===", verbose);
trace.stderr_if(
&format!("[DEBUG-177] used_values count: {}", used_values.len()),
verbose,
);
for value_id in &used_values {
if let Some(remapped) = remapper.get_value(*value_id) {
trace.stderr_if(
&format!("[DEBUG-177] JoinIR {:?} → Host {:?}", value_id, remapped),
verbose,
);
} else {
trace.stderr_if(
&format!("[DEBUG-177] JoinIR {:?} → NOT FOUND ❌", value_id),
verbose,
);
}
}
// Check condition_bindings specifically
if let Some(boundary) = boundary {
trace.stderr_if("[DEBUG-177] === Condition bindings check ===", verbose);
for binding in &boundary.condition_bindings {
let lookup_result = remapper.get_value(binding.join_value);
trace.stderr_if(
&format!(
"[DEBUG-177] '{}': JoinIR {:?}{:?}",
binding.name, binding.join_value, lookup_result
),
verbose,
);
}
}
trace.stderr_if("[DEBUG-177] ==============================", verbose);
// 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() {
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 177-3: Protected ValueIds (condition-only, not carriers): {:?}",
condition_binding_ids
),
verbose,
);
for cb in &boundary.condition_bindings {
let is_carrier = carrier_names.contains(cb.name.as_str());
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 177-3: '{}': JoinIR {:?} (carrier={})",
cb.name, cb.join_value, is_carrier
),
verbose,
);
}
}
let canonical_main = crate::mir::join_ir::lowering::canonical_names::MAIN;
let canonical_loop_step = crate::mir::join_ir::lowering::canonical_names::LOOP_STEP;
let main_func_name = if function_params.contains_key(canonical_main) {
canonical_main
} else {
"join_func_0"
};
let loop_step_func_name = if function_params.contains_key(canonical_loop_step) {
canonical_loop_step
} else {
"join_func_1"
};
if function_params.get(main_func_name).is_none() {
trace.stderr_if(
&format!(
"[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}",
main_func_name,
function_params.keys().collect::<Vec<_>>()
),
verbose,
);
}
if let Some(main_params) = function_params.get(main_func_name) {
trace.stderr_if(
&format!(
"[DEBUG-177] Phase 33-21: main ({}) params: {:?}",
main_func_name, main_params
),
verbose,
);
trace.stderr_if(
&format!(
"[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<_>>()
),
verbose,
);
// 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) {
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 177-3: Skipping override for condition_binding {:?} ('{}')",
main_param, carrier_name
),
verbose,
);
continue;
}
trace.stderr_if(
&format!(
"[DEBUG-177] Phase 33-21: REMAP main param[{}] {:?}{:?} ('{}')",
idx, main_param, entry.phi_dst, carrier_name
),
verbose,
);
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
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 177-3-B: Body-only carrier '{}': JoinIR {:?} → PHI {:?}",
carrier_name, binding.join_value, entry.phi_dst
),
verbose,
);
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
trace.stderr_if(
&format!(
"[DEBUG-177] Phase 33-21: function_params keys: {:?}",
function_params.keys().collect::<Vec<_>>()
),
verbose,
);
if function_params.get(loop_step_func_name).is_none() {
trace.stderr_if(
&format!(
"[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}",
loop_step_func_name,
function_params.keys().collect::<Vec<_>>()
),
verbose,
);
}
if let Some(loop_step_params) = function_params.get(loop_step_func_name) {
// DEBUG-177: Always log loop_step params
trace.stderr_if(
&format!(
"[DEBUG-177] Phase 33-21: loop_step ({}) params: {:?}",
loop_step_func_name, loop_step_params
),
verbose,
);
// 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) {
trace.stderr_if(
&format!(
"[DEBUG-177] Phase 177-FIX: Skipping condition_binding {:?}",
loop_step_param
),
verbose,
);
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 {
trace.stderr_if(
&format!(
"[DEBUG-177] Phase 177-FIX: Skipping {:?} (already mapped by Phase 177-3-B)",
loop_step_param
),
verbose,
);
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),
) {
trace.stderr_if(
&format!(
"[DEBUG-177] Phase 177-STRUCT-2: REMAP loop_step param[{}] {:?}{:?} (carrier '{}')",
param_idx, loop_step_param, entry.phi_dst, carrier_name
),
verbose,
);
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);
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 33-16 fallback: Override remap ValueId(0) → {:?} (PHI dst)",
phi_dst
),
debug,
);
} else {
trace.stderr_if(
"[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding ValueId(0)",
verbose,
);
}
}
// 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);
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 33-20 fallback: Override remap {:?}{:?} (carrier '{}' PHI dst)",
join_value_id, entry.phi_dst, carrier_name
),
debug,
);
} else {
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding {:?} ('{}')",
join_value_id, carrier_name
),
verbose,
);
}
}
}
// Phase 177-3 DEBUG: Check remapper after Phase 33-21 overrides
trace.stderr_if("[DEBUG-177] === Remapper state after Phase 33-21 ===", verbose);
for binding in &boundary.condition_bindings {
let lookup_result = remapper.get_value(binding.join_value);
trace.stderr_if(
&format!(
"[DEBUG-177] '{}': JoinIR {:?}{:?} (after 33-21)",
binding.name, binding.join_value, lookup_result
),
verbose,
);
}
// 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
// Phase 260 P0.1: Use rewriter module (re-exports instruction_rewriter)
let merge_result = 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, 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() {
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 4.5: Finalizing {} header PHIs",
loop_header_phi_info.carrier_phis.len()
),
debug,
);
LoopHeaderPhiBuilder::finalize(builder, &loop_header_phi_info, debug)?;
}
// Contract check (Fail-Fast): ensure we didn't leave dangling Jump/Branch targets.
// Phase 131 Task 6: Use MergeConfig.strict_mode instead of env checks
if config.strict_mode || config.dev_log {
if let Some(ref current_func) = builder.scope_ctx.current_function {
// Note: exit_block_id may be allocated but not inserted yet (it becomes the
// current block after merge, and subsequent AST lowering fills it).
// We still want to catch truly dangling targets (e.g., jumps to skipped k_exit).
let contracts = MergeContracts {
allowed_missing_jump_targets: vec![merge_result.exit_block_id],
};
contract_checks::verify_all_terminator_targets_exist(current_func, &contracts)?;
}
}
// 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
// Phase 246-EX: REVERT Phase 33-20 - Use EXIT PHI dsts, not header PHI dsts!
// Phase 131 P1.5: DirectValue mode completely skips PHI generation
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 131 P1.5 DEBUG: boundary={:?}, mode={:?}",
boundary.is_some(),
boundary.map(|b| b.exit_reconnect_mode)
),
debug,
);
// Phase 131 P1.5: Check if DirectValue mode (skip PHI generation)
let is_direct_value_mode = boundary
.map(|b| b.exit_reconnect_mode == crate::mir::join_ir::lowering::carrier_info::ExitReconnectMode::DirectValue)
.unwrap_or(false);
// Phase 131 P1.5: Mode detection (dev-only visibility)
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 131 P1.5: exit_reconnect_mode={:?}, is_direct_value_mode={}",
boundary.map(|b| b.exit_reconnect_mode),
is_direct_value_mode
),
debug || config.dev_log,
);
let (exit_phi_result_id, exit_carrier_phis) = if is_direct_value_mode {
// DirectValue mode: Skip PHI generation completely
trace.stderr_if(
"[cf_loop/joinir] Phase 131 P1.5: DirectValue mode - skipping exit PHI generation",
debug,
);
(None, BTreeMap::new())
} else {
// Phi mode: Generate exit PHIs as usual
trace.stderr_if(
"[cf_loop/joinir] Phase 131 P1.5: Phi mode - generating exit PHIs",
debug,
);
exit_phi_builder::build_exit_phi(
builder,
merge_result.exit_block_id,
&merge_result.exit_phi_inputs,
&merge_result.carrier_inputs,
debug,
)?
};
// Phase 118 P2: Contract check (Fail-Fast) - exit_bindings LoopState carriers must have exit PHIs.
// Phase 131 P1.5: Skip this check in DirectValue mode
if let Some(boundary) = boundary {
if !is_direct_value_mode {
contract_checks::verify_exit_bindings_have_exit_phis(boundary, &exit_carrier_phis)?;
}
}
// Phase 118 P1: Dev-only carrier-phi SSOT logs (exit_bindings vs carrier_inputs vs exit_carrier_phis)
// Phase 131 Task 6: Use config.dev_log instead of env check
if config.dev_log {
if let Some(boundary) = boundary {
let exit_binding_names: Vec<&str> = boundary
.exit_bindings
.iter()
.map(|b| b.carrier_name.as_str())
.collect();
let carrier_input_names: Vec<&str> =
merge_result.carrier_inputs.keys().map(|s| s.as_str()).collect();
let exit_phi_names: Vec<&str> =
exit_carrier_phis.keys().map(|s| s.as_str()).collect();
trace.stderr_if(
&format!(
"[joinir/phase118/dev] exit_bindings carriers={:?}",
exit_binding_names
),
true,
);
trace.stderr_if(
&format!(
"[joinir/phase118/dev] carrier_inputs keys={:?}",
carrier_input_names
),
true,
);
trace.stderr_if(
&format!(
"[joinir/phase118/dev] exit_carrier_phis keys={:?}",
exit_phi_names
),
true,
);
}
}
// Phase 246-EX: CRITICAL FIX - Use exit PHI dsts for variable_map reconnection
//
// **Why EXIT PHI, not HEADER PHI?**
//
// Header PHI represents the value at the BEGINNING of each iteration.
// Exit PHI represents the FINAL value when leaving the loop (from any exit path).
//
// For Pattern 2 loops with multiple exit paths (natural exit + break):
// - Header PHI: `%15 = phi [%3, bb7], [%42, bb14]` (loop variable at iteration start)
// - Exit PHI: `%5 = phi [%15, bb11], [%15, bb13]` (final value from exit paths)
//
// When we exit the loop, we want the FINAL value (%5), not the iteration-start value (%15).
// Phase 33-20 incorrectly used header PHI, causing loops to return initial values (e.g., 0 instead of 42).
//
// Example (_atoi):
// - Initial: result=0 (header PHI)
// - After iteration 1: result=4 (updated in loop body)
// - After iteration 2: result=42 (updated in loop body)
// - Exit: Should return 42 (exit PHI), not 0 (header PHI initial value)
//
// The exit PHI correctly merges values from both exit paths, giving us the final result.
let carrier_phis = &exit_carrier_phis;
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 246-EX: Using EXIT PHI dsts for variable_map (not header): {:?}",
carrier_phis
.iter()
.map(|(n, v)| (n.as_str(), v))
.collect::<Vec<_>>()
),
debug && !carrier_phis.is_empty(),
);
// 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 246-EX: Now uses EXIT PHI dsts (reverted Phase 33-20)
// Phase 131 P2: DirectValue mode SSOT uses MergeResult.remapped_exit_values
let remapped_exit_values = merge_result.remapped_exit_values.clone();
if let Some(boundary) = boundary {
exit_line::ExitLineOrchestrator::execute(
builder,
boundary,
carrier_phis,
&remapped_exit_values, // Phase 131 P1.5: Now populated with exit PHI dsts
debug,
)?;
}
let exit_block_id = merge_result.exit_block_id;
// Phase 256.7-fix: Use merge_entry_block for the Jump
// This is the block where boundary Copies are injected (main's entry when condition_bindings exist).
// The host should Jump here first, then main's tail call jumps to the loop header.
let entry_block = merge_entry_block;
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 256.7-fix: Entry block (merge_entry_block): {:?}, loop_header={:?}",
entry_block, loop_header_phi_info.header_block
),
debug,
);
trace.stderr_if(
&format!(
"[cf_loop/joinir] Current block before emit_jump: {:?}",
builder.current_block
),
debug,
);
trace.stderr_if(
&format!(
"[cf_loop/joinir] Jumping to entry block: {:?}",
entry_block
),
debug,
);
crate::mir::builder::emission::branch::emit_jump(builder, entry_block)?;
trace.stderr_if(
&format!(
"[cf_loop/joinir] After emit_jump, current_block: {:?}",
builder.current_block
),
debug,
);
// Switch to exit block for subsequent code
builder.start_new_block(exit_block_id)?;
// Phase 287 P0.5: Delegated to boundary_logging module
boundary_logging::log_merge_complete(mir_module.functions.len(), exit_block_id, &trace, debug);
// Phase 200-3: Verify JoinIR contracts (debug only)
#[cfg(debug_assertions)]
{
if let Some(boundary) = boundary {
if let Some(ref func) = builder.scope_ctx.current_function {
debug_assertions::verify_joinir_contracts(
func,
entry_block,
exit_block_id,
&loop_header_phi_info,
boundary,
);
}
trace.stderr_if(
"[cf_loop/joinir] Phase 200-3: Contract verification passed",
debug,
);
}
}
// Phase 246-EX-FIX: Handle loop variable expr_result separately from carrier expr_result
//
// The loop variable (e.g., 'i') is returned via exit_phi_result_id, not carrier_phis.
// Other carriers use carrier_phis. We need to check which case we're in.
let expr_result_value = if let Some(b) = boundary {
if let Some(expr_result_id) = b.expr_result {
// Check if expr_result is the loop variable
if let Some(loop_var_name) = &b.loop_var_name {
// Find the exit binding for the loop variable
let loop_var_binding = b
.exit_bindings
.iter()
.find(|binding| binding.carrier_name == *loop_var_name);
if let Some(binding) = loop_var_binding {
if binding.join_exit_value == expr_result_id {
// expr_result is the loop variable! Use exit_phi_result_id
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 246-EX-FIX: expr_result {:?} is loop variable '{}', using exit_phi_result_id {:?}",
expr_result_id, loop_var_name, exit_phi_result_id
),
debug,
);
exit_phi_result_id
} else {
// expr_result is not the loop variable, resolve as carrier
expr_result_resolver::ExprResultResolver::resolve(
Some(expr_result_id),
b.exit_bindings.as_slice(),
&carrier_phis,
&remapper,
debug,
)?
}
} else {
// No loop variable binding, resolve normally
expr_result_resolver::ExprResultResolver::resolve(
Some(expr_result_id),
b.exit_bindings.as_slice(),
&carrier_phis,
&remapper,
debug,
)?
}
} else {
// No loop variable name, resolve normally
expr_result_resolver::ExprResultResolver::resolve(
Some(expr_result_id),
b.exit_bindings.as_slice(),
&carrier_phis,
&remapper,
debug,
)?
}
} else {
None
}
} else {
None
};
// Return expr_result if present, otherwise fall back to exit_phi_result_id
if let Some(resolved) = expr_result_value {
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 246-EX-FIX: Returning expr_result_value {:?}",
resolved
),
debug,
);
Ok(Some(resolved))
} else {
// Fallback: return exit_phi_result_id (for legacy patterns or carrier-only loops)
trace.stderr_if(
&format!(
"[cf_loop/joinir] Phase 221-R: Returning exit_phi_result_id (fallback): {:?}",
exit_phi_result_id
),
debug && exit_phi_result_id.is_some(),
);
Ok(exit_phi_result_id)
}
}
// Phase 287 P0: merge/mod.rs Modularization Complete
//
// Line reduction: 1,555 (Phase 286 start) → 1,034 (Phase 287 P0.6) = -521 lines (-33%)
//
// Extracted modules:
// - P0.1: debug_assertions.rs (verification functions)
// - P0.2: value_remapper.rs (ValueId remapping helper)
// - P0.3: entry_selector.rs (SSOT entry function selection)
// - P0.4: header_phi_prebuild.rs (PHI pre-build orchestration)
// - P0.5: boundary_logging.rs (consolidated logging)
//
// Remaining in mod.rs:
// - Public API: merge_joinir_mir_blocks()
// - Orchestration: Phase 1-6 pipeline coordination
// - Phase 3.5: Parameter → PHI dst remapping (complex, kept inline)
// - Phase 6: Boundary reconnection and expr_result resolution
//
// SSOT Principles Enforced:
// - Entry selection: boundary.loop_header_func_name > continuation_func_ids (no string heuristics)
// - Logging: debug/verbose only (no constant logs in quick profile)
// - Reserved ValueIds: PHI dsts protected from conflicts