/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <atomic>
#include <cstdint>

namespace folly {

//  relaxed_atomic
//
//  Like atomic, but without the std::memory_order parameters on any member
//  functions. All operations use std::memory_order_relaxed, rather than the
//  std::memory_order_seq_cst, which is the default memory order in std::atomic.
//
//  Useful for values which may be loaded and stored concurrently, such as
//  counters, but which do not guard any associated data.
template <typename T>
struct relaxed_atomic;

namespace detail {

template <typename T>
struct relaxed_atomic_base : protected std::atomic<T> {
 private:
  using atomic = std::atomic<T>;

 public:
  using value_type = T;

  using atomic::atomic;

  T operator=(T desired) noexcept {
    store(desired);
    return desired;
  }
  T operator=(T desired) volatile noexcept {
    store(desired);
    return desired;
  }

  bool is_lock_free() const noexcept { return atomic::is_lock_free(); }
  bool is_lock_free() const volatile noexcept { return atomic::is_lock_free(); }

  void store(T desired) noexcept {
    atomic::store(desired, std::memory_order_relaxed);
  }
  void store(T desired) volatile noexcept {
    atomic::store(desired, std::memory_order_relaxed);
  }

  T load() const noexcept { return atomic::load(std::memory_order_relaxed); }
  T load() const volatile noexcept {
    return atomic::load(std::memory_order_relaxed);
  }

  operator T() const noexcept { return load(); }
  operator T() const volatile noexcept { return load(); }

  T exchange(T desired) noexcept {
    return atomic::exchange(desired, std::memory_order_relaxed);
  }
  T exchange(T desired) volatile noexcept {
    return atomic::exchange(desired, std::memory_order_relaxed);
  }

  bool compare_exchange_weak(T& expected, T desired) noexcept {
    return atomic::compare_exchange_weak(
        expected, desired, std::memory_order_relaxed);
  }
  bool compare_exchange_weak(T& expected, T desired) volatile noexcept {
    return atomic::compare_exchange_weak(
        expected, desired, std::memory_order_relaxed);
  }

  bool compare_exchange_strong(T& expected, T desired) noexcept {
    return atomic::compare_exchange_strong(
        expected, desired, std::memory_order_relaxed);
  }
  bool compare_exchange_strong(T& expected, T desired) volatile noexcept {
    return atomic::compare_exchange_strong(
        expected, desired, std::memory_order_relaxed);
  }
};

template <typename T>
struct relaxed_atomic_integral_base : private relaxed_atomic_base<T> {
 private:
  using atomic = std::atomic<T>;
  using base = relaxed_atomic_base<T>;

 public:
  using typename base::value_type;

  using base::relaxed_atomic_base;
  using base::operator=;
  using base::operator T;
  using base::compare_exchange_strong;
  using base::compare_exchange_weak;
  using base::exchange;
  using base::is_lock_free;
  using base::load;
  using base::store;

  T fetch_add(T arg) noexcept {
    return atomic::fetch_add(arg, std::memory_order_relaxed);
  }
  T fetch_add(T arg) volatile noexcept {
    return atomic::fetch_add(arg, std::memory_order_relaxed);
  }

  T fetch_sub(T arg) noexcept {
    return atomic::fetch_sub(arg, std::memory_order_relaxed);
  }
  T fetch_sub(T arg) volatile noexcept {
    return atomic::fetch_sub(arg, std::memory_order_relaxed);
  }

  T fetch_and(T arg) noexcept {
    return atomic::fetch_and(arg, std::memory_order_relaxed);
  }
  T fetch_and(T arg) volatile noexcept {
    return atomic::fetch_and(arg, std::memory_order_relaxed);
  }

  T fetch_or(T arg) noexcept {
    return atomic::fetch_or(arg, std::memory_order_relaxed);
  }
  T fetch_or(T arg) volatile noexcept {
    return atomic::fetch_or(arg, std::memory_order_relaxed);
  }

  T fetch_xor(T arg) noexcept {
    return atomic::fetch_xor(arg, std::memory_order_relaxed);
  }
  T fetch_xor(T arg) volatile noexcept {
    return atomic::fetch_xor(arg, std::memory_order_relaxed);
  }

  T operator++() noexcept { return fetch_add(1) + 1; }
  T operator++() volatile noexcept { return fetch_add(1) + 1; }

  T operator++(int) noexcept { return fetch_add(1); }
  T operator++(int) volatile noexcept { return fetch_add(1); }

  T operator--() noexcept { return fetch_sub(1) - 1; }
  T operator--() volatile noexcept { return fetch_sub(1) - 1; }

  T operator--(int) noexcept { return fetch_sub(1); }
  T operator--(int) volatile noexcept { return fetch_sub(1); }

  T operator+=(T arg) noexcept { return fetch_add(arg) + arg; }
  T operator+=(T arg) volatile noexcept { return fetch_add(arg) + arg; }

  T operator-=(T arg) noexcept { return fetch_sub(arg) - arg; }
  T operator-=(T arg) volatile noexcept { return fetch_sub(arg) - arg; }

  T operator&=(T arg) noexcept { return fetch_and(arg) & arg; }
  T operator&=(T arg) volatile noexcept { return fetch_and(arg) & arg; }

  T operator|=(T arg) noexcept { return fetch_or(arg) | arg; }
  T operator|=(T arg) volatile noexcept { return fetch_or(arg) | arg; }

  T operator^=(T arg) noexcept { return fetch_xor(arg) ^ arg; }
  T operator^=(T arg) volatile noexcept { return fetch_xor(arg) ^ arg; }
};

} // namespace detail

template <>
struct relaxed_atomic<bool> : detail::relaxed_atomic_base<bool> {
 private:
  using base = detail::relaxed_atomic_base<bool>;

 public:
  using typename base::value_type;

  using base::relaxed_atomic_base;
  using base::operator=;
  using base::operator bool;
};

using relaxed_atomic_bool = relaxed_atomic<bool>;

template <typename T>
struct relaxed_atomic : detail::relaxed_atomic_base<T> {
 private:
  using base = detail::relaxed_atomic_base<T>;

 public:
  using typename base::value_type;

  using base::relaxed_atomic_base;
  using base::operator=;
  using base::operator T;
};

template <typename T>
struct relaxed_atomic<T*> : detail::relaxed_atomic_base<T*> {
 private:
  using atomic = std::atomic<T*>;
  using base = detail::relaxed_atomic_base<T*>;

 public:
  using typename base::value_type;

  using detail::relaxed_atomic_base<T*>::relaxed_atomic_base;
  using base::operator=;
  using base::operator T*;

  T* fetch_add(std::ptrdiff_t arg) noexcept {
    return atomic::fetch_add(arg, std::memory_order_relaxed);
  }
  T* fetch_add(std::ptrdiff_t arg) volatile noexcept {
    return atomic::fetch_add(arg, std::memory_order_relaxed);
  }

  T* fetch_sub(std::ptrdiff_t arg) noexcept {
    return atomic::fetch_sub(arg, std::memory_order_relaxed);
  }
  T* fetch_sub(std::ptrdiff_t arg) volatile noexcept {
    return atomic::fetch_sub(arg, std::memory_order_relaxed);
  }

  T* operator++() noexcept { return fetch_add(1) + 1; }
  T* operator++() volatile noexcept { return fetch_add(1) + 1; }

  T* operator++(int) noexcept { return fetch_add(1); }
  T* operator++(int) volatile noexcept { return fetch_add(1); }

  T* operator--() noexcept { return fetch_sub(1) - 1; }
  T* operator--() volatile noexcept { return fetch_sub(1) - 1; }

  T* operator--(int) noexcept { return fetch_sub(1); }
  T* operator--(int) volatile noexcept { return fetch_sub(1); }

  T* operator+=(std::ptrdiff_t arg) noexcept { return fetch_add(arg) + arg; }
  T* operator+=(std::ptrdiff_t arg) volatile noexcept {
    return fetch_add(arg) + arg;
  }

  T* operator-=(std::ptrdiff_t arg) noexcept { return fetch_sub(arg) - arg; }
  T* operator-=(std::ptrdiff_t arg) volatile noexcept {
    return fetch_sub(arg) - arg;
  }
};

template <typename T>
relaxed_atomic(T) -> relaxed_atomic<T>;

#define FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(type)            \
  template <>                                                                \
  struct relaxed_atomic<type> : detail::relaxed_atomic_integral_base<type> { \
   private:                                                                  \
    using base = detail::relaxed_atomic_integral_base<type>;                 \
                                                                             \
   public:                                                                   \
    using base::relaxed_atomic_integral_base;                                \
    using base::operator=;                                                   \
    using base::operator type;                                               \
  };

FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(char)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed char)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned char)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed short)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned short)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed int)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned int)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed long)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned long)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(signed long long)
FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION(unsigned long long)

