https://gcc.gnu.org/g:301747370a2a8f5654dfefba7561ccde995f512e

commit r17-5412-g301747370a2a8f5654dfefba7561ccde995f512e
Author: Kyrylo Tkachov <[email protected]>
Date:   Tue Aug 11 07:50:36 2026 +0200

    reassoc: Use target policy to order conditional compares
    
    Only the first comparison of a conditional-compare sequence is a plain
    comparison.  Its operand constraints can differ from those of later
    conditional comparisons.  Reassociation orders operands by rank and does
    not know these target constraints.  For
    
      int
      f (int a, int b, int c, int d, int e)
      {
        return (e == 100) & (a < b) & (c < d);
      }
    
    AArch64 materialised the constant for a conditional comparison:
    
            cmp     w0, w1
            mov     w0, 100
            ccmp    w2, w3, 0, lt
            ccmp    w4, w0, 0, lt
            cset    w0, eq
    
    Add a target hook that rejects an unsupported comparison or classifies it
    as having no head preference, preferring the head, or requiring the head.
    Targets that do not implement the hook keep the normal reassociation order.
    The generic pass also requires a forwarded trapping comparison to lead.
    Reject a chain with two comparisons that require this position.
    
    AArch64 prefers constants that a plain comparison accepts but a conditional
    comparison does not.  It also prefers a floating-point zero.  The example
    then emits:
    
            cmp     w4, 100
            ccmp    w2, w3, 0, eq
            ccmp    w0, w1, 0, lt
            cset    w0, lt
    
    Tests cover AArch64 immediate and floating-point constraints and a conflict
    between a trapping comparison and preferred constants.
    
    Changes since v1:
    - Replace generic operand scoring with a target policy hook.
    - Separate unsupported, neutral, preferred, and required heads.
    - Implement only the AArch64 policy and leave x86 on the safe default.
    
    gcc/
    
            * config/aarch64/aarch64.cc
            (aarch64_ccmp_first_preference): New function.
            (TARGET_CCMP_FIRST_PREFERENCE): Define.
            * coretypes.h (ccmp_first_kind): New enum.
            * doc/tm.texi: Regenerate.
            * doc/tm.texi.in (TARGET_CCMP_FIRST_PREFERENCE): Add hook.
            * target.def (ccmp_first_preference): New hook.
            * targhooks.cc (default_ccmp_first_preference): New function.
            * targhooks.h (default_ccmp_first_preference): Declare.
            * tree-ssa-reassoc.cc (ccmp_first_preference): New function.
            (prepare_ccmp_chain): Use it to select one head.  Reject unsupported
            comparisons and multiple required heads.
    
    gcc/testsuite/
    
            * gcc.target/aarch64/ccmp_8.c: New test.
            * gcc.target/aarch64/fccmp_4.c: Likewise.
            * gcc.target/aarch64/fccmp_7.c: Likewise.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/config/aarch64/aarch64.cc              | 35 +++++++++++++++++++++++++++
 gcc/coretypes.h                            |  8 +++++++
 gcc/doc/tm.texi                            |  9 +++++++
 gcc/doc/tm.texi.in                         |  2 ++
 gcc/target.def                             | 13 +++++++++-
 gcc/targhooks.cc                           |  9 +++++++
 gcc/targhooks.h                            |  2 ++
 gcc/testsuite/gcc.target/aarch64/ccmp_8.c  | 38 ++++++++++++++++++++++++++++++
 gcc/testsuite/gcc.target/aarch64/fccmp_4.c | 20 ++++++++++++++++
 gcc/testsuite/gcc.target/aarch64/fccmp_7.c | 18 ++++++++++++++
 gcc/tree-ssa-reassoc.cc                    | 38 +++++++++++++++++++++++++-----
 11 files changed, 185 insertions(+), 7 deletions(-)

diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index a2043a57dcf0..5b1c78462a37 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -29905,6 +29905,38 @@ aarch64_asan_shadow_offset (void)
     return (HOST_WIDE_INT_1 << 36);
 }
 
