diff --git a/.gitignore b/.gitignore index ea323cb541..fc7ef7029c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ **/*.toml~ *.ir *.o +*.bc diff --git a/examples/while_loop_with_continue.st b/examples/while_loop_with_continue.st new file mode 100644 index 0000000000..82f87c0bac --- /dev/null +++ b/examples/while_loop_with_continue.st @@ -0,0 +1,8 @@ +FUNCTION main : DINT + main := 1; + WHILE main < 10 DO + main := main + 1; + CONTINUE; + main := 200; + END_WHILE +END_FUNCTION diff --git a/src/ast.rs b/src/ast.rs index 4585a07a8e..e7f2b5f812 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -552,6 +552,14 @@ pub enum Statement { condition: Box, id: AstId, }, + ExitStatement { + location: SourceRange, + id: AstId, + }, + ContinueStatement { + location: SourceRange, + id: AstId, + }, ReturnStatement { location: SourceRange, id: AstId, @@ -763,6 +771,8 @@ impl Debug for Statement { .field("condition", condition) .finish(), Statement::ReturnStatement { .. } => f.debug_struct("ReturnStatement").finish(), + Statement::ContinueStatement { .. } => f.debug_struct("ContinueStatement").finish(), + Statement::ExitStatement { .. } => f.debug_struct("ExitStatement").finish(), } } } @@ -844,6 +854,8 @@ impl Statement { Statement::MultipliedStatement { location, .. } => location.clone(), Statement::CaseCondition { condition, .. } => condition.get_location(), Statement::ReturnStatement { location, .. } => location.clone(), + Statement::ContinueStatement { location, .. } => location.clone(), + Statement::ExitStatement { location, .. } => location.clone(), } } @@ -877,6 +889,8 @@ impl Statement { Statement::CaseStatement { id, .. } => *id, Statement::CaseCondition { id, .. } => *id, Statement::ReturnStatement { id, .. } => *id, + Statement::ContinueStatement { id, .. } => *id, + Statement::ExitStatement { id, .. } => *id, } } } diff --git a/src/codegen/generators/statement_generator.rs b/src/codegen/generators/statement_generator.rs index 7b0eb36a03..e9cff3b590 100644 --- a/src/codegen/generators/statement_generator.rs +++ b/src/codegen/generators/statement_generator.rs @@ -1,25 +1,23 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder -use std::ops::Range; - -use super::pou_generator::PouGenerator; -use super::{expression_generator::ExpressionCodeGenerator, llvm::Llvm}; -use crate::ast::PouType; -use crate::codegen::LlvmTypedIndex; -use crate::typesystem::{RANGE_CHECK_LS_FN, RANGE_CHECK_LU_FN, RANGE_CHECK_S_FN, RANGE_CHECK_U_FN}; -use crate::{ast::SourceRange, codegen::llvm_typesystem::cast_if_needed}; -use crate::{ - ast::{flatten_expression_list, ConditionalBlock, Operator, Statement}, - compile_error::CompileError, +use super::{ + expression_generator::ExpressionCodeGenerator, llvm::Llvm, pou_generator::PouGenerator, }; use crate::{ + ast::{flatten_expression_list, ConditionalBlock, Operator, PouType, SourceRange, Statement}, + codegen::{llvm_typesystem::cast_if_needed, LlvmTypedIndex}, + compile_error::CompileError, index::{ImplementationIndexEntry, Index}, - typesystem::DataTypeInformation, + typesystem::{ + DataTypeInformation, RANGE_CHECK_LS_FN, RANGE_CHECK_LU_FN, RANGE_CHECK_S_FN, + RANGE_CHECK_U_FN, + }, }; use inkwell::{ basic_block::BasicBlock, values::{BasicValueEnum, FunctionValue}, IntPredicate, }; +use std::ops::Range; /// the full context when generating statements inside a POU pub struct FunctionContext<'a> { @@ -40,6 +38,11 @@ pub struct StatementCodeGenerator<'a, 'b> { pub load_prefix: String, pub load_suffix: String, + + /// the block to jump to when you want to exit the loop + pub current_loop_exit: Option>, + /// the block to jump to when you want to continue the loop + pub current_loop_continue: Option>, } impl<'a, 'b> StatementCodeGenerator<'a, 'b> { @@ -61,6 +64,8 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { function_context: linking_context, load_prefix: "load_".to_string(), load_suffix: "".to_string(), + current_loop_exit: None, + current_loop_continue: None, } } @@ -83,6 +88,19 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { Ok(()) } + /// some versions of llvm will crash on two consecutive return or + /// unconditional jump statements. the solution is to insert another + /// building block before the second one, so the don't directly + /// follow each other. this is what we call a buffer block. + fn generate_buffer_block(&self) { + let builder = &self.llvm.builder; + let buffer_block = self + .llvm + .context + .insert_basic_block_after(builder.get_insert_block().unwrap(), "buffer_block"); + builder.position_at_end(buffer_block); + } + /// genertes a single statement /// /// - `statement` the statement to be generated @@ -134,6 +152,29 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { self.pou_type, Some(location.clone()), )?; + self.generate_buffer_block(); + } + Statement::ExitStatement { location, .. } => { + if let Some(exit_block) = &self.current_loop_exit { + self.llvm.builder.build_unconditional_branch(*exit_block); + self.generate_buffer_block(); + } else { + return Err(CompileError::CodeGenError { + message: "Cannot break out of loop when not inside a loop".into(), + location: location.clone(), + }); + } + } + Statement::ContinueStatement { location, .. } => { + if let Some(cont_block) = &self.current_loop_continue { + self.llvm.builder.build_unconditional_branch(*cont_block); + self.generate_buffer_block(); + } else { + return Err(CompileError::CodeGenError { + message: "Cannot continue loop when not inside a loop".into(), + location: location.clone(), + }); + } } _ => { self.create_expr_generator() @@ -241,10 +282,15 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { .llvm .context .append_basic_block(current_function, "for_body"); + let increment_block = self + .llvm + .context + .append_basic_block(current_function, "increment"); let continue_block = self .llvm .context .append_basic_block(current_function, "continue"); + //Generate an initial jump to the for condition builder.build_unconditional_branch(condition_check); @@ -264,9 +310,18 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { //Enter the for loop builder.position_at_end(for_body); - self.generate_body(body)?; + let body_generator = StatementCodeGenerator { + current_loop_exit: Some(continue_block), + current_loop_continue: Some(increment_block), + load_prefix: self.load_prefix.clone(), + load_suffix: self.load_suffix.clone(), + ..*self + }; + body_generator.generate_body(body)?; + builder.build_unconditional_branch(increment_block); //Increment + builder.position_at_end(increment_block); let expression_generator = self.create_expr_generator(); let (_, step_by_value) = by_step.as_ref().map_or_else( || { @@ -531,7 +586,14 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { //Enter the for loop builder.position_at_end(while_body); - self.generate_body(&body)?; + let body_generator = StatementCodeGenerator { + current_loop_exit: Some(continue_block), + current_loop_continue: Some(condition_check), + load_prefix: self.load_prefix.clone(), + load_suffix: self.load_suffix.clone(), + ..*self + }; + body_generator.generate_body(&body)?; //Loop back builder.build_unconditional_branch(condition_check); diff --git a/src/codegen/tests/code_gen_tests.rs b/src/codegen/tests/code_gen_tests.rs index 8a3127b9d8..3136d7af86 100644 --- a/src/codegen/tests/code_gen_tests.rs +++ b/src/codegen/tests/code_gen_tests.rs @@ -1263,13 +1263,16 @@ fn for_statement_with_steps_test() { r#"store i32 3, i32* %x, align 4 br label %condition_check -condition_check: ; preds = %for_body, %entry +condition_check: ; preds = %increment, %entry %load_x = load i32, i32* %x, align 4 %tmpVar = icmp sle i32 %load_x, 10 br i1 %tmpVar, label %for_body, label %continue for_body: ; preds = %condition_check %load_x1 = load i32, i32* %x, align 4 + br label %increment + +increment: ; preds = %for_body %tmpVar2 = add i32 %load_x, 7 store i32 %tmpVar2, i32* %x, align 4 br label %condition_check @@ -1282,6 +1285,173 @@ continue: ; preds = %condition_check assert_eq!(result, expected); } +#[test] +fn for_statement_with_continue() { + let result = codegen!( + " + PROGRAM prg + VAR + x : DINT; + END_VAR + FOR x := 3 TO 10 BY 7 DO + x := x + 1; + CONTINUE; + x := x - 1; + END_FOR + END_PROGRAM + " + ); + + let expected = generate_program_boiler_plate( + "prg", + &[("i32", "x")], + "void", + "", + "", + r#"store i32 3, i32* %x, align 4 + br label %condition_check + +condition_check: ; preds = %increment, %entry + %load_x = load i32, i32* %x, align 4 + %tmpVar = icmp sle i32 %load_x, 10 + br i1 %tmpVar, label %for_body, label %continue + +for_body: ; preds = %condition_check + %load_x1 = load i32, i32* %x, align 4 + %tmpVar2 = add i32 %load_x1, 1 + store i32 %tmpVar2, i32* %x, align 4 + br label %increment + +buffer_block: ; No predecessors! + %load_x3 = load i32, i32* %x, align 4 + %tmpVar4 = sub i32 %load_x3, 1 + store i32 %tmpVar4, i32* %x, align 4 + br label %increment + +increment: ; preds = %buffer_block, %for_body + %tmpVar5 = add i32 %load_x, 7 + store i32 %tmpVar5, i32* %x, align 4 + br label %condition_check + +continue: ; preds = %condition_check + ret void +"#, + ); + + assert_eq!(result, expected); +} + +#[test] +fn for_statement_with_exit() { + let result = codegen!( + " + PROGRAM prg + VAR + x : DINT; + END_VAR + FOR x := 3 TO 10 BY 7 DO + x := x + 2; + EXIT; + x := x + 5; + END_FOR + END_PROGRAM + " + ); + + let expected = generate_program_boiler_plate( + "prg", + &[("i32", "x")], + "void", + "", + "", + r#"store i32 3, i32* %x, align 4 + br label %condition_check + +condition_check: ; preds = %increment, %entry + %load_x = load i32, i32* %x, align 4 + %tmpVar = icmp sle i32 %load_x, 10 + br i1 %tmpVar, label %for_body, label %continue + +for_body: ; preds = %condition_check + %load_x1 = load i32, i32* %x, align 4 + %tmpVar2 = add i32 %load_x1, 2 + store i32 %tmpVar2, i32* %x, align 4 + br label %continue + +buffer_block: ; No predecessors! + %load_x3 = load i32, i32* %x, align 4 + %tmpVar4 = add i32 %load_x3, 5 + store i32 %tmpVar4, i32* %x, align 4 + br label %increment + +increment: ; preds = %buffer_block + %tmpVar5 = add i32 %load_x, 7 + store i32 %tmpVar5, i32* %x, align 4 + br label %condition_check + +continue: ; preds = %for_body, %condition_check + ret void +"#, + ); + + assert_eq!(result, expected); +} + +#[test] +fn while_loop_with_if_exit() { + let result = codegen!( + " + PROGRAM prg + VAR + x : DINT; + END_VAR + WHILE x < 20 DO + x := x + 1; + IF x >= 10 THEN + EXIT; + END_IF + END_PROGRAM + " + ); + + let expected = generate_program_boiler_plate( + "prg", + &[("i32", "x")], + "void", + "", + "", + r#"br label %condition_check + +condition_check: ; preds = %entry, %continue3 + %load_x = load i32, i32* %x, align 4 + %tmpVar = icmp slt i32 %load_x, 20 + br i1 %tmpVar, label %while_body, label %continue + +while_body: ; preds = %condition_check + %load_x1 = load i32, i32* %x, align 4 + %tmpVar2 = add i32 %load_x1, 1 + store i32 %tmpVar2, i32* %x, align 4 + %load_x4 = load i32, i32* %x, align 4 + %tmpVar5 = icmp sge i32 %load_x4, 10 + br i1 %tmpVar5, label %condition_body, label %continue3 + +continue: ; preds = %condition_body, %condition_check + ret void + +condition_body: ; preds = %while_body + br label %continue + +buffer_block: ; No predecessors! + br label %continue3 + +continue3: ; preds = %buffer_block, %while_body + br label %condition_check +"#, + ); + + assert_eq!(result, expected); +} + #[test] fn for_statement_without_steps_test() { let result = codegen!( @@ -1306,13 +1476,16 @@ fn for_statement_without_steps_test() { r#"store i32 3, i32* %x, align 4 br label %condition_check -condition_check: ; preds = %for_body, %entry +condition_check: ; preds = %increment, %entry %load_x = load i32, i32* %x, align 4 %tmpVar = icmp sle i32 %load_x, 10 br i1 %tmpVar, label %for_body, label %continue for_body: ; preds = %condition_check %load_x1 = load i32, i32* %x, align 4 + br label %increment + +increment: ; preds = %for_body %tmpVar2 = add i32 %load_x, 1 store i32 %tmpVar2, i32* %x, align 4 br label %condition_check @@ -1349,12 +1522,15 @@ fn for_statement_continue() { r#"store i32 3, i32* %x, align 4 br label %condition_check -condition_check: ; preds = %for_body, %entry +condition_check: ; preds = %increment, %entry %load_x = load i32, i32* %x, align 4 %tmpVar = icmp sle i32 %load_x, 10 br i1 %tmpVar, label %for_body, label %continue for_body: ; preds = %condition_check + br label %increment + +increment: ; preds = %for_body %tmpVar1 = add i32 %load_x, 1 store i32 %tmpVar1, i32* %x, align 4 br label %condition_check @@ -1396,7 +1572,7 @@ fn for_statement_with_references_steps_test() { store i32 %load_y, i32* %x, align 4 br label %condition_check -condition_check: ; preds = %for_body, %entry +condition_check: ; preds = %increment, %entry %load_x = load i32, i32* %x, align 4 %load_z = load i32, i32* %z, align 4 %tmpVar = icmp sle i32 %load_x, %load_z @@ -1404,6 +1580,9 @@ condition_check: ; preds = %for_body, %entry for_body: ; preds = %condition_check %load_x1 = load i32, i32* %x, align 4 + br label %increment + +increment: ; preds = %for_body %load_step = load i32, i32* %step, align 4 %tmpVar2 = add i32 %load_x, %load_step store i32 %tmpVar2, i32* %x, align 4 @@ -3827,9 +4006,11 @@ entry: condition_body: ; preds = %entry %smaller_than_ten_ret = load i16, i16* %smaller_than_ten, align 2 ret i16 %smaller_than_ten_ret + +buffer_block: ; No predecessors! br label %continue -continue: ; preds = %condition_body, %entry +continue: ; preds = %buffer_block, %entry %smaller_than_ten_ret1 = load i16, i16* %smaller_than_ten, align 2 ret i16 %smaller_than_ten_ret1 } @@ -3866,9 +4047,11 @@ entry: condition_body: ; preds = %entry ret void + +buffer_block: ; No predecessors! br label %continue -continue: ; preds = %condition_body, %entry +continue: ; preds = %buffer_block, %entry ret void } "#; diff --git a/src/codegen/tests/codegen_error_messages_tests.rs b/src/codegen/tests/codegen_error_messages_tests.rs index 6321e4fe92..730ea66449 100644 --- a/src/codegen/tests/codegen_error_messages_tests.rs +++ b/src/codegen/tests/codegen_error_messages_tests.rs @@ -21,6 +21,56 @@ fn unknown_reference_should_be_reported_with_line_number() { } } +#[test] +fn exit_not_in_loop() { + let result = codegen_wihout_unwrap!( + " + PROGRAM prg + VAR + x : INT; + END_VAR + EXIT; + END_PROGRAM + " + ); + if let Err(msg) = result { + assert_eq!( + CompileError::CodeGenError { + message: "Cannot break out of loop when not inside a loop".into(), + location: crate::ast::SourceRange::new(95..99), + }, + msg + ); + } else { + panic!("expected code-gen error but got none") + } +} + +#[test] +fn continue_not_in_loop() { + let result = codegen_wihout_unwrap!( + " + PROGRAM prg + VAR + x : INT; + END_VAR + CONTINUE; + END_PROGRAM + " + ); + if let Err(msg) = result { + assert_eq!( + CompileError::CodeGenError { + message: "Cannot continue loop when not inside a loop".into(), + location: crate::ast::SourceRange::new(95..103), + }, + msg + ); + } else { + panic!("expected code-gen error but got none") + } +} + #[test] #[ignore] fn unknown_type_should_be_reported_with_line_number() { diff --git a/src/lexer/tokens.rs b/src/lexer/tokens.rs index 21d5479f2b..c9d5a5ecf4 100644 --- a/src/lexer/tokens.rs +++ b/src/lexer/tokens.rs @@ -224,6 +224,12 @@ pub enum Token { #[token("RETURN")] KeywordReturn, + #[token("EXIT")] + KeywordExit, + + #[token("CONTINUE")] + KeywordContinue, + #[token("ARRAY")] KeywordArray, diff --git a/src/parser/control_parser.rs b/src/parser/control_parser.rs index 35b9afa3bc..a290e47993 100644 --- a/src/parser/control_parser.rs +++ b/src/parser/control_parser.rs @@ -18,6 +18,8 @@ pub fn parse_control_statement(lexer: &mut ParseSession) -> Statement { KeywordRepeat => parse_repeat_statement(lexer), KeywordCase => parse_case_statement(lexer), KeywordReturn => parse_return_statement(lexer), + KeywordContinue => parse_continue_statement(lexer), + KeywordExit => parse_exit_statement(lexer), _ => parse_statement(lexer), } } @@ -31,6 +33,24 @@ fn parse_return_statement(lexer: &mut ParseSession) -> Statement { } } +fn parse_exit_statement(lexer: &mut ParseSession) -> Statement { + let location = lexer.location(); + lexer.advance(); + Statement::ExitStatement { + location, + id: lexer.next_id(), + } +} + +fn parse_continue_statement(lexer: &mut ParseSession) -> Statement { + let location = lexer.location(); + lexer.advance(); + Statement::ContinueStatement { + location, + id: lexer.next_id(), + } +} + fn parse_if_statement(lexer: &mut ParseSession) -> Statement { let start = lexer.range().start; lexer.advance(); //If diff --git a/src/parser/tests/control_parser_tests.rs b/src/parser/tests/control_parser_tests.rs index 4a315de41e..529b58ab82 100644 --- a/src/parser/tests/control_parser_tests.rs +++ b/src/parser/tests/control_parser_tests.rs @@ -43,6 +43,26 @@ fn test_return_statement() { assert_eq!(format!("{:?}", stmt), "ReturnStatement"); } +#[test] +fn test_continue_statement() { + let lexer = super::lex("PROGRAM ret CONTINUE END_PROGRAM"); + let result = parse(lexer).0; + let prg = &result.implementations[0]; + let stmt = &prg.statements[0]; + + assert_eq!(format!("{:?}", stmt), "ContinueStatement"); +} + +#[test] +fn test_exit_statement() { + let lexer = super::lex("PROGRAM ret EXIT END_PROGRAM"); + let result = parse(lexer).0; + let prg = &result.implementations[0]; + let stmt = &prg.statements[0]; + + assert_eq!(format!("{:?}", stmt), "ExitStatement"); +} + #[test] fn if_else_statement_with_expressions() { let lexer = super::lex( diff --git a/tests/correctness/control_flow.rs b/tests/correctness/control_flow.rs index d162cd7a13..5099c54fe2 100644 --- a/tests/correctness/control_flow.rs +++ b/tests/correctness/control_flow.rs @@ -111,11 +111,7 @@ fn early_return_test() { let function = r#" FUNCTION main : DINT main := 100; - // Windows does not like multiple returns in a - // row. That's why we wrap it inside a dummy IF. - IF TRUE THEN - RETURN - END_IF; + RETURN main := 200; END_FUNCTION "#; @@ -124,6 +120,76 @@ fn early_return_test() { assert_eq!(res, 100); } +#[test] +fn for_continue_test() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + ret: i32, + } + + let function = r#" + FUNCTION main : DINT + FOR main := 1 TO 10 BY 1 DO + main := 10; + CONTINUE; + main := 200; + END_FOR + END_FUNCTION + "#; + + let (res, _) = compile_and_run(function.to_string(), &mut MainType { ret: 0 }); + assert_eq!(res, 11); +} + +#[test] +fn while_continue_test() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + ret: i32, + } + + let function = r#" + FUNCTION main : DINT + main := 1; + WHILE main < 10 DO + main := main + 1; + CONTINUE; + main := 200; + END_WHILE + END_FUNCTION + "#; + + let (res, _) = compile_and_run(function.to_string(), &mut MainType { ret: 0 }); + assert_eq!(res, 10); +} + +#[test] +fn loop_exit_test() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + ret: i32, + } + + let function = r#" + FUNCTION main : DINT + FOR main := 100 TO 1000 BY 7 DO + EXIT; + main := 200; + END_FOR + WHILE main > 50 DO + EXIT; + main := 200; + END_WHILE + END_FUNCTION + "#; + + let (res, _) = compile_and_run(function.to_string(), &mut MainType { ret: 0 }); + assert_eq!(res, 100); +} + #[test] fn for_loop_and_increment_10_times() { #[allow(dead_code)] @@ -201,6 +267,94 @@ fn while_loop_no_entry() { assert_eq!(res, 5); } +#[test] +fn exit_in_if_in_while_loop() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + i: i16, + ret: i32, + } + + let function = r#" + FUNCTION main : DINT + VAR + i : INT; + END_VAR + i := 0; + WHILE i < 20 DO + i := i+1; + IF i >= 10 THEN + EXIT; + END_IF + END_WHILE + main := i; + END_FUNCTION + "#; + + let (res, _) = compile_and_run(function.to_string(), &mut MainType { i: 0, ret: 0 }); + assert_eq!(res, 10); +} + +#[test] +fn exit_in_for_loop_in_while_loop() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + i: i16, + ret: i32, + } + + let function = r#" + FUNCTION main : DINT + VAR + i : INT; + END_VAR + main := 0; + WHILE main < 20 DO + main := main+1; + FOR i := 0 TO 10 BY 1 DO + EXIT; + END_FOR + END_WHILE + main := i + main; + END_FUNCTION + "#; + + let (res, _) = compile_and_run(function.to_string(), &mut MainType { i: 0, ret: 0 }); + assert_eq!(res, 20); +} + +#[test] +fn continue_in_for_loop_in_while_loop() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + i: i16, + ret: i32, + } + + let function = r#" + FUNCTION main : DINT + VAR + i : INT; + END_VAR + main := 0; + WHILE main < 20 DO + main := main+1; + FOR i := 0 TO 10 BY 1 DO + CONTINUE; + main := 200; + END_FOR + END_WHILE + main := i + main; + END_FUNCTION + "#; + + let (res, _) = compile_and_run(function.to_string(), &mut MainType { i: 0, ret: 0 }); + assert_eq!(res, 31); +} + #[test] fn repeat_loop_no_entry() { #[allow(dead_code)]