http://gcc.gnu.org/bugzilla/show_bug.cgi?id=58369

--- Comment #4 from Mikael Pettersson <mikpe at it dot uu.se> ---
(All source references here are for vanilla gcc-4.8.1.)

The problem appears to start in choose_reload_regs, in the "if (inheritance)"
block at lines 6497 to 6679.  It finds (reg:DF 0 %d0 [orig:109 D.2384 ] [109])
in rld[r].in.  The "if (regno >= 0 ...)" test at line 6543 passes so that block
is entered.  last_reg is (reg:XF 17 %fp1) and byte is still 0, so it calls
subreg_regno_offset (17, XFmode, 0, DFmode) at line 6560.

subreg_regno_offset in turn just calls subreg_get_info.

In subreg_get_info xmode is XFmode with size 12 and precision 80, while ymode
is DFmode with size 8 and precision 64.

The first "if (HARD_REGNO_NREGS_HAS_PADDING ...)" is false so that block is
skipped.  nregs_xmode and nregs_ymode are computed, both are 1.

The second if for paradoxical subregs is false, so that block is skipped.

The third if "If registers store different numbers of bits in the different
modes, we cannot generally form this subreg" at line 3345 is true, so that
block is entered.  regsize_xmode and regsize_ymode are computed, they are 12
and 8 respectively.  None of the inner if:s there are true, because nregs_xmode
and nregs_ymode are both 1, which isn't > 1, so we don't return.

The fourth if for lowpart subregs at line 3371 is false, because the input
offset is 0 ("byte" from choose_reload_regs), but subreg_lowpart_offset
(DFmode, XFmode) returns 4, and 0 != 4.  So that block is skipped.

Next we reach the "gcc_assert ((GET_MODE_SIZE (xmode) % GET_MODE_SIZE (ymode))
== 0);" at line 3387.  However, this evaluates as (12 % 8) == 0, which is
false, so the assertion fails and we ICE.

So the problem as I understand it is that choose_reload_regs forms a virtual
subreg with differently-sized modes and offset 0, while on big-endian machines
the offset must be >0 (see subreg_lowpart_offset in emit-rtl.c), so the virtual
subreg is not recognized as a normal lowpart subreg.

I *think* other machines don't see this problem because:
a) offset 0 is correct for little-endian like x86 and most ARMs,
b) many big-endian machines define CANNOT_CHANGE_MODE_CLASS to reject
differently-sized modes in floating-point registers, which can prevent the
bogus subreg_regno_offset call,
c) their larger modes are whole multiples of their smaller modes, so the
assertion doesn't fail.

However, m68k:
d) is big-endian,
e) doesn't define CANNOT_CHANGE_MODE_CLASS,
f) has an XFmode which is 1.5 times as large as its DFmode.

If I add a suitable definition of CANNOT_CHANGE_MODE_CLASS, e.g.

--- gcc-4.8.1/gcc/config/m68k/m68k.h.~1~        2013-01-10 21:38:27.000000000
+0100
+++ gcc-4.8.1/gcc/config/m68k/m68k.h    2013-09-11 18:28:58.160242077 +0200
@@ -409,6 +409,11 @@ along with GCC; see the file COPYING3.
 #define HARD_REGNO_MODE_OK(REGNO, MODE) \
   m68k_regno_mode_ok ((REGNO), (MODE))

+#define CANNOT_CHANGE_MODE_CLASS(FROM, TO, CLASS)      \
+  (GET_MODE_SIZE (FROM) != GET_MODE_SIZE (TO)          \
+   ? reg_classes_intersect_p (FP_REGS, CLASS)          \
+   : 0)
+
 #define SECONDARY_RELOAD_CLASS(CLASS, MODE, X) \
   m68k_secondary_reload_class (CLASS, MODE, X)

then the ICE disappears.  However, reading the m68k backend I think this is
against its intentions, and I suspect it is stricter than necessary.

Other calls to subreg_regno_offset either take the parameters from an existing
real subreg, or compute a correct lowpart offset (regcprop.c:maybe_mode_change,
var-tracking.c:var_lowpart).  So I'm starting to think that the bug is in
choose_reload_regs: it should pass a proper lowpart offset in byte, not the
constant 0 (byte is only set for input subregs that are pseudos).  The
following patch:

--- gcc-4.8.1/gcc/reload1.c.~1~ 2013-01-21 15:55:05.000000000 +0100
+++ gcc-4.8.1/gcc/reload1.c     2013-09-11 19:58:37.979482251 +0200
@@ -6497,6 +6497,7 @@ choose_reload_regs (struct insn_chain *c
          if (inheritance)
            {
              int byte = 0;
+             bool byte_is_fixed = false;
              int regno = -1;
              enum machine_mode mode = VOIDmode;

@@ -6519,7 +6520,10 @@ choose_reload_regs (struct insn_chain *c
                  if (regno < FIRST_PSEUDO_REGISTER)
                    regno = subreg_regno (rld[r].in_reg);
                  else
-                   byte = SUBREG_BYTE (rld[r].in_reg);
+                   {
+                     byte = SUBREG_BYTE (rld[r].in_reg);
+                     byte_is_fixed = true;
+                   }
                  mode = GET_MODE (rld[r].in_reg);
                }
 #ifdef AUTO_INC_DEC
@@ -6557,6 +6561,8 @@ choose_reload_regs (struct insn_chain *c
                  rtx last_reg = reg_last_reload_reg[regno];

                  i = REGNO (last_reg);
+                 if (! byte_is_fixed)
+                   byte = subreg_lowpart_offset (mode, GET_MODE (last_reg));
                  i += subreg_regno_offset (i, GET_MODE (last_reg), byte,
mode);
                  last_class = REGNO_REG_CLASS (i);

also fixes the ICE, and seems more correct to me.  I'll do full bootstraps and
regression test runs with it on my usual targets (x86_64, arm, m68k, sparc64,
powerpc64) shortly.

Reply via email to