Conversation
Added parser tests for retain variables Added index entries and test for retain variables in globals, programs and fbs.
A retain variable in a program is added to the global vars and a reference to the variable is added to the program If a variable contains a retain variable nested inside it is also treated as retain
During codegen, if a global variable should be retained add it to the .retain linker section
There was a problem hiding this comment.
Pull request overview
Adds support for RETAIN variable blocks by propagating retain-ness through the type system, carrying retain metadata through indexing, and emitting retained globals into a dedicated LLVM linker section. Also introduces a new lowering pipeline participant intended to move/indirect retain variables, plus associated test/snapshot updates.
Changes:
- Add
is_retaintracking to variable indexing and implement transitiveshould_retainchecks via datatype membership/aliasing/arrays. - Emit globals that
should_retaininto the LLVM section.retain. - Introduce a new lowering pass (
plc_lowering::retain) and update parser/index/codegen tests & snapshots for the new retain/constant debug output and fields.
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/typesystem.rs |
Adds recursive should_retain computation on datatypes (struct/alias/array). |
src/index.rs |
Adds is_retain flag to VariableIndexEntry and should_retain() helper for codegen/lowering decisions. |
src/index/indexer/pou_indexer.rs |
Plumbs block.retain into member variables (is_retain). |
src/index/indexer/global_var_indexer.rs |
Plumbs retain into global variables (is_retain) via VarGlobalIndexer. |
src/index/indexer.rs |
Passes block.retain into the global var indexer. |
src/index/indexer/user_type_indexer.rs |
Ensures struct members are never directly marked retain (but can be contained in retained types). |
src/codegen/generators/llvm.rs |
Adds GlobalValueExt::make_retain() which sets the LLVM section to .retain. |
src/codegen/generators/variable_generator.rs |
Marks globals as retain in IR when global_variable.should_retain(...) is true. |
compiler/plc_lowering/src/retain.rs |
New lowering pass intended to move retain vars into global retain blocks and add indirection for PROGRAM retain vars. |
compiler/plc_lowering/src/lib.rs |
Exposes the new retain module. |
compiler/plc_driver/src/pipelines.rs |
Adds RetainParticipant into the driver pipeline. |
compiler/plc_driver/src/pipelines/participant.rs |
Implements PipelineParticipantMut for RetainParticipant (runs post-index, then re-indexes). |
compiler/plc_ast/src/ast.rs |
Updates Debug formatting of VariableBlock to conditionally include constant/retain fields. |
compiler/plc_source/src/source_location.rs |
Minor simplification (or_else → or). |
compiler/plc_diagnostics/src/reporter/codespan.rs |
Minor simplification (unwrap_or_else → unwrap_or). |
src/lowering/calls.rs |
Fixes assignment into boxed operator (*stmt.operator = ...). |
src/parser/tests/variable_parser_tests.rs |
Adds parsing tests for RETAIN on variable blocks. |
src/tests/adr/vla_adr.rs |
Updates ADR expectations to include is_retain: false. |
src/tests/adr/pou_adr.rs |
Updates ADR expectations to include is_retain: false. |
src/resolver/tests/resolve_expressions_tests.rs |
Updates expected variable entry to include is_retain: false. |
src/index/tests/interface_tests.rs |
Updates expected variable entries to include is_retain: false. |
src/index/tests/index_tests.rs |
Adds retain-related index tests and updates expected entries with is_retain. |
src/codegen/tests/code_gen_tests.rs |
Adds .retain section assertions for retained globals/program instances and whitespace normalization. |
src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__date_and_time_constants_test.snap |
Snapshot update due to VariableBlock debug now showing constant: true. |
src/lowering/snapshots/*.snap |
Snapshot updates adding is_retain: false in debug outputs. |
src/index/tests/snapshots/*.snap |
Snapshot updates adding is_retain: false in debug outputs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 42 out of 42 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/codegen/generators/variable_generator.rs:194
- Potential section override issue: when online_change is enabled, the section is set again on line 194, which will override the retain section set on line 175. Consider combining the retain section with the online_change section or checking if retain variables are compatible with online_change.
if global_variable.should_retain(self.global_index) {
global_ir_variable = global_ir_variable.make_retain();
};
let global_name = if global_variable.get_name().ends_with("instance") {
global_variable.get_name()
} else {
global_variable.get_qualified_name()
};
let global_name = global_name.to_lowercase();
if self.online_change.is_enabled() {
let section = SectionMangler::variable(
global_name,
section_names::mangle_type(
self.global_index,
self.global_index.get_effective_type_by_name(global_variable.get_type_name())?,
)?,
)
.mangle();
global_ir_variable.set_section(Some(§ion));
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| FUNCTION main | ||
| VAR | ||
| VAR |
There was a problem hiding this comment.
Duplicate VAR declaration found. Remove one of these VAR keywords as it appears to be unintentional.
| VAR |
compiler/plc_lowering/src/retain.rs
Outdated
| unit.global_vars | ||
| .iter_mut() | ||
| .find(|block| block.retain) | ||
| .map(|block| block.variables.append(&mut self.context.retain_variables)) |
There was a problem hiding this comment.
Inconsistent handling of retain_variables Vec after appending. When an existing retain block is found and variables are appended, the retain_variables Vec is not cleared, which could lead to duplicates if visit_compilation_unit is called multiple times or if the Vec is used elsewhere. Consider using drain() consistently or explicitly clearing the Vec after append.
| .map(|block| block.variables.append(&mut self.context.retain_variables)) | |
| .map(|block| { | |
| block | |
| .variables | |
| .extend(self.context.retain_variables.drain(..)); | |
| }) |
|
|
||
| %main = type { i16, [81 x i8] } | ||
|
|
||
| @main_instance = global %main zeroinitializer, section ".retain" |
There was a problem hiding this comment.
Variables in PROGRAMS that are marked as retain of have variables in their datatype that are retained are converted into a pointer to a global retain variable
From the comment I would have assumed we create internal global variables, mark them as retain and point to them. According to this codegen output, we make the whole instance retain instead?
There was a problem hiding this comment.
good catch this seems wrong to me, this should have made variables pointers and created 2 new gobal vars
There was a problem hiding this comment.
the funny thing is, the application examples we are using for testing did this correctly, maybe the codegen method here is not lowering?
There was a problem hiding this comment.
Try running with test_utils::codegen perhaps
| } | ||
|
|
||
| pub fn should_retain(&self, index: &Index) -> bool { | ||
| self.should_retain_recursive(index, BTreeSet::new()) |
There was a problem hiding this comment.
Just curious, why a BTreeSet? Could've been a simply HashSet as well right?
There was a problem hiding this comment.
I plead the 5th..
Honestly not sure anymore what the thought process here was..
| //! This module handles indirection for retain variables in PROGRAMS | ||
| //! It also moves retain variables that are declared in non retain blocks into retain blocks. |
There was a problem hiding this comment.
Burn some tokens and let your clanker write a nice module documentation here please. Like a small introduction as to what this module is responsible for with perhaps an example.
| if let Some(variable_index) = | ||
| self.index.find_variable(self.context.container_name.as_deref(), &[variable.get_name()]) | ||
| { |
There was a problem hiding this comment.
This should always yield Some(...) and otherwise is a bug in our compiler. I would hard-unwrap here, or at the very least log an internal error.
| { | ||
| // If the variable should be retained | ||
| if variable_index.should_retain(&self.index) { | ||
| if self.context.in_program { |
There was a problem hiding this comment.
Does retain only make sense for global variables and programs or stateful POUs (i.e. function blocks)? Because this logic only covers the former I believe?
| fn visit_variable_block(&mut self, block: &mut plc_ast::ast::VariableBlock) { | ||
| let variables = std::mem::take(&mut block.variables); | ||
| for mut variable in variables { | ||
| if let Some(variable_index) = |
There was a problem hiding this comment.
I feel like this can benefit from de-nesting in general / early returns, but probably more of a nit than anything important really.
| } else if matches!(block.kind, plc_ast::ast::VariableBlockType::Global) && !block.retain { | ||
| self.context.retain_variables.push(variable); |
There was a problem hiding this comment.
This covers nested datatypes? I'm just confused because the if says "!block.retain" but it's guarded by the should_retain check, so I'm assuming it has to with nested datatypes? Maybe a brief comment would help?
|
|
||
| let unit = &project.units[0]; | ||
| // Expect the unit to have a global retain variable and the original variable to be replaced with a auto reference to the retain variable | ||
| assert_debug_snapshot!(unit, @r#" |
There was a problem hiding this comment.
Let's not be lazy here and make use of a helper function perhaps instead of snapshot-testing the whole unit?
| }, | ||
| ], | ||
| variable_block_type: Local, | ||
| retain: true, |
There was a problem hiding this comment.
The (now) replaced variable still contains retain information. Is this intentional given its now just a pointer to a retain variable?
Adds support for retain sections in the code.
If a global variable is marked as retain, or has in its datatype any variable that is marked as retain, the variable will be added tot he
retainsection.Variables in PROGRAMS that are marked as retain of have variables in their datatype that are retained are converted into a pointer to a global retain variable. That variable is then in turn placed int he retain section during codegen.