https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128185
Bug ID: 128185
Summary: auto-var-init memset not removed when a non-inlined
callee fully initializes the object
Product: gcc
Version: 17.0
URL: https://godbolt.org/z/bechb55hx
Status: UNCONFIRMED
Keywords: missed-optimization
Severity: normal
Priority: P3
Component: ipa
Assignee: unassigned at gcc dot gnu.org
Reporter: jmcruz at uma dot es
Target Milestone: ---
Host: x86_64-linux-gnu
Target: x86_64-linux-gnu
Build: x86_64-linux-gnu
With -std=c++26, a local object whose non-inlined constructor writes every
byte before any read is still zero-initialized by the caller first.
Testcase (no includes):
void use(const void*);
struct A {
char buf[1024];
[[gnu::noinline]] A();
};
A::A() { __builtin_memset(buf, 'x', sizeof buf); }
void test_a() { A a; use(&a); }
g++ -std=c++26 -O2 (also -O3), GCC 16.2 and trunk:
test_a():
memset(rsp, 0, 1024)
call A::A()
call use
A::A() is an exact definition in the same TU. -fdump-tree-optimized shows
that it writes the whole object before any read:
A::A (struct A * const this)
_1 = &this_2(D)->buf;
__builtin_memset (_1, 120, 1024); [tail call]
while the caller still keeps the opaque auto-init:
a = .DEFERRED_INIT (1024, 4, &"a"[0]);
A::A (&a);
Clang (-ftrivial-auto-var-init=zero -O2) removes the store entirely. In its
LLVM IR the `this` parameter of A::A() carries `initializes((0, 1024))`, a
parameter attribute that LLVM infers (FunctionAttrs) and that DSE uses across
calls. It was proposed as "[RFC] LLVM: new `initialized` parameter attribute
for improved interprocedural DSE" (March 2024) and committed under the name
`initializes`:
https://discourse.llvm.org/t/rfc-llvm-new-initialized-parameter-attribute-for-improved-interprocedural-dse/77337
https://github.com/llvm/llvm-project/commit/5ece35df8586
It also trims the partial case (constructor writing only part of the object);
a testcase for that case has been added to PR121894.
As in PR121894 (comment #0, partial overwrite case), the auto-init remains a
.DEFERRED_INIT until RTL expansion, so a fix probably needs DSE to handle
.DEFERRED_INIT against callee kill summaries (ipa-modref).
With C++26 enabling auto-init by default, every non-inlined constructor call
on a local object pays this double initialization. WG21 paper P4424R0
(B. Revzin) reports out-of-line constructors as a source of such double
initializations when moving a large codebase to C++26 with GCC 16:
https://wg21.link/p4424r0
Compiler (gcc -v):
gcc version 17.0.0 20261025 (experimental), revision
cd7a85774a992c4759ebd03a26ce03cbb934e4d9 (Compiler Explorer gcc-snapshot)
Target: x86_64-linux-gnu
Command: g++ -std=c++26 -O2 -S test.cc
Compiler Explorer (GCC trunk, Clang trunk and Clang's LLVM IR side by side):
https://godbolt.org/z/bechb55hx