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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
|
[/
/ Copyright (c) 2009 Helge Bahmann
/
/ Distributed under the Boost Software License, Version 1.0. (See accompanying
/ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
/]
[section:template_organization Organization of class template layers]
The implementation uses multiple layers of template classes that
inherit from the next lower level each and refine or adapt the respective
underlying class:
* [^boost::atomic<T>] is the topmost-level, providing
the external interface. Implementation-wise, it does not add anything
(except for hiding copy constructor and assignment operator).
* [^boost::detail::atomic::internal_atomic&<T,S=sizeof(T),I=is_integral_type<T> >]:
This layer is mainly responsible for providing the overloaded operators
mapping to API member functions (e.g. [^+=] to [^fetch_add]).
The defaulted template parameter [^I] allows
to expose the correct API functions (via partial template
specialization): For non-integral types, it only
publishes the various [^exchange] functions
as well as load and store, for integral types it
additionally exports arithmetic and logic operations.
[br]
Depending on whether the given type is integral, it
inherits from either [^boost::detail::atomic::platform_atomic<T,S=sizeof(T)>]
or [^boost::detail::atomic::platform_atomic_integral<T,S=sizeof(T)>].
There is however some special-casing: for non-integral types
of size 1, 2, 4 or 8, it will coerce the datatype into an integer representation
and delegate to [^boost::detail::atomic::platform_atomic_integral<T,S=sizeof(T)>]
-- the rationale is that platform implementors only need to provide
integer-type operations.
* [^boost::detail::atomic::platform_atomic_integral<T,S=sizeof(T)>]
must provide the full set of operations for an integral type T
(i.e. [^load], [^store], [^exchange],
[^compare_exchange_weak], [^compare_exchange_strong],
[^fetch_add], [^fetch_sub], [^fetch_and],
[^fetch_or], [^fetch_xor], [^is_lock_free]).
The default implementation uses locking to emulate atomic operations, so
this is the level at which implementors should provide template specializations
to add support for platform-specific atomic operations.
[br]
The two separate template parameters allow separate specialization
on size and type (which, with fixed size, cannot
specify more than signedness/unsignedness). The rationale is that
most platform-specific atomic operations usually depend only on the
operand size, so that common implementations for signed/unsigned
types are possible. Signedness allows to properly to choose sign-extending
instructions for the [^load] operation, avoiding later
conversion. The expectation is that in most implementations this will
be a normal assignment in C, possibly accompanied by memory
fences, so that the compiler can automatically choose the correct
instruction.
* At the lowest level, [^boost::detail::atomic::platform_atomic<T,S=sizeof(T)>]
provides the most basic atomic operations ([^load], [^store],
[^exchange], [^compare_exchange_weak],
[^compare_exchange_strong]) for arbitrarily generic data types.
The default implementation uses locking as a fallback mechanism.
Implementors generally do not have to specialize at this level
(since these will not be used for the common integral type sizes
of 1, 2, 4 and 8 bytes), but if s/he can if s/he so wishes to
provide truly atomic operations for "odd" data type sizes.
Some amount of care must be taken as the "raw" data type
passed in from the user through [^boost::atomic<T>]
is visible here -- it thus needs to be type-punned or otherwise
manipulated byte-by-byte to avoid using overloaded assignment,
comparison operators and copy constructors.
[endsect]
[section:platform_atomic_implementation Implementing platform-specific atomic operations]
In principle implementors are responsible for providing the
full range of named member functions of an atomic object
(i.e. [^load], [^store], [^exchange],
[^compare_exchange_weak], [^compare_exchange_strong],
[^fetch_add], [^fetch_sub], [^fetch_and],
[^fetch_or], [^fetch_xor], [^is_lock_free]).
These must be implemented as partial template specializations for
[^boost::detail::atomic::platform_atomic_integral<T,S=sizeof(T)>]:
[c++]
template<typename T>
class platform_atomic_integral<T, 4>
{
public:
explicit platform_atomic_integral(T v) : i(v) {}
platform_atomic_integral(void) {}
T load(memory_order order=memory_order_seq_cst) const volatile
{
// platform-specific code
}
void store(T v, memory_order order=memory_order_seq_cst) volatile
{
// platform-specific code
}
private:
volatile T i;
};
As noted above, it will usually suffice to specialize on the second
template argument, indicating the size of the data type in bytes.
[section:automatic_buildup Templates for automatic build-up]
Often only a portion of the required operations can be
usefully mapped to machine instructions. Several helper template
classes are provided that can automatically synthesize missing methods to
complete an implementation.
At the minimum, an implementor must provide the
[^load], [^store],
[^compare_exchange_weak] and
[^is_lock_free] methods:
[c++]
template<typename T>
class my_atomic_32 {
public:
my_atomic_32() {}
my_atomic_32(T initial_value) : value(initial_value) {}
T load(memory_order order=memory_order_seq_cst) volatile const
{
// platform-specific code
}
void store(T new_value, memory_order order=memory_order_seq_cst) volatile
{
// platform-specific code
}
bool compare_exchange_weak(T &expected, T desired,
memory_order success_order,
memory_order_failure_order) volatile
{
// platform-specific code
}
bool is_lock_free() const volatile {return true;}
protected:
// typedef is required for classes inheriting from this
typedef T integral_type;
private:
T value;
};
The template [^boost::detail::atomic::build_atomic_from_minimal]
can then take care of the rest:
[c++]
template<typename T>
class platform_atomic_integral<T, 4>
: public boost::detail::atomic::build_atomic_from_minimal<my_atomic_32<T> >
{
public:
typedef build_atomic_from_minimal<my_atomic_32<T> > super;
explicit platform_atomic_integral(T v) : super(v) {}
platform_atomic_integral(void) {}
};
There are several helper classes to assist in building "complete"
atomic implementations from different starting points:
* [^build_atomic_from_minimal] requires
* [^load]
* [^store]
* [^compare_exchange_weak] (4-operand version)
* [^build_atomic_from_exchange] requires
* [^load]
* [^store]
* [^compare_exchange_weak] (4-operand version)
* [^compare_exchange_strong] (4-operand version)
* [^exchange]
* [^build_atomic_from_add] requires
* [^load]
* [^store]
* [^compare_exchange_weak] (4-operand version)
* [^compare_exchange_strong] (4-operand version)
* [^exchange]
* [^fetch_add]
* [^build_atomic_from_typical] (<I>supported on gcc only</I>) requires
* [^load]
* [^store]
* [^compare_exchange_weak] (4-operand version)
* [^compare_exchange_strong] (4-operand version)
* [^exchange]
* [^fetch_add_var] (protected method)
* [^fetch_inc] (protected method)
* [^fetch_dec] (protected method)
This will generate a [^fetch_add] method
that calls [^fetch_inc]/[^fetch_dec]
when the given parameter is a compile-time constant
equal to +1 or -1 respectively, and [^fetch_add_var]
in all other cases. This provides a mechanism for
optimizing the extremely common case of an atomic
variable being used as a counter.
The prototypes for these methods to be implemented is:
[c++]
template<typename T>
class my_atomic {
public:
T fetch_inc(memory_order order) volatile;
T fetch_dec(memory_order order) volatile;
T fetch_add_var(T counter, memory_order order) volatile;
};
These helper templates are defined in [^boost/atomic/detail/builder.hpp].
[endsect]
[section:automatic_buildup_small Build sub-word-sized atomic data types]
There is one other helper template that can build sub-word-sized
atomic data types even though the underlying architecture allows
only word-sized atomic operations:
[c++]
template<typename T>
class platform_atomic_integral<T, 1> :
public build_atomic_from_larger_type<my_atomic_32<uint32_t>, T>
{
public:
typedef build_atomic_from_larger_type<my_atomic_32<uint32_t>, T> super;
explicit platform_atomic_integral(T v) : super(v) {}
platform_atomic_integral(void) {}
};
The above would create an atomic data type of 1 byte size, and
use masking and shifts to map it to 32-bit atomic operations.
The base type must implement [^load], [^store]
and [^compare_exchange_weak] for this to work.
[endsect]
[section:other_sizes Atomic data types for unusual object sizes]
In unusual circumstances, an implementor may also opt to specialize
[^public boost::detail::atomic::platform_atomic<T,S=sizeof(T)>]
to provide support for atomic objects not fitting an integral size.
If you do that, keep the following things in mind:
* There is no reason to ever do this for object sizes
of 1, 2, 4 and 8
* Only the following methods need to be implemented:
* [^load]
* [^store]
* [^compare_exchange_weak] (4-operand version)
* [^compare_exchange_strong] (4-operand version)
* [^exchange]
The type of the data to be stored in the atomic
variable (template parameter [^T])
is exposed to this class, and the type may have
overloaded assignment and comparison operators --
using these overloaded operators however will result
in an error. The implementor is responsible for
accessing the objects in a way that does not
invoke either of these operators (using e.g.
[^memcpy] or type-casts).
[endsect]
[endsect]
[section:platform_atomic_fences Fences]
Platform implementors need to provide a function performing
the action required for [funcref boost::atomic_thread_fence atomic_thread_fence]
(the fallback implementation will just perform an atomic operation
on an integer object). This is achieved by specializing the
[^boost::detail::atomic::platform_atomic_thread_fence] template
function in the following way:
[c++]
template<>
void platform_atomic_thread_fence(memory_order order)
{
// platform-specific code here
}
[endsect]
[section:platform_atomic_puttogether Putting it altogether]
The template specializations should be put into a header file
in the [^boost/atomic/detail] directory, preferably
specifying supported compiler and architecture in its name.
The file [^boost/atomic/detail/platform.hpp] must
subsequently be modified to conditionally include the new
header.
[endsect]
|