From: Soumya AR <[email protected]>

This patch implements the expand_ifn_atomic_fetch_minmax function to expand the
IFN_ATOMIC_FETCH_MINMAX internal function to target specific optabs, if
available. It also defines optabs to support atomic fetch min/max operations.

If the target does not support the optab, continue lowering to a CAS loop.

This is done by adding a new callback in atomic_op_lowering alongside the
lowering callback, which checks if backend expansion is possible for that IFN:

If a handler exists, the pass leaves the IFN alone and RTL expand picks it up
via expand_ATOMIC_FETCH_MINMAX -> expand_ifn_atomic_fetch_minmax. Otherwise
we fall back to the CAS loop as before.

----

The expander itself calls expand_atomic_fetch_op rather than the _no_fallback
variant.

This is because can_expand_atomic_minmax in pass_lower_atomic_ifn only confirms
that a backend optab exists; it does not guarantee that maybe_expand_insn's
operand predicates will accept these particular operands.

----

When handling sub-word atomic calls, signed types need special handling.

Consider the following gimple:

    short int atomic_var;
    short int _1;
    atomic_var = -5;
    _1 = .ATOMIC_FETCH_MINMAX (&atomic_var, -10, 5, MIN_EXPR, (short) 0);

When expanding lhs (_1), the RTL is generated as a SUBREG with "/s/u" flags set,
which imply that the value is a sign-extended promoted value. An atomic call
returning a non-extended value to this target will violate the contract.

Just emitting a sign extension after the atomic call is not enough since the
compiler will incorrectly assume that the value is already sign-extended, on the
basis of the previously set flags.

To maintain this contract and ensure the sign extension is not optimized away,
we use a temporary non-promoted register for the atomic operation, explicitly
perform the extension, then move to the original promoted target.

----

Bootstrapped and regression tested on aarch64-linux-gnu and x86_64-linux-gnu,
no regression.  Cross-compiled and regression tested under qemu for
arm-linux-gnueabihf-armv7-a and aarch64-linux-gnu without LSE.

OK for mainline?

Signed-off-by: Soumya AR <[email protected]>

gcc/ChangeLog:

        * atomic-ifn-lowering.cc (struct atomic_op_lowering): Add
        can_expand_directly callback.
        (can_expand_atomic_minmax): Check whether the target supports
        atomic min/max for this datatype.
        (lower_atomic_ifn): Honour can_expand_directly: skip lowering
        when the IFN can be expanded via an optab.
        * builtins.cc (expand_ifn_atomic_fetch_minmax): New.
        * builtins.h (expand_ifn_atomic_fetch_minmax): Declare.
        * internal-fn.cc (expand_ATOMIC_FETCH_MINMAX): Dispatch to
        expand_ifn_atomic_fetch_minmax.
        * optabs.cc (get_atomic_op_for_code): Handle SMIN, SMAX, UMIN,
        UMAX.
        (maybe_emit_op): Likewise.
        * optabs.def (OPTAB_NC, OPTAB_D): Add atomic_fetch_{s,u}{min,max}
        and atomic_{s,u}{min,max} entries.
        * optabs.h (expand_atomic_fetch_op_no_fallback): Export.
---
 gcc/atomic-ifn-lowering.cc | 60 +++++++++++++++++++++++++---
 gcc/builtins.cc            | 82 ++++++++++++++++++++++++++++++++++++++
 gcc/builtins.h             |  1 +
 gcc/internal-fn.cc         |  4 +-
 gcc/optabs.cc              | 38 +++++++++++++++++-
 gcc/optabs.def             | 24 +++++++++++
 gcc/optabs.h               |  2 +
 7 files changed, 202 insertions(+), 9 deletions(-)

diff --git a/gcc/atomic-ifn-lowering.cc b/gcc/atomic-ifn-lowering.cc
index 77129a1ca7e..ae689dfdae7 100644
--- a/gcc/atomic-ifn-lowering.cc
+++ b/gcc/atomic-ifn-lowering.cc
@@ -50,6 +50,7 @@ After lowering the CFG looks somewhat like:
 #include "coretypes.h"
 #include "backend.h"
 #include "target.h"
+#include "rtl.h"
 #include "tree.h"
 #include "gimple.h"
 #include "gimple-iterator.h"
@@ -59,19 +60,21 @@ After lowering the CFG looks somewhat like:
 #include "gimple-pretty-print.h"
 #include "memmodel.h"
 #include "ssa.h"
