From: Soumya AR <[email protected]> This patch adds support for atomic fetch min/max through two new generic builtins: - __atomic_fetch_min - __atomic_fetch_max which resolve to a new internal function that gimple-isel lowers to a CAS loop.
op_fetch variants for these builtins are not necessary since libstdc++ uses them only for operator overloads (and min/max have no operator). Builtins resolve in the frontend to a single internal function: IFN_ATOMIC_FETCH_MINMAX (ptr, value, op_code, memorder) where 'op_code' specifies MIN_EXPR or MAX_EXPR. The frontend converts 'value' to the type pointed to by 'ptr' and uses that for the operation's width and signedness. This is done because gimplification can drop pointer casts as useless conversions later. The lowering is written to be shared by future atomic fetch operations (FP fetch_add/sub, parts of reductions etc). Each IFN is lowered to the same load + CAS loop: expected_load = __atomic_load_N (ptr, RELAXED); loop: expected = PHI <expected_load (entry), observed (latch)>; desired = op (expected, value); (observed, cas_succeeded) = CAS (ptr, expected, desired, memorder); if (!cas_succeeded) goto loop; and uses of the IFN's result are replaced with 'expected'. Only the computation of 'desired' differs per operation, which is provided by the emit_desired_value callback. Lowering runs in gimple-isel, after IPA, so that target queries see the correct backend. That matters under offloading, where we can compile for either host or target, and querying optabs pre-IPA would not give us the right backend. See https://gcc.gnu.org/pipermail/gcc-patches/2025-November/699630.html for the relevant discussion. Bootstrapped and regression tested on aarch64-linux-gnu and x86_64-linux-gnu. Cross-compiled and regression tested for arm-linux-gnueabihf-armv7-a and aarch64-linux-gnu without LSE. OK for mainline? gcc/ChangeLog: * Makefile.in: Add atomic-ifn-lowering.o. * atomic-ifn-lowering.cc: New file. * atomic-ifn-lowering.h: New file. * doc/extend.texi: Document __atomic_fetch_min and __atomic_fetch_max. * gimple-isel.cc: Include atomic-ifn-lowering.h. (pass_gimple_isel::execute): Invoke atomic IFN lowering. * internal-fn.cc (expand_ATOMIC_FETCH_MINMAX): Add placeholder. * internal-fn.def (ATOMIC_FETCH_MINMAX): New internal function. * sync-builtins.def (BUILT_IN_ATOMIC_FETCH_MIN): New builtin. (BUILT_IN_ATOMIC_FETCH_MAX): Likewise. gcc/c-family/ChangeLog: * c-common.cc (atomic_bitint_fetch_using_cas_loop): Handle atomic fetch min/max. (resolve_overloaded_builtin): Resolve atomic min/max builtins to IFN_ATOMIC_FETCH_MINMAX. * c-common.h (mark_exp_read): Declare. gcc/testsuite/ChangeLog: * c-c++-common/atomic-fetch-minmax-const.c: New test. * c-c++-common/atomic-fetch-minmax-unused.c: New test. * g++.dg/eh/atomic-fetch-minmax-nce.C: New test. * g++.dg/eh/atomic-fetch-minmax-no-nce.C: New test. * g++.dg/template/builtin-atomic-overloads6.C: Extend for fetch min/max. * g++.dg/template/builtin-atomic-overloads7.C: Likewise. * gcc.dg/atomic-fetch-minmax-bad-types.c: New test. * gcc.dg/atomic-fetch-minmax-bitint.c: New test. * gcc.dg/atomic-fetch-minmax-ifn.c: New test. * gcc.dg/atomic-fetch-minmax-lower.c: New test. * gcc.dg/atomic-fetch-minmax-pointers.c: New test. * gcc.dg/atomic-fetch-minmax-sign-cast.c: New test. * gcc.dg/atomic-fetch-minmax-type-mismatch.c: New test. * gcc.dg/atomic-op-1.c: Extend for fetch min/max. * gcc.dg/atomic-op-2.c: Likewise. * gcc.dg/atomic-op-3.c: Likewise. * gcc.dg/atomic-op-4.c: Likewise. * gcc.dg/atomic-op-5.c: Likewise. Signed-off-by: Soumya AR <[email protected]> --- gcc/Makefile.in | 1 + gcc/atomic-ifn-lowering.cc | 412 ++++++++++++++++++ gcc/atomic-ifn-lowering.h | 25 ++ gcc/c-family/c-common.cc | 108 ++++- gcc/c-family/c-common.h | 1 + gcc/doc/extend.texi | 18 + gcc/gimple-isel.cc | 7 +- gcc/internal-fn.cc | 8 + gcc/internal-fn.def | 1 + gcc/sync-builtins.def | 7 + .../c-c++-common/atomic-fetch-minmax-const.c | 8 + .../c-c++-common/atomic-fetch-minmax-unused.c | 28 ++ .../g++.dg/eh/atomic-fetch-minmax-nce.C | 18 + .../g++.dg/eh/atomic-fetch-minmax-no-nce.C | 20 + .../template/builtin-atomic-overloads6.C | 26 +- .../template/builtin-atomic-overloads7.C | 17 +- .../gcc.dg/atomic-fetch-minmax-bad-types.c | 14 + .../gcc.dg/atomic-fetch-minmax-bitint.c | 34 ++ .../gcc.dg/atomic-fetch-minmax-ifn.c | 16 + .../gcc.dg/atomic-fetch-minmax-lower.c | 26 ++ .../gcc.dg/atomic-fetch-minmax-pointers.c | 16 + .../gcc.dg/atomic-fetch-minmax-sign-cast.c | 38 ++ .../atomic-fetch-minmax-type-mismatch.c | 31 ++ gcc/testsuite/gcc.dg/atomic-op-1.c | 242 ++++++++++ gcc/testsuite/gcc.dg/atomic-op-2.c | 242 ++++++++++ gcc/testsuite/gcc.dg/atomic-op-3.c | 242 ++++++++++ gcc/testsuite/gcc.dg/atomic-op-4.c | 242 ++++++++++ gcc/testsuite/gcc.dg/atomic-op-5.c | 244 +++++++++++ 28 files changed, 2084 insertions(+), 8 deletions(-) create mode 100644 gcc/atomic-ifn-lowering.cc create mode 100644 gcc/atomic-ifn-lowering.h create mode 100644 gcc/testsuite/c-c++-common/atomic-fetch-minmax-const.c create mode 100644 gcc/testsuite/c-c++-common/atomic-fetch-minmax-unused.c create mode 100644 gcc/testsuite/g++.dg/eh/atomic-fetch-minmax-nce.C create mode 100644 gcc/testsuite/g++.dg/eh/atomic-fetch-minmax-no-nce.C create mode 100644 gcc/testsuite/gcc.dg/atomic-fetch-minmax-bad-types.c create mode 100644 gcc/testsuite/gcc.dg/atomic-fetch-minmax-bitint.c create mode 100644 gcc/testsuite/gcc.dg/atomic-fetch-minmax-ifn.c create mode 100644 gcc/testsuite/gcc.dg/atomic-fetch-minmax-lower.c create mode 100644 gcc/testsuite/gcc.dg/atomic-fetch-minmax-pointers.c create mode 100644 gcc/testsuite/gcc.dg/atomic-fetch-minmax-sign-cast.c create mode 100644 gcc/testsuite/gcc.dg/atomic-fetch-minmax-type-mismatch.c diff --git a/gcc/Makefile.in b/gcc/Makefile.in index f2a015d729df..29b9a73ed6c0 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1433,6 +1433,7 @@ OBJS = \ alias.o \ alloc-pool.o \ asm-toplevel.o \ + atomic-ifn-lowering.o \ auto-inc-dec.o \ auto-profile.o \ bb-reorder.o \ diff --git a/gcc/atomic-ifn-lowering.cc b/gcc/atomic-ifn-lowering.cc new file mode 100644 index 000000000000..6f5e889a6ca3 --- /dev/null +++ b/gcc/atomic-ifn-lowering.cc @@ -0,0 +1,412 @@ +/* Lower atomic-fetch internal functions to a Compare-and-Swap (CAS) loop. + Copyright The GNU Toolchain Authors. + + This file is part of GCC. + + GCC is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + GCC is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GCC; see the file COPYING3. If not see + <http://www.gnu.org/licenses/>. + +gimple-isel uses this code to lower atomic-fetch IFNs to a load + CAS loop: + + current_value = atomic_load (ptr); // initial observation + do { + desired_value = op (current_value, value); + // CAS writes desired_value if memory still holds current_value; + // otherwise it refreshes current_value with the actual contents + // and the loop retries. + } while (!atomic_compare_exchange (ptr, ¤t_value, desired_value)); + // The IFN's result is current_value from the successful attempt. + +After lowering the CFG looks somewhat like: + + before_bb: + loaded_value = atomic_load (ptr) + goto loop_bb + + loop_bb: + current_value = PHI<loaded_value, observed_value> + desired_value = op (current_value, value) + cas_result = .ATOMIC_COMPARE_EXCHANGE (ptr, current_value, + desired_value, ...) + observed_value = REALPART_EXPR <cas_result> + cas_succeeded = IMAGPART_EXPR <cas_result> + if (cas_succeeded) goto exit_bb else goto loop_bb + + exit_bb: + // uses of the IFN's result now use current_value +*/ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "target.h" +#include "rtl.h" +#include "tree.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "cfghooks.h" +#include "cfgloop.h" +#include "tree-pass.h" +#include "tree-eh.h" +#include "tree-cfg.h" +#include "gimple-pretty-print.h" +#include "memmodel.h" +#include "ssa.h" +#include "fold-const.h" +#include "builtins.h" +#include "internal-fn.h" +#include "atomic-ifn-lowering.h" + +/* Descriptor for an atomic-fetch IFN that this code can lower. + lower replaces the IFN call with a CAS loop. */ + +struct atomic_op_lowering +{ + internal_fn ifn; + void (*lower) (gimple_stmt_iterator *); +}; + +/* Helper to convert between types and insert into the gimple sequence. + Returns the SSA name holding the converted value. */ + +static tree +insert_type_conversion (gimple_stmt_iterator *gsi, tree src_value, + tree dest_type, enum tree_code convert_code = NOP_EXPR, + bool insert_after = true) +{ + if (TREE_TYPE (src_value) == dest_type) + return src_value; + + tree result = make_ssa_name (dest_type); + gassign *convert_stmt = gimple_build_assign (result, convert_code, src_value); + + if (insert_after) + gsi_insert_after (gsi, convert_stmt, GSI_NEW_STMT); + else + gsi_insert_before (gsi, convert_stmt, GSI_SAME_STMT); + + return result; +} + +/* Add an EH edge for STMT that has the same profile and PHI arguments as + OLD_EH_EDGE. */ + +static void +duplicate_eh_edge (gimple *stmt, edge old_eh_edge) +{ + edge new_eh_edge = make_eh_edge (stmt); + new_eh_edge->probability = old_eh_edge->probability; + copy_phi_arg_into_existing_phi (old_eh_edge, new_eh_edge); +} + +/* Lower the atomic-fetch IFN call at GSI to a load + CAS loop. The + op-specific emit_desired_value callback produces the value that CAS will + try to store and sets fetch_value to the value returned by the operation. + On return, GSI points to the first unprocessed statement in the exit + block. */ + +static void +lower_via_cas_loop (gimple_stmt_iterator *gsi, + tree (*emit_desired_value) (gimple_stmt_iterator *, tree, + gcall *, tree *)) +{ + gimple_stmt_iterator iter = *gsi; + gcall *stmt = as_a<gcall *> (gsi_stmt (iter)); + + bool nothrow_p = gimple_call_nothrow_p (stmt); + int eh_lp = lookup_stmt_eh_lp (stmt); + edge old_eh_edge = NULL; + if (eh_lp > 0) + { + edge e; + edge_iterator ei; + FOR_EACH_EDGE (e, ei, gimple_bb (stmt)->succs) + if (e->flags & EDGE_EH) + old_eh_edge = e; + gcc_assert (old_eh_edge); + } + + tree ptr = gimple_call_arg (stmt, 0); + tree memorder = gimple_call_arg (stmt, 3); + + tree datatype = TREE_TYPE (gimple_call_arg (stmt, 1)); + + /* Build the atomic builtins we need. */ + int size_log2 = exact_log2 (tree_to_uhwi (TYPE_SIZE_UNIT (datatype))); + built_in_function load_builtin_fn + = (built_in_function) ((int) BUILT_IN_ATOMIC_LOAD_N + size_log2 + 1); + tree load_fn = builtin_decl_explicit (load_builtin_fn); + + /* The atomic load builtin returns a fixed width type (and cannot return a + _BitInt). Use that return type for the load and CAS to keep the types + consistent. */ + tree atomic_type = TREE_TYPE (TREE_TYPE (load_fn)); + + /* Insert atomic load before the IFN. We will be using the IFN as a split + point later to insert the loop body. Since the load is not part of the + loop body, we insert it before the IFN. */ + tree loaded_value = make_ssa_name (atomic_type); + gcall *load_call + = gimple_build_call (load_fn, 2, ptr, + build_int_cst (integer_type_node, MEMMODEL_RELAXED)); + gimple_call_set_lhs (load_call, loaded_value); + gimple_call_set_nothrow (load_call, nothrow_p); + gimple_set_location (load_call, gimple_location (stmt)); + gsi_insert_before (&iter, load_call, GSI_SAME_STMT); + + tree lhs = gimple_call_lhs (stmt); + + /* Build the loop body. We will be inserting this after the split point. */ + gimple_seq loop_body = NULL; + gimple_stmt_iterator gsi_loop = gsi_last (loop_body); + + /* Create a SSA name for the loop variable. This will be the PHI node in the + loop body. */ + tree current_value = make_ssa_name (atomic_type); + + /* Emit the op-specific gimple producing the value CAS will store. */ + tree fetch_value; + tree desired_value + = emit_desired_value (&gsi_loop, current_value, stmt, &fetch_value); + + /* Emit the CAS as IFN_ATOMIC_COMPARE_EXCHANGE. The IFN takes 'expected' + by value and returns a complex type (REALPART = observed expected, + IMAGPART = success flag). + Arg 3 is a flag computed as: (weak ? 256 : 0) + size_in_bytes. But + we never emit weak here, so the flag is just the size. */ + int flag = int_size_in_bytes (datatype); + tree complex_type = build_complex_type (atomic_type); + tree cas_result = make_ssa_name (complex_type); + gcall *cas_call = gimple_build_call_internal ( + IFN_ATOMIC_COMPARE_EXCHANGE, 6, ptr, /* address to update */ + current_value, /* expected */ + desired_value, /* desired value */ + build_int_cst (integer_type_node, flag), memorder, /* success order */ + build_int_cst (integer_type_node, MEMMODEL_RELAXED)); + gimple_call_set_lhs (cas_call, cas_result); + gimple_call_set_nothrow (cas_call, nothrow_p); + gimple_set_location (cas_call, gimple_location (stmt)); + gsi_insert_after (&gsi_loop, cas_call, GSI_NEW_STMT); + + if (eh_lp != 0) + { + maybe_duplicate_eh_stmt (load_call, stmt); + maybe_duplicate_eh_stmt (cas_call, stmt); + } + + /* Extract the observed value (REALPART) and success flag (IMAGPART) from the + complex result. */ + tree observed_value = make_ssa_name (atomic_type); + gsi_insert_after (&gsi_loop, + gimple_build_assign (observed_value, REALPART_EXPR, + build1 (REALPART_EXPR, atomic_type, + cas_result)), + GSI_NEW_STMT); + + tree cas_succeeded_raw = make_ssa_name (atomic_type); + gsi_insert_after (&gsi_loop, + gimple_build_assign (cas_succeeded_raw, IMAGPART_EXPR, + build1 (IMAGPART_EXPR, atomic_type, + cas_result)), + GSI_NEW_STMT); + + /* Add conditional to end of loop body. The conditional statement currently + has no successors so we add loop_bb and exit_bb as successors below. */ + gcond *loop_cond = gimple_build_cond (NE_EXPR, cas_succeeded_raw, + build_zero_cst (atomic_type), + NULL_TREE, NULL_TREE); + gsi_insert_after (&gsi_loop, loop_cond, GSI_NEW_STMT); + + /* Get the previous statement in the basic block. We use it to split the + block after the atomic load. Currently the iter points to the IFN and the + bb looks like: + + <previous_code> + | + atomic_load (ptr) + | + IFN_ATOMIC_FETCH_MINMAX <- gsi + | + <following_code> + + We remove the IFN and replace it with the loop body. Split at the IFN, + then split the resultant edge to insert the loop body, resulting in: + + before_bb (contains <previous_code> and atomic_load (ptr)) + | + loop_bb (contains the MIN/MAX operation and the CAS operation) + | + after_bb (contains <following_code>) + */ + + gimple_stmt_iterator prev_gsi = iter; + gsi_prev (&prev_gsi); + + /* Remove the IFN. */ + gsi_remove (&iter, true); + + edge e = split_block (gsi_bb (prev_gsi), gsi_stmt (prev_gsi)); + basic_block exit_bb = e->dest; + basic_block loop_bb = split_edge (e); + + /* Create the PHI for current_value: loaded_value on entry, and + observed_value on the back edge, which we add below. */ + gphi *loop_phi = create_phi_node (current_value, loop_bb); + add_phi_arg (loop_phi, loaded_value, single_pred_edge (loop_bb), + UNKNOWN_LOCATION); + + /* Insert the loop body into loop_bb. */ + set_bb_seq (loop_bb, loop_body); + + /* set_bb_seq does not update the statements' basic block. */ + for (gimple_stmt_iterator si = gsi_start_bb (loop_bb); !gsi_end_p (si); + gsi_next (&si)) + gimple_set_bb (gsi_stmt (si), loop_bb); + + /* A statement with an EH successor must end its basic block. The initial + load already ends before_bb. Do the same for the CAS call. */ + basic_block condition_bb = loop_bb; + if (old_eh_edge) + condition_bb = split_block (loop_bb, cas_call)->dest; + + /* condition_bb currently has a fallthrough edge to exit_bb. Replace it + with the two edges controlled by loop_cond. */ + remove_edge (single_succ_edge (condition_bb)); + edge true_edge = make_edge (condition_bb, exit_bb, EDGE_TRUE_VALUE); + edge false_edge = make_edge (condition_bb, loop_bb, EDGE_FALSE_VALUE); + + false_edge->probability = profile_probability::unlikely (); + true_edge->probability = false_edge->probability.invert (); + + /* We've introduced a new back-edge, flag the loop tree for fixup. */ + if (current_loops) + loops_state_set (LOOPS_NEED_FIXUP); + + /* Add the loop-back phi argument. */ + add_phi_arg (loop_phi, observed_value, false_edge, UNKNOWN_LOCATION); + + if (old_eh_edge) + { + duplicate_eh_edge (load_call, old_eh_edge); + duplicate_eh_edge (cas_call, old_eh_edge); + + /* Splitting before_bb moved the IFN's old EH edge to exit_bb. exit_bb + no longer ends in a throwing statement so this edge can be removed. */ + gimple_purge_dead_eh_edges (exit_bb); + } + + gimple_stmt_iterator exit_gsi = gsi_after_labels (exit_bb); + if (lhs) + replace_uses_by (lhs, fetch_value); + + *gsi = exit_gsi; +} + +/* Emit gimple computing MIN/MAX (CURRENT_VALUE, value) at GSI_LOOP for the + IFN_ATOMIC_FETCH_MINMAX call STMT. CURRENT_VALUE is this iteration's + loaded value in the sized atomic builtin's unsigned type. Returns an SSA + name (in the same type) holding the value CAS will try to store. */ + +static tree +emit_minmax_desired_value (gimple_stmt_iterator *gsi_loop, tree current_value, + gcall *stmt, tree *fetch_value) +{ + tree value = gimple_call_arg (stmt, 1); + tree datatype = TREE_TYPE (value); + tree_code cmp_code = (tree_code) tree_to_uhwi (gimple_call_arg (stmt, 2)); + gcc_assert (cmp_code == MIN_EXPR || cmp_code == MAX_EXPR); + tree atomic_type = TREE_TYPE (current_value); + + /* Atomic load and CAS operate in unsigned type, so CURRENT_VALUE arrives + from the load unsigned. For MIN/MAX, cast CURRENT_VALUE back to DATATYPE + (which can be signed). */ + tree oldval_typed + = useless_type_conversion_p (datatype, TREE_TYPE (current_value)) + ? current_value + : insert_type_conversion (gsi_loop, current_value, datatype); + *fetch_value = oldval_typed; + + /* Compute newval = MIN/MAX (oldval_typed, value) in DATATYPE. */ + tree newval = make_ssa_name (datatype); + gassign *minmax_assign + = gimple_build_assign (newval, cmp_code, oldval_typed, value); + gsi_insert_after (gsi_loop, minmax_assign, GSI_NEW_STMT); + + /* Cast newval back to ATOMIC_TYPE so CAS can store it. */ + return useless_type_conversion_p (atomic_type, datatype) + ? newval + : insert_type_conversion (gsi_loop, newval, atomic_type); +} + +/* Lower an IFN_ATOMIC_FETCH_MINMAX call to a CAS loop. */ + +static void +lower_minmax_call (gimple_stmt_iterator *gsi) +{ + lower_via_cas_loop (gsi, emit_minmax_desired_value); +} + +/* Atomic-fetch IFNs supported by this lowering code. */ + +static const atomic_op_lowering atomic_op_table[] = { + { IFN_ATOMIC_FETCH_MINMAX, lower_minmax_call }, +}; + +/* Lower the IFN at GSI. On a true return, GSI points to the first statement in + the new exit block. */ + +static bool +maybe_lower_atomic_ifn (gimple_stmt_iterator *gsi) +{ + gcall *call = dyn_cast<gcall *> (gsi_stmt (*gsi)); + if (!call || !gimple_call_internal_p (call)) + return false; + + internal_fn ifn = gimple_call_internal_fn (call); + for (const atomic_op_lowering &op : atomic_op_table) + if (ifn == op.ifn) + { + if (dump_file) + { + fprintf (dump_file, "Lowering atomic IFN:\n "); + print_gimple_stmt (dump_file, call, 0, TDF_SLIM); + } + + op.lower (gsi); + return true; + } + + return false; +} + +unsigned int +lower_atomic_ifn (const vec<gcall *> &atomic_calls) +{ + bool changed = false; + + for (gcall *call : atomic_calls) + { + gimple_stmt_iterator gsi = gsi_for_stmt (call); + changed |= maybe_lower_atomic_ifn (&gsi); + } + + if (!changed) + return 0; + + free_dominance_info (CDI_DOMINATORS); + free_dominance_info (CDI_POST_DOMINATORS); + + return TODO_update_ssa | TODO_cleanup_cfg; +} diff --git a/gcc/atomic-ifn-lowering.h b/gcc/atomic-ifn-lowering.h new file mode 100644 index 000000000000..370a449c2fd1 --- /dev/null +++ b/gcc/atomic-ifn-lowering.h @@ -0,0 +1,25 @@ +/* Lower atomic-fetch internal functions to a Compare-and-Swap (CAS) loop. + Copyright The GNU Toolchain Authors. + + This file is part of GCC. + + GCC is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + GCC is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GCC; see the file COPYING3. If not see + <http://www.gnu.org/licenses/>. */ + +#ifndef GCC_ATOMIC_IFN_LOWERING_H +#define GCC_ATOMIC_IFN_LOWERING_H + +extern unsigned int lower_atomic_ifn (const vec<gcall *> &); + +#endif /* GCC_ATOMIC_IFN_LOWERING_H */ diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc index 6380bec4515d..e9de4930118f 100644 --- a/gcc/c-family/c-common.cc +++ b/gcc/c-family/c-common.cc @@ -46,6 +46,7 @@ along with GCC; see the file COPYING3. If not see #include "tree-iterator.h" #include "opts.h" #include "gimplify.h" +#include "internal-fn.h" #include "substring-locations.h" #include "spellcheck.h" #include "c-spellcheck.h" @@ -8530,6 +8531,14 @@ atomic_bitint_fetch_using_cas_loop (location_t loc, return_old_p = true; sync_p = true; break; + case BUILT_IN_ATOMIC_FETCH_MIN: + code = MIN_EXPR; + return_old_p = true; + break; + case BUILT_IN_ATOMIC_FETCH_MAX: + code = MAX_EXPR; + return_old_p = true; + break; default: gcc_unreachable (); } @@ -8640,6 +8649,8 @@ atomic_bitint_fetch_using_cas_loop (location_t loc, case BIT_AND_EXPR: case BIT_IOR_EXPR: case BIT_XOR_EXPR: + case MIN_EXPR: + case MAX_EXPR: rhs = build2_loc (loc, code, nonatomic_lhs_type, old, val); break; case ERROR_MARK: @@ -8896,6 +8907,8 @@ resolve_overloaded_builtin (location_t loc, tree function, case BUILT_IN_ATOMIC_FETCH_NAND_N: case BUILT_IN_ATOMIC_FETCH_XOR_N: case BUILT_IN_ATOMIC_FETCH_OR_N: + case BUILT_IN_ATOMIC_FETCH_MIN: + case BUILT_IN_ATOMIC_FETCH_MAX: orig_format = false; /* FALLTHRU */ case BUILT_IN_SYNC_FETCH_AND_ADD_N: @@ -8925,12 +8938,14 @@ resolve_overloaded_builtin (location_t loc, tree function, int n = sync_resolve_size (function, params, fetch_op, orig_format, complain); - tree new_function, first_param, result; + tree new_function, result; enum built_in_function fncode; if (n == 0) return error_mark_node; + tree first_param = (*params)[0]; + if (n == -1) { /* complain is related to SFINAE context. @@ -8946,13 +8961,102 @@ resolve_overloaded_builtin (location_t loc, tree function, params); } + /* Implement atomic fetch min/max builtins through an IFN. */ + if (orig_code == BUILT_IN_ATOMIC_FETCH_MIN + || orig_code == BUILT_IN_ATOMIC_FETCH_MAX) + { + /* Similar to sync_resolve_params, we should call TYPE_MAIN_VARIANT + to drop volatile. */ + tree datatype + = TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (first_param))); + + /* sync_resolve_params expects a sized builtin decl; we only + register the generic form and lower via IFN, therefore, validate + params manually. */ + if (params->length () < 3) + { + if (complain) + error_at (loc, "too few arguments to function %qE", function); + return error_mark_node; + } + if (params->length () > 3) + { + if (complain) + error_at (loc, "too many arguments to function %qE", function); + return error_mark_node; + } + if (!INTEGRAL_TYPE_P (datatype) && !POINTER_TYPE_P (datatype)) + { + if (complain) + error_at (loc, + "argument 1 of %qE must point to an integer or " + "pointer type", function); + return error_mark_node; + } + if (!INTEGRAL_TYPE_P (TREE_TYPE ((*params)[1])) + && !POINTER_TYPE_P (TREE_TYPE ((*params)[1]))) + { + if (complain) + error_at (loc, + "argument 2 of %qE must be an integer or pointer " + "type", function); + return error_mark_node; + } + if (!INTEGRAL_TYPE_P (TREE_TYPE ((*params)[2]))) + { + if (complain) + error_at (loc, "non-integer memory model argument 3 of %qE", + function); + return error_mark_node; + } + tree ptr_type = TREE_TYPE (first_param); + if (TYPE_READONLY (TREE_TYPE (ptr_type))) + { + if (c_dialect_cxx ()) + { + if (!complain) + return error_mark_node; + permerror (loc, "invalid conversion from %qT to %qT", + ptr_type, build_pointer_type (datatype)); + } + else + pedwarn (loc, OPT_Wdiscarded_qualifiers, + "passing argument 1 of %qE discards %qv qualifier " + "from pointer target type", + function, TYPE_QUAL_CONST); + } + + /* Building the IFN directly does not mark the arguments as read, + so do it here to avoid bogus -Wunused-but-set-* warnings. */ + for (tree arg : *params) + mark_exp_read (arg); + + tree_code op_code + = (orig_code == BUILT_IN_ATOMIC_FETCH_MIN) ? MIN_EXPR : MAX_EXPR; + + /* The checks above guarantee that VALUE and DATATYPE are integer or + pointer types, so convert () should not emit an error. */ + tree value = convert (datatype, (*params)[1]); + tree memorder = (*params)[2]; + tree op_code_tree = build_int_cst (integer_type_node, op_code); + + tree ret + = build_call_expr_internal_loc (loc, IFN_ATOMIC_FETCH_MINMAX, + datatype, 4, first_param, value, + op_code_tree, memorder); + /* Atomics can throw under -fnon-call-exceptions; mark the call + NOTHROW otherwise. */ + if (!flag_non_call_exceptions) + TREE_NOTHROW (ret) = 1; + + return ret; + } fncode = (enum built_in_function)((int)orig_code + exact_log2 (n) + 1); new_function = builtin_decl_explicit (fncode); if (!sync_resolve_params (loc, function, new_function, params, orig_format, complain)) return error_mark_node; - first_param = (*params)[0]; result = build_function_call_vec (loc, vNULL, new_function, params, NULL); if (result == error_mark_node) diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index 197db508c163..6274b78eab6c 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -1088,6 +1088,7 @@ extern tree build_real_imag_expr (location_t, enum tree_code, tree); extern tree build_unary_op (location_t, enum tree_code, tree, bool); extern tree build_binary_op (location_t, enum tree_code, tree, tree, bool); extern tree perform_integral_promotions (tree); +extern void mark_exp_read (tree); /* These functions must be defined by each front-end which implements a variant of the C language. They are used by port files. */ diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 684af9cdbb3b..59f0b0f967ce 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -17696,6 +17696,24 @@ alignment. A value of 0 indicates typical alignment should be used. The compiler may also ignore this parameter. @enddefbuiltin +@defbuiltin{@var{type} __atomic_fetch_min (@var{type} *@var{ptr}, @var{type} @var{val}, int @var{memorder})} +@defbuiltinx{@var{type} __atomic_fetch_max (@var{type} *@var{ptr}, @var{type} @var{val}, int @var{memorder})} +These built-in functions perform an atomic minimum or maximum operation, and +return the value that had previously been in @code{*@var{ptr}}. Minimum +and maximum comparisons are signed or unsigned according to the type +pointed to by @var{ptr}. Operations on pointer arguments are performed as +if the operands were of the @code{uintptr_t} type. That is, they are not +scaled by the size of the type to which the pointer points. + +@smallexample +@{ tmp = *ptr; *ptr = tmp < val ? tmp : val; return tmp; @} // min +@{ tmp = *ptr; *ptr = tmp > val ? tmp : val; return tmp; @} // max +@end smallexample + +The object pointed to by the first argument must be of integer or pointer +type. It must not be a boolean type. All memory orders are valid. + +@enddefbuiltin @node __sync Builtins @subsection Legacy @code{__sync} Built-in Functions for Atomic Memory Access diff --git a/gcc/gimple-isel.cc b/gcc/gimple-isel.cc index 4f6fa97e6201..cac3ac106874 100644 --- a/gcc/gimple-isel.cc +++ b/gcc/gimple-isel.cc @@ -41,6 +41,7 @@ along with GCC; see the file COPYING3. If not see #include "internal-fn.h" #include "fold-const.h" #include "gimple-range.h" +#include "atomic-ifn-lowering.h" /* Expand all ARRAY_REF(VIEW_CONVERT_EXPR) gimple assignments into calls to @@ -1359,6 +1360,7 @@ pass_gimple_isel::execute (struct function *fun) gimple_stmt_iterator gsi; basic_block bb; bool cfg_changed = false; + auto_vec<gcall *> atomic_calls; if (optimize) enable_ranger (fun); @@ -1386,6 +1388,8 @@ pass_gimple_isel::execute (struct function *fun) if (gcall *call = dyn_cast <gcall*>(*gsi)) { + if (gimple_call_internal_p (call, IFN_ATOMIC_FETCH_MINMAX)) + atomic_calls.safe_push (call); gimple_isel_builtin_call (call, &gsi); continue; } @@ -1401,7 +1405,8 @@ pass_gimple_isel::execute (struct function *fun) if (optimize) disable_ranger (fun); - return cfg_changed ? TODO_cleanup_cfg : 0; + return lower_atomic_ifn (atomic_calls) + | (cfg_changed ? TODO_cleanup_cfg : 0); } } // anon namespace diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc index 94a601d34d6d..6b6ab9bc86bd 100644 --- a/gcc/internal-fn.cc +++ b/gcc/internal-fn.cc @@ -3773,6 +3773,14 @@ expand_ATOMIC_XOR_FETCH_CMP_0 (internal_fn, gcall *call) expand_ifn_atomic_op_fetch_cmp_0 (call); } +/* Expand atomic fetch minmax. */ + +static void +expand_ATOMIC_FETCH_MINMAX (internal_fn, gcall *) +{ + /* Implement this. */ +} + /* Expand LAUNDER to assignment, lhs = arg0. */ static void diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def index b1c46a82a520..9a221edfa710 100644 --- a/gcc/internal-fn.def +++ b/gcc/internal-fn.def @@ -601,6 +601,7 @@ DEF_INTERNAL_FN (ATOMIC_SUB_FETCH_CMP_0, ECF_LEAF, NULL) DEF_INTERNAL_FN (ATOMIC_AND_FETCH_CMP_0, ECF_LEAF, NULL) DEF_INTERNAL_FN (ATOMIC_OR_FETCH_CMP_0, ECF_LEAF, NULL) DEF_INTERNAL_FN (ATOMIC_XOR_FETCH_CMP_0, ECF_LEAF, NULL) +DEF_INTERNAL_FN (ATOMIC_FETCH_MINMAX, ECF_LEAF, NULL) /* To implement [[fallthrough]]. If the TREE_NOTHROW or GF_CALL_NOTHROW flag is set on the call (normally redundant with ECF_NOTHROW), it marks diff --git a/gcc/sync-builtins.def b/gcc/sync-builtins.def index 1a5d84f5db5a..704b43a683d2 100644 --- a/gcc/sync-builtins.def +++ b/gcc/sync-builtins.def @@ -597,6 +597,13 @@ DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_IS_LOCK_FREE, "__atomic_is_lock_free", BT_FN_BOOL_SIZE_CONST_VPTR, ATTR_CONST_NOTHROW_LEAF_LIST) +DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_FETCH_MIN, + "__atomic_fetch_min", + BT_FN_VOID_VAR, ATTR_NOTHROWCALL_LEAF_LIST) + +DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_FETCH_MAX, + "__atomic_fetch_max", + BT_FN_VOID_VAR, ATTR_NOTHROWCALL_LEAF_LIST) DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_THREAD_FENCE, "__atomic_thread_fence", diff --git a/gcc/testsuite/c-c++-common/atomic-fetch-minmax-const.c b/gcc/testsuite/c-c++-common/atomic-fetch-minmax-const.c new file mode 100644 index 000000000000..8feea4aecf9c --- /dev/null +++ b/gcc/testsuite/c-c++-common/atomic-fetch-minmax-const.c @@ -0,0 +1,8 @@ +/* { dg-do compile } */ + +int +fetch_min_const (const int *p, int v) +{ + return __atomic_fetch_min (p, v, __ATOMIC_RELAXED); /* { dg-warning "passing argument 1 of .__atomic_fetch_min. discards .const. qualifier from pointer target type" "" { target c } } */ + /* { dg-error "invalid conversion from .const int\\*. to .int\\*." "" { target c++ } .-1 } */ +} diff --git a/gcc/testsuite/c-c++-common/atomic-fetch-minmax-unused.c b/gcc/testsuite/c-c++-common/atomic-fetch-minmax-unused.c new file mode 100644 index 000000000000..ae7a63a41a45 --- /dev/null +++ b/gcc/testsuite/c-c++-common/atomic-fetch-minmax-unused.c @@ -0,0 +1,28 @@ +/* { dg-do compile } */ +/* { dg-options "-Wall -Wextra" } */ + +int mem_int; + +void +fetch_min_ptr (int **src) +{ + int *p; + p = *src; + __atomic_fetch_min (p, 1, __ATOMIC_RELAXED); +} + +void +fetch_max_value (int *src) +{ + int v; + v = *src; + __atomic_fetch_max (&mem_int, v, __ATOMIC_RELAXED); +} + +void +fetch_min_memorder (int *src) +{ + int o; + o = *src; + __atomic_fetch_min (&mem_int, 1, o); +} diff --git a/gcc/testsuite/g++.dg/eh/atomic-fetch-minmax-nce.C b/gcc/testsuite/g++.dg/eh/atomic-fetch-minmax-nce.C new file mode 100644 index 000000000000..6818033761a6 --- /dev/null +++ b/gcc/testsuite/g++.dg/eh/atomic-fetch-minmax-nce.C @@ -0,0 +1,18 @@ +// { dg-do compile } +// { dg-options "-O2 -fexceptions -fnon-call-exceptions -fno-inline-atomics -fdump-tree-isel-eh" } + +int +test_fetch_min (int *ptr, int value) +{ + try + { + return __atomic_fetch_min (ptr, value, __ATOMIC_RELAXED); + } + catch (...) + { + return -1; + } +} + +// Check that load + CAS have the same LP. +// { dg-final { scan-tree-dump {LP ([0-9]+)\].*__atomic_load_4.*LP \1\].*\.ATOMIC_COMPARE_EXCHANGE} "isel" } } diff --git a/gcc/testsuite/g++.dg/eh/atomic-fetch-minmax-no-nce.C b/gcc/testsuite/g++.dg/eh/atomic-fetch-minmax-no-nce.C new file mode 100644 index 000000000000..b7c50ff9e729 --- /dev/null +++ b/gcc/testsuite/g++.dg/eh/atomic-fetch-minmax-no-nce.C @@ -0,0 +1,20 @@ +// { dg-do compile } +// { dg-options "-O2 -fexceptions -fno-non-call-exceptions -fno-inline-atomics -fdump-tree-isel-eh" } + +int +test_fetch_min (int *ptr, int value) +{ + try + { + return __atomic_fetch_min (ptr, value, __ATOMIC_RELAXED); + } + catch (...) + { + return -1; + } +} + +// Load + CAS should not have an EH edge or LP. +// { dg-final { scan-tree-dump-times "__atomic_load_4" 1 "isel" } } +// { dg-final { scan-tree-dump-times "\\.ATOMIC_COMPARE_EXCHANGE" 1 "isel" } } +// { dg-final { scan-tree-dump-not "LP .*__atomic" "isel" } } diff --git a/gcc/testsuite/g++.dg/template/builtin-atomic-overloads6.C b/gcc/testsuite/g++.dg/template/builtin-atomic-overloads6.C index 15d01c10b720..bb428b25a89a 100644 --- a/gcc/testsuite/g++.dg/template/builtin-atomic-overloads6.C +++ b/gcc/testsuite/g++.dg/template/builtin-atomic-overloads6.C @@ -132,11 +132,26 @@ typedef __UINT32_TYPE__ uint32_t; */ #define BITINT_FETCHCAS_ERRS(X) +#define FETCHMIN_TOOFEW(X) \ + X(fetch_min, (std::declval<int*>(), int()), 0) +#define FETCHMIN_TOOMANY(X) \ + X(fetch_min, (std::declval<int*>(), int(), int(), int()), 1) +#define FETCHMAX_TOOFEW(X) \ + X(fetch_max, (std::declval<int*>(), int()), 0) +#define FETCHMAX_TOOMANY(X) \ + X(fetch_max, (std::declval<int*>(), int(), int(), int()), 1) +#define MINMAX_FETCH_ERRS(X) \ + FETCHMIN_TOOFEW(X) \ + FETCHMIN_TOOMANY(X) \ + FETCHMAX_TOOFEW(X) \ + FETCHMAX_TOOMANY(X) + #define ALL_ERRS(X) \ GET_ATOMIC_GENERIC_ERRS(X) \ SYNC_SIZE_ERRS(X) \ SYNC_PARM_ERRS(X) \ - BITINT_FETCHCAS_ERRS(X) + BITINT_FETCHCAS_ERRS(X) \ + MINMAX_FETCH_ERRS(X) #define SFINAE_TYPE_CHECK(NAME, PARAMS, COUNTER) \ template <typename T, typename = void> \ @@ -152,8 +167,8 @@ ALL_ERRS(SFINAE_TYPE_CHECK) /* { dg-error "operand type 'int' is incompatible with argument 1 of '__atomic_load_n'" "" { target *-*-* } 110 } */ /* { dg-error "too few arguments to function '__atomic_load_n'" "" { target *-*-* } 116 } */ /* { dg-error "too many arguments to function '__atomic_load_n'" "" { target *-*-* } 118 } */ -/* { dg-error "template argument 1 is invalid" "" { target *-*-* } 146 } */ -/* { dg-error "template argument 2 is invalid" "" { target *-*-* } 146 } */ +/* { dg-error "template argument 1 is invalid" "" { target *-*-* } 161 } */ +/* { dg-error "template argument 2 is invalid" "" { target *-*-* } 161 } */ /* { dg-error "incorrect number of arguments to function '__atomic_load'" "" { target *-*-* } 48 } */ /* { dg-error "argument 1 of '__atomic_load' must be a non-void pointer type" "" { target *-*-* } 51 } */ /* { dg-error "argument 1 of '__atomic_load' must be a non-void pointer type" "" { target *-*-* } 53 } */ @@ -167,5 +182,8 @@ ALL_ERRS(SFINAE_TYPE_CHECK) /* { dg-error "argument 2 of '__atomic_load' must not be a pointer to a 'const' type" "" { target *-*-* } 80 } */ /* { dg-error "argument 2 of '__atomic_load' must not be a pointer to a 'volatile' type" "" { target *-*-* } 82 } */ /* { dg-error "non-integer memory model argument 3 of '__atomic_load'" "" { target *-*-* } 93 } */ - +/* { dg-error "too few arguments to function '__atomic_fetch_min'" "" { target *-*-* } 136 } */ +/* { dg-error "too many arguments to function '__atomic_fetch_min'" "" { target *-*-* } 138 } */ +/* { dg-error "too few arguments to function '__atomic_fetch_max'" "" { target *-*-* } 140 } */ +/* { dg-error "too many arguments to function '__atomic_fetch_max'" "" { target *-*-* } 142 } */ /* { dg-warning {invalid memory model argument 3 of '__atomic_load' \[-Winvalid-memory-model\]} "" { target *-*-* } 95 } */ diff --git a/gcc/testsuite/g++.dg/template/builtin-atomic-overloads7.C b/gcc/testsuite/g++.dg/template/builtin-atomic-overloads7.C index c0996d15c9f0..4ddf473498ca 100644 --- a/gcc/testsuite/g++.dg/template/builtin-atomic-overloads7.C +++ b/gcc/testsuite/g++.dg/template/builtin-atomic-overloads7.C @@ -136,12 +136,27 @@ typedef __UINT32_TYPE__ uint32_t; */ #define BITINT_FETCHCAS_ERRS(X) +#define FETCHMIN_TOOFEW(X) \ + X(fetch_min, (std::declval<T>(), int()), 0) +#define FETCHMIN_TOOMANY(X) \ + X(fetch_min, (std::declval<T>(), int(), int(), int()), 1) +#define FETCHMAX_TOOFEW(X) \ + X(fetch_max, (std::declval<T>(), int()), 0) +#define FETCHMAX_TOOMANY(X) \ + X(fetch_max, (std::declval<T>(), int(), int(), int()), 1) +#define MINMAX_FETCH_ERRS(X) \ + FETCHMIN_TOOFEW(X) \ + FETCHMIN_TOOMANY(X) \ + FETCHMAX_TOOFEW(X) \ + FETCHMAX_TOOMANY(X) + #define ALL_ERRS(X) \ GET_ATOMIC_GENERIC_ERRS(X) \ SYNC_SIZE_ERRS(X) \ SYNC_PARM_ERRS(X) \ MEMMODEL_TOOLARGE(X) \ - BITINT_FETCHCAS_ERRS(X) + BITINT_FETCHCAS_ERRS(X) \ + MINMAX_FETCH_ERRS(X) #define SFINAE_TYPE_CHECK(NAME, PARAMS, COUNTER) \ template <typename T, typename = void> \ diff --git a/gcc/testsuite/gcc.dg/atomic-fetch-minmax-bad-types.c b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-bad-types.c new file mode 100644 index 000000000000..4816b05faf38 --- /dev/null +++ b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-bad-types.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-O2" } */ + +int +test_bad_value (int *ptr, float value) +{ + return __atomic_fetch_min (ptr, value, __ATOMIC_SEQ_CST); /* { dg-error "must be an integer or pointer type" } */ +} + +int +test_bad_memorder (int *ptr, int value, float memorder) +{ + return __atomic_fetch_max (ptr, value, memorder); /* { dg-error "non-integer memory model" } */ +} diff --git a/gcc/testsuite/gcc.dg/atomic-fetch-minmax-bitint.c b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-bitint.c new file mode 100644 index 000000000000..32d0e334d449 --- /dev/null +++ b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-bitint.c @@ -0,0 +1,34 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -std=c23 -fno-inline-atomics -fdump-tree-isel" } */ +/* { dg-require-effective-target bitint128 } */ +/* { dg-require-effective-target int128 } */ + +typedef _BitInt(65) int65_t; +typedef unsigned _BitInt(65) uint65_t; + +int65_t +test_fetch_min_int65 (int65_t *ptr, int65_t value) +{ + return __atomic_fetch_min (ptr, value, __ATOMIC_SEQ_CST); +} + +int65_t +test_fetch_max_int65 (int65_t *ptr, int65_t value) +{ + return __atomic_fetch_max (ptr, value, __ATOMIC_SEQ_CST); +} + +uint65_t +test_fetch_min_uint65 (uint65_t *ptr, uint65_t value) +{ + return __atomic_fetch_min (ptr, value, __ATOMIC_SEQ_CST); +} + +uint65_t +test_fetch_max_uint65 (uint65_t *ptr, uint65_t value) +{ + return __atomic_fetch_max (ptr, value, __ATOMIC_SEQ_CST); +} + +/* { dg-final { scan-tree-dump-times "__atomic_load_16" 4 "isel" } } */ +/* { dg-final { scan-tree-dump-times "\\.ATOMIC_COMPARE_EXCHANGE" 4 "isel" } } */ diff --git a/gcc/testsuite/gcc.dg/atomic-fetch-minmax-ifn.c b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-ifn.c new file mode 100644 index 000000000000..73c4bb1c3a16 --- /dev/null +++ b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-ifn.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-gimple" } */ + +int +test_fetch_min (int *ptr, int value) +{ + return __atomic_fetch_min (ptr, value, __ATOMIC_SEQ_CST); +} + +int +test_fetch_max (int *ptr, int value) +{ + return __atomic_fetch_max (ptr, value, __ATOMIC_SEQ_CST); +} + +/* { dg-final { scan-tree-dump-times "ATOMIC_FETCH_MINMAX" 2 "gimple" } } */ diff --git a/gcc/testsuite/gcc.dg/atomic-fetch-minmax-lower.c b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-lower.c new file mode 100644 index 000000000000..8a93e6d723cc --- /dev/null +++ b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-lower.c @@ -0,0 +1,26 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-inline-atomics -fdump-tree-isel" } */ + +int +test_fetch_min (int *ptr, int value) +{ + return __atomic_fetch_min (ptr, value, __ATOMIC_SEQ_CST); +} + +int +test_fetch_max (int *ptr, int value) +{ + return __atomic_fetch_max (ptr, value, __ATOMIC_SEQ_CST); +} + +/* Make sure processing two IFNs together doesn't trip the lowering pass. */ +int +test_adjacent (int *ptr, int min_value, int max_value) +{ + int min_result = __atomic_fetch_min (ptr, min_value, __ATOMIC_SEQ_CST); + int max_result = __atomic_fetch_max (ptr, max_value, __ATOMIC_SEQ_CST); + return min_result + max_result; +} + +/* { dg-final { scan-tree-dump-times "Lowering atomic IFN" 4 "isel" } } */ +/* { dg-final { scan-tree-dump-times "\\.ATOMIC_COMPARE_EXCHANGE" 4 "isel" } } */ diff --git a/gcc/testsuite/gcc.dg/atomic-fetch-minmax-pointers.c b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-pointers.c new file mode 100644 index 000000000000..c84ca8325781 --- /dev/null +++ b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-pointers.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-gimple" } */ + +int * +test_fetch_min (int **ptr, int *value) +{ + return __atomic_fetch_min (ptr, value, __ATOMIC_RELAXED); +} + +int * +test_fetch_max (int **ptr, int *value) +{ + return __atomic_fetch_max (ptr, value, __ATOMIC_RELAXED); +} + +/* { dg-final { scan-tree-dump-times "ATOMIC_FETCH_MINMAX" 2 "gimple" } } */ diff --git a/gcc/testsuite/gcc.dg/atomic-fetch-minmax-sign-cast.c b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-sign-cast.c new file mode 100644 index 000000000000..0baa5ce66826 --- /dev/null +++ b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-sign-cast.c @@ -0,0 +1,38 @@ +/* Verify that __atomic_fetch_min/max correctly preserves signedness when the +underlying storage is unsigned but accessed via a signed pointer cast. */ + +/* { dg-do run } */ +/* { dg-require-effective-target sync_int_long } */ +/* { dg-options "-O2" } */ + +extern void abort (void); + +unsigned value; + +int +main (void) +{ + int old; + + value = (unsigned) -1; + old = __atomic_fetch_min ((int *) &value, 5, __ATOMIC_SEQ_CST); + if (old != -1 || (int) value != -1) + abort (); + + value = (unsigned) -1; + old = __atomic_fetch_max ((int *) &value, 5, __ATOMIC_SEQ_CST); + if (old != -1 || (int) value != 5) + abort (); + + value = (unsigned) -3; + old = __atomic_fetch_min ((int *) &value, -10, __ATOMIC_SEQ_CST); + if (old != -3 || (int) value != -10) + abort (); + + value = (unsigned) -10; + old = __atomic_fetch_max ((int *) &value, -3, __ATOMIC_SEQ_CST); + if (old != -10 || (int) value != -3) + abort (); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/atomic-fetch-minmax-type-mismatch.c b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-type-mismatch.c new file mode 100644 index 000000000000..5ce6e75d863c --- /dev/null +++ b/gcc/testsuite/gcc.dg/atomic-fetch-minmax-type-mismatch.c @@ -0,0 +1,31 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-gimple" } */ + +int +test_fetch_min_long_long (int *ptr, long long value) +{ + return __atomic_fetch_min (ptr, value, __ATOMIC_SEQ_CST); +} + +int +test_fetch_max_long_long (int *ptr, long long value) +{ + return __atomic_fetch_max (ptr, value, __ATOMIC_SEQ_CST); +} + +int +test_fetch_min_short (int *ptr, short value) +{ + return __atomic_fetch_min (ptr, value, __ATOMIC_SEQ_CST); +} + +int +test_fetch_min_char (int *ptr, char value) +{ + return __atomic_fetch_min (ptr, value, __ATOMIC_SEQ_CST); +} + +/* Each function must explicitly convert value to the type pointed to by ptr + before passing it to ATOMIC_FETCH_MINMAX. The four functions each produce + one such cast, so we expect exactly 4 in total. */ +/* { dg-final { scan-tree-dump-times "= \\(int\\) value" 4 "gimple" } } */ diff --git a/gcc/testsuite/gcc.dg/atomic-op-1.c b/gcc/testsuite/gcc.dg/atomic-op-1.c index a8a97c401b7a..cb4c8e3f0a25 100644 --- a/gcc/testsuite/gcc.dg/atomic-op-1.c +++ b/gcc/testsuite/gcc.dg/atomic-op-1.c @@ -5,6 +5,8 @@ /* Test the execution of the __atomic_*OP builtin routines for a char. */ +#include "limits.h" + extern void abort(void); char v, count, res; @@ -167,6 +169,114 @@ test_fetch_or () abort (); } +void +test_fetch_smin () +{ + signed char sv = 10; + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE) != -10) + abort (); + + if (__atomic_fetch_min (&sv, SCHAR_MIN, __ATOMIC_ACQ_REL) != -20) + abort (); + + if (__atomic_fetch_min (&sv, SCHAR_MAX, __ATOMIC_SEQ_CST) != SCHAR_MIN) + abort (); + + if (sv != SCHAR_MIN) + abort (); +} + +void +test_fetch_umin () +{ + unsigned char uv = 100; + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED) != 100) + abort (); + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_min (&uv, UCHAR_MAX, __ATOMIC_SEQ_CST) != 0) + abort (); + + if (uv != 0) + abort (); +} + +void +test_fetch_smax () +{ + signed char sv = -10; + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED) != -10) + abort (); + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME) != -5) + abort (); + + if (__atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_max (&sv, SCHAR_MAX, __ATOMIC_SEQ_CST) != 50) + abort (); + + if (sv != SCHAR_MAX) + abort (); +} + +void +test_fetch_umax () +{ + unsigned char uv = 10; + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL) != 100) + abort (); + + if (__atomic_fetch_max (&uv, UCHAR_MAX, __ATOMIC_SEQ_CST) != 200) + abort (); + + if (uv != UCHAR_MAX) + abort (); +} + /* The OP_fetch routines return the new value after the operation. */ void @@ -527,6 +637,130 @@ test_or () abort (); } +void +test_smin () +{ + signed char sv = 10; + + __atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE); + if (sv != -10) + abort (); + + __atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE); + if (sv != -20) + abort (); + + __atomic_fetch_min (&sv, SCHAR_MIN, __ATOMIC_ACQ_REL); + if (sv != SCHAR_MIN) + abort (); + + __atomic_fetch_min (&sv, SCHAR_MAX, __ATOMIC_SEQ_CST); + + if (sv != SCHAR_MIN) + abort (); +} + +void +test_umin () +{ + unsigned char uv = 100; + + __atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE); + if (uv != 10) + abort (); + + __atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL); + if (uv != 0) + abort (); + + __atomic_fetch_min (&uv, UCHAR_MAX, __ATOMIC_SEQ_CST); + + if (uv != 0) + abort (); +} + +void +test_smax () +{ + signed char sv = -10; + + __atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE); + if (sv != 10) + abort (); + + __atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL); + if (sv != 50) + abort (); + + __atomic_fetch_max (&sv, SCHAR_MAX, __ATOMIC_SEQ_CST); + + if (sv != SCHAR_MAX) + abort (); +} + +void +test_umax () +{ + unsigned char uv = 10; + + __atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE); + if (uv != 100) + abort (); + + __atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL); + if (uv != 200) + abort (); + + __atomic_fetch_max (&uv, UCHAR_MAX, __ATOMIC_SEQ_CST); + + if (uv != UCHAR_MAX) + abort (); +} + int main () { @@ -536,6 +770,10 @@ main () test_fetch_nand (); test_fetch_xor (); test_fetch_or (); + test_fetch_smin (); + test_fetch_umin (); + test_fetch_smax (); + test_fetch_umax (); test_add_fetch (); test_sub_fetch (); @@ -550,6 +788,10 @@ main () test_nand (); test_xor (); test_or (); + test_smin (); + test_umin (); + test_smax (); + test_umax (); return 0; } diff --git a/gcc/testsuite/gcc.dg/atomic-op-2.c b/gcc/testsuite/gcc.dg/atomic-op-2.c index 949850345b5b..bed3762bdab8 100644 --- a/gcc/testsuite/gcc.dg/atomic-op-2.c +++ b/gcc/testsuite/gcc.dg/atomic-op-2.c @@ -6,6 +6,8 @@ /* Test the execution of the __atomic_*OP builtin routines for a short. */ +#include "limits.h" + extern void abort(void); short v, count, res; @@ -168,6 +170,114 @@ test_fetch_or () abort (); } +void +test_fetch_smin () +{ + short sv = 10; + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE) != -10) + abort (); + + if (__atomic_fetch_min (&sv, SHRT_MIN, __ATOMIC_ACQ_REL) != -20) + abort (); + + if (__atomic_fetch_min (&sv, SHRT_MAX, __ATOMIC_SEQ_CST) != SHRT_MIN) + abort (); + + if (sv != SHRT_MIN) + abort (); +} + +void +test_fetch_umin () +{ + unsigned short uv = 100; + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED) != 100) + abort (); + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_min (&uv, USHRT_MAX, __ATOMIC_SEQ_CST) != 0) + abort (); + + if (uv != 0) + abort (); +} + +void +test_fetch_smax () +{ + short sv = -10; + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED) != -10) + abort (); + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME) != -5) + abort (); + + if (__atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_max (&sv, SHRT_MAX, __ATOMIC_SEQ_CST) != 50) + abort (); + + if (sv != SHRT_MAX) + abort (); +} + +void +test_fetch_umax () +{ + unsigned short uv = 10; + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL) != 100) + abort (); + + if (__atomic_fetch_max (&uv, USHRT_MAX, __ATOMIC_SEQ_CST) != 200) + abort (); + + if (uv != USHRT_MAX) + abort (); +} + /* The OP_fetch routines return the new value after the operation. */ void @@ -528,6 +638,130 @@ test_or () abort (); } +void +test_smin () +{ + short sv = 10; + + __atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE); + if (sv != -10) + abort (); + + __atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE); + if (sv != -20) + abort (); + + __atomic_fetch_min (&sv, SHRT_MIN, __ATOMIC_ACQ_REL); + if (sv != SHRT_MIN) + abort (); + + __atomic_fetch_min (&sv, SHRT_MAX, __ATOMIC_SEQ_CST); + + if (sv != SHRT_MIN) + abort (); +} + +void +test_umin () +{ + unsigned short uv = 100; + + __atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE); + if (uv != 10) + abort (); + + __atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL); + if (uv != 0) + abort (); + + __atomic_fetch_min (&uv, USHRT_MAX, __ATOMIC_SEQ_CST); + + if (uv != 0) + abort (); +} + +void +test_smax () +{ + short sv = -10; + + __atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE); + if (sv != 10) + abort (); + + __atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL); + if (sv != 50) + abort (); + + __atomic_fetch_max (&sv, SHRT_MAX, __ATOMIC_SEQ_CST); + + if (sv != SHRT_MAX) + abort (); +} + +void +test_umax () +{ + unsigned short uv = 10; + + __atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE); + if (uv != 100) + abort (); + + __atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL); + if (uv != 200) + abort (); + + __atomic_fetch_max (&uv, USHRT_MAX, __ATOMIC_SEQ_CST); + + if (uv != USHRT_MAX) + abort (); +} + int main () { @@ -537,6 +771,10 @@ main () test_fetch_nand (); test_fetch_xor (); test_fetch_or (); + test_fetch_smin (); + test_fetch_umin (); + test_fetch_smax (); + test_fetch_umax (); test_add_fetch (); test_sub_fetch (); @@ -551,6 +789,10 @@ main () test_nand (); test_xor (); test_or (); + test_smin (); + test_umin (); + test_smax (); + test_umax (); return 0; } diff --git a/gcc/testsuite/gcc.dg/atomic-op-3.c b/gcc/testsuite/gcc.dg/atomic-op-3.c index 9a54a2a41783..dc272cad52cf 100644 --- a/gcc/testsuite/gcc.dg/atomic-op-3.c +++ b/gcc/testsuite/gcc.dg/atomic-op-3.c @@ -5,6 +5,8 @@ /* Test the execution of the __atomic_*OP builtin routines for an int. */ +#include "limits.h" + extern void abort(void); int v, count, res; @@ -167,6 +169,114 @@ test_fetch_or () abort (); } +void +test_fetch_smin () +{ + int sv = 10; + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE) != -10) + abort (); + + if (__atomic_fetch_min (&sv, INT_MIN, __ATOMIC_ACQ_REL) != -20) + abort (); + + if (__atomic_fetch_min (&sv, INT_MAX, __ATOMIC_SEQ_CST) != INT_MIN) + abort (); + + if (sv != INT_MIN) + abort (); +} + +void +test_fetch_umin () +{ + unsigned int uv = 100; + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED) != 100) + abort (); + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_min (&uv, UINT_MAX, __ATOMIC_SEQ_CST) != 0) + abort (); + + if (uv != 0) + abort (); +} + +void +test_fetch_smax () +{ + int sv = -10; + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED) != -10) + abort (); + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME) != -5) + abort (); + + if (__atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_max (&sv, INT_MAX, __ATOMIC_SEQ_CST) != 50) + abort (); + + if (sv != INT_MAX) + abort (); +} + +void +test_fetch_umax () +{ + unsigned int uv = 10; + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL) != 100) + abort (); + + if (__atomic_fetch_max (&uv, UINT_MAX, __ATOMIC_SEQ_CST) != 200) + abort (); + + if (uv != UINT_MAX) + abort (); +} + /* The OP_fetch routines return the new value after the operation. */ void @@ -527,6 +637,130 @@ test_or () abort (); } +void +test_smin () +{ + int sv = 10; + + __atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE); + if (sv != -10) + abort (); + + __atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE); + if (sv != -20) + abort (); + + __atomic_fetch_min (&sv, INT_MIN, __ATOMIC_ACQ_REL); + if (sv != INT_MIN) + abort (); + + __atomic_fetch_min (&sv, INT_MAX, __ATOMIC_SEQ_CST); + + if (sv != INT_MIN) + abort (); +} + +void +test_umin () +{ + unsigned int uv = 100; + + __atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE); + if (uv != 10) + abort (); + + __atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL); + if (uv != 0) + abort (); + + __atomic_fetch_min (&uv, UINT_MAX, __ATOMIC_SEQ_CST); + + if (uv != 0) + abort (); +} + +void +test_smax () +{ + int sv = -10; + + __atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE); + if (sv != 10) + abort (); + + __atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL); + if (sv != 50) + abort (); + + __atomic_fetch_max (&sv, INT_MAX, __ATOMIC_SEQ_CST); + + if (sv != INT_MAX) + abort (); +} + +void +test_umax () +{ + unsigned int uv = 10; + + __atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE); + if (uv != 100) + abort (); + + __atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL); + if (uv != 200) + abort (); + + __atomic_fetch_max (&uv, UINT_MAX, __ATOMIC_SEQ_CST); + + if (uv != UINT_MAX) + abort (); +} + int main () { @@ -536,6 +770,10 @@ main () test_fetch_nand (); test_fetch_xor (); test_fetch_or (); + test_fetch_smin (); + test_fetch_umin (); + test_fetch_smax (); + test_fetch_umax (); test_add_fetch (); test_sub_fetch (); @@ -550,6 +788,10 @@ main () test_nand (); test_xor (); test_or (); + test_smin (); + test_umin (); + test_smax (); + test_umax (); return 0; } diff --git a/gcc/testsuite/gcc.dg/atomic-op-4.c b/gcc/testsuite/gcc.dg/atomic-op-4.c index 6990b0e2d751..5414cdff07ad 100644 --- a/gcc/testsuite/gcc.dg/atomic-op-4.c +++ b/gcc/testsuite/gcc.dg/atomic-op-4.c @@ -7,6 +7,8 @@ /* Test the execution of the __atomic_*OP builtin routines for long long. */ +#include "limits.h" + extern void abort(void); long long v, count, res; @@ -169,6 +171,114 @@ test_fetch_or () abort (); } +void +test_fetch_smin () +{ + long long sv = 10; + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE) != -10) + abort (); + + if (__atomic_fetch_min (&sv, LLONG_MIN, __ATOMIC_ACQ_REL) != -20) + abort (); + + if (__atomic_fetch_min (&sv, LLONG_MAX, __ATOMIC_SEQ_CST) != LLONG_MIN) + abort (); + + if (sv != LLONG_MIN) + abort (); +} + +void +test_fetch_umin () +{ + unsigned long long uv = 100; + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED) != 100) + abort (); + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_min (&uv, ULLONG_MAX, __ATOMIC_SEQ_CST) != 0) + abort (); + + if (uv != 0) + abort (); +} + +void +test_fetch_smax () +{ + long long sv = -10; + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED) != -10) + abort (); + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME) != -5) + abort (); + + if (__atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_max (&sv, LLONG_MAX, __ATOMIC_SEQ_CST) != 50) + abort (); + + if (sv != LLONG_MAX) + abort (); +} + +void +test_fetch_umax () +{ + unsigned long long uv = 10; + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL) != 100) + abort (); + + if (__atomic_fetch_max (&uv, ULLONG_MAX, __ATOMIC_SEQ_CST) != 200) + abort (); + + if (uv != ULLONG_MAX) + abort (); +} + /* The OP_fetch routines return the new value after the operation. */ void @@ -529,6 +639,130 @@ test_or () abort (); } +void +test_smin () +{ + long long sv = 10; + + __atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE); + if (sv != -10) + abort (); + + __atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE); + if (sv != -20) + abort (); + + __atomic_fetch_min (&sv, LLONG_MIN, __ATOMIC_ACQ_REL); + if (sv != LLONG_MIN) + abort (); + + __atomic_fetch_min (&sv, LLONG_MAX, __ATOMIC_SEQ_CST); + + if (sv != LLONG_MIN) + abort (); +} + +void +test_umin () +{ + unsigned long long uv = 100; + + __atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE); + if (uv != 10) + abort (); + + __atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL); + if (uv != 0) + abort (); + + __atomic_fetch_min (&uv, ULLONG_MAX, __ATOMIC_SEQ_CST); + + if (uv != 0) + abort (); +} + +void +test_smax () +{ + long long sv = -10; + + __atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE); + if (sv != 10) + abort (); + + __atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL); + if (sv != 50) + abort (); + + __atomic_fetch_max (&sv, LLONG_MAX, __ATOMIC_SEQ_CST); + + if (sv != LLONG_MAX) + abort (); +} + +void +test_umax () +{ + unsigned long long uv = 10; + + __atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE); + if (uv != 100) + abort (); + + __atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL); + if (uv != 200) + abort (); + + __atomic_fetch_max (&uv, ULLONG_MAX, __ATOMIC_SEQ_CST); + + if (uv != ULLONG_MAX) + abort (); +} + int main () { @@ -538,6 +772,10 @@ main () test_fetch_nand (); test_fetch_xor (); test_fetch_or (); + test_fetch_smin (); + test_fetch_umin (); + test_fetch_smax (); + test_fetch_umax (); test_add_fetch (); test_sub_fetch (); @@ -552,6 +790,10 @@ main () test_nand (); test_xor (); test_or (); + test_smin (); + test_umin (); + test_smax (); + test_umax (); return 0; } diff --git a/gcc/testsuite/gcc.dg/atomic-op-5.c b/gcc/testsuite/gcc.dg/atomic-op-5.c index 4c6dcef8bf23..0d16babd711b 100644 --- a/gcc/testsuite/gcc.dg/atomic-op-5.c +++ b/gcc/testsuite/gcc.dg/atomic-op-5.c @@ -9,6 +9,10 @@ extern void abort(void); +#define SINT128_MIN ((__int128_t)((__uint128_t)1 << (sizeof(__int128_t) * 8 - 1))) +#define SINT128_MAX ((__int128_t)~SINT128_MIN) +#define UINT128_MAX ((__uint128_t)~0) + __int128_t v, count, res; const __int128_t init = ~0; @@ -169,6 +173,114 @@ test_fetch_or () abort (); } +void +test_fetch_smin () +{ + __int128_t sv = 10; + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE) != 5) + abort (); + + if (__atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE) != -10) + abort (); + + if (__atomic_fetch_min (&sv, SINT128_MIN, __ATOMIC_ACQ_REL) != -20) + abort (); + + if (__atomic_fetch_min (&sv, SINT128_MAX, __ATOMIC_SEQ_CST) != SINT128_MIN) + abort (); + + if (sv != SINT128_MIN) + abort (); +} + +void +test_fetch_umin () +{ + __uint128_t uv = 100; + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED) != 100) + abort (); + + if (__atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_min (&uv, UINT128_MAX, __ATOMIC_SEQ_CST) != 0) + abort (); + + if (uv != 0) + abort (); +} + +void +test_fetch_smax () +{ + __int128_t sv = -10; + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED) != -10) + abort (); + + if (__atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME) != -5) + abort (); + + if (__atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE) != -5) + abort (); + + if (__atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL) != 10) + abort (); + + if (__atomic_fetch_max (&sv, SINT128_MAX, __ATOMIC_SEQ_CST) != 50) + abort (); + + if (sv != SINT128_MAX) + abort (); +} + +void +test_fetch_umax () +{ + __uint128_t uv = 10; + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED) != 10) + abort (); + + if (__atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE) != 50) + abort (); + + if (__atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL) != 100) + abort (); + + if (__atomic_fetch_max (&uv, UINT128_MAX, __ATOMIC_SEQ_CST) != 200) + abort (); + + if (uv != UINT128_MAX) + abort (); +} + /* The OP_fetch routines return the new value after the operation. */ void @@ -529,6 +641,130 @@ test_or () abort (); } +void +test_smin () +{ + __int128_t sv = 10; + + __atomic_fetch_min (&sv, 5, __ATOMIC_RELAXED); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, 5, __ATOMIC_CONSUME); + if (sv != 5) + abort (); + + __atomic_fetch_min (&sv, -10, __ATOMIC_ACQUIRE); + if (sv != -10) + abort (); + + __atomic_fetch_min (&sv, -20, __ATOMIC_RELEASE); + if (sv != -20) + abort (); + + __atomic_fetch_min (&sv, SINT128_MIN, __ATOMIC_ACQ_REL); + if (sv != SINT128_MIN) + abort (); + + __atomic_fetch_min (&sv, SINT128_MAX, __ATOMIC_SEQ_CST); + + if (sv != SINT128_MIN) + abort (); +} + +void +test_umin () +{ + __uint128_t uv = 100; + + __atomic_fetch_min (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 75, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_min (&uv, 10, __ATOMIC_RELEASE); + if (uv != 10) + abort (); + + __atomic_fetch_min (&uv, 0, __ATOMIC_ACQ_REL); + if (uv != 0) + abort (); + + __atomic_fetch_min (&uv, UINT128_MAX, __ATOMIC_SEQ_CST); + + if (uv != 0) + abort (); +} + +void +test_smax () +{ + __int128_t sv = -10; + + __atomic_fetch_max (&sv, -5, __ATOMIC_RELAXED); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -5, __ATOMIC_CONSUME); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, -20, __ATOMIC_ACQUIRE); + if (sv != -5) + abort (); + + __atomic_fetch_max (&sv, 10, __ATOMIC_RELEASE); + if (sv != 10) + abort (); + + __atomic_fetch_max (&sv, 50, __ATOMIC_ACQ_REL); + if (sv != 50) + abort (); + + __atomic_fetch_max (&sv, SINT128_MAX, __ATOMIC_SEQ_CST); + + if (sv != SINT128_MAX) + abort (); +} + +void +test_umax () +{ + __uint128_t uv = 10; + + __atomic_fetch_max (&uv, 50, __ATOMIC_RELAXED); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 50, __ATOMIC_CONSUME); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 30, __ATOMIC_ACQUIRE); + if (uv != 50) + abort (); + + __atomic_fetch_max (&uv, 100, __ATOMIC_RELEASE); + if (uv != 100) + abort (); + + __atomic_fetch_max (&uv, 200, __ATOMIC_ACQ_REL); + if (uv != 200) + abort (); + + __atomic_fetch_max (&uv, UINT128_MAX, __ATOMIC_SEQ_CST); + + if (uv != UINT128_MAX) + abort (); +} + int main () { @@ -538,6 +774,10 @@ main () test_fetch_nand (); test_fetch_xor (); test_fetch_or (); + test_fetch_smin (); + test_fetch_umin (); + test_fetch_smax (); + test_fetch_umax (); test_add_fetch (); test_sub_fetch (); @@ -552,6 +792,10 @@ main () test_nand (); test_xor (); test_or (); + test_smin (); + test_umin (); + test_smax (); + test_umax (); return 0; } -- 2.43.0
