/**
 * Many similar implementations exist. See for example libwsbm
 * or the linux kernel include/atomic.h
 *
 * No copyright claimed on this file.
 *
 */

#ifndef U_ATOMIC_H
#define U_ATOMIC_H

#include "pipe/p_compiler.h"
#include "pipe/p_defines.h"

/* Favor OS-provided implementations.
 *
 * Where no OS-provided implementation is available, fall back to
 * locally coded assembly, compiler intrinsic or ultimately a
 * mutex-based implementation.
 */
#if defined(PIPE_OS_SOLARIS)
#define PIPE_ATOMIC_OS_SOLARIS
#elif defined(PIPE_CC_MSVC)
#define PIPE_ATOMIC_MSVC_INTRINSIC
#elif (defined(PIPE_CC_MSVC) && defined(PIPE_ARCH_X86))
#define PIPE_ATOMIC_ASM_MSVC_X86                
#elif (defined(PIPE_CC_GCC) && defined(PIPE_ARCH_X86))
#define PIPE_ATOMIC_ASM_GCC_X86
#elif (defined(PIPE_CC_GCC) && defined(PIPE_ARCH_X86_64))
#define PIPE_ATOMIC_ASM_GCC_X86_64
#elif defined(PIPE_CC_GCC) && (PIPE_CC_GCC_VERSION >= 401)
#define PIPE_ATOMIC_GCC_INTRINSIC
#else
#error "Unsupported platform"
#endif


#if defined(PIPE_ATOMIC_ASM_GCC_X86_64)
#define PIPE_ATOMIC "GCC x86_64 assembly"

#ifdef __cplusplus
extern "C" {
#endif

#define p_atomic_set(_v, _i) (*(_v) = (_i))
#define p_atomic_read(_v) (*(_v))

static INLINE boolean
p_atomic_dec_zero(int32_t *v)
{
   unsigned char c;

   __asm__ __volatile__("lock; decl %0; sete %1":"+m"(*v), "=qm"(c)
			::"memory");

   return c != 0;
}

static INLINE void
p_atomic_inc(int32_t *v)
{
   __asm__ __volatile__("lock; incl %0":"+m"(*v));
}

static INLINE void
p_atomic_dec(int32_t *v)
{
   __asm__ __volatile__("lock; decl %0":"+m"(*v));
}

static INLINE int32_t
p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new)
{
   return __sync_val_compare_and_swap(v, old, _new);
}

#ifdef __cplusplus
}
#endif

#endif /* PIPE_ATOMIC_ASM_GCC_X86_64 */


#if defined(PIPE_ATOMIC_ASM_GCC_X86)

#define PIPE_ATOMIC "GCC x86 assembly"

#ifdef __cplusplus
extern "C" {
#endif

#define p_atomic_set(_v, _i) (*(_v) = (_i))
#define p_atomic_read(_v) (*(_v))

static INLINE boolean
p_atomic_dec_zero(int32_t *v)
{
   unsigned char c;

   __asm__ __volatile__("lock; decl %0; sete %1":"+m"(*v), "=qm"(c)
			::"memory");

   return c != 0;
}

static INLINE void
p_atomic_inc(int32_t *v)
{
   __asm__ __volatile__("lock; incl %0":"+m"(*v));
}

static INLINE void
p_atomic_dec(int32_t *v)
{
   __asm__ __volatile__("lock; decl %0":"+m"(*v));
}

static INLINE int32_t
p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new)
{
   return __sync_val_compare_and_swap(v, old, _new);
}

#ifdef __cplusplus
}
#endif

#endif



/* Implementation using GCC-provided synchronization intrinsics
 */
#if defined(PIPE_ATOMIC_GCC_INTRINSIC)

#define PIPE_ATOMIC "GCC Sync Intrinsics"

#ifdef __cplusplus
extern "C" {
#endif

#define p_atomic_set(_v, _i) (*(_v) = (_i))
#define p_atomic_read(_v) (*(_v))

static INLINE boolean
p_atomic_dec_zero(int32_t *v)
{
   return (__sync_sub_and_fetch(v, 1) == 0);
}

static INLINE void
p_atomic_inc(int32_t *v)
{
   (void) __sync_add_and_fetch(v, 1);
}

static INLINE void
p_atomic_dec(int32_t *v)
{
   (void) __sync_sub_and_fetch(v, 1);
}

static INLINE int32_t
p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new)
{
   return __sync_val_compare_and_swap(v, old, _new);
}

