AtomicQueue.h
4.93 KB
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
#pragma once
#include "UnityPlatformConfigure.h"
#include "ExtendedAtomicTypes.h"
#include "AtomicNode.h"
UNITY_PLATFORM_BEGIN_NAMESPACE;
#if ATOMIC_HAS_QUEUE
// clang has arm-specific bultins to implement LL/SC
// NB the common pattern in implementation *there* seems to be chain of ifdef-s (not if-s!). So keep it same
// if you compare arm+clang specific impl to generic cpp impl, you will notice that stricter memory ordering is used.
// It was conscious decision. Performance-wise it is on-par (or faster) than generic cpp code anyway
// some whining about DCAS define and its usage: AtomicQueue/AtomicStack do actually use DCAS
// as in second "word" in atomic_word2 is for counter to avoid ABA problem
// at the same time, in AtomicList atomic_word2 is used NOT for DCAS
// as its second word is "tag" which has some meaning to the outside code (AtomicQueue),
// so it is actually "please use two words atomically"
// as opposed to "please use one word atomically and use second as implementation detail to fight ABA problem"
// that means that for AtomicQueue/AtomicStack we can easily do specific impl with LL/SC
// but for AtomicList we need to use atomic_word2
#if (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) && UNITY_ATOMIC_USE_CLANG_ATOMICS
#define UNITY_ATOMICQUEUE_USE_CLANG_ARM_ATOMICS 1
#endif
// A generic lockfree stack.
// Any thread can Push / Pop nodes to the stack.
// The stack is lockfree and highly optimized. It has different implementations for different architectures.
// On intel / arm it is built with double CAS:
// http://en.wikipedia.org/wiki/Double_compare-and-swap
// On PPC it is built on LL/SC:
// http://en.wikipedia.org/wiki/Load-link/store-conditional
class AtomicStack
{
#if defined(ATOMIC_HAS_DCAS)
volatile atomic_word2 _top;
#else
volatile atomic_word _top;
#endif
public:
AtomicStack();
~AtomicStack();
int IsEmpty() const;
void Push(AtomicNode *node);
void PushAll(AtomicNode *first, AtomicNode *last);
AtomicNode *Pop();
AtomicNode *PopAll();
};
AtomicStack* CreateAtomicStack();
void DestroyAtomicStack(AtomicStack* s);
// A generic lockfree queue FIFO queue.
// Any thread can Enqueue / Dequeue in parallel.
// We do guarantee that all 3 data pointer are the same after dequeuing.
//
// But when pushing / popping a node there is no guarantee that the pointer to the AtomicNode is the same.
// Enqueue adds node to the head, and Dequeue pops it from the tail.
// Implementation relies on dummy head node which allow to modify next pointer atomically.
// Thus Dequeue pops not the enqueued node, but the next one.
// Empty: [ head ] [next] [ tail ]
// dummy 0 dummy
// Enqueue: [ head ] [next] [next] [ tail ]
// node1 dummy 0 dummy
// Dequeue: [ head ] [next] [ tail ] -> dummy dequeued, but with node1 data[3]
// node1 0 node1
// Make sure to destroy nodes consistently.
// The queue is lockfree and highly optimized. It has different implementations for different architectures.
// On intel / arm it is built with double CAS:
// http://en.wikipedia.org/wiki/Double_compare-and-swap
// On PPC it is built on LL/SC:
// http://en.wikipedia.org/wiki/Load-link/store-conditional
class AtomicQueue
{
#if defined(ATOMIC_HAS_DCAS)
volatile atomic_word2 _tail;
#else
volatile atomic_word _tail;
#endif
volatile atomic_word _head;
public:
AtomicQueue();
~AtomicQueue();
int IsEmpty() const;
void Enqueue(AtomicNode *node);
void EnqueueAll(AtomicNode *first, AtomicNode *last);
AtomicNode *Dequeue();
};
AtomicQueue* CreateAtomicQueue();
void DestroyAtomicQueue(AtomicQueue* s);
#elif IL2CPP_SUPPORT_THREADS
#if IL2CPP_TARGET_JAVASCRIPT
// Provide a dummy implementation for Emscripten that lets us build, but won't
// work at runtime.
class AtomicStack
{
public:
AtomicStack() {}
int IsEmpty() const
{
return 1;
}
void Push(AtomicNode *node)
{
}
void PushAll(AtomicNode *first, AtomicNode *last)
{
}
AtomicNode *Pop()
{
return NULL;
}
AtomicNode *PopAll()
{
return NULL;
}
};
#else
#error Platform is missing atomic queue implementation
#endif // IL2CPP_TARGET_JAVASCRIPT
#endif
//
// Special concurrent list for JobQueue
// This code is not meant to be general purpose and should not be used outside of the job queue.
class AtomicList
{
#if defined(ATOMIC_HAS_DCAS) || defined(UNITY_ATOMICQUEUE_USE_CLANG_ARM_ATOMICS)
volatile atomic_word2 _top;
#else
volatile atomic_word _top;
volatile atomic_word _ver;
#endif
public:
void Init();
atomic_word Tag();
AtomicNode *Peek();
AtomicNode *Load(atomic_word &tag);
AtomicNode *Clear(AtomicNode *old, atomic_word tag);
bool Add(AtomicNode *first, AtomicNode *last, atomic_word tag);
AtomicNode* Touch(atomic_word tag);
void Reset(AtomicNode *node, atomic_word tag);
};
UNITY_PLATFORM_END_NAMESPACE;