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