Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 6 additions & 1 deletion .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ jobs:

test-linux:
name: Test Linux
runs-on: ubuntu-latest
runs-on: ${{ matrix.config.os }}
strategy:
matrix:
config:
- { os: "ubuntu-latest" }
- { os: "ubuntu-24.04-arm" }
container: ghcr.io/plc-lang/rust-llvm:latest
steps:
- uses: actions/checkout@v3
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/lit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ on:
jobs:
lit-linux-debug:
name: lit tests (Linux, debug build)
runs-on: ubuntu-latest
runs-on: ${{ matrix.config.os }}
strategy:
matrix:
config:
- { os: "ubuntu-latest" }
- { os: "ubuntu-24.04-arm" }
container: ghcr.io/plc-lang/rust-llvm:latest
steps:
- uses: actions/checkout@v3
Expand All @@ -25,11 +30,16 @@ jobs:
lit-linux-release:
name: lit tests (Linux, release build)
runs-on: ubuntu-latest
strategy:
matrix:
config:
- { os: "ubuntu-latest" }
- { os: "ubuntu-24.04-arm" }
container: ghcr.io/plc-lang/rust-llvm:latest
steps:
- uses: actions/checkout@v3

- name: Run `build.sh --lit --release`
shell: bash
run: |
./scripts/build.sh --lit --release
./scripts/build.sh --lit --release
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ plc_util = { path = "./compiler/plc_util" }
plc_diagnostics = { path = "./compiler/plc_diagnostics" }
plc_index = { path = "./compiler/plc_index" }
section_mangler = { path = "./compiler/section_mangler" }
plc_llvm = { path = "./compiler/plc_llvm" }
logos = "0.12.0"
clap = { version = "3.0", features = ["derive"] }
indexmap = "2.0"
Expand All @@ -31,6 +32,7 @@ regex = "1"
shell-words = "1.1.0"
plc_derive = { path = "./compiler/plc_derive" }
lld_rs = { git = "https://github.com/mun-lang/lld-rs", rev = "3798ace" }

which = "4.2.5"
log.workspace = true
inkwell.workspace = true
Expand Down Expand Up @@ -79,6 +81,7 @@ members = [
"compiler/plc_index",
"compiler/section_mangler",
"compiler/plc_lowering",
"compiler/plc_llvm",
"tests/test_utils",
]
default-members = [".", "compiler/plc_driver", "compiler/plc_xml"]
Expand Down
167 changes: 167 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Initializer Implementation Status

## Overview

The goal is for each datatype/POU to get a constructor (`<TypeName>_ctor`), and then a global constructor is created with calls to all constructors in the unit.

## Current Implementation

### What's Already Created (in `plc_lowering/src/initializer.rs`)

#### 1. Per-Type Constructors (`<TypeName>_ctor`)
- [x] Collects constructor body statements for each POU and struct
- [x] Tracks `Body::Internal`, `Body::External`, or `Body::None` per type
- [x] Generates assignment statements (`self.field := value`) for variables with initializers
- [x] Handles nested struct initializers
- [x] Generates call to user-defined `FB_INIT` method if present
- [x] Creates the constructor POU and Implementation AST nodes via `new_constructor()`
- [x] Adds constructors to the CompilationUnit

#### 2. Stack Constructors (for temp/local variables in stateless POUs)
- [x] Tracks per-POU (`stack_constructor` map)
- [ ] **NOT YET APPLIED** to function bodies

#### 3. Global Constructor Statements
- [x] Collects statements for global variables into `global_constructor` Vec
- [ ] **NOT YET GENERATED** as a function

---

## What's Missing

### 1. Global Constructor Function (`__global_ctor`)

The `global_constructor` Vec collects statements but no function is created.

**Need to generate a `__global_ctor` POU that:**
- Calls each type's `<TypeName>_ctor` for global instances
- Contains the collected `global_constructor` assignment statements
- Is marked for static initialization (via `.init_array` / LLVM global constructors)

**Location:** `compiler/plc_lowering/src/initializer.rs` - `apply_initialization()` method (around line 240)

### 2. Stack Constructor Integration

The `stack_constructor` map collects statements for temp/local vars but they are not applied.

**Need to:**
- Prepend `stack_constructor[pou_name]` statements to each function's implementation body

**Location:** `compiler/plc_lowering/src/initializer.rs` - `apply_initialization()` method (around line 240)

### 3. Constructor Call Chain

When a struct member is of a struct type, the constructor should call the member's constructor before applying field overrides.

**Current behavior:** Tries to add constructor call (lines 134-139) but may not be matching correctly for all cases.

**Need to verify:**
- Constructor calls are generated for nested struct members
- Order is correct: first call nested constructor, then apply overrides

### 4. Features from Old Implementation Not Yet Ported

Comparing old `InitVisitor` (in `src/lowering/init_visitor.rs` + `src/lowering/initializers.rs`) vs new `Initializer`:

| Feature | Old (`InitVisitor`) | New (`Initializer`) |
|---------|---------------------|---------------------|
| Type constructors (`__init_<type>`) | Done | Done (`<type>_ctor`) |
| User init functions (`__user_init_<type>`) | Done | Missing |
| Global wrapper (`__init___<project>`) | Done | Missing |
| VAR_CONFIG init | Done | Missing |
| VTable init in constructor | Done | Missing |
| Stack var init in functions | Done | Collected but not applied |
| External linkage handling | Done | Done |

---

## Implementation Plan

### Step 1: Generate Global Constructor

In `apply_initialization()`, after adding all type constructors:

