-
Notifications
You must be signed in to change notification settings - Fork 62
feat: Polymorphism (Classes and Function Blocks) #1493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
f7b7c11
to
04bc60d
Compare
`POINTER TO <function>` are now indexed in the codegen
…for less nosiy output
Previously when dealing with function pointer calls such as `fnPtr^()` the resolver would only annotate the operator and any arguments. Some codegen parts require an annotation on the whole call statement however (which makes sense), as such this commit fixes the described issue. Visuallized, assuming `fnPtr` points to a function returning a DINT: ``` fnPtr^(); ^^^^^^^^ -> StatementAnnotation::Value { resulting_type: "DINT" } ```
04bc60d
to
e79c95e
Compare
Function pointers can now point to the body of a function block, allowing for code execution such as ``` FUNCTION_BLOCK A VAR localState: DINT := 5; END_VAR METHOD foo // ... END_METHOD printf('localState = %d$N', localState); END_FUNCTION_BLOCK FUNCTION main VAR instanceA: A; fnBodyPtr: FNPTR A := ADR(A); END_VAR fnBodyPtr^(instanceA); // prints "localState = 5" END_FUNCTION ```
Also inject `__vtable` member variable into "root" POUs
Methods calls which must make use of the virtual table are now desugared. For example a method call within a method such as `foo(1, 2)` will now be desugared into `__vtable_{FB_NAME}#(THIS^.__vtable^).foo^(THIS^, 1, 2)`. Similarly, a variable like `refInstance: POINTER TO FbA` making a method call such as `refInstance^.foo(1, 2)` will be lowered into some similar form, except not making use of THIS, rather of the operator name (excluding the method name), i.e. `__vtable_FbA#(refInstance^.__vtable^).foo(refInstance^, 1, 2)`.
The `__vtable` member field is now initialized with a right hand side of `ADR(__vtable_<POU_NAME>_instance)` which represents the global instance variable of a virtual table that is initialized with its function pointers.
e79c95e
to
27a06a5
Compare
The `__body` function pointer now points at the function blocks body method in the virtual table struct. As a result, code execution like the following is now possible ``` VAR instanceA: A; instanceB: B; // Child of A refInstanceA: POINTER TO A; END_VAR refInstanceA := ADR(A); refInstanceA^(); // Calls body method of A refInstanceA := ADR(B); refInstanceA^(); // Calls body method of B ```
956a3eb
to
213376f
Compare
b3b3dec
to
521ed19
Compare
521ed19
to
68cdbf7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces polymorphism support for structured text by implementing virtual tables and method dispatch. The implementation enables dynamic method calls on function blocks and classes through pointer-based references.
Key changes:
- Virtual table infrastructure to support polymorphic method calls
- Method call lowering to use indirect function calls through virtual tables
- Test cases for various polymorphism scenarios including inheritance chains, method overriding, and reference types
Reviewed Changes
Copilot reviewed 103 out of 107 changed files in this pull request and generated no comments.
Show a summary per file
File | Description |
---|---|
tests/tests.rs | Removes classes module reference to fix test organization |
tests/lit/single/polymorphism/*.st | Comprehensive test suite covering polymorphic scenarios (inheritance, method calls, references) |
tests/lit/single/pointer/value_behind_function_block_pointer_is_assigned_to_correctly.st | Test for function block pointer assignment behavior |
tests/lit/single/oop/*.st | Minor formatting improvements for existing OOP tests |
tests/lit/multi/extern_C_fb_init/foo.c | Updates external C structure to include vtable pointer |
tests/lit/correctness/pointers/references/*.st | Reference handling tests moved to lit framework |
tests/lit/correctness/functions/*.st | Function block behavior tests moved to lit framework |
tests/lit/correctness/classes/class_reference_in_pou.st | Class reference test moved to lit framework |
tests/lit/correctness/lit.local.cfg | Configuration for lit test framework |
tests/integration/snapshots/*.snap | Updated snapshots reflecting vtable struct changes |
tests/correctness/*.rs | Test cases migrated from unit tests to lit framework |
src/lowering/vtable.rs | New module implementing virtual table generation for polymorphism |
src/validation/*.rs | Updates to support polymorphic type checking and inheritance validation |
src/typesystem.rs | Type system extensions for method and function block identification |
src/resolver.rs | Updates to method resolution for inheritance chains |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
%foo7 = getelementptr inbounds %__vtable_A, %__vtable_A* %cast6, i32 0, i32 1 | ||
%4 = load i16 (%A*, i32)*, i16 (%A*, i32)** %foo7, align 8 | ||
%deref8 = load %A*, %A** %refInstanceA, align 8 | ||
%fnptr_call9 = call i16 %4(%A* %deref8, i32 10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While writing these tests, I realized that the calls look somewhat "wrong". Here we call call i16 %4(%A* %deref8, i32 10)
but the signature in the virtual table of B expects i16 (%B*, i32)*
, that is we pass an object of type A
to a function pointer that would expect an object of B
. Technically wrong, but not really an issue because up- and downcasting for child POUs works. A "much cleaner" version of this however would be consistent function signatures, i.e. inherit the types of the base classes and explicitly type cast at the start of a method. For example
%__vtable_A = type { void (%A*)*, i16 (%A*, i32)* }
-%__vtable_B = type { void (%B*)*, i16 (%B*, i32)* }
+%__vtable_B = type { void (%A*)*, i16 (%A*, i32)* }
and
-define i16 @B__foo(%B* %0, i32 %1) {
+define i16 @B__foo(%A* %0, i32 %1) {
+%this = bitcast %A* %0 to %B*
Not a blocker, just wanted to mention this as a general fyi.
This PR is the first of two introducing polymorphism to the compiler. This PR focuses on polymorphism for classes and function blocks whereas the second will focus on introducing interfaces and validations. For reviewing this monster of a PR, best to start reading either the tests or
lowering/vtable.rs
andlowering/polymorphism.rs
before exploring other files.In short, polymorphism enables dynamic dispatch allowing for user code such as the following to execute
This is achieved by calling methods indirectly through a virtual table. Any class or function block will generate a
__vtable_<POU_NAME>
struct which will be part of the POU of the following formTaking the previous example, the compiler would generate
Calling methods then invoke the virtual table to access the correct method, deciding which method needs to be called at runtime. This happens by transforming the method calls such as
ADR(getName())
to__vtable_A#(THIS^.__vtable^).getName^(THIS^))
. For child POUs this means upcasting their virtual table to their parent one.The
vtable
member fields thereby will be initialized in the corresponding__init
function, pointing to some global variable instance. That is, every POU has a global variable instance of formFinally, the final form of the previous example as transformed by the compiler would look as follows: