/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2014. All Rights Reserved.
 *
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * %CopyrightEnd%
 */

/*
 * Description: Native double word atomics using libatomic_ops
 * Author: Rickard Green
 */

#ifndef ETHR_LIBATOMIC_OPS_DW_ATOMIC_H__
#define ETHR_LIBATOMIC_OPS_DW_ATOMIC_H__

#if defined(AO_HAVE_double_t)						\
    && (defined(AO_HAVE_double_load_acquire)				\
	|| defined(AO_HAVE_double_load))				\
    && (defined(AO_HAVE_compare_double_and_swap_double)			\
	|| defined(AO_HAVE_compare_double_and_swap_double_full)		\
	|| defined(AO_HAVE_compare_double_and_swap_double_acquire)	\
	|| defined(AO_HAVE_compare_double_and_swap_double_release)	\
	|| defined(AO_HAVE_double_compare_and_swap)			\
	|| defined(AO_HAVE_double_compare_and_swap_full)		\
	|| defined(AO_HAVE_double_compare_and_swap_acquire)		\
	|| defined(AO_HAVE_double_compare_and_swap_release))

#if ETHR_SIZEOF_PTR == 4
#  define ETHR_NATIVE_SU_DW_SINT_T ethr_sint64_t
#elif ETHR_SIZEOF_PTR == 8 && defined(ETHR_HAVE_INT128_T)
#  define ETHR_NATIVE_SU_DW_SINT_T ethr_sint128_t
#endif

typedef union {
    volatile AO_double_t dw_mem;
#if defined(ETHR_NATIVE_SU_DW_SINT_T)
    ETHR_NATIVE_SU_DW_SINT_T su_dw_sint;
#endif
} ethr_native_dw_atomic_t;

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_NATIVE_SU_DW_ATOMIC
#else
#  define ETHR_HAVE_NATIVE_DW_ATOMIC
#endif

#define ETHR_NATIVE_DW_ATOMIC_IMPL ETHR_NATIVE_IMPL__

#if defined(ETHR_TRY_INLINE_FUNCS) || defined(ETHR_ATOMIC_IMPL__)


#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_NDWA_FUNC__(Func) ethr_native_su_dw_atomic_ ## Func
#  define ETHR_NDWA_RET_3_TYPE__ ETHR_NATIVE_SU_DW_SINT_T
#  define ETHR_NDWA_RET_2_TYPE__ ETHR_NATIVE_SU_DW_SINT_T
#  define ETHR_NDWA_VAL_ARG_TYPE__ ETHR_NATIVE_SU_DW_SINT_T
#  define ETHR_NDWA_DECL_ARG__(Arg)
#  if defined(AO_HAVE_DOUBLE_PTR_STORAGE)
#    define ETHR_NDWA_VAL2AOVAL__(AOV, V)			\
    ((AOV).AO_whole = (double_ptr_storage) (V))
#    define ETHR_NDWA_AOVAL2VAL__(AOV, V)			\
    ((V) = (ETHR_NATIVE_SU_DW_SINT_T) (AOV).AO_whole)
#    define ETHR_NDWA_RETURN_VAL_3__(SUCCESS, AOVAL, VAL)	\
    do {							\
	return (ETHR_NATIVE_SU_DW_SINT_T) (AOVAL).AO_whole;	\
    } while (0)
#    define ETHR_NDWA_RETURN_VAL_2__(AOVAL, VAL)		\
    do {							\
	return (ETHR_NATIVE_SU_DW_SINT_T) (AOVAL).AO_whole;	\
    } while (0)
#    define ETHR_NDWA_AOVAL_EQ__(AOV1, AOV2)			\
    ((AOV1).AO_whole == (AOV2).AO_whole)
#  else
typedef union {
    ethr_sint_t sint[2];
    ETHR_NATIVE_SU_DW_SINT_T dw_sint;
}  ethr_dw_splitter_t;
#    define ETHR_NDWA_VAL2AOVAL__(AOV, V)			\
    do {							\
	ethr_dw_splitter_t tmp__;				\
	tmp__.dw_sint = (V);					\
	(AOV).AO_val1 = (AO_t) tmp__.sint[0];			\
	(AOV).AO_val2 = (AO_t) tmp__.sint[1];			\
    } while (0)
