502 lines
14 KiB
C
Executable File
502 lines
14 KiB
C
Executable File
/**
|
|
* \file
|
|
* Atomic operations
|
|
*
|
|
* Author:
|
|
* Dick Porter (dick@ximian.com)
|
|
*
|
|
* (C) 2002 Ximian, Inc.
|
|
* Copyright 2012 Xamarin Inc
|
|
* Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
*/
|
|
|
|
#ifndef _WAPI_ATOMIC_H_
|
|
#define _WAPI_ATOMIC_H_
|
|
|
|
#include "config.h"
|
|
#include <glib.h>
|
|
#include <mono/utils/mono-membar.h>
|
|
|
|
/*
|
|
The current Nexus 7 arm-v7a fails with:
|
|
F/MonoDroid( 1568): shared runtime initialization error: Cannot load library: reloc_library[1285]: 37 cannot locate '__sync_val_compare_and_swap_8'
|
|
|
|
Apple targets have historically being problematic, xcode 4.6 would miscompile the intrinsic.
|
|
*/
|
|
|
|
#if defined(HOST_WIN32)
|
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#include <windows.h>
|
|
|
|
static inline gint32
|
|
mono_atomic_cas_i32 (volatile gint32 *dest, gint32 exch, gint32 comp)
|
|
{
|
|
return InterlockedCompareExchange ((LONG volatile *)dest, (LONG)exch, (LONG)comp);
|
|
}
|
|
|
|
static inline gint64
|
|
mono_atomic_cas_i64 (volatile gint64 *dest, gint64 exch, gint64 comp)
|
|
{
|
|
return InterlockedCompareExchange64 ((LONG64 volatile *)dest, (LONG64)exch, (LONG64)comp);
|
|
}
|
|
|
|
static inline gpointer
|
|
mono_atomic_cas_ptr (volatile gpointer *dest, gpointer exch, gpointer comp)
|
|
{
|
|
return InterlockedCompareExchangePointer ((PVOID volatile *)dest, (PVOID)exch, (PVOID)comp);
|
|
}
|
|
|
|
static inline gint32
|
|
mono_atomic_add_i32 (volatile gint32 *dest, gint32 add)
|
|
{
|
|
return InterlockedAdd ((LONG volatile *)dest, (LONG)add);
|
|
}
|
|
|
|
static inline gint64
|
|
mono_atomic_add_i64 (volatile gint64 *dest, gint64 add)
|
|
{
|
|
return InterlockedAdd64 ((LONG64 volatile *)dest, (LONG64)add);
|
|
}
|
|
|
|
static inline gint32
|
|
mono_atomic_inc_i32 (volatile gint32 *dest)
|
|
{
|
|
return InterlockedIncrement ((LONG volatile *)dest);
|
|
}
|
|
|
|
static inline gint64
|
|
mono_atomic_inc_i64 (volatile gint64 *dest)
|
|
{
|
|
return InterlockedIncrement64 ((LONG64 volatile *)dest);
|
|
}
|
|
|
|
static inline gint32
|
|
mono_atomic_dec_i32 (volatile gint32 *dest)
|
|
{
|
|
return InterlockedDecrement ((LONG volatile *)dest);
|
|
}
|
|
|
|
static inline gint64
|
|
mono_atomic_dec_i64 (volatile gint64 *dest)
|
|
{
|
|
return InterlockedDecrement64 ((LONG64 volatile *)dest);
|
|
}
|
|
|
|
static inline gint32
|
|
mono_atomic_xchg_i32 (volatile gint32 *dest, gint32 exch)
|
|
{
|
|
return InterlockedExchange ((LONG volatile *)dest, (LONG)exch);
|
|
}
|
|
|
|
static inline gint64
|
|
mono_atomic_xchg_i64 (volatile gint64 *dest, gint64 exch)
|
|
{
|
|
return InterlockedExchange64 ((LONG64 volatile *)dest, (LONG64)exch);
|
|
}
|
|
|
|
static inline gpointer
|
|
mono_atomic_xchg_ptr (volatile gpointer *dest, gpointer exch)
|
|
{
|
|
return InterlockedExchangePointer ((PVOID volatile *)dest, (PVOID)exch);
|
|
}
|
|
|
|
static inline gint32
|
|
mono_atomic_fetch_add_i32 (volatile gint32 *dest, gint32 add)
|
|
{
|
|
return InterlockedExchangeAdd ((LONG volatile *)dest, (LONG)add);
|
|
}
|
|
|
|
static inline gint64
|
|
mono_atomic_fetch_add_i64 (volatile gint64 *dest, gint64 add)
|
|
{
|
|
return InterlockedExchangeAdd64 ((LONG64 volatile *)dest, (LONG)add);
|
|
}
|
|
|
|
static inline gint8
|
|
mono_atomic_load_i8 (volatile gint8 *src)
|
|
{
|
|
gint8 loaded_value = *src;
|
|
_ReadWriteBarrier ();
|
|
|
|
return loaded_value;
|
|
}
|
|
|
|
static inline gint16
|
|
mono_atomic_load_i16 (volatile gint16 *src)
|
|
{
|
|
gint16 loaded_value = *src;
|
|
_ReadWriteBarrier ();
|
|
|
|
return loaded_value;
|
|
}
|
|
|
|
static inline gint32 mono_atomic_load_i32 (volatile gint32 *src)
|
|
{
|
|
gint32 loaded_value = *src;
|
|
_ReadWriteBarrier ();
|
|
|
|
return loaded_value;
|
|
}
|
|
|
|
static inline gint64
|
|
mono_atomic_load_i64 (volatile gint64 *src)
|
|
{
|
|
#if defined(TARGET_AMD64)
|
|
gint64 loaded_value = *src;
|
|
_ReadWriteBarrier ();
|
|
|
|
return loaded_value;
|
|
#else
|
|
return InterlockedCompareExchange64 ((LONG64 volatile *)src, 0, 0);
|
|
#endif
|
|
}
|
|
|
|
static inline gpointer
|
|
mono_atomic_load_ptr (volatile gpointer *src)
|
|
{
|
|
gpointer loaded_value = *src;
|
|
_ReadWriteBarrier ();
|
|
|
|
return loaded_value;
|
|
}
|
|
|
|
static inline void
|
|
mono_atomic_store_i8 (volatile gint8 *dst, gint8 val)
|
|
{
|
|
#if (_MSC_VER >= 1600)
|
|
_InterlockedExchange8 ((CHAR volatile *)dst, (CHAR)val);
|
|
#else
|
|
*dst = val;
|
|
mono_memory_barrier ();
|
|
#endif
|
|
}
|
|
|
|
static inline void
|
|
mono_atomic_store_i16 (volatile gint16 *dst, gint16 val)
|
|
{
|
|
#if (_MSC_VER >= 1600)
|
|
InterlockedExchange16 ((SHORT volatile *)dst, (SHORT)val);
|
|
#else
|
|
*dst = val;
|
|
mono_memory_barrier ();
|
|
#endif
|
|
}
|
|
|
|
static inline void
|
|
mono_atomic_store_i32 (volatile gint32 *dst, gint32 val)
|
|
{
|
|
InterlockedExchange ((LONG volatile *)dst, (LONG)val);
|
|
}
|
|
|
|
static inline void
|
|
mono_atomic_store_i64 (volatile gint64 *dst, gint64 val)
|
|
{
|
|
InterlockedExchange64 ((LONG64 volatile *)dst, (LONG64)val);
|
|
}
|
|
|
|
static inline void
|
|
mono_atomic_store_ptr (volatile gpointer *dst, gpointer val)
|
|
{
|
|
InterlockedExchangePointer ((PVOID volatile *)dst, (PVOID)val);
|
|
}
|
|
|
|
/* Prefer GCC atomic ops if the target supports it (see configure.ac). */
|
|
#elif defined(USE_GCC_ATOMIC_OPS)
|
|
|
|
/*
|
|
* As of this comment (August 2016), all current Clang versions get atomic
|
|
* intrinsics on ARM64 wrong. All GCC versions prior to 5.3.0 do, too. The bug
|
|
* is the same: The compiler developers thought that the acq + rel barriers
|
|
* that ARM64 load/store instructions can impose are sufficient to provide
|
|
* sequential consistency semantics. This is not the case:
|
|
*
|
|
* http://lists.infradead.org/pipermail/linux-arm-kernel/2014-February/229588.html
|
|
*
|
|
* We work around this bug by inserting full barriers around each atomic
|
|
* intrinsic if we detect that we're built with a buggy compiler.
|
|
*/
|
|
|
|
#if defined (HOST_ARM64) && (defined (__clang__) || MONO_GNUC_VERSION < 50300)
|
|
#define WRAP_ATOMIC_INTRINSIC(INTRIN) \
|
|
({ \
|
|
mono_memory_barrier (); \
|
|
__typeof__ (INTRIN) atomic_ret__ = (INTRIN); \
|
|
mono_memory_barrier (); \
|
|
atomic_ret__; \
|
|
})
|
|
|
|
#define gcc_sync_val_compare_and_swap(a, b, c) WRAP_ATOMIC_INTRINSIC (__sync_val_compare_and_swap (a, b, c))
|
|
#define gcc_sync_add_and_fetch(a, b) WRAP_ATOMIC_INTRINSIC (__sync_add_and_fetch (a, b))
|
|
#define gcc_sync_sub_and_fetch(a, b) WRAP_ATOMIC_INTRINSIC (__sync_sub_and_fetch (a, b))
|
|
#define gcc_sync_fetch_and_add(a, b) WRAP_ATOMIC_INTRINSIC (__sync_fetch_and_add (a, b))
|
|
#else
|
|
#define gcc_sync_val_compare_and_swap(a, b, c) __sync_val_compare_and_swap (a, b, c)
|
|
#define gcc_sync_add_and_fetch(a, b) __sync_add_and_fetch (a, b)
|
|
#define gcc_sync_sub_and_fetch(a, b) __sync_sub_and_fetch (a, b)
|
|
#define gcc_sync_fetch_and_add(a, b) __sync_fetch_and_add (a, b)
|
|
#endif
|
|
|
|
static inline gint32 mono_atomic_cas_i32(volatile gint32 *dest,
|
|
gint32 exch, gint32 comp)
|
|
{
|
|
return gcc_sync_val_compare_and_swap (dest, comp, exch);
|
|
}
|
|
|
|
static inline gpointer mono_atomic_cas_ptr(volatile gpointer *dest, gpointer exch, gpointer comp)
|
|
{
|
|
return gcc_sync_val_compare_and_swap (dest, comp, exch);
|
|
}
|
|
|
|
static inline gint32 mono_atomic_add_i32(volatile gint32 *dest, gint32 add)
|
|
{
|
|
return gcc_sync_add_and_fetch (dest, add);
|
|
}
|
|
|
|
static inline gint32 mono_atomic_inc_i32(volatile gint32 *val)
|
|
{
|
|
return gcc_sync_add_and_fetch (val, 1);
|
|
}
|
|
|
|
static inline gint32 mono_atomic_dec_i32(volatile gint32 *val)
|
|
{
|
|
return gcc_sync_sub_and_fetch (val, 1);
|
|
}
|
|
|
|
static inline gint32 mono_atomic_xchg_i32(volatile gint32 *val, gint32 new_val)
|
|
{
|
|
gint32 old_val;
|
|
do {
|
|
old_val = *val;
|
|
} while (gcc_sync_val_compare_and_swap (val, old_val, new_val) != old_val);
|
|
return old_val;
|
|
}
|
|
|
|
static inline gpointer mono_atomic_xchg_ptr(volatile gpointer *val,
|
|
gpointer new_val)
|
|
{
|
|
gpointer old_val;
|
|
do {
|
|
old_val = *val;
|
|
} while (gcc_sync_val_compare_and_swap (val, old_val, new_val) != old_val);
|
|
return old_val;
|
|
}
|
|
|
|
static inline gint32 mono_atomic_fetch_add_i32(volatile gint32 *val, gint32 add)
|
|
{
|
|
return gcc_sync_fetch_and_add (val, add);
|
|
}
|
|
|
|
static inline gint8 mono_atomic_load_i8(volatile gint8 *src)
|
|
{
|
|
/* Kind of a hack, but GCC doesn't give us anything better, and it's
|
|
* certainly not as bad as using a CAS loop. */
|
|
return gcc_sync_fetch_and_add (src, 0);
|
|
}
|
|
|
|
static inline gint16 mono_atomic_load_i16(volatile gint16 *src)
|
|
{
|
|
return gcc_sync_fetch_and_add (src, 0);
|
|
}
|
|
|
|
static inline gint32 mono_atomic_load_i32(volatile gint32 *src)
|
|
{
|
|
return gcc_sync_fetch_and_add (src, 0);
|
|
}
|
|
|
|
static inline void mono_atomic_store_i8(volatile gint8 *dst, gint8 val)
|
|
{
|
|
/* Nothing useful from GCC at all, so fall back to CAS. */
|
|
gint8 old_val;
|
|
do {
|
|
old_val = *dst;
|
|
} while (gcc_sync_val_compare_and_swap (dst, old_val, val) != old_val);
|
|
}
|
|
|
|
static inline void mono_atomic_store_i16(volatile gint16 *dst, gint16 val)
|
|
{
|
|
gint16 old_val;
|
|
do {
|
|
old_val = *dst;
|
|
} while (gcc_sync_val_compare_and_swap (dst, old_val, val) != old_val);
|
|
}
|
|
|
|
static inline void mono_atomic_store_i32(volatile gint32 *dst, gint32 val)
|
|
{
|
|
/* Nothing useful from GCC at all, so fall back to CAS. */
|
|
gint32 old_val;
|
|
do {
|
|
old_val = *dst;
|
|
} while (gcc_sync_val_compare_and_swap (dst, old_val, val) != old_val);
|
|
}
|
|
|
|
#if defined (TARGET_OSX) || defined (__arm__) || (defined (__mips__) && !defined (__mips64)) || (defined (__powerpc__) && !defined (__powerpc64__)) || (defined (__sparc__) && !defined (__arch64__))
|
|
#define BROKEN_64BIT_ATOMICS_INTRINSIC 1
|
|
#endif
|
|
|
|
#if !defined (BROKEN_64BIT_ATOMICS_INTRINSIC)
|
|
|
|
static inline gint64 mono_atomic_cas_i64(volatile gint64 *dest, gint64 exch, gint64 comp)
|
|
{
|
|
return gcc_sync_val_compare_and_swap (dest, comp, exch);
|
|
}
|
|
|
|
static inline gint64 mono_atomic_add_i64(volatile gint64 *dest, gint64 add)
|
|
{
|
|
return gcc_sync_add_and_fetch (dest, add);
|
|
}
|
|
|
|
static inline gint64 mono_atomic_inc_i64(volatile gint64 *val)
|
|
{
|
|
return gcc_sync_add_and_fetch (val, 1);
|
|
}
|
|
|
|
static inline gint64 mono_atomic_dec_i64(volatile gint64 *val)
|
|
{
|
|
return gcc_sync_sub_and_fetch (val, 1);
|
|
}
|
|
|
|
static inline gint64 mono_atomic_fetch_add_i64(volatile gint64 *val, gint64 add)
|
|
{
|
|
return gcc_sync_fetch_and_add (val, add);
|
|
}
|
|
|
|
static inline gint64 mono_atomic_load_i64(volatile gint64 *src)
|
|
{
|
|
/* Kind of a hack, but GCC doesn't give us anything better. */
|
|
return gcc_sync_fetch_and_add (src, 0);
|
|
}
|
|
|
|
#else
|
|
|
|
/* Implement 64-bit cas by hand or emulate it. */
|
|
extern gint64 mono_atomic_cas_i64(volatile gint64 *dest, gint64 exch, gint64 comp);
|
|
|
|
/* Implement all other 64-bit atomics in terms of a specialized CAS
|
|
* in this case, since chances are that the other 64-bit atomic
|
|
* intrinsics are broken too.
|
|
*/
|
|
|
|
static inline gint64 mono_atomic_fetch_add_i64(volatile gint64 *dest, gint64 add)
|
|
{
|
|
gint64 old_val;
|
|
do {
|
|
old_val = *dest;
|
|
} while (mono_atomic_cas_i64 (dest, old_val + add, old_val) != old_val);
|
|
return old_val;
|
|
}
|
|
|
|
static inline gint64 mono_atomic_inc_i64(volatile gint64 *val)
|
|
{
|
|
gint64 get, set;
|
|
do {
|
|
get = *val;
|
|
set = get + 1;
|
|
} while (mono_atomic_cas_i64 (val, set, get) != get);
|
|
return set;
|
|
}
|
|
|
|
static inline gint64 mono_atomic_dec_i64(volatile gint64 *val)
|
|
{
|
|
gint64 get, set;
|
|
do {
|
|
get = *val;
|
|
set = get - 1;
|
|
} while (mono_atomic_cas_i64 (val, set, get) != get);
|
|
return set;
|
|
}
|
|
|
|
static inline gint64 mono_atomic_add_i64(volatile gint64 *dest, gint64 add)
|
|
{
|
|
gint64 get, set;
|
|
do {
|
|
get = *dest;
|
|
set = get + add;
|
|
} while (mono_atomic_cas_i64 (dest, set, get) != get);
|
|
return set;
|
|
}
|
|
|
|
static inline gint64 mono_atomic_load_i64(volatile gint64 *src)
|
|
{
|
|
return mono_atomic_cas_i64 (src, 0, 0);
|
|
}
|
|
|
|
#endif
|
|
|
|
static inline gpointer mono_atomic_load_ptr(volatile gpointer *src)
|
|
{
|
|
return mono_atomic_cas_ptr (src, NULL, NULL);
|
|
}
|
|
|
|
static inline void mono_atomic_store_ptr(volatile gpointer *dst, gpointer val)
|
|
{
|
|
mono_atomic_xchg_ptr (dst, val);
|
|
}
|
|
|
|
/* We always implement this in terms of a 64-bit cas since
|
|
* GCC doesn't have an intrisic to model it anyway. */
|
|
static inline gint64 mono_atomic_xchg_i64(volatile gint64 *val, gint64 new_val)
|
|
{
|
|
gint64 old_val;
|
|
do {
|
|
old_val = *val;
|
|
} while (mono_atomic_cas_i64 (val, new_val, old_val) != old_val);
|
|
return old_val;
|
|
}
|
|
|
|
static inline void mono_atomic_store_i64(volatile gint64 *dst, gint64 val)
|
|
{
|
|
/* Nothing useful from GCC at all, so fall back to CAS. */
|
|
mono_atomic_xchg_i64 (dst, val);
|
|
}
|
|
|
|
#else
|
|
|
|
#define WAPI_NO_ATOMIC_ASM
|
|
|
|
extern gint32 mono_atomic_cas_i32(volatile gint32 *dest, gint32 exch, gint32 comp);
|
|
extern gint64 mono_atomic_cas_i64(volatile gint64 *dest, gint64 exch, gint64 comp);
|
|
extern gpointer mono_atomic_cas_ptr(volatile gpointer *dest, gpointer exch, gpointer comp);
|
|
extern gint32 mono_atomic_add_i32(volatile gint32 *dest, gint32 add);
|
|
extern gint64 mono_atomic_add_i64(volatile gint64 *dest, gint64 add);
|
|
extern gint32 mono_atomic_inc_i32(volatile gint32 *dest);
|
|
extern gint64 mono_atomic_inc_i64(volatile gint64 *dest);
|
|
extern gint32 mono_atomic_dec_i32(volatile gint32 *dest);
|
|
extern gint64 mono_atomic_dec_i64(volatile gint64 *dest);
|
|
extern gint32 mono_atomic_xchg_i32(volatile gint32 *dest, gint32 exch);
|
|
extern gint64 mono_atomic_xchg_i64(volatile gint64 *dest, gint64 exch);
|
|
extern gpointer mono_atomic_xchg_ptr(volatile gpointer *dest, gpointer exch);
|
|
extern gint32 mono_atomic_fetch_add_i32(volatile gint32 *dest, gint32 add);
|
|
extern gint64 mono_atomic_fetch_add_i64(volatile gint64 *dest, gint64 add);
|
|
extern gint8 mono_atomic_load_i8(volatile gint8 *src);
|
|
extern gint16 mono_atomic_load_i16(volatile gint16 *src);
|
|
extern gint32 mono_atomic_load_i32(volatile gint32 *src);
|
|
extern gint64 mono_atomic_load_i64(volatile gint64 *src);
|
|
extern gpointer mono_atomic_load_ptr(volatile gpointer *src);
|
|
extern void mono_atomic_store_i8(volatile gint8 *dst, gint8 val);
|
|
extern void mono_atomic_store_i16(volatile gint16 *dst, gint16 val);
|
|
extern void mono_atomic_store_i32(volatile gint32 *dst, gint32 val);
|
|
extern void mono_atomic_store_i64(volatile gint64 *dst, gint64 val);
|
|
extern void mono_atomic_store_ptr(volatile gpointer *dst, gpointer val);
|
|
|
|
#endif
|
|
|
|
#if SIZEOF_VOID_P == 4
|
|
#define mono_atomic_fetch_add_word(p,add) mono_atomic_fetch_add_i32 ((volatile gint32*)p, (gint32)add)
|
|
#else
|
|
#define mono_atomic_fetch_add_word(p,add) mono_atomic_fetch_add_i64 ((volatile gint64*)p, (gint64)add)
|
|
#endif
|
|
|
|
/* The following functions cannot be found on any platform, and thus they can be declared without further existence checks */
|
|
|
|
static inline void
|
|
mono_atomic_store_bool (volatile gboolean *dest, gboolean val)
|
|
{
|
|
/* both, gboolean and gint32, are int32_t; the purpose of these casts is to make things explicit */
|
|
mono_atomic_store_i32 ((volatile gint32 *)dest, (gint32)val);
|
|
}
|
|
|
|
#endif /* _WAPI_ATOMIC_H_ */
|