fix(joinir/merge): stabilize Pattern2 entry remap
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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!(
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {:?}",
|
||||
|
||||
Reference in New Issue
Block a user