diff --git a/sycl/plugins/level_zero/pi_level_zero.hpp b/sycl/plugins/level_zero/pi_level_zero.hpp index 820bbb5d7a2a0..23c1ca9ee5837 100644 --- a/sycl/plugins/level_zero/pi_level_zero.hpp +++ b/sycl/plugins/level_zero/pi_level_zero.hpp @@ -65,101 +65,6 @@ template <> uint32_t inline pi_cast(uint64_t Value) { return CastedValue; } -// The wrapper for immutable Level-Zero data. -// The data is initialized only once at first access (via ->) with the -// initialization function provided in Init. All subsequent access to -// the data just returns the already stored data. -// -template struct ZeCache : private T { - // The initialization function takes a reference to the data - // it is going to initialize, since it is private here in - // order to disallow access other than through "->". - // - using InitFunctionType = std::function; - InitFunctionType Compute{nullptr}; - bool Computed{false}; - pi_mutex ZeCacheMutex; - - ZeCache() : T{} {} - - // Access to the fields of the original T data structure. - T *operator->() { - std::unique_lock Lock(ZeCacheMutex); - if (!Computed) { - Compute(*this); - Computed = true; - } - return this; - } -}; - -// This wrapper around std::atomic is created to limit operations with reference -// counter and to make allowed operations more transparent in terms of -// thread-safety in the plugin. increment() and load() operations do not need a -// mutex guard around them since the underlying data is already atomic. -// decrementAndTest() method is used to guard a code which needs to be -// executed when object's ref count becomes zero after release. This method also -// doesn't need a mutex guard because decrement operation is atomic and only one -// thread can reach ref count equal to zero, i.e. only a single thread can pass -// through this check. -struct ReferenceCounter { - ReferenceCounter() : RefCount{1} {} - - // Reset the counter to the initial value. - void reset() { RefCount = 1; } - - // Used when retaining an object. - void increment() { RefCount++; } - - // Supposed to be used in pi*GetInfo* methods where ref count value is - // requested. - pi_uint32 load() { return RefCount.load(); } - - // This method allows to guard a code which needs to be executed when object's - // ref count becomes zero after release. It is important to notice that only a - // single thread can pass through this check. This is true because of several - // reasons: - // 1. Decrement operation is executed atomically. - // 2. It is not allowed to retain an object after its refcount reaches zero. - // 3. It is not allowed to release an object more times than the value of - // the ref count. - // 2. and 3. basically means that we can't use an object at all as soon as its - // refcount reaches zero. Using this check guarantees that code for deleting - // an object and releasing its resources is executed once by a single thread - // and we don't need to use any mutexes to guard access to this object in the - // scope after this check. Of course if we access another objects in this code - // (not the one which is being deleted) then access to these objects must be - // guarded, for example with a mutex. - bool decrementAndTest() { return --RefCount == 0; } - -private: - std::atomic RefCount; -}; - -// Base class to store common data -struct _pi_object { - _pi_object() : RefCount{} {} - - // Level Zero doesn't do the reference counting, so we have to do. - // Must be atomic to prevent data race when incrementing/decrementing. - ReferenceCounter RefCount; - - // This mutex protects accesses to all the non-const member variables. - // Exclusive access is required to modify any of these members. - // - // To get shared access to the object in a scope use std::shared_lock: - // std::shared_lock Lock(Obj->Mutex); - // To get exclusive access to the object in a scope use std::scoped_lock: - // std::scoped_lock Lock(Obj->Mutex); - // - // If several pi objects are accessed in a scope then each object's mutex must - // be locked. For example, to get write access to Obj1 and Obj2 and read - // access to Obj3 in a scope use the following approach: - // std::shared_lock Obj3Lock(Obj3->Mutex, std::defer_lock); - // std::scoped_lock LockAll(Obj1->Mutex, Obj2->Mutex, Obj3Lock); - pi_shared_mutex Mutex; -}; - // Record for a memory allocation. This structure is used to keep information // for each memory allocation. struct MemAllocRecord : _pi_object { diff --git a/sycl/plugins/unified_runtime/ur/ur.hpp b/sycl/plugins/unified_runtime/ur/ur.hpp index 89bd0551d65de..ef669f27920fb 100644 --- a/sycl/plugins/unified_runtime/ur/ur.hpp +++ b/sycl/plugins/unified_runtime/ur/ur.hpp @@ -8,7 +8,9 @@ #pragma once #include +#include #include +#include #include #include #include @@ -99,6 +101,100 @@ class SpinLock { std::atomic_flag MLock = ATOMIC_FLAG_INIT; }; +// The wrapper for immutable data. +// The data is initialized only once at first access (via ->) with the +// initialization function provided in Init. All subsequent access to +// the data just returns the already stored data. +// +template struct ZeCache : private T { + // The initialization function takes a reference to the data + // it is going to initialize, since it is private here in + // order to disallow access other than through "->". + // + using InitFunctionType = std::function; + InitFunctionType Compute{nullptr}; + bool Computed{false}; + pi_mutex ZeCacheMutex; + + ZeCache() : T{} {} + + // Access to the fields of the original T data structure. + T *operator->() { + std::unique_lock Lock(ZeCacheMutex); + if (!Computed) { + Compute(*this); + Computed = true; + } + return this; + } +}; + +// This wrapper around std::atomic is created to limit operations with reference +// counter and to make allowed operations more transparent in terms of +// thread-safety in the plugin. increment() and load() operations do not need a +// mutex guard around them since the underlying data is already atomic. +// decrementAndTest() method is used to guard a code which needs to be +// executed when object's ref count becomes zero after release. This method also +// doesn't need a mutex guard because decrement operation is atomic and only one +// thread can reach ref count equal to zero, i.e. only a single thread can pass +// through this check. +struct ReferenceCounter { + ReferenceCounter() : RefCount{1} {} + + // Reset the counter to the initial value. + void reset() { RefCount = 1; } + + // Used when retaining an object. + void increment() { RefCount++; } + + // Supposed to be used in pi*GetInfo* methods where ref count value is + // requested. + uint32_t load() { return RefCount.load(); } + + // This method allows to guard a code which needs to be executed when object's + // ref count becomes zero after release. It is important to notice that only a + // single thread can pass through this check. This is true because of several + // reasons: + // 1. Decrement operation is executed atomically. + // 2. It is not allowed to retain an object after its refcount reaches zero. + // 3. It is not allowed to release an object more times than the value of + // the ref count. + // 2. and 3. basically means that we can't use an object at all as soon as its + // refcount reaches zero. Using this check guarantees that code for deleting + // an object and releasing its resources is executed once by a single thread + // and we don't need to use any mutexes to guard access to this object in the + // scope after this check. Of course if we access another objects in this code + // (not the one which is being deleted) then access to these objects must be + // guarded, for example with a mutex. + bool decrementAndTest() { return --RefCount == 0; } + +private: + std::atomic RefCount; +}; + +// Base class to store common data +struct _pi_object { + _pi_object() : RefCount{} {} + + // Must be atomic to prevent data race when incrementing/decrementing. + ReferenceCounter RefCount; + + // This mutex protects accesses to all the non-const member variables. + // Exclusive access is required to modify any of these members. + // + // To get shared access to the object in a scope use std::shared_lock: + // std::shared_lock Lock(Obj->Mutex); + // To get exclusive access to the object in a scope use std::scoped_lock: + // std::scoped_lock Lock(Obj->Mutex); + // + // If several pi objects are accessed in a scope then each object's mutex must + // be locked. For example, to get write access to Obj1 and Obj2 and read + // access to Obj3 in a scope use the following approach: + // std::shared_lock Obj3Lock(Obj3->Mutex, std::defer_lock); + // std::scoped_lock LockAll(Obj1->Mutex, Obj2->Mutex, Obj3Lock); + pi_shared_mutex Mutex; +}; + // Helper for one-liner validation #define PI_ASSERT(condition, error) \ if (!(condition)) \