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.

Reply via email to