summaryrefslogtreecommitdiff
path: root/src/dbinc/atomic.h
blob: 61f2ead99b3c6c419d20a5103f4f7e767caa5fd8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2009, 2015 Oracle and/or its affiliates.  All rights reserved.
 *
 * $Id$
 */

#ifndef _DB_ATOMIC_H_
#define	_DB_ATOMIC_H_

#if defined(__cplusplus)
extern "C" {
#endif

/*
 *	Atomic operation support for Oracle Berkeley DB
 *
 * HAVE_ATOMIC_SUPPORT configures whether to use the assembly language
 * or system calls to perform:
 *
 *	 atomic_inc(env, valueptr)
 *	    Adds 1 to the db_atomic_t value, returning the new value.
 *
 *	 atomic_dec(env, valueptr)
 *	    Subtracts 1 from the db_atomic_t value, returning the new value.
 *
 *	 atomic_compare_exchange(env, valueptr, oldval, newval)
 *	    If the db_atomic_t's value is still oldval, set it to newval.
 *	    It returns 1 for success or 0 for failure.
 *
 * The ENV * parameter is used only when HAVE_ATOMIC_SUPPORT is undefined.
 *
 * If the platform does not natively support any one of these operations,
 * then atomic operations will be emulated with this sequence:
 *		MUTEX_LOCK()
 *		<op>
 *		MUTEX_UNLOCK();
 * Uses where mutexes are not available (e.g. the environment has not yet
 * attached to the mutex region) must be avoided.
 */
#if defined(DB_WIN32)
typedef DWORD	atomic_value_t;
#else
typedef int32_t	 atomic_value_t;
#endif

/*
 * Windows CE has strange issues using the Interlocked APIs with variables
 * stored in shared memory. It seems like the page needs to have been written
 * prior to the API working as expected. Work around this by allocating an
 * additional 32-bit value that can be harmlessly written for each value
 * used in Interlocked instructions.
 */
#if defined(DB_WINCE)
typedef struct {
	volatile atomic_value_t value;
	volatile atomic_value_t dummy;
} db_atomic_t;
#else
typedef struct {
	volatile atomic_value_t value;
} db_atomic_t;
#endif

/*
 * These macro hide the db_atomic_t structure layout and help detect
 * non-atomic_t actual argument to the atomic_xxx() calls. DB requires
 * aligned 32-bit reads to be atomic even outside of explicit 'atomic' calls.
 * These have no memory barriers; the caller must include them when necessary.
 */
#define	atomic_read(p)		((p)->value)
#define	atomic_init(p, val)	((p)->value = (val))

#ifdef HAVE_ATOMIC_SUPPORT

#if defined(DB_WIN32)
#if defined(DB_WINCE)
#define	WINCE_ATOMIC_MAGIC(p)						\
	/*								\
	 * Memory mapped regions on Windows CE cause problems with	\
	 * InterlockedXXX calls. Each process making an InterlockedXXX	\
	 * call must make sure that it has written to the page prior to	\
	 * the call, or the InterlockedXXX call hangs. This does not	\
	 * seem	to be documented anywhere. Write a non-critical piece	\
	 * of memory from the shared region prior to attempting an	\
	 * InterlockedXXX operation.					\
	 */								\
	(p)->dummy = 0
#else
#define	WINCE_ATOMIC_MAGIC(p) 0
#endif

#if defined(DB_WINCE) || (defined(_MSC_VER) && _MSC_VER < 1300)
/*
 * The Interlocked instructions on Windows CE have different parameter
 * definitions. The parameters lost their 'volatile' qualifier,
 * cast it away, to avoid compiler warnings.
 * These definitions should match those in dbinc/mutex_int.h for tsl_t, except
 * that the WINCE version drops the volatile qualifier.
 */
typedef PLONG interlocked_val;
#define	atomic_inc(env, p)						\
	(WINCE_ATOMIC_MAGIC(p),						\
	InterlockedIncrement((interlocked_val)(&(p)->value)))

#else
typedef LONG volatile *interlocked_val;
#define	atomic_inc(env, p)	\
	InterlockedIncrement((interlocked_val)(&(p)->value))
#endif

#define	atomic_dec(env, p)						\
	(WINCE_ATOMIC_MAGIC(p),						\
	InterlockedDecrement((interlocked_val)(&(p)->value)))
#if defined(_MSC_VER) && _MSC_VER < 1300
#define	atomic_compare_exchange(env, p, oldval, newval)			\
	(WINCE_ATOMIC_MAGIC(p),						\
	(InterlockedCompareExchange((PVOID *)(&(p)->value),		\
	(PVOID)(newval), (PVOID)(oldval)) == (PVOID)(oldval)))
#else
#define	atomic_compare_exchange(env, p, oldval, newval)			\
	(WINCE_ATOMIC_MAGIC(p),						\
	(InterlockedCompareExchange((interlocked_val)(&(p)->value),	\
	(newval), (oldval)) == (oldval)))
#endif
#endif

#if defined(HAVE_ATOMIC_SOLARIS)
/* Solaris sparc & x86/64 */
#include <atomic.h>
#define	atomic_inc(env, p)	\
	atomic_inc_uint_nv((volatile unsigned int *) &(p)->value)
#define	atomic_dec(env, p)	\
	atomic_dec_uint_nv((volatile unsigned int *) &(p)->value)
#define	atomic_compare_exchange(env, p, oval, nval)		\
	(atomic_cas_32((volatile unsigned int *) &(p)->value,	\
	    (oval), (nval)) == (oval))
#endif

#if defined(HAVE_ATOMIC_X86_GCC_ASSEMBLY)
/* x86/x86_64 gcc  */
#define	atomic_inc(env, p)	__atomic_inc(p)
#define	atomic_dec(env, p)	__atomic_dec(p)
#define	atomic_compare_exchange(env, p, o, n)	\
	__atomic_compare_exchange_int((p), (o), (n))
static inline int __atomic_inc(db_atomic_t *p)
{
	int	temp;

	temp = 1;
	__asm__ __volatile__("lock; xadd %0, (%1)"
		: "+r"(temp)
		: "r"(p));
	return (temp + 1);
}

static inline int __atomic_dec(db_atomic_t *p)
{
	int	temp;

	temp = -1;
	__asm__ __volatile__("lock; xadd %0, (%1)"
		: "+r"(temp)
		: "r"(p));
	return (temp - 1);
}

/*
 * x86/gcc Compare exchange for shared latches. i486+
 *	Returns 1 for success, 0 for failure
 *
 * GCC 4.1+ has an equivalent  __sync_bool_compare_and_swap() as well as
 * __sync_val_compare_and_swap() which returns the value read from *dest
 * http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html
 * which configure could be changed to use.
 */
static inline int __atomic_compare_exchange_int(
	db_atomic_t *p, atomic_value_t oldval, atomic_value_t newval)
{
	atomic_value_t was;

	if (p->value != oldval)	/* check without expensive cache line locking */
		return 0;
	__asm__ __volatile__("lock; cmpxchgl %1, (%2);"
	    :"=a"(was)
	    :"r"(newval), "r"(p), "a"(oldval)
	    :"memory", "cc");
	return (was == oldval);
}
#endif

#else
/*
 * No native hardware support for atomic increment, decrement, and
 * compare-exchange. Emulate them when mutexes are supported;
 * do them without concern for atomicity when no mutexes.
 */
#ifndef HAVE_MUTEX_SUPPORT
/*
 * These minimal versions are correct to use only for single-threaded,
 * single-process environments.
 */
#define	atomic_inc(env, p)	(++(p)->value)
#define	atomic_dec(env, p)	(--(p)->value)
#define	atomic_compare_exchange(env, p, oldval, newval)		\
	(DB_ASSERT(env, atomic_read(p) == (oldval)),		\
	atomic_init(p, (newval)), 1)
#else
#define atomic_inc(env, p)	__atomic_inc(env, p)
#define atomic_dec(env, p)	__atomic_dec(env, p)
#endif
#endif

#if defined(__cplusplus)
}
#endif

#endif /* !_DB_ATOMIC_H_ */