https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128158
Bug ID: 128158
Summary: missed devirtualization after an equivalent two-step
cast to a virtual base
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Keywords: missed-optimization
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: yqtian668 at gmail dot com
Target Milestone: ---
Target: x86_64-pc-linux-gnu
Created attachment 65955
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65955&action=edit
Preprocessed testcase (gcc-missed-vbase-cast-path.ii)
At `-O2`, GCC leaves a guarded indirect call after converting a complete local
`C` object to its virtual base `A` through `B`. GCC devirtualizes the
equivalent direct conversion from `C` to `A`. The difference is already present
in the GIMPLE emitted by the C++ front end.
## Reproducer
```cpp
struct A { int x = 1; virtual int f() { return x; } };
struct B : virtual A { int f() override { return x + 1; } };
struct C final : B {};
void escape(C*);
int two_casts() {
C c;
escape(&c);
return static_cast<A&>(static_cast<B&>(c)).f();
}
int one_cast() {
C c;
escape(&c);
return static_cast<A&>(c).f();
}
int no_escape() {
C c;
return static_cast<A&>(static_cast<B&>(c)).f();
}
```
## Command
```text
g++ -O2 -c testcase.cpp
objdump -drC testcase.o
```
## Actual behavior
`two_casts()` loads the virtual-base offset and vptr after `escape(&c)`,
compares the loaded target with `B::f()`, and retains an indirect call on the
fallback path. `one_cast()` and `no_escape()` do not contain an indirect
virtual call.
In the initial GIMPLE, `two_casts()` still contains an `OBJ_TYPE_REF`. In
contrast, the front end has already converted the call in `one_cast()` to a
direct call to the virtual thunk for `B::f()`.
## Expected behavior
GCC should resolve the call in `two_casts()` to `B::f()`, as it does in
`one_cast()`. The two conversions name the same unique `A` virtual-base
subobject of the complete local `C` object. `C` is `final`, so the intermediate
cast through `B&` does not introduce another possible final overrider.
Removing the call to `escape` also allows GCC to devirtualize the two-step
form. This shows that the remaining obstacle is the vptr-dependent virtual-base
offset introduced by the intermediate cast, not a different set of possible
dynamic types.
## Compiler version
```text
Target: x86_64-pc-linux-gnu
Configured with: /src/configure --prefix=/opt/gcc --enable-languages=c,c++
--disable-multilib --disable-bootstrap --enable-checking=yes
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 17.0.0 20261025 (experimental) (GCC)
```
Verified with GCC trunk commit `cd7a85774a992c4759ebd03a26ce03cbb934e4d9`.