Files
hakorune/tests/mir_verification_unit.rs

326 lines
11 KiB
Rust
Raw Normal View History

use nyash_rust::ast::{ASTNode, LiteralValue, Span};
use nyash_rust::mir::{
BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirBuilder, MirFunction,
MirInstruction, MirPrinter, MirType, MirVerifier, VerificationError,
};
#[test]
fn test_valid_function_verification() {
let signature = FunctionSignature {
name: "test".to_string(),
params: vec![],
return_type: MirType::Void,
effects: EffectMask::PURE,
};
let entry_block = BasicBlockId::new(0);
let function = MirFunction::new(signature, entry_block);
let mut verifier = MirVerifier::new();
let result = verifier.verify_function(&function);
assert!(result.is_ok(), "Valid function should pass verification");
}
#[test]
fn test_undefined_value_detection() {
// Placeholder: Define a minimal function without uses; this test is a scaffold.
let signature = FunctionSignature {
name: "undef_sanity".to_string(),
params: vec![],
return_type: MirType::Void,
effects: EffectMask::PURE,
};
let entry_block = BasicBlockId::new(0);
let function = MirFunction::new(signature, entry_block);
let mut verifier = MirVerifier::new();
let _ = verifier.verify_function(&function).unwrap();
}
#[test]
fn test_if_merge_uses_phi_not_predecessor() {
// Program:
// if true { result = "A" } else { result = "B" }
// result
let ast = ASTNode::Program {
statements: vec![
ASTNode::If {
condition: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(true),
span: Span::unknown(),
}),
then_body: vec![ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "result".to_string(),
span: Span::unknown(),
}),
value: Box::new(ASTNode::Literal {
value: LiteralValue::String("A".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}],
else_body: Some(vec![ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "result".to_string(),
span: Span::unknown(),
}),
value: Box::new(ASTNode::Literal {
value: LiteralValue::String("B".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}]),
span: Span::unknown(),
},
ASTNode::Variable {
name: "result".to_string(),
span: Span::unknown(),
},
],
span: Span::unknown(),
};
let mut builder = MirBuilder::new();
let module = builder.build_module(ast).expect("build mir");
// Verify: should be OK (no MergeUsesPredecessorValue)
let mut verifier = MirVerifier::new();
let res = verifier.verify_module(&module);
if let Err(errs) = &res {
eprintln!("Verifier errors: {:?}", errs);
}
assert!(res.is_ok(), "MIR should pass merge-phi verification");
// Optional: ensure printer shows a phi in merge and ret returns a defined value
let mut printer = MirPrinter::verbose();
let mir_text = printer.print_module(&module);
assert!(
mir_text.contains("phi"),
"Printed MIR should contain a phi in merge block\n{}",
mir_text
);
}
#[test]
fn test_merge_use_before_phi_detected() {
// Construct a function with a bad merge use (no phi)
let signature = FunctionSignature {
name: "merge_bad".to_string(),
params: vec![],
return_type: MirType::String,
effects: EffectMask::PURE,
};
let entry = BasicBlockId::new(0);
let mut f = MirFunction::new(signature, entry);
let then_bb = BasicBlockId::new(1);
let else_bb = BasicBlockId::new(2);
let merge_bb = BasicBlockId::new(3);
let cond = f.next_value_id(); // %0
{
let b0 = f.get_block_mut(entry).unwrap();
b0.add_instruction(MirInstruction::Const {
dst: cond,
value: ConstValue::Bool(true),
});
b0.add_instruction(MirInstruction::Branch {
condition: cond,
then_bb,
else_bb,
});
}
let v1 = f.next_value_id(); // %1
let mut b1 = BasicBlock::new(then_bb);
b1.add_instruction(MirInstruction::Const {
dst: v1,
value: ConstValue::String("A".to_string()),
});
b1.add_instruction(MirInstruction::Jump { target: merge_bb });
f.add_block(b1);
let v2 = f.next_value_id(); // %2
let mut b2 = BasicBlock::new(else_bb);
b2.add_instruction(MirInstruction::Const {
dst: v2,
value: ConstValue::String("B".to_string()),
});
b2.add_instruction(MirInstruction::Jump { target: merge_bb });
f.add_block(b2);
let mut b3 = BasicBlock::new(merge_bb);
// Illegal: directly use v1 from predecessor
b3.add_instruction(MirInstruction::Return { value: Some(v1) });
f.add_block(b3);
f.update_cfg();
let mut verifier = MirVerifier::new();
let res = verifier.verify_function(&f);
assert!(
res.is_err(),
"Verifier should error on merge use without phi"
);
let errs = res.err().unwrap();
assert!(
errs.iter().any(|e| matches!(
e,
VerificationError::MergeUsesPredecessorValue { .. }
| VerificationError::DominatorViolation { .. }
)),
"Expected merge/dominator error, got: {:?}",
errs
);
}
#[test]
fn test_loop_phi_normalization() {
// Program:
// local i = 0
// loop(false) { i = 1 }
// i
let ast = ASTNode::Program {
statements: vec![
ASTNode::Local {
variables: vec!["i".to_string()],
initial_values: vec![Some(Box::new(ASTNode::Literal {
value: LiteralValue::Integer(0),
span: Span::unknown(),
}))],
span: Span::unknown(),
},
ASTNode::Loop {
condition: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(false),
span: Span::unknown(),
}),
body: vec![ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
value: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(1),
span: Span::unknown(),
}),
span: Span::unknown(),
}],
span: Span::unknown(),
},
ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
},
],
span: Span::unknown(),
};
let mut builder = MirBuilder::new();
let module = builder.build_module(ast).expect("build mir");
// Verify SSA/dominance: should pass
let mut verifier = MirVerifier::new();
let res = verifier.verify_module(&module);
if let Err(errs) = &res {
eprintln!("Verifier errors: {:?}", errs);
}
assert!(
res.is_ok(),
"MIR loop with phi normalization should pass verification"
);
// Ensure phi is printed (header phi for variable i)
let printer = MirPrinter::verbose();
let mir_text = printer.print_module(&module);
assert!(
mir_text.contains("phi"),
"Printed MIR should contain a phi for loop header\n{}",
mir_text
);
}
#[test]
fn test_loop_nested_if_phi() {
// Program:
// local x = 0
// loop(false) { if true { x = 1 } else { x = 2 } }
// x
let ast = ASTNode::Program {
statements: vec![
ASTNode::Local {
variables: vec!["x".to_string()],
initial_values: vec![Some(Box::new(ASTNode::Literal {
value: LiteralValue::Integer(0),
span: Span::unknown(),
}))],
span: Span::unknown(),
},
ASTNode::Loop {
condition: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(false),
span: Span::unknown(),
}),
body: vec![ASTNode::If {
condition: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(true),
span: Span::unknown(),
}),
then_body: vec![ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: Span::unknown(),
}),
value: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(1),
span: Span::unknown(),
}),
span: Span::unknown(),
}],
else_body: Some(vec![ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: Span::unknown(),
}),
value: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(2),
span: Span::unknown(),
}),
span: Span::unknown(),
}]),
span: Span::unknown(),
}],
span: Span::unknown(),
},
ASTNode::Variable {
name: "x".to_string(),
span: Span::unknown(),
},
],
span: Span::unknown(),
};
let mut builder = MirBuilder::new();
let module = builder.build_module(ast).expect("build mir");
let mut verifier = MirVerifier::new();
let res = verifier.verify_module(&module);
if let Err(errs) = &res {
eprintln!("Verifier errors: {:?}", errs);
}
assert!(
res.is_ok(),
"Nested if in loop should pass verification with proper phis"
);
let printer = MirPrinter::verbose();
let mir_text = printer.print_module(&module);
assert!(
mir_text.contains("phi"),
"Printed MIR should contain phi nodes for nested if/loop\n{}",
mir_text
);
}