+/* Implement TARGET_CCMP_FIRST_PREFERENCE.  */
+
+static ccmp_first_kind
+aarch64_ccmp_first_preference (machine_mode mode, tree_code code,
+                              const_tree op0, const_tree op1)
+{
+  if (SCALAR_FLOAT_MODE_P (mode))
+    {
+      if (!((mode == HFmode && TARGET_FP_F16INST)
+           || mode == SFmode || mode == DFmode))
+       return CCMP_FIRST_UNSUPPORTED;
+      return real_zerop (op1) ? CCMP_FIRST_PREFERRED : CCMP_FIRST_NONE;
+    }
+
+  if (!(mode == QImode || mode == HImode
+       || mode == SImode || mode == DImode))
+    return CCMP_FIRST_UNSUPPORTED;
+
+  if (TREE_CODE (op1) != INTEGER_CST)
+    return CCMP_FIRST_NONE;
+
+  scalar_int_mode int_mode = as_a <scalar_int_mode> (mode);
+  rtx_code rcode = get_rtx_code (code, TYPE_UNSIGNED (TREE_TYPE (op0)));
+  rtx imm = immed_wide_int_const
+    (wi::to_wide (op1, GET_MODE_PRECISION (int_mode)), int_mode);
+  canonicalize_comparison (int_mode, &rcode, &imm);
+
+  return (aarch64_plus_immediate (imm, int_mode)
+         && !aarch64_ccmp_immediate (imm, int_mode)
+         ? CCMP_FIRST_PREFERRED : CCMP_FIRST_NONE);
+}
+
 static rtx
 aarch64_gen_ccmp_first (rtx_insn **prep_seq, rtx_insn **gen_seq,
                        rtx_code code, tree treeop0, tree treeop1)
