From bfc63b0e3c4dfcbce7e3b9f885e0a9169e083c95 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Thu, 25 Dec 2025 05:05:49 +0900 Subject: [PATCH] fix(joinir): collect carrier_inputs for skippable ExitJump (Phase 286C-4.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the Phase 286C-4 refactoring, carrier_inputs was not being collected when a tail call's TailCallKind is ExitJump and the target is a skippable continuation. This caused json_lint_vm to fail with: [joinir/phase118/exit_phi/missing_carrier_phi] exit_bindings carrier 'i' is missing from exit_carrier_phis Root cause: The 3-stage pipeline refactoring separated Return→Jump conversion (which collected carrier_inputs) from tail call handling. When found_tail_call=true, the Return processing was skipped entirely, but tail call handling didn't collect carrier_inputs. Fix: Add carrier_inputs collection in the ExitJump (skippable) path at lines 901-934. This mirrors the fallback logic from the Return processing path. Test results: 45/46 PASS (same as before refactoring) - json_lint_vm: PASS (was FAIL) - core_direct_array_oob_set_rc_vm: FAIL (unchanged, known issue) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../joinir/merge/instruction_rewriter.rs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs index 9ad0c4c8..df6418bd 100644 --- a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -872,6 +872,42 @@ fn plan_rewrites( "[plan_rewrites] ExitJump (skippable): redirecting from {:?} to exit_block_id {:?}", target_block, ctx.exit_block_id ); + + // Phase 286C-4.1: Collect carrier_inputs for skippable exit jump + // This was missing in the refactored code + if let Some(b) = boundary { + for binding in &b.exit_bindings { + // Skip ConditionOnly carriers + if binding.role == crate::mir::join_ir::lowering::carrier_info::CarrierRole::ConditionOnly { + continue; + } + + // Try to get carrier value from header PHI + if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(&binding.carrier_name) { + result.carrier_inputs + .entry(binding.carrier_name.clone()) + .or_insert_with(Vec::new) + .push((new_block_id, phi_dst)); + log!( + verbose, + "[plan_rewrites] ExitJump carrier '{}': from {:?} value {:?}", + binding.carrier_name, new_block_id, phi_dst + ); + } else if b.exit_reconnect_mode == crate::mir::join_ir::lowering::carrier_info::ExitReconnectMode::DirectValue { + // DirectValue fallback: use host_slot + result.carrier_inputs + .entry(binding.carrier_name.clone()) + .or_insert_with(Vec::new) + .push((new_block_id, binding.host_slot)); + log!( + verbose, + "[plan_rewrites] ExitJump DirectValue fallback for '{}': host_slot {:?}", + binding.carrier_name, binding.host_slot + ); + } + } + } + ctx.exit_block_id } else { log!(