Skip to content

Commit 79abdb6

Browse files
authored
feat: Introduce REF= and REFERENCE_TO (#1251)
This PR introduces two new keywords, namely `REF=` and `REFERENCE TO`: * `REF=` is essentially syntactic sugar for an assignment where the right-hand side is wrapped in a `REF()` function call. Therefore `foo := REF(bar)` and `foo REF= bar` are equivalent. * `REFERENCE TO` is identical to `REF_TO` with the exception of being auto-deref by default. A variable `foo` declared as `REFERENCE TO` will therefore auto-deref on assignments, i.e. `foo := 5` is equivalent to `foo^ := 5`. More information on CodeSys' [REF=](https://help.codesys.com/api-content/2/codesys/3.5.12.0/en/_cds_ref_assignment/) and [REFERENCE TO](https://help.codesys.com/api-content/2/codesys/3.5.12.0/en/_cds_datatype_reference/) documentation pages.
1 parent 4aea520 commit 79abdb6

File tree

45 files changed

+971
-54
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+971
-54
lines changed

compiler/plc_ast/src/ast.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,9 @@ pub enum DataType {
487487
PointerType {
488488
name: Option<String>,
489489
referenced_type: Box<DataTypeDeclaration>,
490+
auto_deref: bool,
491+
/// Denotes whether the variable was declared as `REFERENCE TO`, e.g. `foo : REFERENCE TO DINT`
492+
is_reference_to: bool,
490493
},
491494
StringType {
492495
name: Option<String>,
@@ -596,11 +599,14 @@ pub struct AstNode {
596599
#[derive(Debug, Clone, PartialEq)]
597600
pub enum AstStatement {
598601
EmptyStatement(EmptyStatement),
599-
// a placeholder that indicates a default value of a datatype
602+
603+
// A placeholder which indicates a default value of a datatype
600604
DefaultValue(DefaultValue),
605+
601606
// Literals
602607
Literal(AstLiteral),
603608
MultipliedStatement(MultipliedStatement),
609+
604610
// Expressions
605611
ReferenceExpr(ReferenceExpr),
606612
Identifier(String),
@@ -612,15 +618,17 @@ pub enum AstStatement {
612618
ParenExpression(Box<AstNode>),
613619
RangeStatement(RangeStatement),
614620
VlaRangeStatement,
615-
// Assignment
621+
622+
// TODO: Merge these variants with a `kind` field?
623+
// Assignments
616624
Assignment(Assignment),
617-
// OutputAssignment
618625
OutputAssignment(Assignment),
619-
//Call Statement
626+
RefAssignment(Assignment),
627+
620628
CallStatement(CallStatement),
629+
621630
// Control Statements
622631
ControlStatement(AstControlStatement),
623-
624632
CaseCondition(Box<AstNode>),
625633
ExitStatement(()),
626634
ContinueStatement(()),
@@ -661,6 +669,9 @@ impl Debug for AstNode {
661669
AstStatement::OutputAssignment(Assignment { left, right }) => {
662670
f.debug_struct("OutputAssignment").field("left", left).field("right", right).finish()
663671
}
672+
AstStatement::RefAssignment(Assignment { left, right }) => {
673+
f.debug_struct("ReferenceAssignment").field("left", left).field("right", right).finish()
674+
}
664675
AstStatement::CallStatement(CallStatement { operator, parameters }) => f
665676
.debug_struct("CallStatement")
666677
.field("operator", operator)
@@ -1319,6 +1330,19 @@ impl AstFactory {
13191330
)
13201331
}
13211332

1333+
// TODO: Merge `create_assignment`, `create_output_assignment` and `create_ref_assignment`
1334+
// once the the Assignment AstStatements have been merged and a `kind` field is available
1335+
// I.e. something like `AstStatement::Assignment { data, kind: AssignmentKind { Normal, Output, Reference } }
1336+
// and then fn create_assignment(kind: AssignmentKind, ...)
1337+
pub fn create_ref_assignment(left: AstNode, right: AstNode, id: AstId) -> AstNode {
1338+
let location = left.location.span(&right.location);
1339+
AstNode::new(
1340+
AstStatement::RefAssignment(Assignment { left: Box::new(left), right: Box::new(right) }),
1341+
id,
1342+
location,
1343+
)
1344+
}
1345+
13221346
pub fn create_member_reference(member: AstNode, base: Option<AstNode>, id: AstId) -> AstNode {
13231347
let location = base
13241348
.as_ref()

compiler/plc_ast/src/visitor.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,15 @@ pub trait AstVisitor: Sized {
312312
stmt.walk(self)
313313
}
314314

315+
/// Visits an `RefAssignment` node.
316+
/// Make sure to call `walk` on the `Assignment` node to visit its children.
317+
/// # Arguments
318+
/// * `stmt` - The unwraped, typed `Assignment` node to visit.
319+
/// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
320+
fn visit_ref_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
321+
stmt.walk(self)
322+
}
323+
315324
/// Visits a `CallStatement` node.
316325
/// Make sure to call `walk` on the `CallStatement` node to visit its children.
317326
/// # Arguments
@@ -556,6 +565,7 @@ impl Walker for AstNode {
556565
AstStatement::VlaRangeStatement => visitor.visit_vla_range_statement(node),
557566
AstStatement::Assignment(stmt) => visitor.visit_assignment(stmt, node),
558567
AstStatement::OutputAssignment(stmt) => visitor.visit_output_assignment(stmt, node),
568+
AstStatement::RefAssignment(stmt) => visitor.visit_ref_assignment(stmt, node),
559569
AstStatement::CallStatement(stmt) => visitor.visit_call_statement(stmt, node),
560570
AstStatement::ControlStatement(stmt) => visitor.visit_control_statement(stmt, node),
561571
AstStatement::CaseCondition(stmt) => visitor.visit_case_condition(stmt, node),

compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ lazy_static! {
199199
E095, Error, include_str!("./error_codes/E095.md"), // Action call without `()`
200200
E096, Warning, include_str!("./error_codes/E096.md"), // Integer Condition
201201
E097, Error, include_str!("./error_codes/E097.md"), // Invalid Array Range
202+
E098, Error, include_str!("./error_codes/E098.md"), // Invalid `REF=` assignment
203+
E099, Error, include_str!("./error_codes/E099.md"), // Invalid `REFERENCE TO` declaration
202204
);
203205
}
204206

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Invalid REF= assignment
2+
3+
`REF=` assignments are considered valid if the left-hand side of the assignment is a pointer variable
4+
and the right-hand side is a variable of the type that is being referenced.
5+
6+
For example assignments such as the following are invalid
7+
8+
```smalltalk
9+
VAR
10+
foo : DINT;
11+
bar : DINT;
12+
qux : SINT;
13+
refFoo : REFERENCE TO DINT;
14+
END_VAR
15+
16+
refFoo REF= 5; // `5` is not a variable
17+
foo REF= bar; // `foo` is not a pointer
18+
refFoo REF= qux; // `refFoo` and `qux` have different types, DINT vs SINT
19+
```
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Invalid `REFERENCE TO` declaration
2+
3+
`REFERENCE TO` variable declarations are considered valid if the referenced type is not of the following form
4+
* `foo : REFERENCE TO REFERENCE TO (* ... *)`
5+
* `foo : ARRAY[...] OF REFERENCE TO (* ... *)`
6+
* `foo : REF_TO REFERENCE TO (* ... *)`
7+
8+
Furthermore `REFERENCE_TO` variables must not be initialized in their declaration, e.g. `foo : REFERENCE TO DINT := bar`.

src/codegen/generators/expression_generator.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2507,14 +2507,13 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
25072507
/// - `access` the ReferenceAccess of the reference to generate
25082508
/// - `base` the "previous" segment of an optional qualified reference-access
25092509
/// - `original_expression` the original ast-statement used to report Diagnostics
2510-
fn generate_reference_expression(
2510+
pub(crate) fn generate_reference_expression(
25112511
&self,
25122512
access: &ReferenceAccess,
25132513
base: Option<&AstNode>,
25142514
original_expression: &AstNode,
25152515
) -> Result<ExpressionValue<'ink>, Diagnostic> {
25162516
match (access, base) {
2517-
25182517
// expressions like `base.member`, or just `member`
25192518
(ReferenceAccess::Member(member), base) => {
25202519
let base_value = base.map(|it| self.generate_expression_value(it)).transpose()?;

src/codegen/generators/statement_generator.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> {
125125
AstStatement::Assignment(data, ..) => {
126126
self.generate_assignment_statement(&data.left, &data.right)?;
127127
}
128-
128+
AstStatement::RefAssignment(data, ..) => {
129+
self.generate_ref_assignment(&data.left, &data.right)?;
130+
}
129131
AstStatement::ControlStatement(ctl_statement, ..) => {
130132
self.generate_control_statement(ctl_statement)?
131133
}
@@ -234,6 +236,31 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> {
234236
}
235237
}
236238

239+
/// Generates IR for a `REF=` assignment, which is syntactic sugar for an assignment where the
240+
/// right-hand side is wrapped in a `REF(...)` call. Specifically `foo REF= bar` and
241+
/// `foo := REF(bar)` are the same.
242+
///
243+
/// Note: Although somewhat similar to the [`generate_assignment_statement`] function, we can't
244+
/// apply the code here because the left side of a `REF=` assignment is flagged as auto-deref.
245+
/// For `REF=` assignments we don't want (and can't) deref without generating incorrect IR.
246+
pub fn generate_ref_assignment(&self, left: &AstNode, right: &AstNode) -> Result<(), Diagnostic> {
247+
let exp = self.create_expr_generator();
248+
let ref_builtin = self.index.get_builtin_function("REF").expect("REF must exist");
249+
250+
let AstStatement::ReferenceExpr(data) = &left.stmt else {
251+
unreachable!("should be covered by a validation")
252+
};
253+
254+
let left_ptr_val = {
255+
let expr = exp.generate_reference_expression(&data.access, data.base.as_deref(), left)?;
256+
expr.get_basic_value_enum().into_pointer_value()
257+
};
258+
let right_expr_val = ref_builtin.codegen(&exp, &[&right], right.get_location())?;
259+
260+
self.llvm.builder.build_store(left_ptr_val, right_expr_val.get_basic_value_enum());
261+
Ok(())
262+
}
263+
237264
/// generates an assignment statement _left_ := _right_
238265
///
239266
/// `left_statement` the left side of the assignment

src/codegen/tests/statement_codegen_test.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,127 @@ fn floating_point_type_casting() {
184184

185185
insta::assert_snapshot!(result);
186186
}
187+
188+
#[test]
189+
fn ref_assignment() {
190+
let result = codegen(
191+
r#"
192+
FUNCTION main
193+
VAR
194+
a : REF_TO DINT;
195+
b : DINT;
196+
END_VAR
197+
a REF= b;
198+
END_PROGRAM
199+
"#,
200+
);
201+
202+
insta::assert_snapshot!(result, @r###"
203+
; ModuleID = 'main'
204+
source_filename = "main"
205+
206+
define void @main() section "fn-$RUSTY$main:v" {
207+
entry:
208+
%a = alloca i32*, align 8
209+
%b = alloca i32, align 4
210+
store i32* null, i32** %a, align 8
211+
store i32 0, i32* %b, align 4
212+
store i32* %b, i32** %a, align 8
213+
ret void
214+
}
215+
"###);
216+
}
217+
218+
#[test]
219+
fn reference_to_assignment() {
220+
let auto_deref = codegen(
221+
r#"
222+
FUNCTION main
223+
VAR
224+
a : REFERENCE TO DINT;
225+
END_VAR
226+
a := 5;
227+
END_FUNCTION
228+
"#,
229+
);
230+
231+
let manual_deref = codegen(
232+
r#"
233+
FUNCTION main
234+
VAR
235+
a : REF_TO DINT;
236+
END_VAR
237+
a^ := 5;
238+
END_FUNCTION
239+
"#,
240+
);
241+
242+
// We want to assert that `a := 5` and `a^ := 5` yield identical IR
243+
assert_eq!(auto_deref, manual_deref);
244+
245+
insta::assert_snapshot!(auto_deref, @r###"
246+
; ModuleID = 'main'
247+
source_filename = "main"
248+
249+
define void @main() section "fn-$RUSTY$main:v" {
250+
entry:
251+
%a = alloca i32*, align 8
252+
store i32* null, i32** %a, align 8
253+
%deref = load i32*, i32** %a, align 8
254+
store i32 5, i32* %deref, align 4
255+
ret void
256+
}
257+
"###);
258+
}
259+
260+
#[test]
261+
fn reference_to_string_assignment() {
262+
let auto_deref = codegen(
263+
r#"
264+
FUNCTION main
265+
VAR
266+
a : REFERENCE TO STRING;
267+
END_VAR
268+
269+
a := 'hello';
270+
END_FUNCTION
271+
"#,
272+
);
273+
274+
let manual_deref = codegen(
275+
r#"
276+
FUNCTION main
277+
VAR
278+
a : REF_TO STRING;
279+
END_VAR
280+
281+
a^ := 'hello';
282+
END_FUNCTION
283+
"#,
284+
);
285+
286+
// We want to assert that `a := 'hello'` and `a^ := 'hello'` yield identical IR
287+
assert_eq!(auto_deref, manual_deref);
288+
289+
insta::assert_snapshot!(auto_deref, @r###"
290+
; ModuleID = 'main'
291+
source_filename = "main"
292+
293+
@utf08_literal_0 = private unnamed_addr constant [6 x i8] c"hello\00"
294+
295+
define void @main() section "fn-$RUSTY$main:v" {
296+
entry:
297+
%a = alloca [81 x i8]*, align 8
298+
store [81 x i8]* null, [81 x i8]** %a, align 8
299+
%deref = load [81 x i8]*, [81 x i8]** %a, align 8
300+
%0 = bitcast [81 x i8]* %deref to i8*
301+
call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 1 %0, i8* align 1 getelementptr inbounds ([6 x i8], [6 x i8]* @utf08_literal_0, i32 0, i32 0), i32 6, i1 false)
302+
ret void
303+
}
304+
305+
; Function Attrs: argmemonly nofree nounwind willreturn
306+
declare void @llvm.memcpy.p0i8.p0i8.i32(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i32, i1 immarg) #0
307+
308+
attributes #0 = { argmemonly nofree nounwind willreturn }
309+
"###);
310+
}

src/index.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,7 @@ impl Index {
11301130
if segments.is_empty() {
11311131
return None;
11321132
}
1133+
11331134
//For the first element, if the context does not contain that element, it is possible that the element is also a global variable
11341135
let init = match context {
11351136
Some(context) => self

src/index/tests/index_tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,7 @@ fn pointer_and_in_out_pointer_should_not_conflict() {
12531253
name: "__main_x".to_string(),
12541254
inner_type_name: "INT".to_string(),
12551255
auto_deref: false,
1256+
is_reference_to: false,
12561257
}
12571258
);
12581259

@@ -1264,6 +1265,7 @@ fn pointer_and_in_out_pointer_should_not_conflict() {
12641265
name: "__auto_pointer_to_INT".to_string(),
12651266
inner_type_name: "INT".to_string(),
12661267
auto_deref: true,
1268+
is_reference_to: false,
12671269
}
12681270
);
12691271
}
@@ -1303,6 +1305,7 @@ fn pointer_and_in_out_pointer_should_not_conflict_2() {
13031305
name: "__main_x".to_string(),
13041306
inner_type_name: "INT".to_string(),
13051307
auto_deref: false,
1308+
is_reference_to: false,
13061309
}
13071310
);
13081311

@@ -1314,6 +1317,7 @@ fn pointer_and_in_out_pointer_should_not_conflict_2() {
13141317
name: "__auto_pointer_to_INT".to_string(),
13151318
inner_type_name: "INT".to_string(),
13161319
auto_deref: true,
1320+
is_reference_to: false,
13171321
}
13181322
);
13191323
}

0 commit comments

Comments
 (0)