https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127725
Bug ID: 127725
Summary: -fcompare-debug failures from debug-blind neighbor
lookups in generic delay-slot scheduling
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Keywords: compare-debug-failure
Severity: normal
Priority: P3
Component: rtl-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: yqtian668 at gmail dot com
Target Milestone: ---
Target: sparc-elf, mips-elf
GCC trunk's target-independent delayed-branch scheduling code makes several
ordinary-code decisions using neighbor queries that do not skip DEBUG_INSNs.
Three independent sites in gcc/reorg.cc are affected. All three reproduce on
SPARC, and two also reproduce on MIPS.
Tested revision:
5f9f8711bf791130fd30bc1f04b2097f3ef25f0b
The three standalone C testcases are included here.
optimize-skip.c (59 bytes):
void f (int a, int b, int *p)
{
if (a > b)
*p = b;
}
fill-slots.c (309 bytes):
int h (int);
int f (void)
{
unsigned mid, lo = 0, hi = 3, last = -1;
for (;;)
{
mid = (lo + hi) / 2;
if (last == mid)
return 0;
last = mid;
int r = h (mid);
if (r < 0)
hi = mid;
else if (r > 0)
lo = mid;
else
return 1;
}
}
get-label.c (96 bytes):
int b;
void foo (int n, char *c)
{
for (;;)
for (int f = 0; f < n; f++)
b = c[f];
}
The SPARC commands are:
common=(
-O2 -mcpu=v8 -S -fcompare-debug -fno-var-tracking
--param=max-delay-slot-live-search=100000
--param=max-delay-slot-insn-search=100000
)
sparc-elf-gcc "${common[@]}" optimize-skip.c
sparc-elf-gcc "${common[@]}" fill-slots.c
sparc-elf-gcc "${common[@]}" get-label.c
The MIPS commands are:
common=(
-O2 -S -fcompare-debug -fno-var-tracking
--param=max-delay-slot-live-search=100000
--param=max-delay-slot-insn-search=100000
)
mips-elf-gcc "${common[@]}" fill-slots.c
mips-elf-gcc "${common[@]}" get-label.c
Every command above reports:
xgcc: error: <file>: '-fcompare-debug' failure (length)
The large parameter values rule out the two bounded-search issues in the same
pass. Adding -fno-delayed-branch makes all five failing configurations pass.
The affected sites and observed effects are:
* optimize_skip uses next_nonnote_insn. This prevents an annulled-branch
transformation and leaves an empty delay slot. Confirmed on SPARC.
* fill_slots_from_thread uses next_nonnote_insn. This blocks a register
substitution used to make an instruction eligible for a delay slot. Confirmed
on SPARC and MIPS.
* get_label_before uses prev_nonnote_insn. This creates a redundant new label
instead of reusing the label at the same position. Confirmed on SPARC and MIPS.
optimize_skip
-------------
At gcc/reorg.cc:673, the pass starts with:
rtx_insn *trial = next_nonnote_insn (insn);
With -g -fno-var-tracking, this returns a source-derived DEBUG_INSN at the head
of the fall-through block. !NONJUMP_INSN_P (trial) then makes optimize_skip
return, although the next ordinary instruction is the same store used by the
-g0 compilation.
The SPARC ordinary assembly changes from:
// -g0
cmp %o0, %o1
bg,a .LL4
st %o1, [%o2]
// -g
cmp %o0, %o1
ble .LL4
nop
st %o1, [%o2]
This testcase fails at -O1, -O2, and -Os on SPARC.
fill_slots_from_thread
----------------------
At gcc/reorg.cc:2583, the pass examines the instruction after a
register-to-register copy:
rtx_insn *next = next_nonnote_insn (trial);
If the next ordinary instruction uses the copy destination, the pass can
substitute the source register and make that instruction eligible for the delay
slot. A DEBUG_INSN between the copy and the next ordinary instruction makes
this lookup stop early, so the substitution is skipped.
On SPARC, -g0 fills the branch delay slot, while -g leaves it empty and retains
an additional move:
// -g0
bge .LL10
add %i4, %i5, %g1
// -g
bge .LL10
nop
mov %i5, %i3
add %i4, %i3, %g1
MIPS also emits different ordinary instructions:
-addu $2,$17,$16
+addu $2,$17,$18
get_label_before
----------------
At gcc/reorg.cc:1768, the pass tries to reuse an existing label:
label = prev_nonnote_insn (insn);
When fill_slots_from_thread redirects a jump past instructions moved into a
delay slot, a DEBUG_INSN can occur between the existing label and insn. The
lookup returns that debug marker, so get_label_before concludes that no label
exists and creates a redundant new one.
This third effect is label-only. On MIPS the branch target changes from $L2 to
a new $L6 at the same position; SPARC similarly changes .LL2 to .LL6. After
normalizing local labels, the ordinary instruction streams are identical.
Causal checks and scope
-----------------------
Changing the three lookups to their existing nondebug counterparts fixes the
corresponding testcases:
next_nonnote_nondebug_insn
next_nonnote_nondebug_insn
prev_nonnote_nondebug_insn
Each change was tested in isolation. The fill_slots_from_thread change does not
fix the get_label_before testcase, and vice versa. The optimize_skip change
does not fix the SPARC fill_slots_from_thread testcase.
These sites are in generic reorg.cc, not in a SPARC or MIPS backend. The
current GCC source tree has define_delay descriptions under 13 target
directories. This report establishes concrete effects on SPARC and MIPS; but I
am not sure whether every delay-slot target reaches every affected site. The
same three source files did not trigger on SH or PA-RISC in the tested
configurations.
Expected result: debug-only instructions should not change ordinary neighbor
relationships used to make delay-slot scheduling and label-reuse decisions.
I am not familiar with these backends. Any suggestions would be appreciated.