#ifdef __cplusplus
}
#endif

#endif



/* Unlocked version for single threaded environments, such as some
 * windows kernel modules.
 */
#if defined(PIPE_ATOMIC_OS_UNLOCKED) 

#define PIPE_ATOMIC "Unlocked"

#define p_atomic_set(_v, _i) (*(_v) = (_i))
#define p_atomic_read(_v) (*(_v))
#define p_atomic_dec_zero(_v) ((boolean) --(*(_v)))
#define p_atomic_inc(_v) ((void) (*(_v))++)
#define p_atomic_dec(_v) ((void) (*(_v))--)
#define p_atomic_cmpxchg(_v, old, _new) (*(_v) == old ? *(_v) = (_new) : *(_v))

#endif


/* Locally coded assembly for MSVC on x86:
 */
#if defined(PIPE_ATOMIC_ASM_MSVC_X86)

#define PIPE_ATOMIC "MSVC x86 assembly"

#ifdef __cplusplus
extern "C" {
#endif

#define p_atomic_set(_v, _i) (*(_v) = (_i))
#define p_atomic_read(_v) (*(_v))

static INLINE boolean
p_atomic_dec_zero(int32_t *v)
{
   unsigned char c;

   __asm {
      mov       eax, [v]
      lock dec  dword ptr [eax]
      sete      byte ptr [c]
   }

   return c != 0;
}

static INLINE void
p_atomic_inc(int32_t *v)
{
   __asm {
      mov       eax, [v]
      lock inc  dword ptr [eax]
   }
}

static INLINE void
p_atomic_dec(int32_t *v)
{
   __asm {
      mov       eax, [v]
      lock dec  dword ptr [eax]
   }
}

static INLINE int32_t
p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new)
{
   int32_t orig;

   __asm {
      mov ecx, [v]
      mov eax, [old]
      mov edx, [_new]
      lock cmpxchg [ecx], edx
      mov [orig], eax
   }

   return orig;
}

#ifdef __cplusplus
}
#endif

#endif


#if defined(PIPE_ATOMIC_MSVC_INTRINSIC)

#define PIPE_ATOMIC "MSVC Intrinsics"

#include <intrin.h>

#pragma intrinsic(_InterlockedIncrement)
#pragma intrinsic(_InterlockedDecrement)
#pragma intrinsic(_InterlockedCompareExchange)

#ifdef __cplusplus
extern "C" {
#endif

#define p_atomic_set(_v, _i) (*(_v) = (_i))
#define p_atomic_read(_v) (*(_v))

static INLINE boolean
p_atomic_dec_zero(int32_t *v)
{
   return _InterlockedDecrement((long *)v) == 0;
}

static INLINE void
p_atomic_inc(int32_t *v)
{
   _InterlockedIncrement((long *)v);
}

static INLINE void
p_atomic_dec(int32_t *v)
{
   _InterlockedDecrement((long *)v);
}

static INLINE int32_t
p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new)
{
   return _InterlockedCompareExchange((long *)v, _new, old);
}

#ifdef __cplusplus
}
#endif

#endif

#if defined(PIPE_ATOMIC_OS_SOLARIS)

#define PIPE_ATOMIC "Solaris OS atomic functions"

#include <atomic.h>

#ifdef __cplusplus
extern "C" {
#endif

#define p_atomic_set(_v, _i) (*(_v) = (_i))
#define p_atomic_read(_v) (*(_v))

static INLINE boolean
p_atomic_dec_zero(int32_t *v)
{
   uint32_t n = atomic_dec_32_nv((uint32_t *) v);

   return n != 0;
}

#define p_atomic_inc(_v) atomic_inc_32((uint32_t *) _v)
#define p_atomic_dec(_v) atomic_dec_32((uint32_t *) _v)

#define p_atomic_cmpxchg(_v, _old, _new) \
	atomic_cas_32( (uint32_t *) _v, (uint32_t) _old, (uint32_t) _new)

#ifdef __cplusplus
}
#endif

#endif


#ifndef PIPE_ATOMIC
#error "No pipe_atomic implementation selected"
#endif



#endif /* U_ATOMIC_H */