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