2025-11-27 03:28:32 +09:00
|
|
|
//! Phase 33-3: If/Else → Select lowering integration tests
|
|
|
|
|
//!
|
|
|
|
|
//! Tests the pattern matching and lowering of if/else to JoinIR Select instruction.
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use crate::mir::join_ir::lowering::try_lower_if_to_joinir;
|
|
|
|
|
use crate::mir::join_ir::JoinInst;
|
|
|
|
|
use crate::mir::{
|
|
|
|
|
BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, ValueId,
|
|
|
|
|
};
|
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
|
|
|
|
|
/// Helper to create a simple if/else function matching the "simple" pattern
|
|
|
|
|
fn create_simple_pattern_mir() -> MirFunction {
|
|
|
|
|
let mut blocks = BTreeMap::new();
|
|
|
|
|
|
|
|
|
|
// Entry block (bb0): branch on cond
|
|
|
|
|
let mut entry = BasicBlock::new(BasicBlockId::new(0));
|
|
|
|
|
entry.terminator = Some(MirInstruction::Branch {
|
|
|
|
|
condition: ValueId(0), // cond parameter
|
|
|
|
|
then_bb: BasicBlockId::new(1),
|
|
|
|
|
else_bb: BasicBlockId::new(2),
|
|
|
|
|
});
|
|
|
|
|
blocks.insert(BasicBlockId::new(0), entry);
|
|
|
|
|
|
|
|
|
|
// Then block (bb1): return 10
|
|
|
|
|
// NOTE: Pattern matcher expects empty blocks (Return only)
|
|
|
|
|
let mut then_block = BasicBlock::new(BasicBlockId::new(1));
|
|
|
|
|
then_block.terminator = Some(MirInstruction::Return {
|
|
|
|
|
value: Some(ValueId(1)), // Assumes ValueId(1) is const 10
|
|
|
|
|
});
|
|
|
|
|
blocks.insert(BasicBlockId::new(1), then_block);
|
|
|
|
|
|
|
|
|
|
// Else block (bb2): return 20
|
|
|
|
|
// NOTE: Pattern matcher expects empty blocks (Return only)
|
|
|
|
|
let mut else_block = BasicBlock::new(BasicBlockId::new(2));
|
|
|
|
|
else_block.terminator = Some(MirInstruction::Return {
|
|
|
|
|
value: Some(ValueId(2)), // Assumes ValueId(2) is const 20
|
|
|
|
|
});
|
|
|
|
|
blocks.insert(BasicBlockId::new(2), else_block);
|
|
|
|
|
|
|
|
|
|
use crate::mir::{EffectMask, MirType};
|
|
|
|
|
use crate::mir::function::FunctionMetadata;
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
MirFunction {
|
|
|
|
|
signature: crate::mir::FunctionSignature {
|
|
|
|
|
name: "IfSelectTest.test/1".to_string(),
|
|
|
|
|
params: vec![MirType::Unknown],
|
|
|
|
|
return_type: MirType::Integer,
|
|
|
|
|
effects: EffectMask::PURE,
|
|
|
|
|
},
|
|
|
|
|
entry_block: BasicBlockId::new(0),
|
|
|
|
|
blocks: blocks.into_iter().collect(),
|
|
|
|
|
locals: vec![],
|
|
|
|
|
params: vec![ValueId(0)],
|
|
|
|
|
next_value_id: 3,
|
|
|
|
|
metadata: FunctionMetadata::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Helper to create a local pattern function
|
|
|
|
|
fn create_local_pattern_mir() -> MirFunction {
|
|
|
|
|
let mut blocks = BTreeMap::new();
|
|
|
|
|
|
|
|
|
|
// Entry block (bb0): branch on cond
|
|
|
|
|
let mut entry = BasicBlock::new(BasicBlockId::new(0));
|
|
|
|
|
entry.terminator = Some(MirInstruction::Branch {
|
|
|
|
|
condition: ValueId(0), // cond
|
|
|
|
|
then_bb: BasicBlockId::new(1),
|
|
|
|
|
else_bb: BasicBlockId::new(2),
|
|
|
|
|
});
|
|
|
|
|
blocks.insert(BasicBlockId::new(0), entry);
|
|
|
|
|
|
|
|
|
|
// Then block (bb1): x = 100; jump merge
|
|
|
|
|
// NOTE: Pattern matcher expects exactly 1 Copy instruction
|
|
|
|
|
let mut then_block = BasicBlock::new(BasicBlockId::new(1));
|
|
|
|
|
then_block.instructions.push(MirInstruction::Copy {
|
|
|
|
|
dst: ValueId(3), // x
|
|
|
|
|
src: ValueId(10), // Assumes ValueId(10) is const 100
|
|
|
|
|
});
|
|
|
|
|
then_block.terminator = Some(MirInstruction::Jump {
|
|
|
|
|
target: BasicBlockId::new(3),
|
|
|
|
|
});
|
|
|
|
|
blocks.insert(BasicBlockId::new(1), then_block);
|
|
|
|
|
|
|
|
|
|
// Else block (bb2): x = 200; jump merge
|
|
|
|
|
// NOTE: Pattern matcher expects exactly 1 Copy instruction
|
|
|
|
|
let mut else_block = BasicBlock::new(BasicBlockId::new(2));
|
|
|
|
|
else_block.instructions.push(MirInstruction::Copy {
|
|
|
|
|
dst: ValueId(3), // x
|
|
|
|
|
src: ValueId(20), // Assumes ValueId(20) is const 200
|
|
|
|
|
});
|
|
|
|
|
else_block.terminator = Some(MirInstruction::Jump {
|
|
|
|
|
target: BasicBlockId::new(3),
|
|
|
|
|
});
|
|
|
|
|
blocks.insert(BasicBlockId::new(2), else_block);
|
|
|
|
|
|
|
|
|
|
// Merge block (bb3): return x
|
|
|
|
|
let mut merge_block = BasicBlock::new(BasicBlockId::new(3));
|
|
|
|
|
merge_block.terminator = Some(MirInstruction::Return {
|
|
|
|
|
value: Some(ValueId(3)),
|
|
|
|
|
});
|
|
|
|
|
blocks.insert(BasicBlockId::new(3), merge_block);
|
|
|
|
|
|
|
|
|
|
use crate::mir::{EffectMask, MirType};
|
|
|
|
|
use crate::mir::function::FunctionMetadata;
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
MirFunction {
|
|
|
|
|
signature: crate::mir::FunctionSignature {
|
|
|
|
|
name: "IfSelectTest.main/0".to_string(),
|
|
|
|
|
params: vec![],
|
|
|
|
|
return_type: MirType::Integer,
|
|
|
|
|
effects: EffectMask::PURE,
|
|
|
|
|
},
|
|
|
|
|
entry_block: BasicBlockId::new(0),
|
|
|
|
|
blocks: blocks.into_iter().collect(),
|
|
|
|
|
locals: vec![],
|
|
|
|
|
params: vec![],
|
|
|
|
|
next_value_id: 21,
|
|
|
|
|
metadata: FunctionMetadata::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_if_select_simple_pattern() {
|
|
|
|
|
// Set environment variable for this test
|
|
|
|
|
std::env::set_var("NYASH_JOINIR_IF_SELECT", "1");
|
|
|
|
|
|
|
|
|
|
let func = create_simple_pattern_mir();
|
|
|
|
|
let entry_block = func.entry_block;
|
|
|
|
|
|
|
|
|
|
// Try to lower to JoinIR
|
|
|
|
|
let result = try_lower_if_to_joinir(&func, entry_block, true);
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_some(),
|
|
|
|
|
"Expected simple pattern to be lowered to Select"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if let Some(JoinInst::Select {
|
|
|
|
|
dst,
|
|
|
|
|
cond,
|
|
|
|
|
then_val,
|
|
|
|
|
else_val,
|
|
|
|
|
}) = result
|
|
|
|
|
{
|
|
|
|
|
eprintln!("✅ Simple pattern successfully lowered to Select");
|
|
|
|
|
eprintln!(" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", dst, cond, then_val, else_val);
|
|
|
|
|
} else {
|
|
|
|
|
panic!("Expected JoinInst::Select, got {:?}", result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
std::env::remove_var("NYASH_JOINIR_IF_SELECT");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_if_select_local_pattern() {
|
|
|
|
|
std::env::set_var("NYASH_JOINIR_IF_SELECT", "1");
|
|
|
|
|
|
|
|
|
|
let func = create_local_pattern_mir();
|
|
|
|
|
let entry_block = func.entry_block;
|
|
|
|
|
|
|
|
|
|
// Try to lower to JoinIR
|
|
|
|
|
let result = try_lower_if_to_joinir(&func, entry_block, true);
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_some(),
|
|
|
|
|
"Expected local pattern to be lowered to Select"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if let Some(JoinInst::Select {
|
|
|
|
|
dst,
|
|
|
|
|
cond,
|
|
|
|
|
then_val,
|
|
|
|
|
else_val,
|
|
|
|
|
}) = result
|
|
|
|
|
{
|
|
|
|
|
eprintln!("✅ Local pattern successfully lowered to Select");
|
|
|
|
|
eprintln!(" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", dst, cond, then_val, else_val);
|
|
|
|
|
} else {
|
|
|
|
|
panic!("Expected JoinInst::Select, got {:?}", result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::env::remove_var("NYASH_JOINIR_IF_SELECT");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_if_select_disabled_by_default() {
|
|
|
|
|
// Ensure environment variable is NOT set
|
|
|
|
|
std::env::remove_var("NYASH_JOINIR_IF_SELECT");
|
|
|
|
|
|
|
|
|
|
let func = create_simple_pattern_mir();
|
|
|
|
|
let entry_block = func.entry_block;
|
|
|
|
|
|
|
|
|
|
// Should return None when disabled
|
|
|
|
|
let result = try_lower_if_to_joinir(&func, entry_block, false);
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_none(),
|
|
|
|
|
"Expected None when NYASH_JOINIR_IF_SELECT is not set"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
eprintln!("✅ If/Select lowering correctly disabled by default");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_if_select_wrong_function_name() {
|
|
|
|
|
std::env::set_var("NYASH_JOINIR_IF_SELECT", "1");
|
|
|
|
|
|
|
|
|
|
// Create function with wrong name (not IfSelectTest.*)
|
|
|
|
|
let mut func = create_simple_pattern_mir();
|
|
|
|
|
func.signature.name = "WrongName.test/1".to_string();
|
|
|
|
|
|
|
|
|
|
let entry_block = func.entry_block;
|
|
|
|
|
|
|
|
|
|
// Should return None for non-IfSelectTest functions
|
|
|
|
|
let result = try_lower_if_to_joinir(&func, entry_block, true);
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_none(),
|
|
|
|
|
"Expected None for non-IfSelectTest functions"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
eprintln!("✅ Function name filter working correctly");
|
|
|
|
|
|
|
|
|
|
std::env::remove_var("NYASH_JOINIR_IF_SELECT");
|
|
|
|
|
}
|
2025-11-27 03:55:45 +09:00
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Phase 33-3.2: Select verification tests
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// Helper to create a JoinFunction with a valid Select instruction
|
|
|
|
|
fn create_select_joinir() -> crate::mir::join_ir::JoinFunction {
|
|
|
|
|
use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinFunction, JoinInst, MirLikeInst};
|
|
|
|
|
|
|
|
|
|
let func_id = JoinFuncId::new(0);
|
|
|
|
|
let mut join_func = JoinFunction::new(
|
|
|
|
|
func_id,
|
|
|
|
|
"IfSelectTest.test/1".to_string(),
|
|
|
|
|
vec![ValueId(0)], // cond parameter
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Create constants for then/else values
|
|
|
|
|
join_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
|
|
|
|
dst: ValueId(1),
|
|
|
|
|
value: ConstValue::Integer(10),
|
|
|
|
|
}));
|
|
|
|
|
join_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
|
|
|
|
dst: ValueId(2),
|
|
|
|
|
value: ConstValue::Integer(20),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Select instruction
|
|
|
|
|
join_func.body.push(JoinInst::Select {
|
|
|
|
|
dst: ValueId(3),
|
|
|
|
|
cond: ValueId(0),
|
|
|
|
|
then_val: ValueId(1),
|
|
|
|
|
else_val: ValueId(2),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Return result
|
|
|
|
|
join_func.body.push(JoinInst::Ret {
|
|
|
|
|
value: Some(ValueId(3)),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
join_func
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Helper to create a JoinFunction with multiple Select instructions (invalid)
|
|
|
|
|
fn create_double_select_joinir() -> crate::mir::join_ir::JoinFunction {
|
|
|
|
|
use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinFunction, JoinInst, MirLikeInst};
|
|
|
|
|
|
|
|
|
|
let func_id = JoinFuncId::new(0);
|
|
|
|
|
let mut join_func = JoinFunction::new(
|
|
|
|
|
func_id,
|
|
|
|
|
"IfSelectTest.test/1".to_string(),
|
|
|
|
|
vec![ValueId(0)],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// First Select
|
|
|
|
|
join_func.body.push(JoinInst::Select {
|
|
|
|
|
dst: ValueId(1),
|
|
|
|
|
cond: ValueId(0),
|
|
|
|
|
then_val: ValueId(10),
|
|
|
|
|
else_val: ValueId(20),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Second Select (violates single PHI invariant)
|
|
|
|
|
join_func.body.push(JoinInst::Select {
|
|
|
|
|
dst: ValueId(2),
|
|
|
|
|
cond: ValueId(0),
|
|
|
|
|
then_val: ValueId(30),
|
|
|
|
|
else_val: ValueId(40),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
join_func.body.push(JoinInst::Ret {
|
|
|
|
|
value: Some(ValueId(1)),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
join_func
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_if_select_simple_with_verify() {
|
|
|
|
|
use crate::mir::join_ir::verify::verify_select_minimal;
|
|
|
|
|
|
|
|
|
|
// Create simple pattern JoinIR
|
|
|
|
|
let join_func = create_select_joinir();
|
|
|
|
|
|
|
|
|
|
// Verifier should pass
|
|
|
|
|
let result = verify_select_minimal(&join_func, true);
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_ok(),
|
|
|
|
|
"Verify should pass for simple pattern: {:?}",
|
|
|
|
|
result
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
eprintln!("✅ verify_select_minimal passed for simple pattern");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_if_select_local_with_verify() {
|
|
|
|
|
use crate::mir::join_ir::verify::verify_select_minimal;
|
|
|
|
|
|
|
|
|
|
// Create local pattern JoinIR
|
|
|
|
|
let join_func = create_select_joinir();
|
|
|
|
|
|
|
|
|
|
// Verifier should pass
|
|
|
|
|
let result = verify_select_minimal(&join_func, true);
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_ok(),
|
|
|
|
|
"Verify should pass for local pattern: {:?}",
|
|
|
|
|
result
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
eprintln!("✅ verify_select_minimal passed for local pattern");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_if_select_verify_rejects_multiple_selects() {
|
|
|
|
|
use crate::mir::join_ir::verify::verify_select_minimal;
|
|
|
|
|
|
|
|
|
|
// Create JoinIR with 2 Select instructions (invalid)
|
|
|
|
|
let join_func = create_double_select_joinir();
|
|
|
|
|
|
|
|
|
|
// Verifier should reject
|
|
|
|
|
let result = verify_select_minimal(&join_func, true);
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_err(),
|
|
|
|
|
"Verify should reject multiple Selects"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Err(e) => {
|
|
|
|
|
let msg = e.to_string();
|
|
|
|
|
assert!(
|
|
|
|
|
msg.contains("expected exactly 1 Select, found 2"),
|
|
|
|
|
"Error message should mention multiple Selects: {}",
|
|
|
|
|
msg
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
msg.contains("single PHI"),
|
|
|
|
|
"Error message should reference single PHI invariant: {}",
|
|
|
|
|
msg
|
|
|
|
|
);
|
|
|
|
|
eprintln!("✅ verify_select_minimal correctly rejected multiple Selects");
|
|
|
|
|
}
|
|
|
|
|
Ok(_) => panic!("Expected Err, got Ok"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_if_select_verify_checks_invariants() {
|
|
|
|
|
use crate::mir::join_ir::verify::verify_select_minimal;
|
|
|
|
|
|
|
|
|
|
// Create valid JoinIR
|
|
|
|
|
let join_func = create_select_joinir();
|
|
|
|
|
|
|
|
|
|
// Capture debug output
|
|
|
|
|
let result = verify_select_minimal(&join_func, true);
|
|
|
|
|
assert!(result.is_ok(), "Verification should pass");
|
|
|
|
|
|
|
|
|
|
// The debug output (visible with --nocapture) should mention:
|
|
|
|
|
// - "Invariants verified"
|
|
|
|
|
// - "single PHI (from conservative.rs)"
|
|
|
|
|
// - "completeness (from phi_invariants.rs)"
|
|
|
|
|
|
|
|
|
|
eprintln!("✅ verify_select_minimal properly checks invariants from phi_invariants.rs and conservative.rs");
|
|
|
|
|
}
|
2025-11-27 03:28:32 +09:00
|
|
|
}
|