Skip to content

Commit f2f442e

Browse files
authored
feat: FB_INIT user code initialization (#1458)
Adds initialization logic for user-defined `FB_INIT` methods and updates the user-documentation. Struct-types now get an additional init-function for user-initialization, which will call the `FB_INIT` if present and then initialize each member, similar to the already present `__init_<type>` functions. The user_init functions (`__user_init_<type>`) are guaranteed to be called after the implicit init is finished to allow pointers to be initialized beforehand.
1 parent 78cd38e commit f2f442e

33 files changed

+1824
-38
lines changed

book/src/libraries/api_lib_guide.md

+62-3
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,6 @@ void Count(CountStruct* countInst) {
142142
}
143143
```
144144

145-
146-
147145
#### 2.2.3 Return values
148146

149147
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 {
334332
```
335333

336334
### 2.5 `FUNCTION_BLOCK` initialization
337-
Not yet implemented.
335+
336+
When creating a library with `FUNCTION_BLOCK`s, you can implement initialization logic that runs when an instance is created.
337+
338+
For more details on `FB_INIT` in IEC61131-3, refer to the [Program Organization Units (POUs)](../pous.md#function_block-initialization) documentation.
339+
340+
#### Interoperability with libraries written in other languages
341+
342+
When implementing a `FUNCTION_BLOCK` with initialization in C or other languages, you need to follow a specific naming convention for the initialization function.
343+
344+
For a C implementation:
345+
346+
1. Define a struct that matches your `FUNCTION_BLOCK` variables:
347+
348+
```c
349+
typedef struct {
350+
int a;
351+
int b;
352+
// Other members as needed
353+
} myFunctionBlock;
354+
```
355+
356+
2. ruSTy expects a default-initializer to be present to initialize instances on the stack
357+
(`VAR_TEMP` blocks or `VAR` blocks in functions or methods)
358+
359+
This global instance follows the naming scheme of `__<FunctionBlockName>__init`, below is an example of a zero-initializer:
360+
361+
```c
362+
myFunctionBlock __myFunctionBlock__init = { 0 };
363+
```
364+
365+
3. Optionally create an initialization function following the naming pattern `<FunctionBlockName>_FB_INIT`:
366+
367+
```c
368+
void myFunctionBlock_FB_INIT(myFunctionBlock* fb_instance) {
369+
// Initialize members here
370+
fb_instance->a = 1;
371+
fb_instance->b = 2;
372+
373+
// ...perform any other needed initialization
374+
}
375+
```
376+
377+
4. In your IEC61131-3 declaration (e.g., in a header file [`*.pli`]), ensure your `FUNCTION_BLOCK` includes the `FB_INIT` method (if present):
378+
379+
```
380+
{external}
381+
FUNCTION_BLOCK myFunctionBlock
382+
VAR
383+
a : DINT;
384+
b : DINT;
385+
END_VAR
386+
METHOD FB_INIT
387+
END_METHOD
388+
END_FUNCTION_BLOCK
389+
```
390+
391+
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.
392+
393+
#### Project-wide initialization
394+
395+
See [Project-wide initialization](../using_rusty.md#project-wide-initialization)
396+
338397

339398

book/src/pous.md

+22
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,28 @@ END_VAR
147147
END_FUNCTION_BLOCK
148148
```
149149

150+
#### `FUNCTION_BLOCK` initialization
151+
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.
152+
153+
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.
154+
155+
`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.
156+
157+
```iecst
158+
FUNCTION_BLOCK MyFB
159+
VAR
160+
x : INT;
161+
y : INT;
162+
END_VAR
163+
METHOD FB_INIT
164+
x := 1;
165+
y := 2;
166+
END_METHOD
167+
168+
// Other methods and code...
169+
END_FUNCTION_BLOCK
170+
```
171+
150172
### Action
151173

152174
An action is represented by a parent struct, and does not define its own interface (VAR blocks).

book/src/using_rusty.md

+26
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,29 @@ Outputs the json schema used for the validation of the `plc.json` file
136136
Ouputs a json file with the default error severity configuration for the project.
137137
See [Error Configuration](./error_configuration.md) for more information.
138138

139+
## Project-wide initialization
140+
141+
When your code is compiled, the compiler creates a special initialization function with the naming pattern `__init___<projectname>`. This function is responsible for calling all implicit and user-defined initialization code, including all [`FB_INIT`](../pous.md#function_block-initialization) methods.
142+
143+
`<projectname>` 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`).
144+
145+
This function is added to the global constructor list, therefore loading the binary will automatically call the `__init___<projectname>` function (and therefore your `<FunctionBlockName>_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.
146+
147+
> **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.
148+
If you're using the executable without a runtime, you **must** ensure that `__init___<projectname>` 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.
149+
150+
Example of ensuring initialization when using C (crt0):
151+
152+
```c
153+
int main() {
154+
// Call the project initialization function first
155+
__init___myproject();
156+
157+
// Now it's safe to start cyclic execution
158+
for (;;) {
159+
mainProg();
160+
}
161+
162+
return 0;
163+
}
164+
```

compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_in_different_locations_with_debug_info.snap

+10
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ entry:
106106

107107
declare void @mainProg(%mainProg*)
108108

109+
define void @__user_init_mainProg(%mainProg* %0) {
110+
entry:
111+
%self = alloca %mainProg*, align 8
112+
store %mainProg* %0, %mainProg** %self, align 8
113+
ret void
114+
}
115+
109116
; ModuleID = '__init___TestProject'
110117
source_filename = "__init___TestProject"
111118

@@ -117,9 +124,12 @@ source_filename = "__init___TestProject"
117124
define void @__init___TestProject() {
118125
entry:
119126
call void @__init_mainprog(%mainProg* @mainProg_instance)
127+
call void @__user_init_mainProg(%mainProg* @mainProg_instance)
120128
ret void
121129
}
122130

123131
declare void @__init_mainprog(%mainProg*)
124132

125133
declare void @mainProg(%mainProg*)
134+
135+
declare void @__user_init_mainProg(%mainProg*)

compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_files_with_debug_info.snap

+10
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ entry:
106106

107107
declare void @mainProg(%mainProg*)
108108

109+
define void @__user_init_mainProg(%mainProg* %0) {
110+
entry:
111+
%self = alloca %mainProg*, align 8
112+
store %mainProg* %0, %mainProg** %self, align 8
113+
ret void
114+
}
115+
109116
; ModuleID = '__init___TestProject'
110117
source_filename = "__init___TestProject"
111118

@@ -117,9 +124,12 @@ source_filename = "__init___TestProject"
117124
define void @__init___TestProject() {
118125
entry:
119126
call void @__init_mainprog(%mainProg* @mainProg_instance)
127+
call void @__user_init_mainProg(%mainProg* @mainProg_instance)
120128
ret void
121129
}
122130

123131
declare void @__init_mainprog(%mainProg*)
124132

125133
declare void @mainProg(%mainProg*)
134+
135+
declare void @__user_init_mainProg(%mainProg*)

compiler/plc_driver/src/tests/snapshots/plc_driver__tests__multi_files__multiple_source_files_generated.snap

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
source: compiler/plc_driver/./src/tests/multi_files.rs
33
expression: "results.join(\"\\n\")"
4+
snapshot_kind: text
45
---
56
; ModuleID = 'external_file1.st'
67
source_filename = "external_file1.st"
@@ -48,6 +49,13 @@ entry:
4849

4950
declare void @mainProg(%mainProg*)
5051

52+
define void @__user_init_mainProg(%mainProg* %0) {
53+
entry:
54+
%self = alloca %mainProg*, align 8
55+
store %mainProg* %0, %mainProg** %self, align 8
56+
ret void
57+
}
58+
5159
; ModuleID = '__init___TestProject'
5260
source_filename = "__init___TestProject"
5361

@@ -59,9 +67,12 @@ source_filename = "__init___TestProject"
5967
define void @__init___TestProject() {
6068
entry:
6169
call void @__init_mainprog(%mainProg* @mainProg_instance)
70+
call void @__user_init_mainProg(%mainProg* @mainProg_instance)
6271
ret void
6372
}
6473

6574
declare void @__init_mainprog(%mainProg*)
6675

6776
declare void @mainProg(%mainProg*)
77+
78+
declare void @__user_init_mainProg(%mainProg*)

compiler/plc_lowering/src/tests/inheritance_tests.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -1323,7 +1323,7 @@ mod units_tests {
13231323

13241324
let (_, project) = parse_and_annotate("test", vec![src]).unwrap();
13251325
let unit = &project.units[0].get_unit().implementations[3];
1326-
assert_debug_snapshot!(unit, @r###"
1326+
assert_debug_snapshot!(unit, @r#"
13271327
Implementation {
13281328
name: "main",
13291329
type_name: "main",
@@ -1350,6 +1350,26 @@ mod units_tests {
13501350
},
13511351
),
13521352
},
1353+
CallStatement {
1354+
operator: ReferenceExpr {
1355+
kind: Member(
1356+
Identifier {
1357+
name: "__user_init_child",
1358+
},
1359+
),
1360+
base: None,
1361+
},
1362+
parameters: Some(
1363+
ReferenceExpr {
1364+
kind: Member(
1365+
Identifier {
1366+
name: "fb",
1367+
},
1368+
),
1369+
base: None,
1370+
},
1371+
),
1372+
},
13531373
Assignment {
13541374
left: ReferenceExpr {
13551375
kind: Deref,
@@ -1481,7 +1501,7 @@ mod units_tests {
14811501
generic: false,
14821502
access: None,
14831503
}
1484-
"###)
1504+
"#)
14851505
}
14861506

14871507
#[test]

compiler/plc_project/src/project.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ impl<S: SourceContainer> Project<S> {
302302
/// Returns the symbol name of this projects main initializer function
303303
pub fn get_init_symbol_name(&self) -> &'static str {
304304
//Converts into static because this will live forever
305-
format!("__init___{}", self.get_name().replace('.', "_")).leak()
305+
format!("__init___{}", self.get_name().replace(['.', '-'], "_")).leak()
306306
}
307307
}
308308

src/codegen/tests/debug_tests.rs

+33
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ fn dbg_declare_has_valid_metadata_references_for_methods() {
380380
ret void
381381
}
382382
383+
define void @__user_init_fb(%fb* %0) {
384+
entry:
385+
%self = alloca %fb*, align 8
386+
store %fb* %0, %fb** %self, align 8
387+
ret void
388+
}
389+
383390
define void @__init___Test() {
384391
entry:
385392
ret void
@@ -487,9 +494,17 @@ fn action_with_var_temp() {
487494
ret void
488495
}
489496
497+
define void @__user_init_PLC_PRG(%PLC_PRG* %0) {
498+
entry:
499+
%self = alloca %PLC_PRG*, align 8
500+
store %PLC_PRG* %0, %PLC_PRG** %self, align 8
501+
ret void
502+
}
503+
490504
define void @__init___Test() {
491505
entry:
492506
call void @__init_plc_prg(%PLC_PRG* @PLC_PRG_instance)
507+
call void @__user_init_PLC_PRG(%PLC_PRG* @PLC_PRG_instance)
493508
ret void
494509
}
495510
@@ -616,6 +631,7 @@ END_FUNCTION
616631
call void @llvm.dbg.declare(metadata i16* %i, metadata !48, metadata !DIExpression()), !dbg !49
617632
store i16 0, i16* %i, align 2
618633
call void @__init_struct_(%struct_* %st), !dbg !50
634+
call void @__user_init_struct_(%struct_* %st), !dbg !50
619635
%s1 = getelementptr inbounds %struct_, %struct_* %st, i32 0, i32 2, !dbg !51
620636
%3 = bitcast [81 x i8]* %s to i8*, !dbg !51
621637
%4 = bitcast [81 x i8]* %s1 to i8*, !dbg !51
@@ -700,6 +716,23 @@ END_FUNCTION
700716
ret void
701717
}
702718
719+
define void @__user_init_inner(%inner* %0) {
720+
entry:
721+
%self = alloca %inner*, align 8
722+
store %inner* %0, %inner** %self, align 8
723+
ret void
724+
}
725+
726+
define void @__user_init_struct_(%struct_* %0) {
727+
entry:
728+
%self = alloca %struct_*, align 8
729+
store %struct_* %0, %struct_** %self, align 8
730+
%deref = load %struct_*, %struct_** %self, align 8
731+
%inner = getelementptr inbounds %struct_, %struct_* %deref, i32 0, i32 0
732+
call void @__user_init_inner(%inner* %inner)
733+
ret void
734+
}
735+
703736
define void @__init___Test() {
704737
entry:
705738
ret void

0 commit comments

Comments
 (0)