https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127752
Bug ID: 127752
Summary: [Regression] volatile member read of a never-written
global dropped with -O2 -flto
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Keywords: lto, wrong-code
Severity: normal
Priority: P3
Component: middle-end
Assignee: unassigned at gcc dot gnu.org
Reporter: yqtian668 at gmail dot com
Target Milestone: ---
Host: x86_64-pc-linux-gnu
Target: x86_64-pc-linux-gnu
Build: x86_64-pc-linux-gnu
On current GCC trunk, compiling this program with LTO at -O2, -O3 or -Os
removes a required read of a volatile member. The global struct is never
written, but that does not permit its volatile member access to be replaced by
a constant.
The two source files are reproduced below:
/* a.c */
struct S { volatile short a; int b; };
struct S c = { 1, 2 };
void d (void);
void g (void);
int
main (void)
{
d ();
g ();
return 0;
}
/* b.c */
struct S { volatile short a; int b; };
extern struct S c;
void
d (void)
{
short e = c.b;
}
void
g (void)
{
c.a;
}
Reproduce with:
gcc -O2 -flto a.c b.c -o t
objdump -d t | sed -n '/<main>:/,/^$/p'
With GCC trunk, main contains no load from c and c is not emitted. The same
result occurs at -O3 and -Os. The access c.a; reads a scalar volatile object in
a void context, so the load must be preserved.
Tested with GCC 17.0.0 20261003 (experimental), revision
8e84facee1236564d6db77234c656db0f9b79e91, configured for x86_64-pc-linux-gnu.
For comparison, GCC 13.3.0 retains the load at these optimization levels when
compiling without debug information, but drops it when -g is added. Current
trunk drops it in both the -g0 and -g builds at -O2 and above. Thus the trunk
behavior reported here does not require debug information, while the older
-g-dependent behavior appears to have existed since at least GCC 13.3.0. I have
not bisected when the behavior without -g was introduced.
The first wrong-code difference appears during RTL expansion; the LTO tree
dumps before expansion are identical modulo debug statements. The apparent
cause is:
* Because c is never written, LTO marks it TREE_READONLY.
* Its DECL_INITIAL is loaded lazily by lto1.
* At -O2 and above, the constructor is loaded before expansion.
* The PR95126 fast path in expand_expr_real_1 treats the TREE_READONLY variable
as an immediate constant while expanding the base of the volatile
COMPONENT_REF. The volatile access is consequently replaced by extraction from
the initializer rather than a memory read.
A possible fix is to force the base of a volatile inner reference to be
expanded with EXPAND_MEMORY. That preserves the load in my targeted testing.
This appears related to PR107036. At -O1, adding -g causes the same volatile
read to disappear because a debug bind loads the constructor before expansion.
The new problem reported here is different in impact: on current trunk at -O2
and above, it reproduces without -g, so both debug and non-debug builds lose
the required volatile access.