#    define ETHR_NDWA_AOVAL2VAL__(AOV, V)			\
    do {							\
	ethr_dw_splitter_t tmp__;				\
	tmp__.sint[0] = (ethr_sint_t) (AOV).AO_val1;		\
	tmp__.sint[1] = (ethr_sint_t) (AOV).AO_val2;		\
	(V) = tmp__.dw_sint;					\
    } while (0)
#    define ETHR_NDWA_RETURN_VAL_3__(SUCCESS, AOVAL, VAL)	\
    do {							\
	ethr_dw_splitter_t tmp__;				\
	tmp__.sint[0] = (ethr_sint_t) (AOVAL).AO_val1;		\
	tmp__.sint[1] = (ethr_sint_t) (AOVAL).AO_val2;		\
	return tmp__.dw_sint;					\
    } while (0)
#    define ETHR_NDWA_AOVAL_EQ__(AOV1, AOV2)			\
    ((AOV1).AO_val1 == (AOV2).AO_val1				\
	 && (AOV1).AO_val2 == (AOV2).AO_val2)
#  endif
#else
#  define ETHR_NDWA_FUNC__(Func) ethr_native_dw_atomic_ ## Func
#  define ETHR_NDWA_RET_3_TYPE__ int
#  define ETHR_NDWA_RET_2_TYPE__ void
#  define ETHR_NDWA_VAL_ARG_TYPE__ ethr_sint_t *
#  define ETHR_NDWA_DECL_ARG__(Arg) , ETHR_NDWA_VAL_ARG_TYPE__ Arg
#    define ETHR_NDWA_VAL2AOVAL__(AOV, V)			\
    do {							\
	(AOV).AO_val1 = (AO_t) (V)[0];				\
	(AOV).AO_val2 = (AO_t) (V)[1];				\
    } while (0)
#    define ETHR_NDWA_AOVAL2VAL__(AOV, V)			\
    do {							\
	ethr_dw_splitter_t tmp__;				\
	(V)[0] = (ethr_sint_t) (AOV).AO_val1;			\
	(V)[1] = (ethr_sint_t) (AOV).AO_val2;			\
    } while (0)
#    define ETHR_NDWA_RETURN_VAL_3__(SUCCESS, AOVAL, VAL)	\
    do {							\
	(VAL)[0] = (ethr_sint_t) (AOVAL).AO_val1;		\
	(VAL)[1] = (ethr_sint_t) (AOVAL).AO_val2;		\
	return (SUCCESS);					\
    } while (0)
#    define ETHR_NDWA_RETURN_VAL_2__(AOVAL, VAL)		\
    do {							\
	(VAL)[0] = (ethr_sint_t) (AOVAL).AO_val1;		\
	(VAL)[1] = (ethr_sint_t) (AOVAL).AO_val2;		\
	return;							\
    } while (0)
#  if defined(AO_HAVE_DOUBLE_PTR_STORAGE)
#    define ETHR_NDWA_AOVAL_EQ__(AOV1, AOV2)			\
    ((AOV1).AO_whole == (AOV2).AO_whole)
#  else
#    define ETHR_NDWA_AOVAL_EQ__(AOV1, AOV2)			\
    ((AOV1).AO_val1 == (AOV2).AO_val1				\
	 && (AOV1).AO_val2 == (AOV2).AO_val2)
#  endif
#endif

#define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_ADDR
static ETHR_INLINE ethr_sint_t *
ethr_native_dw_atomic_addr(ethr_native_dw_atomic_t *var)
{
    return (ethr_sint_t *) &var->dw_mem;
}

#ifdef AO_HAVE_double_load

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_READ
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_READ
#endif

static ETHR_INLINE ETHR_NDWA_RET_2_TYPE__
ETHR_NDWA_FUNC__(read)(ethr_native_dw_atomic_t *var
		       ETHR_NDWA_DECL_ARG__(val))
{
    AO_double_t act = AO_double_load(&var->dw_mem);
    ETHR_NDWA_RETURN_VAL_2__(act, val);
}

#endif

#ifdef AO_HAVE_double_load_read

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_READ_RB
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_READ_RB
#endif

static ETHR_INLINE ETHR_NDWA_RET_2_TYPE__
ETHR_NDWA_FUNC__(read_rb)(ethr_native_dw_atomic_t *var
			  ETHR_NDWA_DECL_ARG__(val))
{
    AO_double_t act = AO_double_load_read(&var->dw_mem);
    ETHR_NDWA_RETURN_VAL_2__(act, val);
}

#endif

#ifdef AO_HAVE_double_load_acquire

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_READ_ACQB
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_READ_ACQB
#endif