```rust
// Create __global_ctor function
if !self.global_constructor.is_empty() {
let mut global_ctor_body = vec![];

// Add calls to constructors for global struct instances
for (var_name, var_type) in global_struct_instances {
if self.constructors.contains_key(&var_type) {
let call = create_call_statement(
&format!("{}_ctor", var_type),
var_name,
None,
self.id_provider.clone(),
&SourceLocation::internal(),
);
global_ctor_body.push(call);
}
}

// Add collected assignment statements
global_ctor_body.extend(self.global_constructor);

// Create the POU and implementation
let (pou, impl) = new_global_constructor("__global_ctor", global_ctor_body, ...);
unit.pous.push(pou);
unit.implementations.push(impl);
}
```

### Step 2: Apply Stack Constructors

Modify function implementations to include stack initialization:

```rust
// For each implementation in unit.implementations
for impl in &mut unit.implementations {
if let Some(Body::Internal(stmts)) = self.stack_constructor.get(&impl.name) {
// Prepend stack constructor statements to the implementation body
let mut new_body = stmts.clone();
new_body.extend(impl.statements.drain(..));
impl.statements = new_body;
}
}
```

### Step 3: Ensure Constructor Call Chain

In `visit_variable()`, verify that for struct-typed variables:
1. First, call the struct's constructor: `<StructType>_ctor(self.var_name)`
2. Then, apply any field overrides from the initializer

### Step 4: Port Remaining Features

1. **VTable initialization:** Add `self.__vtable := ADR(__vtable_<type>_instance)` to constructor body for classes/FBs
2. **User init functions:** Generate `__user_init_<type>` that calls `FB_INIT` if present
3. **VAR_CONFIG init:** Handle VAR_CONFIG initialization in global constructor

---

## Files to Modify

- `compiler/plc_lowering/src/initializer.rs` - Main implementation
- `src/lowering/helper.rs` - May need additional helper functions
- `compiler/plc_driver/src/pipelines/participant.rs` - InitParticipant integration

## Testing

Existing tests in `compiler/plc_lowering/src/initializer.rs` cover:
- Struct constructors
- Nested structs
- Pointer initializers
- Enum defaults
- Global constructor collection
- Function/program constructors
- External types
- FB_INIT calls
- Inheritance chains

Additional tests needed:
- [ ] Global constructor function generation
- [ ] Stack constructor application to function bodies
- [ ] Constructor call chain for nested structs
- [ ] VTable initialization
- [ ] Integration with codegen (actual LLVM output)
59 changes: 57 additions & 2 deletions compiler/plc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,26 @@ impl Pou {
pub fn is_generic(&self) -> bool {
!self.generics.is_empty()
}

pub fn is_stateful(&self) -> bool {
matches!(self.kind, PouType::Program | PouType::FunctionBlock | PouType::Action | PouType::Class)
}

pub fn is_external(&self) -> bool {
matches!(self.linkage, LinkageType::External)
}

pub fn is_built_in(&self) -> bool {
matches!(self.linkage, LinkageType::BuiltIn)
}

pub fn is_function_block(&self) -> bool {
matches!(self.kind, PouType::FunctionBlock)
}

pub fn is_class(&self) -> bool {
matches!(self.kind, PouType::Class)
}
}

#[derive(Debug, PartialEq)]
Expand All @@ -313,11 +333,19 @@ pub struct Implementation {
pub access: Option<AccessModifier>,
}

/// Marks declaration and linking requirements for an ast member
#[derive(Debug, Copy, PartialEq, Eq, Clone, Hash)]
pub enum LinkageType {
/// The element is declared in the project currently being complied
Internal,
/// The element is declared externally and being used by the project
External,
/// This indicates an element that should not have any declarations within the compiled project
/// For example a built in function is implied to exist but not declared
BuiltIn,
// TODO: A private linkage indicates an internal element that should not be visible externally
// This is for example a static constructor that should not leak outside its module
// Private,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
Expand Down Expand Up @@ -480,6 +508,19 @@ pub enum VariableBlockType {
InOut,
External,
}
impl VariableBlockType {
pub fn is_temp(&self) -> bool {
matches!(self, VariableBlockType::Temp)
}

pub fn is_local(&self) -> bool {
matches!(self, VariableBlockType::Local)
}

pub fn is_global(&self) -> bool {
matches!(self, VariableBlockType::Global)
}
}

impl Display for VariableBlockType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Expand Down Expand Up @@ -517,6 +558,11 @@ impl VariableBlock {
VariableBlock::default().with_block_type(VariableBlockType::Global)
}

pub fn with_linkage(mut self, linkage: LinkageType) -> Self {
self.linkage = linkage;
self
}

pub fn with_block_type(mut self, block_type: VariableBlockType) -> Self {
self.kind = block_type;
self
Expand All @@ -526,6 +572,14 @@ impl VariableBlock {
self.variables = variables;
self
}

pub fn is_local(&self) -> bool {
matches!(self.kind, VariableBlockType::Local)
}

pub fn is_temp(&self) -> bool {
matches!(self.kind, VariableBlockType::Temp)
}
}

impl Default for VariableBlock {
Expand Down Expand Up @@ -640,9 +694,9 @@ impl DataTypeDeclaration {
}
}

pub fn get_referenced_type(&self) -> Option<String> {
pub fn get_referenced_type(&self) -> Option<&str> {
let DataTypeDeclaration::Reference { referenced_type, .. } = self else { return None };
Some(referenced_type.to_owned())
Some(referenced_type.as_str())
}

pub fn get_inner_pointer_ty(&self) -> Option<DataTypeDeclaration> {
Expand Down Expand Up @@ -679,6 +733,7 @@ pub struct UserTypeDeclaration {
pub location: SourceLocation,
/// stores the original scope for compiler-generated types
pub scope: Option<String>,
pub linkage: LinkageType,
}

impl Debug for UserTypeDeclaration {
Expand Down
Loading
Loading