Skip to content

Commit 85e7514

Browse files
committed
constant propagation support
when generating an expression we check if this may refer to a constant expression and rather generate it's initial value instead of a reference to the global variable. fixes #391 and #291
1 parent 679ffe8 commit 85e7514

10 files changed

+282
-49
lines changed

src/codegen/generators/expression_generator.rs

+47-2
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,19 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
172172
"{}{}{}",
173173
self.temp_variable_prefix, name, self.temp_variable_suffix
174174
);
175-
let l_value = self.generate_element_pointer(expression)?;
176-
Ok(self.llvm.load_pointer(&l_value, load_name.as_str()))
175+
if let Some(StatementAnnotation::Variable {
176+
qualified_name,
177+
constant: true,
178+
..
179+
}) = self.annotations.get(expression)
180+
{
181+
// constant propagation
182+
self.generate_constant_expression(qualified_name, expression)
183+
} else {
184+
// general reference generation
185+
let l_value = self.generate_element_pointer(expression)?;
186+
Ok(self.llvm.load_pointer(&l_value, load_name.as_str()))
187+
}
177188
}
178189
AstStatement::QualifiedReference { elements, .. } => {
179190
//If direct access, don't load pointers
@@ -212,6 +223,40 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
212223
}
213224
}
214225

226+
/// Propagate the constant value of the constant reference to `qualified_name`.
227+
/// - `qualified _name` the qualified name of the referenced constant variable we want to propagate
228+
/// - `expression` the original expression
229+
fn generate_constant_expression(
230+
&self,
231+
qualified_name: &str,
232+
expression: &AstStatement,
233+
) -> Result<BasicValueEnum<'ink>, Diagnostic> {
234+
let const_expression = self
235+
.index
236+
// try to find a constant variable
237+
.find_variable(None, &[qualified_name])
238+
// or else try to find an enum element
239+
.or_else(|| self.index.find_qualified_enum_element(qualified_name))
240+
// if this is no constant we have a problem
241+
.filter(|v| v.is_constant())
242+
.and_then(|v| v.initial_value)
243+
// fetch the constant's initial value fron the const-expressions arena
244+
.and_then(|constant_variable| {
245+
self.index
246+
.get_const_expressions()
247+
.get_resolved_constant_statement(&constant_variable)
248+
})
249+
.ok_or_else(|| {
250+
Diagnostic::codegen_error(
251+
format!("Cannot propagate constant value for '{:}'", qualified_name).as_str(),
252+
expression.get_location(),
253+
)
254+
})?;
255+
256+
// generate the resulting constant-expression (which should be a Value, no ptr-reference)
257+
self.generate_expression(const_expression)
258+
}
259+
215260
/// generates a binary expression (e.g. a + b, x AND y, etc.) and returns the resulting `BasicValueEnum`
216261
/// - `left` the AstStatement left of the operator
217262
/// - `right` the AstStatement right of the operator

src/codegen/tests/code_gen_tests.rs

+78-2
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,84 @@ fn case_with_ranges_statement() {
12601260
insta::assert_snapshot!(result);
12611261
}
12621262

