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:
@ -308,6 +308,15 @@ pub struct JoinInlineBoundary {
|
||||
/// Used to track which PHI corresponds to the loop variable.
|
||||
pub loop_var_name: Option<String>,
|
||||
|
||||
/// Phase 287 P2: Loop header function name (SSOT)
|
||||
///
|
||||
/// Merge must not guess the loop header function from "first non-main non-continuation".
|
||||
/// For loop patterns, set this explicitly (typically `"loop_step"`).
|
||||
///
|
||||
/// - `Some(name)`: Merge uses this as the loop header function.
|
||||
/// - `None`: Legacy heuristic remains (for backwards compatibility).
|
||||
pub loop_header_func_name: Option<String>,
|
||||
|
||||
/// Phase 228: Carrier metadata (for header PHI generation)
|
||||
///
|
||||
/// Contains full carrier information including initialization policies.
|
||||
@ -435,6 +444,7 @@ impl JoinInlineBoundary {
|
||||
expr_result: None, // Phase 33-14: Default to carrier-only pattern
|
||||
jump_args_layout: JumpArgsLayout::CarriersOnly, // Phase 256 P1.12
|
||||
loop_var_name: None, // Phase 33-16
|
||||
loop_header_func_name: None, // Phase 287 P2
|
||||
carrier_info: None, // Phase 228: Default to None
|
||||
continuation_func_ids: Self::default_continuations(),
|
||||
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
||||
@ -482,6 +492,7 @@ impl JoinInlineBoundary {
|
||||
expr_result: None, // Phase 33-14
|
||||
jump_args_layout: JumpArgsLayout::CarriersOnly, // Phase 256 P1.12
|
||||
loop_var_name: None, // Phase 33-16
|
||||
loop_header_func_name: None, // Phase 287 P2
|
||||
carrier_info: None, // Phase 228
|
||||
continuation_func_ids: Self::default_continuations(),
|
||||
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
||||
@ -546,6 +557,7 @@ impl JoinInlineBoundary {
|
||||
expr_result: None, // Phase 33-14
|
||||
jump_args_layout: JumpArgsLayout::CarriersOnly, // Phase 256 P1.12
|
||||
loop_var_name: None, // Phase 33-16
|
||||
loop_header_func_name: None, // Phase 287 P2
|
||||
carrier_info: None, // Phase 228
|
||||
continuation_func_ids: Self::default_continuations(),
|
||||
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
||||
@ -596,6 +608,7 @@ impl JoinInlineBoundary {
|
||||
expr_result: None, // Phase 33-14
|
||||
jump_args_layout: JumpArgsLayout::CarriersOnly, // Phase 256 P1.12
|
||||
loop_var_name: None, // Phase 33-16
|
||||
loop_header_func_name: None, // Phase 287 P2
|
||||
carrier_info: None, // Phase 228
|
||||
continuation_func_ids: Self::default_continuations(),
|
||||
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
||||
@ -650,6 +663,7 @@ impl JoinInlineBoundary {
|
||||
expr_result: None, // Phase 33-14
|
||||
jump_args_layout: JumpArgsLayout::CarriersOnly, // Phase 256 P1.12
|
||||
loop_var_name: None, // Phase 33-16
|
||||
loop_header_func_name: None, // Phase 287 P2
|
||||
carrier_info: None, // Phase 228
|
||||
continuation_func_ids: Self::default_continuations(),
|
||||
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
||||
@ -711,6 +725,7 @@ impl JoinInlineBoundary {
|
||||
expr_result: None, // Phase 33-14
|
||||
jump_args_layout: JumpArgsLayout::CarriersOnly, // Phase 256 P1.12
|
||||
loop_var_name: None, // Phase 33-16
|
||||
loop_header_func_name: None, // Phase 287 P2
|
||||
carrier_info: None, // Phase 228
|
||||
continuation_func_ids: Self::default_continuations(),
|
||||
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
||||
@ -767,6 +782,7 @@ mod tests {
|
||||
expr_result: Some(ValueId(10)),
|
||||
jump_args_layout: JumpArgsLayout::ExprResultPlusCarriers,
|
||||
loop_var_name: None,
|
||||
loop_header_func_name: None,
|
||||
carrier_info: None,
|
||||
continuation_func_ids: JoinInlineBoundary::default_continuations(),
|
||||
exit_reconnect_mode: ExitReconnectMode::default(),
|
||||
|
||||
@ -98,6 +98,7 @@ impl JoinInlineBoundaryBuilder {
|
||||
expr_result: None,
|
||||
jump_args_layout: JumpArgsLayout::CarriersOnly,
|
||||
loop_var_name: None,
|
||||
loop_header_func_name: None, // Phase 287 P2
|
||||
carrier_info: None, // Phase 228: Initialize as None
|
||||
continuation_func_ids: JoinInlineBoundary::default_continuations(),
|
||||
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5
|
||||
@ -184,6 +185,14 @@ impl JoinInlineBoundaryBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Phase 287 P2: Set loop header function name (SSOT)
|
||||
///
|
||||
/// If omitted for loop patterns, `build()` defaults it to `"loop_step"` when `loop_var_name` is set.
|
||||
pub fn with_loop_header_func_name(mut self, name: Option<String>) -> Self {
|
||||
self.boundary.loop_header_func_name = name;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set expression result (Phase 33-14)
|
||||
///
|
||||
/// If the loop is used as an expression, this is the JoinIR-local ValueId
|
||||
@ -200,6 +209,14 @@ impl JoinInlineBoundaryBuilder {
|
||||
boundary.expr_result,
|
||||
boundary.exit_bindings.as_slice(),
|
||||
);
|
||||
|
||||
// Phase 287 P2: Default loop header function name for loop patterns.
|
||||
// If a pattern sets loop_var_name, it must have a loop header function.
|
||||
if boundary.loop_var_name.is_some() && boundary.loop_header_func_name.is_none() {
|
||||
boundary.loop_header_func_name =
|
||||
Some(crate::mir::join_ir::lowering::canonical_names::LOOP_STEP.to_string());
|
||||
}
|
||||
|
||||
boundary
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user