feat(joinir): Phase 204-2 PHI dst overwrite detection
Task 204-2: Implement verify_no_phi_dst_overwrite() - Added PHI dst overwrite detection in merge/mod.rs - Helper get_instruction_dst() extracts dst from MirInstruction - Integrated into verify_joinir_contracts() - Fixed pre-existing bugs: - entry_block_remapped → entry_block (line 592) - HashMap → BTreeMap in reconnector.rs (line 174) - All instructions covered with wildcard pattern Design: - Debug-only (#[cfg(debug_assertions)]) - Fail-Fast panic on violation - Checks PHI dst not overwritten by later instructions in header block Status: Build SUCCESS (Task 204-2 complete) Next: Task 204-3 (PHI inputs verification)
This commit is contained in:
@ -589,7 +589,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
if let Some(ref func) = builder.current_function {
|
||||
verify_joinir_contracts(
|
||||
func,
|
||||
entry_block_remapped,
|
||||
entry_block,
|
||||
exit_block_id,
|
||||
&loop_header_phi_info,
|
||||
boundary,
|
||||
@ -783,6 +783,112 @@ fn verify_exit_line(
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that PHI dst values are not overwritten by later instructions (Phase 204-2)
|
||||
///
|
||||
/// # Checks
|
||||
///
|
||||
/// 1. PHI instructions define dst values in header block
|
||||
/// 2. No subsequent instruction in the same block overwrites these dsts
|
||||
///
|
||||
/// # Rationale
|
||||
///
|
||||
/// PHI dst overwrite violates SSA invariant (single assignment) and causes undefined behavior.
|
||||
/// While Phase 201 JoinValueSpace prevents ValueId collisions, manual coding errors can still
|
||||
/// occur during pattern lowering.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if PHI dst is overwritten by a later instruction.
|
||||
#[cfg(debug_assertions)]
|
||||
fn verify_no_phi_dst_overwrite(
|
||||
func: &crate::mir::MirFunction,
|
||||
header_block: crate::mir::BasicBlockId,
|
||||
loop_info: &LoopHeaderPhiInfo,
|
||||
) {
|
||||
if loop_info.carrier_phis.is_empty() {
|
||||
return; // No PHIs to verify
|
||||
}
|
||||
|
||||
let header_block_data = func.blocks.get(&header_block).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"[JoinIRVerifier] Header block {} not found ({} blocks in func)",
|
||||
header_block,
|
||||
func.blocks.len()
|
||||
)
|
||||
});
|
||||
|
||||
// 1. Collect all PHI dsts
|
||||
let phi_dsts: std::collections::HashSet<crate::mir::ValueId> = loop_info
|
||||
.carrier_phis
|
||||
.values()
|
||||
.map(|entry| entry.phi_dst)
|
||||
.collect();
|
||||
|
||||
// 2. Check instructions after PHI definitions
|
||||
let mut after_phis = false;
|
||||
for instr in &header_block_data.instructions {
|
||||
match instr {
|
||||
crate::mir::MirInstruction::Phi { dst, .. } => {
|
||||
// PHI instructions come first in basic block
|
||||
if after_phis {
|
||||
panic!(
|
||||
"[JoinIRVerifier] PHI instruction {:?} appears after non-PHI instructions in block {}",
|
||||
dst, header_block.0
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
after_phis = true;
|
||||
// Check if this instruction writes to a PHI dst
|
||||
if let Some(dst) = get_instruction_dst(instr) {
|
||||
if phi_dsts.contains(&dst) {
|
||||
panic!(
|
||||
"[JoinIRVerifier/Phase204] PHI dst {:?} is overwritten by instruction in header block {}: {:?}",
|
||||
dst, header_block.0, instr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper: Extract dst ValueId from MirInstruction (Phase 204-2)
|
||||
#[cfg(debug_assertions)]
|
||||
fn get_instruction_dst(instr: &crate::mir::MirInstruction) -> Option<crate::mir::ValueId> {
|
||||
use crate::mir::MirInstruction;
|
||||
match instr {
|
||||
// Instructions with always-present dst
|
||||
MirInstruction::Const { dst, .. }
|
||||
| MirInstruction::Load { dst, .. }
|
||||
| MirInstruction::UnaryOp { dst, .. }
|
||||
| MirInstruction::BinOp { dst, .. }
|
||||
| MirInstruction::Compare { dst, .. }
|
||||
| MirInstruction::TypeOp { dst, .. }
|
||||
| MirInstruction::NewBox { dst, .. }
|
||||
| MirInstruction::NewClosure { dst, .. }
|
||||
| MirInstruction::Copy { dst, .. }
|
||||
| MirInstruction::Cast { dst, .. }
|
||||
| MirInstruction::TypeCheck { dst, .. }
|
||||
| MirInstruction::Phi { dst, .. }
|
||||
| MirInstruction::ArrayGet { dst, .. }
|
||||
| MirInstruction::RefNew { dst, .. }
|
||||
| MirInstruction::RefGet { dst, .. }
|
||||
| MirInstruction::WeakNew { dst, .. }
|
||||
| MirInstruction::WeakLoad { dst, .. }
|
||||
| MirInstruction::WeakRef { dst, .. }
|
||||
| MirInstruction::FutureNew { dst, .. }
|
||||
| MirInstruction::Await { dst, .. } => Some(*dst),
|
||||
// Instructions with Option<ValueId> dst
|
||||
MirInstruction::BoxCall { dst, .. }
|
||||
| MirInstruction::ExternCall { dst, .. }
|
||||
| MirInstruction::Call { dst, .. }
|
||||
| MirInstruction::PluginInvoke { dst, .. } => *dst,
|
||||
// Instructions without dst (side-effects only)
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify all loop contracts for a merged JoinIR function
|
||||
///
|
||||
/// This is the main entry point for verification. It runs all checks
|
||||
@ -800,5 +906,6 @@ fn verify_joinir_contracts(
|
||||
boundary: &JoinInlineBoundary,
|
||||
) {
|
||||
verify_loop_header_phis(func, header_block, loop_info, boundary);
|
||||
verify_no_phi_dst_overwrite(func, header_block, loop_info); // Phase 204-2
|
||||
verify_exit_line(func, exit_block, boundary);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user