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

            Bug ID: 127577
           Summary: [[no_unique_address]]/base tail padding member lost
                    after aggregate init with -fstore-merging
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: mail-gcc at bueddl dot de
  Target Milestone: ---

Created attachment 65671
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65671&action=edit
preprocessed file with -fstore-merging enabled

Target: x86_64-pc-linux-gnu
Command line: g++ -O1 -fstore-merging test.cc

Example available at https://godbolt.org/z/ThfssTncx

#include <cstdio>
#include <cstddef>

struct Base {
    void *self = this; // could be any runtime-dependent value to force the
object init to runtime
    long pad[31]{1, 2, 3, 4, 5, 6, 7, 8 /* specific values dont't really
matter, just here to help identify it in the asm */};
    int b = 1337; // a and b are split between Base and S
};

struct S { // Base could equally also be a base class instead of a member with
no-unique-address
    [[no_unique_address]] Base base; // no unique address important to make a
reuse some of Base memory
    int a; // Base::b ends at offset 260, a reuses tail padding of Base
};

static_assert(offsetof(Base, b) == 256);
static_assert(offsetof(Base, b) + sizeof(Base::b) == 260);
static_assert(sizeof(Base) == 264);
static_assert(sizeof(S) == 264);
static_assert(offsetof(S, a) == 260);

[[gnu::noinline]] void show(S const& s) { // force noinline so the s.a value is
actually read from the object
    printf("a=%d\n", s.a); 
}

int main() {
    S s{Base{}, 42}; // attempt to init s.a to 42
    show(s); // prints 0
}


Expected: a=42
Actual:   a=0

S::a lives in the tail padding of Base (offset 260, situation is shown by the
static_asserts). After S s{Base{}, 42}, s.a reads 0 instead of 42. 
Happens with Base as a [[no_unique_address]] member or as a base class.
The issue goes away with -fno-store-merging.

Looking at the asm: without store merging, main does memset(&s, 0, 264),
stores 42 at offset 260, then memset(&s, 0, 260) for Base and the Base member
stores. That's correct. With store merging, the store of 42 and the 260 byte
memset are both gone. My guess is the 260 byte clear gets treated as 264
bytes, so the store to a looks dead.

-O1 -fno-store-merging:
        call    memset            # 264 bytes
        mov     DWORD PTR [rsp+260], 42
        mov     edx, 260
        ...
        call    memset            # 260 bytes

-O1 -fstore-merging:
        call    memset            # 264 bytes, no store of 42 and no second
memset

Reply via email to