Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 62 additions & 3 deletions book/src/libraries/api_lib_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 `__<FunctionBlockName>__init`, below is an example of a zero-initializer:

```c
myFunctionBlock __myFunctionBlock__init = { 0 };
```

3. Optionally create an initialization function following the naming pattern `<FunctionBlockName>_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)



22 changes: 22 additions & 0 deletions book/src/pous.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
26 changes: 26 additions & 0 deletions book/src/using_rusty.md
Original file line number Diff line number Diff line change
Expand Up @@ -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___<projectname>`. This function is responsible for calling all implicit and user-defined initialization code, including all [`FB_INIT`](../pous.md#function_block-initialization) methods.

`<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`).

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.

> **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___<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.

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;
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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*)
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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*)
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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"

Expand All @@ -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*)
24 changes: 22 additions & 2 deletions compiler/plc_lowering/src/tests/inheritance_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
Expand Down Expand Up @@ -1481,7 +1501,7 @@ mod units_tests {
generic: false,
access: None,
}
"###)
"#)
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion compiler/plc_project/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ impl<S: SourceContainer> Project<S> {
/// 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()
}
}

Expand Down
33 changes: 33 additions & 0 deletions src/codegen/tests/debug_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading