/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
/*
 * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
 *                         University Research and Technology
 *                         Corporation.  All rights reserved.
 * Copyright (c) 2004-2006 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 * Copyright (c) 2004-2005 High Performance Computing Center Stuttgart,
 *                         University of Stuttgart.  All rights reserved.
 * Copyright (c) 2004-2005 The Regents of the University of California.
 *                         All rights reserved.
 * Copyright (c) 2007      Sun Microsystems, Inc.  All rights reserved.
 * Copyright (c) 2011      Sandia National Laboratories. All rights reserved.
 * Copyright (c) 2011-2017 Los Alamos National Security, LLC. All rights
 *                         reserved.
 * Copyright (c) 2017      Research Organization for Information Science
 *                         and Technology (RIST). All rights reserved.
 * Copyright (c) 2018-2020 Intel, Inc.  All rights reserved.
 * Copyright (c) 2021-2022 Nanook Consulting  All rights reserved.
 * Copyright (c)           Amazon.com, Inc. or its affiliates.  All Rights
 *                         reserved.
 * Copyright (c) 2023      Triad National Security, LLC. All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

/** @file
 *
 * Atomic operations.
 *
 * This API is patterned after the FreeBSD kernel atomic interface
 * (which is influenced by Intel's ia64 architecture).  The
 * FreeBSD interface is documented at
 *
 * http://www.freebsd.org/cgi/man.cgi?query=atomic&sektion=9
 *
 * Only the necessary subset of functions are implemented here.
 *
 * The following #defines will be true / false based on
 * assembly support:
 *
 *  - \c PMIX_HAVE_ATOMIC_MEM_BARRIER atomic memory barriers
 *  - \c PMIX_HAVE_ATOMIC_SPINLOCKS atomic spinlocks
 *  - \c PMIX_HAVE_ATOMIC_MATH_32 if 32 bit add/sub/compare-exchange can be done "atomically"
 *  - \c PMIX_HAVE_ATOMIC_MATH_64 if 64 bit add/sub/compare-exchange can be done "atomically"
 *
 * Note that for the Atomic math, atomic add/sub may be implemented as
 * C code using pmix_atomic_compare_exchange.  The appearance of atomic
 * operation will be upheld in these cases.
 */

#ifndef PMIX_SYS_ATOMIC_H
#define PMIX_SYS_ATOMIC_H 1

#include "src/include/pmix_config.h"
#include "src/include/pmix_stdatomic.h"

#if PMIX_USE_C11_ATOMICS

#include <stdatomic.h>

static inline void pmix_atomic_wmb(void)
{
    atomic_thread_fence(memory_order_release);
}

static inline void pmix_atomic_rmb(void)
{
#    if defined(PMIX_ATOMIC_X86_64)
    /* work around a bug in older gcc versions (observed in gcc 6.x)
     * where acquire seems to get treated as a no-op instead of being
     * equivalent to __asm__ __volatile__("": : :"memory") on x86_64 */
    __asm__ __volatile__("" : : : "memory");
#    else
    atomic_thread_fence(memory_order_acquire);
#    endif
}

#define PMIX_ATOMIC_DEFINE_OP(type, bits, operator, name)                   \
    static inline type                                                      \
    pmix_atomic_fetch_##name##_##bits(pmix_atomic_##type *addr, type value) \
    {                                                                       \
        return atomic_fetch_##name##_explicit(                              \
            addr, value, memory_order_relaxed);                             \
    }                                                                       \
                                                                            \
    static inline type                                                      \
    pmix_atomic_##name##_fetch_##bits(pmix_atomic_##type *addr, type value) \
    {                                                                       \
        return atomic_fetch_##name##_explicit(                              \
            addr, value, memory_order_relaxed) operator value;              \
    }

/* end of PMIX_USE_C11_ATOMICS */
#elif PMIX_USE_GCC_BUILTIN_ATOMICS

static inline void pmix_atomic_wmb(void)
{
    __atomic_thread_fence(__ATOMIC_RELEASE);
}

static inline void pmix_atomic_rmb(void)
{
#if defined(PMIX_ATOMIC_X86_64)
    /* work around a bug in older gcc versions where ACQUIRE seems to get
     * treated as a no-op instead of being equivalent to
     * __asm__ __volatile__("": : :"memory") */
    __asm__ __volatile__("" : : : "memory");
#else
    __atomic_thread_fence(__ATOMIC_ACQUIRE);
#endif
}

#define PMIX_ATOMIC_DEFINE_OP(type, bits, operator, name)                      \
    static inline type                                                         \
    pmix_atomic_fetch_##name##_##bits(pmix_atomic_##type *addr, type value)    \
    {                                                                          \
        return __atomic_fetch_##name(addr, value, __ATOMIC_RELAXED);           \
    }                                                                          \
                                                                               \
    static inline type                                                         \
    pmix_atomic_##name##_fetch_##bits(pmix_atomic_##type *addr, type value)    \
    {                                                                          \
        return __atomic_##name##_fetch(addr, value, __ATOMIC_RELAXED);         \
    }

/* end of PMIX_USE_GCC_BUILTIN_ATOMICS */
#else
#error OpenPMIx requires either C11 atomics support or GCC built-in atomics.
#endif

PMIX_ATOMIC_DEFINE_OP(int32_t, 32, +, add)
PMIX_ATOMIC_DEFINE_OP(int32_t, 32, &, and)
PMIX_ATOMIC_DEFINE_OP(int32_t, 32, |, or)
PMIX_ATOMIC_DEFINE_OP(int32_t, 32, ^, xor)
PMIX_ATOMIC_DEFINE_OP(int32_t, 32, -, sub)

#endif /* PMIX_SYS_ATOMIC_H */