+#include "optabs.h"
 #include "optabs-query.h"
 #include "fold-const.h"
 #include "builtins.h"
 #include "internal-fn.h"
 
 /* Descriptor for a single atomic-fetch IFN this pass knows how to lower.
-   LOWER is called to replace the IFN call: it must remove the call from
-   the gimple, typically by emitting a CAS loop via lower_via_cas_loop
-   with IFN-specific callbacks.  */
+   CAN_EXPAND_DIRECTLY returns true when the backend can expand the IFN via
+   a direct optab, in which case the pass leaves the call alone for the RTL
+   expander.  LOWER is called to replace the IFN call with a CAS loop.  */
 
 struct atomic_op_lowering
 {
   internal_fn ifn;
+  bool (*can_expand_directly) (gcall *);
   void (*lower) (gcall *);
 };
 
@@ -334,6 +337,48 @@ emit_minmax_lhs_value (tree datatype, tree current_value,
                                     NOP_EXPR, false);
 }
 
+/* Return true when the backend can expand an IFN_ATOMIC_FETCH_MINMAX call
+   directly via a target optab.  */
+
+static bool
+can_expand_atomic_minmax (gcall *call)
+{
+  if (!flag_inline_atomics)
+    return false;
+
+  tree datatype = TREE_TYPE (gimple_call_arg (call, 4));
+  /* Arg 3 is the operation tree_code (MIN_EXPR or MAX_EXPR).  */
+  tree_code cmp_code
+    = (tree_code) tree_to_uhwi (gimple_call_arg (call, 3));
+  gcc_assert (cmp_code == MIN_EXPR || cmp_code == MAX_EXPR);
+  bool is_min = (cmp_code == MIN_EXPR);
+  bool is_signed = !TYPE_UNSIGNED (datatype);
+  bool unused_result = (gimple_call_lhs (call) == NULL_TREE);
+  machine_mode mode = TYPE_MODE (datatype);
+
+  optab fetch_op_optab
+    = is_signed ? (is_min ? atomic_fetch_smin_optab : atomic_fetch_smax_optab)
+               : (is_min ? atomic_fetch_umin_optab : atomic_fetch_umax_optab);
+
+  if (optab_handler (fetch_op_optab, mode) != CODE_FOR_nothing)
+    return true;
+
+  /* If the result is unused, the no-result optab is also sufficient.
+     MIN/MAX does not support op_fetch in the frontend, so no need to check
+     for those optabs.  Even if they were supported by the backend, they
+     would not be helpful here.  */
+  if (unused_result)
+    {
+      optab no_result_optab
+       = is_signed ? (is_min ? atomic_smin_optab : atomic_smax_optab)
+                   : (is_min ? atomic_umin_optab : atomic_umax_optab);
+      if (direct_optab_handler (no_result_optab, mode) != CODE_FOR_nothing)
+       return true;
+    }
+
+  return false;
+}
+
 /* Lower an IFN_ATOMIC_FETCH_MINMAX call to a CAS loop.  */
 
 static void
@@ -342,10 +387,11 @@ lower_minmax_call (gcall *call)
   lower_via_cas_loop (call, emit_minmax_desired_value, emit_minmax_lhs_value);
 }
 
-/* Registry of atomic-fetch IFNs this pass lowers.  */
+/* Registry of atomic-fetch IFNs this pass lowers when the target lacks a
+   direct optab.  */
 
 static const atomic_op_lowering atomic_op_table[] = {
-  { IFN_ATOMIC_FETCH_MINMAX, lower_minmax_call },
+  {IFN_ATOMIC_FETCH_MINMAX, can_expand_atomic_minmax, lower_minmax_call},
 };
 
 static unsigned int
@@ -368,7 +414,9 @@ lower_atomic_ifn (void)
        for (const atomic_op_lowering &op : atomic_op_table)
          if (gimple_call_internal_p (call, op.ifn))
            {
-             to_lower.safe_push ({ call, &op });
+             if (op.can_expand_directly && op.can_expand_directly (call))
+               break;
+             to_lower.safe_push ({call, &op});
              break;
            }
       }
diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 52f3ee029d0..8574abe826a 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -6770,6 +6770,88 @@ expand_builtin_atomic_compare_exchange (machine_mode 
mode, tree exp,
   return target;
 }
 