static ETHR_INLINE ETHR_NDWA_RET_2_TYPE__
ETHR_NDWA_FUNC__(read_acqb)(ethr_native_dw_atomic_t *var
			    ETHR_NDWA_DECL_ARG__(val))
{
    AO_double_t act = AO_double_load_acquire(&var->dw_mem);
    ETHR_NDWA_RETURN_VAL_2__(act, val);
}

#endif

#ifdef AO_HAVE_double_store

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_SET
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_SET
#endif

static ETHR_INLINE void
ETHR_NDWA_FUNC__(set)(ethr_native_dw_atomic_t *var,
		      ETHR_NDWA_VAL_ARG_TYPE__ val)
{
    AO_double_t new;
    ETHR_NDWA_VAL2AOVAL__(new, val);
    AO_double_store(&var->dw_mem, new);
}

#endif

#ifdef AO_HAVE_double_store_write

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_SET_WB
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_SET_WB
#endif

static ETHR_INLINE void
ETHR_NDWA_FUNC__(set_wb)(ethr_native_dw_atomic_t *var,
			 ETHR_NDWA_VAL_ARG_TYPE__ val)
{
    AO_double_t new;
    ETHR_NDWA_VAL2AOVAL__(new, val);
    AO_double_store_write(&var->dw_mem, new);
}

#endif

#ifdef AO_HAVE_double_store_release

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_SET_RELB
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_SET_RELB
#endif

static ETHR_INLINE void
ETHR_NDWA_FUNC__(set_relb)(ethr_native_dw_atomic_t *var,
			   ETHR_NDWA_VAL_ARG_TYPE__ val)
{
    AO_double_t new;
    ETHR_NDWA_VAL2AOVAL__(new, val);
    AO_double_store_release(&var->dw_mem, new);
}

#endif

#if defined(AO_HAVE_double_compare_and_swap_full) || defined(AO_HAVE_compare_double_and_swap_double_full)

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_CMPXCHG_MB
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_CMPXCHG_MB
#endif

static ETHR_INLINE ETHR_NDWA_RET_3_TYPE__
ETHR_NDWA_FUNC__(cmpxchg_mb)(ethr_native_dw_atomic_t *var,
			     ETHR_NDWA_VAL_ARG_TYPE__ new,
			     ETHR_NDWA_VAL_ARG_TYPE__ exp)
{
    AO_double_t ao_act, ao_new, ao_exp;

    ETHR_NDWA_VAL2AOVAL__(ao_exp, exp);
    ETHR_NDWA_VAL2AOVAL__(ao_new, new);

    do {
	int xchgd;
#if defined(AO_HAVE_double_compare_and_swap_full)
	xchgd = AO_double_compare_and_swap_full(&var->dw_mem, ao_exp, ao_new);
#elif defined(AO_HAVE_compare_double_and_swap_double_full)
	xchgd = AO_compare_double_and_swap_double_full(&var->dw_mem,
						       ao_exp.AO_val1,
						       ao_exp.AO_val2,
						       ao_new.AO_val1,
						       ao_new.AO_val2);
#endif

	if (xchgd)
	    ETHR_NDWA_RETURN_VAL_3__(1, ao_exp, exp);

#ifdef AO_HAVE_double_load_acquire
	ao_act = AO_double_load_acquire(&var->dw_mem);
#else
	ao_act = AO_double_load(&var->dw_mem);
#endif

    } while (ETHR_NDWA_AOVAL_EQ__(ao_exp, ao_act));

#ifndef AO_HAVE_double_load_acquire
    AO_nop_full();
#endif

    ETHR_NDWA_RETURN_VAL_3__(1, ao_act, exp);
}

#endif

#if defined(AO_HAVE_double_compare_and_swap) || defined(AO_HAVE_compare_double_and_swap_double)

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_CMPXCHG
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_CMPXCHG
#endif

