refactor(joinir): Phase 287 P0.1 - Move verification to debug_assertions
- Move verify_no_phi_dst_overwrite() to debug_assertions.rs - Move verify_phi_inputs_defined() to debug_assertions.rs - Move verify_joinir_contracts() to debug_assertions.rs - Remove duplicate get_instruction_dst() from mod.rs - mod.rs: 1,555 → ~1,380 lines (-176 lines) - Semantic invariance: 154/154 smoke tests PASS, Pattern6 RC:9 Phase 287 P0: Big Files Refactoring (意味論不変) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -438,3 +438,156 @@ pub(super) fn verify_header_phi_dsts_not_redefined(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 204-2: Verify PHI dst ValueIds are not overwritten by subsequent instructions in header block
|
||||
///
|
||||
/// # Contract
|
||||
///
|
||||
/// PHI instructions must appear first in a basic block, and their dst ValueIds
|
||||
/// must not be overwritten by any subsequent non-PHI instructions in the same block.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if:
|
||||
/// - PHI instruction appears after non-PHI instructions
|
||||
/// - Non-PHI instruction overwrites a PHI dst in the header block
|
||||
#[cfg(debug_assertions)]
|
||||
pub(super) fn verify_no_phi_dst_overwrite(
|
||||
func: &MirFunction,
|
||||
header_block: 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<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 {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify PHI inputs are defined (Phase 204-3 - Conservative sanity checks)
|
||||
///
|
||||
/// # Checks
|
||||
///
|
||||
/// 1. PHI inputs have reasonable ValueId values (< threshold)
|
||||
/// 2. No obviously undefined values (e.g., suspiciously large IDs)
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Full data-flow analysis (DFA) verification is deferred to Phase 205+.
|
||||
/// This function only performs conservative sanity checks.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if suspicious PHI inputs are detected.
|
||||
#[cfg(debug_assertions)]
|
||||
pub(super) fn verify_phi_inputs_defined(
|
||||
func: &MirFunction,
|
||||
header_block: BasicBlockId,
|
||||
) {
|
||||
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()
|
||||
)
|
||||
});
|
||||
|
||||
for instr in &header_block_data.instructions {
|
||||
if let MirInstruction::Phi {
|
||||
dst,
|
||||
inputs,
|
||||
type_hint: _,
|
||||
} = instr
|
||||
{
|
||||
for (value_id, pred_block) in inputs {
|
||||
// Conservative sanity check: ValueId should not be suspiciously large
|
||||
// Phase 201 JoinValueSpace uses regions:
|
||||
// - PHI Reserved: 0-99
|
||||
// - Param: 100-999
|
||||
// - Local: 1000+
|
||||
// - Reasonable max: 100000 (arbitrary but catches obvious bugs)
|
||||
if value_id.0 >= 100000 {
|
||||
panic!(
|
||||
"[JoinIRVerifier/Phase204-3] PHI {:?} has suspiciously large input {:?} from predecessor block {:?}",
|
||||
dst, value_id, pred_block
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify all loop contracts for a merged JoinIR function
|
||||
///
|
||||
/// This is the main entry point for verification. It runs all checks
|
||||
/// and panics if any contract violation is found.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if any contract violation is detected.
|
||||
#[cfg(debug_assertions)]
|
||||
pub(super) fn verify_joinir_contracts(
|
||||
func: &MirFunction,
|
||||
header_block: BasicBlockId,
|
||||
exit_block: BasicBlockId,
|
||||
loop_info: &LoopHeaderPhiInfo,
|
||||
boundary: &JoinInlineBoundary,
|
||||
) {
|
||||
// Phase 135 P1 Step 1: Verify condition_bindings consistency (before merge)
|
||||
verify_condition_bindings_consistent(boundary);
|
||||
|
||||
verify_loop_header_phis(func, header_block, loop_info, boundary);
|
||||
verify_no_phi_dst_overwrite(func, header_block, loop_info); // Phase 204-2
|
||||
verify_phi_inputs_defined(func, header_block); // Phase 204-3
|
||||
verify_exit_line(func, exit_block, boundary);
|
||||
verify_valueid_regions(loop_info, boundary); // Phase 205-4
|
||||
|
||||
// Phase 135 P1 Step 2: Verify header PHI dsts not redefined (after merge)
|
||||
let phi_dsts: std::collections::HashSet<_> = loop_info
|
||||
.carrier_phis
|
||||
.values()
|
||||
.map(|entry| entry.phi_dst)
|
||||
.collect();
|
||||
verify_header_phi_dsts_not_redefined(func, header_block, &phi_dsts);
|
||||
}
|
||||
|
||||
@ -1209,7 +1209,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
{
|
||||
if let Some(boundary) = boundary {
|
||||
if let Some(ref func) = builder.scope_ctx.current_function {
|
||||
verify_joinir_contracts(
|
||||
debug_assertions::verify_joinir_contracts(
|
||||
func,
|
||||
entry_block,
|
||||
exit_block_id,
|
||||
@ -1361,195 +1361,4 @@ fn remap_values(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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 PHI inputs are defined (Phase 204-3 - Conservative sanity checks)
|
||||
///
|
||||
/// # Checks
|
||||
///
|
||||
/// 1. PHI inputs have reasonable ValueId values (< threshold)
|
||||
/// 2. No obviously undefined values (e.g., suspiciously large IDs)
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Full data-flow analysis (DFA) verification is deferred to Phase 205+.
|
||||
/// This function only performs conservative sanity checks.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if suspicious PHI inputs are detected.
|
||||
#[cfg(debug_assertions)]
|
||||
fn verify_phi_inputs_defined(
|
||||
func: &crate::mir::MirFunction,
|
||||
header_block: crate::mir::BasicBlockId,
|
||||
) {
|
||||
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()
|
||||
)
|
||||
});
|
||||
|
||||
for instr in &header_block_data.instructions {
|
||||
if let crate::mir::MirInstruction::Phi {
|
||||
dst,
|
||||
inputs,
|
||||
type_hint: _,
|
||||
} = instr
|
||||
{
|
||||
for (value_id, pred_block) in inputs {
|
||||
// Conservative sanity check: ValueId should not be suspiciously large
|
||||
// Phase 201 JoinValueSpace uses regions:
|
||||
// - PHI Reserved: 0-99
|
||||
// - Param: 100-999
|
||||
// - Local: 1000+
|
||||
// - Reasonable max: 100000 (arbitrary but catches obvious bugs)
|
||||
if value_id.0 >= 100000 {
|
||||
panic!(
|
||||
"[JoinIRVerifier/Phase204-3] PHI {:?} has suspiciously large input {:?} from predecessor block {:?}",
|
||||
dst, value_id, pred_block
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify all loop contracts for a merged JoinIR function
|
||||
///
|
||||
/// This is the main entry point for verification. It runs all checks
|
||||
/// and panics if any contract violation is found.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if any contract violation is detected.
|
||||
#[cfg(debug_assertions)]
|
||||
fn verify_joinir_contracts(
|
||||
func: &crate::mir::MirFunction,
|
||||
header_block: crate::mir::BasicBlockId,
|
||||
exit_block: crate::mir::BasicBlockId,
|
||||
loop_info: &LoopHeaderPhiInfo,
|
||||
boundary: &JoinInlineBoundary,
|
||||
) {
|
||||
// Phase 135 P1 Step 1: Verify condition_bindings consistency (before merge)
|
||||
debug_assertions::verify_condition_bindings_consistent(boundary);
|
||||
|
||||
debug_assertions::verify_loop_header_phis(func, header_block, loop_info, boundary);
|
||||
verify_no_phi_dst_overwrite(func, header_block, loop_info); // Phase 204-2
|
||||
verify_phi_inputs_defined(func, header_block); // Phase 204-3
|
||||
debug_assertions::verify_exit_line(func, exit_block, boundary);
|
||||
debug_assertions::verify_valueid_regions(loop_info, boundary); // Phase 205-4
|
||||
|
||||
// Phase 135 P1 Step 2: Verify header PHI dsts not redefined (after merge)
|
||||
let phi_dsts: std::collections::HashSet<_> = loop_info
|
||||
.carrier_phis
|
||||
.values()
|
||||
.map(|entry| entry.phi_dst)
|
||||
.collect();
|
||||
debug_assertions::verify_header_phi_dsts_not_redefined(func, header_block, &phi_dsts);
|
||||
}
|
||||
// Phase 287 P0.1: Verification functions moved to debug_assertions.rs
|
||||
|
||||
Reference in New Issue
Block a user