Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce REF= and REFERENCE_TO #1251

Merged
merged 28 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
91f485f
Initial commit w/ segfault 😎
volsa Jun 19, 2024
963064b
Create ADR call in parser
volsa Jun 20, 2024
5482c54
Merge branch 'master' of https://github.com/plc-lang/rusty into volsa…
volsa Jun 20, 2024
9486255
Remove ReferenceAssignment node
volsa Jun 20, 2024
c79d74e
Initial commit
volsa Jun 24, 2024
d3a9e69
Merge branch 'volsa/ref-assignment' into volsa/referenceto
volsa Jun 24, 2024
acd5770
Introduce RefAssignment node, adjust codegen
volsa Jun 25, 2024
525701d
Merge branch 'volsa/ref-assignment' into volsa/referenceto
volsa Jun 25, 2024
29f7e96
polish
volsa Jun 25, 2024
6496fa1
wip
volsa Jun 25, 2024
d4b0b00
Add REF= validation
volsa Jun 26, 2024
75133b9
add is_reference_to field
volsa Jun 26, 2024
b0861e5
Add unit test
volsa Jun 26, 2024
0e6d595
Validation
volsa Jun 27, 2024
1ce7bc2
Merge branch 'master' into volsa/referenceto
volsa Jun 27, 2024
65e32ff
Polish
volsa Jun 28, 2024
04a3879
further (failing) tests
volsa Jun 28, 2024
d946dbd
Merge branch 'master' of https://github.com/plc-lang/rusty into volsa…
volsa Jun 28, 2024
1cccae8
Merge branch 'volsa/referenceto' of https://github.com/plc-lang/rusty…
volsa Jun 28, 2024
a991a8c
polish
volsa Jul 1, 2024
64b759a
convert correctness tests to lit
volsa Jul 1, 2024
3f74eac
Feedback
volsa Jul 3, 2024
892f676
Update E099.md
volsa Jul 4, 2024
39da064
Add TODO for non-blocking issue
volsa Jul 4, 2024
88f2c21
Merge branch 'master' into volsa/referenceto
volsa Jul 9, 2024
180e51b
Feedback
volsa Jul 9, 2024
05ee69f
Merge branch 'volsa/referenceto' of https://github.com/plc-lang/rusty…
volsa Jul 9, 2024
1ccc09d
Delete tests/lit/single/pointer/referenceto_variable_uninitialized_ad…
volsa Jul 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions compiler/plc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,9 @@ pub enum DataType {
PointerType {
name: Option<String>,
referenced_type: Box<DataTypeDeclaration>,
auto_deref: bool,
/// Denotes whether the variable was declared as `REFERENCE TO`, e.g. `foo : REFERENCE TO DINT`
is_reference_to: bool,
},
StringType {
name: Option<String>,
Expand Down Expand Up @@ -596,11 +599,14 @@ pub struct AstNode {
#[derive(Debug, Clone, PartialEq)]
pub enum AstStatement {
EmptyStatement(EmptyStatement),
// a placeholder that indicates a default value of a datatype

// A placeholder which indicates a default value of a datatype
DefaultValue(DefaultValue),

// Literals
Literal(AstLiteral),
MultipliedStatement(MultipliedStatement),

// Expressions
ReferenceExpr(ReferenceExpr),
Identifier(String),
Expand All @@ -612,15 +618,17 @@ pub enum AstStatement {
ParenExpression(Box<AstNode>),
RangeStatement(RangeStatement),
VlaRangeStatement,
// Assignment

// TODO: Merge these variants with a `kind` field?
// Assignments
Assignment(Assignment),
// OutputAssignment
OutputAssignment(Assignment),
//Call Statement
RefAssignment(Assignment),

CallStatement(CallStatement),

// Control Statements
ControlStatement(AstControlStatement),

CaseCondition(Box<AstNode>),
ExitStatement(()),
ContinueStatement(()),
Expand Down Expand Up @@ -661,6 +669,9 @@ impl Debug for AstNode {
AstStatement::OutputAssignment(Assignment { left, right }) => {
f.debug_struct("OutputAssignment").field("left", left).field("right", right).finish()
}
AstStatement::RefAssignment(Assignment { left, right }) => {
f.debug_struct("ReferenceAssignment").field("left", left).field("right", right).finish()
}
AstStatement::CallStatement(CallStatement { operator, parameters }) => f
.debug_struct("CallStatement")
.field("operator", operator)
Expand Down Expand Up @@ -1319,6 +1330,19 @@ impl AstFactory {
)
}

// TODO: Merge `create_assignment`, `create_output_assignment` and `create_ref_assignment`
// once the the Assignment AstStatements have been merged and a `kind` field is available
// I.e. something like `AstStatement::Assignment { data, kind: AssignmentKind { Normal, Output, Reference } }
// and then fn create_assignment(kind: AssignmentKind, ...)
pub fn create_ref_assignment(left: AstNode, right: AstNode, id: AstId) -> AstNode {
let location = left.location.span(&right.location);
AstNode::new(
AstStatement::RefAssignment(Assignment { left: Box::new(left), right: Box::new(right) }),
id,
location,
)
}

pub fn create_member_reference(member: AstNode, base: Option<AstNode>, id: AstId) -> AstNode {
let location = base
.as_ref()
Expand Down
10 changes: 10 additions & 0 deletions compiler/plc_ast/src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,15 @@ pub trait AstVisitor: Sized {
stmt.walk(self)
}

/// Visits an `RefAssignment` node.
/// Make sure to call `walk` on the `Assignment` node to visit its children.
/// # Arguments
/// * `stmt` - The unwraped, typed `Assignment` node to visit.
/// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId
fn visit_ref_assignment(&mut self, stmt: &Assignment, _node: &AstNode) {
stmt.walk(self)
}

/// Visits a `CallStatement` node.
/// Make sure to call `walk` on the `CallStatement` node to visit its children.
/// # Arguments
Expand Down Expand Up @@ -556,6 +565,7 @@ impl Walker for AstNode {
AstStatement::VlaRangeStatement => visitor.visit_vla_range_statement(node),
AstStatement::Assignment(stmt) => visitor.visit_assignment(stmt, node),
AstStatement::OutputAssignment(stmt) => visitor.visit_output_assignment(stmt, node),
AstStatement::RefAssignment(stmt) => visitor.visit_ref_assignment(stmt, node),
AstStatement::CallStatement(stmt) => visitor.visit_call_statement(stmt, node),
AstStatement::ControlStatement(stmt) => visitor.visit_control_statement(stmt, node),
AstStatement::CaseCondition(stmt) => visitor.visit_case_condition(stmt, node),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ lazy_static! {
E095, Error, include_str!("./error_codes/E095.md"), // Action call without `()`
E096, Warning, include_str!("./error_codes/E096.md"), // Integer Condition
E097, Error, include_str!("./error_codes/E097.md"), // Invalid Array Range
E098, Error, include_str!("./error_codes/E098.md"), // Invalid `REF=` assignment
E099, Error, include_str!("./error_codes/E099.md"), // Invalid `REFERENCE TO` declaration
);
}

Expand Down
19 changes: 19 additions & 0 deletions compiler/plc_diagnostics/src/diagnostics/error_codes/E098.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Invalid REF= assignment

`REF=` assignments are considered valid if the left-hand side of the assignment is a pointer variable
and the right-hand side is a variable of the type that is being referenced.

For example assignments such as the following are invalid

```smalltalk
VAR
foo : DINT;
bar : DINT;
qux : SINT;
refFoo : REFERENCE TO DINT;
END_VAR

refFoo REF= 5; // `5` is not a variable
foo REF= bar; // `foo` is not a pointer
refFoo REF= qux; // `refFoo` and `qux` have different types, DINT vs SINT
```
8 changes: 8 additions & 0 deletions compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Invalid `REFERENCE TO` declaration

`REFERENCE TO` variable declarations are considered valid if the referenced type is not of the following form
* `foo : REFERENCE TO REFERENCE TO (* ... *)`
* `foo : ARRAY[...] OF REFERENCE TO (* ... *)`
* `foo : REF_TO REFERENCE TO (* ... *)`

Furthermore `REFERENCE_TO` variables must not be initialized in their declaration, e.g. `foo : REFERENCE TO DINT := bar`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi, this is not entirely correct but will be addressed in the aliasing PR later on

3 changes: 1 addition & 2 deletions src/codegen/generators/expression_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2507,14 +2507,13 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
/// - `access` the ReferenceAccess of the reference to generate
/// - `base` the "previous" segment of an optional qualified reference-access
/// - `original_expression` the original ast-statement used to report Diagnostics
fn generate_reference_expression(
pub(crate) fn generate_reference_expression(
&self,
access: &ReferenceAccess,
base: Option<&AstNode>,
original_expression: &AstNode,
) -> Result<ExpressionValue<'ink>, Diagnostic> {
match (access, base) {

// expressions like `base.member`, or just `member`
(ReferenceAccess::Member(member), base) => {
let base_value = base.map(|it| self.generate_expression_value(it)).transpose()?;
Expand Down
29 changes: 28 additions & 1 deletion src/codegen/generators/statement_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> {
AstStatement::Assignment(data, ..) => {
self.generate_assignment_statement(&data.left, &data.right)?;
}

AstStatement::RefAssignment(data, ..) => {
self.generate_ref_assignment(&data.left, &data.right)?;
}
AstStatement::ControlStatement(ctl_statement, ..) => {
self.generate_control_statement(ctl_statement)?
}
Expand Down Expand Up @@ -234,6 +236,31 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> {
}
}

/// Generates IR for a `REF=` assignment, which is syntactic sugar for an assignment where the
/// right-hand side is wrapped in a `REF(...)` call. Specifically `foo REF= bar` and
/// `foo := REF(bar)` are the same.
///
/// Note: Although somewhat similar to the [`generate_assignment_statement`] function, we can't
/// apply the code here because the left side of a `REF=` assignment is flagged as auto-deref.
/// For `REF=` assignments we don't want (and can't) deref without generating incorrect IR.
pub fn generate_ref_assignment(&self, left: &AstNode, right: &AstNode) -> Result<(), Diagnostic> {
let exp = self.create_expr_generator();
let ref_builtin = self.index.get_builtin_function("REF").expect("REF must exist");

let AstStatement::ReferenceExpr(data) = &left.stmt else {
unreachable!("should be covered by a validation")
};

let left_ptr_val = {
let expr = exp.generate_reference_expression(&data.access, data.base.as_deref(), left)?;
expr.get_basic_value_enum().into_pointer_value()
};
let right_expr_val = ref_builtin.codegen(&exp, &[&right], right.get_location())?;

self.llvm.builder.build_store(left_ptr_val, right_expr_val.get_basic_value_enum());
Ok(())
}

/// generates an assignment statement _left_ := _right_
///
/// `left_statement` the left side of the assignment
Expand Down
124 changes: 124 additions & 0 deletions src/codegen/tests/statement_codegen_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,127 @@ fn floating_point_type_casting() {

insta::assert_snapshot!(result);
}

#[test]
fn ref_assignment() {
let result = codegen(
r#"
FUNCTION main
VAR
a : REF_TO DINT;
b : DINT;
END_VAR
a REF= b;
END_PROGRAM
"#,
);

insta::assert_snapshot!(result, @r###"
; ModuleID = 'main'
source_filename = "main"

define void @main() section "fn-$RUSTY$main:v" {
entry:
%a = alloca i32*, align 8
%b = alloca i32, align 4
store i32* null, i32** %a, align 8
store i32 0, i32* %b, align 4
store i32* %b, i32** %a, align 8
ret void
}
"###);
}

#[test]
fn reference_to_assignment() {
let auto_deref = codegen(
r#"
FUNCTION main
VAR
a : REFERENCE TO DINT;
END_VAR
a := 5;
END_FUNCTION
mhasel marked this conversation as resolved.
Show resolved Hide resolved
"#,
);

let manual_deref = codegen(
r#"
FUNCTION main
VAR
a : REF_TO DINT;
END_VAR
a^ := 5;
END_FUNCTION
"#,
);

// We want to assert that `a := 5` and `a^ := 5` yield identical IR
assert_eq!(auto_deref, manual_deref);

insta::assert_snapshot!(auto_deref, @r###"
; ModuleID = 'main'
source_filename = "main"

define void @main() section "fn-$RUSTY$main:v" {
entry:
%a = alloca i32*, align 8
store i32* null, i32** %a, align 8
%deref = load i32*, i32** %a, align 8
store i32 5, i32* %deref, align 4
ret void
}
"###);
}

#[test]
fn reference_to_string_assignment() {
let auto_deref = codegen(
r#"
FUNCTION main
VAR
a : REFERENCE TO STRING;
END_VAR

a := 'hello';
END_FUNCTION
"#,
);

let manual_deref = codegen(
r#"
FUNCTION main
VAR
a : REF_TO STRING;
END_VAR

a^ := 'hello';
END_FUNCTION
"#,
);

// We want to assert that `a := 'hello'` and `a^ := 'hello'` yield identical IR
assert_eq!(auto_deref, manual_deref);

insta::assert_snapshot!(auto_deref, @r###"
; ModuleID = 'main'
source_filename = "main"

@utf08_literal_0 = private unnamed_addr constant [6 x i8] c"hello\00"

define void @main() section "fn-$RUSTY$main:v" {
entry:
%a = alloca [81 x i8]*, align 8
store [81 x i8]* null, [81 x i8]** %a, align 8
%deref = load [81 x i8]*, [81 x i8]** %a, align 8
%0 = bitcast [81 x i8]* %deref to i8*
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)
ret void
}

; Function Attrs: argmemonly nofree nounwind willreturn
declare void @llvm.memcpy.p0i8.p0i8.i32(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i32, i1 immarg) #0

attributes #0 = { argmemonly nofree nounwind willreturn }
"###);
}
1 change: 1 addition & 0 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,7 @@ impl Index {
if segments.is_empty() {
return None;
}

//For the first element, if the context does not contain that element, it is possible that the element is also a global variable
let init = match context {
Some(context) => self
Expand Down
4 changes: 4 additions & 0 deletions src/index/tests/index_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,7 @@ fn pointer_and_in_out_pointer_should_not_conflict() {
name: "__main_x".to_string(),
inner_type_name: "INT".to_string(),
auto_deref: false,
is_reference_to: false,
}
);

Expand All @@ -1264,6 +1265,7 @@ fn pointer_and_in_out_pointer_should_not_conflict() {
name: "__auto_pointer_to_INT".to_string(),
inner_type_name: "INT".to_string(),
auto_deref: true,
is_reference_to: false,
}
);
}
Expand Down Expand Up @@ -1303,6 +1305,7 @@ fn pointer_and_in_out_pointer_should_not_conflict_2() {
name: "__main_x".to_string(),
inner_type_name: "INT".to_string(),
auto_deref: false,
is_reference_to: false,
}
);

Expand All @@ -1314,6 +1317,7 @@ fn pointer_and_in_out_pointer_should_not_conflict_2() {
name: "__auto_pointer_to_INT".to_string(),
inner_type_name: "INT".to_string(),
auto_deref: true,
is_reference_to: false,
}
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ UserTypeDeclaration {
referenced_type: DataTypeReference {
referenced_type: "__foo_inline_pointer_",
},
auto_deref: false,
is_reference_to: false,
},
initializer: None,
scope: Some(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ UserTypeDeclaration {
referenced_type: DataTypeReference {
referenced_type: "INT",
},
auto_deref: false,
is_reference_to: false,
},
initializer: None,
scope: Some(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ UserTypeDeclaration {
referenced_type: DataTypeReference {
referenced_type: "INT",
},
auto_deref: false,
is_reference_to: false,
},
initializer: None,
scope: Some(
Expand Down
Loading
Loading