Skip to content

Commit 075f404

Browse files
authored
feat: Initializer support for struct member fields (#1478)
Adds support for struct initializers of complex data types, with a main focus on polymorphism. Specifically the following use-cases are now supported: Adds support for initalizers in struct member variables, with a main focus on creating virtual tables for polymorphism. Specifically the following use-cases are now supported: ``` VAR_GLOBAL globalVtable : vtable := (value := REF(...)); END_VAR TYPE vtable: STRUCT value : REF_TO ...; // or with a initializer such as `REF_TO DINT := ...` END_STRUCT END_TYPE ``` These initializer can also be nested structs such that `a := (b := (c := REF(d))` is valid. Generally this is achieved by adding fully-qualified variable assignments to the `__init*` function, i.e. the given example would internally resolve to `self.a.b.c := REF(d)`.
1 parent 146e5f9 commit 075f404

20 files changed

+1576
-234
lines changed

compiler/plc_driver/src/pipelines.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,12 @@ impl AnnotatedUnit {
639639
}
640640
}
641641

642+
impl From<AnnotatedUnit> for CompilationUnit {
643+
fn from(value: AnnotatedUnit) -> Self {
644+
value.unit
645+
}
646+
}
647+
642648
/// A project that has been annotated with information about different types and used units
643649
pub struct AnnotatedProject {
644650
pub units: Vec<AnnotatedUnit>,

libs/stdlib/tests/date_time_numeric_functions_tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ fn sub_overflow() {
305305
}
306306

307307
#[test]
308+
#[cfg_attr(target_os = "macos", ignore = "does not work under macos, needs investigation")]
308309
fn mul_signed() {
309310
let src = "
310311
PROGRAM main
@@ -349,6 +350,7 @@ fn mul_signed_overflow() {
349350
}
350351

351352
#[test]
353+
#[cfg_attr(target_os = "macos", ignore = "does not work under macos, needs investigation")]
352354
fn mul_unsigned() {
353355
let src = "
354356
PROGRAM main
@@ -667,6 +669,7 @@ fn div_ltime_unsigned() {
667669
}
668670

669671
#[test]
672+
#[cfg_attr(target_os = "macos", ignore = "does not work under macos, needs investigation")]
670673
fn mul_real() {
671674
let src = "
672675
PROGRAM main
@@ -714,6 +717,7 @@ fn mul_real_overflow() {
714717
}
715718

716719
#[test]
720+
#[cfg_attr(target_os = "macos", ignore = "does not work under macos, needs investigation")]
717721
fn mul_lreal() {
718722
let src = "
719723
PROGRAM main

src/codegen/tests/debug_tests.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ END_FUNCTION
621621
622622
@__struct___init = unnamed_addr constant %struct_ { %inner { [81 x i8] c"Hello\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", i8 1, float 0x400921CAC0000000, [3 x [81 x i8]] [[81 x i8] c"aaaa\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", [81 x i8] c"bbbb\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", [81 x i8] c"cccc\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"], i16 42 }, [3 x %inner] zeroinitializer, [81 x i8] c"Hello\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", i8 1, float 0x400921CAC0000000, [3 x [81 x i8]] [[81 x i8] c"aa\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", [81 x i8] c"bb\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", [81 x i8] c"cc\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"], i16 42 }, !dbg !0
623623
@__inner__init = unnamed_addr constant %inner { [81 x i8] c"Hello\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", i8 1, float 0x400921CAC0000000, [3 x [81 x i8]] [[81 x i8] c"aaaa\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", [81 x i8] c"bbbb\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", [81 x i8] c"cccc\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"], i16 42 }, !dbg !31
624+
@utf08_literal_0 = private unnamed_addr constant [6 x i8] c"Hello\00"
624625
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @__init___Test, i8* null }]
625626
626627
define void @main() !dbg !38 {
@@ -719,13 +720,39 @@ END_FUNCTION
719720
%deref = load %struct_*, %struct_** %self, align 8
720721
%inner = getelementptr inbounds %struct_, %struct_* %deref, i32 0, i32 0
721722
call void @__init_inner(%inner* %inner)
723+
%deref1 = load %struct_*, %struct_** %self, align 8
724+
%s = getelementptr inbounds %struct_, %struct_* %deref1, i32 0, i32 2
725+
%1 = bitcast [81 x i8]* %s to i8*
726+
call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 1 %1, i8* align 1 getelementptr inbounds ([6 x i8], [6 x i8]* @utf08_literal_0, i32 0, i32 0), i32 6, i1 false)
727+
%deref2 = load %struct_*, %struct_** %self, align 8
728+
%b = getelementptr inbounds %struct_, %struct_* %deref2, i32 0, i32 3
729+
store i8 1, i8* %b, align 1
730+
%deref3 = load %struct_*, %struct_** %self, align 8
731+
%r = getelementptr inbounds %struct_, %struct_* %deref3, i32 0, i32 4
732+
store float 0x400921CAC0000000, float* %r, align 4
733+
%deref4 = load %struct_*, %struct_** %self, align 8
734+
%i = getelementptr inbounds %struct_, %struct_* %deref4, i32 0, i32 6
735+
store i16 42, i16* %i, align 2
722736
ret void
723737
}
724738
725739
define void @__init_inner(%inner* %0) {
726740
entry:
727741
%self = alloca %inner*, align 8
728742
store %inner* %0, %inner** %self, align 8
743+
%deref = load %inner*, %inner** %self, align 8
744+
%s = getelementptr inbounds %inner, %inner* %deref, i32 0, i32 0
745+
%1 = bitcast [81 x i8]* %s to i8*
746+
call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 1 %1, i8* align 1 getelementptr inbounds ([6 x i8], [6 x i8]* @utf08_literal_0, i32 0, i32 0), i32 6, i1 false)
747+
%deref1 = load %inner*, %inner** %self, align 8
748+
%b = getelementptr inbounds %inner, %inner* %deref1, i32 0, i32 1
749+
store i8 1, i8* %b, align 1
750+
%deref2 = load %inner*, %inner** %self, align 8
751+
%r = getelementptr inbounds %inner, %inner* %deref2, i32 0, i32 2
752+
store float 0x400921CAC0000000, float* %r, align 4
753+
%deref3 = load %inner*, %inner** %self, align 8
754+
%i = getelementptr inbounds %inner, %inner* %deref3, i32 0, i32 4
755+
store i16 42, i16* %i, align 2
729756
ret void
730757
}
731758

src/codegen/tests/initialization_test/complex_initializers.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -472,11 +472,11 @@ fn nested_initializer_pous() {
472472
%self = alloca %foo*, align 8
473473
store %foo* %0, %foo** %self, align 8
474474
%deref = load %foo*, %foo** %self, align 8
475-
%str_ref = getelementptr inbounds %foo, %foo* %deref, i32 0, i32 0
476-
store [81 x i8]* @str, [81 x i8]** %str_ref, align 8
477-
%deref1 = load %foo*, %foo** %self, align 8
478-
%b = getelementptr inbounds %foo, %foo* %deref1, i32 0, i32 1
475+
%b = getelementptr inbounds %foo, %foo* %deref, i32 0, i32 1
479476
call void @__init_bar(%bar* %b)
477+
%deref1 = load %foo*, %foo** %self, align 8
478+
%str_ref = getelementptr inbounds %foo, %foo* %deref1, i32 0, i32 0
479+
store [81 x i8]* @str, [81 x i8]** %str_ref, align 8
480480
ret void
481481
}
482482
@@ -505,11 +505,11 @@ fn nested_initializer_pous() {
505505
%self = alloca %mainProg*, align 8
506506
store %mainProg* %0, %mainProg** %self, align 8
507507
%deref = load %mainProg*, %mainProg** %self, align 8
508-
%other_ref_to_global = getelementptr inbounds %mainProg, %mainProg* %deref, i32 0, i32 0
509-
store [81 x i8]* @str, [81 x i8]** %other_ref_to_global, align 8
510-
%deref1 = load %mainProg*, %mainProg** %self, align 8
511-
%f = getelementptr inbounds %mainProg, %mainProg* %deref1, i32 0, i32 1
508+
%f = getelementptr inbounds %mainProg, %mainProg* %deref, i32 0, i32 1
512509
call void @__init_foo(%foo* %f)
510+
%deref1 = load %mainProg*, %mainProg** %self, align 8
511+
%other_ref_to_global = getelementptr inbounds %mainProg, %mainProg* %deref1, i32 0, i32 0
512+
store [81 x i8]* @str, [81 x i8]** %other_ref_to_global, align 8
513513
ret void
514514
}
515515
@@ -518,11 +518,11 @@ fn nested_initializer_pous() {
518518
%self = alloca %sideProg*, align 8
519519
store %sideProg* %0, %sideProg** %self, align 8
520520
%deref = load %sideProg*, %sideProg** %self, align 8
521-
%other_ref_to_global = getelementptr inbounds %sideProg, %sideProg* %deref, i32 0, i32 0
522-
store [81 x i8]* @str, [81 x i8]** %other_ref_to_global, align 8
523-
%deref1 = load %sideProg*, %sideProg** %self, align 8
524-
%f = getelementptr inbounds %sideProg, %sideProg* %deref1, i32 0, i32 1
521+
%f = getelementptr inbounds %sideProg, %sideProg* %deref, i32 0, i32 1
525522
call void @__init_foo(%foo* %f)
523+
%deref1 = load %sideProg*, %sideProg** %self, align 8
524+
%other_ref_to_global = getelementptr inbounds %sideProg, %sideProg* %deref1, i32 0, i32 0
525+
store [81 x i8]* @str, [81 x i8]** %other_ref_to_global, align 8
526526
ret void
527527
}
528528

src/index/indexer/user_type_indexer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ impl UserTypeIndexer<'_, '_> {
435435
}
436436

437437
fn index_struct_type(&mut self, name: &str, variables: &[Variable], source: StructSource) {
438-
let scope = self.current_scope();
438+
let scope = Some(name.to_string());
439439
let members = variables
440440
.iter()
441441
.enumerate()
@@ -448,7 +448,7 @@ impl UserTypeIndexer<'_, '_> {
448448
var.initializer.clone(),
449449
member_type,
450450
scope.clone(),
451-
None,
451+
Some(var.name.clone()),
452452
);
453453

454454
let binding = var

src/lowering.rs

Lines changed: 92 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use crate::{
55
use initializers::{get_user_init_fn_name, Init, InitAssignments, Initializers, GLOBAL_SCOPE};
66
use plc_ast::{
77
ast::{
8-
AstFactory, AstNode, AstStatement, CallStatement, CompilationUnit, ConfigVariable, DataType,
9-
LinkageType, PouType,
8+
Assignment, AstFactory, AstNode, AstStatement, CallStatement, CompilationUnit, ConfigVariable,
9+
DataType, LinkageType, PouType, ReferenceExpr,
1010
},
1111
mut_visitor::{AstVisitorMut, WalkerMut},
1212
provider::IdProvider,
@@ -36,7 +36,7 @@ impl InitVisitor {
3636
) -> Vec<CompilationUnit> {
3737
let mut visitor = Self::new(index, unresolvables, id_provider);
3838
// before visiting, we need to collect all candidates for user-defined init functions
39-
units.iter().for_each(|unit| {
39+
units.iter_mut().for_each(|unit| {
4040
visitor.collect_user_init_candidates(unit);
4141
});
4242
// visit all units
@@ -70,7 +70,7 @@ impl InitVisitor {
7070
self.ctxt.scope(old);
7171
}
7272

73-
fn collect_user_init_candidates(&mut self, unit: &CompilationUnit) {
73+
fn collect_user_init_candidates(&mut self, unit: &mut CompilationUnit) {
7474
// collect all candidates for user-defined init functions
7575
for pou in unit.pous.iter().filter(|it| matches!(it.kind, PouType::FunctionBlock | PouType::Program))
7676
{
@@ -80,7 +80,7 @@ impl InitVisitor {
8080
}
8181

8282
for user_type in
83-
unit.user_types.iter().filter(|it| matches!(it.data_type, DataType::StructType { .. }))
83+
unit.user_types.iter_mut().filter(|it| matches!(it.data_type, DataType::StructType { .. }))
8484
{
8585
// add the struct to potential `STRUCT_INIT` candidates
8686
if let Some(name) = user_type.data_type.get_name() {
@@ -284,30 +284,31 @@ impl InitVisitor {
284284
fn update_struct_initializers(&mut self, user_type: &mut plc_ast::ast::UserTypeDeclaration) {
285285
let effective_type =
286286
user_type.data_type.get_name().and_then(|it| self.index.find_effective_type_by_name(it));
287-
if let DataType::StructType { .. } = &user_type.data_type {
287+
if let DataType::StructType { ref mut variables, .. } = &mut user_type.data_type {
288288
let Some(ty) = effective_type else {
289289
return user_type.walk(self);
290290
};
291291
let name = ty.get_name();
292292

293-
let member_inits = self
294-
.index
295-
.get_container_members(name)
296-
.iter()
297-
.filter_map(|var| {
298-
// struct member initializers don't have a qualifier/scope while evaluated in `const_evaluator.rs` and are registered as globals under their data-type name;
299-
// look for member initializers for this struct in the global initializers, remove them and add new entries with the correct qualifier and left-hand-side
300-
self.unresolved_initializers
301-
.get_mut(GLOBAL_SCOPE)
302-
.and_then(|it| it.swap_remove(var.get_type_name()))
303-
.map(|node| (var.get_name(), node))
304-
})
305-
.collect::<Vec<_>>();
306-
307-
for (lhs, init) in member_inits {
308-
// update struct member initializers
309-
self.unresolved_initializers.maybe_insert_initializer(name, Some(lhs), &init);
293+
for variable in variables {
294+
self.unresolved_initializers.maybe_insert_initializer(
295+
name,
296+
Some(&variable.name),
297+
&variable.initializer,
298+
);
299+
300+
// XXX: Very duct-tapey but essentially we now have two initializers, one in the struct datatype
301+
// definition itself (`DataType::StructType { initializer: Some(<arena id>), ... }`) and one in the
302+
// `__init_*` function now. The former is unresolvable because it has the raw initializer, e.g.
303+
// `foo := (a := (b := (c := REF(...))))` whereas the latter is resolvable because it yields something
304+
// like `foo.a.b.c := REF(...)`. Thus we remove the initializer from the struct datatype definition
305+
// as the codegen would otherwise fail at generating them and result in a `Cannot generate values for..`
306+
// Literals and references are ignored however, since they are resolvable and / or constant.
307+
if variable.initializer.as_ref().is_some_and(|opt| !opt.is_literal() && !opt.is_reference()) {
308+
variable.initializer = None;
309+
}
310310
}
311+
311312
// add container to keys if not already present
312313
self.unresolved_initializers.maybe_insert_initializer(name, None, &user_type.initializer);
313314
}
@@ -429,18 +430,76 @@ fn create_member_reference(ident: &str, id_provider: IdProvider, base: Option<As
429430
create_member_reference_with_location(ident, id_provider, base, SourceLocation::internal())
430431
}
431432

432-
fn create_assignment_if_necessary(
433-
lhs_ident: &str,
434-
base_ident: Option<&str>,
433+
/// Takes some expression such as `bar := (baz := (qux := ADR(val)), baz2 := (qux := ADR(val)))` returning all final
434+
/// assignment paths such as [`bar.baz.qux := ADR(val)`, `bar.baz2.qux := ADR(val)`].
435+
fn create_assignment_paths(node: &AstNode, id_provider: IdProvider) -> Vec<Vec<AstNode>> {
436+
match node.get_stmt() {
437+
AstStatement::Assignment(Assignment { left, right }) => {
438+
let mut result = create_assignment_paths(right, id_provider.clone());
439+
for inner in result.iter_mut() {
440+
inner.insert(0, left.as_ref().clone());
441+
}
442+
result
443+
}
444+
AstStatement::ExpressionList(nodes) => {
445+
let mut result = vec![];
446+
for node in nodes {
447+
let inner = create_assignment_paths(node, id_provider.clone());
448+
result.extend(inner);
449+
}
450+
result
451+
}
452+
AstStatement::ParenExpression(node) => create_assignment_paths(node, id_provider),
453+
_ => vec![vec![node.clone()]],
454+
}
455+
}
456+
457+
/// Takes some expression such as `foo : FooStruct := (bar := (baz := (qux := ADR(val)), baz2 := (qux := ADR(val))));`
458+
/// and returns assignments of form [`foo.bar.baz.qux := ADR(val)`, `foo.bar.baz2.qux := ADR(val)`].
459+
fn create_assignments_from_initializer(
460+
var_ident: &str,
461+
self_ident: Option<&str>,
435462
rhs: &Option<AstNode>,
436463
mut id_provider: IdProvider,
437-
) -> Option<AstNode> {
438-
let lhs = create_member_reference(
439-
lhs_ident,
440-
id_provider.clone(),
441-
base_ident.map(|id| create_member_reference(id, id_provider.clone(), None)),
442-
);
443-
rhs.as_ref().map(|node| AstFactory::create_assignment(lhs, node.to_owned(), id_provider.next_id()))
464+
) -> Vec<AstNode> {
465+
let Some(initializer) = rhs else {
466+
return Vec::new();
467+
};
468+
469+
let mut result = vec![];
470+
for mut path in create_assignment_paths(initializer, id_provider.clone()) {
471+
path.insert(0, create_member_reference(var_ident, id_provider.clone(), None));
472+
if self_ident.is_some() {
473+
path.insert(0, create_member_reference("self", id_provider.clone(), None));
474+
}
475+
476+
let right = path.pop().expect("must have at least one node in the path");
477+
let mut left = path.pop().expect("must have at least one node in the path");
478+
479+
for node in path.into_iter().rev() {
480+
insert_base_node(&mut left, node);
481+
}
482+
483+
result.push(AstFactory::create_assignment(left, right, id_provider.next_id()));
484+
}
485+
486+
result
487+
}
488+
489+
/// Inserts a new base node into the member reference chain. For example a call such as `insert_base_node("b.c", a")`
490+
/// will yield `a.b.c`.
491+
fn insert_base_node(member: &mut AstNode, new_base: AstNode) {
492+
match &mut member.stmt {
493+
AstStatement::ReferenceExpr(ReferenceExpr { base, .. }) => match base {
494+
Some(inner) => insert_base_node(inner, new_base),
495+
None => {
496+
// We hit the end of the chain, simply replace the base (which must be None) with the new one
497+
base.replace(Box::new(new_base));
498+
}
499+
},
500+
501+
_ => panic!("invalid function call, expected a member reference"),
502+
}
444503
}
445504

446505
fn create_ref_assignment(

0 commit comments

Comments
 (0)