324 lines
10 KiB
Rust
324 lines
10 KiB
Rust
|
|
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
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|