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.

Reply via email to