fix(joinir/merge): stabilize Pattern2 entry remap

This commit is contained in:
2025-12-29 03:58:45 +09:00
parent 11adec0abd
commit cf95afbd83
5 changed files with 116 additions and 61 deletions

View File

@ -387,33 +387,38 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
),
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;
}
// Map main's parameters to header PHI dsts.
//
// IMPORTANT: Do not iterate carrier_phis (BTreeMap) here.
// JoinIR params are laid out in carrier_order, not alphabetical order.
for (idx, &main_param) in main_params.iter().enumerate() {
let (Some(carrier_name), Some(entry)) = (
loop_header_phi_info.get_carrier_at_index(idx),
loop_header_phi_info.get_entry_at_index(idx),
) else {
continue;
};
// Phase 177-3: Don't override condition_bindings
if condition_binding_ids.contains(&main_param) {
trace.stderr_if(
&format!(
"[DEBUG-177] Phase 33-21: REMAP main param[{}] {:?} {:?} ('{}')",
idx, main_param, entry.phi_dst, carrier_name
"[cf_loop/joinir] Phase 177-3: Skipping override for condition_binding {:?} ('{}')",
main_param, carrier_name
),
verbose,
);
remapper.set_value(main_param, entry.phi_dst);
continue;
}
trace.stderr_if(
&format!(
"[DEBUG-177] Phase 33-21: REMAP main param[{}] {:?}{:?} (carrier '{}')",
idx, main_param, entry.phi_dst, carrier_name
),
verbose,
);
remapper.set_value(main_param, entry.phi_dst);
}
}

View File

@ -0,0 +1,59 @@
//! Header predecessor policy (SSOT)
//!
//! Entry preds:
//! - carrier entry_incoming blocks
//! - host entry block (if any)
//! Latch preds:
//! - header predecessors not in entry preds
use super::loop_header_phi_info::LoopHeaderPhiInfo;
use crate::mir::BasicBlockId;
use std::collections::BTreeSet;
pub(super) struct HeaderPredGroups {
pub entry_preds: Vec<BasicBlockId>,
pub latch_preds: Vec<BasicBlockId>,
pub host_entry_added: bool,
}
pub(super) fn split_header_preds(
info: &LoopHeaderPhiInfo,
header_preds: &[BasicBlockId],
host_entry_block_opt: Option<BasicBlockId>,
latch_block: BasicBlockId,
) -> HeaderPredGroups {
let mut entry_pred_set: BTreeSet<BasicBlockId> = BTreeSet::new();
for entry in info.carrier_phis.values() {
entry_pred_set.insert(entry.entry_incoming.0);
}
if let Some(host_entry_block) = host_entry_block_opt {
entry_pred_set.insert(host_entry_block);
}
entry_pred_set.remove(&latch_block);
let mut entry_preds: Vec<BasicBlockId> = header_preds
.iter()
.filter(|&&pred| entry_pred_set.contains(&pred))
.copied()
.collect();
let mut host_entry_added = false;
if let Some(host_entry_block) = host_entry_block_opt {
if !entry_preds.contains(&host_entry_block) && host_entry_block != latch_block {
entry_preds.push(host_entry_block);
host_entry_added = true;
}
}
let latch_preds: Vec<BasicBlockId> = header_preds
.iter()
.filter(|&&pred| !entry_pred_set.contains(&pred))
.copied()
.collect();
HeaderPredGroups {
entry_preds,
latch_preds,
host_entry_added,
}
}

View File

@ -28,6 +28,7 @@
//! (instruction_rewriter).
use super::dev_log;
use super::header_pred_policy;
use super::loop_header_phi_info::{CarrierPhiEntry, LoopHeaderPhiInfo};
use super::super::trace;
use crate::mir::{BasicBlockId, MirInstruction, ValueId};
@ -425,50 +426,29 @@ impl LoopHeaderPhiBuilder {
})?
};
// Step 3: Compute entry predecessors (entry_incoming blocks + host entry).
// Step 3: Compute entry/latch predecessors (SSOT).
// Phase 257 P1.2-FIX: Multiple entry preds are OK (bb0 host + bb10 JoinIR main)
let mut entry_pred_set: std::collections::BTreeSet<BasicBlockId> =
std::collections::BTreeSet::new();
for entry in info.carrier_phis.values() {
entry_pred_set.insert(entry.entry_incoming.0);
}
if let Some(host_entry_block) = host_entry_block_opt {
entry_pred_set.insert(host_entry_block);
}
// Latch block is never an entry predecessor.
entry_pred_set.remove(&latch_block);
let header_pred_groups = header_pred_policy::split_header_preds(
info,
header_preds,
host_entry_block_opt,
latch_block,
);
let entry_preds = header_pred_groups.entry_preds;
let latch_preds = header_pred_groups.latch_preds;
let mut entry_preds: Vec<BasicBlockId> = header_preds
.iter()
.filter(|&&pred| entry_pred_set.contains(&pred))
.copied()
.collect();
// Phase 257 P1.2-FIX: Add host_entry_block if not already in entry_preds
// The host block's terminator (Jump to loop header) is emitted AFTER finalize(),
// so it won't appear in the CFG yet. We need to manually add it.
if let Some(host_entry_block) = host_entry_block_opt {
if !entry_preds.contains(&host_entry_block) && host_entry_block != latch_block {
entry_preds.push(host_entry_block);
if dev_debug {
trace.stderr_if(
&format!(
"[joinir/header-phi] Added host_entry_block bb{} to entry_preds (terminator not set yet)",
host_entry_block.0
),
true,
);
}
if dev_debug && header_pred_groups.host_entry_added {
if let Some(host_entry_block) = host_entry_block_opt {
trace.stderr_if(
&format!(
"[joinir/header-phi] Added host_entry_block bb{} to entry_preds (terminator not set yet)",
host_entry_block.0
),
true,
);
}
}
// Latch preds are all header predecessors that are not entry preds.
let latch_preds: Vec<BasicBlockId> = header_preds
.iter()
.filter(|&&pred| !entry_pred_set.contains(&pred))
.copied()
.collect();
// Step 4: Validate at least one entry predecessor
if entry_preds.is_empty() {
return Err(format!(

View File

@ -24,6 +24,7 @@ mod debug_assertions; // Phase 286C-4.3: Debug-only assertions (split from contr
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
mod header_pred_policy; // Phase 29ae P1: Header pred SSOT
pub mod exit_line;
mod exit_phi_builder;
mod expr_result_resolver;

View File

@ -142,7 +142,17 @@ pub(super) fn process_tail_call_params(
.values()
.any(|entry| entry.phi_dst == param_val_dst);
if is_loop_header_entry_block && is_header_phi_dst {
// Phase 29ae: Do not emit Copy that defines header-PHI dsts.
//
// These PHI dsts must be defined only by header PHIs (at the loop header),
// not by preheader (LoopEntry) or by the header entry block itself.
//
// This prevents SSA double-defs and avoids "undefined value" copies when
// the LoopEntry preheader tries to bind carrier params via remapped PHI ids.
// LoopEntry (host → loop_step) must NOT define header-PHI dsts.
// BackEdge (loop_step → loop_step) is allowed to define them (latch update).
let is_loop_entry_source = is_target_loop_entry && !is_recursive_call;
if is_header_phi_dst && (is_loop_header_entry_block || is_loop_entry_source) {
log!(
verbose,
"[plan_rewrites] Skip param binding to PHI dst {:?}",