Files
hakorune/src/tests/loop_nested_no_phi_tests.rs

184 lines
10 KiB
Rust

use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::mir::{MirCompiler, MirInstruction};
fn lit_i(i: i64) -> ASTNode {
ASTNode::Literal { value: LiteralValue::Integer(i), span: Span::unknown() }
}
fn bin(op: BinaryOperator, l: ASTNode, r: ASTNode) -> ASTNode {
ASTNode::BinaryOp { operator: op, left: Box::new(l), right: Box::new(r), span: Span::unknown() }
}
#[test]
fn nested_loop_with_multi_continue_break_edge_copy_merge() {
// PHI-off mode
std::env::set_var("NYASH_MIR_NO_PHI", "1");
// i=0; sum=0; loop(i < 10) {
// i = i + 1;
// if (i == 2 || i == 4) { continue }
// if (i == 7) { if (1 == 1) { break } }
// if ((i % 3) == 0) { continue }
// sum = sum + i;
// }
// return sum
let ast = ASTNode::Program {
statements: vec![
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "i".into(), span: Span::unknown() }), value: Box::new(lit_i(0)), span: Span::unknown() },
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "sum".into(), span: Span::unknown() }), value: Box::new(lit_i(0)), span: Span::unknown() },
ASTNode::Loop {
condition: Box::new(bin(BinaryOperator::Less, ASTNode::Variable { name: "i".into(), span: Span::unknown() }, lit_i(10))),
body: vec![
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "i".into(), span: Span::unknown() }), value: Box::new(bin(BinaryOperator::Add, ASTNode::Variable { name: "i".into(), span: Span::unknown() }, lit_i(1))), span: Span::unknown() },
ASTNode::If { condition: Box::new(bin(BinaryOperator::Or, bin(BinaryOperator::Equal, ASTNode::Variable { name: "i".into(), span: Span::unknown() }, lit_i(2)), bin(BinaryOperator::Equal, ASTNode::Variable { name: "i".into(), span: Span::unknown() }, lit_i(4)))), then_body: vec![ ASTNode::Continue { span: Span::unknown() } ], else_body: Some(vec![]), span: Span::unknown() },
ASTNode::If { condition: Box::new(bin(BinaryOperator::Equal, ASTNode::Variable { name: "i".into(), span: Span::unknown() }, lit_i(7))), then_body: vec![ ASTNode::If { condition: Box::new(bin(BinaryOperator::Equal, lit_i(1), lit_i(1))), then_body: vec![ ASTNode::Break { span: Span::unknown() } ], else_body: Some(vec![]), span: Span::unknown() } ], else_body: Some(vec![]), span: Span::unknown() },
ASTNode::If { condition: Box::new(bin(BinaryOperator::Equal, bin(BinaryOperator::Modulo, ASTNode::Variable { name: "i".into(), span: Span::unknown() }, lit_i(3)), lit_i(0))), then_body: vec![ ASTNode::Continue { span: Span::unknown() } ], else_body: Some(vec![]), span: Span::unknown() },
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "sum".into(), span: Span::unknown() }), value: Box::new(bin(BinaryOperator::Add, ASTNode::Variable { name: "sum".into(), span: Span::unknown() }, ASTNode::Variable { name: "i".into(), span: Span::unknown() })), span: Span::unknown() },
],
span: Span::unknown(),
},
ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: "sum".into(), span: Span::unknown() })), span: Span::unknown() }
],
span: Span::unknown(),
};
let mut mc = MirCompiler::with_options(false);
let cr = mc.compile(ast).expect("compile");
let f = cr.module.functions.get("main").expect("function main");
// Locate return block/value
let (ret_block, out_v) = f
.blocks
.iter()
.find_map(|(bid, bb)| match &bb.terminator {
Some(MirInstruction::Return { value: Some(v) }) => Some((*bid, *v)),
_ => None,
})
.expect("ret block");
// In PHI-off, every predecessor of the ret block should write the merged value via edge Copy
let preds: Vec<_> = f.blocks.get(&ret_block).unwrap().predecessors.iter().copied().collect();
assert!(preds.len() >= 1, "ret must have at least one predecessor");
for p in preds {
let bb = f.blocks.get(&p).unwrap();
let has_copy = bb.instructions.iter().any(|inst| matches!(inst, MirInstruction::Copy { dst, .. } if *dst == out_v));
assert!(has_copy, "expected edge Copy to merged value at predecessor {:?}", p);
}
// ret block itself must not contain Copy to out_v
let merge_has_copy = f
.blocks
.get(&ret_block)
.unwrap()
.instructions
.iter()
.any(|inst| matches!(inst, MirInstruction::Copy { dst, .. } if *dst == out_v));
assert!(!merge_has_copy, "ret/merge must not contain Copy to merged value");
}
#[test]
fn loop_inner_if_multilevel_edge_copy() {
// PHI-off
std::env::set_var("NYASH_MIR_NO_PHI", "1");
// j=0; acc=0; loop(j<6){ j=j+1; if(j<3){ if(j%2==0){continue} acc=acc+10 } else { if(j==5){break} acc=acc+1 } } return acc
let ast = ASTNode::Program {
statements: vec![
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "j".into(), span: Span::unknown() }), value: Box::new(lit_i(0)), span: Span::unknown() },
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "acc".into(), span: Span::unknown() }), value: Box::new(lit_i(0)), span: Span::unknown() },
ASTNode::Loop {
condition: Box::new(bin(BinaryOperator::Less, ASTNode::Variable { name: "j".into(), span: Span::unknown() }, lit_i(6))),
body: vec![
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "j".into(), span: Span::unknown() }), value: Box::new(bin(BinaryOperator::Add, ASTNode::Variable { name: "j".into(), span: Span::unknown() }, lit_i(1))), span: Span::unknown() },
ASTNode::If {
condition: Box::new(bin(BinaryOperator::Less, ASTNode::Variable { name: "j".into(), span: Span::unknown() }, lit_i(3))),
then_body: vec![
ASTNode::If {
condition: Box::new(bin(BinaryOperator::Equal, bin(BinaryOperator::Modulo, ASTNode::Variable { name: "j".into(), span: Span::unknown() }, lit_i(2)), lit_i(0))),
then_body: vec![ASTNode::Continue { span: Span::unknown() }],
else_body: Some(vec![]),
span: Span::unknown(),
},
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "acc".into(), span: Span::unknown() }), value: Box::new(bin(BinaryOperator::Add, ASTNode::Variable { name: "acc".into(), span: Span::unknown() }, lit_i(10))), span: Span::unknown() },
],
else_body: Some(vec![
ASTNode::If { condition: Box::new(bin(BinaryOperator::Equal, ASTNode::Variable { name: "j".into(), span: Span::unknown() }, lit_i(5))), then_body: vec![ ASTNode::Break { span: Span::unknown() } ], else_body: Some(vec![]), span: Span::unknown() },
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "acc".into(), span: Span::unknown() }), value: Box::new(bin(BinaryOperator::Add, ASTNode::Variable { name: "acc".into(), span: Span::unknown() }, lit_i(1))), span: Span::unknown() },
]),
span: Span::unknown(),
},
],
span: Span::unknown(),
},
ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: "acc".into(), span: Span::unknown() })), span: Span::unknown() }
],
span: Span::unknown(),
};
let mut mc = MirCompiler::with_options(false);
let cr = mc.compile(ast).expect("compile");
let f = cr.module.functions.get("main").expect("function main");
// Find ret block/value
let (ret_block, out_v) = f
.blocks
.iter()
.find_map(|(bid, bb)| match &bb.terminator {
Some(MirInstruction::Return { value: Some(v) }) => Some((*bid, *v)),
_ => None,
})
.expect("ret block");
let preds: Vec<_> = f.blocks.get(&ret_block).unwrap().predecessors.iter().copied().collect();
assert!(preds.len() >= 1, "ret must have predecessors");
for p in preds {
let bb = f.blocks.get(&p).unwrap();
assert!(bb.instructions.iter().any(|inst| matches!(inst, MirInstruction::Copy { dst, .. } if *dst == out_v)));
}
}
#[cfg(feature = "phi-legacy")]
#[test]
#[ignore]
fn phi_on_loop_has_phi_in_header() {
// PHI-on (explicit)
std::env::set_var("NYASH_MIR_NO_PHI", "0");
// x=0; loop(x<3){ x=x+1 } return x
let ast = ASTNode::Program {
statements: vec![
ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "x".into(), span: Span::unknown() }), value: Box::new(lit_i(0)), span: Span::unknown() },
ASTNode::Loop {
condition: Box::new(bin(BinaryOperator::Less, ASTNode::Variable { name: "x".into(), span: Span::unknown() }, lit_i(3))),
body: vec![ ASTNode::Assignment { target: Box::new(ASTNode::Variable { name: "x".into(), span: Span::unknown() }), value: Box::new(bin(BinaryOperator::Add, ASTNode::Variable { name: "x".into(), span: Span::unknown() }, lit_i(1))), span: Span::unknown() } ],
span: Span::unknown(),
},
ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: "x".into(), span: Span::unknown() })), span: Span::unknown() }
],
span: Span::unknown(),
};
let mut mc = MirCompiler::with_options(false);
let cr = mc.compile(ast).expect("compile");
let f = cr.module.functions.get("main").expect("function main");
// Find any block with Phi(s) — loop header should have them in PHI-on.
let phi_blocks: Vec<_> = f
.blocks
.iter()
.filter(|(_bid, bb)| bb.phi_instructions().count() > 0)
.map(|(bid, _)| *bid)
.collect();
assert!(!phi_blocks.is_empty(), "expected at least one Phi block in PHI-on");
// Spot-check: each Phi should have at least 2 inputs (preheader + latch) in a loop
for bid in phi_blocks.into_iter() {
let bb = f.blocks.get(&bid).unwrap();
for inst in bb.phi_instructions() {
if let MirInstruction::Phi { inputs, .. } = inst {
assert!(inputs.len() >= 2, "Phi should have at least 2 inputs");
}
}
}
}