[arm] Handle some constant comparisons using rsbs+rscs
authorRichard Earnshaw <rearnsha@arm.com>
Fri, 18 Oct 2019 19:03:50 +0000 (19:03 +0000)
committerRichard Earnshaw <rearnsha@gcc.gnu.org>
Fri, 18 Oct 2019 19:03:50 +0000 (19:03 +0000)
In a small number of cases it is preferable to handle comparisons with
constants using the sequence

RSBS tmp, Xlo, constlo
RSCS tmp, Xhi, consthi

which allows us to handle a small number of LE/GT/LEU/GEU cases when
changing the code to use LT/GE/LTU/GEU would make the constant more
expensive.  Sadly, we cannot do this on Thumb, since we need RSC, so we
now always use the incremented constant in that case since normally that
still works out cheaper than forcing the entire constant into a register.

Further investigation has also shown that the canonicalization of a
reverse subtract and compare is valid for signed as well as unsigned value,
so we relax the restriction on selecting CC_RSBmode to allow all types
of compare.

* config/arm/arm.c (arm_const_double_prefer_rsbs_rsc): New function.
(arm_canonicalize_comparison): For GT/LE/GTU/GEU, use the constant
unchanged only if that will be cheaper.
(arm_select_cc_mode): Recognize a swapped comparison that will
be regenerated using RSBS or RSCS.  Relax restriction on selecting
CC_RSBmode.
(arm_gen_dicompare_reg): Handle LE/GT/LEU/GEU comparisons against
a constant.
(arm_gen_compare_reg): Handle compare (CONST, X) when the mode
is CC_RSBmode.
(maybe_get_arm_condition_code): CC_RSBmode now returns the same codes
as CCmode.
* config/arm/arm.md (rsb_imm_compare_scratch): New pattern.
(rscsi3_<CC_EXTEND>out_scratch): New pattern.

From-SVN: r277180

gcc/ChangeLog
gcc/config/arm/arm.c
gcc/config/arm/arm.md

index aa489bd9c985d92cc7b32711f1fc9710e8a7af98..b4b18113fbd4d9ae94da99aa00cf93c603505ad5 100644 (file)
@@ -1,3 +1,20 @@
+2019-10-18  Richard Earnshaw  <rearnsha@arm.com>
+
+       * config/arm/arm.c (arm_const_double_prefer_rsbs_rsc): New function.
+       (arm_canonicalize_comparison): For GT/LE/GTU/GEU, use the constant
+       unchanged only if that will be cheaper.
+       (arm_select_cc_mode): Recognize a swapped comparison that will
+       be regenerated using RSBS or RSCS.  Relax restriction on selecting
+       CC_RSBmode.
+       (arm_gen_dicompare_reg): Handle LE/GT/LEU/GEU comparisons against
+       a constant.
+       (arm_gen_compare_reg): Handle compare (CONST, X) when the mode
+       is CC_RSBmode.
+       (maybe_get_arm_condition_code): CC_RSBmode now returns the same codes
+       as CCmode.
+       * config/arm/arm.md (rsb_imm_compare_scratch): New pattern.
+       (rscsi3_<CC_EXTEND>out_scratch): New pattern.
+
 2019-10-18  Richard Earnshaw  <rearnsha@arm.com>
 
        * config/arm/arm-modes.def (CC_NV, CC_B): New CC modes.
index 74c8eca7a646d454abc1796a498b8c7144f1fab0..55fd16df67886a05e73bd92369ac05cca49d7666 100644 (file)
@@ -5355,6 +5355,21 @@ arm_gen_constant (enum rtx_code code, machine_mode mode, rtx cond,
   return insns;
 }
 
+/* Return TRUE if op is a constant where both the low and top words are
+   suitable for RSB/RSC instructions.  This is never true for Thumb, since
+   we do not have RSC in that case.  */
+static bool
+arm_const_double_prefer_rsbs_rsc (rtx op)
+{
+  /* Thumb lacks RSC, so we never prefer that sequence.  */
+  if (TARGET_THUMB || !CONST_INT_P (op))
+    return false;
+  HOST_WIDE_INT hi, lo;
+  lo = UINTVAL (op) & 0xffffffffULL;
+  hi = UINTVAL (op) >> 32;
+  return const_ok_for_arm (lo) && const_ok_for_arm (hi);
+}
+
 /* Canonicalize a comparison so that we are more likely to recognize it.
    This can be done for a few constant compares, where we can make the
    immediate value easier to load.  */
