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