@@ -30110,6 +30142,9 @@ aarch64_gen_ccmp_next (rtx_insn **prep_seq, rtx_insn 
**gen_seq, rtx prev,
 #undef TARGET_GEN_CCMP_FIRST
 #define TARGET_GEN_CCMP_FIRST aarch64_gen_ccmp_first
 
+#undef TARGET_CCMP_FIRST_PREFERENCE
+#define TARGET_CCMP_FIRST_PREFERENCE aarch64_ccmp_first_preference
+
 #undef TARGET_GEN_CCMP_NEXT
 #define TARGET_GEN_CCMP_NEXT aarch64_gen_ccmp_next
 
diff --git a/gcc/coretypes.h b/gcc/coretypes.h
index 5cc602ed7e5d..afd228fb12eb 100644
--- a/gcc/coretypes.h
+++ b/gcc/coretypes.h
@@ -297,6 +297,14 @@ enum optimization_type {
   OPTIMIZE_FOR_SIZE
 };
 
+/* How strongly a comparison should start a conditional-compare sequence.  */
+enum ccmp_first_kind {
+  CCMP_FIRST_UNSUPPORTED,
+  CCMP_FIRST_NONE,
+  CCMP_FIRST_PREFERRED,
+  CCMP_FIRST_REQUIRED
+};
+
 /* Enumerates a padding direction.  */
 enum pad_direction {
   /* No padding is required.  */
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 17fae2e3a0e5..09512a07a87c 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -12664,6 +12664,15 @@ This function prepares to emit a comparison insn for 
the first compare in a
  @var{code} is the @code{rtx_code} of the compare for @var{op0} and @var{op1}.
 @end deftypefn
 
+@deftypefn {Target Hook} ccmp_first_kind TARGET_CCMP_FIRST_PREFERENCE 
(machine_mode @var{mode}, tree_code @var{code}, const_tree @var{op0}, 
const_tree @var{op1})
+Return how strongly the comparison of @var{op0} and @var{op1} with tree
+code @var{code} and mode @var{mode} should start a conditional-compare
+sequence.  Return @code{CCMP_FIRST_REQUIRED} if the comparison cannot be
+conditional, @code{CCMP_FIRST_PREFERRED} if starting with it is profitable,
+@code{CCMP_FIRST_NONE} if its position does not matter, or
+@code{CCMP_FIRST_UNSUPPORTED} if it cannot be part of the sequence.
+@end deftypefn
+
 @deftypefn {Target Hook} rtx TARGET_GEN_CCMP_NEXT (rtx_insn **@var{prep_seq}, 
rtx_insn **@var{gen_seq}, rtx @var{prev}, rtx_code @var{cmp_code}, tree 
@var{op0}, tree @var{op1}, rtx_code @var{bit_code})
 This function prepares to emit a conditional comparison within a sequence
  of conditional comparisons.  It returns an appropriate comparison with
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 2113b9c679a3..9faacb28781d 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -8010,6 +8010,8 @@ lists.
 
 @hook TARGET_GEN_CCMP_FIRST
 
+@hook TARGET_CCMP_FIRST_PREFERENCE
+
 @hook TARGET_GEN_CCMP_NEXT
 
 @hook TARGET_HAVE_CCMP
diff --git a/gcc/target.def b/gcc/target.def
index 4c4f91827b2c..20c7f111295d 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -2831,6 +2831,18 @@ DEFHOOK
  rtx, (rtx_insn **prep_seq, rtx_insn **gen_seq, rtx_code code, tree op0, tree 
op1),
  NULL)
 
+DEFHOOK
+(ccmp_first_preference,
+ "Return how strongly the comparison of @var{op0} and @var{op1} with tree\n\
+code @var{code} and mode @var{mode} should start a conditional-compare\n\
+sequence.  Return @code{CCMP_FIRST_REQUIRED} if the comparison cannot be\n\
+conditional, @code{CCMP_FIRST_PREFERRED} if starting with it is profitable,\n\
+@code{CCMP_FIRST_NONE} if its position does not matter, or\n\
+@code{CCMP_FIRST_UNSUPPORTED} if it cannot be part of the sequence.",
+ ccmp_first_kind,
+ (machine_mode mode, tree_code code, const_tree op0, const_tree op1),
+ default_ccmp_first_preference)
+
 DEFHOOK
 (gen_ccmp_next,
  "This function prepares to emit a conditional comparison within a sequence\n\
@@ -7749,4 +7761,3 @@ The default value is NULL.",
 
 /* Close the 'struct gcc_target' definition.  */
 HOOK_VECTOR_END (C90_EMPTY_HACK)
-
diff --git a/gcc/targhooks.cc b/gcc/targhooks.cc
index 0e4d0a15d0e5..21cbfda48018 100644
--- a/gcc/targhooks.cc
+++ b/gcc/targhooks.cc
@@ -1962,6 +1962,15 @@ default_have_ccmp (void)
   return targetm.gen_ccmp_first != NULL;
 }
 
+/* Default implementation of TARGET_CCMP_FIRST_PREFERENCE.  */
+
+ccmp_first_kind
+default_ccmp_first_preference (machine_mode, tree_code, const_tree,
+                              const_tree)
+{
+  return CCMP_FIRST_UNSUPPORTED;
+}
+
 /* By default we assume that c99 functions are present at the runtime,
    but sincos is not.  */
 bool
diff --git a/gcc/targhooks.h b/gcc/targhooks.h
index 2a429e7f4a48..4b029bbe4053 100644
--- a/gcc/targhooks.h
+++ b/gcc/targhooks.h
@@ -221,6 +221,8 @@ extern rtx default_addr_space_convert (rtx, tree, tree);
 extern unsigned int default_case_values_threshold (void);
 extern bool default_have_conditional_execution (void);
 extern bool default_have_ccmp (void);
+extern ccmp_first_kind default_ccmp_first_preference
+  (machine_mode, tree_code, const_tree, const_tree);
 
 extern bool default_libc_has_function (enum function_class, tree);
 extern bool default_libc_has_fast_function (int fcode);
diff --git a/gcc/testsuite/gcc.target/aarch64/ccmp_8.c 
b/gcc/testsuite/gcc.target/aarch64/ccmp_8.c
new file mode 100644
index 000000000000..8ed28057caf8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/ccmp_8.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/* Only the leading comparison of a conditional compare sequence is a plain
+   comparison, which accepts constants that CCMP rejects.  */
+
+/*
+** int_head:
+**     cmp     w4, 100
+**     ccmp    w0, w1, 0, eq
+**     ccmp    w2, w3, 0, lt
+**     cset    w0, lt
+**     ret
+*/
+int
+int_head (int a, int b, int c, int d, int e)
+{
+  return (e == 100) & (a < b) & (c < d);
+}
+
+/* Both comparisons against a constant reach the leading pair, so the
+   expander can compare their costs and lead with the constant that CCMP
+   cannot encode.  */
+
+/*
+** int_two_constants:
+**     cmp     w0, 100
+**     ccmp    w1, w2, 0, eq
+**     ccmp    w3, 3, 0, lt
+**     cset    w0, eq
+**     ret
+*/
+int
+int_two_constants (int a, int b, int c, int d)
+{
+  return (a == 100) & (b < c) & (d == 3);
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/fccmp_4.c 
b/gcc/testsuite/gcc.target/aarch64/fccmp_4.c
new file mode 100644
index 000000000000..978436e89541
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/fccmp_4.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -ffinite-math-only" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/* FCMP compares against zero, FCCMP does not.  A comparison against a
+   constant therefore has to reach the head of the sequence.  */
+
+/*
+** fp_zero_head:
+**     fcmp    d4, #0\.0
+**     fccmp   d0, d1, 0, mi
+**     fccmp   d2, d3, 0, mi
+**     cset    w0, mi
+**     ret
+*/
+int
+fp_zero_head (double a, double b, double c, double d, double e)
+{
+  return (e < 0.0) & (a < b) & (c < d);
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/fccmp_7.c 
b/gcc/testsuite/gcc.target/aarch64/fccmp_7.c
new file mode 100644
index 000000000000..23f6f81e1035
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/fccmp_7.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+/* A trapping comparison takes precedence over comparisons that are cheaper
+   at the start of the sequence.  */
+
+int
+trap_before_zero (double a, double b, double c, double x, double y,
+                 int i, int j)
+{
+  return (a == 0.0) & (x < y) & (c == 0.0) & (i < j);
+}
+
+/* { dg-final { scan-assembler-times {\tfcmpe\t} 1 } } */
+/* { dg-final { scan-assembler-times {\tfccmp\t} 2 } } */
+/* { dg-final { scan-assembler-times {\tccmp\t} 1 } } */
+/* { dg-final { scan-assembler-times {\tcset\t} 1 } } */
+/* { dg-final { scan-assembler-not {\tand\t} } } */
diff --git a/gcc/tree-ssa-reassoc.cc b/gcc/tree-ssa-reassoc.cc
index ca342ba3d8c8..8df554ebc8ce 100644
--- a/gcc/tree-ssa-reassoc.cc
+++ b/gcc/tree-ssa-reassoc.cc
@@ -5196,8 +5196,28 @@ ccmp_comparison_p (tree op, basic_block bb)
 
   /* A conditional compare sets the flags from one scalar comparison.  */
   machine_mode mode = TYPE_MODE (TREE_TYPE (gimple_assign_rhs1 (def)));
-  return ((SCALAR_INT_MODE_P (mode) || SCALAR_FLOAT_MODE_P (mode))
-         && known_le (GET_MODE_SIZE (mode), UNITS_PER_WORD));
+  return SCALAR_INT_MODE_P (mode) || SCALAR_FLOAT_MODE_P (mode);
+}
+
+/* Return how strongly OP should start a conditional compare sequence.
+   FOLDABLE is true if OP can be folded into the flags.  */
+
+static ccmp_first_kind
+ccmp_first_preference (tree op, bool foldable)
+{
+  if (!foldable)
+    return CCMP_FIRST_NONE;
+
+  gimple *def = SSA_NAME_DEF_STMT (op);
+  gcc_checking_assert (is_gimple_assign (def));
+
+  tree op0 = gimple_assign_rhs1 (def);
+  tree op1 = gimple_assign_rhs2 (def);
+  ccmp_first_kind kind = targetm.ccmp_first_preference
+    (TYPE_MODE (TREE_TYPE (op0)), gimple_assign_rhs_code (def), op0, op1);
+  if (kind == CCMP_FIRST_UNSUPPORTED || !gimple_could_trap_p (def))
+    return kind;
+  return CCMP_FIRST_REQUIRED;
 }
 
 /* Return true if all operands in OPS form one conditional compare sequence
@@ -5214,6 +5234,7 @@ prepare_ccmp_chain (vec<operand_entry *> &ops, tree_code 
code, basic_block bb)
 
   bool seen_foldable = false;
   int head = -1;
+  ccmp_first_kind head_kind = CCMP_FIRST_NONE;
 
   for (unsigned int i = 0; i < ops.length (); ++i)
     {
@@ -5222,12 +5243,17 @@ prepare_ccmp_chain (vec<operand_entry *> &ops, 
tree_code code, basic_block bb)
        return false;
 
       bool foldable = has_single_use (oe->op);
-      if (foldable
-         && gimple_could_trap_p (SSA_NAME_DEF_STMT (oe->op)))
+
+      ccmp_first_kind kind
+       = ccmp_first_preference (oe->op, foldable);
+      if (kind == CCMP_FIRST_UNSUPPORTED)
+       return false;
+      if (kind == CCMP_FIRST_REQUIRED && head_kind == CCMP_FIRST_REQUIRED)
+       return false;
+      if (kind > head_kind)
        {
-         if (head >= 0)
-           return false;
          head = i;
+         head_kind = kind;
        }
 
       /* Comparison leaves can have uses outside this chain.  */

Reply via email to