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:
2025-12-27 10:10:59 +09:00
parent a04b48416e
commit 433e1d45c0
5 changed files with 416 additions and 193 deletions

View File

@ -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);
}

View File

@ -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