Skip to content

Commit

Permalink
docs(cfc): Architecture (PLC-lang#1007)
Browse files Browse the repository at this point in the history
Rough architectural overview of the CFC implementation.

Co-authored-by: Michael <[email protected]>
Co-authored-by: Michael <[email protected]>
  • Loading branch information
3 people authored Nov 20, 2023
1 parent 77cd41e commit a26aa0b
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 10 deletions.
2 changes: 2 additions & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@
- [Linker](./arch/linker.md)
- [Validation](./arch/validation.md)
- [Codegen](./arch/codegen.md)
- [CFC](./cfc/cfc.md)
- [Model-to-Model Conversion](./cfc/m2m.md)
30 changes: 26 additions & 4 deletions book/src/arch/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

## Overview

RuSTy is a Compiler for Structured Text.
It utilizes the llvm compiler infrastructurue and contributes a [Structured Text](https://en.wikipedia.org/wiki/Structured_text) Frontend that translates Structured Text into llvm's language independent intermediate representation (IR).
The Further optimization and native code generation is performed by the existing LLVM infrastructure, namely llvm's common optimizer and the platform specific backend (see [here](https://www.aosabook.org/en/llvm.html)).
RuSTy is a compiler for IEC61131-3 languages. At the moment, ST and CFC ("FBD") are supported.
It utilizes the LLVM compiler infrastructurue and contributes a [Structured Text](https://en.wikipedia.org/wiki/Structured_text) frontend that translates Structured Text into LLVM's language independent intermediate representation (IR).
[CFC](../cfc/cfc.md) uses a M2M-transformation and reuses most of the ST frontend for compilation.
The further optimization and native code generation is performed by the existing LLVM infrastructure, namely LLVM's common optimizer and the platform specific backend (see [here](https://www.aosabook.org/en/llvm.html)).

```ignore
┌──────────────────┐ ┌───────────────┐ ┌────────────────┐
Expand All @@ -21,10 +22,12 @@ This means that this compiler can benefit from llvm's existing compiler-optimiza

## Rusty Frontend Architecture

Ultimately the goal of a compiler frontend, is to translate the original source code into the infrastructure's intermediate representation (in this case we're talking about [LLVM IR](https://llvm.org/docs/LangRef.html)).
Ultimately the goal of a compiler frontend is to translate the original source code into the infrastructure's intermediate representation (in this case we're talking about [LLVM IR](https://llvm.org/docs/LangRef.html)).
RuSTy treats this task as a compilation step of its own.
While a fully fledged compiler generates machine code as a last step, RuSTy generates LLVM IR assembly code.

## Structured Text

```ignore
┌────────┐ ┌────────┐
│ Source │ │ LLVM │
Expand All @@ -41,3 +44,22 @@ While a fully fledged compiler generates machine code as a last step, RuSTy gene
│ │ │ │ │ │ │ │ │ │
└────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘
```

## CFC/FBD

```ignore
┌────────┐ ┌────────┐
│ Source │ │ LLVM │
│ │ │ IR │
│ Files │ │ │
└───┬────┘ └────────┘
│ ▲
▼ │
┌────────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────┴─────┐
│ │ │ │ │ │ │ │ │ │
│ Model-to-Model │ │ │ │ │ │ │ │ │
│ Transformation ├───►│ Indexer ├──►│ Linker ├──►│ Validation ├──►│ Codegen │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
└────────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘
```
1 change: 1 addition & 0 deletions book/src/cfc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# CFC
8 changes: 8 additions & 0 deletions book/src/cfc/cfc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# CFC (Continous Function Chart)

RuSTy is compatible with CFC, as per the FBD part detailed in the [IEC61131-3 XML-exchange format](https://www.plcopen.org/system/files/downloads/tc6_xml_v201_technical_doc.pdf).
The CFC implementation borrows extensively from the [ST compiler-pipeline](../arch/architecture.md), with the exception that the lexical analysis and parsing phases are replaced by a model-to-model conversion process.
This involves converting the XML into a structured model, which is then converted into ST AST statements.


The next chapter will walk you through the CFC implementation, giving you a better understanding of underlying [code](https://github.com/PLC-lang/rusty/tree/master/compiler/plc_xml).
134 changes: 134 additions & 0 deletions book/src/cfc/m2m.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Model-to-Model Conversion

As previously mentioned, the lexical and parsing phases are replaced by a model-to-model conversion process which consists of two steps:
1. Transform the input file (XML) into a data-model
2. Transform the data-model into an AST

## XML to Data-Model

Consider the heavily minified CFC file [`MyProgram.cfc`](m2m.md#myprogramcfc), which translates to the CFC chart below.
```ignore
x MyAdd
┌─────────────┐ ┌─────────────────┐
│ │ │ exec_id:0 │
│ ├───────►│ a │ z
│ local_id: 0 │ │ ref_local_id: 0 │ ┌──────────────┐
└─────────────┘ │ │ │ exec_id: 1 │
y │ ├─────────►│ │
┌─────────────┐ │ │ │ref_local_id:2│
│ │ │ │ └──────────────┘
│ ├───────►│ b │ local_id: 3
│ local_id:1 │ │ ref_local_id: 1 │
└─────────────┘ └─────────────────┘
local_id: 2
```

The initial phase of the transformation process involves streaming the entire input file.
During the streaming process, whenever important keywords such as `block` are encountered, they are directly mapped into a corresponding model structure.
For example, when reaching the line `<block localId="3" ...>` within the XML file, we generate a model that can be represented as follows:
```rust,ignore
struct Block {
localId: 2,
type_name: "MyAdd",
instance_name: None,
execution_order_id: 0,
variables: [
InputVariable { ... }, // x, with localId = 0
InputVariable { ... }, // y, with localId = 1
OutputVariable { ... }, // MyAdd eventually becoming `z := MyAdd`, with z having a localId = 2
]
}
```

This process is repeated for every element in the input file which has a corresponding model implementation. For more information on implementation details, see the [model](https://github.com/PLC-lang/rusty/tree/master/compiler/plc_xml/src/model) folder.

Since the CFC programming language utilizes blocks and their interconnections to establish the program's logic flow,
with the sequencing of block execution and inter-block links represented through corresponding `localId`, `refLocalId` and `excutionOrderId`,
we have to order each element by their execution ID before proceeding to the next phase.
Otherwise the generated AST statements would be out of order and hence semantically incorrect.

## Data-Model to AST
The final part of the model-to-model transformation takes the input from the previous step and transforms it into an AST which the compiler pipeline understands and can generate code from.
Consider the previous `block` example - the transformer first encounters the element with the `executionOrderId` of 0, which is a call to `myAdd`.
We then check and transform each parameter, input `a` and `b` corresponding to the variables `x` and `y` respectively. The result of this transformation looks as follows:

```rust,ignore
CallStatement {
operator: myAdd,
parameters: [x, y]
}
```

Next, we process the element with an `executionOrderId` of 1, which corresponds to an assignment of the previous call's result to z. This update modifies the generated AST as follows:

```rust,ignore
AssignmentStatement {
left: z,
right: CallStatement {
operator: myAdd,
parameters: [x, y]
}
}
```

While this explanation covers the handling of blocks and variables, there are other elements (e.g. control-flow), that are not discussed here. For more information on implementation details, see [`plc_xml/src/xml_parser`](https://github.com/PLC-lang/rusty/tree/master/compiler/plc_xml/src/xml_parser).

Finally, after transforming all elements into their respective AST statements, the result is passed to the indexer and subsequently enters the next stages of the compiler pipeline, as described in the [architecture documentation](../arch/architecture.md#rusty-frontend-architecture)).

## Appendix
### MyAdd.st
```st,ignore
FUNCTION MyAdd : DINT
VAR_INPUT
x, y : DINT;
END_VAR
MyAdd := x + y;
END_FUNCTION
```

### MyProgram.cfc
```xml,ignore
<pou xmlns="http://www.plcopen.org/xml/tc6_0201" name="myProgram" pouType="program">
<content>
PROGRAM myProgram
VAR
x, y, z : DINT;
END_VAR
</content>
<body>
<FBD>
<inVariable localId="1" height="20" width="80" negated="false">
<expression>x</expression>
</inVariable>
<inVariable localId="2" height="20" width="80" negated="false">
<expression>y</expression>
</inVariable>
<block localId="3" width="74" height="60" typeName="MyAdd" executionOrderId="0">
<inputVariables>
<variable formalParameter="x" negated="false">
<connectionPointIn>
<connection refLocalId="1"/>
</connectionPointIn>
</variable>
<variable formalParameter="y" negated="false">
<connectionPointIn>
<connection refLocalId="2"/>
</connectionPointIn>
</variable>
</inputVariables>
<outputVariables>
</variable formalParameter="MyAdd" negated="false">
</outputVariables>
</block>
<outVariable localId="4" height="20" width="80" executionOrderId="1" negated="false" storage="none">
<position x="680" y="160"/>
<connectionPointIn>
<connection refLocalId="3" formalParameter="MyAdd"/>
</connectionPointIn>
<expression>z</expression>
</outVariable>
</FBD>
</body>
</pou>
```
12 changes: 6 additions & 6 deletions book/src/using_rusty.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> The RuSTy compiler binary is called `plc`
`plc` offers a comprehensive help via the -h (--help) option.
`plc` offers a comprehensive help via the `-h` (`--help`) option.
`plc` takes one output-format parameter and any number of input-files.
The input files can also be written as [glob patterns](https://en.wikipedia.org/wiki/Glob_(programming)).

Expand All @@ -16,13 +16,13 @@ the output filename will consist of the first input filename, but with an approp
file extension depending on the output file format.

A minimal invocation looks like this:
`plc input.st` ... this will take in the file input.st and compile it into a static object that will be written to a file named input.o.
`plc input.st` this will take in the file `input.st` and compile it into a static object that will be written to a file named `input.o`.

More examples:

- `plc --ir file1.st file2.st` will compile file1.st and file2.st.
- `plc --ir src/*.st` will compile all st files in the src-folder.
- `plc --ir "**/*.st"` will compile all st-files in the current folder and its subfolders recursively.
- `plc --ir file1.cfc file2.st` will compile file1.cfc and file2.st.
- `plc --ir src/*.st` will compile all ST files in the src-folder.
- `plc --ir "**/*.st"` will compile all ST-files in the current folder and its subfolders recursively.

## Example: Building a hello world program

Expand All @@ -33,7 +33,7 @@ This example is available under `examples/hello_world.st` in the main RuSTy repo

- `main` is our entry point to the program.
- To link the program, we are going to use the system's linker using the `--linker=cc` argument.
- On windows, replace this with --linker=clang as cc is usually not available.
- On Windows and MacOS, replace this with `--linker=clang` as cc is usually not available.

```iecst
{external}
Expand Down
3 changes: 3 additions & 0 deletions compiler/plc_xml/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# plc_xml

This crate contains the CFC implementation of RuSTy, for more information refer to the [book](https://plc-lang.github.io/rusty/cfc/cfc.html).

0 comments on commit a26aa0b

Please sign in to comment.