+/* Expand IFN_ATOMIC_FETCH_MINMAX internal function.  */
+
+void
+expand_ifn_atomic_fetch_minmax (gcall *call)
+{
+  tree ptr = gimple_call_arg (call, 0);
+  tree value = gimple_call_arg (call, 1);
+  tree memorder_tree = gimple_call_arg (call, 2);
+  tree_code cmp_code
+    = (tree_code) tree_to_uhwi (gimple_call_arg (call, 3));
+  gcc_assert (cmp_code == MIN_EXPR || cmp_code == MAX_EXPR);
+  tree datatype = TREE_TYPE (gimple_call_arg (call, 4));
+
+  bool is_min = (cmp_code == MIN_EXPR);
+  bool is_signed = !TYPE_UNSIGNED (datatype);
+
+  memmodel model = get_memmodel (memorder_tree);
+  tree lhs = gimple_call_lhs (call);
+
+  enum rtx_code code;
+  if (is_signed)
+    code = is_min ? SMIN : SMAX;
+  else
+    code = is_min ? UMIN : UMAX;
+
+  machine_mode mode = TYPE_MODE (datatype);
+
+  rtx mem = get_builtin_sync_mem (ptr, mode);
+
+  rtx val = expand_expr_force_mode (value, mode);
+
+  rtx target;
+  if (lhs)
+    target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE);
+  else
+    target = gen_reg_rtx (mode);
+
+  /* For sub-word signed operations, we need to handle sign extension
+     carefully.  The issue is that atomic libcalls may not return properly
+     sign-extended values, but the LHS might be a promoted variable that
+     has been marked as a sign-extended register.  A forced sign extension
+     after the atomic call might get optimized away if the target has been
+     marked as sign-extended.  */
+  rtx temp_target = target;
+  if (GET_CODE (target) == SUBREG && SUBREG_PROMOTED_VAR_P (target)
+      && GET_MODE_SIZE (mode).to_constant () < UNITS_PER_WORD)
+    {
+      /* Use a temporary non-promoted register for the atomic operation
+        to avoid incorrect flags for the target register.  */
+      temp_target = gen_reg_rtx (mode);
+    }
+
+  /* Even though we are certain at this stage that we have a backend optab
+     for fetch min/max (otherwise pass_lower_atomic_ifn would have lowered
+     this to CAS), still don't use the _no_fallback version since checking
+     for the optab is not enough to guarantee its emission.  */
+  rtx result = expand_atomic_fetch_op (temp_target, mem, val, code,
+                                      model, false);
+
+  if (!result)
+    gcc_unreachable ();
+
+  /* If we used a temporary target due to promotion, handle the conversion
+     properly.  */
+  if (temp_target != target)
+    {
+      /* For signed types, explicitly sign-extend the result.  */
+      if (is_signed)
+       {
+         rtx extended = gen_reg_rtx (word_mode);
+         emit_insn (
+           gen_rtx_SET (extended, gen_rtx_SIGN_EXTEND (word_mode, result)));
+         result = gen_lowpart (mode, extended);
+       }
+
+      /* Now store to the actual target.  */
+      convert_move (SUBREG_REG (target), result, !is_signed);
+    }
+  else if (result != target)
+    emit_move_insn (target, result);
+}
+
 /* Helper function for expand_ifn_atomic_compare_exchange - expand
    internal ATOMIC_COMPARE_EXCHANGE call into __atomic_compare_exchange_N
    call.  The weak parameter must be dropped to match the expected parameter
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 9cca88c647c..627f4087dea 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -133,6 +133,7 @@ extern void expand_builtin_trap (void);
 extern void expand_ifn_atomic_bit_test_and (gcall *);
 extern void expand_ifn_atomic_compare_exchange (gcall *);
 extern void expand_ifn_atomic_op_fetch_cmp_0 (gcall *);
+extern void expand_ifn_atomic_fetch_minmax (gcall *);
 extern rtx expand_builtin_crc_table_based (internal_fn, scalar_mode,
                                           scalar_mode, machine_mode,
                                           tree, rtx);
diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc
index 692391f67dc..9611d5f922a 100644
--- a/gcc/internal-fn.cc
+++ b/gcc/internal-fn.cc
@@ -3765,9 +3765,9 @@ expand_ATOMIC_XOR_FETCH_CMP_0 (internal_fn, gcall *call)
 /* Expand atomic fetch minmax.  */
 
 static void