@@ -5380,8 +5395,7 @@ arm_canonicalize_comparison (int *code, rtx *op0, rtx *op1,
     {
 
       if (*code == GT || *code == LE
-         || ((!TARGET_ARM || CONST_INT_P (*op1))
-             && (*code == GTU || *code == LEU)))
+         || *code == GTU || *code == LEU)
        {
          /* Missing comparison.  First try to use an available
             comparison.  */
@@ -5392,10 +5406,13 @@ arm_canonicalize_comparison (int *code, rtx *op0, rtx *op1,
                {
                case GT:
                case LE:
-                 if (i != maxval
-                     && (!arm_const_double_by_immediates (*op1)
-                         || arm_const_double_by_immediates (GEN_INT (i + 1))))
+                 if (i != maxval)
                    {
+                     /* Try to convert to GE/LT, unless that would be more
+                        expensive.  */
+                     if (!arm_const_double_by_immediates (GEN_INT (i + 1))
+                         && arm_const_double_prefer_rsbs_rsc (*op1))
+                       return;
                      *op1 = GEN_INT (i + 1);
                      *code = *code == GT ? GE : LT;
                      return;
@@ -5404,10 +5421,13 @@ arm_canonicalize_comparison (int *code, rtx *op0, rtx *op1,
 
                case GTU:
                case LEU:
-                 if (i != ~((unsigned HOST_WIDE_INT) 0)
-                     && (!arm_const_double_by_immediates (*op1)
-                         || arm_const_double_by_immediates (GEN_INT (i + 1))))
+                 if (i != ~((unsigned HOST_WIDE_INT) 0))
                    {
+                     /* Try to convert to GEU/LTU, unless that would
+                        be more expensive.  */
+                     if (!arm_const_double_by_immediates (GEN_INT (i + 1))
+                         && arm_const_double_prefer_rsbs_rsc (*op1))
+                       return;
                      *op1 = GEN_INT (i + 1);
                      *code = *code == GTU ? GEU : LTU;
                      return;
@@ -5419,7 +5439,6 @@ arm_canonicalize_comparison (int *code, rtx *op0, rtx *op1,
                }
            }
 
-         /* If that did not work, reverse the condition.  */
          if (!op0_preserve_value)
            {
              std::swap (*op0, *op1);
@@ -15251,6 +15270,28 @@ arm_select_cc_mode (enum rtx_code op, rtx x, rtx y)
          || GET_CODE (x) == ROTATERT))
     return CC_SWPmode;
 
+  /* A widened compare of the sum of a value plus a carry against a
+     constant.  This is a representation of RSC.  We want to swap the
+     result of the comparison at output.  Not valid if the Z bit is
+     needed.  */
+  if (GET_MODE (x) == DImode
+      && GET_CODE (x) == PLUS
+      && arm_borrow_operation (XEXP (x, 1), DImode)
+      && CONST_INT_P (y)
+      && ((GET_CODE (XEXP (x, 0)) == SIGN_EXTEND
+          && (op == LE || op == GT))
+         || (GET_CODE (XEXP (x, 0)) == ZERO_EXTEND
+             && (op == LEU || op == GTU))))
+    return CC_SWPmode;
+
+  /* If X is a constant we want to use CC_RSBmode.  This is
+     non-canonical, but arm_gen_compare_reg uses this to generate the
+     correct canonical form.  */
+  if (GET_MODE (y) == SImode
+      && (REG_P (y) || GET_CODE (y) == SUBREG)
+      && CONST_INT_P (x))
+    return CC_RSBmode;
+
   /* This operation is performed swapped, but since we only rely on the Z
      flag we don't need an additional mode.  */
   if (GET_MODE (y) == SImode
@@ -15329,15 +15370,13 @@ arm_select_cc_mode (enum rtx_code op, rtx x, rtx y)
          || (TARGET_32BIT && GET_CODE (x) == ZERO_EXTRACT)))
     return CC_NOOVmode;
 
-  /* An unsigned comparison of ~reg with a const is really a special
+  /* A comparison of ~reg with a const is really a special
      canoncialization of compare (~const, reg), which is a reverse
      subtract operation.  We may not get here if CONST is 0, but that
      doesn't matter because ~0 isn't a valid immediate for RSB.  */
   if (GET_MODE (x) == SImode
       && GET_CODE (x) == NOT
-      && CONST_INT_P (y)
-      && (op == EQ || op == NE
-         || op == LTU || op == LEU || op == GEU || op == GTU))
+      && CONST_INT_P (y))
     return CC_RSBmode;
 
   if (GET_MODE (x) == QImode && (op == EQ || op == NE))
@@ -15431,6 +15470,7 @@ arm_gen_dicompare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
 
     /* We don't currently handle DImode in thumb1, but rely on libgcc.  */
   gcc_assert (TARGET_32BIT);
+  gcc_assert (!CONST_INT_P (x));
 
   rtx x_lo = simplify_gen_subreg (SImode, x, DImode,
                                  subreg_lowpart_offset (SImode, DImode));
@@ -15445,9 +15485,6 @@ arm_gen_dicompare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
     case EQ:
     case NE:
       {
-       /* We should never have X as a const_int in this case.  */
-       gcc_assert (!CONST_INT_P (x));
-
        if (y_lo == const0_rtx || y_hi == const0_rtx)
          {
            if (y_lo != const0_rtx)
@@ -15525,10 +15562,6 @@ arm_gen_dicompare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
        if (!arm_add_operand (y_lo, SImode))
          y_lo = force_reg (SImode, y_lo);
 
-       /* Just for now.  */
-       if (!register_operand (x_lo, SImode))
-         x_lo = force_reg (SImode, x_lo);
-
        rtx cmp1
          = gen_rtx_LTU (DImode,
                         arm_gen_compare_reg (LTU, x_lo, y_lo, NULL_RTX),
@@ -15536,13 +15569,10 @@ arm_gen_dicompare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
 
        if (!scratch)
          scratch = gen_rtx_SCRATCH (SImode);
+
        if (!arm_not_operand (y_hi, SImode))
          y_hi = force_reg (SImode, y_hi);
 
-       /* Just for now.  */
-       if (!register_operand (x_hi, SImode))
-         x_hi = force_reg (SImode, x_hi);
-
        rtx_insn *insn;
        if (y_hi == const0_rtx)
          insn = emit_insn (gen_cmpsi3_0_carryin_CC_NVout (scratch, x_hi,
@@ -15556,6 +15586,27 @@ arm_gen_dicompare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
        return SET_DEST (single_set (insn));
       }
 
+    case LE:
+    case GT:
+      {
+       /* During expansion, we only expect to get here if y is a
+          constant that we want to handle, otherwise we should have
+          swapped the operands already.  */
+       gcc_assert (arm_const_double_prefer_rsbs_rsc (y));
+
+       if (!const_ok_for_arm (INTVAL (y_lo)))
+         y_lo = force_reg (SImode, y_lo);
+
+       /* Perform a reverse subtract and compare.  */
+       rtx cmp1
+         = gen_rtx_LTU (DImode,
+                        arm_gen_compare_reg (LTU, y_lo, x_lo, scratch),
+                        const0_rtx);
+       rtx_insn *insn = emit_insn (gen_rscsi3_CC_NVout_scratch (scratch, y_hi,
+                                                                x_hi, cmp1));
+       return SET_DEST (single_set (insn));
+      }
+
     case LTU:
     case GEU:
       {
@@ -15572,10 +15623,6 @@ arm_gen_dicompare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
        if (!arm_add_operand (y_lo, SImode))
          y_lo = force_reg (SImode, y_lo);
 
-       /* Just for now.  */
-       if (!register_operand (x_lo, SImode))
-         x_lo = force_reg (SImode, x_lo);
-
        rtx cmp1
          = gen_rtx_LTU (DImode,
                         arm_gen_compare_reg (LTU, x_lo, y_lo, NULL_RTX),
@@ -15586,10 +15633,6 @@ arm_gen_dicompare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
        if (!arm_not_operand (y_hi, SImode))
          y_hi = force_reg (SImode, y_hi);
 
-       /* Just for now.  */
-       if (!register_operand (x_hi, SImode))
-         x_hi = force_reg (SImode, x_hi);
-
        rtx_insn *insn;
        if (y_hi == const0_rtx)
          insn = emit_insn (gen_cmpsi3_0_carryin_CC_Bout (scratch, x_hi,
@@ -15607,6 +15650,28 @@ arm_gen_dicompare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
        return SET_DEST (single_set (insn));
       }
 
+    case LEU:
+    case GTU:
+      {
+       /* During expansion, we only expect to get here if y is a
+          constant that we want to handle, otherwise we should have
+          swapped the operands already.  */
+       gcc_assert (arm_const_double_prefer_rsbs_rsc (y));
+
+       if (!const_ok_for_arm (INTVAL (y_lo)))
+         y_lo = force_reg (SImode, y_lo);
+
+       /* Perform a reverse subtract and compare.  */
+       rtx cmp1
+         = gen_rtx_LTU (DImode,
+                        arm_gen_compare_reg (LTU, y_lo, x_lo, scratch),
+                        const0_rtx);
+       y_hi = GEN_INT (0xffffffff & UINTVAL (y_hi));
+       rtx_insn *insn = emit_insn (gen_rscsi3_CC_Bout_scratch (scratch, y_hi,
+                                                               x_hi, cmp1));
+       return SET_DEST (single_set (insn));
+      }
+
     default:
       break;
     }
@@ -15695,8 +15760,15 @@ arm_gen_compare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
 
   machine_mode mode = SELECT_CC_MODE (code, x, y);
   rtx cc_reg = gen_rtx_REG (mode, CC_REGNUM);
-
-  emit_set_insn (cc_reg, gen_rtx_COMPARE (mode, x, y));
+  if (mode == CC_RSBmode)
+    {
+      if (!scratch)
+       scratch = gen_rtx_SCRATCH (SImode);
+      emit_insn (gen_rsb_imm_compare_scratch (scratch,
+                                             GEN_INT (~UINTVAL (x)), y));
+    }
+  else
+    emit_set_insn (cc_reg, gen_rtx_COMPARE (mode, x, y));
 
   return cc_reg;
 }
@@ -24025,19 +24097,8 @@ maybe_get_arm_condition_code (rtx comparison)
        default: return ARM_NV;
        }
 
-    case E_CC_RSBmode:
-      switch (comp_code)
-       {
-       case NE: return ARM_NE;
-       case EQ: return ARM_EQ;
-       case GEU: return ARM_CS;
-       case GTU: return ARM_HI;
-       case LEU: return ARM_LS;
-       case LTU: return ARM_CC;
-       default: return ARM_NV;
-       }
-
     case E_CCmode:
+    case E_CC_RSBmode:
       switch (comp_code)
        {
        case NE: return ARM_NE;
index f0ff4dda39679751258df1b3ff0b3dba04ba458c..8607c6f95da9e965b192e8c5fb4987967ec135a5 100644 (file)
    (set_attr "type" "alus_imm")]
 )
 
+;; Similarly, but the result is unused.
+(define_insn "rsb_imm_compare_scratch"
+  [(set (reg:CC_RSB CC_REGNUM)
+       (compare:CC_RSB (not:SI (match_operand:SI 2 "s_register_operand" "r"))
+                       (match_operand 1 "arm_not_immediate_operand" "K")))
+   (clobber (match_scratch:SI 0 "=r"))]
+  "TARGET_32BIT"
+  "rsbs\\t%0, %2, #%B1"
+  [(set_attr "conds" "set")
+   (set_attr "type" "alus_imm")]
+)
+
+;; Compare the sum of a value plus a carry against a constant.  Uses
+;; RSC, so the result is swapped.  Only available on Arm
+(define_insn "rscsi3_<CC_EXTEND>out_scratch"
+  [(set (reg:CC_SWP CC_REGNUM)
+       (compare:CC_SWP
+        (plus:DI (SE:DI (match_operand:SI 2 "s_register_operand" "r"))
+                 (match_operand:DI 3 "arm_borrow_operation" ""))
+        (match_operand 1 "arm_immediate_operand" "I")))
+   (clobber (match_scratch:SI 0 "=r"))]
+  "TARGET_ARM"
+  "rscs\\t%0, %2, %1"
+  [(set_attr "conds" "set")
+   (set_attr "type" "alus_imm")]
+)
+
 (define_expand "subsf3"
   [(set (match_operand:SF           0 "s_register_operand")
        (minus:SF (match_operand:SF 1 "s_register_operand")