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

            Bug ID: 128623
           Summary: [14/15/16/17 Regression] Per-function var-tracking
                    settings are lost when optimization nodes are shared
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: debug
          Assignee: unassigned at gcc dot gnu.org
          Reporter: luolongjuna at gmail dot com
  Target Milestone: ---

Functions with explicit optimize("var-tracking") and
optimize("no-var-tracking") attributes share a single OPTIMIZATION_NODE, so
whichever function is seen first dictates whether variable tracking runs for
both. With LTO, the same node merging makes a -g0 object linked first disable
variable tracking in a -g object linked after it.

Reproduced on x86_64 GNU/Linux with GCC 17.0.0 (g:bd921def14a4) and binutils
2.46.1, and with Ubuntu GCC 13.3.0.

GCC 17 build configuration:
  --enable-languages=c,c++ --disable-bootstrap --disable-multilib
  --disable-nls --disable-werror --enable-checking=yes
  --without-isl --with-system-zlib

A. Explicit optimize attributes without LTO (-O2 -g -fno-lto)

Compiling attributes.c in both definition orders (toggled by -DON_FIRST),
-fdump-rtl-vartrack records:

  Definition order  Functions processed by vartrack
  ----------------  -------------------------------
  off, then on      neither
  on, then off      both
  Expected          on only, in either order

Checking emitted .LVL location labels on Compiler Explorer gives the expected
order-independent output in GCC 8.5, 9.1, 10.5 and 11.5, and reproduces the
order dependence in GCC 12.1, 14.1 and 16.1.

B. Mixed -g/-g0 LTO link order (-O2 -gdwarf-5 -flto -flto-partition=one)

main.c is compiled with -g and other.c with -g0. Linking the same two objects
in opposite orders changes main's debug info:

  Compile debug flags    Link order      main's location lists 
DW_TAG_call_site
  ---------------------  --------------  --------------------- 
----------------
  main: -g, other: -g0   main.o other.o            2                   2
  main: -g, other: -g0   other.o main.o            0                   0
  both: -g               either order              2                   2

With other.o first, its -g0 OPTIMIZATION_NODE (flag_var_tracking == 0) prevails
during WPA SCC merging and disables variable tracking for every function from
main.o, dropping the location lists on argc/argv and both DW_TAG_call_site
entries.

Mechanism

-fvar-tracking (along with -fvar-tracking-assignments and
-fvar-tracking-uninit) is marked PerFunction without Optimization in
common.opt. optc-save-gen.awk saves and restores PerFunction fields in
cl_optimization, but excludes non-Optimization fields from cl_optimization_hash
and cl_optimization_option_eq.

While ignoring debug-only flags in code-equivalence checks (such as IPA-ICF) is
intentional, build_optimization_node uses these helpers through
cl_option_hasher, and LTO uses them for SCC hashing and comparison (see
https://gcc.gnu.org/legacy-ml/gcc-patches/2017-01/msg00282.html on separating
node interning from ICF comparison). Before r12-4282-g9abf8c9534144f ("options:
use cl_optimization_hash."), cl_option_hasher::hash included PerFunction values
while the equality comparison already ignored them. That change made both
hashing and equality ignore those values. PR47590 tracks recomputing derived
flags after optimize pragmas; Case A sets var-tracking explicitly.

Reproducers

Save Case A and Case B in separate directories.

=== Case A ===

--- attributes.c ---
__attribute__((noipa)) int target (int x) { return x + 1; }

#ifdef ON_FIRST
__attribute__((noipa, optimize("var-tracking")))
int on (int x) { return target (x); }

__attribute__((noipa, optimize("no-var-tracking")))
int off (int x) { return target (x); }
#else
__attribute__((noipa, optimize("no-var-tracking")))
int off (int x) { return target (x); }

__attribute__((noipa, optimize("var-tracking")))
int on (int x) { return target (x); }
#endif

int main (int argc, char **argv)
{
  return off (argc) + on (argc);
}

--- run.sh ---
#!/bin/sh
set -eu
export LC_ALL=C

"${CC:-gcc}" -O2 -g -fno-lto \
    -fdump-rtl-vartrack=off-first.vartrack \
    attributes.c -o off-first
"${CC:-gcc}" -O2 -g -fno-lto -DON_FIRST \
    -fdump-rtl-vartrack=on-first.vartrack \
    attributes.c -o on-first

for name in off-first on-first; do
  printf '%s:\n' "$name"
  awk '/^;; Function (on|off) / { print $3 }' "$name.vartrack"
done

Run with:
  CC=gcc sh run.sh

=== Case B ===

--- main.c ---
extern int other (int);
__attribute__((noipa)) int local (int x) { return x + 1; }

int main (int argc, char **argv)
{
  return local (argc) + other (argc);
}

--- other.c ---
__attribute__((noipa)) int other (int x) { return x + 2; }

--- run.sh ---
#!/bin/sh
set -eu
export LC_ALL=C
other_debug=${1:--g0}

"${CC:-gcc}" -O2 -g -gdwarf-5 -flto -c main.c -o main.o
"${CC:-gcc}" -O2 -gdwarf-5 -flto "$other_debug" -c other.c -o other.o

"${CC:-gcc}" -O2 -g -gdwarf-5 -flto -flto-partition=one \
    main.o other.o -o main-first
"${CC:-gcc}" -O2 -g -gdwarf-5 -flto -flto-partition=one \
    other.o main.o -o other-first

for binary in main-first other-first; do
  readelf --debug-dump=info "$binary" > "$binary.info"
  readelf --debug-dump=loc "$binary" > "$binary.loc"
  printf '%s: location lists=' "$binary"
  awk '/DW_AT_location/ && /location list/ {n++} END {print n+0}'
"$binary.info"
  printf '%s: call sites=' "$binary"
  awk '/\(DW_TAG_call_site\)/ {n++} END {print n+0}' "$binary.info"
done

Run with:
  CC=gcc sh run.sh
  CC=gcc sh run.sh -g

Reply via email to