fix(joinir): Phase 287 P2 - Pattern6 nested loop latch overwrite fix

Fix infinite loop in Pattern6 (nested loop minimal) caused by main→loop_step
overwriting k_inner_exit→loop_step latch values.

Root cause: JoinIR main entry block was incorrectly treated as BackEdge,
causing it to overwrite the correct latch incoming values set by the true
back edge (k_inner_exit → loop_step).

Solution:
- Restrict latch recording to TailCallKind::BackEdge only
- Treat only MAIN's entry block as entry-like (not loop_step's entry block)
- Add debug_assert! to detect double latch set in future

Refactoring:
- Extract latch recording to latch_incoming_recorder module (SSOT)
- Add boundary.loop_header_func_name for explicit header identification
- Strengthen tail_call_classifier with is_source_entry_like parameter

Tests: apps/tests/phase1883_nested_minimal.hako → RC:9 (was infinite loop)
Smoke: 154/154 PASS, no regressions

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-27 09:39:29 +09:00
parent bbfc3c1d94
commit a04b48416e
20 changed files with 401 additions and 171 deletions

View File

@ -217,7 +217,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
"[cf_loop/joinir] Boundary join_inputs={:?} host_inputs={:?}",
boundary.join_inputs, boundary.host_inputs
),
true,
verbose,
);
trace.stderr_if(
&format!(
@ -225,7 +225,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
boundary.exit_bindings.len(),
exit_summary.join(", ")
),
true,
verbose,
);
if !cond_summary.is_empty() {
trace.stderr_if(
@ -234,7 +234,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
cond_summary.len(),
cond_summary.join(", ")
),
true,
verbose,
);
}
if let Some(ci) = &boundary.carrier_info {
@ -244,11 +244,11 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
"[cf_loop/joinir] Boundary carrier_info: loop_var='{}', carriers={:?}",
ci.loop_var_name, carriers
),
true,
verbose,
);
}
} else {
trace.stderr_if("[cf_loop/joinir] No boundary provided", true);
trace.stderr_if("[cf_loop/joinir] No boundary provided", verbose);
}
}
@ -364,18 +364,34 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
let (mut loop_header_phi_info, merge_entry_block) = if let Some(boundary) = boundary {
if let Some(loop_var_name) = &boundary.loop_var_name {
// Get loop_step function for PHI placement (the actual loop header)
let (loop_step_func_name, loop_step_func) = mir_module
.functions
.iter()
.find(|(name, _)| {
let is_continuation = boundary.continuation_func_ids.contains(*name);
let is_main = *name == crate::mir::join_ir::lowering::canonical_names::MAIN;
!is_continuation && !is_main
let loop_step_func_name = boundary
.loop_header_func_name
.as_deref()
.or_else(|| {
mir_module
.functions
.iter()
.find(|(name, _)| {
let is_continuation = boundary.continuation_func_ids.contains(*name);
let is_main =
*name == crate::mir::join_ir::lowering::canonical_names::MAIN;
!is_continuation && !is_main
})
.map(|(name, _)| name.as_str())
})
.or_else(|| mir_module.functions.iter().next())
.map(|(name, func)| (name.as_str(), func))
.or_else(|| mir_module.functions.keys().next().map(|s| s.as_str()))
.ok_or("JoinIR module has no functions (Phase 201-A)")?;
let loop_step_func = mir_module
.functions
.get(loop_step_func_name)
.ok_or_else(|| {
format!(
"loop_header_func_name '{}' not found in JoinIR module (Phase 287 P2)",
loop_step_func_name
)
})?;
// Phase 256.7-fix: Determine merge_entry_block
// When main has condition_bindings as params, we enter through main first
// (for boundary Copies), then main's tail call jumps to loop_step.
@ -479,7 +495,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
"[cf_loop/joinir] Phase 256.7-fix: merge_entry_func='{}', merge_entry_block={:?}, loop_header_block={:?}",
entry_func_name, entry_block_remapped, loop_header_block
),
true, // Always log for debugging
debug,
);
trace.stderr_if(
&format!(