Skip to content

Commit ef09b87

Browse files
authored
fix: for loops no longer execute once when condition is already met (#1248)
* fix: for loop condition This PR fixes for loops executing once when the predicate already should not be met for decrementing loops. I have also re-implemented the codegen logic for for-loops, resulting in fewer predecessors and hopefully more readable IR. Resolves #1207
1 parent c7f3d82 commit ef09b87

13 files changed

+542
-660
lines changed

src/codegen/generators/statement_generator.rs

Lines changed: 96 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
// Copyright (c) 2020 Ghaith Hachem and Mathias Rieder
22
use super::{
3-
expression_generator::{to_i1, ExpressionCodeGenerator},
3+
expression_generator::{to_i1, ExpressionCodeGenerator, ExpressionValue},
44
llvm::Llvm,
55
};
66
use crate::{
7-
codegen::debug::Debug,
8-
codegen::{debug::DebugBuilderEnum, LlvmTypedIndex},
7+
codegen::{
8+
debug::{Debug, DebugBuilderEnum},
9+
llvm_typesystem::cast_if_needed,
10+
LlvmTypedIndex,
11+
},
912
index::{ImplementationIndexEntry, Index},
1013
resolver::{AnnotationMap, AstAnnotations, StatementAnnotation},
11-
typesystem::DataTypeInformation,
14+
typesystem::{get_bigger_type, DataTypeInformation, DINT_TYPE},
1215
};
1316
use inkwell::{
1417
basic_block::BasicBlock,
1518
builder::Builder,
1619
context::Context,
17-
values::{BasicValueEnum, FunctionValue, PointerValue},
20+
values::{FunctionValue, PointerValue},
1821
};
1922
use plc_ast::{
2023
ast::{
@@ -325,117 +328,107 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> {
325328
body: &[AstNode],
326329
) -> Result<(), Diagnostic> {
327330
let (builder, current_function, context) = self.get_llvm_deps();
328-
self.generate_assignment_statement(counter, start)?;
329-
let condition_check = context.append_basic_block(current_function, "condition_check");
330-
let for_body = context.append_basic_block(current_function, "for_body");
331-
let increment_block = context.append_basic_block(current_function, "increment");
332-
let continue_block = context.append_basic_block(current_function, "continue");
333-
334-
//Generate an initial jump to the for condition
335-
builder.build_unconditional_branch(condition_check);
336-
337-
//Check loop condition
338-
builder.position_at_end(condition_check);
339331
let exp_gen = self.create_expr_generator();
340-
let counter_statement = exp_gen.generate_expression(counter)?;
341332

342-
//. / and_2 \
343-
//. / and 1 \
344-
//. (counter_end_le && counter_start_ge) || (counter_end_ge && counter_start_le)
345-
let or_eval = self.generate_compare_expression(counter, end, start, &exp_gen)?;
333+
let end_ty = self.annotations.get_type_or_void(end, self.index);
334+
let counter_ty = self.annotations.get_type_or_void(counter, self.index);
335+
let cast_target_ty = get_bigger_type(self.index.get_type_or_panic(DINT_TYPE), counter_ty, self.index);
336+
let cast_target_llty = self.llvm_index.find_associated_type(cast_target_ty.get_name()).unwrap();
337+
338+
let step_ty = by_step.as_ref().map(|it| {
339+
self.register_debug_location(it);
340+
self.annotations.get_type_or_void(it, self.index)
341+
});
342+
343+
let eval_step = || {
344+
step_ty.map_or_else(
345+
|| self.llvm.create_const_numeric(&cast_target_llty, "1", SourceLocation::undefined()),
346+
|step_ty| {
347+
let step = exp_gen.generate_expression(by_step.as_ref().unwrap())?;
348+
Ok(cast_if_needed!(exp_gen, cast_target_ty, step_ty, step, None))
349+
},
350+
)
351+
};
346352

347-
builder.build_conditional_branch(to_i1(or_eval.into_int_value(), builder), for_body, continue_block);
353+
let predicate_incrementing = context.append_basic_block(current_function, "predicate_sle");
354+
let predicate_decrementing = context.append_basic_block(current_function, "predicate_sge");
355+
let loop_body = context.append_basic_block(current_function, "loop");
356+
let increment = context.append_basic_block(current_function, "increment");
357+
let afterloop = context.append_basic_block(current_function, "continue");
348358

349-
//Enter the for loop
350-
builder.position_at_end(for_body);
351-
let body_generator = StatementCodeGenerator {
352-
current_loop_exit: Some(continue_block),
353-
current_loop_continue: Some(increment_block),
359+
self.generate_assignment_statement(counter, start)?;
360+
let counter = exp_gen.generate_lvalue(counter)?;
361+
362+
// generate loop predicate selector. since `STEP` can be a reference, this needs to be a runtime eval
363+
// XXX(mhasel): IR could possibly be improved by generating phi instructions.
364+
// Candidate for frontend optimization for builds without optimization when `STEP`
365+
// is a compile-time constant
366+
let is_incrementing = builder.build_int_compare(
367+
inkwell::IntPredicate::SGT,
368+
eval_step()?.into_int_value(),
369+
self.llvm
370+
.create_const_numeric(&cast_target_llty, "0", SourceLocation::undefined())?
371+
.into_int_value(),
372+
"is_incrementing",
373+
);
374+
builder.build_conditional_branch(is_incrementing, predicate_incrementing, predicate_decrementing);
375+
// generate predicates for incrementing and decrementing counters
376+
let generate_predicate = |predicate| {
377+
builder.position_at_end(match predicate {
378+
inkwell::IntPredicate::SLE => predicate_incrementing,
379+
inkwell::IntPredicate::SGE => predicate_decrementing,
380+
_ => unreachable!(),
381+
});
382+
383+
let end = exp_gen.generate_expression_value(end).unwrap();
384+
let end_value = match end {
385+
ExpressionValue::LValue(ptr) => builder.build_load(ptr, ""),
386+
ExpressionValue::RValue(val) => val,
387+
};
388+
let counter_value = builder.build_load(counter, "");
389+
let cmp = builder.build_int_compare(
390+
predicate,
391+
cast_if_needed!(exp_gen, cast_target_ty, counter_ty, counter_value, None).into_int_value(),
392+
cast_if_needed!(exp_gen, cast_target_ty, end_ty, end_value, None).into_int_value(),
393+
"condition",
394+
);
395+
builder.build_conditional_branch(cmp, loop_body, afterloop);
396+
};
397+
generate_predicate(inkwell::IntPredicate::SLE);
398+
generate_predicate(inkwell::IntPredicate::SGE);
399+
400+
// generate loop body
401+
builder.position_at_end(loop_body);
402+
let body_builder = StatementCodeGenerator {
403+
current_loop_continue: Some(increment),
404+
current_loop_exit: Some(afterloop),
354405
load_prefix: self.load_prefix.clone(),
355406
load_suffix: self.load_suffix.clone(),
356407
..*self
357408
};
358-
body_generator.generate_body(body)?;
359-
builder.build_unconditional_branch(increment_block);
360-
361-
//Increment
362-
builder.position_at_end(increment_block);
363-
let expression_generator = self.create_expr_generator();
364-
let step_by_value = by_step.as_ref().map_or_else(
365-
|| {
366-
self.llvm.create_const_numeric(
367-
&counter_statement.get_type(),
368-
"1",
369-
SourceLocation::undefined(),
370-
)
371-
},
372-
|step| {
373-
self.register_debug_location(step);
374-
expression_generator.generate_expression(step)
375-
},
376-
)?;
377-
378-
let next = builder.build_int_add(
379-
counter_statement.into_int_value(),
380-
step_by_value.into_int_value(),
381-
"tmpVar",
409+
body_builder.generate_body(body)?;
410+
411+
// increment counter
412+
builder.build_unconditional_branch(increment);
413+
builder.position_at_end(increment);
414+
let counter_value = builder.build_load(counter, "");
415+
let inc = inkwell::values::BasicValue::as_basic_value_enum(&builder.build_int_add(
416+
eval_step()?.into_int_value(),
417+
cast_if_needed!(exp_gen, cast_target_ty, counter_ty, counter_value, None).into_int_value(),
418+
"next",
419+
));
420+
builder.build_store(
421+
counter,
422+
cast_if_needed!(exp_gen, counter_ty, cast_target_ty, inc, None).into_int_value(),
382423
);
383424

384-
let ptr = expression_generator.generate_lvalue(counter)?;
385-
builder.build_store(ptr, next);
386-
387-
//Loop back
388-
builder.build_unconditional_branch(condition_check);
389-
390-
//Continue
391-
builder.position_at_end(continue_block);
392-
425+
// check condition
426+
builder.build_conditional_branch(is_incrementing, predicate_incrementing, predicate_decrementing);
427+
// continue
428+
builder.position_at_end(afterloop);
393429
Ok(())
394430
}
395431

396-
fn generate_compare_expression(
397-
&'a self,
398-
counter: &AstNode,
399-
end: &AstNode,
400-
start: &AstNode,
401-
exp_gen: &'a ExpressionCodeGenerator,
402-
) -> Result<BasicValueEnum<'a>, Diagnostic> {
403-
let bool_id = self.annotations.get_bool_id();
404-
let counter_end_ge = AstFactory::create_binary_expression(
405-
counter.clone(),
406-
Operator::GreaterOrEqual,
407-
end.clone(),
408-
bool_id,
409-
);
410-
let counter_start_ge = AstFactory::create_binary_expression(
411-
counter.clone(),
412-
Operator::GreaterOrEqual,
413-
start.clone(),
414-
bool_id,
415-
);
416-
let counter_end_le = AstFactory::create_binary_expression(
417-
counter.clone(),
418-
Operator::LessOrEqual,
419-
end.clone(),
420-
bool_id,
421-
);
422-
let counter_start_le = AstFactory::create_binary_expression(
423-
counter.clone(),
424-
Operator::LessOrEqual,
425-
start.clone(),
426-
bool_id,
427-
);
428-
let and_1 =
429-
AstFactory::create_binary_expression(counter_end_le, Operator::And, counter_start_ge, bool_id);
430-
let and_2 =
431-
AstFactory::create_binary_expression(counter_end_ge, Operator::And, counter_start_le, bool_id);
432-
let or = AstFactory::create_binary_expression(and_1, Operator::Or, and_2, bool_id);
433-
434-
self.register_debug_location(&or);
435-
let or_eval = exp_gen.generate_expression(&or)?;
436-
Ok(or_eval)
437-
}
438-
439432
/// genertes a case statement
440433
///
441434
/// CASE selector OF

src/codegen/tests/code_gen_tests.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,145 @@ fn for_statement_with_references_steps_test() {
11551155
insta::assert_snapshot!(result);
11561156
}
11571157

1158+
#[test]
1159+
fn for_statement_with_binary_expressions() {
1160+
let result = codegen(
1161+
"
1162+
PROGRAM prg
1163+
VAR
1164+
step: DINT;
1165+
x : DINT;
1166+
y : DINT;
1167+
z : DINT;
1168+
END_VAR
1169+
FOR x := y + 1 TO z - 2 BY step * 3 DO
1170+
x;
1171+
END_FOR
1172+
END_PROGRAM
1173+
",
1174+
);
1175+
1176+
insta::assert_snapshot!(result, @r###"
1177+
; ModuleID = 'main'
1178+
source_filename = "main"
1179+
1180+
%prg = type { i32, i32, i32, i32 }
1181+
1182+
@prg_instance = global %prg zeroinitializer, section "var-$RUSTY$prg_instance:r4i32i32i32i32"
1183+
1184+
define void @prg(%prg* %0) section "fn-$RUSTY$prg:v" {
1185+
entry:
1186+
%step = getelementptr inbounds %prg, %prg* %0, i32 0, i32 0
1187+
%x = getelementptr inbounds %prg, %prg* %0, i32 0, i32 1
1188+
%y = getelementptr inbounds %prg, %prg* %0, i32 0, i32 2
1189+
%z = getelementptr inbounds %prg, %prg* %0, i32 0, i32 3
1190+
%load_y = load i32, i32* %y, align 4
1191+
%tmpVar = add i32 %load_y, 1
1192+
store i32 %tmpVar, i32* %x, align 4
1193+
%load_step = load i32, i32* %step, align 4
1194+
%tmpVar1 = mul i32 %load_step, 3
1195+
%is_incrementing = icmp sgt i32 %tmpVar1, 0
1196+
br i1 %is_incrementing, label %predicate_sle, label %predicate_sge
1197+
1198+
predicate_sle: ; preds = %increment, %entry
1199+
%load_z = load i32, i32* %z, align 4
1200+
%tmpVar2 = sub i32 %load_z, 2
1201+
%1 = load i32, i32* %x, align 4
1202+
%condition = icmp sle i32 %1, %tmpVar2
1203+
br i1 %condition, label %loop, label %continue
1204+
1205+
predicate_sge: ; preds = %increment, %entry
1206+
%load_z3 = load i32, i32* %z, align 4
1207+
%tmpVar4 = sub i32 %load_z3, 2
1208+
%2 = load i32, i32* %x, align 4
1209+
%condition5 = icmp sge i32 %2, %tmpVar4
1210+
br i1 %condition5, label %loop, label %continue
1211+
1212+
loop: ; preds = %predicate_sge, %predicate_sle
1213+
%load_x = load i32, i32* %x, align 4
1214+
br label %increment
1215+
1216+
increment: ; preds = %loop
1217+
%3 = load i32, i32* %x, align 4
1218+
%load_step6 = load i32, i32* %step, align 4
1219+
%tmpVar7 = mul i32 %load_step6, 3
1220+
%next = add i32 %tmpVar7, %3
1221+
store i32 %next, i32* %x, align 4
1222+
br i1 %is_incrementing, label %predicate_sle, label %predicate_sge
1223+
1224+
continue: ; preds = %predicate_sge, %predicate_sle
1225+
ret void
1226+
}
1227+
"###);
1228+
}
1229+
1230+
#[test]
1231+
fn for_statement_type_casting() {
1232+
let result = codegen(
1233+
"FUNCTION main
1234+
VAR
1235+
a: USINT;
1236+
b: INT := 1;
1237+
END_VAR
1238+
FOR a := 0 TO 10 BY b DO
1239+
b := b * 3;
1240+
END_FOR
1241+
END_FUNCTION",
1242+
);
1243+
insta::assert_snapshot!(result, @r###"
1244+
; ModuleID = 'main'
1245+
source_filename = "main"
1246+
1247+
define void @main() section "fn-$RUSTY$main:v" {
1248+
entry:
1249+
%a = alloca i8, align 1
1250+
%b = alloca i16, align 2
1251+
store i8 0, i8* %a, align 1
1252+
store i16 1, i16* %b, align 2
1253+
store i8 0, i8* %a, align 1
1254+
%load_b = load i16, i16* %b, align 2
1255+
%0 = trunc i16 %load_b to i8
1256+
%1 = sext i8 %0 to i32
1257+
%is_incrementing = icmp sgt i32 %1, 0
1258+
br i1 %is_incrementing, label %predicate_sle, label %predicate_sge
1259+
1260+
predicate_sle: ; preds = %increment, %entry
1261+
%2 = load i8, i8* %a, align 1
1262+
%3 = zext i8 %2 to i32
1263+
%condition = icmp sle i32 %3, 10
1264+
br i1 %condition, label %loop, label %continue
1265+
1266+
predicate_sge: ; preds = %increment, %entry
1267+
%4 = load i8, i8* %a, align 1
1268+
%5 = zext i8 %4 to i32
1269+
%condition1 = icmp sge i32 %5, 10
1270+
br i1 %condition1, label %loop, label %continue
1271+
1272+
loop: ; preds = %predicate_sge, %predicate_sle
1273+
%load_b2 = load i16, i16* %b, align 2
1274+
%6 = sext i16 %load_b2 to i32
1275+
%tmpVar = mul i32 %6, 3
1276+
%7 = trunc i32 %tmpVar to i16
1277+
store i16 %7, i16* %b, align 2
1278+
br label %increment
1279+
1280+
increment: ; preds = %loop
1281+
%8 = load i8, i8* %a, align 1
1282+
%load_b3 = load i16, i16* %b, align 2
1283+
%9 = trunc i16 %load_b3 to i8
1284+
%10 = sext i8 %9 to i32
1285+
%11 = zext i8 %8 to i32
1286+
%next = add i32 %10, %11
1287+
%12 = trunc i32 %next to i8
1288+
store i8 %12, i8* %a, align 1
1289+
br i1 %is_incrementing, label %predicate_sle, label %predicate_sge
1290+
1291+
continue: ; preds = %predicate_sge, %predicate_sle
1292+
ret void
1293+
}
1294+
"###);
1295+
}
1296+
11581297
#[test]
11591298
fn while_statement() {
11601299
let result = codegen(

0 commit comments

Comments
 (0)