1263+
#[test]
1264+
fn case_with_constant_expressions_in_case_selectors() {
1265+
let result = codegen(
1266+
r##"
1267+
VAR_GLOBAL CONSTANT
1268+
FORWARD : DINT := 7;
1269+
UP : DINT := FORWARD + 1;
1270+
DOWN : DINT := FORWARD + UP;
1271+
END_VAR
1272+
1273+
FUNCTION drive : DINT
1274+
VAR
1275+
input : DINT;
1276+
horiz, depth : DINT;
1277+
END_VAR
1278+
1279+
CASE input OF
1280+
FORWARD :
1281+
horiz := horiz + 1;
1282+
FORWARD*2:
1283+
horiz := horiz + 2;
1284+
UP :
1285+
depth := depth - 1;
1286+
DOWN :
1287+
depth := depth + 1;
1288+
1289+
END_CASE
1290+
1291+
END_FUNCTION
1292+
"##,
1293+
);
1294+
1295+
// WHEN we compile, we want to see propagated constant in the switch statement
1296+
// -> so no references to variables, but int-values (7, 14, 8 and 15)
1297+
insta::assert_snapshot!(result);
1298+
}
1299+
1300+
#[test]
1301+
fn case_with_enum_expressions_in_case_selectors() {
1302+
let result = codegen(
1303+
r##"
1304+
VAR_GLOBAL CONSTANT
1305+
BASE : DINT := 7;
1306+
END_VAR
1307+
1308+
TYPE Direction: (
1309+
FORWARD := BASE,
1310+
UP,
1311+
DOWN := BASE * 2);
1312+
END_TYPE
1313+
1314+
FUNCTION drive : DINT
1315+
VAR
1316+
input : DINT;
1317+
horiz, depth : DINT;
1318+
END_VAR
1319+
1320+
CASE input OF
1321+
FORWARD :
1322+
horiz := horiz + 1;
1323+
FORWARD*2:
1324+
horiz := horiz + 2;
1325+
UP :
1326+
depth := depth - 1;
1327+
DOWN :
1328+
depth := depth + 1;
1329+
1330+
END_CASE
1331+
1332+
END_FUNCTION
1333+
"##,
1334+
);
1335+
1336+
// WHEN we compile, we want to see propagated constant in the switch statement
1337+
// -> so no references to variables, but int-values (7, 14, 8 and 15)
1338+
insta::assert_snapshot!(result);
1339+
}
1340+
12631341
#[test]
12641342
fn function_called_in_program() {
12651343
let result = codegen(
@@ -2597,8 +2675,6 @@ fn using_global_consts_in_expressions() {
25972675
);
25982676
//WHEN we compile
25992677
// we expect the constants to be inlined
2600-
//TODO inline constant values into body-expression
2601-
// https://github.com/ghaith/rusty/issues/291
26022678
insta::assert_snapshot!(result);
26032679
}
26042680

src/codegen/tests/codegen_error_messages_tests.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2020 Ghaith Hachem and Mathias Rieder
2-
use crate::{ast::SourceRange, diagnostics::Diagnostic, test_utils::tests::codegen_without_unwrap};
2+
use crate::{diagnostics::Diagnostic, test_utils::tests::codegen_without_unwrap};
33
use pretty_assertions::assert_eq;
44

55
#[test]
@@ -255,10 +255,7 @@ fn constants_without_initialization() {
255255

256256
if let Err(msg) = result {
257257
assert_eq!(
258-
Diagnostic::codegen_error(
259-
"Cannot generate literal initializer for 'b': Value can not be derived",
260-
SourceRange::undefined()
261-
),
258+
Diagnostic::codegen_error("Cannot propagate constant value for 'a'", (73..74).into()),
262259
msg
263260
)
264261
} else {
@@ -279,10 +276,7 @@ fn recursive_initial_constant_values() {
279276

280277
if let Err(msg) = result {
281278
assert_eq!(
282-
Diagnostic::codegen_error(
283-
"Cannot generate literal initializer for 'a': Value can not be derived",
284-
SourceRange::undefined()
285-
),
279+
Diagnostic::codegen_error("Cannot propagate constant value for 'b'", (52..53).into()),
286280
msg
287281
)
288282
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
source: src/codegen/tests/code_gen_tests.rs
3+
expression: result
4+
---
5+
; ModuleID = 'main'
6+
source_filename = "main"
7+
8+
@FORWARD = unnamed_addr constant i32 7
9+
@UP = unnamed_addr constant i32 8
10+
@DOWN = unnamed_addr constant i32 15
11+
12+
define i32 @drive() {
13+
entry:
14+
%input = alloca i32, align 4
15+
%horiz = alloca i32, align 4
16+
%depth = alloca i32, align 4
17+
%drive = alloca i32, align 4
18+
store i32 0, i32* %input, align 4
19+
store i32 0, i32* %horiz, align 4
20+
store i32 0, i32* %depth, align 4
21+
store i32 0, i32* %drive, align 4
22+
%load_input = load i32, i32* %input, align 4
23+
switch i32 %load_input, label %else [
24+
i32 7, label %case
25+
i32 14, label %case1
26+
i32 8, label %case4
27+
i32 15, label %case6
28+
]
29+
30+
case: ; preds = %entry
31+
%load_horiz = load i32, i32* %horiz, align 4
32+
%tmpVar = add i32 %load_horiz, 1
33+
store i32 %tmpVar, i32* %horiz, align 4
34+
br label %continue
35+
36+
case1: ; preds = %entry
37+
%load_horiz2 = load i32, i32* %horiz, align 4
38+
%tmpVar3 = add i32 %load_horiz2, 2
39+
store i32 %tmpVar3, i32* %horiz, align 4
40+
br label %continue
41+
42+
case4: ; preds = %entry
43+
%load_depth = load i32, i32* %depth, align 4
44+
%tmpVar5 = sub i32 %load_depth, 1
45+
store i32 %tmpVar5, i32* %depth, align 4
46+
br label %continue
47+
48+
case6: ; preds = %entry
49+
%load_depth7 = load i32, i32* %depth, align 4
50+
%tmpVar8 = add i32 %load_depth7, 1
51+
store i32 %tmpVar8, i32* %depth, align 4
52+
br label %continue
53+
54+
else: ; preds = %entry
55+
br label %continue
56+
57+
continue: ; preds = %else, %case6, %case4, %case1, %case
58+
%drive_ret = load i32, i32* %drive, align 4
59+
ret i32 %drive_ret
60+
}
61+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
source: src/codegen/tests/code_gen_tests.rs
3+
expression: result
4+
---
5+
; ModuleID = 'main'
6+
source_filename = "main"
7+
8+
@BASE = unnamed_addr constant i32 7
9+
@FORWARD = unnamed_addr constant i32 7
10+
@UP = unnamed_addr constant i32 8
11+
@DOWN = unnamed_addr constant i32 14
12+
13+
define i32 @drive() {
14+
entry:
15+
%input = alloca i32, align 4
16+
%horiz = alloca i32, align 4
17+
%depth = alloca i32, align 4
18+
%drive = alloca i32, align 4
19+
store i32 0, i32* %input, align 4
20+
store i32 0, i32* %horiz, align 4
21+
store i32 0, i32* %depth, align 4
22+
store i32 0, i32* %drive, align 4
23+
%load_input = load i32, i32* %input, align 4
24+
switch i32 %load_input, label %else [
25+
i32 7, label %case
26+
i32 14, label %case1
27+
i32 8, label %case4
28+
i32 14, label %case6
29+
]
30+
31+
case: ; preds = %entry
32+
%load_horiz = load i32, i32* %horiz, align 4
33+
%tmpVar = add i32 %load_horiz, 1
34+
store i32 %tmpVar, i32* %horiz, align 4
35+
br label %continue
36+
37+
case1: ; preds = %entry
38+
%load_horiz2 = load i32, i32* %horiz, align 4
39+
%tmpVar3 = add i32 %load_horiz2, 2
40+
store i32 %tmpVar3, i32* %horiz, align 4
41+
br label %continue
42+
43+
case4: ; preds = %entry
44+
%load_depth = load i32, i32* %depth, align 4
45+
%tmpVar5 = sub i32 %load_depth, 1
46+
store i32 %tmpVar5, i32* %depth, align 4
47+
br label %continue
48+
49+
case6: ; preds = %entry
50+
%load_depth7 = load i32, i32* %depth, align 4
51+
%tmpVar8 = add i32 %load_depth7, 1
52+
store i32 %tmpVar8, i32* %depth, align 4
53+
br label %continue
54+
55+
else: ; preds = %entry
56+
br label %continue
57+
58+
continue: ; preds = %else, %case6, %case4, %case1, %case
59+
%drive_ret = load i32, i32* %drive, align 4
60+
ret i32 %drive_ret
61+
}
62+
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
---
22
source: src/codegen/tests/code_gen_tests.rs
3-
assertion_line: 2113
43
expression: result
5-
64
---
75
; ModuleID = 'main'
86
source_filename = "main"
@@ -17,12 +15,9 @@ source_filename = "main"
1715
define void @main(%main_interface* %0) {
1816
entry:
1917
%color = getelementptr inbounds %main_interface, %main_interface* %0, i32 0, i32 0
20-
%load_red = load i32, i32* @red, align 4
21-
store i32 %load_red, i32* %color, align 4
22-
%load_yellow = load i32, i32* @yellow, align 4
23-
store i32 %load_yellow, i32* %color, align 4
24-
%load_green = load i32, i32* @green, align 4
25-
store i32 %load_green, i32* %color, align 4
18+
store i32 0, i32* %color, align 4
19+
store i32 1, i32* %color, align 4
20+
store i32 2, i32* %color, align 4
2621
ret void
2722
}
2823

Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
---
22
source: src/codegen/tests/code_gen_tests.rs
3-
assertion_line: 2511
43
expression: result
5-
64
---
75
; ModuleID = 'main'
86
source_filename = "main"
@@ -17,15 +15,7 @@ source_filename = "main"
1715
define void @prg(%prg_interface* %0) {
1816
entry:
1917
%z = getelementptr inbounds %prg_interface, %prg_interface* %0, i32 0, i32 0
20-
%load_cA = load i16, i16* @cA, align 2
21-
%1 = sext i16 %load_cA to i32
22-
%load_cB = load i16, i16* @cB, align 2
23-
%2 = sext i16 %load_cB to i32
24-
%tmpVar = add i32 %1, %2
25-
%load_cC = load i16, i16* @cC, align 2
26-
%3 = sext i16 %load_cC to i32
27-
%tmpVar1 = add i32 %tmpVar, %3
28-
store i32 %tmpVar1, i32* %z, align 4
18+
store i32 6, i32* %z, align 4
2919
ret void
3020
}
3121

src/index.rs

+7
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,13 @@ impl Index {
964964
.get(&format!("{}.{}", enum_name, element_name).to_lowercase())
965965
}
966966

967+
/// returns the index entry of the enum-element denoted by the given fully `qualified_name` (e.g. "Color.RED")
968+
/// or None if the requested Enum-Type or -Element does not exist
969+
pub fn find_qualified_enum_element(&self, qualified_name: &str) -> Option<&VariableIndexEntry> {
970+
self.enum_qualified_variables
971+
.get(&qualified_name.to_lowercase())
972+
}
973+
967974
/// returns all member variables of the given container (e.g. FUNCTION, PROGRAM, STRUCT, etc.)
968975
pub fn get_container_members(&self, container_name: &str) -> Vec<&VariableIndexEntry> {
969976
self.member_variables

0 commit comments

Comments
 (0)