/*
//@HEADER
// ************************************************************************
//
//                        Kokkos v. 3.0
//       Copyright (2020) National Technology & Engineering
//               Solutions of Sandia, LLC (NTESS).
//
// Under the terms of Contract DE-NA0003525 with NTESS,
// the U.S. Government retains certain rights in this software.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the Corporation nor the names of the
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Questions? Contact Christian R. Trott (crtrott@sandia.gov)
//
// ************************************************************************
//@HEADER
*/

#if defined(KOKKOS_ENABLE_RFO_PREFETCH)
#include <xmmintrin.h>
#endif

#include <Kokkos_Macros.hpp>
#if defined(KOKKOS_ATOMIC_HPP) && \
    !defined(KOKKOS_ATOMIC_COMPARE_EXCHANGE_STRONG_HPP)
#define KOKKOS_ATOMIC_COMPARE_EXCHANGE_STRONG_HPP

#include <impl/Kokkos_Atomic_Memory_Order.hpp>
#include <impl/Kokkos_Memory_Fence.hpp>

#if defined(KOKKOS_ENABLE_CUDA)
#include <Cuda/Kokkos_Cuda_Atomic_Intrinsics.hpp>
#endif

namespace Kokkos {

//----------------------------------------------------------------------------
// Cuda native CAS supports int, unsigned int, and unsigned long long int
// (non-standard type). Must cast-away 'volatile' for the CAS call.

#if defined(KOKKOS_ENABLE_CUDA)

#if defined(__CUDA_ARCH__) || defined(KOKKOS_IMPL_CUDA_CLANG_WORKAROUND)
__inline__ __device__ int atomic_compare_exchange(volatile int* const dest,
                                                  const int compare,
                                                  const int val) {
  return atomicCAS((int*)dest, compare, val);
}

__inline__ __device__ unsigned int atomic_compare_exchange(
    volatile unsigned int* const dest, const unsigned int compare,
    const unsigned int val) {
  return atomicCAS((unsigned int*)dest, compare, val);
}

__inline__ __device__ unsigned long long int atomic_compare_exchange(
    volatile unsigned long long int* const dest,
    const unsigned long long int compare, const unsigned long long int val) {
  return atomicCAS((unsigned long long int*)dest, compare, val);
}

template <typename T>
__inline__ __device__ T atomic_compare_exchange(
    volatile T* const dest, const T& compare,
    std::enable_if_t<sizeof(T) == sizeof(int), const T&> val) {
  const int tmp = atomicCAS((int*)dest, *((int*)&compare), *((int*)&val));
  return *((T*)&tmp);
}

template <typename T>
__inline__ __device__ T atomic_compare_exchange(
    volatile T* const dest, const T& compare,
    std::enable_if_t<sizeof(T) != sizeof(int) &&
                         sizeof(T) == sizeof(unsigned long long int),
                     const T&>
        val) {
  using type     = unsigned long long int;
  const type tmp = atomicCAS((type*)dest, *((type*)&compare), *((type*)&val));
  return *((T*)&tmp);
}

template <typename T>
__inline__ __device__ T atomic_compare_exchange(
    volatile T* const dest, const T& compare,
    std::enable_if_t<(sizeof(T) != 4) && (sizeof(T) != 8), const T>& val) {
  T return_val;
  // This is a way to (hopefully) avoid dead lock in a warp
  int done                 = 0;
  unsigned int mask        = __activemask();
  unsigned int active      = __ballot_sync(mask, 1);
  unsigned int done_active = 0;
  while (active != done_active) {
    if (!done) {
      if (Impl::lock_address_cuda_space((void*)dest)) {
        Kokkos::memory_fence();
        return_val = *dest;
        if (return_val == compare) *dest = val;
        Kokkos::memory_fence();
        Impl::unlock_address_cuda_space((void*)dest);
        done = 1;
      }
    }
    done_active = __ballot_sync(mask, done);
  }
  return return_val;
}
#endif
#endif

//----------------------------------------------------------------------------
// GCC native CAS supports int, long, unsigned int, unsigned long.
// Intel native CAS support int and long with the same interface as GCC.
#if !defined(__CUDA_ARCH__) || defined(KOKKOS_IMPL_CUDA_CLANG_WORKAROUND)
#if defined(KOKKOS_ENABLE_WINDOWS_ATOMICS)
// atomic_compare_exchange are already defined in Kokkos_Atomic_Windows.hpp
#elif defined(KOKKOS_ENABLE_GNU_ATOMICS) || defined(KOKKOS_ENABLE_INTEL_ATOMICS)

inline int atomic_compare_exchange(volatile int* const dest, const int compare,
                                   const int val) {
#if defined(KOKKOS_ENABLE_RFO_PREFETCH)
  _mm_prefetch((const char*)dest, _MM_HINT_ET0);
#endif
  return __sync_val_compare_and_swap(dest, compare, val);
}

inline long atomic_compare_exchange(volatile long* const dest,
                                    const long compare, const long val) {
#if defined(KOKKOS_ENABLE_RFO_PREFETCH)
  _mm_prefetch((const char*)dest, _MM_HINT_ET0);
#endif
  return __sync_val_compare_and_swap(dest, compare, val);
}

#if defined(KOKKOS_ENABLE_GNU_ATOMICS)

// GCC supports unsigned

inline unsigned int atomic_compare_exchange(volatile unsigned int* const dest,
                                            const unsigned int compare,
                                            const unsigned int val) {
  return __sync_val_compare_and_swap(dest, compare, val);
}

inline unsigned long atomic_compare_exchange(volatile unsigned long* const dest,
                                             const unsigned long compare,
                                             const unsigned long val) {
  return __sync_val_compare_and_swap(dest, compare, val);
}

inline unsigned long long atomic_compare_exchange(
    volatile unsigned long long* const dest, const unsigned long long compare,
    const unsigned long long val) {
  return __sync_val_compare_and_swap(dest, compare, val);
}

#endif

template <typename T>
inline T atomic_compare_exchange(
    volatile T* const dest, const T& compare,
    std::enable_if_t<sizeof(T) == sizeof(int), const T&> val) {
  union U {
    int i;
    T t;
    KOKKOS_INLINE_FUNCTION U() {}
  } tmp;

#if defined(KOKKOS_ENABLE_RFO_PREFETCH)
  _mm_prefetch((const char*)dest, _MM_HINT_ET0);
#endif

  tmp.i =
      __sync_val_compare_and_swap((int*)dest, *((int*)&compare), *((int*)&val));
  return tmp.t;
}

template <typename T>
inline T atomic_compare_exchange(
    volatile T* const dest, const T& compare,
    std::enable_if_t<sizeof(T) != sizeof(int) && sizeof(T) == sizeof(long),
                     const T&>
        val) {
  union U {
    long i;
    T t;
    KOKKOS_INLINE_FUNCTION U() {}
  } tmp;

#if defined(KOKKOS_ENABLE_RFO_PREFETCH)
  _mm_prefetch((const char*)dest, _MM_HINT_ET0);
#endif

  tmp.i = __sync_val_compare_and_swap((long*)dest, *((long*)&compare),
                                      *((long*)&val));
  return tmp.t;
}

#if defined(KOKKOS_ENABLE_ASM) && defined(KOKKOS_ENABLE_ISA_X86_64)
template <typename T>
inline T atomic_compare_exchange(
    volatile T* const dest, const T& compare,
    std::enable_if_t<sizeof(T) != sizeof(int) && sizeof(T) != sizeof(long) &&
                         sizeof(T) == sizeof(Impl::cas128_t),
                     const T&>
        val) {
  union U {
    Impl::cas128_t i;
    T t;
    KOKKOS_INLINE_FUNCTION U() {}
  } tmp;

#if defined(KOKKOS_ENABLE_RFO_PREFETCH)
  _mm_prefetch((const char*)dest, _MM_HINT_ET0);
#endif

  tmp.i = Impl::cas128((Impl::cas128_t*)dest, *((Impl::cas128_t*)&compare),
                       *((Impl::cas128_t*)&val));
  return tmp.t;
}
#endif

template <typename T>
inline T atomic_compare_exchange(
    volatile T* const dest, const T compare,
    std::enable_if_t<(sizeof(T) != 4) && (sizeof(T) != 8)
#if defined(KOKKOS_ENABLE_ASM) && defined(KOKKOS_ENABLE_ISA_X86_64)
                         && (sizeof(T) != 16)
#endif
                         ,
                     const T>& val) {
#if defined(KOKKOS_ENABLE_RFO_PREFETCH)
  _mm_prefetch((const char*)dest, _MM_HINT_ET0);
#endif

  while (!Impl::lock_address_host_space((void*)dest))
    ;
  Kokkos::memory_fence();
  T return_val = *dest;
  if (return_val == compare) {
    // Don't use the following line of code here:
    //
    // const T tmp = *dest = val;
    //
    // Instead, put each assignment in its own statement.  This is
    // because the overload of T::operator= for volatile *this should
    // return void, not volatile T&.  See Kokkos #177:
    //
    // https://github.com/kokkos/kokkos/issues/177
    *dest       = val;
    const T tmp = *dest;
#ifndef KOKKOS_COMPILER_CLANG
    (void)tmp;
#endif
    Kokkos::memory_fence();
  }
  Impl::unlock_address_host_space((void*)dest);
  return return_val;
}
//----------------------------------------------------------------------------

#elif defined(KOKKOS_ENABLE_OPENMP_ATOMICS)

template <typename T>
KOKKOS_INLINE_FUNCTION T atomic_compare_exchange(volatile T* const dest,
                                                 const T compare, const T val) {
  T retval;
#pragma omp critical
  {
    retval = dest[0];
    if (retval == compare) dest[0] = val;
  }
  return retval;
}

#elif defined(KOKKOS_ENABLE_SERIAL_ATOMICS)

template <typename T>
KOKKOS_INLINE_FUNCTION T atomic_compare_exchange(volatile T* const dest_v,
                                                 const T compare, const T val) {
  T* dest  = const_cast<T*>(dest_v);
  T retval = *dest;
  if (retval == compare) *dest = val;
  return retval;
}

#endif
#endif

// dummy for non-CUDA Kokkos headers being processed by NVCC
#if defined(__CUDA_ARCH__) && !defined(KOKKOS_ENABLE_CUDA)
template <typename T>
__inline__ __device__ T
atomic_compare_exchange(volatile T* const, const Kokkos::Impl::identity_t<T>,
                        const Kokkos::Impl::identity_t<T>) {
  return T();
}
#endif

template <typename T>
KOKKOS_INLINE_FUNCTION bool atomic_compare_exchange_strong(
    volatile T* const dest, const T compare, const T val) {
  return compare == atomic_compare_exchange(dest, compare, val);
}
//----------------------------------------------------------------------------

namespace Impl {
// memory-ordered versions are in the Impl namespace

template <class T, class MemoryOrderFailure>
KOKKOS_INLINE_FUNCTION bool _atomic_compare_exchange_strong_fallback(
    T* dest, T compare, T val, memory_order_seq_cst_t, MemoryOrderFailure) {
  Kokkos::memory_fence();
  auto rv = Kokkos::atomic_compare_exchange_strong(dest, compare, val);
  Kokkos::memory_fence();
  return rv;
}

template <class T, class MemoryOrderFailure>
KOKKOS_INLINE_FUNCTION bool _atomic_compare_exchange_strong_fallback(
    T* dest, T compare, T val, memory_order_acquire_t, MemoryOrderFailure) {
  auto rv = Kokkos::atomic_compare_exchange_strong(dest, compare, val);
  Kokkos::memory_fence();
  return rv;
}

template <class T, class MemoryOrderFailure>
KOKKOS_INLINE_FUNCTION bool _atomic_compare_exchange_strong_fallback(
    T* dest, T compare, T val, memory_order_release_t, MemoryOrderFailure) {
  Kokkos::memory_fence();
  return Kokkos::atomic_compare_exchange_strong(dest, compare, val);
}

template <class T, class MemoryOrderFailure>
KOKKOS_INLINE_FUNCTION bool _atomic_compare_exchange_strong_fallback(
    T* dest, T compare, T val, memory_order_relaxed_t, MemoryOrderFailure) {
  return Kokkos::atomic_compare_exchange_strong(dest, compare, val);
}

#if (defined(KOKKOS_ENABLE_GNU_ATOMICS) && !defined(__CUDA_ARCH__)) ||   \
    (defined(KOKKOS_ENABLE_INTEL_ATOMICS) && !defined(__CUDA_ARCH__)) || \
    defined(KOKKOS_ENABLE_CUDA_ASM_ATOMICS)

#if defined(__CUDA_ARCH__)
#define KOKKOS_INTERNAL_INLINE_DEVICE_IF_CUDA_ARCH __inline__ __device__
#else
#define KOKKOS_INTERNAL_INLINE_DEVICE_IF_CUDA_ARCH inline
#endif

template <class T, class MemoryOrderSuccess, class MemoryOrderFailure>
KOKKOS_INTERNAL_INLINE_DEVICE_IF_CUDA_ARCH bool _atomic_compare_exchange_strong(
    T* dest, T compare, T val, MemoryOrderSuccess, MemoryOrderFailure,
    std::enable_if_t<
        (sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8 ||
         sizeof(T) == 16) &&
            std::is_same<typename MemoryOrderSuccess::memory_order,
                         std::remove_cv_t<MemoryOrderSuccess>>::value &&
            std::is_same<typename MemoryOrderFailure::memory_order,
                         std::remove_cv_t<MemoryOrderFailure>>::value,
        void const**> = nullptr) {
  return __atomic_compare_exchange_n(dest, &compare, val, /* weak = */ false,
                                     MemoryOrderSuccess::gnu_constant,
                                     MemoryOrderFailure::gnu_constant);
}

template <class T, class MemoryOrderSuccess, class MemoryOrderFailure>
KOKKOS_INTERNAL_INLINE_DEVICE_IF_CUDA_ARCH bool _atomic_compare_exchange_strong(
    T* dest, T compare, T val, MemoryOrderSuccess order_success,
    MemoryOrderFailure order_failure,
    std::enable_if_t<
        !(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 ||
          sizeof(T) == 8 || sizeof(T) == 16) &&
            std::is_same<typename MemoryOrderSuccess::memory_order,
                         std::remove_cv_t<MemoryOrderSuccess>>::value &&
            std::is_same<typename MemoryOrderFailure::memory_order,
                         std::remove_cv_t<MemoryOrderFailure>>::value,
        void const**> = nullptr) {
  return _atomic_compare_exchange_fallback(dest, compare, val, order_success,
                                           order_failure);
}

#else

template <class T, class MemoryOrderSuccess, class MemoryOrderFailure>
KOKKOS_INLINE_FUNCTION bool _atomic_compare_exchange_strong(
    T* dest, T compare, T val, MemoryOrderSuccess order_success,
    MemoryOrderFailure order_failure) {
  return _atomic_compare_exchange_strong_fallback(dest, compare, val,
                                                  order_success, order_failure);
}

#endif

// TODO static asserts in overloads that don't make sense (as listed in
// https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/_005f_005fatomic-Builtins.html)
template <class T, class MemoryOrderSuccess, class MemoryOrderFailure>
KOKKOS_FORCEINLINE_FUNCTION bool atomic_compare_exchange_strong(
    T* dest, T compare, T val, MemoryOrderSuccess order_success,
    MemoryOrderFailure order_failure) {
  return _atomic_compare_exchange_strong(dest, compare, val, order_success,
                                         order_failure);
}

}  // end namespace Impl

}  // namespace Kokkos

#if defined(KOKKOS_ENABLE_CUDA)
#include <Cuda/Kokkos_Cuda_Atomic_Intrinsics_Restore_Builtins.hpp>
#endif

#endif
