Skip to content

Commit 2670c7c

Browse files
ghaithclaude
andcommitted
test(lit): cover cross-unit __vtable__ctor in FB member (Phase 4.5 revert scenario)
A FUNCTION_BLOCK that holds a polymorphic FB as a member must, in its synthesised constructor, call the member's __ctor — which in turn must call __vtable__ctor(self.__vtable) for the member. The Phase 4.5 revert (commit 85181cb) backed out a UnitLowerer migration of the polymorphism table pass that broke this chain: 13 initializer snapshots all lost the __vtable__ctor(self.__vtable) line because the table pass and dispatch pass diverged across two PolymorphismLowerer instances. This lit test exercises the chain end-to-end across three compilation units (Base, Derived, Wrapper holding Derived as a member) and dispatches through a POINTER TO Base aimed at the embedded member. If the member's vtable is not initialised, the dispatch lands on uninitialised memory and the CHECK line fails. The test thus documents what the `Rc<RefCell<PolymorphismLowerer>>` workaround in participant.rs is load-bearing for and gates a future re-attempt of the table-pass split. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8ee1641 commit 2670c7c

6 files changed

Lines changed: 66 additions & 0 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FUNCTION main : DINT
2+
VAR
3+
w : Wrapper;
4+
ptr : POINTER TO Base;
5+
END_VAR
6+
7+
// Dispatch through a base pointer aimed at the embedded Derived. This
8+
// only works if Wrapper's constructor chain set up `w.member.__vtable`,
9+
// which in turn requires `Derived__ctor(self.member)` to be present in
10+
// Wrapper's synthesised constructor.
11+
ptr := ADR(w.member);
12+
13+
// CHECK: kind via base ptr -> wrapper.member = 2
14+
printf('kind via base ptr -> wrapper.member = %d$N', ptr^.kind());
15+
END_FUNCTION
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "CrossUnitVtableCtorInMember",
3+
"files": [
4+
"main.st",
5+
"src/base.st",
6+
"src/derived.st",
7+
"src/wrapper.st",
8+
"../../util/printf.pli"
9+
],
10+
"compile_type": "Static",
11+
"libraries": []
12+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
RUN: %COMPILE build plc.json && %RUN | %CHECK %S/main.st
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Base FB lives in its own compilation unit. The polymorphism participant
2+
// emits a vtable type and instance for Base; the per-unit re-index has to
3+
// make those visible to the initializer participant before it synthesizes
4+
// the wrapper's constructor (see Phase 4.5 revert: a split of the
5+
// polymorphism table pass into its own UnitLowerer broke 13 initializer
6+
// snapshots because `__vtable__ctor(self.__vtable)` stopped being emitted
7+
// for FBs whose vtables had been split off into a separate pass).
8+
9+
FUNCTION_BLOCK Base
10+
METHOD PUBLIC kind : DINT
11+
kind := 1;
12+
END_METHOD
13+
END_FUNCTION_BLOCK
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Derived FB overrides `kind`. Lives in a separate compilation unit so its
2+
// vtable is emitted in this unit while Wrapper's vtable lives elsewhere.
3+
// Dispatch through a `POINTER TO Base` aimed at `wrapper.member` must
4+
// resolve through the right vtable for the actual `Derived` instance — if
5+
// `Derived`'s `__vtable__ctor` was dropped from `Wrapper`'s constructor
6+
// chain, the dispatch would land on uninitialized memory.
7+
8+
FUNCTION_BLOCK Derived EXTENDS Base
9+
METHOD PUBLIC kind : DINT
10+
kind := 2;
11+
END_METHOD
12+
END_FUNCTION_BLOCK
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Wrapper holds a `Derived` as a member field. The initializer
2+
// participant must synthesize a constructor for `Wrapper` that:
3+
// 1. Initialises Wrapper's own vtable (`__Wrapper___vtable__ctor(self.__vtable)`).
4+
// 2. Calls `Derived__ctor(self.member)` so Derived's constructor runs and
5+
// sets up `self.member.__vtable`.
6+
// Both steps cross compilation-unit boundaries; the post-index re-index
7+
// has to make both vtables visible to this unit's initializer pass.
8+
9+
FUNCTION_BLOCK Wrapper
10+
VAR
11+
member : Derived;
12+
END_VAR
13+
END_FUNCTION_BLOCK

0 commit comments

Comments
 (0)