#undef FOLLY_RELAXED_ATOMIC_DEFINE_INTEGRAL_SPECIALIZATION

using relaxed_atomic_char = relaxed_atomic<char>;
using relaxed_atomic_schar = relaxed_atomic<signed char>;
using relaxed_atomic_uchar = relaxed_atomic<unsigned char>;
using relaxed_atomic_short = relaxed_atomic<short>;
using relaxed_atomic_ushort = relaxed_atomic<unsigned short>;
using relaxed_atomic_int = relaxed_atomic<int>;
using relaxed_atomic_uint = relaxed_atomic<unsigned int>;
using relaxed_atomic_long = relaxed_atomic<long>;
using relaxed_atomic_ulong = relaxed_atomic<unsigned long>;
using relaxed_atomic_llong = relaxed_atomic<long long>;
using relaxed_atomic_ullong = relaxed_atomic<unsigned long long>;
using relaxed_atomic_char16_t = relaxed_atomic<char16_t>;
using relaxed_atomic_char32_t = relaxed_atomic<char32_t>;
using relaxed_atomic_wchar_t = relaxed_atomic<wchar_t>;
using relaxed_atomic_int8_t = relaxed_atomic<std::int8_t>;
using relaxed_atomic_uint8_t = relaxed_atomic<std::uint8_t>;
using relaxed_atomic_int16_t = relaxed_atomic<std::int16_t>;
using relaxed_atomic_uint16_t = relaxed_atomic<std::uint16_t>;
using relaxed_atomic_int32_t = relaxed_atomic<std::int32_t>;
using relaxed_atomic_uint32_t = relaxed_atomic<std::uint32_t>;
using relaxed_atomic_int64_t = relaxed_atomic<std::int64_t>;
using relaxed_atomic_uint64_t = relaxed_atomic<std::uint64_t>;
using relaxed_atomic_int_least8_t = relaxed_atomic<std::int_least8_t>;
using relaxed_atomic_uint_least8_t = relaxed_atomic<std::uint_least8_t>;
using relaxed_atomic_int_least16_t = relaxed_atomic<std::int_least16_t>;
using relaxed_atomic_uint_least16_t = relaxed_atomic<std::uint_least16_t>;
using relaxed_atomic_int_least32_t = relaxed_atomic<std::int_least32_t>;
using relaxed_atomic_uint_least32_t = relaxed_atomic<std::uint_least32_t>;
using relaxed_atomic_int_least64_t = relaxed_atomic<std::int_least64_t>;
using relaxed_atomic_uint_least64_t = relaxed_atomic<std::uint_least64_t>;
using relaxed_atomic_int_fast8_t = relaxed_atomic<std::int_fast8_t>;
using relaxed_atomic_uint_fast8_t = relaxed_atomic<std::uint_fast8_t>;
using relaxed_atomic_int_fast16_t = relaxed_atomic<std::int_fast16_t>;
using relaxed_atomic_uint_fast16_t = relaxed_atomic<std::uint_fast16_t>;
using relaxed_atomic_int_fast32_t = relaxed_atomic<std::int_fast32_t>;
using relaxed_atomic_uint_fast32_t = relaxed_atomic<std::uint_fast32_t>;
using relaxed_atomic_int_fast64_t = relaxed_atomic<std::int_fast64_t>;
using relaxed_atomic_uint_fast64_t = relaxed_atomic<std::uint_fast64_t>;
using relaxed_atomic_intptr_t = relaxed_atomic<std::intptr_t>;
using relaxed_atomic_uintptr_t = relaxed_atomic<std::uintptr_t>;
using relaxed_atomic_size_t = relaxed_atomic<std::size_t>;
using relaxed_atomic_ptrdiff_t = relaxed_atomic<std::ptrdiff_t>;
using relaxed_atomic_intmax_t = relaxed_atomic<std::intmax_t>;
using relaxed_atomic_uintmax_t = relaxed_atomic<std::uintmax_t>;

} // namespace folly
