Skip to content

Commit ec5aaee

Browse files
committed
Add std.atomic support
1 parent d6af824 commit ec5aaee

File tree

1 file changed

+316
-0
lines changed

1 file changed

+316
-0
lines changed

std/atomic.d

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
/**
2+
* The atomic module provides atomic struct support for lock-free
3+
* concurrent programming.
4+
*
5+
* Copyright: Copyright Roy David Margalit 2022 - 2025.
6+
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7+
* Authors: Roy David Margalit
8+
* Source: $(DRUNTIMESRC core/_atomic.d)
9+
*/
10+
module std.atomic;
11+
12+
/** Atomic data like std::atomic
13+
14+
Params:
15+
T = Integral type or pointer for atomic operations
16+
17+
Example:
18+
-------------------------
19+
__gshared Atomic!int a;
20+
assert(a == 0);
21+
assert(a++ == 0)
22+
assert(a == 1);
23+
-------------------------
24+
25+
*/
26+
struct Atomic(T)
27+
if (__traits(isIntegral, T) || isPointer!T)
28+
{
29+
import core.atomic : atomicLoad, atomicStore, atomicExchange, atomicFetchAdd,
30+
atomicFetchSub, atomicCas = cas, atomicCasWeak = casWeak, atomicOp;
31+
32+
private T val;
33+
34+
/// Constructor
35+
this(T init) shared
36+
{
37+
val.atomicStore(init);
38+
}
39+
40+
private shared(T)* ptr() shared
41+
{
42+
return &val;
43+
}
44+
45+
/** Load the value from the atomic location with SC access
46+
Params:
47+
mo = Memory order
48+
49+
Returns: The stored value
50+
*/
51+
T load(MemoryOrder mo = MemoryOrder.seq)() shared
52+
{
53+
return val.atomicLoad!(mo.toCore);
54+
}
55+
56+
/// ditto
57+
alias load this;
58+
59+
60+
/** Store the value to the atomic location
61+
Params:
62+
mo = Memory order
63+
newVal = Value to store to atomic
64+
*/
65+
void store(MemoryOrder mo = MemoryOrder.seq)(T newVal) shared
66+
{
67+
return val.atomicStore!(mo.toCore)(newVal);
68+
}
69+
70+
/// Store using SC access
71+
alias opAssign = store;
72+
73+
/** Atomically increment the value
74+
Params:
75+
mo = Memory order
76+
mod = Value to add to atomic
77+
78+
Returns: The old stored value
79+
*/
80+
T fadd(MemoryOrder mo = MemoryOrder.seq)(T mod) shared
81+
{
82+
return atomicFetchAdd!(mo.toCore)(val, mod);
83+
}
84+
85+
/** Atomically decrement the value
86+
Params:
87+
mo = Memory order
88+
mod = Value to decrement from atomic
89+
90+
Returns: The old stored value
91+
*/
92+
T fsub(MemoryOrder mo = MemoryOrder.seq)(T mod) shared
93+
{
94+
return atomicFetchSub!(mo.toCore)(val, mod);
95+
}
96+
97+
/** Atomically swap the value
98+
Params:
99+
mo = Memory order
100+
desired = New value to store
101+
102+
Returns: The old stored value
103+
*/
104+
T exchange(MemoryOrder mo = MemoryOrder.seq)(T desired) shared
105+
{
106+
return atomicExchange!(mo.toCore)(&val, desired);
107+
}
108+
109+
/** Compare and swap
110+
Params:
111+
mo = Memory order on success
112+
fmo = Memory order on failure
113+
oldVal = Expected value to preform the swap
114+
newVal = New value to store if condition holds
115+
116+
Returns: If the value was swapped
117+
*/
118+
bool cas(MemoryOrder mo = MemoryOrder.seq, MemoryOrder fmo = MemoryOrder.seq)(T oldVal, T newVal) shared
119+
{
120+
return atomicCas!(mo.toCore, fmo.toCore)(ptr, oldVal, newVal);
121+
}
122+
123+
/** Compare and swap (May fail even if current value is equal to oldVal)
124+
Params:
125+
mo = Memory order on success
126+
fmo = Memory order on failure
127+
oldVal = Expected value to preform the swap
128+
newVal = New value to store if condition holds
129+
130+
Returns: If the value was swapped
131+
*/
132+
bool casWeak(MemoryOrder mo = MemoryOrder.seq, MemoryOrder fmo = MemoryOrder.seq)(T oldVal,
133+
T newVal) shared
134+
{
135+
return atomicCasWeak!(mo.toCore, fmo.toCore)(ptr, oldVal, newVal);
136+
}
137+
138+
/** Op assign with SC semantics
139+
Params:
140+
op = Assignment operator
141+
rhs = Value to assign
142+
143+
Returns: Computation result
144+
*/
145+
T opOpAssign(string op)(T rhs) shared
146+
{
147+
return val.atomicOp!(op ~ `=`)(rhs);
148+
}
149+
150+
/** Implicit conversion to FADD with SC access
151+
Params:
152+
op = ++
153+
154+
Returns: Pre-incremented value
155+
*/
156+
T opUnary(string op)() shared if (op == `++`)
157+
{
158+
return fadd(1);
159+
}
160+
161+
/** Implicit conversion to FSUB with SC access
162+
Params:
163+
op = --
164+
165+
Returns: Pre-decremented value
166+
*/
167+
T opUnary(string op)() shared if (op == `--`)
168+
{
169+
return fsub(1);
170+
}
171+
172+
/** Dereference atomic pointer with SC access
173+
Params:
174+
op = *
175+
176+
Returns: Reference to the pointed location
177+
*/
178+
auto ref opUnary(string op)() shared if (op == `*`)
179+
{
180+
return *(load);
181+
}
182+
}
183+
184+
static import core.atomic;
185+
/**
186+
* Specifies the memory ordering semantics of an atomic operation.
187+
*
188+
* See_Also:
189+
* $(HTTP en.cppreference.com/w/cpp/atomic/memory_order)
190+
*/
191+
enum MemoryOrder
192+
{
193+
/**
194+
* Corresponds to $(LINK2 https://llvm.org/docs/Atomics.html#monotonic, LLVM AtomicOrdering.Monotonic)
195+
* and C++11/C11 `memory_order_relaxed`.
196+
*/
197+
rlx = cast(int) core.atomic.MemoryOrder.raw,
198+
/**
199+
* Corresponds to $(LINK2 https://llvm.org/docs/Atomics.html#acquire, LLVM AtomicOrdering.Acquire)
200+
* and C++11/C11 `memory_order_acquire`.
201+
*/
202+
acq = cast(int) core.atomic.MemoryOrder.acq,
203+
/**
204+
* Corresponds to $(LINK2 https://llvm.org/docs/Atomics.html#release, LLVM AtomicOrdering.Release)
205+
* and C++11/C11 `memory_order_release`.
206+
*/
207+
rel = cast(int) core.atomic.MemoryOrder.rel,
208+
/**
209+
* Corresponds to $(LINK2 https://llvm.org/docs/Atomics.html#acquirerelease, LLVM AtomicOrdering.AcquireRelease)
210+
* and C++11/C11 `memory_order_acq_rel`.
211+
*/
212+
acq_rel = cast(int) core.atomic.MemoryOrder.acq_rel,
213+
/**
214+
* Corresponds to $(LINK2 https://llvm.org/docs/Atomics.html#sequentiallyconsistent, LLVM AtomicOrdering.SequentiallyConsistent)
215+
* and C++11/C11 `memory_order_seq_cst`.
216+
*/
217+
seq = cast(int) core.atomic.MemoryOrder.seq,
218+
}
219+
220+
private auto toCore(MemoryOrder mo)
221+
{
222+
static import core.atomic;
223+
return cast(core.atomic.MemoryOrder) mo;
224+
}
225+
226+
@safe unittest
227+
{
228+
shared Atomic!int a;
229+
assert(a == 0);
230+
assert(a.load == 0);
231+
assert(a.fadd!(MemoryOrder.rlx)(5) == 0);
232+
assert(a.load!(MemoryOrder.acq) == 5);
233+
assert(!a.casWeak(4, 5));
234+
assert(!a.cas(4, 5));
235+
assert(a.cas!(MemoryOrder.rel, MemoryOrder.acq)(5, 4));
236+
assert(a.fsub!(MemoryOrder.acq_rel)(2) == 4);
237+
assert(a.exchange!(MemoryOrder.acq_rel)(3) == 2);
238+
assert(a.load!(MemoryOrder.rlx) == 3);
239+
a.store!(MemoryOrder.rel)(7);
240+
assert(a.load == 7);
241+
a = 32;
242+
assert(a == 32);
243+
a += 5;
244+
assert(a == 37);
245+
assert(a++ == 37);
246+
assert(a == 38);
247+
}
248+
249+
// static array of shared atomics
250+
@safe unittest
251+
{
252+
static shared(Atomic!int)[5] arr;
253+
arr[4] = 4;
254+
assert(arr[4].load == 4);
255+
}
256+
257+
@system unittest
258+
{
259+
import core.thread : Thread;
260+
261+
shared(Atomic!int)[2] arr;
262+
263+
void reltest() @safe
264+
{
265+
arr[0].store!(MemoryOrder.rel)(1);
266+
arr[1].store!(MemoryOrder.rel)(1);
267+
}
268+
269+
void acqtest() @safe
270+
{
271+
while (arr[1].load!(MemoryOrder.acq) != 1) { }
272+
assert(arr[0].load!(MemoryOrder.acq) == 1);
273+
}
274+
275+
auto t1 = new Thread(&acqtest);
276+
auto t2 = new Thread(&reltest);
277+
t2.start;
278+
t1.start;
279+
t2.join;
280+
t1.join;
281+
}
282+
283+
@safe unittest
284+
{
285+
shared Atomic!(shared(int)) a = 5;
286+
assert(a.load == shared(int)(5));
287+
a = 2;
288+
assert(a == 2);
289+
}
290+
291+
@safe unittest
292+
{
293+
shared Atomic!(shared(int)*) ptr = new shared(int);
294+
*ptr.load!(MemoryOrder.rlx)() = 5;
295+
assert(*ptr.load == 5);
296+
*(ptr.load) = 42;
297+
assert(*ptr.load == 42);
298+
}
299+
300+
@safe unittest
301+
{
302+
shared Atomic!(shared(int)*) ptr = new shared(int);
303+
*ptr = 5;
304+
assert(*ptr == 5);
305+
*ptr = 42;
306+
assert(*ptr == 42);
307+
}
308+
309+
@safe unittest
310+
{
311+
//shared Atomic!(shared(Atomic!(int))*) ptr = new shared(Atomic!int);
312+
}
313+
314+
private enum bool isAggregateType(T) = is(T == struct) || is(T == union)
315+
|| is(T == class) || is(T == interface);
316+
private enum bool isPointer(T) = is(T == U*, U) && !isAggregateType!T;

0 commit comments

Comments
 (0)