-expand_ATOMIC_FETCH_MINMAX (internal_fn, gcall *)
+expand_ATOMIC_FETCH_MINMAX (internal_fn, gcall *call)
 {
-       /* Implement this. */
+  expand_ifn_atomic_fetch_minmax (call);
 }
 
 /* Expand LAUNDER to assignment, lhs = arg0.  */
diff --git a/gcc/optabs.cc b/gcc/optabs.cc
index 57fc9d3306e..828f6bb1089 100644
--- a/gcc/optabs.cc
+++ b/gcc/optabs.cc
@@ -7977,6 +7977,42 @@ get_atomic_op_for_code (struct atomic_op_functions *op, 
enum rtx_code code)
       op->no_result = sync_nand_optab;
       op->reverse_code = UNKNOWN;
       break;
+    case SMIN:
+      op->mem_fetch_before = atomic_fetch_smin_optab;
+      op->mem_fetch_after = atomic_smin_fetch_optab;
+      op->mem_no_result = atomic_smin_optab;
+      op->fetch_before = sync_old_smin_optab;
+      op->fetch_after = sync_new_smin_optab;
+      op->no_result = sync_smin_optab;
+      op->reverse_code = UNKNOWN;
+      break;
+    case SMAX:
+      op->mem_fetch_before = atomic_fetch_smax_optab;
+      op->mem_fetch_after = atomic_smax_fetch_optab;
+      op->mem_no_result = atomic_smax_optab;
+      op->fetch_before = sync_old_smax_optab;
+      op->fetch_after = sync_new_smax_optab;
+      op->no_result = sync_smax_optab;
+      op->reverse_code = UNKNOWN;
+      break;
+    case UMIN:
+      op->mem_fetch_before = atomic_fetch_umin_optab;
+      op->mem_fetch_after = atomic_umin_fetch_optab;
+      op->mem_no_result = atomic_umin_optab;
+      op->fetch_before = sync_old_umin_optab;
+      op->fetch_after = sync_new_umin_optab;
+      op->no_result = sync_umin_optab;
+      op->reverse_code = UNKNOWN;
+      break;
+    case UMAX:
+      op->mem_fetch_before = atomic_fetch_umax_optab;
+      op->mem_fetch_after = atomic_umax_fetch_optab;
+      op->mem_no_result = atomic_umax_optab;
+      op->fetch_before = sync_old_umax_optab;
+      op->fetch_after = sync_new_umax_optab;
+      op->no_result = sync_umax_optab;
+      op->reverse_code = UNKNOWN;
+      break;
     default:
       gcc_unreachable ();
     }
