https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127938
Bug ID: 127938
Summary: single_set_2 counts debug uses with DF_REG_USE_COUNT,
so a debug bind of an unused PARALLEL output changes
code
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: ---
Host: x86_64-pc-linux-gnu
Target: x86_64-pc-linux-gnu
Build: GCC 17.0.0 (experimental),
84348add236100e5f69a3e9ec4f7b1038bf2b83d
Created attachment 65870
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65870&action=edit
GCC plugin reproducer (bindquot.cc)
Since r16-6062 (PR 121852, "rtlanal: Use REG_UNUSED notes in single_set
only in passes where df_analyze has computed them"), `single_set_2` decides
whether a second `SET` of a `PARALLEL` is dead, in passes that have `df`
but no note problem, as follows:
```c
/* gcc/rtlanal.cc, single_set_2 */
auto unused = [] (const rtx_insn *insn, rtx dest) {
if (!df)
return false;
if (df_note)
return !!find_reg_note (insn, REG_UNUSED, dest);
return (REG_P (dest)
&& !HARD_REGISTER_P (dest)
&& REGNO (dest) < df->regs_inited
&& DF_REG_USE_COUNT (REGNO (dest)) == 0);
};
```
`DF_REG_USE_COUNT` includes uses in debug insns.
`regstat_init_n_sets_and_refs` subtracts them for `REG_N_REFS` for this
reason. The REG_UNUSED notes used before r16-6062 ignore debug uses as
well. So if a debug insn binds a variable to the otherwise unused output of a
multi-SET PARALLEL, `single_set` returns NULL with `-g` and the live SET
with `-g0`. Every caller of `single_set` in those passes can then decide
differently. Such passes include fwprop, cprop, `loop2_invariant`, ifcvt,
`late_combine`, `ree` and `ext_dce`.
The example below is on x86_64 at `-O2`. `1 % a` in
`gcc.dg/torture/pr54458.c` expands to `*udivmodsi4`, whose quotient pseudo
is unused:
```
(parallel [(set (reg:SI 111) (udiv:SI (reg:SI 109) (reg:SI 100)))
(set (reg:SI 110) (umod:SI (reg:SI 109) (reg:SI 100)))
(clobber (reg:CC flags))])
```
With `-g0`, `loop2_invariant` treats the insn as a single set of `reg 110`
and hoists it out of the loop. With one debug bind of `reg 111`,
`single_set` returns NULL, the insn stays in the loop, and the final code
differs.
## Reproducer
I have not found a C or C++ source that makes GCC emit such a bind by
itself. A small plugin adds one before `loop2_invariant`. Under `-g0` it
does nothing, so `-fcompare-debug` compares the compilation with the bind
against the compilation without it.
I am not sure whether it is a bug or not, so any suggestion would be
appreciated.
`bindquot.cc`:
```c++
/* bindquot.cc: GCC plugin for the single_set_2 report. Before
loop2_invariant, after every two-SET PARALLEL whose first SET is a pseudo,
add one debug bind of an artificial variable to that pseudo. Under -g0
(no debug bind insns) it does nothing, so -fcompare-debug compares the
compilation with the bind against the one without it. */
#include "gcc-plugin.h"
#include "plugin-version.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "tree.h"
#include "rtl.h"
#include "memmodel.h"
#include "emit-rtl.h"
#include "langhooks.h"
#include "tree-pass.h"
#include "context.h"
#include "stringpool.h"
int plugin_is_GPL_compatible;
namespace {
const pass_data data = { RTL_PASS, "bindquot", OPTGROUP_NONE, TV_NONE,
PROP_rtl, 0, 0, 0, 0 };
struct bindquot_pass : rtl_opt_pass
{
bindquot_pass (gcc::context *c) : rtl_opt_pass (data, c) {}
unsigned int execute (function *fun) final override
{
if (!MAY_HAVE_DEBUG_BIND_INSNS)
return 0;
basic_block bb;
rtx_insn *insn;
FOR_EACH_BB_FN (bb, fun)
FOR_BB_INSNS (bb, insn)
{
if (!NONDEBUG_INSN_P (insn) || GET_CODE (PATTERN (insn)) != PARALLEL)
continue;
rtx pat = PATTERN (insn);
if (XVECLEN (pat, 0) < 2
|| GET_CODE (XVECEXP (pat, 0, 0)) != SET
|| GET_CODE (XVECEXP (pat, 0, 1)) != SET)
continue;
rtx dest = SET_DEST (XVECEXP (pat, 0, 0));
if (!REG_P (dest) || HARD_REGISTER_P (dest))
continue;
tree var = build_decl (UNKNOWN_LOCATION, VAR_DECL,
get_identifier ("quot"),
lang_hooks.types.type_for_mode (GET_MODE
(dest),
1));
DECL_CONTEXT (var) = current_function_decl;
DECL_ARTIFICIAL (var) = 1;
emit_debug_insn_after (gen_rtx_VAR_LOCATION (GET_MODE (dest), var,
dest,
VAR_INIT_STATUS_INITIALIZED),
insn);
}
return 0;
}
};
}
int
plugin_init (struct plugin_name_args *info, struct plugin_gcc_version *)
{
struct register_pass_info p;
p.pass = new bindquot_pass (g);
p.reference_pass_name = "loop2_invariant";
p.ref_pass_instance_number = 1;
p.pos_op = PASS_POS_INSERT_BEFORE;
register_callback (info->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &p);
return 0;
}
```
`pr54458.c` (`gcc.dg/torture/pr54458.c`):
```c
/* { dg-do compile } */
unsigned int a, b, c;
void
foo (unsigned int x)
{
do
{
if (a == 0 ? 1 : 1 % a)
for (; b; b--)
lab:;
else
while (x)
;
if (c)
goto lab;
}
while (1);
}
```
Commands (x86_64-pc-linux-gnu, master
84348add236100e5f69a3e9ec4f7b1038bf2b83d):
```sh
g++ -std=c++17 -shared -fPIC -fno-rtti \
-I"$(gcc -print-file-name=plugin)/include" bindquot.cc -o bindquot.so
gcc -O2 -fcompare-debug -fplugin=./bindquot.so -S pr54458.c
```
Actual result:
```text
gcc: error: pr54458.c: '-fcompare-debug' failure (length)
```
Without the plugin, or with the change below, the compilation succeeds.
The `loop2_invariant` dump shows the difference:
```text
-g0: Set in insn 23 is invariant (0), cost 4, depends on
Set in insn 24 is invariant (1), cost 6, depends on 0
-g: Set in insn 25 is invariant (0), cost 4, depends on
```
Insn 24 (`-g0`) is the divmod. With `-g` it is no longer an invariant
candidate. The register allocation, block layout and position of the `divl`
in the final assembly change too.
## Possible fix
Count only uses outside debug insns, as `regstat_init_n_sets_and_refs`
does:
```diff
--- a/gcc/rtlanal.cc
+++ b/gcc/rtlanal.cc
@@ -1563,10 +1563,17 @@
return false;
if (df_note)
return !!find_reg_note (insn, REG_UNUSED, dest);
- return (REG_P (dest)
- && !HARD_REGISTER_P (dest)
- && REGNO (dest) < df->regs_inited
- && DF_REG_USE_COUNT (REGNO (dest)) == 0);
+ if (!REG_P (dest)
+ || HARD_REGISTER_P (dest)
+ || REGNO (dest) >= df->regs_inited)
+ return false;
+ /* Uses in debug insns do not make the SET live. */
+ for (df_ref use = DF_REG_USE_CHAIN (REGNO (dest)); use;
+ use = DF_REF_NEXT_REG (use))
+ if (DF_REF_IS_ARTIFICIAL (use)
+ || !DEBUG_INSN_P (DF_REF_INSN (use)))
+ return false;
+ return true;
};
if (!set_verified)
{
```
With this change, the reproducer passes `-fcompare-debug`. The `-g0`
assembly is byte-identical to that of the unpatched compiler for the 5,235
files of the C torture tests and `gcc.dg/torture` that compile at `-O2`
without their dg-options. I have not bootstrapped or regression-tested it.
Walking the use chain makes `single_set` more expensive in these passes.
A cheaper alternative may be preferable, for example caching the count of
non-debug uses.