diff --git a/book/src/libraries/api_lib_guide.md b/book/src/libraries/api_lib_guide.md index 880316beaf3..15d15acb51d 100644 --- a/book/src/libraries/api_lib_guide.md +++ b/book/src/libraries/api_lib_guide.md @@ -142,8 +142,6 @@ void Count(CountStruct* countInst) { } ``` - - #### 2.2.3 Return values A `FUNCTION` defines a return value in the signature, while a `FUNCTION_BLOCK` relies on `VAR_OUTPUT` definitions. @@ -334,6 +332,67 @@ pub struct myStruct { ``` ### 2.5 `FUNCTION_BLOCK` initialization -Not yet implemented. + +When creating a library with `FUNCTION_BLOCK`s, you can implement initialization logic that runs when an instance is created. + +For more details on `FB_INIT` in IEC61131-3, refer to the [Program Organization Units (POUs)](../pous.md#function_block-initialization) documentation. + +#### Interoperability with libraries written in other languages + +When implementing a `FUNCTION_BLOCK` with initialization in C or other languages, you need to follow a specific naming convention for the initialization function. + +For a C implementation: + +1. Define a struct that matches your `FUNCTION_BLOCK` variables: + +```c +typedef struct { + int a; + int b; + // Other members as needed +} myFunctionBlock; +``` + +2. ruSTy expects a default-initializer to be present to initialize instances on the stack +(`VAR_TEMP` blocks or `VAR` blocks in functions or methods) + +This global instance follows the naming scheme of `____init`, below is an example of a zero-initializer: + +```c +myFunctionBlock __myFunctionBlock__init = { 0 }; +``` + +3. Optionally create an initialization function following the naming pattern `_FB_INIT`: + +```c +void myFunctionBlock_FB_INIT(myFunctionBlock* fb_instance) { + // Initialize members here + fb_instance->a = 1; + fb_instance->b = 2; + + // ...perform any other needed initialization +} +``` + +4. In your IEC61131-3 declaration (e.g., in a header file [`*.pli`]), ensure your `FUNCTION_BLOCK` includes the `FB_INIT` method (if present): + +``` +{external} +FUNCTION_BLOCK myFunctionBlock +VAR + a : DINT; + b : DINT; +END_VAR + METHOD FB_INIT + END_METHOD +END_FUNCTION_BLOCK +``` + +Note that the `FB_INIT` method doesn't need implementation details in the IEC61131-3 declaration when using an external implementation - the declaration just signals that initialization is available. + +#### Project-wide initialization + +See [Project-wide initialization](../using_rusty.md#project-wide-initialization) + diff --git a/book/src/pous.md b/book/src/pous.md index d923d74fe00..9222b0dc9f5 100644 --- a/book/src/pous.md +++ b/book/src/pous.md @@ -147,6 +147,28 @@ END_VAR END_FUNCTION_BLOCK ``` +#### `FUNCTION_BLOCK` initialization +Function blocks can define a special method called `FB_INIT` that is automatically called when an instance is created. This is analogous to a constructor in object-oriented programming. + +The `FB_INIT` method allows you to initialize the function block's variables to specific values. It is called during program initialization before any other code runs. + +`FB_INIT` methods can neither have parameters nor a return type in their current implementation - violating this contract will lead to undefined behaviour at runtime. + +```iecst +FUNCTION_BLOCK MyFB +VAR + x : INT; + y : INT; +END_VAR + METHOD FB_INIT + x := 1; + y := 2; + END_METHOD + + // Other methods and code... +END_FUNCTION_BLOCK +``` + ### Action An action is represented by a parent struct, and does not define its own interface (VAR blocks). diff --git a/book/src/using_rusty.md b/book/src/using_rusty.md index a9623987d86..042040bafac 100644 --- a/book/src/using_rusty.md +++ b/book/src/using_rusty.md @@ -136,3 +136,29 @@ Outputs the json schema used for the validation of the `plc.json` file Ouputs a json file with the default error severity configuration for the project. See [Error Configuration](./error_configuration.md) for more information. +## Project-wide initialization + +When your code is compiled, the compiler creates a special initialization function with the naming pattern `__init___`. This function is responsible for calling all implicit and user-defined initialization code, including all [`FB_INIT`](../pous.md#function_block-initialization) methods. + +`` is either taken directly from the `plc.json`'s `name` field or derived from the first input file (replacing `.`/`-` with `_`) when compiling without a `plc.json` (e.g. `plc prog.st ...` would yield `__init___prog_st`). + +This function is added to the global constructor list, therefore loading the binary will automatically call the `__init___` function (and therefore your `_FB_INIT` function) when an instance of your function block is created, before any other methods are called. This allows you to set default values or perform required setup for your function block. + +> **IMPORTANT:** The global constructor initialization is currently only supported for `x86` ISAs. To make sure initialization code runs reliably regardless of target-architecture, ensure your runtime calls this function before starting main task execution. +If you're using the executable without a runtime, you **must** ensure that `__init___` is called before any other code runs. Failure to do so will result in uninitialized function blocks and pointers, which can lead to undefined behavior and/or crashes. + +Example of ensuring initialization when using C (crt0): + +```c +int main() { + // Call the project initialization function first + __init___myproject(); + + // Now it's safe to start cyclic execution + for (;;) { + mainProg(); + } + + return 0; +} +``` \ No newline at end of file diff --git a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_in_different_locations_with_debug_info.snap b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_in_different_locations_with_debug_info.snap index 950fafb8b10..862277ce5ca 100644 --- a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_in_different_locations_with_debug_info.snap +++ b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_in_different_locations_with_debug_info.snap @@ -106,6 +106,13 @@ entry: declare void @mainProg(%mainProg*) +define void @__user_init_mainProg(%mainProg* %0) { +entry: + %self = alloca %mainProg*, align 8 + store %mainProg* %0, %mainProg** %self, align 8 + ret void +} + ; ModuleID = '__init___TestProject' source_filename = "__init___TestProject" @@ -117,9 +124,12 @@ source_filename = "__init___TestProject" define void @__init___TestProject() { entry: call void @__init_mainprog(%mainProg* @mainProg_instance) + call void @__user_init_mainProg(%mainProg* @mainProg_instance) ret void } declare void @__init_mainprog(%mainProg*) declare void @mainProg(%mainProg*) + +declare void @__user_init_mainProg(%mainProg*) diff --git a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_with_debug_info.snap b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_with_debug_info.snap index 97756adc60a..16002d352ab 100644 --- a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_with_debug_info.snap +++ b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_with_debug_info.snap @@ -106,6 +106,13 @@ entry: declare void @mainProg(%mainProg*) +define void @__user_init_mainProg(%mainProg* %0) { +entry: + %self = alloca %mainProg*, align 8 + store %mainProg* %0, %mainProg** %self, align 8 + ret void +} + ; ModuleID = '__init___TestProject' source_filename = "__init___TestProject" @@ -117,9 +124,12 @@ source_filename = "__init___TestProject" define void @__init___TestProject() { entry: call void @__init_mainprog(%mainProg* @mainProg_instance) + call void @__user_init_mainProg(%mainProg* @mainProg_instance) ret void } declare void @__init_mainprog(%mainProg*) declare void @mainProg(%mainProg*) + +declare void @__user_init_mainProg(%mainProg*) diff --git a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_source_files_generated.snap b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_source_files_generated.snap index aca0e9ee64d..ec0b04ae0a2 100644 --- a/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_source_files_generated.snap +++ b/compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_source_files_generated.snap @@ -1,6 +1,7 @@ --- source: compiler/plc_driver/./src/tests/multi_files.rs expression: "results.join(\"\\n\")" +snapshot_kind: text --- ; ModuleID = 'external_file1.st' source_filename = "external_file1.st" @@ -48,6 +49,13 @@ entry: declare void @mainProg(%mainProg*) +define void @__user_init_mainProg(%mainProg* %0) { +entry: + %self = alloca %mainProg*, align 8 + store %mainProg* %0, %mainProg** %self, align 8 + ret void +} + ; ModuleID = '__init___TestProject' source_filename = "__init___TestProject" @@ -59,9 +67,12 @@ source_filename = "__init___TestProject" define void @__init___TestProject() { entry: call void @__init_mainprog(%mainProg* @mainProg_instance) + call void @__user_init_mainProg(%mainProg* @mainProg_instance) ret void } declare void @__init_mainprog(%mainProg*) declare void @mainProg(%mainProg*) + +declare void @__user_init_mainProg(%mainProg*) diff --git a/compiler/plc_lowering/src/tests/inheritance_tests.rs b/compiler/plc_lowering/src/tests/inheritance_tests.rs index cb3aab1c3e1..a4d8804e834 100644 --- a/compiler/plc_lowering/src/tests/inheritance_tests.rs +++ b/compiler/plc_lowering/src/tests/inheritance_tests.rs @@ -1323,7 +1323,7 @@ mod units_tests { let (_, project) = parse_and_annotate("test", vec![src]).unwrap(); let unit = &project.units[0].get_unit().implementations[3]; - assert_debug_snapshot!(unit, @r###" + assert_debug_snapshot!(unit, @r#" Implementation { name: "main", type_name: "main", @@ -1350,6 +1350,26 @@ mod units_tests { }, ), }, + CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "__user_init_child", + }, + ), + base: None, + }, + parameters: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "fb", + }, + ), + base: None, + }, + ), + }, Assignment { left: ReferenceExpr { kind: Deref, @@ -1481,7 +1501,7 @@ mod units_tests { generic: false, access: None, } - "###) + "#) } #[test] diff --git a/compiler/plc_project/src/project.rs b/compiler/plc_project/src/project.rs index 1d617d87da6..5cca51c62cb 100644 --- a/compiler/plc_project/src/project.rs +++ b/compiler/plc_project/src/project.rs @@ -302,7 +302,7 @@ impl Project { /// Returns the symbol name of this projects main initializer function pub fn get_init_symbol_name(&self) -> &'static str { //Converts into static because this will live forever - format!("__init___{}", self.get_name().replace('.', "_")).leak() + format!("__init___{}", self.get_name().replace(['.', '-'], "_")).leak() } } diff --git a/src/codegen/tests/debug_tests.rs b/src/codegen/tests/debug_tests.rs index 558eb6c18ee..1539cbb55a8 100644 --- a/src/codegen/tests/debug_tests.rs +++ b/src/codegen/tests/debug_tests.rs @@ -380,6 +380,13 @@ fn dbg_declare_has_valid_metadata_references_for_methods() { ret void } + define void @__user_init_fb(%fb* %0) { + entry: + %self = alloca %fb*, align 8 + store %fb* %0, %fb** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -487,9 +494,17 @@ fn action_with_var_temp() { ret void } + define void @__user_init_PLC_PRG(%PLC_PRG* %0) { + entry: + %self = alloca %PLC_PRG*, align 8 + store %PLC_PRG* %0, %PLC_PRG** %self, align 8 + ret void + } + define void @__init___Test() { entry: call void @__init_plc_prg(%PLC_PRG* @PLC_PRG_instance) + call void @__user_init_PLC_PRG(%PLC_PRG* @PLC_PRG_instance) ret void } @@ -616,6 +631,7 @@ END_FUNCTION call void @llvm.dbg.declare(metadata i16* %i, metadata !48, metadata !DIExpression()), !dbg !49 store i16 0, i16* %i, align 2 call void @__init_struct_(%struct_* %st), !dbg !50 + call void @__user_init_struct_(%struct_* %st), !dbg !50 %s1 = getelementptr inbounds %struct_, %struct_* %st, i32 0, i32 2, !dbg !51 %3 = bitcast [81 x i8]* %s to i8*, !dbg !51 %4 = bitcast [81 x i8]* %s1 to i8*, !dbg !51 @@ -700,6 +716,23 @@ END_FUNCTION ret void } + define void @__user_init_inner(%inner* %0) { + entry: + %self = alloca %inner*, align 8 + store %inner* %0, %inner** %self, align 8 + ret void + } + + define void @__user_init_struct_(%struct_* %0) { + entry: + %self = alloca %struct_*, align 8 + store %struct_* %0, %struct_** %self, align 8 + %deref = load %struct_*, %struct_** %self, align 8 + %inner = getelementptr inbounds %struct_, %struct_* %deref, i32 0, i32 0 + call void @__user_init_inner(%inner* %inner) + ret void + } + define void @__init___Test() { entry: ret void diff --git a/src/codegen/tests/initialization_test/complex_initializers.rs b/src/codegen/tests/initialization_test/complex_initializers.rs index f0d863b04bb..857e3c94bfc 100644 --- a/src/codegen/tests/initialization_test/complex_initializers.rs +++ b/src/codegen/tests/initialization_test/complex_initializers.rs @@ -141,9 +141,17 @@ fn init_functions_generated_for_programs() { ret void } + define void @__user_init_PLC_PRG(%PLC_PRG* %0) { + entry: + %self = alloca %PLC_PRG*, align 8 + store %PLC_PRG* %0, %PLC_PRG** %self, align 8 + ret void + } + define void @__init___Test() { entry: call void @__init_plc_prg(%PLC_PRG* @PLC_PRG_instance) + call void @__user_init_PLC_PRG(%PLC_PRG* @PLC_PRG_instance) ret void } "#); @@ -203,6 +211,13 @@ fn init_functions_work_with_adr() { } declare void @PLC_PRG(%PLC_PRG*) + + define void @__user_init_PLC_PRG(%PLC_PRG* %0) { + entry: + %self = alloca %PLC_PRG*, align 8 + store %PLC_PRG* %0, %PLC_PRG** %self, align 8 + ret void + } ; ModuleID = '__init___testproject' source_filename = "__init___testproject" @@ -214,12 +229,15 @@ fn init_functions_work_with_adr() { define void @__init___testproject() { entry: call void @__init_plc_prg(%PLC_PRG* @PLC_PRG_instance) + call void @__user_init_PLC_PRG(%PLC_PRG* @PLC_PRG_instance) ret void } declare void @__init_plc_prg(%PLC_PRG*) declare void @PLC_PRG(%PLC_PRG*) + + declare void @__user_init_PLC_PRG(%PLC_PRG*) "#); } @@ -269,6 +287,13 @@ fn init_functions_generated_for_function_blocks() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -475,10 +500,59 @@ fn nested_initializer_pous() { ret void } + define void @__user_init_sideProg(%sideProg* %0) { + entry: + %self = alloca %sideProg*, align 8 + store %sideProg* %0, %sideProg** %self, align 8 + %deref = load %sideProg*, %sideProg** %self, align 8 + %f = getelementptr inbounds %sideProg, %sideProg* %deref, i32 0, i32 1 + call void @__user_init_foo(%foo* %f) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + %deref = load %foo*, %foo** %self, align 8 + %b = getelementptr inbounds %foo, %foo* %deref, i32 0, i32 1 + call void @__user_init_bar(%bar* %b) + ret void + } + + define void @__user_init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + %deref = load %bar*, %bar** %self, align 8 + %b = getelementptr inbounds %bar, %bar* %deref, i32 0, i32 0 + call void @__user_init_baz(%baz* %b) + ret void + } + + define void @__user_init_baz(%baz* %0) { + entry: + %self = alloca %baz*, align 8 + store %baz* %0, %baz** %self, align 8 + ret void + } + + define void @__user_init_mainProg(%mainProg* %0) { + entry: + %self = alloca %mainProg*, align 8 + store %mainProg* %0, %mainProg** %self, align 8 + %deref = load %mainProg*, %mainProg** %self, align 8 + %f = getelementptr inbounds %mainProg, %mainProg* %deref, i32 0, i32 1 + call void @__user_init_foo(%foo* %f) + ret void + } + define void @__init___Test() { entry: call void @__init_mainprog(%mainProg* @mainProg_instance) call void @__init_sideprog(%sideProg* @sideProg_instance) + call void @__user_init_mainProg(%mainProg* @mainProg_instance) + call void @__user_init_sideProg(%sideProg* @sideProg_instance) ret void } "#); @@ -675,9 +749,27 @@ fn struct_types() { ret void } + define void @__user_init_myStruct(%myStruct* %0) { + entry: + %self = alloca %myStruct*, align 8 + store %myStruct* %0, %myStruct** %self, align 8 + ret void + } + + define void @__user_init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + %deref = load %prog*, %prog** %self, align 8 + %str = getelementptr inbounds %prog, %prog* %deref, i32 0, i32 0 + call void @__user_init_myStruct(%myStruct* %str) + ret void + } + define void @__init___Test() { entry: call void @__init_prog(%prog* @prog_instance) + call void @__user_init_prog(%prog* @prog_instance) ret void } "#); @@ -790,9 +882,31 @@ fn stateful_pous_methods_and_structs_get_init_functions() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + + define void @__user_init_myStruct(%myStruct* %0) { + entry: + %self = alloca %myStruct*, align 8 + store %myStruct* %0, %myStruct** %self, align 8 + ret void + } + + define void @__user_init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + ret void + } + define void @__init___Test() { entry: call void @__init_prog(%prog* @prog_instance) + call void @__user_init_prog(%prog* @prog_instance) ret void } "#); @@ -865,10 +979,26 @@ fn global_instance() { ret void } + define void @__user_init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: call void @__init_prog(%prog* @prog_instance) call void @__init_foo(%foo* @fb) + call void @__user_init_prog(%prog* @prog_instance) + call void @__user_init_foo(%foo* @fb) ret void } "#); @@ -950,10 +1080,29 @@ fn aliased_types() { ret void } + define void @__user_init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + %deref = load %prog*, %prog** %self, align 8 + %fb = getelementptr inbounds %prog, %prog* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %fb) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: call void @__init_prog(%prog* @prog_instance) call void @__init_foo(%foo* @global_alias) + call void @__user_init_prog(%prog* @prog_instance) + call void @__user_init_foo(%foo* @global_alias) ret void } "#); @@ -1100,10 +1249,31 @@ fn var_config_aliased_variables_initialized() { ret void } + define void @__user_init_FB(%FB* %0) { + entry: + %self = alloca %FB*, align 8 + store %FB* %0, %FB** %self, align 8 + ret void + } + + define void @__user_init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + %deref = load %prog*, %prog** %self, align 8 + %instance1 = getelementptr inbounds %prog, %prog* %deref, i32 0, i32 0 + call void @__user_init_FB(%FB* %instance1) + %deref1 = load %prog*, %prog** %self, align 8 + %instance2 = getelementptr inbounds %prog, %prog* %deref1, i32 0, i32 1 + call void @__user_init_FB(%FB* %instance2) + ret void + } + define void @__init___Test() { entry: call void @__init_prog(%prog* @prog_instance) call void @__init___var_config() + call void @__user_init_prog(%prog* @prog_instance) ret void } @@ -1170,6 +1340,13 @@ fn var_external_blocks_are_ignored_in_init_functions() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: store [81 x i8]* @s, [81 x i8]** @refString, align 8 @@ -1236,6 +1413,13 @@ fn ref_to_local_member() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1306,6 +1490,13 @@ fn ref_to_local_member_shadows_global() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1364,6 +1555,13 @@ fn temporary_variable_ref_to_local_member() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1476,6 +1674,13 @@ fn initializing_method_variables_with_refs() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1535,6 +1740,13 @@ fn initializing_method_variables_with_refs_referencing_parent_pou_variable() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1593,6 +1805,13 @@ fn initializing_method_variables_with_refs_referencing_global_variable() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1654,6 +1873,13 @@ fn initializing_method_variables_with_refs_shadowing() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1710,6 +1936,13 @@ fn initializing_method_variables_with_alias() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1766,6 +1999,13 @@ fn initializing_method_variables_with_reference_to() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1827,6 +2067,7 @@ fn methods_call_init_functions_for_their_members() { %1 = bitcast %foo* %fb to i8* call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 bitcast (%foo* @__foo__init to i8*), i64 ptrtoint (%foo* getelementptr (%foo, %foo* null, i32 1) to i64), i1 false) call void @__init_foo(%foo* %fb) + call void @__user_init_foo(%foo* %fb) ret void } @@ -1852,6 +2093,20 @@ fn methods_call_init_functions_for_their_members() { ret void } + define void @__user_init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1860,3 +2115,337 @@ fn methods_call_init_functions_for_their_members() { attributes #0 = { argmemonly nofree nounwind willreturn } "#); } + +#[test] +fn user_fb_init_is_added_and_called_if_it_exists() { + let res = generate_to_string( + "Test", + vec![SourceCode::from( + r#" + FUNCTION_BLOCK foo + VAR + x : INT := 0; + y : INT := 0; + END_VAR + METHOD FB_INIT + x := 1; + y := 2; + END_METHOD + END_FUNCTION_BLOCK + + PROGRAM prog + VAR + f : foo; + END_VAR + f(); + END_PROGRAM + "#, + )], + ) + .unwrap(); + + assert_snapshot!(res, @r#" + ; ModuleID = '' + source_filename = "" + + %prog = type { %foo } + %foo = type { i16, i16 } + + @llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @__init___Test, i8* null }] + @prog_instance = global %prog zeroinitializer + @__foo__init = constant %foo zeroinitializer + + define void @foo(%foo* %0) { + entry: + %x = getelementptr inbounds %foo, %foo* %0, i32 0, i32 0 + %y = getelementptr inbounds %foo, %foo* %0, i32 0, i32 1 + ret void + } + + define void @foo_FB_INIT(%foo* %0) { + entry: + %x = getelementptr inbounds %foo, %foo* %0, i32 0, i32 0 + %y = getelementptr inbounds %foo, %foo* %0, i32 0, i32 1 + store i16 1, i16* %x, align 2 + store i16 2, i16* %y, align 2 + ret void + } + + define void @prog(%prog* %0) { + entry: + %f = getelementptr inbounds %prog, %prog* %0, i32 0, i32 0 + call void @foo(%foo* %f) + ret void + } + + define void @__init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + + define void @__init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + %deref = load %prog*, %prog** %self, align 8 + %f = getelementptr inbounds %prog, %prog* %deref, i32 0, i32 0 + call void @__init_foo(%foo* %f) + ret void + } + + define void @__user_init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + %deref = load %prog*, %prog** %self, align 8 + %f = getelementptr inbounds %prog, %prog* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %f) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + %deref = load %foo*, %foo** %self, align 8 + call void @foo_FB_INIT(%foo* %deref) + ret void + } + + define void @__init___Test() { + entry: + call void @__init_prog(%prog* @prog_instance) + call void @__user_init_prog(%prog* @prog_instance) + ret void + } + "#); +} + +#[test] +fn user_fb_init_in_global_struct() { + let res = generate_to_string( + "Test", + vec![SourceCode::from( + r#" + TYPE + bar : STRUCT + f: foo; + END_STRUCT; + END_TYPE + + VAR_GLOBAL + str: bar; + END_VAR + + FUNCTION_BLOCK foo + VAR + x : INT := 0; + y : INT := 0; + END_VAR + METHOD FB_INIT + x := 1; + y := 2; + END_METHOD + END_FUNCTION_BLOCK + + PROGRAM prog + VAR + str: bar; + END_VAR + str.f(); + END_PROGRAM + "#, + )], + ) + .unwrap(); + + assert_snapshot!(res, @r#" + ; ModuleID = '' + source_filename = "" + + %prog = type { %bar } + %bar = type { %foo } + %foo = type { i16, i16 } + + @llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @__init___Test, i8* null }] + @prog_instance = global %prog zeroinitializer + @__bar__init = constant %bar zeroinitializer + @__foo__init = constant %foo zeroinitializer + @str = global %bar zeroinitializer + + define void @foo(%foo* %0) { + entry: + %x = getelementptr inbounds %foo, %foo* %0, i32 0, i32 0 + %y = getelementptr inbounds %foo, %foo* %0, i32 0, i32 1 + ret void + } + + define void @foo_FB_INIT(%foo* %0) { + entry: + %x = getelementptr inbounds %foo, %foo* %0, i32 0, i32 0 + %y = getelementptr inbounds %foo, %foo* %0, i32 0, i32 1 + store i16 1, i16* %x, align 2 + store i16 2, i16* %y, align 2 + ret void + } + + define void @prog(%prog* %0) { + entry: + %str = getelementptr inbounds %prog, %prog* %0, i32 0, i32 0 + %f = getelementptr inbounds %bar, %bar* %str, i32 0, i32 0 + call void @foo(%foo* %f) + ret void + } + + define void @__init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + %deref = load %bar*, %bar** %self, align 8 + %f = getelementptr inbounds %bar, %bar* %deref, i32 0, i32 0 + call void @__init_foo(%foo* %f) + ret void + } + + define void @__init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + + define void @__init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + %deref = load %prog*, %prog** %self, align 8 + %str = getelementptr inbounds %prog, %prog* %deref, i32 0, i32 0 + call void @__init_bar(%bar* %str) + ret void + } + + define void @__user_init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + %deref = load %prog*, %prog** %self, align 8 + %str = getelementptr inbounds %prog, %prog* %deref, i32 0, i32 0 + call void @__user_init_bar(%bar* %str) + ret void + } + + define void @__user_init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + %deref = load %bar*, %bar** %self, align 8 + %f = getelementptr inbounds %bar, %bar* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %f) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + %deref = load %foo*, %foo** %self, align 8 + call void @foo_FB_INIT(%foo* %deref) + ret void + } + + define void @__init___Test() { + entry: + call void @__init_prog(%prog* @prog_instance) + call void @__init_bar(%bar* @str) + call void @__user_init_prog(%prog* @prog_instance) + call void @__user_init_bar(%bar* @str) + ret void + } + "#); +} + +#[test] +fn user_init_called_when_declared_as_external() { + let res = generate_to_string( + "Test", + vec![SourceCode::from( + r#" + {external} + FUNCTION_BLOCK foo + VAR + x : INT; + y : INT; + END_VAR + METHOD FB_INIT + END_METHOD + END_FUNCTION_BLOCK + + PROGRAM prog + VAR + f: foo; + END_VAR + f(); + END_PROGRAM + "#, + )], + ) + .unwrap(); + + assert_snapshot!(res, @r#" + ; ModuleID = '' + source_filename = "" + + %prog = type { %foo } + %foo = type { i16, i16 } + + @llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @__init___Test, i8* null }] + @prog_instance = global %prog zeroinitializer + @__foo__init = external global %foo + + declare void @foo(%foo*) + + declare void @foo_FB_INIT(%foo*) + + define void @prog(%prog* %0) { + entry: + %f = getelementptr inbounds %prog, %prog* %0, i32 0, i32 0 + call void @foo(%foo* %f) + ret void + } + + define void @__init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + ret void + } + + define void @__user_init_prog(%prog* %0) { + entry: + %self = alloca %prog*, align 8 + store %prog* %0, %prog** %self, align 8 + %deref = load %prog*, %prog** %self, align 8 + %f = getelementptr inbounds %prog, %prog* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %f) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + %deref = load %foo*, %foo** %self, align 8 + call void @foo_FB_INIT(%foo* %deref) + ret void + } + + define void @__init___Test() { + entry: + call void @__init_prog(%prog* @prog_instance) + call void @__user_init_prog(%prog* @prog_instance) + ret void + } + "#); +} diff --git a/src/codegen/tests/initialization_test/global_initializers.rs b/src/codegen/tests/initialization_test/global_initializers.rs index ceaea82b999..09968ff2c3c 100644 --- a/src/codegen/tests/initialization_test/global_initializers.rs +++ b/src/codegen/tests/initialization_test/global_initializers.rs @@ -163,7 +163,7 @@ fn external_pous_get_external_initializers() { ", ); - insta::assert_snapshot!(result, @r###" + insta::assert_snapshot!(result, @r#" ; ModuleID = '' source_filename = "" @@ -176,7 +176,7 @@ fn external_pous_get_external_initializers() { declare void @ext_fb(%ext_fb*) declare void @ext_prog(%ext_prog*) - "###); + "#); } #[test] diff --git a/src/codegen/tests/oop_tests.rs b/src/codegen/tests/oop_tests.rs index 7d1f78179fe..29c8eae4a8f 100644 --- a/src/codegen/tests/oop_tests.rs +++ b/src/codegen/tests/oop_tests.rs @@ -19,7 +19,7 @@ fn members_from_base_class_are_available_in_subclasses() { END_FUNCTION_BLOCK "#, ); - insta::assert_snapshot!(result, @r###" + insta::assert_snapshot!(result, @r#" ; ModuleID = '' source_filename = "" @@ -61,11 +61,28 @@ fn members_from_base_class_are_available_in_subclasses() { ret void } + define void @__user_init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + %deref = load %bar*, %bar** %self, align 8 + %__foo = getelementptr inbounds %bar, %bar* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %__foo) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void } - "###); + "#); } #[test] @@ -91,7 +108,7 @@ fn write_to_parent_variable_qualified_access() { ", ); - insta::assert_snapshot!(res, @r###" + insta::assert_snapshot!(res, @r#" ; ModuleID = '' source_filename = "" @@ -153,11 +170,38 @@ fn write_to_parent_variable_qualified_access() { ret void } + define void @__user_init_fb(%fb* %0) { + entry: + %self = alloca %fb*, align 8 + store %fb* %0, %fb** %self, align 8 + ret void + } + + define void @__user_init_fb2(%fb2* %0) { + entry: + %self = alloca %fb2*, align 8 + store %fb2* %0, %fb2** %self, align 8 + %deref = load %fb2*, %fb2** %self, align 8 + %__fb = getelementptr inbounds %fb2, %fb2* %deref, i32 0, i32 0 + call void @__user_init_fb(%fb* %__fb) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + %deref = load %foo*, %foo** %self, align 8 + %myFb = getelementptr inbounds %foo, %foo* %deref, i32 0, i32 0 + call void @__user_init_fb2(%fb2* %myFb) + ret void + } + define void @__init___Test() { entry: ret void } - "###); + "#); } #[test] @@ -232,6 +276,7 @@ fn write_to_parent_variable_in_instance() { %1 = bitcast %bar* %fb to i8* call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 getelementptr inbounds (%bar, %bar* @__bar__init, i32 0, i32 0, i32 0, i32 0), i64 ptrtoint (%bar* getelementptr (%bar, %bar* null, i32 1) to i64), i1 false) call void @__init_bar(%bar* %fb) + call void @__user_init_bar(%bar* %fb) %__foo = getelementptr inbounds %bar, %bar* %fb, i32 0, i32 0 call void @foo_baz(%foo* %__foo) call void @bar(%bar* %fb) @@ -264,6 +309,23 @@ fn write_to_parent_variable_in_instance() { ret void } + define void @__user_init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + %deref = load %bar*, %bar** %self, align 8 + %__foo = getelementptr inbounds %bar, %bar* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %__foo) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -310,7 +372,7 @@ fn array_in_parent_generated() { END_FUNCTION "#, ); - insta::assert_snapshot!(result, @r###" + insta::assert_snapshot!(result, @r#" ; ModuleID = '' source_filename = "" @@ -407,13 +469,40 @@ fn array_in_parent_generated() { ret void } + define void @__user_init_grandparent(%grandparent* %0) { + entry: + %self = alloca %grandparent*, align 8 + store %grandparent* %0, %grandparent** %self, align 8 + ret void + } + + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + %deref = load %parent*, %parent** %self, align 8 + %__grandparent = getelementptr inbounds %parent, %parent* %deref, i32 0, i32 0 + call void @__user_init_grandparent(%grandparent* %__grandparent) + ret void + } + define void @__init___Test() { entry: ret void } attributes #0 = { argmemonly nofree nounwind willreturn writeonly } - "###); + "#); } #[test] @@ -443,7 +532,7 @@ fn complex_array_access_generated() { "#, ); - insta::assert_snapshot!(result, @r###" + insta::assert_snapshot!(result, @r#" ; ModuleID = '' source_filename = "" @@ -529,11 +618,38 @@ fn complex_array_access_generated() { ret void } + define void @__user_init_grandparent(%grandparent* %0) { + entry: + %self = alloca %grandparent*, align 8 + store %grandparent* %0, %grandparent** %self, align 8 + ret void + } + + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + %deref = load %parent*, %parent** %self, align 8 + %__grandparent = getelementptr inbounds %parent, %parent* %deref, i32 0, i32 0 + call void @__user_init_grandparent(%grandparent* %__grandparent) + ret void + } + define void @__init___Test() { entry: ret void } - "###); + "#); } #[test] diff --git a/src/codegen/tests/oop_tests/debug_tests.rs b/src/codegen/tests/oop_tests/debug_tests.rs index e6e4658ed37..e46ab16c2a3 100644 --- a/src/codegen/tests/oop_tests/debug_tests.rs +++ b/src/codegen/tests/oop_tests/debug_tests.rs @@ -63,6 +63,23 @@ fn members_from_base_class_are_available_in_subclasses() { ret void } + define void @__user_init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + %deref = load %bar*, %bar** %self, align 8 + %__foo = getelementptr inbounds %bar, %bar* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %__foo) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -203,6 +220,33 @@ fn write_to_parent_variable_qualified_access() { ret void } + define void @__user_init_fb(%fb* %0) { + entry: + %self = alloca %fb*, align 8 + store %fb* %0, %fb** %self, align 8 + ret void + } + + define void @__user_init_fb2(%fb2* %0) { + entry: + %self = alloca %fb2*, align 8 + store %fb2* %0, %fb2** %self, align 8 + %deref = load %fb2*, %fb2** %self, align 8 + %__fb = getelementptr inbounds %fb2, %fb2* %deref, i32 0, i32 0 + call void @__user_init_fb(%fb* %__fb) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + %deref = load %foo*, %foo** %self, align 8 + %myFb = getelementptr inbounds %foo, %foo* %deref, i32 0, i32 0 + call void @__user_init_fb2(%fb2* %myFb) + ret void + } + define void @__init___Test() { entry: ret void @@ -332,6 +376,7 @@ fn write_to_parent_variable_in_instance() { %1 = bitcast %bar* %fb to i8* call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 getelementptr inbounds (%bar, %bar* @__bar__init, i32 0, i32 0, i32 0, i32 0), i64 ptrtoint (%bar* getelementptr (%bar, %bar* null, i32 1) to i64), i1 false) call void @__init_bar(%bar* %fb), !dbg !42 + call void @__user_init_bar(%bar* %fb), !dbg !42 %__foo = getelementptr inbounds %bar, %bar* %fb, i32 0, i32 0, !dbg !42 call void @foo_baz(%foo* %__foo), !dbg !43 call void @bar(%bar* %fb), !dbg !44 @@ -367,6 +412,23 @@ fn write_to_parent_variable_in_instance() { ret void } + define void @__user_init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + %deref = load %bar*, %bar** %self, align 8 + %__foo = getelementptr inbounds %bar, %bar* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %__foo) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -568,6 +630,33 @@ fn array_in_parent_generated() { ret void } + define void @__user_init_grandparent(%grandparent* %0) { + entry: + %self = alloca %grandparent*, align 8 + store %grandparent* %0, %grandparent** %self, align 8 + ret void + } + + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + %deref = load %parent*, %parent** %self, align 8 + %__grandparent = getelementptr inbounds %parent, %parent* %deref, i32 0, i32 0 + call void @__user_init_grandparent(%grandparent* %__grandparent) + ret void + } + define void @__init___Test() { entry: ret void @@ -760,6 +849,33 @@ fn complex_array_access_generated() { ret void } + define void @__user_init_grandparent(%grandparent* %0) { + entry: + %self = alloca %grandparent*, align 8 + store %grandparent* %0, %grandparent** %self, align 8 + ret void + } + + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + %deref = load %parent*, %parent** %self, align 8 + %__grandparent = getelementptr inbounds %parent, %parent* %deref, i32 0, i32 0 + call void @__user_init_grandparent(%grandparent* %__grandparent) + ret void + } + define void @__init___Test() { entry: ret void @@ -884,6 +1000,23 @@ fn function_block_method_debug_info() { ret void } + define void @__user_init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + %deref = load %bar*, %bar** %self, align 8 + %__foo = getelementptr inbounds %bar, %bar* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %__foo) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1054,6 +1187,9 @@ END_FUNCTION call void @__init_parent(%parent* %parent1), !dbg !61 call void @__init_child(%child* %child1), !dbg !61 call void @__init_grandchild(%grandchild* %grandchild1), !dbg !61 + call void @__user_init_parent(%parent* %parent1), !dbg !61 + call void @__user_init_child(%child* %child1), !dbg !61 + call void @__user_init_grandchild(%grandchild* %grandchild1), !dbg !61 %a = getelementptr inbounds %parent, %parent* %parent1, i32 0, i32 0, !dbg !62 store i32 1, i32* %a, align 4, !dbg !62 %__parent = getelementptr inbounds %child, %child* %child1, i32 0, i32 0, !dbg !63 @@ -1176,6 +1312,33 @@ END_FUNCTION ret void } + define void @__user_init_grandchild(%grandchild* %0) { + entry: + %self = alloca %grandchild*, align 8 + store %grandchild* %0, %grandchild** %self, align 8 + %deref = load %grandchild*, %grandchild** %self, align 8 + %__child = getelementptr inbounds %grandchild, %grandchild* %deref, i32 0, i32 0 + call void @__user_init_child(%child* %__child) + ret void + } + + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void diff --git a/src/codegen/tests/oop_tests/super_tests.rs b/src/codegen/tests/oop_tests/super_tests.rs index b13ab2a4ee4..197afdfda98 100644 --- a/src/codegen/tests/oop_tests/super_tests.rs +++ b/src/codegen/tests/oop_tests/super_tests.rs @@ -58,6 +58,23 @@ fn super_keyword_basic_access() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -126,6 +143,23 @@ fn super_without_deref() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -239,6 +273,23 @@ fn super_in_method_calls() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -318,6 +369,23 @@ fn super_in_complex_expressions() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -393,6 +461,23 @@ fn super_with_array_access() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -536,6 +621,33 @@ fn super_in_multi_level_inheritance() { ret void } + define void @__user_init_grandparent(%grandparent* %0) { + entry: + %self = alloca %grandparent*, align 8 + store %grandparent* %0, %grandparent** %self, align 8 + ret void + } + + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + %deref = load %parent*, %parent** %self, align 8 + %__grandparent = getelementptr inbounds %parent, %parent* %deref, i32 0, i32 0 + call void @__user_init_grandparent(%grandparent* %__grandparent) + ret void + } + define void @__init___Test() { entry: ret void @@ -614,6 +726,23 @@ fn super_with_pointer_operations() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -740,6 +869,23 @@ fn super_in_conditionals() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -809,6 +955,23 @@ fn super_with_const_variables() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -927,6 +1090,23 @@ fn super_as_function_parameter() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1053,6 +1233,23 @@ fn super_with_deeply_nested_expressions() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1264,6 +1461,23 @@ fn super_in_loop_constructs() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -1390,6 +1604,33 @@ fn super_with_method_overrides_in_three_levels() { ret void } + define void @__user_init_grandparent(%grandparent* %0) { + entry: + %self = alloca %grandparent*, align 8 + store %grandparent* %0, %grandparent** %self, align 8 + ret void + } + + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + %deref = load %parent*, %parent** %self, align 8 + %__grandparent = getelementptr inbounds %parent, %parent* %deref, i32 0, i32 0 + call void @__user_init_grandparent(%grandparent* %__grandparent) + ret void + } + define void @__init___Test() { entry: ret void @@ -1511,6 +1752,7 @@ fn super_with_structured_types() { %1 = bitcast %Complex_Type* %local_data to i8* call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 bitcast (%Complex_Type* @__Complex_Type__init to i8*), i64 ptrtoint (%Complex_Type* getelementptr (%Complex_Type, %Complex_Type* null, i32 1) to i64), i1 false) call void @__init_complex_type(%Complex_Type* %local_data) + call void @__user_init_Complex_Type(%Complex_Type* %local_data) %x = getelementptr inbounds %Complex_Type, %Complex_Type* %local_data, i32 0, i32 0 %data = getelementptr inbounds %parent, %parent* %__parent, i32 0, i32 0 %x1 = getelementptr inbounds %Complex_Type, %Complex_Type* %data, i32 0, i32 0 @@ -1574,6 +1816,33 @@ fn super_with_structured_types() { ret void } + define void @__user_init_Complex_Type(%Complex_Type* %0) { + entry: + %self = alloca %Complex_Type*, align 8 + store %Complex_Type* %0, %Complex_Type** %self, align 8 + ret void + } + + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + %deref = load %parent*, %parent** %self, align 8 + %data = getelementptr inbounds %parent, %parent* %deref, i32 0, i32 0 + call void @__user_init_Complex_Type(%Complex_Type* %data) + ret void + } + define void @__init___Test() { entry: ret void @@ -1672,6 +1941,23 @@ fn super_in_action_blocks() { ret void } + define void @__user_init_child(%child* %0) { + entry: + %self = alloca %child*, align 8 + store %child* %0, %child** %self, align 8 + %deref = load %child*, %child** %self, align 8 + %__parent = getelementptr inbounds %child, %child* %deref, i32 0, i32 0 + call void @__user_init_parent(%parent* %__parent) + ret void + } + + define void @__user_init_parent(%parent* %0) { + entry: + %self = alloca %parent*, align 8 + store %parent* %0, %parent** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__external_program_global_var_is_external.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__external_program_global_var_is_external.snap index bff4e8363c6..8581b6af078 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__external_program_global_var_is_external.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__external_program_global_var_is_external.snap @@ -1,6 +1,7 @@ --- source: src/codegen/tests/code_gen_tests.rs expression: result +snapshot_kind: text --- ; ModuleID = '' source_filename = "" diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__debug_tests__global_var_nested_struct_added_to_debug_info.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__debug_tests__global_var_nested_struct_added_to_debug_info.snap index 7781e6700f4..e25ae6ee60b 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__debug_tests__global_var_nested_struct_added_to_debug_info.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__debug_tests__global_var_nested_struct_added_to_debug_info.snap @@ -31,9 +31,27 @@ entry: ret void } +define void @__user_init_myStruct(%myStruct* %0) { +entry: + %self = alloca %myStruct*, align 8 + store %myStruct* %0, %myStruct** %self, align 8 + %deref = load %myStruct*, %myStruct** %self, align 8 + %b = getelementptr inbounds %myStruct, %myStruct* %deref, i32 0, i32 1 + call void @__user_init_myStruct2(%myStruct2* %b) + ret void +} + +define void @__user_init_myStruct2(%myStruct2* %0) { +entry: + %self = alloca %myStruct2*, align 8 + store %myStruct2* %0, %myStruct2** %self, align 8 + ret void +} + define void @__init___Test() { entry: call void @__init_mystruct(%myStruct* @gStruct) + call void @__user_init_myStruct(%myStruct* @gStruct) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__debug_tests__global_var_struct_added_to_debug_info.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__debug_tests__global_var_struct_added_to_debug_info.snap index 14ff3812d8d..5581f650c2f 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__debug_tests__global_var_struct_added_to_debug_info.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__debug_tests__global_var_struct_added_to_debug_info.snap @@ -20,9 +20,17 @@ entry: ret void } +define void @__user_init_myStruct(%myStruct* %0) { +entry: + %self = alloca %myStruct*, align 8 + store %myStruct* %0, %myStruct** %self, align 8 + ret void +} + define void @__init___Test() { entry: call void @__init_mystruct(%myStruct* @gStruct) + call void @__user_init_myStruct(%myStruct* @gStruct) ret void } diff --git a/src/lowering.rs b/src/lowering.rs index 2f4fd51df62..e9a0e533c2f 100644 --- a/src/lowering.rs +++ b/src/lowering.rs @@ -2,7 +2,7 @@ use crate::{ index::{get_init_fn_name, Index, PouIndexEntry, VariableIndexEntry}, resolver::const_evaluator::UnresolvableConstant, }; -use initializers::{Init, InitAssignments, Initializers, GLOBAL_SCOPE}; +use initializers::{get_user_init_fn_name, Init, InitAssignments, Initializers, GLOBAL_SCOPE}; use plc_ast::{ ast::{ AstFactory, AstNode, AstStatement, CallStatement, CompilationUnit, ConfigVariable, DataType, @@ -12,6 +12,7 @@ use plc_ast::{ provider::IdProvider, }; use plc_source::source_location::SourceLocation; +use rustc_hash::FxHashMap; pub mod calls; mod initializers; @@ -21,6 +22,7 @@ pub struct InitVisitor { index: Index, unresolved_initializers: Initializers, var_config_initializers: Vec, + user_inits: FxHashMap, ctxt: Context, } @@ -33,6 +35,10 @@ impl InitVisitor { init_symbol_name: &'static str, ) -> Vec { let mut visitor = Self::new(index, unresolvables, id_provider); + // before visiting, we need to collect all candidates for user-defined init functions + units.iter().for_each(|unit| { + visitor.collect_user_init_candidates(unit); + }); // visit all units units.iter_mut().for_each(|unit| { visitor.visit_compilation_unit(unit); @@ -50,6 +56,7 @@ impl InitVisitor { index, unresolved_initializers: Initializers::new(&unresolved_initializers), var_config_initializers: vec![], + user_inits: FxHashMap::default(), ctxt: Context::new(id_provider), } } @@ -63,6 +70,25 @@ impl InitVisitor { self.ctxt.scope(old); } + fn collect_user_init_candidates(&mut self, unit: &CompilationUnit) { + // collect all candidates for user-defined init functions + for pou in unit.pous.iter().filter(|it| matches!(it.kind, PouType::FunctionBlock | PouType::Program)) + { + // add the POU to potential `FB_INIT` candidates + self.user_inits + .insert(pou.name.to_owned(), self.index.find_method(&pou.name, "FB_INIT").is_some()); + } + + for user_type in + unit.user_types.iter().filter(|it| matches!(it.data_type, DataType::StructType { .. })) + { + // add the struct to potential `STRUCT_INIT` candidates + if let Some(name) = user_type.data_type.get_name() { + self.user_inits.insert(name.to_string(), false); + }; + } + } + fn update_initializer(&mut self, variable: &mut plc_ast::ast::Variable) { // flat references to stateful pou-local variables need to have a qualifier added, so they can be resolved in the init functions let scope = self.ctxt.get_scope().as_ref().map(|it| it.as_str()).unwrap_or(GLOBAL_SCOPE); @@ -213,30 +239,41 @@ impl InitVisitor { }) }); - // collect necessary call statements to init-functions - let delegated_calls = self - .index - .get_pou_members(&implementation.name) - .iter() - .filter(|var| predicate(var)) - .filter_map(|var| { + // collect necessary call statements to init-functions and user-defined init-functions + let mut implicit_calls = Vec::new(); + let mut user_init_calls = Vec::new(); + self.index.get_pou_members(&implementation.name).iter().filter(|var| predicate(var)).for_each( + |var| { let dti = self.index.get_effective_type_or_void_by_name(var.get_type_name()).get_type_information(); - if dti.is_struct() { - Some(create_call_statement( + let is_external = self + .index + .find_pou(dti.get_name()) + .is_some_and(|it| it.get_linkage() == &LinkageType::External); + if dti.is_struct() && !is_external { + implicit_calls.push(create_call_statement( &get_init_fn_name(dti.get_name()), var.get_name(), None, self.ctxt.get_id_provider(), &implementation.name_location, - )) - } else { - None + )); + } + if self.user_inits.contains_key(dti.get_name()) { + user_init_calls.push(create_call_statement( + &get_user_init_fn_name(dti.get_name()), + var.get_name(), + None, + self.ctxt.get_id_provider(), + &implementation.name_location, + )); } - }); + }, + ); let stmts = assignments - .chain(delegated_calls) + .chain(implicit_calls) + .chain(user_init_calls) .chain(std::mem::take(&mut implementation.statements)) .collect::>(); implementation.statements = stmts; diff --git a/src/lowering/initializers.rs b/src/lowering/initializers.rs index 6b30794814d..45e76eecb57 100644 --- a/src/lowering/initializers.rs +++ b/src/lowering/initializers.rs @@ -145,6 +145,7 @@ fn create_init_units(lowerer: &InitVisitor) -> Vec { create_init_unit(lowerer, container, init, &lookup) }) + .chain(create_user_init_units(lowerer)) .collect() } @@ -228,6 +229,69 @@ fn create_init_unit( Some(new_unit(init_pou, implementation, INIT_COMPILATION_UNIT)) } +fn create_user_init_units(lowerer: &InitVisitor) -> Vec { + lowerer + .user_inits + .iter() + .map(|(container_name, has_fb_init)| { + let location = SourceLocation::internal_in_unit(Some(INIT_COMPILATION_UNIT)); + let mut id_provider = lowerer.ctxt.get_id_provider(); + let param = vec![VariableBlock::default() + .with_block_type(VariableBlockType::InOut) + .with_variables(vec![Variable { + name: "self".into(), + data_type_declaration: DataTypeDeclaration::Reference { + referenced_type: container_name.to_string(), + location: location.clone(), + }, + initializer: None, + address: None, + location: location.clone(), + }])]; + + let fn_name = get_user_init_fn_name(container_name); + let init_pou = new_pou(&fn_name, id_provider.next_id(), param, PouType::Init, &location); + + let mut statements = lowerer + .index + .get_container_members(container_name) + .iter() + .filter_map(|member| { + let member_type_name = member.get_type_name(); + let type_name = lowerer + .index + .get_effective_type_by_name(member_type_name) + .map(|it| it.get_type_information().get_name()) + .unwrap_or(member_type_name); + let call_name = get_user_init_fn_name(type_name); + if !member.is_temp() && lowerer.user_inits.contains_key(type_name) { + Some(create_call_statement( + &call_name, + member.get_name(), + Some("self"), + id_provider.clone(), + &location, + )) + } else { + None + } + }) + .collect::>(); + + if *has_fb_init { + let base = create_member_reference("self", id_provider.clone(), None); + let op = create_member_reference("fb_init", id_provider.clone(), Some(base)); + let call_statement = + AstFactory::create_call_statement(op, None, id_provider.next_id(), location.clone()); + statements.push(call_statement); + } + let implementation = new_implementation(&fn_name, statements, PouType::Init, location); + + new_unit(init_pou, implementation, INIT_COMPILATION_UNIT) + }) + .collect() +} + fn create_init_wrapper_function( lowerer: &mut InitVisitor, init_symbol_name: &'static str, @@ -274,7 +338,7 @@ fn create_init_wrapper_function( } }); - let mut assignments = if let Some(stmts) = lowerer.unresolved_initializers.get(GLOBAL_SCOPE) { + let mut statements = if let Some(stmts) = lowerer.unresolved_initializers.get(GLOBAL_SCOPE) { stmts .iter() .filter_map(|(var_name, initializer)| { @@ -298,10 +362,10 @@ fn create_init_wrapper_function( }) .collect::>(); - assignments.extend(calls); + statements.extend(calls); if !skip_var_config { - assignments.push(AstFactory::create_call_statement( + statements.push(AstFactory::create_call_statement( create_member_reference(VAR_CONFIG_INIT, id_provider.clone(), None), None, id_provider.next_id(), @@ -309,8 +373,10 @@ fn create_init_wrapper_function( )); }; + let user_init_calls = get_global_user_init_statements(lowerer); + statements.extend(user_init_calls); let implementation = - new_implementation(init_symbol_name, assignments, PouType::ProjectInit, SourceLocation::internal()); + new_implementation(init_symbol_name, statements, PouType::ProjectInit, SourceLocation::internal()); let mut global_init = new_unit(init_pou, implementation, init_symbol_name); if skip_var_config { @@ -323,6 +389,50 @@ fn create_init_wrapper_function( Some(global_init) } +fn get_global_user_init_statements(lowerer: &InitVisitor) -> Vec { + let global_instances = if let Some(global_instances) = + lowerer.unresolved_initializers.get(GLOBAL_SCOPE).map(|it| { + it.keys().filter_map(|var_name| { + lowerer.index.find_variable(None, &[var_name]).and_then(|it| { + lowerer.index.find_effective_type_by_name(it.get_type_name()).and_then(|dt| { + let name = dt.get_type_information().get_name(); + if lowerer.user_inits.contains_key(name) { + Some((get_user_init_fn_name(name), var_name)) + } else { + None + } + }) + }) + }) + }) { + global_instances.collect::>() + } else { + vec![] + }; + + let programs = lowerer.unresolved_initializers.iter().filter_map(|(scope, _)| { + if lowerer.index.find_pou(scope).is_some_and(|pou| pou.is_program()) { + Some((get_user_init_fn_name(scope), scope)) + } else { + None + } + }); + let mut id_provider = lowerer.ctxt.id_provider.clone(); + programs + .chain(global_instances) + .map(|(fn_name, param)| { + let op = create_member_reference(&fn_name, lowerer.ctxt.id_provider.clone(), None); + let param = create_member_reference(param, lowerer.ctxt.id_provider.clone(), None); + AstFactory::create_call_statement( + op, + Some(param), + id_provider.next_id(), + SourceLocation::internal(), + ) + }) + .collect::>() +} + fn new_pou( name: &str, id: AstId, @@ -380,3 +490,7 @@ fn new_unit(pou: Pou, implementation: Implementation, file_name: &'static str) - file: FileMarker::Internal(file_name), } } + +pub(super) fn get_user_init_fn_name(type_name: &str) -> String { + format!("__user_init_{}", type_name) +} diff --git a/src/resolver/tests/resolve_and_lower_init_functions.rs b/src/resolver/tests/resolve_and_lower_init_functions.rs index b92f3bf924b..c3b5479f8dd 100644 --- a/src/resolver/tests/resolve_and_lower_init_functions.rs +++ b/src/resolver/tests/resolve_and_lower_init_functions.rs @@ -234,9 +234,9 @@ fn init_wrapper_function_created() { // we expect this function to have no parameters assert!(init.pous[0].variable_blocks.is_empty()); - // we expect to the body to have 2 statements + // we expect to the body to have 3 statements let statements = &implementation.statements; - assert_eq!(statements.len(), 2); + assert_eq!(statements.len(), 3); // we expect the first statement in the function-body to assign `REF(s)` to `gs`, since // global variables are to be initialized first @@ -297,6 +297,30 @@ fn init_wrapper_function_created() { } "###); + // we expect the third statement to call `__user_init_foo`, which checks for user-defined init functions and calls them + assert_debug_snapshot!(statements[2], @r#" + CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "__user_init_foo", + }, + ), + base: None, + }, + parameters: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, + }, + ), + } + "#); + // since `foo` has a member-instance of `bar`, we expect its initializer to call/propagate to `__init_bar` with its local member let init_foo = &units[1].implementations[1]; assert_debug_snapshot!(init_foo.statements[0], @r###" diff --git a/src/tests/adr/initializer_functions_adr.rs b/src/tests/adr/initializer_functions_adr.rs index bcade4134dd..0dc08c1cc29 100644 --- a/src/tests/adr/initializer_functions_adr.rs +++ b/src/tests/adr/initializer_functions_adr.rs @@ -354,7 +354,7 @@ fn global_initializers_are_wrapped_in_single_init_function() { let init_impl = &units[2].implementations[0]; assert_eq!(&init_impl.name, "__init___Test"); - assert_eq!(init_impl.statements.len(), 4); + assert_eq!(init_impl.statements.len(), 7); // global variable blocks are initialized first, hence we expect the first statement in the `__init` body to be an // `Assignment`, assigning `REF(s)` to `gs`. This is followed by three `CallStatements`, one for each global `PROGRAM` // instance. @@ -507,6 +507,20 @@ fn generating_init_functions() { ret void } + define void @__user_init_myStruct(%myStruct* %0) { + entry: + %self = alloca %myStruct*, align 8 + store %myStruct* %0, %myStruct** %self, align 8 + ret void + } + + define void @__user_init_myRefStruct(%myRefStruct* %0) { + entry: + %self = alloca %myRefStruct*, align 8 + store %myRefStruct* %0, %myRefStruct** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -618,10 +632,46 @@ fn generating_init_functions() { ret void } + define void @__user_init_baz(%baz* %0) { + entry: + %self = alloca %baz*, align 8 + store %baz* %0, %baz** %self, align 8 + %deref = load %baz*, %baz** %self, align 8 + %fb = getelementptr inbounds %baz, %baz* %deref, i32 0, i32 0 + call void @__user_init_bar(%bar* %fb) + ret void + } + + define void @__user_init_bar(%bar* %0) { + entry: + %self = alloca %bar*, align 8 + store %bar* %0, %bar** %self, align 8 + %deref = load %bar*, %bar** %self, align 8 + %fb = getelementptr inbounds %bar, %bar* %deref, i32 0, i32 0 + call void @__user_init_foo(%foo* %fb) + ret void + } + + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + + define void @__user_init_myStruct(%myStruct* %0) { + entry: + %self = alloca %myStruct*, align 8 + store %myStruct* %0, %myStruct** %self, align 8 + ret void + } + define void @__init___Test() { entry: call void @__init_baz(%baz* @baz_instance) call void @__init_mystruct(%myStruct* @s) + call void @__user_init_baz(%baz* @baz_instance) + call void @__user_init_myStruct(%myStruct* @s) ret void } "#); @@ -694,6 +744,7 @@ fn intializing_temporary_variables() { store [81 x i8]* @ps, [81 x i8]** %s, align 8 store [81 x i8]* @ps2, [81 x i8]** %s2, align 8 call void @__init_foo(%foo* %fb) + call void @__user_init_foo(%foo* %fb) call void @foo(%foo* %fb) %main_ret = load i32, i32* %main, align 4 ret i32 %main_ret @@ -712,6 +763,13 @@ fn intializing_temporary_variables() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -772,6 +830,13 @@ fn initializing_method_variables() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -846,6 +911,13 @@ fn initializing_method_variables() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void @@ -902,6 +974,13 @@ fn initializing_method_variables() { ret void } + define void @__user_init_foo(%foo* %0) { + entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void + } + define void @__init___Test() { entry: ret void diff --git a/tests/integration/snapshots/tests__integration__cfc__ir__actions_debug.snap b/tests/integration/snapshots/tests__integration__cfc__ir__actions_debug.snap index 6dee8953e31..3552ed08ed1 100644 --- a/tests/integration/snapshots/tests__integration__cfc__ir__actions_debug.snap +++ b/tests/integration/snapshots/tests__integration__cfc__ir__actions_debug.snap @@ -51,9 +51,17 @@ entry: ret void } +define void @__user_init_main(%main* %0) { +entry: + %self = alloca %main*, align 8 + store %main* %0, %main** %self, align 8 + ret void +} + define void @__init___plc() { entry: call void @__init_main(%main* @main_instance) + call void @__user_init_main(%main* @main_instance) ret void } diff --git a/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return.snap b/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return.snap index ec537c437d8..2938a5a281c 100644 --- a/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return.snap +++ b/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return.snap @@ -1,6 +1,7 @@ --- source: tests/integration/cfc.rs expression: output_file_content_without_headers +snapshot_kind: text --- %conditional_return = type { i32 } @@ -29,6 +30,13 @@ entry: ret void } +define void @__user_init_conditional_return(%conditional_return* %0) { +entry: + %self = alloca %conditional_return*, align 8 + store %conditional_return* %0, %conditional_return** %self, align 8 + ret void +} + define void @__init___plc() { entry: ret void diff --git a/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return_evaluating_true.snap b/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return_evaluating_true.snap index 74638796d36..740b1fac16d 100644 --- a/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return_evaluating_true.snap +++ b/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return_evaluating_true.snap @@ -1,6 +1,7 @@ --- source: tests/integration/cfc.rs expression: output_file_content_without_headers +snapshot_kind: text --- %conditional_return = type { i32 } @@ -17,6 +18,7 @@ entry: call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 bitcast (%conditional_return* @__conditional_return__init to i8*), i64 ptrtoint (%conditional_return* getelementptr (%conditional_return, %conditional_return* null, i32 1) to i64), i1 false) store i32 0, i32* %main, align 4 call void @__init_conditional_return(%conditional_return* %conditional) + call void @__user_init_conditional_return(%conditional_return* %conditional) %val = getelementptr inbounds %conditional_return, %conditional_return* %conditional, i32 0, i32 0 %load_my_val = load i32, i32* %my_val, align 4 store i32 %load_my_val, i32* %val, align 4 @@ -53,6 +55,13 @@ entry: ret void } +define void @__user_init_conditional_return(%conditional_return* %0) { +entry: + %self = alloca %conditional_return*, align 8 + store %conditional_return* %0, %conditional_return** %self, align 8 + ret void +} + define void @__init___plc() { entry: ret void diff --git a/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return_evaluating_true_negated.snap b/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return_evaluating_true_negated.snap index 6a0ec004aa6..9555be0e52f 100644 --- a/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return_evaluating_true_negated.snap +++ b/tests/integration/snapshots/tests__integration__cfc__ir__conditional_return_evaluating_true_negated.snap @@ -1,6 +1,7 @@ --- source: tests/integration/cfc.rs expression: output_file_content_without_headers +snapshot_kind: text --- %conditional_return = type { i32 } @@ -17,6 +18,7 @@ entry: call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 bitcast (%conditional_return* @__conditional_return__init to i8*), i64 ptrtoint (%conditional_return* getelementptr (%conditional_return, %conditional_return* null, i32 1) to i64), i1 false) store i32 0, i32* %main, align 4 call void @__init_conditional_return(%conditional_return* %conditional) + call void @__user_init_conditional_return(%conditional_return* %conditional) %val = getelementptr inbounds %conditional_return, %conditional_return* %conditional, i32 0, i32 0 %load_my_val = load i32, i32* %my_val, align 4 store i32 %load_my_val, i32* %val, align 4 @@ -54,6 +56,13 @@ entry: ret void } +define void @__user_init_conditional_return(%conditional_return* %0) { +entry: + %self = alloca %conditional_return*, align 8 + store %conditional_return* %0, %conditional_return** %self, align 8 + ret void +} + define void @__init___plc() { entry: ret void diff --git a/tests/integration/snapshots/tests__integration__cfc__ir__jump_debug.snap b/tests/integration/snapshots/tests__integration__cfc__ir__jump_debug.snap index 21a2b72f05a..4e933b61684 100644 --- a/tests/integration/snapshots/tests__integration__cfc__ir__jump_debug.snap +++ b/tests/integration/snapshots/tests__integration__cfc__ir__jump_debug.snap @@ -34,9 +34,17 @@ entry: ret void } +define void @__user_init_foo(%foo* %0) { +entry: + %self = alloca %foo*, align 8 + store %foo* %0, %foo** %self, align 8 + ret void +} + define void @__init___plc() { entry: call void @__init_foo(%foo* @foo_instance) + call void @__user_init_foo(%foo* @foo_instance) ret void } diff --git a/tests/integration/snapshots/tests__integration__cfc__ir__sink_source_debug.snap b/tests/integration/snapshots/tests__integration__cfc__ir__sink_source_debug.snap index cc31bfa9f4f..d02dc86e279 100644 --- a/tests/integration/snapshots/tests__integration__cfc__ir__sink_source_debug.snap +++ b/tests/integration/snapshots/tests__integration__cfc__ir__sink_source_debug.snap @@ -26,9 +26,17 @@ entry: ret void } +define void @__user_init_main(%main* %0) { +entry: + %self = alloca %main*, align 8 + store %main* %0, %main** %self, align 8 + ret void +} + define void @__init___plc() { entry: call void @__init_main(%main* @main_instance) + call void @__user_init_main(%main* @main_instance) ret void } diff --git a/tests/lit/multi/extern_C_fb_init/extern_C_fb_init.test b/tests/lit/multi/extern_C_fb_init/extern_C_fb_init.test new file mode 100644 index 00000000000..0743e3f4ede --- /dev/null +++ b/tests/lit/multi/extern_C_fb_init/extern_C_fb_init.test @@ -0,0 +1,4 @@ +RUN: %COMPILE %S/main.st && %RUN | %CHECK %s +CHECK: myFunctionBlock initialized with a = 1, b = 2 +CHECK: 1 +CHECK: 2 \ No newline at end of file diff --git a/tests/lit/multi/extern_C_fb_init/foo.c b/tests/lit/multi/extern_C_fb_init/foo.c new file mode 100644 index 00000000000..47f2db666e6 --- /dev/null +++ b/tests/lit/multi/extern_C_fb_init/foo.c @@ -0,0 +1,14 @@ +#include + +typedef struct { + int a; + int b; +} myFunctionBlock; + +myFunctionBlock __myFunctionBlock__init = { 0 }; + +void myFunctionBlock_FB_INIT(myFunctionBlock* fb_instance) { + fb_instance->a = 1; + fb_instance->b = 2; + printf("myFunctionBlock initialized with a = %d, b = %d\n", fb_instance->a, fb_instance->b); +} \ No newline at end of file diff --git a/tests/lit/multi/extern_C_fb_init/header.pli b/tests/lit/multi/extern_C_fb_init/header.pli new file mode 100644 index 00000000000..25a81fe17a7 --- /dev/null +++ b/tests/lit/multi/extern_C_fb_init/header.pli @@ -0,0 +1,9 @@ +{external} +FUNCTION_BLOCK myFunctionBlock +VAR + a : DINT; + b : DINT; +END_VAR + METHOD FB_INIT + END_METHOD +END_FUNCTION_BLOCK \ No newline at end of file diff --git a/tests/lit/multi/extern_C_fb_init/lit.local.cfg b/tests/lit/multi/extern_C_fb_init/lit.local.cfg new file mode 100644 index 00000000000..214961981bb --- /dev/null +++ b/tests/lit/multi/extern_C_fb_init/lit.local.cfg @@ -0,0 +1,47 @@ +# Override the compile command to include the custom library +import os.path +import subprocess + +# Access the same parameters that the main configuration uses +stdlibLocation = lit_config.params["LIB"] +compilerLocation = lit_config.params["COMPILER"] + +# Derive rusty root directory using relative paths +test_dir = os.path.dirname(__file__) +source_path = os.path.abspath(test_dir) +rustyRootDirectory = os.path.abspath(os.path.join(test_dir, "..", "..", "..", "..")) + +# Use tmp directory for compiled library +tmp_lib_path = "/tmp" +tmp_lib_file = f"{tmp_lib_path}/libfoo.so" + +# Compile foo.c to libfoo.so in the tmp directory +try: + lit_config.note(f"Compiling foo.c into {tmp_lib_file}...") + gcc_cmd = f"gcc -shared -fPIC -o {tmp_lib_file} {source_path}/foo.c" + lit_config.note(f"Running: {gcc_cmd}") + result = subprocess.run(gcc_cmd, shell=True, check=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + lit_config.note(f"Successfully compiled {tmp_lib_file}") +except subprocess.CalledProcessError as e: + lit_config.error(f"Failed to compile foo.c: {e.stderr.decode()}") + raise + +# Build on the existing compile command but add our custom library +compile = f'{compilerLocation}' +compile = f'{compile} -o /tmp/%basename_t.out' +compile = f'{compile} -liec61131std -L{stdlibLocation}/lib -i "{stdlibLocation}/include/*.st"' +compile = f'{compile} -i "{rustyRootDirectory}/tests/lit/util/*.pli"' +compile = f'{compile} -L{tmp_lib_path} -lfoo -i {source_path}/header.pli' +compile = f'{compile} --linker=cc' + +# Log the compile command +lit_config.note(f"Compile command: {compile}") + +# Update the run command to include the custom library path +run_cmd = f'LD_LIBRARY_PATH="{stdlibLocation}/lib:{tmp_lib_path}" /tmp/%basename_t.out' + +# Override the substitutions +config.substitutions = [s for s in config.substitutions if s[0] not in ['%COMPILE', '%RUN']] +config.substitutions.append(('%COMPILE', f'{compile}')) +config.substitutions.append(('%RUN', f'{run_cmd}')) \ No newline at end of file diff --git a/tests/lit/multi/extern_C_fb_init/main.st b/tests/lit/multi/extern_C_fb_init/main.st new file mode 100644 index 00000000000..d494bce9c74 --- /dev/null +++ b/tests/lit/multi/extern_C_fb_init/main.st @@ -0,0 +1,7 @@ +FUNCTION main +VAR + f: myFunctionBlock; +END_VAR + printf('%d$N', f.a); + printf('%d$N', f.b); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/init/user_init.st b/tests/lit/single/init/user_init.st new file mode 100644 index 00000000000..30bc27f7862 --- /dev/null +++ b/tests/lit/single/init/user_init.st @@ -0,0 +1,29 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +TYPE + bar : STRUCT + f: foo; + END_STRUCT; +END_TYPE + +FUNCTION_BLOCK foo +VAR + x : INT := 0; + y : INT := 0; +END_VAR + METHOD FB_INIT + x := 1; + y := 2; + END_METHOD +END_FUNCTION_BLOCK + +PROGRAM prog +VAR + str: bar; +END_VAR + printf('%d$N', str.f.x); // CHECK: 1 + printf('%d$N', str.f.y); // CHECK: 2 +END_PROGRAM + +FUNCTION main: DINT + prog(); +END_FUNCTION \ No newline at end of file