@@ -8095,7 +8131,7 @@ maybe_emit_op (const struct atomic_op_functions *optab, 
rtx target, rtx mem,
    This function will *only* generate instructions if there is a direct
    optab. No compare and swap loops or libcalls will be generated. */
 
-static rtx
+rtx
 expand_atomic_fetch_op_no_fallback (rtx target, rtx mem, rtx val,
                                    enum rtx_code code, enum memmodel model,
                                    bool after)
diff --git a/gcc/optabs.def b/gcc/optabs.def
index 7ccea18543f..9d0eb7dd4ca 100644
--- a/gcc/optabs.def
+++ b/gcc/optabs.def
@@ -215,12 +215,20 @@ OPTAB_NC(sync_old_ior_optab, "sync_old_ior$I$a", UNKNOWN)
 OPTAB_NC(sync_old_and_optab, "sync_old_and$I$a", UNKNOWN)
 OPTAB_NC(sync_old_xor_optab, "sync_old_xor$I$a", UNKNOWN)
 OPTAB_NC(sync_old_nand_optab, "sync_old_nand$I$a", UNKNOWN)
+OPTAB_NC(sync_old_smin_optab, "sync_old_smin$I$a", UNKNOWN)
+OPTAB_NC(sync_old_smax_optab, "sync_old_smax$I$a", UNKNOWN)
+OPTAB_NC(sync_old_umin_optab, "sync_old_umin$I$a", UNKNOWN)
+OPTAB_NC(sync_old_umax_optab, "sync_old_umax$I$a", UNKNOWN)
 OPTAB_NC(sync_new_add_optab, "sync_new_add$I$a", UNKNOWN)
 OPTAB_NC(sync_new_sub_optab, "sync_new_sub$I$a", UNKNOWN)
 OPTAB_NC(sync_new_ior_optab, "sync_new_ior$I$a", UNKNOWN)
 OPTAB_NC(sync_new_and_optab, "sync_new_and$I$a", UNKNOWN)
 OPTAB_NC(sync_new_xor_optab, "sync_new_xor$I$a", UNKNOWN)
 OPTAB_NC(sync_new_nand_optab, "sync_new_nand$I$a", UNKNOWN)
+OPTAB_NC(sync_new_smin_optab, "sync_new_smin$I$a", UNKNOWN)
+OPTAB_NC(sync_new_smax_optab, "sync_new_smax$I$a", UNKNOWN)
+OPTAB_NC(sync_new_umin_optab, "sync_new_umin$I$a", UNKNOWN)
+OPTAB_NC(sync_new_umax_optab, "sync_new_umax$I$a", UNKNOWN)
 OPTAB_NC(sync_compare_and_swap_optab, "sync_compare_and_swap$I$a", UNKNOWN)
 OPTAB_NC(sync_lock_test_and_set_optab, "sync_lock_test_and_set$I$a", UNKNOWN)
 
@@ -524,6 +532,10 @@ OPTAB_D (sync_and_optab, "sync_and$I$a")
 OPTAB_D (sync_ior_optab, "sync_ior$I$a")
 OPTAB_D (sync_lock_release_optab, "sync_lock_release$I$a")
 OPTAB_D (sync_nand_optab, "sync_nand$I$a")
+OPTAB_D (sync_smin_optab, "sync_smin$I$a")
+OPTAB_D (sync_smax_optab, "sync_smax$I$a")
+OPTAB_D (sync_umin_optab, "sync_umin$I$a")
+OPTAB_D (sync_umax_optab, "sync_umax$I$a")
 OPTAB_D (sync_sub_optab, "sync_sub$I$a")
 OPTAB_D (sync_xor_optab, "sync_xor$I$a")
 
@@ -542,6 +554,18 @@ OPTAB_D (atomic_fetch_nand_optab, "atomic_fetch_nand$I$a")
 OPTAB_D (atomic_fetch_or_optab, "atomic_fetch_or$I$a")
 OPTAB_D (atomic_fetch_sub_optab, "atomic_fetch_sub$I$a")
 OPTAB_D (atomic_fetch_xor_optab, "atomic_fetch_xor$I$a")
+OPTAB_D (atomic_fetch_smax_optab, "atomic_fetch_smax$I$a")
+OPTAB_D (atomic_fetch_smin_optab, "atomic_fetch_smin$I$a")
+OPTAB_D (atomic_smax_fetch_optab, "atomic_smax_fetch$I$a")
+OPTAB_D (atomic_smin_fetch_optab, "atomic_smin_fetch$I$a")
+OPTAB_D (atomic_smax_optab, "atomic_smax$I$a")
+OPTAB_D (atomic_smin_optab, "atomic_smin$I$a")
+OPTAB_D (atomic_fetch_umax_optab, "atomic_fetch_umax$I$a")
+OPTAB_D (atomic_fetch_umin_optab, "atomic_fetch_umin$I$a")
+OPTAB_D (atomic_umax_fetch_optab, "atomic_umax_fetch$I$a")
+OPTAB_D (atomic_umin_fetch_optab, "atomic_umin_fetch$I$a")
+OPTAB_D (atomic_umax_optab, "atomic_umax$I$a")
+OPTAB_D (atomic_umin_optab, "atomic_umin$I$a")
 OPTAB_D (atomic_load_optab, "atomic_load$I$a")
 OPTAB_D (atomic_nand_fetch_optab, "atomic_nand_fetch$I$a")
 OPTAB_D (atomic_nand_optab, "atomic_nand$I$a")
diff --git a/gcc/optabs.h b/gcc/optabs.h
index 81f58278887..8647fffaca1 100644
--- a/gcc/optabs.h
+++ b/gcc/optabs.h
@@ -365,6 +365,8 @@ rtx expand_atomic_load (rtx, rtx, enum memmodel);
 rtx expand_atomic_store (rtx, rtx, enum memmodel, bool);
 rtx expand_atomic_fetch_op (rtx, rtx, rtx, enum rtx_code, enum memmodel,
                              bool);
+rtx expand_atomic_fetch_op_no_fallback (rtx, rtx, rtx, enum rtx_code,
+                                       enum memmodel, bool);
 
 extern void expand_asm_reg_clobber_mem_blockage (HARD_REG_SET);
 
-- 
2.43.0

Reply via email to