static ETHR_INLINE ETHR_NDWA_RET_3_TYPE__
ETHR_NDWA_FUNC__(cmpxchg)(ethr_native_dw_atomic_t *var,
			  ETHR_NDWA_VAL_ARG_TYPE__ new,
			  ETHR_NDWA_VAL_ARG_TYPE__ exp)
{
    AO_double_t ao_act, ao_new, ao_exp;

    ETHR_NDWA_VAL2AOVAL__(ao_exp, exp);
    ETHR_NDWA_VAL2AOVAL__(ao_new, new);

    do {
	int xchgd;
#if defined(AO_HAVE_double_compare_and_swap)
	xchgd = AO_double_compare_and_swap(&var->dw_mem, ao_exp, ao_new);
#elif defined(AO_HAVE_compare_double_and_swap_double)
	xchgd = AO_compare_double_and_swap_double(&var->dw_mem,
						  ao_exp.AO_val1,
						  ao_exp.AO_val2,
						  ao_new.AO_val1,
						  ao_new.AO_val2);
#endif

	if (xchgd)
	    ETHR_NDWA_RETURN_VAL_3__(1, ao_exp, exp);

#ifdef AO_HAVE_double_load
	ao_act = AO_double_load(&var->dw_mem);
#else
	ao_act = AO_double_load_acquire(&var->dw_mem);
#endif

    } while (ETHR_NDWA_AOVAL_EQ__(ao_exp, ao_act));

    ETHR_NDWA_RETURN_VAL_3__(1, ao_act, exp);
}

#endif

#if defined(AO_HAVE_double_compare_and_swap_read) || defined(AO_HAVE_compare_double_and_swap_double_read)

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_CMPXCHG_RB
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_CMPXCHG_RB
#endif

static ETHR_INLINE ETHR_NDWA_RET_3_TYPE__
ETHR_NDWA_FUNC__(cmpxchg_rb)(ethr_native_dw_atomic_t *var,
			     ETHR_NDWA_VAL_ARG_TYPE__ new,
			     ETHR_NDWA_VAL_ARG_TYPE__ exp)
{
    AO_double_t ao_act, ao_new, ao_exp;

    ETHR_NDWA_VAL2AOVAL__(ao_exp, exp);
    ETHR_NDWA_VAL2AOVAL__(ao_new, new);

    do {
	int xchgd;
#if defined(AO_HAVE_double_compare_and_swap_read)
	xchgd = AO_double_compare_and_swap_read(&var->dw_mem, ao_exp, ao_new);
#elif defined(AO_HAVE_compare_double_and_swap_double_read)
	xchgd = AO_compare_double_and_swap_double_read(&var->dw_mem,
							  ao_exp.AO_val1,
							  ao_exp.AO_val2,
							  ao_new.AO_val1,
							  ao_new.AO_val2);
#endif

	if (xchgd)
	    ETHR_NDWA_RETURN_VAL_3__(1, ao_exp, exp);

#if defined(AO_HAVE_double_load_read)
	ao_act = AO_double_load_read(&var->dw_mem);
#elif defined(AO_HAVE_double_load)
	ao_act = AO_double_load(&var->dw_mem);
#else
	ao_act = AO_double_load_acquire(&var->dw_mem);
#endif

    } while (ETHR_NDWA_AOVAL_EQ__(ao_exp, ao_act));

#ifndef AO_HAVE_double_load_read
#ifdef AO_HAVE_nop_read
    AO_nop_read();
#else
    AO_nop_full();
#endif
#endif

    ETHR_NDWA_RETURN_VAL_3__(1, ao_act, exp);
}

#endif

#if defined(AO_HAVE_double_compare_and_swap_acquire) || defined(AO_HAVE_compare_double_and_swap_double_acquire)

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_CMPXCHG_ACQB
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_CMPXCHG_ACQB
#endif

static ETHR_INLINE ETHR_NDWA_RET_3_TYPE__
ETHR_NDWA_FUNC__(cmpxchg_acqb)(ethr_native_dw_atomic_t *var,
			       ETHR_NDWA_VAL_ARG_TYPE__ new,
			       ETHR_NDWA_VAL_ARG_TYPE__ exp)
{
    AO_double_t ao_act, ao_new, ao_exp;

    ETHR_NDWA_VAL2AOVAL__(ao_exp, exp);
    ETHR_NDWA_VAL2AOVAL__(ao_new, new);

    do {
	int xchgd;
#if defined(AO_HAVE_double_compare_and_swap_acquire)
	xchgd = AO_double_compare_and_swap_acquire(&var->dw_mem, ao_exp, ao_new);
#elif defined(AO_HAVE_compare_double_and_swap_double_acquire)
	xchgd = AO_compare_double_and_swap_double_acquire(&var->dw_mem,
							  ao_exp.AO_val1,
							  ao_exp.AO_val2,
							  ao_new.AO_val1,
							  ao_new.AO_val2);
#endif

	if (xchgd)
	    ETHR_NDWA_RETURN_VAL_3__(1, ao_exp, exp);

#ifdef AO_HAVE_double_load_acquire
	ao_act = AO_double_load_acquire(&var->dw_mem);
#else
	ao_act = AO_double_load(&var->dw_mem);
#endif

    } while (ETHR_NDWA_AOVAL_EQ__(ao_exp, ao_act));

#ifndef AO_HAVE_double_load_acquire
    AO_nop_full();
#endif

    ETHR_NDWA_RETURN_VAL_3__(1, ao_act, exp);
}

