https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127047

            Bug ID: 127047
           Summary: Superword-Level Parallelism (SLP) Vectorizer Alignment
                    Bug
           Product: gcc
           Version: 16.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: illia.polishchuk at thalesgroup dot com
  Target Milestone: ---

Created attachment 65407
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65407&action=edit
minimum reproducer C++ code

The issue triggers a runtime segmentation fault (`SIGSEGV`) due to a mismatch
between C++ ABI subobject layout alignment (`nvalign`) and the compiler's
vectorization assumptions.

**Note on implicit alignment:** The reproducer uses no explicit `alignas`
attributes. Instead, it relies on a built-in primitive (`long double`) which
inherently has a 16-byte alignment on the x86-64 Linux System V ABI. This
exactly mirrors how types like `json_decimal128_t` natively push class
alignment to 16 bytes.

---

## 1. Minimal C++ Reproduction (`main.cpp`)

```cpp
#include <iostream>

// Base class with a member of type `long double` (implicitly 16-byte aligned
on x86_64 Linux GCC)
struct Base16 {
    virtual ~Base16() = default;
    long double implicit_align16;
};

// Standard virtual base
struct Base8 {
    virtual ~Base8() = default;
};

// Intermediate class virtually inheriting from both Base8 and Base16.
// Because Base8 is the primary virtual base, Subclass shares its offset with
Base8 (alignment 8).
// However, because Subclass virtually inherits from Base16, alignof(Subclass)
is 16.
// Inside Subclass constructor, the compiler assumes `this` is 16-byte aligned.
// Since val1 starts at offset 16 (after virtual pointer at 0 and dummy at 8),
// the vectorized store of val1/val2 starts at offset 16.
// The compiler assumes `this + 16` is 16-byte aligned and uses `movaps`!
struct Subclass : virtual Base8, virtual Base16 {
    __attribute__((noinline)) Subclass() : val1(0), val2(0) {}
    long dummy; // Uninitialized to align the vectorized store of val1/val2 at
offset 16
    long val1;
    long val2;
};

// Non-virtual primary base that has virtual functions
struct PrimaryBase {
    virtual ~PrimaryBase() = default;
};

// Padding to ensure that the non-virtual size is exactly 24 bytes
struct Padding {
    long a = 0;
    long b = 0;
};

// Derived inherits from PrimaryBase and Padding non-virtually, and Subclass
virtually.
// The ABI layout engine places Subclass at offset 24 inside Derived (8-byte
aligned).
struct Derived : public PrimaryBase, public Padding, virtual Subclass {
    virtual ~Derived() = default;
};

int main() {
    std::cout << "Creating Derived object on the stack..." << std::endl;
    Derived obj; // Crashes inside Subclass constructor when compiled with -O3
    std::cout << "Success! No crash." << std::endl;
    return 0;
}
```

---

## 2. Minimal Raw GCC Commands to Reproduce

### Trigger the Crash (Buggy Build)
Compile with optimization `-O3` (which enables `-ftree-slp-vectorize`):
```bash
g++ -O3 main.cpp -o buggy_repro
./buggy_repro
# Output: Segmentation fault (core dumped)
```

### Apply Workaround (Safe Build)
Compile with optimization `-O3` and the flag `-fno-tree-slp-vectorize`:
```bash
g++ -O3 -fno-tree-slp-vectorize main.cpp -o safe_repro
./safe_repro
# Output: Success! No crash.
```

---

## 3. Root Cause Analysis

1. **ABI Layout (`nvalign`)**:
   Under the Itanium ABI, when `Subclass` is laid out as a virtual base inside
`Derived`, it is aligned using its non-virtual alignment `nvalign(Subclass)`.
Since its non-virtual part has no 16-byte aligned members, `nvalign(Subclass)
== 8`. Because `Derived`'s non-virtual size is 24 bytes, the subobject is
placed at offset **24** (`obj + 24`), which is **8-byte aligned but not 16-byte
aligned**.

2. **SLP Vectorizer's False Assumption**:
   When compiling `Subclass::Subclass()`, the compiler sees `alignof(Subclass)
== 16` and assumes `this` is 16-byte aligned.
   Because `val1` and `val2` are consecutive 8-byte stores initialized to `0`
at `this + 16` and `this + 24`, the SLP vectorizer groups them into a single
16-byte store starting at `this + 16`.
   Since it assumes `this` is 16-byte aligned, it assumes `this + 16` is also
16-byte aligned and emits the **`movaps`** (Move Aligned Packed Single) SSE
instruction:
   ```assembly
   movaps %xmm0,0x10(%rdi)
   ```

3. **General Protection Fault**:
   At runtime, `this` is `obj + 24`. The `movaps` instruction tries to write to
`obj + 40` (`this + 16`), which is not 16-byte aligned, causing a hardware
exception and generating the `SIGSEGV` crash.
  • [Bug c++/127047] New:... illia.polishchuk at thalesgroup dot com via Gcc-bugs

Reply via email to