diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index 7aa3a0a546..e7353608c2 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -851,6 +851,14 @@ impl AstNode { matches!(self.stmt, AstStatement::EmptyStatement(..)) } + pub fn is_assignment(&self) -> bool { + matches!(self.stmt, AstStatement::Assignment(..)) + } + + pub fn is_output_assignment(&self) -> bool { + matches!(self.stmt, AstStatement::OutputAssignment(..)) + } + pub fn is_reference(&self) -> bool { matches!(self.stmt, AstStatement::ReferenceExpr(..)) } diff --git a/libs/stdlib/tests/counters_tests.rs b/libs/stdlib/tests/counters_tests.rs index d9793768b7..431f6ac253 100644 --- a/libs/stdlib/tests/counters_tests.rs +++ b/libs/stdlib/tests/counters_tests.rs @@ -28,11 +28,11 @@ fn ctu() { END_VAR // count up until Q_res true and then reset CV_res IF Q_res THEN - ctu_inst(CU:= TRUE, R:= TRUE, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= TRUE, PV:= INT#3, Q => Q_res, CV => CV_res); ELSE - ctu_inst(CU:= TRUE, R:= FALSE, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= FALSE, PV:= INT#3, Q => Q_res, CV => CV_res); // input CU evaluated by R_EDGE, this call will enable to count up again - ctu_inst(CU:= FALSE, R:= FALSE, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= FALSE, R:= FALSE, PV:= INT#3, Q => Q_res, CV => CV_res); END_IF END_PROGRAM "#; @@ -70,11 +70,11 @@ fn ctu_int() { END_VAR // count up until Q_res true and then reset CV_res IF Q_res THEN - ctu_inst(CU:= TRUE, R:= TRUE, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= TRUE, PV:= INT#3, Q => Q_res, CV => CV_res); ELSE - ctu_inst(CU:= TRUE, R:= FALSE, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= FALSE, PV:= INT#3, Q => Q_res, CV => CV_res); // input CU evaluated by R_EDGE, this call will enable to count up again - ctu_inst(CU:= FALSE, R:= FALSE, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= FALSE, R:= FALSE, PV:= INT#3, Q => Q_res, CV => CV_res); END_IF END_PROGRAM "#; @@ -112,11 +112,11 @@ fn ctu_dint() { END_VAR // count up until Q_res true and then reset CV_res IF Q_res THEN - ctu_inst(CU:= TRUE, R:= TRUE, PV:= DINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= TRUE, PV:= DINT#3, Q => Q_res, CV => CV_res); ELSE - ctu_inst(CU:= TRUE, R:= FALSE, PV:= DINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= FALSE, PV:= DINT#3, Q => Q_res, CV => CV_res); // input CU evaluated by R_EDGE, this call will enable to count up again - ctu_inst(CU:= FALSE, R:= FALSE, PV:= DINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= FALSE, R:= FALSE, PV:= DINT#3, Q => Q_res, CV => CV_res); END_IF END_PROGRAM "#; @@ -154,11 +154,11 @@ fn ctu_udint() { END_VAR // count up until Q_res true and then reset CV_res IF Q_res THEN - ctu_inst(CU:= TRUE, R:= TRUE, PV:= UDINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= TRUE, PV:= UDINT#3, Q => Q_res, CV => CV_res); ELSE - ctu_inst(CU:= TRUE, R:= FALSE, PV:= UDINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= FALSE, PV:= UDINT#3, Q => Q_res, CV => CV_res); // input CU evaluated by R_EDGE, this call will enable to count up again - ctu_inst(CU:= FALSE, R:= FALSE, PV:= UDINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= FALSE, R:= FALSE, PV:= UDINT#3, Q => Q_res, CV => CV_res); END_IF END_PROGRAM "#; @@ -196,11 +196,11 @@ fn ctu_lint() { END_VAR // count up until Q_res true and then reset CV_res IF Q_res THEN - ctu_inst(CU:= TRUE, R:= TRUE, PV:= LINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= TRUE, PV:= LINT#3, Q => Q_res, CV => CV_res); ELSE - ctu_inst(CU:= TRUE, R:= FALSE, PV:= LINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= FALSE, PV:= LINT#3, Q => Q_res, CV => CV_res); // input CU evaluated by R_EDGE, this call will enable to count up again - ctu_inst(CU:= FALSE, R:= FALSE, PV:= LINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= FALSE, R:= FALSE, PV:= LINT#3, Q => Q_res, CV => CV_res); END_IF END_PROGRAM "#; @@ -238,11 +238,11 @@ fn ctu_ulint() { END_VAR // count up until Q_res true and then reset CV_res IF Q_res THEN - ctu_inst(CU:= TRUE, R:= TRUE, PV:= ULINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= TRUE, PV:= ULINT#3, Q => Q_res, CV => CV_res); ELSE - ctu_inst(CU:= TRUE, R:= FALSE, PV:= ULINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= TRUE, R:= FALSE, PV:= ULINT#3, Q => Q_res, CV => CV_res); // input CU evaluated by R_EDGE, this call will enable to count up again - ctu_inst(CU:= FALSE, R:= FALSE, PV:= ULINT#3, Q:= Q_res, CV:= CV_res); + ctu_inst(CU:= FALSE, R:= FALSE, PV:= ULINT#3, Q => Q_res, CV => CV_res); END_IF END_PROGRAM "#; @@ -290,12 +290,12 @@ fn ctd() { END_VAR // load PV value IF load THEN - ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res); load := FALSE; END_IF - ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res); // input CD evaluated by R_EDGE, this call will enable to count down again - ctd_inst(CD:= FALSE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= FALSE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res); END_PROGRAM "#; @@ -333,12 +333,12 @@ fn ctd_int() { END_VAR // load PV value IF load THEN - ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res); load := FALSE; END_IF - ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res); // input CD evaluated by R_EDGE, this call will enable to count down again - ctd_inst(CD:= FALSE, LD:= load, PV:= INT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= FALSE, LD:= load, PV:= INT#3, Q => Q_res, CV => CV_res); END_PROGRAM "#; @@ -376,12 +376,12 @@ fn ctd_dint() { END_VAR // load PV value IF load THEN - ctd_inst(CD:= TRUE, LD:= load, PV:= DINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= DINT#3, Q => Q_res, CV => CV_res); load := FALSE; END_IF - ctd_inst(CD:= TRUE, LD:= load, PV:= DINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= DINT#3, Q => Q_res, CV => CV_res); // input CD evaluated by R_EDGE, this call will enable to count down again - ctd_inst(CD:= FALSE, LD:= load, PV:= DINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= FALSE, LD:= load, PV:= DINT#3, Q => Q_res, CV => CV_res); END_PROGRAM "#; @@ -419,12 +419,12 @@ fn ctd_udint() { END_VAR // load PV value IF load THEN - ctd_inst(CD:= TRUE, LD:= load, PV:= UDINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= UDINT#3, Q => Q_res, CV => CV_res); load := FALSE; END_IF - ctd_inst(CD:= TRUE, LD:= load, PV:= UDINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= UDINT#3, Q => Q_res, CV => CV_res); // input CD evaluated by R_EDGE, this call will enable to count down again - ctd_inst(CD:= FALSE, LD:= load, PV:= UDINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= FALSE, LD:= load, PV:= UDINT#3, Q => Q_res, CV => CV_res); END_PROGRAM "#; @@ -462,12 +462,12 @@ fn ctd_lint() { END_VAR // load PV value IF load THEN - ctd_inst(CD:= TRUE, LD:= load, PV:= LINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= LINT#3, Q => Q_res, CV => CV_res); load := FALSE; END_IF - ctd_inst(CD:= TRUE, LD:= load, PV:= LINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= LINT#3, Q => Q_res, CV => CV_res); // input CD evaluated by R_EDGE, this call will enable to count down again - ctd_inst(CD:= FALSE, LD:= load, PV:= LINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= FALSE, LD:= load, PV:= LINT#3, Q => Q_res, CV => CV_res); END_PROGRAM "#; @@ -505,12 +505,12 @@ fn ctd_ulint() { END_VAR // load PV value IF load THEN - ctd_inst(CD:= TRUE, LD:= load, PV:= ULINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= ULINT#3, Q => Q_res, CV => CV_res); load := FALSE; END_IF - ctd_inst(CD:= TRUE, LD:= load, PV:= ULINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= TRUE, LD:= load, PV:= ULINT#3, Q => Q_res, CV => CV_res); // input CD evaluated by R_EDGE, this call will enable to count down again - ctd_inst(CD:= FALSE, LD:= load, PV:= ULINT#3, Q:= Q_res, CV:= CV_res); + ctd_inst(CD:= FALSE, LD:= load, PV:= ULINT#3, Q => Q_res, CV => CV_res); END_PROGRAM "#; @@ -561,31 +561,31 @@ fn ctud() { END_VAR // 1st call, load PV value IF load THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); load := FALSE; END_IF // 2nd call, CU/CD both true, nothing should happen IF i = 1 THEN - ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 3rd call, count down IF i = 2 THEN // input CD evaluated by R_EDGE, this call will enable count down again - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); - ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); + ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 4th call, count up IF i = 3 THEN // input CU evaluated by R_EDGE, third call enabled count up again - ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 5th call, reset IF i = 4 THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF i := i + 1; END_PROGRAM @@ -636,31 +636,31 @@ fn ctud_int() { END_VAR // 1st call, load PV value IF load THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); load := FALSE; END_IF // 2nd call, CU/CD both true, nothing should happen IF i = 1 THEN - ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 3rd call, count down IF i = 2 THEN // input CD evaluated by R_EDGE, this call will enable count down again - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); - ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); + ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 4th call, count up IF i = 3 THEN // input CU evaluated by R_EDGE, third call enabled count up again - ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 5th call, reset IF i = 4 THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= INT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= INT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF i := i + 1; END_PROGRAM @@ -711,31 +711,31 @@ fn ctud_dint() { END_VAR // 1st call, load PV value IF load THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res); load := FALSE; END_IF // 2nd call, CU/CD both true, nothing should happen IF i = 1 THEN - ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 3rd call, count down IF i = 2 THEN // input CD evaluated by R_EDGE, this call will enable count down again - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); - ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res); + ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 4th call, count up IF i = 3 THEN // input CU evaluated by R_EDGE, third call enabled count up again - ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 5th call, reset IF i = 4 THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= DINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= DINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF i := i + 1; END_PROGRAM @@ -786,31 +786,31 @@ fn ctud_udint() { END_VAR // 1st call, load PV value IF load THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res); load := FALSE; END_IF // 2nd call, CU/CD both true, nothing should happen IF i = 1 THEN - ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 3rd call, count down IF i = 2 THEN // input CD evaluated by R_EDGE, this call will enable count down again - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); - ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res); + ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 4th call, count up IF i = 3 THEN // input CU evaluated by R_EDGE, third call enabled count up again - ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 5th call, reset IF i = 4 THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= UDINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= UDINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF i := i + 1; END_PROGRAM @@ -861,31 +861,31 @@ fn ctud_lint() { END_VAR // 1st call, load PV value IF load THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res); load := FALSE; END_IF // 2nd call, CU/CD both true, nothing should happen IF i = 1 THEN - ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 3rd call, count down IF i = 2 THEN // input CD evaluated by R_EDGE, this call will enable count down again - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); - ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res); + ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 4th call, count up IF i = 3 THEN // input CU evaluated by R_EDGE, third call enabled count up again - ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 5th call, reset IF i = 4 THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= LINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= LINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF i := i + 1; END_PROGRAM @@ -936,31 +936,31 @@ fn ctud_ulint() { END_VAR // 1st call, load PV value IF load THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= TRUE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res); load := FALSE; END_IF // 2nd call, CU/CD both true, nothing should happen IF i = 1 THEN - ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 3rd call, count down IF i = 2 THEN // input CD evaluated by R_EDGE, this call will enable count down again - ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); - ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res); + ctud_inst(CU:= FALSE, CD:= TRUE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 4th call, count up IF i = 3 THEN // input CU evaluated by R_EDGE, third call enabled count up again - ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= TRUE, CD:= FALSE, R:= FALSE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF // 5th call, reset IF i = 4 THEN - ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= ULINT#1, QU:= QU_res, QD:= QD_res, CV:= CV_res); + ctud_inst(CU:= FALSE, CD:= FALSE, R:= TRUE, LD:= FALSE, PV:= ULINT#1, QU => QU_res, QD => QD_res, CV => CV_res); END_IF i := i + 1; END_PROGRAM diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index 5db678157f..3ad0a9abf7 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -1,20 +1,5 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder -use crate::{ - codegen::{ - debug::{Debug, DebugBuilderEnum}, - llvm_index::LlvmTypedIndex, - llvm_typesystem::{cast_if_needed, get_llvm_int_type}, - }, - index::{ - const_expressions::ConstId, ArgumentType, ImplementationIndexEntry, Index, PouIndexEntry, - VariableIndexEntry, VariableType, - }, - resolver::{AnnotationMap, AstAnnotations, StatementAnnotation}, - typesystem::{ - is_same_type_class, DataType, DataTypeInformation, DataTypeInformationProvider, Dimension, - StringEncoding, VarArgs, DINT_TYPE, INT_SIZE, INT_TYPE, LINT_TYPE, - }, -}; + use inkwell::{ builder::Builder, types::{BasicType, BasicTypeEnum}, @@ -24,6 +9,9 @@ use inkwell::{ }, AddressSpace, FloatPredicate, IntPredicate, }; +use rustc_hash::FxHashSet; + +use plc_ast::ast::Assignment; use plc_ast::{ ast::{ flatten_expression_list, AstFactory, AstNode, AstStatement, DirectAccessType, Operator, @@ -34,10 +22,27 @@ use plc_ast::{ use plc_diagnostics::diagnostics::{Diagnostic, INTERNAL_LLVM_ERROR}; use plc_source::source_location::SourceLocation; use plc_util::convention::qualified_name; -use rustc_hash::FxHashSet; -use std::vec; + +use crate::{ + codegen::{ + debug::{Debug, DebugBuilderEnum}, + llvm_index::LlvmTypedIndex, + llvm_typesystem::{cast_if_needed, get_llvm_int_type}, + }, + index::{ + const_expressions::ConstId, ArgumentType, ImplementationIndexEntry, Index, PouIndexEntry, + VariableIndexEntry, VariableType, + }, + resolver::{AnnotationMap, AstAnnotations, StatementAnnotation}, + typesystem, + typesystem::{ + is_same_type_class, DataType, DataTypeInformation, DataTypeInformationProvider, Dimension, + StringEncoding, VarArgs, DINT_TYPE, INT_SIZE, INT_TYPE, LINT_TYPE, + }, +}; use super::{llvm::Llvm, statement_generator::FunctionContext, ADDRESS_SPACE_CONST, ADDRESS_SPACE_GENERIC}; + /// the generator for expressions pub struct ExpressionCodeGenerator<'a, 'b> { pub llvm: &'b Llvm<'a>, @@ -62,7 +67,7 @@ pub struct ExpressionCodeGenerator<'a, 'b> { #[derive(Debug)] struct CallParameterAssignment<'a, 'b> { /// the assignmentstatement in the call-argument list (a:=3) - assignment_statement: &'b AstNode, + assignment: &'b AstNode, /// the name of the function we're calling function_name: &'b str, /// the position of the argument in the POU's argument's list @@ -424,7 +429,8 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { self.index.find_pou(name) } else { None - }) + } + ) .ok_or_else(|| Diagnostic::cannot_generate_call_statement(operator))?; // find corresponding implementation @@ -500,12 +506,13 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { // after the call we need to copy the values for assigned outputs // this is only necessary for outputs defined as `rusty::index::ArgumentType::ByVal` (PROGRAM, FUNCTION_BLOCK) - // FUNCTION outputs are defined as `rusty::index::ArgumentType::ByRef` + // FUNCTION outputs are defined as `rusty::index::ArgumentType::ByRef` // FIXME(mhasel): for standard-compliance functions also need to support VAR_OUTPUT if !pou.is_function() { let parameter_struct = match arguments_list.first() { Some(v) => v.into_pointer_value(), None => self.generate_lvalue(operator)?, }; + self.assign_output_values(parameter_struct, implementation_name, parameters_list)? } @@ -522,72 +529,220 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { function_name: &str, parameters: Vec<&AstNode>, ) -> Result<(), Diagnostic> { + let pou_info = self.index.get_declared_parameters(function_name); + let implicit = arguments_are_implicit(¶meters); + for (index, assignment_statement) in parameters.into_iter().enumerate() { - self.assign_output_value(&CallParameterAssignment { - assignment_statement, - function_name, - index: index as u32, - parameter_struct, - })? + let is_output = pou_info.get(index).is_some_and(|param| param.get_variable_type().is_output()); + + if assignment_statement.is_output_assignment() || (implicit && is_output) { + self.assign_output_value(&CallParameterAssignment { + assignment: assignment_statement, + function_name, + index: index as u32, + parameter_struct, + })? + } } + Ok(()) } fn assign_output_value(&self, param_context: &CallParameterAssignment) -> Result<(), Diagnostic> { - match param_context.assignment_statement.get_stmt() { - AstStatement::OutputAssignment(data) | AstStatement::Assignment(data) => self - .generate_explicit_output_assignment( - param_context.parameter_struct, - param_context.function_name, - &data.left, - &data.right, - ), + match ¶m_context.assignment.stmt { + AstStatement::OutputAssignment(assignment) => self.generate_explicit_output_assignment( + param_context.parameter_struct, + param_context.function_name, + assignment, + ), + _ => self.generate_output_assignment(param_context), } } - fn generate_output_assignment(&self, param_context: &CallParameterAssignment) -> Result<(), Diagnostic> { - let builder = &self.llvm.builder; - let expression = param_context.assignment_statement; - let parameter_struct = param_context.parameter_struct; - let function_name = param_context.function_name; - let index = param_context.index; - if let Some(parameter) = self.index.get_declared_parameter(function_name, index) { - if matches!(parameter.get_variable_type(), VariableType::Output) - && !matches!(expression.get_stmt(), AstStatement::EmptyStatement { .. }) + pub fn generate_assignment_with_direct_access( + &self, + left_statement: &AstNode, + left_value: IntValue, + left_pointer: PointerValue, + right_type: &DataType, + right_expr: BasicValueEnum, + ) -> Result<(), Diagnostic> { + let Some((target, access_sequence)) = collect_base_and_direct_access_for_assignment(left_statement) + else { + unreachable!("Invalid direct-access expression: {left_statement:#?}") + }; + + let type_left = self.get_type_hint_for(target)?; + let type_right = + if let DataTypeInformation::Integer { semantic_size: Some(typesystem::U1_SIZE), .. } = + *right_type.get_type_information() { - { - let assigned_output = self.generate_lvalue(expression)?; + // we need to switch to a faked u1 type, when dealing with a single bit + self.index.get_type_or_panic(typesystem::U1_TYPE) + } else { + right_type + }; - let assigned_output_type = - self.annotations.get_type_or_void(expression, self.index).get_type_information(); + let Some((element, direct_access)) = access_sequence.split_first() else { unreachable!("") }; - let output = builder.build_struct_gep(parameter_struct, index, "").map_err(|_| { - Diagnostic::codegen_error( - format!("Cannot build generate parameter: {parameter:#?}"), - parameter.source_location.clone(), - ) - })?; + // Build index + let mut index = if let AstStatement::DirectAccess(data, ..) = element.get_stmt() { + self.generate_direct_access_index( + &data.access, + &data.index, + type_right.get_type_information(), + type_left, + ) + } else { + // TODO: using the global context we could get a slice here; currently not possible because the + // global context isn't passed into codegen + Err(Diagnostic::new(format!("{element:?} not a direct access")) + .with_error_code("E055") + .with_location(element.get_location())) + }?; + for element in direct_access { + let rhs_next = if let AstStatement::DirectAccess(data, ..) = element.get_stmt() { + self.generate_direct_access_index( + &data.access, + &data.index, + type_right.get_type_information(), + type_left, + ) + } else { + // TODO: using the global context we could get a slice here; currently not possible because the + // global context isn't passed into codegen + Err(Diagnostic::new(&format!("{element:?} not a direct access")) + .with_error_code("E055") + .with_location(element.get_location())) + }?; + index = self.llvm.builder.build_int_add(index, rhs_next, ""); + } - let output_value_type = - self.index.get_type_information_or_void(parameter.get_type_name()); - - if assigned_output_type.is_aggregate() && output_value_type.is_aggregate() { - self.build_memcpy( - assigned_output, - assigned_output_type, - expression.get_location(), - output, - output_value_type, - parameter.source_location.clone(), - )?; - } else { - let output_value = builder.build_load(output, ""); - builder.build_store(assigned_output, output_value); - } + //Build mask for the index + //Get the target bit type as all ones + let rhs_type = self.llvm_index.get_associated_type(type_right.get_name())?.into_int_type(); + let ones = rhs_type.const_all_ones(); + + //Extend the mask to the target type + let extended_mask = self.llvm.builder.build_int_z_extend(ones, left_value.get_type(), "ext"); + //Position the ones in their correct locations + let shifted_mask = self.llvm.builder.build_left_shift(extended_mask, index, "shift"); + //Invert the mask + let mask = self.llvm.builder.build_not(shifted_mask, "invert"); + //And the result with the mask to erase the set bits at the target location + let and_value = self.llvm.builder.build_and(left_value, mask, "erase"); + + //Cast the right side to the left side type + let lhs = cast_if_needed!(self, type_left, type_right, right_expr, None).into_int_value(); + //Shift left by the direct access + let value = self.llvm.builder.build_left_shift(lhs, index, "value"); + + //OR the result and store it in the left side + let or_value = self.llvm.builder.build_or(and_value, value, "or"); + self.llvm.builder.build_store(left_pointer, or_value); + + Ok(()) + } + + fn generate_output_assignment_with_direct_access( + &self, + left_statement: &AstNode, + left_pointer: PointerValue, + right_pointer: PointerValue, + right_type: &DataType, + ) -> Result<(), Diagnostic> { + let left_value = self.llvm.builder.build_load(left_pointer, "").into_int_value(); + + //Generate an expression for the right size + let right = self.llvm.builder.build_load(right_pointer, ""); + self.generate_assignment_with_direct_access( + left_statement, + left_value, + left_pointer, + right_type, + right, + )?; + + Ok(()) + } + + fn generate_output_assignment(&self, context: &CallParameterAssignment) -> Result<(), Diagnostic> { + let &CallParameterAssignment { assignment: expr, function_name, index, parameter_struct } = context; + let builder = &self.llvm.builder; + + // We don't want to generate any code if the right side of an assignment is empty, e.g. `foo(out =>)` + if expr.is_empty_statement() { + return Ok(()); + } + + let parameter = self.index.get_declared_parameter(function_name, index).expect("must exist"); + + match expr.get_stmt() { + AstStatement::ReferenceExpr(_) if expr.has_direct_access() => { + let rhs_type = { + let pou = self.index.find_pou(function_name).unwrap(); + let pou_struct = &pou.find_instance_struct_type(self.index).unwrap().information; + let DataTypeInformation::Struct { members, .. } = pou_struct else { unreachable!() }; + + self.index.find_effective_type_by_name(&members[index as usize].data_type_name).unwrap() + }; + + let AstStatement::ReferenceExpr(ReferenceExpr { + access: ReferenceAccess::Member(member), + base, + }) = &expr.get_stmt() + else { + unreachable!("must be a bitaccess, will return early for all other cases") + }; + + if let AstStatement::DirectAccess(_) = member.as_ref().get_stmt() { + // Given `foo.bar.baz.%W1.%B1.%X3`, we want to grab the base i.e. `foo.bar.baz` + let (Some(base), _) = (base, ..) else { panic!() }; + let (base, _) = collect_base_and_direct_access_for_assignment(base).unwrap(); + + let lhs = self.generate_expression_value(base)?.get_basic_value_enum(); + let rhs = self.llvm.builder.build_struct_gep(parameter_struct, index, "").unwrap(); + + self.generate_output_assignment_with_direct_access( + expr, + lhs.into_pointer_value(), + rhs, + rhs_type, + )?; + }; + } + + _ => { + let assigned_output = self.generate_lvalue(expr)?; + let assigned_output_type = + self.annotations.get_type_or_void(expr, self.index).get_type_information(); + + let output = builder.build_struct_gep(parameter_struct, index, "").map_err(|_| { + Diagnostic::codegen_error( + format!("Cannot build generate parameter: {parameter:#?}"), + parameter.source_location.clone(), + ) + })?; + + let output_value_type = self.index.get_type_information_or_void(parameter.get_type_name()); + + if assigned_output_type.is_aggregate() && output_value_type.is_aggregate() { + self.build_memcpy( + assigned_output, + assigned_output_type, + expr.get_location(), + output, + output_value_type, + parameter.source_location.clone(), + )?; + } else { + let output_value = builder.build_load(output, ""); + builder.build_store(assigned_output, output_value); } } - } + }; + Ok(()) } @@ -595,22 +750,25 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { &self, parameter_struct: PointerValue<'ink>, function_name: &str, - left: &AstNode, - right: &AstNode, + assignment: &Assignment, ) -> Result<(), Diagnostic> { + let Assignment { left, right } = assignment; + if let Some(StatementAnnotation::Variable { qualified_name, .. }) = self.annotations.get(left) { let parameter = self .index .find_fully_qualified_variable(qualified_name) .ok_or_else(|| Diagnostic::unresolved_reference(qualified_name, left.get_location()))?; let index = parameter.get_location_in_parent(); - self.assign_output_value(&CallParameterAssignment { - assignment_statement: right, + + self.generate_output_assignment(&CallParameterAssignment { + assignment: right, function_name, index, parameter_struct, })? }; + Ok(()) } @@ -973,7 +1131,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .unwrap_or_else(|| vec![parameter_struct.as_basic_value_enum().into()]); for (i, stmt) in passed_parameters.iter().enumerate() { let parameter = self.generate_call_struct_argument_assignment(&CallParameterAssignment { - assignment_statement: stmt, + assignment: stmt, function_name: pou_name, index: i as u32, parameter_struct, @@ -1049,7 +1207,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { &self, param_context: &CallParameterAssignment, ) -> Result>, Diagnostic> { - let parameter_value = match param_context.assignment_statement.get_stmt() { + let parameter_value = match param_context.assignment.get_stmt() { // explicit call parameter: foo(param := value) AstStatement::OutputAssignment(data) | AstStatement::Assignment(data) => { self.generate_formal_parameter(param_context, &data.left, &data.right)?; @@ -1072,7 +1230,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { let function_name = param_context.function_name; let index = param_context.index; let parameter_struct = param_context.parameter_struct; - let expression = param_context.assignment_statement; + let expression = param_context.assignment; if let Some(parameter) = self.index.get_declared_parameter(function_name, index) { // this happens before the pou call // before the call statement we may only consider inputs and inouts @@ -1148,7 +1306,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { ); if !right.is_empty_statement() || is_auto_deref { self.generate_call_struct_argument_assignment(&CallParameterAssignment { - assignment_statement: right, + assignment: right, function_name, index, parameter_struct, @@ -2115,7 +2273,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { return Err(Diagnostic::codegen_error( format!("Cannot generate phi-expression for operator {operator:}"), left.get_location(), - )) + )); } }; @@ -2361,7 +2519,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { (ReferenceAccess::Member(member), base) => { let base_value = base.map(|it| self.generate_expression_value(it)).transpose()?; - if let AstStatement::DirectAccess (data) = member.as_ref().get_stmt() { + if let AstStatement::DirectAccess(data) = member.as_ref().get_stmt() { let (Some(base), Some(base_value)) = (base, base_value) else { return Err(Diagnostic::codegen_error("Cannot generate DirectAccess without base value.", original_expression.get_location())); }; @@ -2373,7 +2531,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { self.get_load_name(member).as_deref().unwrap_or(member_name), original_expression, ) - .map(ExpressionValue::LValue) + .map(ExpressionValue::LValue) } } @@ -2386,8 +2544,8 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { self.annotations.get(base).expect(""), array_idx.as_ref(), ) - .map_err(|_| unreachable!("invalid access statement")) - .map(ExpressionValue::LValue) + .map_err(|_| unreachable!("invalid access statement")) + .map(ExpressionValue::LValue) } else { // normal array expression self.generate_element_pointer_for_array(base, array_idx).map(ExpressionValue::LValue) @@ -2425,7 +2583,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { | (ReferenceAccess::Cast(_), None) // INT#; | (ReferenceAccess::Deref, None) // ^; | (ReferenceAccess::Address, None) // &; - => Err(Diagnostic::codegen_error( + => Err(Diagnostic::codegen_error( "Expected a base-expressions, but found none.", original_expression.get_location(), )), @@ -2646,3 +2804,32 @@ fn int_value_multiply_accumulate<'ink>( } llvm.builder.build_load(accum, "accessor").into_int_value() } + +// XXX: Could be problematic with https://github.com/PLC-lang/rusty/issues/668 +/// Returns false if any argument in the given list is an (output-)assignment and true otherwise +fn arguments_are_implicit(arguments: &[&AstNode]) -> bool { + !arguments.iter().any(|argument| argument.is_assignment() || argument.is_output_assignment()) +} + +/// when generating an assignment to a direct-access (e.g. a.b.c.%W3.%X2 := 2;) +/// we want to deconstruct the sequence into the base-statement (a.b.c) and the sequence +/// of direct-access commands (vec![%W3, %X2]) +fn collect_base_and_direct_access_for_assignment( + left_statement: &AstNode, +) -> Option<(&AstNode, Vec<&AstNode>)> { + let mut current = Some(left_statement); + let mut access_sequence = Vec::new(); + + while let Some(AstStatement::ReferenceExpr(ReferenceExpr { access: ReferenceAccess::Member(m), base })) = + current.map(|it| it.get_stmt()) + { + if matches!(m.get_stmt(), AstStatement::DirectAccess { .. }) { + access_sequence.insert(0, m.as_ref()); + current = base.as_deref(); + } else { + break; + } + } + + current.zip(Some(access_sequence)) +} diff --git a/src/codegen/generators/statement_generator.rs b/src/codegen/generators/statement_generator.rs index 40b36c193f..30d125f57f 100644 --- a/src/codegen/generators/statement_generator.rs +++ b/src/codegen/generators/statement_generator.rs @@ -4,11 +4,11 @@ use super::{ llvm::Llvm, }; use crate::{ - codegen::{debug::Debug, llvm_typesystem::cast_if_needed}, + codegen::debug::Debug, codegen::{debug::DebugBuilderEnum, LlvmTypedIndex}, index::{ImplementationIndexEntry, Index}, resolver::{AnnotationMap, AstAnnotations, StatementAnnotation}, - typesystem::{self, DataTypeInformation}, + typesystem::DataTypeInformation, }; use inkwell::{ basic_block::BasicBlock, @@ -244,7 +244,7 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { self.register_debug_location(left_statement); //TODO: Looks hacky, the strings will be similar so we should look into making the assignment a bit nicer. if left_statement.has_direct_access() { - return self.generate_direct_access_assignment(left_statement, right_statement); + return self.generate_assignment_statement_direct_access(left_statement, right_statement); } //TODO: Also hacky but for now we cannot generate assignments for hardware access if matches!(left_statement.get_stmt(), AstStatement::HardwareAccess { .. }) { @@ -279,96 +279,32 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { self.debug.set_debug_location(self.llvm, &self.function_context.function, line, column); } - fn generate_direct_access_assignment( + fn generate_assignment_statement_direct_access( &self, left_statement: &AstNode, right_statement: &AstNode, ) -> Result<(), Diagnostic> { - //TODO : Validation let exp_gen = self.create_expr_generator(); - // given a complex direct-access assignemnt: a.b.c.%W3,%X1 - // we want to deconstruct the targe-part (a.b.c) and the direct-access sequence (%W3.%X1) - let Some((target, access_sequence)) = collect_base_and_direct_access_for_assignment(left_statement) - else { + // Left pointer + let Some((base, _)) = collect_base_and_direct_access_for_assignment(left_statement) else { unreachable!("Invalid direct-access expression: {left_statement:#?}") }; + let left_expr_value = exp_gen.generate_expression_value(base)?; + let left_value = left_expr_value.as_r_value(self.llvm, None).into_int_value(); + let left_pointer = left_expr_value.get_basic_value_enum().into_pointer_value(); - let left_type = exp_gen.get_type_hint_for(target)?; + // Generate an expression for the right size let right_type = exp_gen.get_type_hint_for(right_statement)?; - - //special case if we deal with a single bit, then we need to switch to a faked u1 type - let right_type = - if let DataTypeInformation::Integer { semantic_size: Some(typesystem::U1_SIZE), .. } = - *right_type.get_type_information() - { - self.index.get_type_or_panic(typesystem::U1_TYPE) - } else { - right_type - }; - - //Left pointer - let left_expression_value = exp_gen.generate_expression_value(target)?; - let left_value = left_expression_value.as_r_value(self.llvm, None).into_int_value(); - let left = left_expression_value.get_basic_value_enum().into_pointer_value(); - //Build index - if let Some((element, direct_access)) = access_sequence.split_first() { - let mut rhs = if let AstStatement::DirectAccess(data, ..) = element.get_stmt() { - exp_gen.generate_direct_access_index( - &data.access, - &data.index, - right_type.get_type_information(), - left_type, - ) - } else { - //TODO: using the global context we could get a slice here - Err(Diagnostic::new(format!("{element:?} not a direct access")) - .with_error_code("E055") - .with_location(element.get_location())) - }?; - for element in direct_access { - let rhs_next = if let AstStatement::DirectAccess(data, ..) = element.get_stmt() { - exp_gen.generate_direct_access_index( - &data.access, - &data.index, - right_type.get_type_information(), - left_type, - ) - } else { - //TODO: using the global context we could get a slice here - Err(Diagnostic::new(&format!("{element:?} not a direct access")) - .with_error_code("E055") - .with_location(element.get_location())) - }?; - rhs = self.llvm.builder.build_int_add(rhs, rhs_next, ""); - } - //Build mask for the index - //Get the target bit type as all ones - let rhs_type = self.llvm_index.get_associated_type(right_type.get_name())?.into_int_type(); - let ones = rhs_type.const_all_ones(); - //Extend the mask to the target type - let extended_mask = self.llvm.builder.build_int_z_extend(ones, left_value.get_type(), "ext"); - //Position the ones in their correct locations - let shifted_mask = self.llvm.builder.build_left_shift(extended_mask, rhs, "shift"); - //Invert the mask - let mask = self.llvm.builder.build_not(shifted_mask, "invert"); - //And the result with the mask to erase the set bits at the target location - let and_value = self.llvm.builder.build_and(left_value, mask, "erase"); - - //Generate an expression for the right size - let right = exp_gen.generate_expression(right_statement)?; - //Cast the right side to the left side type - let lhs = cast_if_needed!(self, left_type, right_type, right, None).into_int_value(); - //Shift left by the direct access - let value = self.llvm.builder.build_left_shift(lhs, rhs, "value"); - - //OR the result and store it in the left side - let or_value = self.llvm.builder.build_or(and_value, value, "or"); - self.llvm.builder.build_store(left, or_value); - } else { - unreachable!(); - } - Ok(()) + let right_expr = exp_gen.generate_expression(right_statement)?; + + exp_gen.generate_assignment_with_direct_access( + left_statement, + left_value, + left_pointer, + right_type, + right_expr, + ) } /// generates a for-loop statement @@ -842,9 +778,8 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { } } -/// when generating an assignment to a direct-access (e.g. a.b.c.%W3.%X2 := 2;) -/// we want to deconstruct the sequence into the base-statement (a.b.c) and the sequence -/// of direct-access commands (vec![%W3, %X2]) +/// Deconstructs assignments such as `a.b.c.%W3.%X2 := 2` into a base statement and its direct-access sequences. +/// For the given example this function would return `(Node(a.b.c), vec![Node(%W3), Node(%X2)])` fn collect_base_and_direct_access_for_assignment( left_statement: &AstNode, ) -> Option<(&AstNode, Vec<&AstNode>)> { diff --git a/src/codegen/tests/directaccess_test.rs b/src/codegen/tests/directaccess_test.rs index df69af8891..26e2604ff3 100644 --- a/src/codegen/tests/directaccess_test.rs +++ b/src/codegen/tests/directaccess_test.rs @@ -1,3 +1,4 @@ +use insta::assert_snapshot; // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use crate::test_utils::tests::codegen; @@ -112,3 +113,327 @@ fn qualified_reference_assignment() { ); insta::assert_snapshot!(prog); } + +#[test] +fn direct_acess_in_output_assignment_implicit_explicit_and_mixed() { + let ir = codegen( + r" + FUNCTION_BLOCK FOO + VAR_INPUT + X : BOOL; + END_VAR + VAR_OUTPUT + Y : BOOL; + END_VAR + END_FUNCTION_BLOCK + + FUNCTION main : DINT + VAR + error_bits : BYTE; + f : FOO; + END_VAR + + f(X := error_bits.0, Y => error_bits.0); + f(Y => error_bits.0, x := error_bits.0); + f(error_bits.0, error_bits.0); + f(X := error_bits.0, Y =>); + END_FUNCTION + ", + ); + + assert_snapshot!(ir, @r###" + ; ModuleID = 'main' + source_filename = "main" + + %FOO = type { i8, i8 } + + @__FOO__init = unnamed_addr constant %FOO zeroinitializer, section "var-$RUSTY$__FOO__init:r2u8u8" + + define void @FOO(%FOO* %0) section "fn-$RUSTY$FOO:v[u8][u8]" { + entry: + %X = getelementptr inbounds %FOO, %FOO* %0, i32 0, i32 0 + %Y = getelementptr inbounds %FOO, %FOO* %0, i32 0, i32 1 + ret void + } + + define i32 @main() section "fn-$RUSTY$main:i32" { + entry: + %main = alloca i32, align 4 + %error_bits = alloca i8, align 1 + %f = alloca %FOO, align 8 + store i8 0, i8* %error_bits, align 1 + %0 = bitcast %FOO* %f to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 getelementptr inbounds (%FOO, %FOO* @__FOO__init, i32 0, i32 0), i64 ptrtoint (%FOO* getelementptr (%FOO, %FOO* null, i32 1) to i64), i1 false) + store i32 0, i32* %main, align 4 + %1 = getelementptr inbounds %FOO, %FOO* %f, i32 0, i32 0 + %load_error_bits = load i8, i8* %error_bits, align 1 + %shift = lshr i8 %load_error_bits, 0 + %2 = and i8 %shift, 1 + store i8 %2, i8* %1, align 1 + call void @FOO(%FOO* %f) + %3 = getelementptr inbounds %FOO, %FOO* %f, i32 0, i32 1 + %4 = load i8, i8* %error_bits, align 1 + %5 = load i8, i8* %3, align 1 + %erase = and i8 %4, -2 + %value = shl i8 %5, 0 + %or = or i8 %erase, %value + store i8 %or, i8* %error_bits, align 1 + %6 = getelementptr inbounds %FOO, %FOO* %f, i32 0, i32 0 + %load_error_bits1 = load i8, i8* %error_bits, align 1 + %shift2 = lshr i8 %load_error_bits1, 0 + %7 = and i8 %shift2, 1 + store i8 %7, i8* %6, align 1 + call void @FOO(%FOO* %f) + %8 = getelementptr inbounds %FOO, %FOO* %f, i32 0, i32 1 + %9 = load i8, i8* %error_bits, align 1 + %10 = load i8, i8* %8, align 1 + %erase3 = and i8 %9, -2 + %value4 = shl i8 %10, 0 + %or5 = or i8 %erase3, %value4 + store i8 %or5, i8* %error_bits, align 1 + %11 = getelementptr inbounds %FOO, %FOO* %f, i32 0, i32 0 + %load_error_bits6 = load i8, i8* %error_bits, align 1 + %shift7 = lshr i8 %load_error_bits6, 0 + %12 = and i8 %shift7, 1 + store i8 %12, i8* %11, align 1 + call void @FOO(%FOO* %f) + %13 = getelementptr inbounds %FOO, %FOO* %f, i32 0, i32 1 + %14 = load i8, i8* %error_bits, align 1 + %15 = load i8, i8* %13, align 1 + %erase8 = and i8 %14, -2 + %value9 = shl i8 %15, 0 + %or10 = or i8 %erase8, %value9 + store i8 %or10, i8* %error_bits, align 1 + %16 = getelementptr inbounds %FOO, %FOO* %f, i32 0, i32 0 + %load_error_bits11 = load i8, i8* %error_bits, align 1 + %shift12 = lshr i8 %load_error_bits11, 0 + %17 = and i8 %shift12, 1 + store i8 %17, i8* %16, align 1 + call void @FOO(%FOO* %f) + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + + attributes #0 = { argmemonly nofree nounwind willreturn } + "###); +} + +#[test] +fn direct_acess_in_output_assignment_with_simple_expression() { + let ir = codegen( + r" + FUNCTION_BLOCK FOO + VAR_OUTPUT + Q : BOOL := TRUE; + END_VAR + END_FUNCTION_BLOCK + + FUNCTION main : DINT + VAR + error_bits : BYTE := 2#1110_1111; + f : FOO; + END_VAR + + f(Q => error_bits.4); + END_FUNCTION + ", + ); + + assert_snapshot!(ir, @r###" + ; ModuleID = 'main' + source_filename = "main" + + %FOO = type { i8 } + + @__FOO__init = unnamed_addr constant %FOO { i8 1 }, section "var-$RUSTY$__FOO__init:r1u8" + + define void @FOO(%FOO* %0) section "fn-$RUSTY$FOO:v[u8]" { + entry: + %Q = getelementptr inbounds %FOO, %FOO* %0, i32 0, i32 0 + ret void + } + + define i32 @main() section "fn-$RUSTY$main:i32" { + entry: + %main = alloca i32, align 4 + %error_bits = alloca i8, align 1 + %f = alloca %FOO, align 8 + store i8 -17, i8* %error_bits, align 1 + %0 = bitcast %FOO* %f to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 getelementptr inbounds (%FOO, %FOO* @__FOO__init, i32 0, i32 0), i64 ptrtoint (%FOO* getelementptr (%FOO, %FOO* null, i32 1) to i64), i1 false) + store i32 0, i32* %main, align 4 + call void @FOO(%FOO* %f) + %1 = getelementptr inbounds %FOO, %FOO* %f, i32 0, i32 0 + %2 = load i8, i8* %error_bits, align 1 + %3 = load i8, i8* %1, align 1 + %erase = and i8 %2, -17 + %value = shl i8 %3, 4 + %or = or i8 %erase, %value + store i8 %or, i8* %error_bits, align 1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + + attributes #0 = { argmemonly nofree nounwind willreturn } + "###); +} + +#[test] +fn direct_acess_in_output_assignment_with_simple_expression_implicit() { + let ir = codegen( + r" + FUNCTION_BLOCK FOO + VAR_OUTPUT + Q : BOOL := TRUE; + END_VAR + END_FUNCTION_BLOCK + + FUNCTION main : DINT + VAR + error_bits : BYTE := 2#1110_1111; + f : FOO; + END_VAR + + f(error_bits.4); + END_FUNCTION + ", + ); + + assert_snapshot!(ir, @r###" + ; ModuleID = 'main' + source_filename = "main" + + %FOO = type { i8 } + + @__FOO__init = unnamed_addr constant %FOO { i8 1 }, section "var-$RUSTY$__FOO__init:r1u8" + + define void @FOO(%FOO* %0) section "fn-$RUSTY$FOO:v[u8]" { + entry: + %Q = getelementptr inbounds %FOO, %FOO* %0, i32 0, i32 0 + ret void + } + + define i32 @main() section "fn-$RUSTY$main:i32" { + entry: + %main = alloca i32, align 4 + %error_bits = alloca i8, align 1 + %f = alloca %FOO, align 8 + store i8 -17, i8* %error_bits, align 1 + %0 = bitcast %FOO* %f to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 getelementptr inbounds (%FOO, %FOO* @__FOO__init, i32 0, i32 0), i64 ptrtoint (%FOO* getelementptr (%FOO, %FOO* null, i32 1) to i64), i1 false) + store i32 0, i32* %main, align 4 + call void @FOO(%FOO* %f) + %1 = getelementptr inbounds %FOO, %FOO* %f, i32 0, i32 0 + %2 = load i8, i8* %error_bits, align 1 + %3 = load i8, i8* %1, align 1 + %erase = and i8 %2, -17 + %value = shl i8 %3, 4 + %or = or i8 %erase, %value + store i8 %or, i8* %error_bits, align 1 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + + attributes #0 = { argmemonly nofree nounwind willreturn } + "###); +} + +#[test] +fn direct_acess_in_output_assignment_with_complexe_expression() { + let ir = codegen( + r" + TYPE foo_struct : STRUCT + bar : bar_struct; + END_STRUCT END_TYPE + + TYPE bar_struct : STRUCT + baz : LWORD; + END_STRUCT END_TYPE + + FUNCTION_BLOCK QUUX + VAR_OUTPUT + Q : BOOL; + END_VAR + END_FUNCTION_BLOCK + + FUNCTION main : DINT + VAR + foo : foo_struct; + f : QUUX; + END_VAR + + f(Q => foo.bar.baz.%W3); + f(Q => foo.bar.baz.%W3.%B0.%X2); + END_FUNCTION + ", + ); + + assert_snapshot!(ir, @r###" + ; ModuleID = 'main' + source_filename = "main" + + %QUUX = type { i8 } + %foo_struct = type { %bar_struct } + %bar_struct = type { i64 } + + @__QUUX__init = unnamed_addr constant %QUUX zeroinitializer, section "var-$RUSTY$__QUUX__init:r1u8" + @__foo_struct__init = unnamed_addr constant %foo_struct zeroinitializer, section "var-$RUSTY$__foo_struct__init:r1r1u64" + @__bar_struct__init = unnamed_addr constant %bar_struct zeroinitializer, section "var-$RUSTY$__bar_struct__init:r1u64" + + define void @QUUX(%QUUX* %0) section "fn-$RUSTY$QUUX:v[u8]" { + entry: + %Q = getelementptr inbounds %QUUX, %QUUX* %0, i32 0, i32 0 + ret void + } + + define i32 @main() section "fn-$RUSTY$main:i32" { + entry: + %main = alloca i32, align 4 + %foo = alloca %foo_struct, align 8 + %f = alloca %QUUX, align 8 + %0 = bitcast %foo_struct* %foo to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 bitcast (%foo_struct* @__foo_struct__init to i8*), i64 ptrtoint (%foo_struct* getelementptr (%foo_struct, %foo_struct* null, i32 1) to i64), i1 false) + %1 = bitcast %QUUX* %f to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 getelementptr inbounds (%QUUX, %QUUX* @__QUUX__init, i32 0, i32 0), i64 ptrtoint (%QUUX* getelementptr (%QUUX, %QUUX* null, i32 1) to i64), i1 false) + store i32 0, i32* %main, align 4 + call void @QUUX(%QUUX* %f) + %bar = getelementptr inbounds %foo_struct, %foo_struct* %foo, i32 0, i32 0 + %baz = getelementptr inbounds %bar_struct, %bar_struct* %bar, i32 0, i32 0 + %2 = getelementptr inbounds %QUUX, %QUUX* %f, i32 0, i32 0 + %3 = load i64, i64* %baz, align 4 + %4 = load i8, i8* %2, align 1 + %erase = and i64 %3, -281474976710657 + %5 = zext i8 %4 to i64 + %value = shl i64 %5, 48 + %or = or i64 %erase, %value + store i64 %or, i64* %baz, align 4 + call void @QUUX(%QUUX* %f) + %bar1 = getelementptr inbounds %foo_struct, %foo_struct* %foo, i32 0, i32 0 + %baz2 = getelementptr inbounds %bar_struct, %bar_struct* %bar1, i32 0, i32 0 + %6 = getelementptr inbounds %QUUX, %QUUX* %f, i32 0, i32 0 + %7 = load i64, i64* %baz2, align 4 + %8 = load i8, i8* %6, align 1 + %erase3 = and i64 %7, -1125899906842625 + %9 = zext i8 %8 to i64 + %value4 = shl i64 %9, 50 + %or5 = or i64 %erase3, %value4 + store i64 %or5, i64* %baz2, align 4 + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + + attributes #0 = { argmemonly nofree nounwind willreturn } + "###); +} diff --git a/src/index.rs b/src/index.rs index 11be9a5d1a..cb0fdd9837 100644 --- a/src/index.rs +++ b/src/index.rs @@ -347,6 +347,12 @@ pub enum VariableType { Return, } +impl VariableType { + pub fn is_output(&self) -> bool { + matches!(self, VariableType::Output) + } +} + impl std::fmt::Display for VariableType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/resolver.rs b/src/resolver.rs index 6bf25261a2..a77e99ad46 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -545,7 +545,10 @@ pub trait AnnotationMap { fn get_qualified_name(&self, s: &AstNode) -> Option<&str> { match self.get(s) { - Some(StatementAnnotation::Function { qualified_name, .. }) => Some(qualified_name.as_str()), + Some(StatementAnnotation::Function { qualified_name, .. }) + | Some(StatementAnnotation::Variable { qualified_name, .. }) + | Some(StatementAnnotation::Program { qualified_name, .. }) => Some(qualified_name.as_str()), + _ => self.get_call_name(s), } } diff --git a/tests/correctness/bitaccess.rs b/tests/correctness/bitaccess.rs index 06d6e6728f..a412bf4f6a 100644 --- a/tests/correctness/bitaccess.rs +++ b/tests/correctness/bitaccess.rs @@ -1,5 +1,9 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder +// TODO: Some of these tests are incorrect, because a `default()` call will override the values defined in the +// VAR block. Thus any tests relying on data initialized in the VAR block needs to be updated such that +// the initialization happens in the POU body. However, this won't be any issue once we convert to LIT. + use crate::*; use pretty_assertions::assert_eq; @@ -214,3 +218,125 @@ fn byteaccess_assignment_should_not_override_current_values() { let res: i32 = compile_and_run(prog, &mut crate::MainType::default); assert_eq!(res, 0b0000_0000_1100_0011_1010_1010_0101_0101); } + +#[test] +fn bitaccess_in_output_assignments_with_simple_expression() { + let prog = " + FUNCTION_BLOCK foo + VAR_OUTPUT + OUT_FALSE : BOOL; + OUT_TRUE : BOOL; + END_VAR + + OUT_TRUE := TRUE; + OUT_FALSE := FALSE; + END_FUNCTION_BLOCK + + FUNCTION main : DINT + VAR + a : BYTE; + END_VAR + + VAR_TEMP + foo_instance : foo; + END_VAR + + a := 2#1010_1010; + + // Invert these ~~bitc-~~bits + foo_instance(OUT_TRUE => a.0); + foo_instance(OUT_TRUE => a.2); + foo_instance(OUT_TRUE => a.4); + foo_instance(OUT_TRUE => a.6); + + foo_instance(OUT_FALSE => a.1); + foo_instance(OUT_FALSE => a.3); + foo_instance(OUT_FALSE => a.5); + foo_instance(OUT_FALSE => a.7); + + main := a; + END_FUNCTION"; + + let res: i32 = compile_and_run(prog, &mut crate::MainType::default()); + assert_eq!(res, 0b0101_0101); +} + +#[test] +fn bitaccess_in_output_assignment_with_complexish_expression() { + let prog = " + TYPE foo_struct : STRUCT + bar : bar_struct; + END_STRUCT END_TYPE + + TYPE bar_struct : STRUCT + baz : DINT; // 0000_0000_0000_0000_0000_0000_0000_0000 + END_STRUCT END_TYPE + + FUNCTION_BLOCK QUUX + VAR_OUTPUT + Q : BOOL; + END_VAR + + Q := TRUE; + END_FUNCTION_BLOCK + + FUNCTION main : DINT + VAR + foo : foo_struct; + f : QUUX; + END_VAR + + foo.bar.baz := 0; // ...just to be sure + + + // foo.bar.baz: 0000_0000_0000_0000_0000_0000_0000_0000 + f(Q => foo.bar.baz.%W1.%B1.%X3); // 0000_1000_0000_0000_0000_0000_0000_0000 + f(Q => foo.bar.baz.%W1.%B1.%X1); // 0000_1010_0000_0000_0000_0000_0000_0000 + main := foo.bar.baz; + END_FUNCTION + "; + + let res: i32 = compile_and_run(prog, &mut crate::MainType::default()); + assert_eq!(res, 0b0000_1010_0000_0000_0000_0000_0000_0000); +} + +#[test] +fn bitaccess_in_output_assignment_with_complexish_expression_implicit() { + let prog = " + TYPE foo_struct : STRUCT + bar : bar_struct; + END_STRUCT END_TYPE + + TYPE bar_struct : STRUCT + baz : DINT; // 0000_0000_0000_0000_0000_0000_0000_0000 + END_STRUCT END_TYPE + + FUNCTION_BLOCK QUUX + VAR_INPUT + x : DINT; + END_VAR + VAR_OUTPUT + Q : BOOL; + END_VAR + + Q := TRUE; + END_FUNCTION_BLOCK + + FUNCTION main : DINT + VAR + foo : foo_struct; + f : QUUX; + END_VAR + + foo.bar.baz := 0; // ...just to be sure + + // foo.bar.baz: 0000_0000_0000_0000_0000_0000_0000_0000 + f(x := 0, Q => foo.bar.baz.%W1.%B1.%X3); // 0000_1000_0000_0000_0000_0000_0000_0000 + f(0, foo.bar.baz.%W1.%B1.%X1); // 0000_1010_0000_0000_0000_0000_0000_0000 + main := foo.bar.baz; + END_FUNCTION + "; + + let res: i32 = compile_and_run(prog, &mut crate::MainType::default()); + assert_eq!(res, 0b0000_1010_0000_0000_0000_0000_0000_0000); +}