@@ -99,6 +99,101 @@ class SpinLock {
99
99
std::atomic_flag MLock = ATOMIC_FLAG_INIT;
100
100
};
101
101
102
+ // The wrapper for immutable Level-Zero data.
103
+ // The data is initialized only once at first access (via ->) with the
104
+ // initialization function provided in Init. All subsequent access to
105
+ // the data just returns the already stored data.
106
+ //
107
+ template <class T > struct ZeCache : private T {
108
+ // The initialization function takes a reference to the data
109
+ // it is going to initialize, since it is private here in
110
+ // order to disallow access other than through "->".
111
+ //
112
+ using InitFunctionType = std::function<void (T &)>;
113
+ InitFunctionType Compute{nullptr };
114
+ bool Computed{false };
115
+ pi_mutex ZeCacheMutex;
116
+
117
+ ZeCache () : T{} {}
118
+
119
+ // Access to the fields of the original T data structure.
120
+ T *operator ->() {
121
+ std::unique_lock<pi_mutex> Lock (ZeCacheMutex);
122
+ if (!Computed) {
123
+ Compute (*this );
124
+ Computed = true ;
125
+ }
126
+ return this ;
127
+ }
128
+ };
129
+
130
+ // This wrapper around std::atomic is created to limit operations with reference
131
+ // counter and to make allowed operations more transparent in terms of
132
+ // thread-safety in the plugin. increment() and load() operations do not need a
133
+ // mutex guard around them since the underlying data is already atomic.
134
+ // decrementAndTest() method is used to guard a code which needs to be
135
+ // executed when object's ref count becomes zero after release. This method also
136
+ // doesn't need a mutex guard because decrement operation is atomic and only one
137
+ // thread can reach ref count equal to zero, i.e. only a single thread can pass
138
+ // through this check.
139
+ struct ReferenceCounter {
140
+ ReferenceCounter () : RefCount{1 } {}
141
+
142
+ // Reset the counter to the initial value.
143
+ void reset () { RefCount = 1 ; }
144
+
145
+ // Used when retaining an object.
146
+ void increment () { RefCount++; }
147
+
148
+ // Supposed to be used in pi*GetInfo* methods where ref count value is
149
+ // requested.
150
+ pi_uint32 load () { return RefCount.load (); }
151
+
152
+ // This method allows to guard a code which needs to be executed when object's
153
+ // ref count becomes zero after release. It is important to notice that only a
154
+ // single thread can pass through this check. This is true because of several
155
+ // reasons:
156
+ // 1. Decrement operation is executed atomically.
157
+ // 2. It is not allowed to retain an object after its refcount reaches zero.
158
+ // 3. It is not allowed to release an object more times than the value of
159
+ // the ref count.
160
+ // 2. and 3. basically means that we can't use an object at all as soon as its
161
+ // refcount reaches zero. Using this check guarantees that code for deleting
162
+ // an object and releasing its resources is executed once by a single thread
163
+ // and we don't need to use any mutexes to guard access to this object in the
164
+ // scope after this check. Of course if we access another objects in this code
165
+ // (not the one which is being deleted) then access to these objects must be
166
+ // guarded, for example with a mutex.
167
+ bool decrementAndTest () { return --RefCount == 0 ; }
168
+
169
+ private:
170
+ std::atomic<pi_uint32> RefCount;
171
+ };
172
+
173
+ // Base class to store common data
174
+ struct _pi_object {
175
+ _pi_object () : RefCount{} {}
176
+
177
+ // Level Zero doesn't do the reference counting, so we have to do.
178
+ // Must be atomic to prevent data race when incrementing/decrementing.
179
+ ReferenceCounter RefCount;
180
+
181
+ // This mutex protects accesses to all the non-const member variables.
182
+ // Exclusive access is required to modify any of these members.
183
+ //
184
+ // To get shared access to the object in a scope use std::shared_lock:
185
+ // std::shared_lock Lock(Obj->Mutex);
186
+ // To get exclusive access to the object in a scope use std::scoped_lock:
187
+ // std::scoped_lock Lock(Obj->Mutex);
188
+ //
189
+ // If several pi objects are accessed in a scope then each object's mutex must
190
+ // be locked. For example, to get write access to Obj1 and Obj2 and read
191
+ // access to Obj3 in a scope use the following approach:
192
+ // std::shared_lock Obj3Lock(Obj3->Mutex, std::defer_lock);
193
+ // std::scoped_lock LockAll(Obj1->Mutex, Obj2->Mutex, Obj3Lock);
194
+ pi_shared_mutex Mutex;
195
+ };
196
+
102
197
// Helper for one-liner validation
103
198
#define PI_ASSERT (condition, error ) \
104
199
if (!(condition)) \
0 commit comments