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`.

Reply via email to