Skip to content

Commit a6ea592

Browse files
committed
update book
1 parent 13e1373 commit a6ea592

File tree

10 files changed

+145
-22
lines changed

10 files changed

+145
-22
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_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/generators/llvm.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use inkwell::{
1212
values::{BasicValue, BasicValueEnum, GlobalValue, IntValue, PointerValue},
1313
AddressSpace,
1414
};
15-
use plc_ast::ast::{AstNode, LinkageType};
15+
use plc_ast::ast::AstNode;
1616
use plc_diagnostics::diagnostics::Diagnostic;
1717
use plc_source::source_location::SourceLocation;
1818

@@ -318,9 +318,6 @@ impl<'a> Llvm<'a> {
318318
exp_gen: &ExpressionCodeGenerator,
319319
) -> Result<(), Diagnostic> {
320320
let (qualified_name, type_name, location) = variable;
321-
if index.find_pou(type_name).is_some_and(|it| it.get_linkage() == &LinkageType::External) {
322-
return Ok(());
323-
}
324321
let variable_llvm_type =
325322
llvm_index.get_associated_type(type_name).map_err(|err| err.with_location(location))?;
326323

src/codegen/generators/pou_generator.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use inkwell::{
3838
types::{BasicType, StructType},
3939
values::PointerValue,
4040
};
41-
use plc_ast::ast::{AstNode, Implementation, LinkageType, PouType};
41+
use plc_ast::ast::{AstNode, Implementation, PouType};
4242
use plc_diagnostics::diagnostics::{Diagnostic, INTERNAL_LLVM_ERROR};
4343
use plc_source::source_location::SourceLocation;
4444
use rustc_hash::FxHashMap;
@@ -110,10 +110,8 @@ pub fn generate_global_constants_for_pou_members<'ink>(
110110
.filter(|it| it.is_in_unit(location));
111111
for implementation in implementations {
112112
let type_name = implementation.get_type_name();
113-
if implementation.is_init()
114-
|| index.find_pou(type_name).is_some_and(|it| it.get_linkage() == &LinkageType::External)
115-
{
116-
// initializer functions and externals don't need global constants to initialize members
113+
if implementation.is_init() {
114+
// initializer functions don't need global constants to initialize members
117115
continue;
118116
}
119117
let pou_members = index.get_pou_members(type_name);

src/lowering.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ impl InitVisitor {
3737
let mut visitor = Self::new(index, unresolvables, id_provider);
3838
// before visiting, we need to collect all candidates for user-defined init functions
3939
units.iter().for_each(|unit| {
40-
// TODO: probably also need to consider structs here
4140
visitor.collect_user_init_candidates(unit);
4241
});
4342
// visit all units
@@ -72,14 +71,22 @@ impl InitVisitor {
7271
}
7372

7473
fn collect_user_init_candidates(&mut self, unit: &CompilationUnit) {
75-
// TODO: probably also need to consider structs here
7674
// collect all candidates for user-defined init functions
7775
for pou in unit.pous.iter().filter(|it| matches!(it.kind, PouType::FunctionBlock | PouType::Program))
7876
{
7977
// add the POU to potential `FB_INIT` candidates
8078
self.user_inits
8179
.insert(pou.name.to_owned(), self.index.find_method(&pou.name, "FB_INIT").is_some());
8280
}
81+
82+
for user_type in
83+
unit.user_types.iter().filter(|it| matches!(it.data_type, DataType::StructType { .. }))
84+
{
85+
// add the struct to potential `STRUCT_INIT` candidates
86+
if let Some(name) = user_type.data_type.get_name() {
87+
self.user_inits.insert(name.to_string(), false);
88+
};
89+
}
8390
}
8491

8592
fn update_initializer(&mut self, variable: &mut plc_ast::ast::Variable) {
@@ -358,9 +365,6 @@ impl AstVisitorMut for InitVisitor {
358365

359366
fn visit_data_type(&mut self, data_type: &mut DataType) {
360367
if matches!(data_type, plc_ast::ast::DataType::StructType { .. }) {
361-
if let Some(name) = data_type.get_name() {
362-
self.user_inits.insert(name.to_string(), false);
363-
};
364368
self.walk_with_scope(data_type, data_type.get_name().map(ToOwned::to_owned))
365369
} else {
366370
data_type.walk(self)

tests/lit/multi/extern_C_fb_init/foo.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ typedef struct {
55
int b;
66
} myFunctionBlock;
77

8-
// myFunctionBlock __myFunctionBlock__init;
8+
myFunctionBlock __myFunctionBlock__init = { 0 };
99

1010
void myFunctionBlock_FB_INIT(myFunctionBlock* fb_instance) {
1111
fb_instance->a = 1;
-15.1 KB
Binary file not shown.

tests/lit/multi/extern_C_fb_init/lit.local.cfg

+20-3
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,44 @@
11
# Override the compile command to include the custom library
22
import os.path
3+
import subprocess
34

45
# Access the same parameters that the main configuration uses
56
stdlibLocation = lit_config.params["LIB"]
67
compilerLocation = lit_config.params["COMPILER"]
78
rustyRootDirectory = "/home/michael/dev/rusty"
89

910
test_dir = os.path.dirname(__file__)
10-
lib_path = os.path.abspath(test_dir)
11+
source_path = os.path.abspath(test_dir)
12+
13+
# Use tmp directory for compiled library
14+
tmp_lib_path = "/tmp"
15+
tmp_lib_file = f"{tmp_lib_path}/libfoo.so"
16+
17+
# Compile foo.c to libfoo.so in the tmp directory
18+
try:
19+
lit_config.note(f"Compiling foo.c into {tmp_lib_file}...")
20+
gcc_cmd = f"gcc -shared -fPIC -o {tmp_lib_file} {source_path}/foo.c"
21+
lit_config.note(f"Running: {gcc_cmd}")
22+
result = subprocess.run(gcc_cmd, shell=True, check=True,
23+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
24+
lit_config.note(f"Successfully compiled {tmp_lib_file}")
25+
except subprocess.CalledProcessError as e:
26+
lit_config.error(f"Failed to compile foo.c: {e.stderr.decode()}")
27+
raise
1128

1229
# Build on the existing compile command but add our custom library
1330
compile = f'{compilerLocation}'
1431
compile = f'{compile} -o /tmp/%basename_t.out'
1532
compile = f'{compile} -liec61131std -L{stdlibLocation}/lib -i "{stdlibLocation}/include/*.st"'
1633
compile = f'{compile} -i "{rustyRootDirectory}/tests/lit/util/*.pli"'
17-
compile = f'{compile} -L{lib_path} -lfoo -i {lib_path}/header.pli'
34+
compile = f'{compile} -L{tmp_lib_path} -lfoo -i {source_path}/header.pli'
1835
compile = f'{compile} --linker=cc'
1936

2037
# Log the compile command
2138
lit_config.note(f"Compile command: {compile}")
2239

2340
# Update the run command to include the custom library path
24-
run_cmd = f'LD_LIBRARY_PATH="{stdlibLocation}/lib:{lib_path}" /tmp/%basename_t.out'
41+
run_cmd = f'LD_LIBRARY_PATH="{stdlibLocation}/lib:{tmp_lib_path}" /tmp/%basename_t.out'
2542

2643
# Override the substitutions
2744
config.substitutions = [s for s in config.substitutions if s[0] not in ['%COMPILE', '%RUN']]

0 commit comments

Comments
 (0)