diff --git a/sycl/doc/EnvironmentVariables.md b/sycl/doc/EnvironmentVariables.md index fc341b5abff37..b4728ca2f9216 100644 --- a/sycl/doc/EnvironmentVariables.md +++ b/sycl/doc/EnvironmentVariables.md @@ -11,6 +11,7 @@ compiler and runtime. | `SYCL_DEVICE_TYPE` (deprecated) | CPU, GPU, ACC, HOST | Force SYCL to use the specified device type. If unset, default selection rules are applied. If set to any unlisted value, this control has no effect. If the requested device type is not found, a `cl::sycl::runtime_error` exception is thrown. If a non-default device selector is used, a device must satisfy both the selector and this control to be chosen. This control only has effect on devices created with a selector. We are planning to deprecate `SYCL_DEVICE_TYPE` environment variable in the future. The specific grace period is not decided yet. Please use the new env var `SYCL_DEVICE_FILTER` instead. | | `SYCL_DEVICE_FILTER` | `backend:device_type:device_num` | See Section [`SYCL_DEVICE_FILTER`](#sycl_device_filter) below. | | `SYCL_DEVICE_ALLOWLIST` | See [below](#sycl_device_allowlist) | Filter out devices that do not match the pattern specified. `BackendName` accepts `host`, `opencl`, `level_zero` or `cuda`. `DeviceType` accepts `host`, `cpu`, `gpu` or `acc`. `DeviceVendorId` accepts uint32_t in hex form (`0xXYZW`). `DriverVersion`, `PlatformVersion`, `DeviceName` and `PlatformName` accept regular expression. Special characters, such as parenthesis, must be escaped. DPC++ runtime will select only those devices which satisfy provided values above and regex. More than one device can be specified using the piping symbol "\|".| +| `SYCL_DISABLE_AUXILIARY_RESOURCE_POOL` | Any(\*) | Disables the auxiliary resource pool, preventing the reuse of device resources by operations like reductions. | | `SYCL_DISABLE_PARALLEL_FOR_RANGE_ROUNDING` | Any(\*) | Disables automatic rounding-up of `parallel_for` invocation ranges. | | `SYCL_CACHE_DIR` | Path | Path to persistent cache root directory. Default values are `%AppData%\libsycl_cache` for Windows and `$XDG_CACHE_HOME/libsycl_cache` on Linux, if `XDG_CACHE_HOME` is not set then `$HOME/.cache/libsycl_cache`. When none of the environment variables are set SYCL persistent cache is disabled. | | `SYCL_CACHE_DISABLE_PERSISTENT (deprecated)` | Any(\*) | Has no effect. | diff --git a/sycl/include/CL/sycl/buffer.hpp b/sycl/include/CL/sycl/buffer.hpp index 403a30586ec37..5bde082edfbeb 100644 --- a/sycl/include/CL/sycl/buffer.hpp +++ b/sycl/include/CL/sycl/buffer.hpp @@ -24,6 +24,8 @@ class queue; template class range; namespace detail { +template struct ManagedResource; + template buffer make_buffer_helper(pi_native_handle Handle, const context &Ctx, event Evt) { @@ -532,6 +534,8 @@ class buffer { template friend buffer detail::make_buffer_helper(pi_native_handle, const context &, event); + template friend struct detail::ManagedResource; + range Range; // Offset field specifies the origin of the sub buffer inside the parent // buffer diff --git a/sycl/include/CL/sycl/detail/buffer_impl.hpp b/sycl/include/CL/sycl/detail/buffer_impl.hpp index 09595d31bae52..8f71b435bdd13 100644 --- a/sycl/include/CL/sycl/detail/buffer_impl.hpp +++ b/sycl/include/CL/sycl/detail/buffer_impl.hpp @@ -153,6 +153,13 @@ class __SYCL_EXPORT buffer_impl final : public SYCLMemObjT { : BaseT(MemObject, SyclContext, SizeInBytes, std::move(AvailableEvent), std::move(Allocator)) {} + buffer_impl(RT::PiMem MemObject, const context &SyclContext, + const size_t SizeInBytes, + std::unique_ptr Allocator, + event AvailableEvent) + : BaseT(MemObject, SyclContext, SizeInBytes, std::move(AvailableEvent), + std::move(Allocator)) {} + void *allocateMem(ContextImplPtr Context, bool InitFromUserData, void *HostPtr, RT::PiEvent &OutEventToWait) override; void constructorNotification(const detail::code_location &CodeLoc, diff --git a/sycl/include/CL/sycl/detail/resource_pool.hpp b/sycl/include/CL/sycl/detail/resource_pool.hpp new file mode 100644 index 0000000000000..729e065c9f1d0 --- /dev/null +++ b/sycl/include/CL/sycl/detail/resource_pool.hpp @@ -0,0 +1,268 @@ +//==------------- resource_pool.hpp - USM resource pool ---------*- C++-*---==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include +#include + +#include +#include +#include +#include + +__SYCL_INLINE_NAMESPACE(cl) { +namespace sycl { +namespace detail { + +// Forward declarations +class context_impl; +class queue_impl; +class device_impl; +class platform_impl; +class ResourcePool; + +struct __SYCL_EXPORT ManagedResourceBase { + ManagedResourceBase() = delete; + virtual ~ManagedResourceBase(); + +protected: + ManagedResourceBase(size_t Size, RT::PiMem Mem, ResourcePool *Origin) + : MSize(Size), MMem(Mem), MOrigin(Origin) {} + + /// Size of the memory in the managed resource. + size_t MSize; + + /// Memory associated with the managed resource. + RT::PiMem MMem; + + /// The resource pool the resource was taken from. + ResourcePool *MOrigin; + + friend class ResourcePool; +}; + +template +struct ManagedResource : public ManagedResourceBase { + ManagedResource() = delete; + + /// Gets the buffer associated with the resource. + /// + /// \return the buffer associated with the resource. + buffer &getBuffer() { return MBuffer; } + +private: + /// Creates a buffer implementation. + /// + /// \param Size is the size of the memory passed to the buffer. + /// \param Mem is the memory for the buffer. + /// \param ContextImplPtr is the context implementation the memory is + /// associated with. + /// \param AvailableEvent is an event tied to the availability of the data in + /// the memory. + /// \return a shared pointer to the resulting buffer implementation. + static std::shared_ptr + createBufferImpl(size_t Size, RT::PiMem Mem, + const std::shared_ptr &ContextImplPtr, + event AvailableEvent) { + return std::make_shared( + Mem, createSyclObjFromImpl(ContextImplPtr), Size, + make_unique_ptr>(), + AvailableEvent); + } + + ManagedResource(size_t Size, RT::PiMem Mem, ResourcePool *Origin, + range Range, + const std::shared_ptr &ContextImplPtr, + event AvailableEvent = event{}) + : ManagedResourceBase(Size, Mem, Origin), + MBuffer(createBufferImpl(Size, Mem, ContextImplPtr, AvailableEvent), + Range, 0, + /*IsSubBuffer=*/false) {} + + // Constructor for when pool is disabled. + ManagedResource(ResourcePool *Origin, range Range, T *DataPtr) + : ManagedResourceBase(0, nullptr, Origin), MBuffer(DataPtr, Range) {} + + /// Buffer owned by the resource. + buffer MBuffer; + + friend class ResourcePool; +}; + +class __SYCL_EXPORT ResourcePool { +private: + /// Free entry in the resource pool. This represents an allocation owned by + /// the pool that is not currently in use. + struct FreeEntry { + /// Byte size of the free entry. + size_t Size; + /// Memory allocation of the free entry. + RT::PiMem Mem; + }; + + /// Comparison of free entries by size. This is used for fast lookup by size + /// in the pool. + struct FreeEntryCompare { + using is_transparent = void; + bool operator()(FreeEntry const &lhs, FreeEntry const &rhs) const { + return lhs.Size < rhs.Size; + } + bool operator()(FreeEntry const &lhs, size_t rhs) const { + return lhs.Size < rhs; + } + bool operator()(size_t lhs, FreeEntry const &rhs) const { + return lhs < rhs.Size; + } + }; + + /// Extracts a free entry from the pool that fits the size required. If there + /// is no suitable entry, new memory will be allocated. + /// + /// \param Range is the range of the resulting buffer. + /// \param QueueImplPtr is the queue with the context to allocate memory in. + /// \param DataPtr is the pointer to data on the host to initialize the + /// associated memory with. This will only be used if a new entry is + /// allocated. + /// \param IsNewEntry will be set to true if the entry was newly allocated in + /// the pool and false if it was found in the existing free entries in + /// the pool. This is not set if it is nullptr. + /// \return a shared pointer to the new managed resource. + FreeEntry getOrAllocateEntry(const size_t Size, + const std::shared_ptr &QueueImplPtr, + void *DataPtr = nullptr, + bool *IsNewEntry = nullptr); + + /// Extracts a free entry from the pool that fits the size required. If there + /// is no suitable entry, new memory will be allocated. The memory will be + /// initialized with the data given. + /// + /// \param Size is the size of the free entry to find or allocate. + /// \param QueueImplPtr is the queue with the context to allocate memory in. + /// \param DataPtr is the pointer to data on the host to initialize the + /// associated memory with. + /// \param AvailableEvent will be set to an event that is tied to the + /// initialization of the memory. + /// \param IsNewEntry will be set to true if the entry was newly allocated in + /// the pool and false if it was found in the existing free entries in + /// the pool. This is not set if it is nullptr. + /// \return a shared pointer to the new managed resource. + FreeEntry getOrAllocateEntry(const size_t Size, + const std::shared_ptr &QueueImplPtr, + void *DataPtr, event *AvailableEvent, + bool *IsNewEntry = nullptr); + + /// Gets the context implementation associtated with a queue implementation. + /// + /// \param QueueImplPtr is the queue implementation to get the context + /// implementation from. \return the context implementation from the queue + /// implementation. + static const std::shared_ptr & + getQueueContextImpl(const std::shared_ptr &QueueImplPtr); + + using ContextPtr = context_impl *; + +public: + /// Removes and deallocates all free entries currently in the pool. + void clear(); + + ResourcePool(); + ResourcePool(const ResourcePool &) = delete; + ~ResourcePool() { + clear(); + assert(MAllocCount == 0 && "Not all resources have returned to the pool."); + } + + /// Returns true if the resource pool is enabled and false otherwise. + /// + /// \return a boolean value specifying whether the pool is enabled. + bool isEnabled() const { return MIsPoolingEnabled; } + + /// Creates a managed resource from the pool. + /// + /// \param Range is the range of the resulting buffer. + /// \param QueueImplPtr is the queue with the context to allocate memory in. + /// \return a shared pointer to the new managed resource. + template + std::shared_ptr> + getOrAllocateResource(range Range, + const std::shared_ptr &QueueImplPtr) { + // If pool is disabled we return a buffer that will not return to the pool. + if (!MIsPoolingEnabled) + return std::shared_ptr>{ + new ManagedResource(this, Range, nullptr)}; + + // Get or allocate a free entry that fits the requirements. + FreeEntry Entry = + getOrAllocateEntry(Range.size() * sizeof(T), QueueImplPtr); + return std::shared_ptr>{ + new ManagedResource(Entry.Size, Entry.Mem, this, Range, + getQueueContextImpl(QueueImplPtr))}; + } + + /// Creates a managed resource from the pool and sets te data of the + /// associated memory. + /// + /// \param Range is the range of the resulting buffer. + /// \param QueueImplPtr is the queue with the context to allocate memory in. + /// \param DataPtr is the pointer to data on the host to initialize the + /// resource with. This must contain at least the size of Range. + /// \return a shared pointer to the new managed resource. + template + std::shared_ptr> + getOrAllocateResource(range Range, + const std::shared_ptr &QueueImplPtr, + T *DataPtr) { + // If pool is disabled we return a buffer that will not return to the pool. + if (!MIsPoolingEnabled) + return std::shared_ptr>{ + new ManagedResource(this, Range, DataPtr)}; + + // Get or allocate a free entry that fits the requirements. + event AvailableEvent; + FreeEntry Entry = getOrAllocateEntry(Range.size() * sizeof(T), QueueImplPtr, + DataPtr, &AvailableEvent); + return std::shared_ptr>{ + new ManagedResource(Entry.Size, Entry.Mem, this, Range, + getQueueContextImpl(QueueImplPtr), + AvailableEvent)}; + } + +private: + /// Returns a resouce to the pool. + /// + /// \param Size is the size of the resource. + /// \param Mem is the memory of the resource. + void returnResourceToPool(const size_t Size, RT::PiMem Mem) { + std::lock_guard Lock{MMutex}; + MFreeEntries.insert({Size, Mem}); + } + + friend struct ManagedResourceBase; + + /// Is true if the pool is enabled and false otherwise. This is controlled by + /// the SYCL_DISABLE_AUXILIARY_RESOURCE_POOL config. + const bool MIsPoolingEnabled; + + /// The platform associated with the pool. + std::shared_ptr MPlatform; + + /// Counter for allocations done by the pool that are currently alive. This + /// includes managed resources that are currently alive. + size_t MAllocCount = 0; + + /// A set of all free entries in the pool. + std::multiset MFreeEntries; + + /// Mutex protecting access to the pool. + std::mutex MMutex; +}; + +} // namespace detail +} // namespace sycl +} // __SYCL_INLINE_NAMESPACE(cl) diff --git a/sycl/include/CL/sycl/detail/sycl_mem_obj_t.hpp b/sycl/include/CL/sycl/detail/sycl_mem_obj_t.hpp index bd26c70a1d972..e98cbb0a8c970 100644 --- a/sycl/include/CL/sycl/detail/sycl_mem_obj_t.hpp +++ b/sycl/include/CL/sycl/detail/sycl_mem_obj_t.hpp @@ -84,6 +84,10 @@ class __SYCL_EXPORT SYCLMemObjT : public SYCLMemObjI { const size_t SizeInBytes, event AvailableEvent, std::unique_ptr Allocator); + SYCLMemObjT(RT::PiMem MemObject, const context &SyclContext, + const size_t SizeInBytes, event AvailableEvent, + std::unique_ptr Allocator); + SYCLMemObjT(cl_mem MemObject, const context &SyclContext, event AvailableEvent, std::unique_ptr Allocator) diff --git a/sycl/include/CL/sycl/handler.hpp b/sycl/include/CL/sycl/handler.hpp index 3004f438e32b9..7fb04db1dea0e 100644 --- a/sycl/include/CL/sycl/handler.hpp +++ b/sycl/include/CL/sycl/handler.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -476,6 +477,34 @@ class __SYCL_EXPORT handler { /// @param ReduObj is a pointer to object that must be stored. void addReduction(const std::shared_ptr &ReduObj); + /// Gets the resource pool for the context associated with the handler. + /// + /// \return a reference to the resource pool of the underlying context. + detail::ResourcePool &getResourcePool(); + + /// Gets or allocates a new resource from the resource pool. + /// + /// \param Range is the range of the underlying buffer for the resource. + /// \return a shared pointer to the resulting resource. + template + std::shared_ptr> + getOrAllocateResourceFromPool(range Range) { + return getResourcePool().getOrAllocateResource(Range, MQueue); + } + + /// Gets or allocates a new resource from the resource pool and intialize it. + /// + /// \param Range is the range of the underlying buffer for the resource. + /// \param DataPtr is a pointer to the data to initialize the resource with. + /// The data pointed to must be at least the size of range in bytes. + /// \return a shared pointer to the resulting resource. + template + std::shared_ptr> + getOrAllocateResourceFromPool(range Range, T *DataPtr) { + return getResourcePool().getOrAllocateResource(Range, MQueue, + DataPtr); + } + ~handler() = default; bool is_host() { return MIsHost; } diff --git a/sycl/include/sycl/ext/oneapi/reduction.hpp b/sycl/include/sycl/ext/oneapi/reduction.hpp index 1b052f26108cb..9c0b81d6932b4 100644 --- a/sycl/include/sycl/ext/oneapi/reduction.hpp +++ b/sycl/include/sycl/ext/oneapi/reduction.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -655,7 +656,7 @@ class reduction_impl : private reduction_impl_base { accessor getReadAccToPreviousPartialReds(handler &CGH) const { CGH.addReduction(MOutBufPtr); - return {*MOutBufPtr, CGH}; + return {MOutBufPtr->getBuffer(), CGH}; } /// Returns user's USM pointer passed to reduction for editing. @@ -681,9 +682,10 @@ class reduction_impl : private reduction_impl_base { template std::enable_if_t getWriteMemForPartialReds(size_t Size, handler &CGH) { - MOutBufPtr = std::make_shared>(range<1>(Size)); + MOutBufPtr = + CGH.getOrAllocateResourceFromPool(range<1>(Size)); CGH.addReduction(MOutBufPtr); - return createHandlerWiredReadWriteAccessor(CGH, *MOutBufPtr); + return createHandlerWiredReadWriteAccessor(CGH, MOutBufPtr->getBuffer()); } /// Returns an accessor accessing the memory that will hold the reduction @@ -699,9 +701,10 @@ class reduction_impl : private reduction_impl_base { } // Create a new output buffer and return an accessor to it. - MOutBufPtr = std::make_shared>(range<1>(Size)); + MOutBufPtr = + CGH.getOrAllocateResourceFromPool(range<1>(Size)); CGH.addReduction(MOutBufPtr); - return createHandlerWiredReadWriteAccessor(CGH, *MOutBufPtr); + return createHandlerWiredReadWriteAccessor(CGH, MOutBufPtr->getBuffer()); } /// If reduction is initialized with read-write accessor, which does not @@ -717,10 +720,10 @@ class reduction_impl : private reduction_impl_base { auto RWReduVal = std::make_shared(MIdentity); CGH.addReduction(RWReduVal); - MOutBufPtr = std::make_shared>(RWReduVal.get(), range<1>(1)); - MOutBufPtr->set_final_data(); + MOutBufPtr = CGH.getOrAllocateResourceFromPool( + range<1>(1), RWReduVal.get()); CGH.addReduction(MOutBufPtr); - return createHandlerWiredReadWriteAccessor(CGH, *MOutBufPtr); + return createHandlerWiredReadWriteAccessor(CGH, MOutBufPtr->getBuffer()); } accessor(0); CGH.addReduction(CounterMem); - auto CounterBuf = std::make_shared>(CounterMem.get(), 1); - CounterBuf->set_final_data(); + auto CounterBuf = + CGH.getOrAllocateResourceFromPool(range<1>{1}, CounterMem.get()); CGH.addReduction(CounterBuf); - return {*CounterBuf, CGH}; + return {CounterBuf->getBuffer(), CGH}; } bool hasUserDiscardWriteAccessor() { return MDWAcc != nullptr; } @@ -786,7 +789,7 @@ class reduction_impl : private reduction_impl_base { std::shared_ptr MRWAcc; std::shared_ptr MDWAcc; - std::shared_ptr> MOutBufPtr; + std::shared_ptr> MOutBufPtr; /// USM pointer referencing the memory to where the result of the reduction /// must be written. Applicable/used only for USM reductions. diff --git a/sycl/source/CMakeLists.txt b/sycl/source/CMakeLists.txt index b89835c5f4864..a9cb831818988 100644 --- a/sycl/source/CMakeLists.txt +++ b/sycl/source/CMakeLists.txt @@ -157,6 +157,7 @@ set(SYCL_SOURCES "detail/persistent_device_code_cache.cpp" "detail/platform_util.cpp" "detail/reduction.cpp" + "detail/resource_pool.cpp" "detail/sampler_impl.cpp" "detail/stream_impl.cpp" "detail/scheduler/commands.cpp" diff --git a/sycl/source/detail/config.def b/sycl/source/detail/config.def index d0ac3d1c34766..c126911d7e8c8 100644 --- a/sycl/source/detail/config.def +++ b/sycl/source/detail/config.def @@ -17,6 +17,7 @@ CONFIG(SYCL_DEVICE_ALLOWLIST, 1024, __SYCL_DEVICE_ALLOWLIST) CONFIG(SYCL_BE, 16, __SYCL_BE) CONFIG(SYCL_PI_TRACE, 16, __SYCL_PI_TRACE) CONFIG(SYCL_PARALLEL_FOR_RANGE_ROUNDING_TRACE, 16, __SYCL_PARALLEL_FOR_RANGE_ROUNDING_TRACE) +CONFIG(SYCL_DISABLE_AUXILIARY_RESOURCE_POOL, 16, __SYCL_DISABLE_AUXILIARY_RESOURCE_POOL) CONFIG(SYCL_DISABLE_PARALLEL_FOR_RANGE_ROUNDING, 16, __SYCL_DISABLE_PARALLEL_FOR_RANGE_ROUNDING) CONFIG(SYCL_PARALLEL_FOR_RANGE_ROUNDING_PARAMS, 64, __SYCL_PARALLEL_FOR_RANGE_ROUNDING_PARAMS) CONFIG(SYCL_DEVICELIB_NO_FALLBACK, 1, __SYCL_DEVICELIB_NO_FALLBACK) diff --git a/sycl/source/detail/config.hpp b/sycl/source/detail/config.hpp index f0009e0405465..48c6cb0586077 100644 --- a/sycl/source/detail/config.hpp +++ b/sycl/source/detail/config.hpp @@ -217,6 +217,16 @@ template <> class SYCLConfig { } }; +template <> class SYCLConfig { + using BaseT = SYCLConfigBase; + +public: + static bool get() { + static const char *ValStr = BaseT::getRawValue(); + return ValStr != nullptr; + } +}; + template <> class SYCLConfig { using BaseT = SYCLConfigBase; diff --git a/sycl/source/detail/context_impl.cpp b/sycl/source/detail/context_impl.cpp index c10188b83fc15..1a7da2091a16d 100644 --- a/sycl/source/detail/context_impl.cpp +++ b/sycl/source/detail/context_impl.cpp @@ -117,6 +117,8 @@ cl_context context_impl::get() const { bool context_impl::is_host() const { return MHostContext; } context_impl::~context_impl() { + // Clear resource pool before releasing the native context + MAuxiliaryResourcePool.clear(); for (auto LibProg : MCachedLibPrograms) { assert(LibProg.second && "Null program must not be kept in the cache"); getPlugin().call(LibProg.second); diff --git a/sycl/source/detail/context_impl.hpp b/sycl/source/detail/context_impl.hpp index 9049ab1a12760..3d49203aceb37 100644 --- a/sycl/source/detail/context_impl.hpp +++ b/sycl/source/detail/context_impl.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -167,6 +168,11 @@ class context_impl { /// \return a native handle. pi_native_handle getNative() const; + /// Gets the resource pool of the SYCL context. + /// + /// \return a reference to the resource pool. + ResourcePool &getResourcePool() { return MAuxiliaryResourcePool; } + // Returns true if buffer_location property is supported by devices bool isBufferLocationSupported() const; @@ -182,6 +188,7 @@ class context_impl { std::map, RT::PiProgram> MCachedLibPrograms; mutable KernelProgramCache MKernelProgramCache; + ResourcePool MAuxiliaryResourcePool; mutable PropertySupport MSupportBufferLocationByDevices; }; diff --git a/sycl/source/detail/device_image_impl.hpp b/sycl/source/detail/device_image_impl.hpp index 4886c5c8ae25a..c08492a09d833 100644 --- a/sycl/source/detail/device_image_impl.hpp +++ b/sycl/source/detail/device_image_impl.hpp @@ -194,14 +194,12 @@ class device_image_impl { RT::PiMem &get_spec_const_buffer_ref() noexcept { std::lock_guard Lock{MSpecConstAccessMtx}; if (nullptr == MSpecConstsBuffer && !MSpecConstsBlob.empty()) { - const detail::plugin &Plugin = getSyclObjImpl(MContext)->getPlugin(); // Uses PI_MEM_FLAGS_HOST_PTR_COPY instead of PI_MEM_FLAGS_HOST_PTR_USE // since post-enqueue cleanup might trigger destruction of // device_image_impl and, as a result, destruction of MSpecConstsBlob // while MSpecConstsBuffer is still in use. // TODO consider changing the lifetime of device_image_impl instead - memBufferCreateHelper(Plugin, - detail::getSyclObjImpl(MContext)->getHandleRef(), + memBufferCreateHelper(detail::getSyclObjImpl(MContext), PI_MEM_FLAGS_ACCESS_RW | PI_MEM_FLAGS_HOST_PTR_COPY, MSpecConstsBlob.size(), MSpecConstsBlob.data(), &MSpecConstsBuffer, nullptr); diff --git a/sycl/source/detail/mem_alloc_helper.hpp b/sycl/source/detail/mem_alloc_helper.hpp index 0cbc8c4bada86..d8a876c7f76cf 100644 --- a/sycl/source/detail/mem_alloc_helper.hpp +++ b/sycl/source/detail/mem_alloc_helper.hpp @@ -13,7 +13,9 @@ __SYCL_INLINE_NAMESPACE(cl) { namespace sycl { namespace detail { -void memBufferCreateHelper(const plugin &Plugin, pi_context Ctx, +class context_impl; + +void memBufferCreateHelper(std::shared_ptr CtxImpl, pi_mem_flags Flags, size_t Size, void *HostPtr, pi_mem *RetMem, const pi_mem_properties *Props = nullptr); diff --git a/sycl/source/detail/memory_manager.cpp b/sycl/source/detail/memory_manager.cpp index 2a966439776ca..a397a4ea084be 100644 --- a/sycl/source/detail/memory_manager.cpp +++ b/sycl/source/detail/memory_manager.cpp @@ -124,9 +124,11 @@ static void waitForEvents(const std::vector &Events) { } } -void memBufferCreateHelper(const plugin &Plugin, pi_context Ctx, - pi_mem_flags Flags, size_t Size, void *HostPtr, - pi_mem *RetMem, const pi_mem_properties *Props) { +static pi_result memBufferCreateNocheckHelper(const plugin &Plugin, + pi_context Ctx, + pi_mem_flags Flags, size_t Size, + void *HostPtr, pi_mem *RetMem, + const pi_mem_properties *Props) { #ifdef XPTI_ENABLE_INSTRUMENTATION uint64_t CorrID = 0; #endif @@ -147,9 +149,28 @@ void memBufferCreateHelper(const plugin &Plugin, pi_context Ctx, CorrID); }}; #endif - Plugin.call(Ctx, Flags, Size, HostPtr, RetMem, - Props); + return Plugin.call_nocheck( + Ctx, Flags, Size, HostPtr, RetMem, Props); + } +} + +void memBufferCreateHelper(std::shared_ptr CtxImpl, + pi_mem_flags Flags, size_t Size, void *HostPtr, + pi_mem *RetMem, const pi_mem_properties *Props) { + const detail::plugin &Plugin = CtxImpl->getPlugin(); + RT::PiResult Err = memBufferCreateNocheckHelper( + Plugin, CtxImpl->getHandleRef(), Flags, Size, HostPtr, RetMem, Props); + + ResourcePool &Resources = CtxImpl->getResourcePool(); + if (Err == PI_MEM_OBJECT_ALLOCATION_FAILURE && Resources.isEnabled()) { + // Clear resource pool and retry allocation. + Resources.clear(); + Err = memBufferCreateNocheckHelper(Plugin, CtxImpl->getHandleRef(), Flags, + Size, HostPtr, RetMem, Props); } + + if (Err != PI_SUCCESS) + Plugin.reportPiError(Err, "memBufferCreateHelper()"); } void memReleaseHelper(const plugin &Plugin, pi_mem Mem) { @@ -361,8 +382,6 @@ MemoryManager::allocateBufferObject(ContextImplPtr TargetContext, void *UserPtr, CreationFlags |= PI_MEM_FLAGS_HOST_PTR_ALLOC; RT::PiMem NewMem = nullptr; - const detail::plugin &Plugin = TargetContext->getPlugin(); - if (PropsList.has_property()) if (TargetContext->isBufferLocationSupported()) { auto location = @@ -370,12 +389,12 @@ MemoryManager::allocateBufferObject(ContextImplPtr TargetContext, void *UserPtr, .get_buffer_location(); pi_mem_properties props[3] = {PI_MEM_PROPERTIES_ALLOC_BUFFER_LOCATION, location, 0}; - memBufferCreateHelper(Plugin, TargetContext->getHandleRef(), - CreationFlags, Size, UserPtr, &NewMem, props); + memBufferCreateHelper(TargetContext, CreationFlags, Size, UserPtr, + &NewMem, props); return NewMem; } - memBufferCreateHelper(Plugin, TargetContext->getHandleRef(), CreationFlags, - Size, UserPtr, &NewMem, nullptr); + memBufferCreateHelper(TargetContext, CreationFlags, Size, UserPtr, &NewMem, + nullptr); return NewMem; } diff --git a/sycl/source/detail/resource_pool.cpp b/sycl/source/detail/resource_pool.cpp new file mode 100644 index 0000000000000..cf3e256c3f64b --- /dev/null +++ b/sycl/source/detail/resource_pool.cpp @@ -0,0 +1,107 @@ +//==-------------- resource_pool.cpp - USM resource pool -------------------==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include +#include + +__SYCL_INLINE_NAMESPACE(cl) { +namespace sycl { +namespace detail { + +ManagedResourceBase::~ManagedResourceBase() { + // Only return it to the pool if resource pooling is enabled. + if (MOrigin->MIsPoolingEnabled) + MOrigin->returnResourceToPool(MSize, MMem); +} + +ResourcePool::ResourcePool() + : MIsPoolingEnabled( + !SYCLConfig::get()) {} + +void ResourcePool::clear() { + std::lock_guard Lock{MMutex}; + MAllocCount -= MFreeEntries.size(); + for (auto Entry : MFreeEntries) + memReleaseHelper(MPlatform->getPlugin(), Entry.Mem); + MFreeEntries.clear(); +} + +ResourcePool::FreeEntry ResourcePool::getOrAllocateEntry( + const size_t Size, const std::shared_ptr &QueueImplPtr, + void *DataPtr, bool *IsNewEntry) { + assert(Size && "Size must be greater than 0"); + + { + std::lock_guard Lock{MMutex}; + + // Store platform to allow future freeing. + if (!MPlatform) + MPlatform = QueueImplPtr->getContextImplPtr()->getPlatformImpl(); + + // Find the free entry with the smallest suitable size. + auto FoundFreeEntry = MFreeEntries.upper_bound(Size - 1); + + // If there was a fitting free entry in the pool, remove and return it. + const bool IsOldEntry = FoundFreeEntry != MFreeEntries.end(); + if (IsNewEntry) + *IsNewEntry = !IsOldEntry; + if (IsOldEntry) { + FreeEntry Entry = *FoundFreeEntry; + MFreeEntries.erase(FoundFreeEntry); + return Entry; + } + } + + // If there was no suitable free entry we allocate memory and return it in a + // new free entry. + RT::PiMemFlags MemFlags = PI_MEM_FLAGS_ACCESS_RW; + if (DataPtr) + MemFlags |= PI_MEM_FLAGS_HOST_PTR_COPY; + RT::PiMem NewResMem; + memBufferCreateHelper(QueueImplPtr->getContextImplPtr(), MemFlags, Size, + DataPtr, &NewResMem, nullptr); + ++MAllocCount; + return {Size, NewResMem}; +} + +ResourcePool::FreeEntry ResourcePool::getOrAllocateEntry( + const size_t Size, const std::shared_ptr &QueueImplPtr, + void *DataPtr, event *AvailableEvent, bool *IsNewEntry) { + ResourcePool::FreeEntry Entry = + getOrAllocateEntry(Size, QueueImplPtr, DataPtr, IsNewEntry); + + // A new entry will have copied from the host pointer on creation. + if (IsNewEntry) { + *AvailableEvent = event{}; + return Entry; + } + + // If we get old memory we need to copy explicitly. + auto EventImpl = std::make_shared(QueueImplPtr); + EventImpl->setContextImpl(QueueImplPtr->getContextImplPtr()); + QueueImplPtr->getPlugin().call( + QueueImplPtr->getHandleRef(), Entry.Mem, + /*blocking_write=*/CL_FALSE, 0, Size, DataPtr, 0, nullptr, + &EventImpl->getHandleRef()); + *AvailableEvent = createSyclObjFromImpl(EventImpl); + return Entry; +} + +const std::shared_ptr &ResourcePool::getQueueContextImpl( + const std::shared_ptr &QueueImplPtr) { + return QueueImplPtr->getContextImplPtr(); +} + +} // namespace detail +} // namespace sycl +} // __SYCL_INLINE_NAMESPACE(cl) diff --git a/sycl/source/detail/sycl_mem_obj_t.cpp b/sycl/source/detail/sycl_mem_obj_t.cpp index 191a15c0d5b8e..6a2dc9b0d7f59 100644 --- a/sycl/source/detail/sycl_mem_obj_t.cpp +++ b/sycl/source/detail/sycl_mem_obj_t.cpp @@ -51,6 +51,20 @@ SYCLMemObjT::SYCLMemObjT(pi_native_handle MemObject, const context &SyclContext, Plugin.call(Mem); } +// Special internal "interop" constructor. This is used by auxiliary resources. +SYCLMemObjT::SYCLMemObjT(RT::PiMem MemObject, const context &SyclContext, + const size_t SizeInBytes, event AvailableEvent, + std::unique_ptr Allocator) + : MAllocator(std::move(Allocator)), MProps(), + MInteropEvent(detail::getSyclObjImpl(std::move(AvailableEvent))), + MInteropContext(detail::getSyclObjImpl(SyclContext)), + MInteropMemObject(pi::cast(MemObject)), MOpenCLInterop(true), + MHostPtrReadOnly(false), MNeedWriteBack(false), MSizeInBytes(SizeInBytes), + MUserPtr(nullptr), MShadowCopy(nullptr), MUploadDataFunctor(nullptr), + MSharedPtrStorage(nullptr) { + getPlugin().call(MemObject); +} + void SYCLMemObjT::releaseMem(ContextImplPtr Context, void *MemAllocation) { void *Ptr = getUserPtr(); return MemoryManager::releaseMemObj(Context, this, MemAllocation, Ptr); diff --git a/sycl/source/detail/usm/usm_impl.cpp b/sycl/source/detail/usm/usm_impl.cpp index 065033d35512c..1e211ae00990b 100644 --- a/sycl/source/detail/usm/usm_impl.cpp +++ b/sycl/source/detail/usm/usm_impl.cpp @@ -43,12 +43,15 @@ using alloc = cl::sycl::usm::alloc; namespace detail { namespace usm { -void *alignedAllocHost(size_t Alignment, size_t Size, const context &Ctxt, - alloc Kind, const detail::code_location &CL) { +static pi_result alignedAllocHostHelper(size_t Alignment, size_t Size, + const context &Ctxt, alloc Kind, + const detail::code_location &CL, + void **OutPtr) { XPTI_CREATE_TRACEPOINT(CL); - void *RetVal = nullptr; - if (Size == 0) - return nullptr; + if (Size == 0) { + *OutPtr = nullptr; + return PI_SUCCESS; + } if (Ctxt.is_host()) { if (!Alignment) { // worst case default @@ -57,10 +60,11 @@ void *alignedAllocHost(size_t Alignment, size_t Size, const context &Ctxt, aligned_allocator Alloc(Alignment); try { - RetVal = Alloc.allocate(Size); + *OutPtr = Alloc.allocate(Size); } catch (const std::bad_alloc &) { // Conform with Specification behavior - RetVal = nullptr; + *OutPtr = nullptr; + return PI_MEM_OBJECT_ALLOCATION_FAILURE; } } else { std::shared_ptr CtxImpl = detail::getSyclObjImpl(Ctxt); @@ -71,13 +75,13 @@ void *alignedAllocHost(size_t Alignment, size_t Size, const context &Ctxt, switch (Kind) { case alloc::host: { Error = Plugin.call_nocheck( - &RetVal, C, nullptr, Size, Alignment); + OutPtr, C, nullptr, Size, Alignment); break; } case alloc::device: case alloc::shared: case alloc::unknown: { - RetVal = nullptr; + *OutPtr = nullptr; Error = PI_INVALID_VALUE; break; } @@ -86,23 +90,42 @@ void *alignedAllocHost(size_t Alignment, size_t Size, const context &Ctxt, // Error is for debugging purposes. // The spec wants a nullptr returned, not an exception. if (Error != PI_SUCCESS) - return nullptr; + *OutPtr = nullptr; + + return Error; + } + return PI_SUCCESS; +} + +void *alignedAllocHost(size_t Alignment, size_t Size, const context &Ctxt, + alloc Kind, const detail::code_location &CL) { + void *RetVal; + pi_result Err = + alignedAllocHostHelper(Alignment, Size, Ctxt, Kind, CL, &RetVal); + + std::shared_ptr CtxImpl = detail::getSyclObjImpl(Ctxt); + ResourcePool &Resources = CtxImpl->getResourcePool(); + if (Err == PI_OUT_OF_RESOURCES && Resources.isEnabled()) { + // Clear resource pool and retry allocation. + Resources.clear(); + alignedAllocHostHelper(Alignment, Size, Ctxt, Kind, CL, &RetVal); } return RetVal; } -void *alignedAlloc(size_t Alignment, size_t Size, const context &Ctxt, - const device &Dev, alloc Kind, - const detail::code_location &CL, - const property_list &PropList = {}) { +static pi_result alignedAllocHelper(size_t Alignment, size_t Size, + const context &Ctxt, const device &Dev, + alloc Kind, const detail::code_location &CL, + const property_list &PropList, + void **OutPtr) { XPTI_CREATE_TRACEPOINT(CL); - void *RetVal = nullptr; - if (Size == 0) - return nullptr; - + if (Size == 0) { + *OutPtr = nullptr; + return PI_SUCCESS; + } if (Ctxt.is_host()) { if (Kind == alloc::unknown) { - RetVal = nullptr; + *OutPtr = nullptr; } else { if (!Alignment) { // worst case default @@ -111,10 +134,11 @@ void *alignedAlloc(size_t Alignment, size_t Size, const context &Ctxt, aligned_allocator Alloc(Alignment); try { - RetVal = Alloc.allocate(Size); + *OutPtr = Alloc.allocate(Size); } catch (const std::bad_alloc &) { // Conform with Specification behavior - RetVal = nullptr; + *OutPtr = nullptr; + return PI_MEM_OBJECT_ALLOCATION_FAILURE; } } } else { @@ -141,10 +165,10 @@ void *alignedAlloc(size_t Alignment, size_t Size, const context &Ctxt, pi_usm_mem_properties props[3] = {PI_MEM_USM_ALLOC_BUFFER_LOCATION, location, 0}; Error = Plugin.call_nocheck( - &RetVal, C, Id, props, Size, Alignment); + OutPtr, C, Id, props, Size, Alignment); } else { Error = Plugin.call_nocheck( - &RetVal, C, Id, nullptr, Size, Alignment); + OutPtr, C, Id, nullptr, Size, Alignment); } break; } @@ -155,16 +179,16 @@ void *alignedAlloc(size_t Alignment, size_t Size, const context &Ctxt, pi_usm_mem_properties Props[3] = {PI_MEM_ALLOC_FLAGS, PI_MEM_ALLOC_DEVICE_READ_ONLY, 0}; Error = Plugin.call_nocheck( - &RetVal, C, Id, Props, Size, Alignment); + OutPtr, C, Id, Props, Size, Alignment); } else { Error = Plugin.call_nocheck( - &RetVal, C, Id, nullptr, Size, Alignment); + OutPtr, C, Id, nullptr, Size, Alignment); } break; } case alloc::host: case alloc::unknown: { - RetVal = nullptr; + *OutPtr = nullptr; Error = PI_INVALID_VALUE; break; } @@ -173,7 +197,28 @@ void *alignedAlloc(size_t Alignment, size_t Size, const context &Ctxt, // Error is for debugging purposes. // The spec wants a nullptr returned, not an exception. if (Error != PI_SUCCESS) - return nullptr; + *OutPtr = nullptr; + + return Error; + } + + return PI_SUCCESS; +} + +void *alignedAlloc(size_t Alignment, size_t Size, const context &Ctxt, + const device &Dev, alloc Kind, + const detail::code_location &CL, + const property_list &PropList = {}) { + void *RetVal; + pi_result Err = alignedAllocHelper(Alignment, Size, Ctxt, Dev, Kind, CL, + PropList, &RetVal); + + std::shared_ptr CtxImpl = detail::getSyclObjImpl(Ctxt); + ResourcePool &Resources = CtxImpl->getResourcePool(); + if (Err == PI_OUT_OF_RESOURCES && Resources.isEnabled()) { + // Clear resource pool and retry allocation. + Resources.clear(); + alignedAllocHelper(Alignment, Size, Ctxt, Dev, Kind, CL, PropList, &RetVal); } return RetVal; } diff --git a/sycl/source/handler.cpp b/sycl/source/handler.cpp index aa349c90a6f33..528731779f210 100644 --- a/sycl/source/handler.cpp +++ b/sycl/source/handler.cpp @@ -814,5 +814,9 @@ void handler::depends_on(const std::vector &Events) { } } +detail::ResourcePool &handler::getResourcePool() { + return MQueue->getContextImplPtr()->getResourcePool(); +} + } // namespace sycl } // __SYCL_INLINE_NAMESPACE(cl) diff --git a/sycl/test/abi/sycl_symbols_linux.dump b/sycl/test/abi/sycl_symbols_linux.dump index 2f2f94e5bcd54..2964672ef794b 100644 --- a/sycl/test/abi/sycl_symbols_linux.dump +++ b/sycl/test/abi/sycl_symbols_linux.dump @@ -3828,8 +3828,10 @@ _ZN2cl4sycl6detail11SYCLMemObjT16updateHostMemoryEv _ZN2cl4sycl6detail11SYCLMemObjT20getBufSizeForContextERKSt10shared_ptrINS1_12context_implEEP7_cl_mem _ZN2cl4sycl6detail11SYCLMemObjT20getBufSizeForContextERKSt10shared_ptrINS1_12context_implEEm _ZN2cl4sycl6detail11SYCLMemObjTC1EP7_cl_memRKNS0_7contextEmNS0_5eventESt10unique_ptrINS1_19SYCLMemObjAllocatorESt14default_deleteISA_EE +_ZN2cl4sycl6detail11SYCLMemObjTC1EP7_pi_memRKNS0_7contextEmNS0_5eventESt10unique_ptrINS1_19SYCLMemObjAllocatorESt14default_deleteISA_EE _ZN2cl4sycl6detail11SYCLMemObjTC1EmRKNS0_7contextEmNS0_5eventESt10unique_ptrINS1_19SYCLMemObjAllocatorESt14default_deleteIS8_EE _ZN2cl4sycl6detail11SYCLMemObjTC2EP7_cl_memRKNS0_7contextEmNS0_5eventESt10unique_ptrINS1_19SYCLMemObjAllocatorESt14default_deleteISA_EE +_ZN2cl4sycl6detail11SYCLMemObjTC2EP7_pi_memRKNS0_7contextEmNS0_5eventESt10unique_ptrINS1_19SYCLMemObjAllocatorESt14default_deleteISA_EE _ZN2cl4sycl6detail11SYCLMemObjTC2EmRKNS0_7contextEmNS0_5eventESt10unique_ptrINS1_19SYCLMemObjAllocatorESt14default_deleteIS8_EE _ZN2cl4sycl6detail11buffer_impl11allocateMemESt10shared_ptrINS1_12context_implEEbPvRP9_pi_event _ZN2cl4sycl6detail11buffer_impl22destructorNotificationEPv @@ -3846,6 +3848,12 @@ _ZN2cl4sycl6detail11stream_implC1EmmRKNS0_13property_listE _ZN2cl4sycl6detail11stream_implC1EmmRNS0_7handlerE _ZN2cl4sycl6detail11stream_implC2EmmRKNS0_13property_listE _ZN2cl4sycl6detail11stream_implC2EmmRNS0_7handlerE +_ZN2cl4sycl6detail12ResourcePool18getOrAllocateEntryEmRKSt10shared_ptrINS1_10queue_implEEPvPNS0_5eventEPb +_ZN2cl4sycl6detail12ResourcePool18getOrAllocateEntryEmRKSt10shared_ptrINS1_10queue_implEEPvPb +_ZN2cl4sycl6detail12ResourcePool19getQueueContextImplERKSt10shared_ptrINS1_10queue_implEE +_ZN2cl4sycl6detail12ResourcePool5clearEv +_ZN2cl4sycl6detail12ResourcePoolC1Ev +_ZN2cl4sycl6detail12ResourcePoolC2Ev _ZN2cl4sycl6detail12compile_implERKNS0_13kernel_bundleILNS0_12bundle_stateE0EEERKSt6vectorINS0_6deviceESaIS8_EERKNS0_13property_listE _ZN2cl4sycl6detail12isOutOfRangeENS0_3vecIiLi4EEENS0_15addressing_modeENS0_5rangeILi3EEE _ZN2cl4sycl6detail12make_contextEmRKSt8functionIFvNS0_14exception_listEEENS0_7backendE @@ -3901,6 +3909,9 @@ _ZN2cl4sycl6detail18get_kernel_id_implENSt7__cxx1112basic_stringIcSt11char_trait _ZN2cl4sycl6detail18make_kernel_bundleEmRKNS0_7contextENS0_12bundle_stateENS0_7backendE _ZN2cl4sycl6detail18make_kernel_bundleEmRKNS0_7contextEbNS0_12bundle_stateENS0_7backendE _ZN2cl4sycl6detail18stringifyErrorCodeEi +_ZN2cl4sycl6detail19ManagedResourceBaseD0Ev +_ZN2cl4sycl6detail19ManagedResourceBaseD1Ev +_ZN2cl4sycl6detail19ManagedResourceBaseD2Ev _ZN2cl4sycl6detail19convertChannelOrderE23_pi_image_channel_order _ZN2cl4sycl6detail19convertChannelOrderENS0_19image_channel_orderE _ZN2cl4sycl6detail19getImageElementSizeEhNS0_18image_channel_typeE @@ -3996,6 +4007,7 @@ _ZN2cl4sycl7handler10processArgEPvRKNS0_6detail19kernel_param_kind_tEimRmb _ZN2cl4sycl7handler10processArgEPvRKNS0_6detail19kernel_param_kind_tEimRmbb _ZN2cl4sycl7handler12addReductionERKSt10shared_ptrIKvE _ZN2cl4sycl7handler13getKernelNameB5cxx11Ev +_ZN2cl4sycl7handler15getResourcePoolEv _ZN2cl4sycl7handler17use_kernel_bundleERKNS0_13kernel_bundleILNS0_12bundle_stateE2EEE _ZN2cl4sycl7handler18RangeRoundingTraceEv _ZN2cl4sycl7handler18ext_oneapi_barrierERKSt6vectorINS0_5eventESaIS3_EE diff --git a/sycl/test/abi/sycl_symbols_windows.dump b/sycl/test/abi/sycl_symbols_windows.dump index 5c908b3a3865e..c3348afc3132c 100644 --- a/sycl/test/abi/sycl_symbols_windows.dump +++ b/sycl/test/abi/sycl_symbols_windows.dump @@ -298,16 +298,21 @@ ??0LocalAccessorImplHost@detail@sycl@cl@@QEAA@$$QEAV0123@@Z ??0LocalAccessorImplHost@detail@sycl@cl@@QEAA@AEBV0123@@Z ??0LocalAccessorImplHost@detail@sycl@cl@@QEAA@V?$range@$02@23@HH@Z +??0ManagedResourceBase@detail@sycl@cl@@IEAA@_KPEAU_pi_mem@@PEAVResourcePool@123@@Z +??0ManagedResourceBase@detail@sycl@cl@@QEAA@AEBU0123@@Z +??0ResourcePool@detail@sycl@cl@@QEAA@XZ ??0SYCLCategory@detail@sycl@cl@@QEAA@XZ ??0SYCLMemObjT@detail@sycl@cl@@QEAA@AEBVproperty_list@23@V?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@@Z ??0SYCLMemObjT@detail@sycl@cl@@QEAA@PEAU_cl_mem@@AEBVcontext@23@Vevent@23@V?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@@Z ??0SYCLMemObjT@detail@sycl@cl@@QEAA@PEAU_cl_mem@@AEBVcontext@23@_KVevent@23@V?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@@Z +??0SYCLMemObjT@detail@sycl@cl@@QEAA@PEAU_pi_mem@@AEBVcontext@23@_KVevent@23@V?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@@Z ??0SYCLMemObjT@detail@sycl@cl@@QEAA@_KAEBVcontext@23@_KVevent@23@V?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@@Z ??0SYCLMemObjT@detail@sycl@cl@@QEAA@_KAEBVproperty_list@23@V?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@@Z ??0accelerator_selector@sycl@cl@@QEAA@$$QEAV012@@Z ??0accelerator_selector@sycl@cl@@QEAA@AEBV012@@Z ??0accelerator_selector@sycl@cl@@QEAA@XZ ??0buffer_impl@detail@sycl@cl@@QEAA@PEAU_cl_mem@@AEBVcontext@23@_KV?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@Vevent@23@@Z +??0buffer_impl@detail@sycl@cl@@QEAA@PEAU_pi_mem@@AEBVcontext@23@_KV?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@Vevent@23@@Z ??0buffer_impl@detail@sycl@cl@@QEAA@PEAX_K1AEBVproperty_list@23@V?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@@Z ??0buffer_impl@detail@sycl@cl@@QEAA@PEBX_K1AEBVproperty_list@23@V?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@@Z ??0buffer_impl@detail@sycl@cl@@QEAA@_K0AEBVproperty_list@23@V?$unique_ptr@VSYCLMemObjAllocator@detail@sycl@cl@@U?$default_delete@VSYCLMemObjAllocator@detail@sycl@cl@@@std@@@std@@@Z @@ -450,6 +455,8 @@ ??1?$image_impl@$02@detail@sycl@cl@@UEAA@XZ ??1AccessorImplHost@detail@sycl@cl@@QEAA@XZ ??1LocalAccessorImplHost@detail@sycl@cl@@QEAA@XZ +??1ManagedResourceBase@detail@sycl@cl@@UEAA@XZ +??1ResourcePool@detail@sycl@cl@@QEAA@XZ ??1SYCLCategory@detail@sycl@cl@@UEAA@XZ ??1SYCLMemObjT@detail@sycl@cl@@UEAA@XZ ??1accelerator_selector@sycl@cl@@UEAA@XZ @@ -483,6 +490,7 @@ ??4HostProfilingInfo@detail@sycl@cl@@QEAAAEAV0123@AEBV0123@@Z ??4LocalAccessorImplHost@detail@sycl@cl@@QEAAAEAV0123@$$QEAV0123@@Z ??4LocalAccessorImplHost@detail@sycl@cl@@QEAAAEAV0123@AEBV0123@@Z +??4ManagedResourceBase@detail@sycl@cl@@QEAAAEAU0123@AEBU0123@@Z ??4MemoryManager@detail@sycl@cl@@QEAAAEAV0123@$$QEAV0123@@Z ??4MemoryManager@detail@sycl@cl@@QEAAAEAV0123@AEBV0123@@Z ??4OSUtil@detail@sycl@cl@@QEAAAEAV0123@$$QEAV0123@@Z @@ -585,6 +593,7 @@ ??Zhalf_v2@host_half_impl@detail@sycl@cl@@QEAAAEAV01234@AEBV01234@@Z ??_0half@host_half_impl@detail@sycl@cl@@QEAAAEAV01234@AEBV01234@@Z ??_0half_v2@host_half_impl@detail@sycl@cl@@QEAAAEAV01234@AEBV01234@@Z +??_7ManagedResourceBase@detail@sycl@cl@@6B@ ??_7?$image_impl@$00@detail@sycl@cl@@6B@ ??_7?$image_impl@$01@detail@sycl@cl@@6B@ ??_7?$image_impl@$02@detail@sycl@cl@@6B@ @@ -1451,6 +1460,7 @@ ?checkImageFormat@?$image_impl@$00@detail@sycl@cl@@AEAA_NAEBU_pi_image_format@@V?$shared_ptr@Vcontext_impl@detail@sycl@cl@@@std@@@Z ?checkImageFormat@?$image_impl@$01@detail@sycl@cl@@AEAA_NAEBU_pi_image_format@@V?$shared_ptr@Vcontext_impl@detail@sycl@cl@@@std@@@Z ?checkImageFormat@?$image_impl@$02@detail@sycl@cl@@AEAA_NAEBU_pi_image_format@@V?$shared_ptr@Vcontext_impl@detail@sycl@cl@@@std@@@Z +?clear@ResourcePool@detail@sycl@cl@@QEAAXXZ ?clz@__host_std@cl@@YA?AV?$vec@C$00@sycl@2@V342@@Z ?clz@__host_std@cl@@YA?AV?$vec@C$01@sycl@2@V342@@Z ?clz@__host_std@cl@@YA?AV?$vec@C$02@sycl@2@V342@@Z @@ -2192,12 +2202,16 @@ ?getNativeVector@event@sycl@cl@@AEBA?AV?$vector@_KV?$allocator@_K@std@@@std@@XZ ?getOSMemSize@OSUtil@detail@sycl@cl@@SA_KXZ ?getOSModuleHandle@OSUtil@detail@sycl@cl@@SA_JPEBX@Z +?getOrAllocateEntry@ResourcePool@detail@sycl@cl@@AEAA?AUFreeEntry@1234@_KAEBV?$shared_ptr@Vqueue_impl@detail@sycl@cl@@@std@@PEAXPEA_N@Z +?getOrAllocateEntry@ResourcePool@detail@sycl@cl@@AEAA?AUFreeEntry@1234@_KAEBV?$shared_ptr@Vqueue_impl@detail@sycl@cl@@@std@@PEAXPEAVevent@34@PEA_N@Z ?getOrCreateSampler@sampler_impl@detail@sycl@cl@@QEAAPEAU_pi_sampler@@AEBVcontext@34@@Z ?getOrInsertHandlerKernelBundle@handler@sycl@cl@@AEBA?AV?$shared_ptr@Vkernel_bundle_impl@detail@sycl@cl@@@std@@_N@Z ?getOrWaitEvents@detail@sycl@cl@@YA?AV?$vector@PEAU_pi_event@@V?$allocator@PEAU_pi_event@@@std@@@std@@V?$vector@Vevent@sycl@cl@@V?$allocator@Vevent@sycl@cl@@@std@@@5@V?$shared_ptr@Vcontext_impl@detail@sycl@cl@@@5@@Z ?getPixelCoordLinearFiltMode@detail@sycl@cl@@YA?AV?$vec@H$07@23@V?$vec@M$03@23@W4addressing_mode@23@V?$range@$02@23@AEAV523@@Z ?getPixelCoordNearestFiltMode@detail@sycl@cl@@YA?AV?$vec@H$03@23@V?$vec@M$03@23@W4addressing_mode@23@V?$range@$02@23@@Z ?getPlugin@SYCLMemObjT@detail@sycl@cl@@QEBAAEBVplugin@234@XZ +?getQueueContextImpl@ResourcePool@detail@sycl@cl@@CAAEBV?$shared_ptr@Vcontext_impl@detail@sycl@cl@@@std@@AEBV?$shared_ptr@Vqueue_impl@detail@sycl@cl@@@6@@Z +?getResourcePool@handler@sycl@cl@@AEAAAEAVResourcePool@detail@23@XZ ?getRowPitch@?$image_impl@$00@detail@sycl@cl@@QEBA_KXZ ?getRowPitch@?$image_impl@$01@detail@sycl@cl@@QEBA_KXZ ?getRowPitch@?$image_impl@$02@detail@sycl@cl@@QEBA_KXZ @@ -2438,6 +2452,7 @@ ?ilogb@__host_std@cl@@YAHN@Z ?ilogb@__host_std@cl@@YAHVhalf@half_impl@detail@sycl@2@@Z ?isConstOrGlobal@handler@sycl@cl@@CA_NW4target@access@23@@Z +?isEnabled@ResourcePool@detail@sycl@cl@@QEBA_NXZ ?isImageOrImageArray@handler@sycl@cl@@CA_NW4target@access@23@@Z ?isInterop@SYCLMemObjT@detail@sycl@cl@@QEBA_NXZ ?isOutOfRange@detail@sycl@cl@@YA_NV?$vec@H$03@23@W4addressing_mode@23@V?$range@$02@23@@Z @@ -3195,6 +3210,7 @@ ?reset@filter_selector@oneapi@ext@sycl@cl@@QEBAXXZ ?resize@AccessorImplHost@detail@sycl@cl@@QEAAX_K@Z ?resize@buffer_impl@detail@sycl@cl@@QEAAX_K@Z +?returnResourceToPool@ResourcePool@detail@sycl@cl@@AEAAX_KPEAU_pi_mem@@@Z ?rint@__host_std@cl@@YA?AV?$vec@M$00@sycl@2@V342@@Z ?rint@__host_std@cl@@YA?AV?$vec@M$01@sycl@2@V342@@Z ?rint@__host_std@cl@@YA?AV?$vec@M$02@sycl@2@V342@@Z diff --git a/sycl/unittests/misc/CMakeLists.txt b/sycl/unittests/misc/CMakeLists.txt index 127089d85ebaa..e3cca400403dc 100644 --- a/sycl/unittests/misc/CMakeLists.txt +++ b/sycl/unittests/misc/CMakeLists.txt @@ -4,4 +4,5 @@ add_sycl_unittest(MiscTests SHARED CircularBuffer.cpp KernelBuildOptions.cpp OsUtils.cpp + ResourcePool.cpp ) diff --git a/sycl/unittests/misc/ResourcePool.cpp b/sycl/unittests/misc/ResourcePool.cpp new file mode 100644 index 0000000000000..ca7cf496c8953 --- /dev/null +++ b/sycl/unittests/misc/ResourcePool.cpp @@ -0,0 +1,421 @@ +//==---- ResourcePool.cpp --------------------------------------------------==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include + +#include +#include + +#include + +using namespace sycl; + +thread_local size_t AllocCounter = 0; +thread_local std::map AllocRefCountMap; + +static pi_result +redefinedMemBufferCreate(pi_context context, pi_mem_flags flags, size_t size, + void *host_ptr, pi_mem *ret_mem, + const pi_mem_properties *properties = nullptr) { + *ret_mem = reinterpret_cast(++AllocCounter); + AllocRefCountMap.insert({*ret_mem, 1}); + return PI_SUCCESS; +} + +static pi_result redefinedMemRetain(pi_mem mem) { + ++AllocRefCountMap[mem]; + return PI_SUCCESS; +} + +static pi_result redefinedMemRelease(pi_mem mem) { + --AllocRefCountMap[mem]; + return PI_SUCCESS; +} + +static void setupMock(sycl::unittest::PiMock &Mock) { + setupDefaultMockAPIs(Mock); + Mock.redefine(redefinedMemBufferCreate); + Mock.redefine(redefinedMemRetain); + Mock.redefine(redefinedMemRelease); + AllocCounter = 0; + AllocRefCountMap.clear(); +} + +template +using ManagedResourcePtr = std::shared_ptr>; + +template +static pi_mem getResourceMem(ManagedResourcePtr &MR) { + return reinterpret_cast( + detail::getSyclObjImpl(MR->getBuffer())->getUserPtr()); +} + +// Tests that allocated pool resources are correctly allocated, cached, and +// freed. +TEST(ResourcePool, TestResourcePoolAllocate) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + queue Q{Plt.get_devices()[0]}; + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool Pool; + pi_mem ResourceMem; + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(1), QImpl); + ResourceMem = getResourceMem(Res); + } + ASSERT_EQ(AllocRefCountMap[ResourceMem], 1u) + << "Managed resource was released and not returned to the pool."; + Pool.clear(); + ASSERT_EQ(AllocRefCountMap[ResourceMem], 0u) + << "Pool is not empty after clear."; +} + +// Tests that reallocating a resource with the same size and type will reuse +// memory from a previous allocation. +TEST(ResourcePool, TestResourcePoolReallocate) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + queue Q{Plt.get_devices()[0]}; + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool Pool; + pi_mem ResourceMem; + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(1), QImpl); + ResourceMem = getResourceMem(Res); + } + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(1), QImpl); + pi_mem ReallocedResourceMem = getResourceMem(Res); + ASSERT_EQ(ResourceMem, ReallocedResourceMem) + << "Reallocation did not result in the same resource memory."; + } + ASSERT_EQ(AllocRefCountMap[ResourceMem], 1u) + << "Managed resource was released and not returned to the pool."; + Pool.clear(); + ASSERT_EQ(AllocRefCountMap[ResourceMem], 0u) + << "Pool is not empty after clear."; +} + +// Tests that reallocating a resource with the same type but fewer element will +// reuse memory from a previous allocation with more allocated space. +TEST(ResourcePool, TestResourcePoolReallocateSmaller) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + queue Q{Plt.get_devices()[0]}; + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool Pool; + pi_mem ResourceMem; + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(250), QImpl); + ResourceMem = getResourceMem(Res); + } + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(200), QImpl); + pi_mem ReallocedResourceMem = getResourceMem(Res); + ASSERT_EQ(ResourceMem, ReallocedResourceMem) + << "Reallocation did not result in the same resource memory."; + } + ASSERT_EQ(AllocRefCountMap[ResourceMem], 1u) + << "Managed resource was released and not returned to the pool."; + Pool.clear(); + ASSERT_EQ(AllocRefCountMap[ResourceMem], 0u) + << "Pool is not empty after clear."; +} + +// Tests that reallocating a resource with more elements but a smaller element +// type, requiring a smaller allocation, will reuse memory from a previous +// allocation with more allocated space. +TEST(ResourcePool, TestResourcePoolReallocateSmallerByType) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + queue Q{Plt.get_devices()[0]}; + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool Pool; + pi_mem ResourceMem; + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(250), QImpl); + ResourceMem = getResourceMem(Res); + } + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(300), QImpl); + pi_mem ReallocedResourceMem = getResourceMem(Res); + ASSERT_EQ(ResourceMem, ReallocedResourceMem) + << "Reallocation did not result in the same resource memory."; + } + ASSERT_EQ(AllocRefCountMap[ResourceMem], 1u) + << "Managed resource was released and not returned to the pool."; + Pool.clear(); + ASSERT_EQ(AllocRefCountMap[ResourceMem], 0u) + << "Pool is not empty after clear."; +} + +// Tests that reallocating a resource that requires more elements of the same +// type as a previous reduction does not result in a reuse of the memory from +// the previous allocation. +TEST(ResourcePool, TestResourcePoolReallocateLarger) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + queue Q{Plt.get_devices()[0]}; + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool Pool; + pi_mem ResourceMem1, ResourceMem2; + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(250), QImpl); + ResourceMem1 = getResourceMem(Res); + } + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(300), QImpl); + ResourceMem2 = getResourceMem(Res); + } + ASSERT_NE(ResourceMem1, ResourceMem2) + << "Reallocation unexpectedly resulted in the same resource memory."; + ASSERT_EQ(AllocRefCountMap[ResourceMem1], 1u) + << "Managed resource 1 was released and not returned to the pool."; + ASSERT_EQ(AllocRefCountMap[ResourceMem2], 1u) + << "Managed resource 2 was released and not returned to the pool."; + Pool.clear(); + ASSERT_EQ(AllocRefCountMap[ResourceMem1] + AllocRefCountMap[ResourceMem2], 0u) + << "Pool is not empty after clear."; +} + +// Tests that reallocating a resource that requires more memory than a previous +// allocation due to the type, but with a smaller number of elements, does not +// result in a reuse of the memory from the previous allocation. +TEST(ResourcePool, TestResourcePoolReallocateLargerByType) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + queue Q{Plt.get_devices()[0]}; + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool Pool; + pi_mem ResourceMem1, ResourceMem2; + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(250), QImpl); + ResourceMem1 = getResourceMem(Res); + } + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(200), QImpl); + ResourceMem2 = getResourceMem(Res); + } + ASSERT_NE(ResourceMem1, ResourceMem2) + << "Reallocation unexpectedly resulted in the same resource memory."; + ASSERT_EQ(AllocRefCountMap[ResourceMem1], 1u) + << "Managed resource 1 was released and not returned to the pool."; + ASSERT_EQ(AllocRefCountMap[ResourceMem2], 1u) + << "Managed resource 2 was released and not returned to the pool."; + Pool.clear(); + ASSERT_EQ(AllocRefCountMap[ResourceMem1] + AllocRefCountMap[ResourceMem2], 0u) + << "Pool is not empty after clear."; +} + +// Tests that allocating a resource that fits in multiple of the available free +// allocations will pick the smallest of these allocations. +TEST(ResourcePool, TestResourcePoolReallocatePickOptimal) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + queue Q{Plt.get_devices()[0]}; + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool Pool; + pi_mem OptimalReuseMem; + { + ManagedResourcePtr Res1 = + Pool.getOrAllocateResource(range<1>(250), QImpl); + ManagedResourcePtr Res2 = + Pool.getOrAllocateResource(range<1>(210), QImpl); + ManagedResourcePtr Res3 = + Pool.getOrAllocateResource(range<1>(220), QImpl); + ManagedResourcePtr Res4 = + Pool.getOrAllocateResource(range<1>(199), QImpl); + // Res3 is optimal as it is the smallest allocation with room for at least + // 200 ints. + OptimalReuseMem = getResourceMem(Res3); + } + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(200), QImpl); + pi_mem ReusedMem = getResourceMem(Res); + ASSERT_NE(ReusedMem, OptimalReuseMem) + << "Reallocation did not pick the optimal available memory."; + } + ASSERT_EQ(AllocRefCountMap[OptimalReuseMem], 1u) + << "Managed resource was released and not returned to the pool."; + Pool.clear(); + ASSERT_EQ(AllocRefCountMap[OptimalReuseMem], 0u) + << "Pool is not empty after clear."; +} + +// Tests that allocating another resource of the same size as another living +// resource does not cause a reuse of the living resource. +TEST(ResourcePool, TestResourcePoolMultipleLiving) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + queue Q{Plt.get_devices()[0]}; + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool Pool; + pi_mem ResourceMem1, ResourceMem2; + { + ManagedResourcePtr Res1 = + Pool.getOrAllocateResource(range<1>(1), QImpl); + ResourceMem1 = getResourceMem(Res1); + ManagedResourcePtr Res2 = + Pool.getOrAllocateResource(range<1>(1), QImpl); + ResourceMem2 = getResourceMem(Res2); + } + ASSERT_NE(ResourceMem1, ResourceMem2) + << "Reallocation unexpectedly resulted in the same resource memory."; + ASSERT_EQ(AllocRefCountMap[ResourceMem1], 1u) + << "Managed resource 1 was released and not returned to the pool."; + ASSERT_EQ(AllocRefCountMap[ResourceMem2], 1u) + << "Managed resource 2 was released and not returned to the pool."; + Pool.clear(); + ASSERT_EQ(AllocRefCountMap[ResourceMem1] + AllocRefCountMap[ResourceMem2], 0u) + << "Pool is not empty after clear."; +} + +// Tests that clearing the pool while a resource is alive does not cause the +// resource to be freed. +TEST(ResourcePool, TestResourcePoolClearWhileAlive) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + queue Q{Plt.get_devices()[0]}; + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool Pool; + pi_mem ResourceMem; + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(1), QImpl); + ResourceMem = getResourceMem(Res); + ASSERT_GE(AllocRefCountMap[ResourceMem], 1u) + << "Managed resource was dead before clear."; + Pool.clear(); + ASSERT_GE(AllocRefCountMap[ResourceMem], 1u) + << "Managed resource was dead after clear."; + } + ASSERT_EQ(AllocRefCountMap[ResourceMem], 1u) + << "Managed resource was released and not returned to the pool."; + Pool.clear(); + ASSERT_EQ(AllocRefCountMap[ResourceMem], 0u) + << "Pool is not empty after clear."; +} + +// Tests that the resource pool owned by a context correctly clears when the +// context dies. +TEST(ResourcePool, TestResourcePoolClearOnContext) { + platform Plt{default_selector()}; + if (Plt.is_host()) { + std::cout << "Not run on host" << std::endl; + return; + } + + unittest::PiMock Mock{Plt}; + setupMock(Mock); + + pi_mem ResourceMem; + { + context Ctx{Plt}; + queue Q{Ctx, Ctx.get_devices()[0]}; + std::shared_ptr CtxImpl = detail::getSyclObjImpl(Ctx); + std::shared_ptr QImpl = detail::getSyclObjImpl(Q); + + detail::ResourcePool &Pool = CtxImpl->getResourcePool(); + { + ManagedResourcePtr Res = + Pool.getOrAllocateResource(range<1>(1), QImpl); + ResourceMem = getResourceMem(Res); + } + ASSERT_EQ(AllocRefCountMap[ResourceMem], 1u) + << "Managed resource was released and not returned to the pool."; + } + ASSERT_EQ(AllocRefCountMap[ResourceMem], 0u) + << "Context pool was not cleared after destruction."; +}