#endif

#if defined(AO_HAVE_double_compare_and_swap_write) || defined(AO_HAVE_compare_double_and_swap_double_write)

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_CMPXCHG_WB
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_CMPXCHG_WB
#endif

static ETHR_INLINE ETHR_NDWA_RET_3_TYPE__
ETHR_NDWA_FUNC__(cmpxchg_wb)(ethr_native_dw_atomic_t *var,
			     ETHR_NDWA_VAL_ARG_TYPE__ new,
			     ETHR_NDWA_VAL_ARG_TYPE__ exp)
{
    AO_double_t ao_act, ao_new, ao_exp;

    ETHR_NDWA_VAL2AOVAL__(ao_exp, exp);
    ETHR_NDWA_VAL2AOVAL__(ao_new, new);

    do {
	int xchgd;
#if defined(AO_HAVE_double_compare_and_swap_write)
	xchgd = AO_double_compare_and_swap_write(&var->dw_mem, ao_exp, ao_new);
#elif defined(AO_HAVE_compare_double_and_swap_double_write)
	xchgd = AO_compare_double_and_swap_double_write(&var->dw_mem,
							ao_exp.AO_val1,
							ao_exp.AO_val2,
							ao_new.AO_val1,
							ao_new.AO_val2);
#endif

	if (xchgd)
	    ETHR_NDWA_RETURN_VAL_3__(1, ao_exp, exp);

#ifdef AO_HAVE_double_load
	ao_act = AO_double_load(&var->dw_mem);
#else
	ao_act = AO_double_load_acquire(&var->dw_mem);
#endif

    } while (ETHR_NDWA_AOVAL_EQ__(ao_exp, ao_act));

    ETHR_NDWA_RETURN_VAL_3__(1, ao_act, exp);
}

#endif

#if defined(AO_HAVE_double_compare_and_swap_release) || defined(AO_HAVE_compare_double_and_swap_double_release)

#if defined(ETHR_NATIVE_SU_DW_SINT_T)
#  define ETHR_HAVE_ETHR_NATIVE_SU_DW_ATOMIC_CMPXCHG_RELB
#else
#  define ETHR_HAVE_ETHR_NATIVE_DW_ATOMIC_CMPXCHG_RELB
#endif

static ETHR_INLINE ETHR_NDWA_RET_3_TYPE__
ETHR_NDWA_FUNC__(cmpxchg_relb)(ethr_native_dw_atomic_t *var,
			       ETHR_NDWA_VAL_ARG_TYPE__ new,
			       ETHR_NDWA_VAL_ARG_TYPE__ exp)
{
    AO_double_t ao_act, ao_new, ao_exp;

    ETHR_NDWA_VAL2AOVAL__(ao_exp, exp);
    ETHR_NDWA_VAL2AOVAL__(ao_new, new);

    do {
	int xchgd;
#if defined(AO_HAVE_double_compare_and_swap_release)
	xchgd = AO_double_compare_and_swap_release(&var->dw_mem, ao_exp, ao_new);
#elif defined(AO_HAVE_compare_double_and_swap_double_release)
	xchgd = AO_compare_double_and_swap_double_release(&var->dw_mem,
							  ao_exp.AO_val1,
							  ao_exp.AO_val2,
							  ao_new.AO_val1,
							  ao_new.AO_val2);
#endif

	if (xchgd)
	    ETHR_NDWA_RETURN_VAL_3__(1, ao_exp, exp);

	ao_act = AO_double_load(&var->dw_mem);

    } while (ETHR_NDWA_AOVAL_EQ__(ao_exp, ao_act));

    ETHR_NDWA_RETURN_VAL_3__(1, ao_act, exp);
}

#endif

#endif /* defined(ETHR_TRY_INLINE_FUNCS) || defined(ETHR_ATOMIC_IMPL__) */

#endif /* Have AO double functionality ... */

#endif /* ETHR_LIBATOMIC_OPS_DW_ATOMIC_H__ */

