[llvm] [offload] Add context parameter to olCreateQueue (PR #213057)

Ɓukasz Plewa via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 31 07:16:53 PDT 2026


https://github.com/lplewa updated https://github.com/llvm/llvm-project/pull/213057

>From d14dc6e1da93c5877abe2c199e8725464bb6b13e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20Plewa?= <lukasz.plewa at intel.com>
Date: Wed, 8 Jul 2026 19:26:11 +0200
Subject: [PATCH 1/2] [offload] Add context parameter to olCreateQueue

Second patch in the context series.

Outstanding-queue bookkeeping (queues destroyed while still busy) moves
from ol_device_impl_t to ol_context_impl_t, keyed by device. This keeps
backend queue resources from being reused across native contexts and lets
olDestroyContext drain everything it owns.

On the Level Zero side, LevelZeroPluginContextTy owns a ze_context and an
L0QueueCacheTy pooling idle queues per device bound to that ze_context.
L0ContextTy holds a DefaultUserCtx used by the libomptarget path when no
user context is set on the async info. Queue creation, command-list
creation and initAsyncInfoImpl all thread the plugin context through so
queues are bound to the right ze_context.

Note: this patch temporarily passes null as the context in SYCL.
@KseniyaTikhomirova is currently working on proper context management
in libsycl - once that is implemented, this PR will be updated.

Assisted-by: Claude
---
 libsycl/src/detail/queue_impl.cpp             |   3 +-
 libsycl/unittests/mock/helpers.cpp            |   4 +-
 libsycl/unittests/mock/helpers.hpp            |   3 +-
 libsycl/unittests/mock/mock.cpp               |   5 +-
 libsycl/unittests/queue/queue.cpp             |   2 +-
 .../tools/llvm-gpu-loader/llvm-gpu-loader.cpp |   6 +-
 llvm/tools/llvm-gpu-loader/llvm-gpu-loader.h  |  11 +-
 offload/liboffload/API/Queue.td               |  15 +-
 offload/liboffload/src/OffloadImpl.cpp        | 148 ++++++++++--------
 .../common/include/PluginInterface.h          |  14 +-
 .../common/src/PluginInterface.cpp            |  11 +-
 .../level_zero/include/L0Context.h            |  14 +-
 .../level_zero/include/L0Device.h             |  23 +--
 .../level_zero/include/L0Plugin.h             |  11 +-
 .../level_zero/include/L0Queue.h              |  20 ++-
 .../level_zero/src/L0Context.cpp              |  12 ++
 .../level_zero/src/L0Device.cpp               |  31 ++--
 .../level_zero/src/L0Plugin.cpp               |  15 ++
 .../level_zero/src/L0Queue.cpp                |  32 ++--
 .../include/mathtest/DeviceContext.hpp        |   3 +
 .../include/mathtest/OffloadForward.hpp       |   3 +
 .../Conformance/lib/DeviceContext.cpp         |   7 +
 .../unittests/OffloadAPI/common/Fixtures.hpp  |  11 +-
 .../OffloadAPI/context/olCreateContext.cpp    |  16 +-
 .../OffloadAPI/context/olGetContextInfo.cpp   |  15 +-
 .../context/olGetContextInfoSize.cpp          |  15 +-
 .../OffloadAPI/kernel/olLaunchKernel.cpp      |   2 +-
 .../unittests/OffloadAPI/memory/olMemcpy.cpp  |   2 +-
 .../OffloadAPI/queue/olCreateQueue.cpp        |  22 ++-
 .../OffloadAPI/queue/olGetQueueInfo.cpp       |   8 +
 .../OffloadAPI/queue/olGetQueueInfoSize.cpp   |   6 +
 .../OffloadAPI/queue/olLaunchHostFunction.cpp |   2 +-
 .../OffloadAPI/queue/olWaitEvents.cpp         |  10 +-
 33 files changed, 334 insertions(+), 168 deletions(-)

diff --git a/libsycl/src/detail/queue_impl.cpp b/libsycl/src/detail/queue_impl.cpp
index 6116e2d787e6f..c1602ac52d142 100644
--- a/libsycl/src/detail/queue_impl.cpp
+++ b/libsycl/src/detail/queue_impl.cpp
@@ -66,7 +66,8 @@ QueueImpl::QueueImpl(DeviceImpl &deviceImpl, const async_handler &asyncHandler,
     : MIsInorder(false), MAsyncHandler(asyncHandler), MPropList(propList),
       MDevice(deviceImpl),
       MContext(MDevice.getPlatformImpl().getDefaultContext()) {
-  callAndThrow(olCreateQueue, MDevice.getOLHandle(), &MOffloadQueue);
+  // TODO: temporary, waiting for proper context support in sycl.
+  callAndThrow(olCreateQueue, nullptr, MDevice.getOLHandle(), &MOffloadQueue);
 }
 
 QueueImpl::~QueueImpl() {
diff --git a/libsycl/unittests/mock/helpers.cpp b/libsycl/unittests/mock/helpers.cpp
index 6b4460cd66113..562754ae68b4c 100644
--- a/libsycl/unittests/mock/helpers.cpp
+++ b/libsycl/unittests/mock/helpers.cpp
@@ -222,8 +222,10 @@ void mock::MockLiboffload::initDefault() {
       });
 
   ON_CALL(*this, olCreateQueue)
-      .WillByDefault([this](ol_device_handle_t Device,
+      .WillByDefault([this](ol_context_handle_t Context,
+                            ol_device_handle_t Device,
                             ol_queue_handle_t *Queue) -> ol_result_t {
+        std::ignore = Context;
         if (!Device)
           return makeEmptyStrError(OL_ERRC_INVALID_NULL_HANDLE);
         if (!Queue)
diff --git a/libsycl/unittests/mock/helpers.hpp b/libsycl/unittests/mock/helpers.hpp
index 0690970f1f99b..50835284c73d9 100644
--- a/libsycl/unittests/mock/helpers.hpp
+++ b/libsycl/unittests/mock/helpers.hpp
@@ -90,7 +90,8 @@ class MockLiboffload {
               (ol_device_iterate_cb_t Callback, void *UserData));
   MOCK_METHOD(ol_result_t, olDestroyProgram, (ol_program_handle_t Program));
   MOCK_METHOD(ol_result_t, olCreateQueue,
-              (ol_device_handle_t Device, ol_queue_handle_t *Queue));
+              (ol_context_handle_t Context, ol_device_handle_t Device,
+               ol_queue_handle_t *Queue));
   MOCK_METHOD(ol_result_t, olDestroyQueue, (ol_queue_handle_t Queue));
   MOCK_METHOD(ol_result_t, olSyncQueue, (ol_queue_handle_t Queue));
   MOCK_METHOD(ol_result_t, olDestroyEvent, (ol_event_handle_t Event));
diff --git a/libsycl/unittests/mock/mock.cpp b/libsycl/unittests/mock/mock.cpp
index 8f5db4b5cb6ec..f050eb13317e8 100644
--- a/libsycl/unittests/mock/mock.cpp
+++ b/libsycl/unittests/mock/mock.cpp
@@ -50,8 +50,9 @@ ol_result_t olDestroyProgram(ol_program_handle_t Program) {
   return mock::getMockLiboffload().olDestroyProgram(Program);
 }
 
-ol_result_t olCreateQueue(ol_device_handle_t Device, ol_queue_handle_t *Queue) {
-  return mock::getMockLiboffload().olCreateQueue(Device, Queue);
+ol_result_t olCreateQueue(ol_context_handle_t Context,
+                          ol_device_handle_t Device, ol_queue_handle_t *Queue) {
+  return mock::getMockLiboffload().olCreateQueue(Context, Device, Queue);
 }
 
 ol_result_t olDestroyQueue(ol_queue_handle_t Queue) {
diff --git a/libsycl/unittests/queue/queue.cpp b/libsycl/unittests/queue/queue.cpp
index 1234e5eb94281..c505f78ecbdd4 100644
--- a/libsycl/unittests/queue/queue.cpp
+++ b/libsycl/unittests/queue/queue.cpp
@@ -20,7 +20,7 @@ using namespace ::testing;
 TEST(Queue, CommonQueriesAndLifetime) {
   mock::MockWrapper Mock;
 
-  EXPECT_CALL(Mock.get(), olCreateQueue(_, _)).Times(1);
+  EXPECT_CALL(Mock.get(), olCreateQueue(_, _, _)).Times(1);
   EXPECT_CALL(Mock.get(), olDestroyQueue(_)).Times(1);
   {
     queue Q;
diff --git a/llvm/tools/llvm-gpu-loader/llvm-gpu-loader.cpp b/llvm/tools/llvm-gpu-loader/llvm-gpu-loader.cpp
index 2cd2cfaeebb6b..82a8815c8a380 100644
--- a/llvm/tools/llvm-gpu-loader/llvm-gpu-loader.cpp
+++ b/llvm/tools/llvm-gpu-loader/llvm-gpu-loader.cpp
@@ -244,12 +244,15 @@ int main(int argc, const char **argv, const char **envp) {
   ol_device_handle_t Host = getHostDevice();
   assert(Host && "Host device should always be present");
 
+  ol_context_handle_t Context;
+  OFFLOAD_ERR(olCreateContext(1, &Device, &Context));
+
   ol_program_handle_t Program;
   OFFLOAD_ERR(olCreateProgram(Device, Image.getBufferStart(),
                               Image.getBufferSize(), &Program));
 
   ol_queue_handle_t Queue;
-  OFFLOAD_ERR(olCreateQueue(Device, &Queue));
+  OFFLOAD_ERR(olCreateQueue(Context, Device, &Queue));
 
   int DevArgc = static_cast<int>(NewArgv.size());
   void *DevArgv = copyArgumentVector(NewArgv.size(), NewArgv.begin(), Device);
@@ -284,6 +287,7 @@ int main(int argc, const char **argv, const char **envp) {
   OFFLOAD_ERR(olMemFree(DevArgv));
   OFFLOAD_ERR(olMemFree(DevEnvp));
   OFFLOAD_ERR(olDestroyQueue(Queue));
+  OFFLOAD_ERR(olDestroyContext(Context));
   OFFLOAD_ERR(olDestroyProgram(Program));
   OFFLOAD_ERR(olShutDown());
 
diff --git a/llvm/tools/llvm-gpu-loader/llvm-gpu-loader.h b/llvm/tools/llvm-gpu-loader/llvm-gpu-loader.h
index 1e9ae3448e7a0..c257f2ea391b2 100644
--- a/llvm/tools/llvm-gpu-loader/llvm-gpu-loader.h
+++ b/llvm/tools/llvm-gpu-loader/llvm-gpu-loader.h
@@ -108,6 +108,7 @@ typedef struct ol_platform_impl_t *ol_platform_handle_t;
 typedef struct ol_program_impl_t *ol_program_handle_t;
 typedef struct ol_queue_impl_t *ol_queue_handle_t;
 typedef struct ol_symbol_impl_t *ol_symbol_handle_t;
+typedef struct ol_context_impl_t *ol_context_handle_t;
 typedef const struct ol_error_struct_t *ol_result_t;
 
 typedef bool (*ol_device_iterate_cb_t)(ol_device_handle_t Device,
@@ -138,7 +139,13 @@ ol_result_t (*olLaunchKernel)(
     const ol_kernel_launch_prop_t *Properties, size_t NumArgs, void **ArgPtrs,
     const size_t *ArgSizes);
 
-ol_result_t (*olCreateQueue)(ol_device_handle_t Device,
+ol_result_t (*olCreateContext)(size_t DevicesCount, ol_device_handle_t *Devices,
+                               ol_context_handle_t *Context);
+
+ol_result_t (*olDestroyContext)(ol_context_handle_t Context);
+
+ol_result_t (*olCreateQueue)(ol_context_handle_t Context,
+                             ol_device_handle_t Device,
                              ol_queue_handle_t *Queue);
 
 ol_result_t (*olDestroyQueue)(ol_queue_handle_t Queue);
@@ -195,6 +202,8 @@ llvm::Error loadLLVMOffload() {
   DYNAMIC_INIT(olDestroyProgram);
   DYNAMIC_INIT(olGetSymbol);
   DYNAMIC_INIT(olLaunchKernel);
+  DYNAMIC_INIT(olCreateContext);
+  DYNAMIC_INIT(olDestroyContext);
   DYNAMIC_INIT(olCreateQueue);
   DYNAMIC_INIT(olDestroyQueue);
   DYNAMIC_INIT(olSyncQueue);
diff --git a/offload/liboffload/API/Queue.td b/offload/liboffload/API/Queue.td
index 4008432375753..b03ad6d46a1e8 100644
--- a/offload/liboffload/API/Queue.td
+++ b/offload/liboffload/API/Queue.td
@@ -11,13 +11,21 @@
 //===----------------------------------------------------------------------===//
 
 def olCreateQueue : Function {
-    let desc = "Create a queue for the given device.";
-    let details = [];
+    let desc = "Create a queue for the given device within the given context.";
+    let details = [
+        "The queue is scoped to `Context` and `Device` must belong to it.",
+        "The queue can only operate on resources (memory allocations, programs) that were created in the same context."
+    ];
     let params = [
+        Param<"ol_context_handle_t", "Context", "handle of the context", PARAM_IN>,
         Param<"ol_device_handle_t", "Device", "handle of the device", PARAM_IN>,
         Param<"ol_queue_handle_t*", "Queue", "output pointer for the created queue", PARAM_OUT>
     ];
-    let returns = [];
+    let returns = [
+        Return<"OL_ERRC_INVALID_DEVICE", [
+            "Device does not belong to `Context`"
+        ]>
+    ];
 }
 
 def olDestroyQueue : Function {
@@ -61,6 +69,7 @@ def ol_queue_info_t : Enum {
   let is_typed = 1;
   let etors = [
     TaggedEtor<"DEVICE", "ol_device_handle_t", "The handle of the device associated with the queue.">,
+    TaggedEtor<"CONTEXT", "ol_context_handle_t", "The handle of the context associated with the queue.">,
     TaggedEtor<"EMPTY", "bool", "True if the queue is known to be empty. May be unconditionally false if the device does not support status queries.">,
   ];
 }
diff --git a/offload/liboffload/src/OffloadImpl.cpp b/offload/liboffload/src/OffloadImpl.cpp
index 63fe726fbe544..d6a2b466cb92d 100644
--- a/offload/liboffload/src/OffloadImpl.cpp
+++ b/offload/liboffload/src/OffloadImpl.cpp
@@ -71,70 +71,17 @@ struct ol_device_impl_t {
       : DeviceNum(DeviceNum), Device(Device), Platform(Platform),
         Info(std::forward<InfoTreeNode>(DevInfo)) {}
 
-  ~ol_device_impl_t() {
-    assert(!OutstandingQueues.size() &&
-           "Device object dropped with outstanding queues");
-  }
-
   int DeviceNum;
   GenericDeviceTy *Device;
   ol_platform_impl_t &Platform;
   InfoTreeNode Info;
-
-  llvm::SmallVector<__tgt_async_info *> OutstandingQueues;
-  std::mutex OutstandingQueuesMutex;
-
-  /// If the device has any outstanding queues that are now complete, remove it
-  /// from the list and return it.
-  ///
-  /// Queues may be added to the outstanding queue list by olDestroyQueue if
-  /// they are destroyed but not completed.
-  __tgt_async_info *getOutstandingQueue() {
-    // Not locking the `size()` access is fine here - In the worst case we
-    // either miss a queue that exists or loop through an empty array after
-    // taking the lock. Both are sub-optimal but not that bad.
-    if (OutstandingQueues.size()) {
-      std::lock_guard<std::mutex> Lock(OutstandingQueuesMutex);
-
-      // As queues are pulled and popped from this list, longer running queues
-      // naturally bubble to the start of the array. Hence looping backwards.
-      for (auto Q = OutstandingQueues.rbegin(); Q != OutstandingQueues.rend();
-           Q++) {
-        if (!Device->hasPendingWork(*Q)) {
-          auto OutstandingQueue = *Q;
-          *Q = OutstandingQueues.back();
-          OutstandingQueues.pop_back();
-          return OutstandingQueue;
-        }
-      }
-    }
-    return nullptr;
-  }
-
-  /// Complete all pending work for this device and perform any needed cleanup.
-  ///
-  /// After calling this function, no liboffload functions should be called with
-  /// this device handle.
-  llvm::Error destroy() {
-    llvm::Error Result = Plugin::success();
-    for (auto Q : OutstandingQueues)
-      if (auto Err = Device->synchronize(Q, /*Release=*/true))
-        Result = llvm::joinErrors(std::move(Result), std::move(Err));
-    OutstandingQueues.clear();
-    return Result;
-  }
 };
 
 llvm::Error ol_platform_impl_t::destroy() {
-  llvm::Error Result = Plugin::success();
-  for (auto &D : Devices)
-    if (auto Err = D->destroy())
-      Result = llvm::joinErrors(std::move(Result), std::move(Err));
-
   if (auto Res = Plugin->deinit())
-    Result = llvm::joinErrors(std::move(Result), std::move(Res));
+    return Res;
 
-  return Result;
+  return llvm::Error::success();
 }
 
 llvm::Error ol_platform_impl_t::init() {
@@ -160,9 +107,12 @@ llvm::Error ol_platform_impl_t::init() {
 }
 
 struct ol_queue_impl_t {
-  ol_queue_impl_t(__tgt_async_info *AsyncInfo, ol_device_handle_t Device)
-      : AsyncInfo(AsyncInfo), Device(Device), Id(IdCounter++) {}
+  ol_queue_impl_t(__tgt_async_info *AsyncInfo, ol_context_handle_t Context,
+                  ol_device_handle_t Device)
+      : AsyncInfo(AsyncInfo), Context(Context), Device(Device),
+        Id(IdCounter++) {}
   __tgt_async_info *AsyncInfo;
+  ol_context_handle_t Context;
   ol_device_handle_t Device;
   // A unique identifier for the queue
   size_t Id;
@@ -218,6 +168,66 @@ struct ol_context_impl_t {
   ol_platform_impl_t *Platform;
   llvm::SmallVector<ol_device_handle_t> Devices;
   std::unique_ptr<plugin::PluginContextTy> PluginCtx;
+
+  bool contains(ol_device_handle_t Device) const {
+    return llvm::is_contained(Devices, Device);
+  }
+
+  /// Queues destroyed while still busy, keyed by owning device. Per-context
+  /// so their backend resources are not reused across native contexts.
+  llvm::DenseMap<ol_device_handle_t, llvm::SmallVector<__tgt_async_info *>>
+      OutstandingQueues;
+  std::mutex OutstandingQueuesMutex;
+
+  /// If the context has any outstanding queues for \p Device that are now
+  /// complete, remove it from the list and return it.
+  ///
+  /// Queues may be added to the outstanding queue list by olDestroyQueue if
+  /// they are destroyed but not completed.
+  __tgt_async_info *getOutstandingQueue(ol_device_handle_t Device) {
+    std::lock_guard<std::mutex> Lock(OutstandingQueuesMutex);
+    auto It = OutstandingQueues.find(Device);
+    if (It == OutstandingQueues.end() || It->second.empty())
+      return nullptr;
+    auto &Bucket = It->second;
+
+    // As queues are pulled and popped from this list, longer running queues
+    // naturally bubble to the start of the array. Hence looping backwards.
+    for (auto Q = Bucket.rbegin(); Q != Bucket.rend(); Q++) {
+      if (!Device->Device->hasPendingWork(*Q)) {
+        auto OutstandingQueue = *Q;
+        *Q = Bucket.back();
+        Bucket.pop_back();
+        return OutstandingQueue;
+      }
+    }
+    return nullptr;
+  }
+
+  /// Record \p AsyncInfo on the outstanding queue list for \p Device.
+  void addOutstandingQueue(ol_device_handle_t Device,
+                           __tgt_async_info *AsyncInfo) {
+    std::lock_guard<std::mutex> Lock(OutstandingQueuesMutex);
+    OutstandingQueues[Device].push_back(AsyncInfo);
+  }
+
+  /// Complete all pending work for this context. Called from olDestroyContext.
+  llvm::Error drainOutstandingQueues() {
+    llvm::Error Result = Plugin::success();
+    for (auto &Bucket : OutstandingQueues) {
+      auto *Device = Bucket.first;
+      for (auto *AI : Bucket.second)
+        if (auto Err = Device->Device->synchronize(AI, /*Release=*/true))
+          Result = llvm::joinErrors(std::move(Result), std::move(Err));
+    }
+    OutstandingQueues.clear();
+    return Result;
+  }
+
+  ~ol_context_impl_t() {
+    assert(OutstandingQueues.empty() &&
+           "Context dropped with outstanding queues");
+  }
 };
 
 namespace llvm {
@@ -629,6 +639,8 @@ Error olCreateContext_impl(size_t DevicesCount, ol_device_handle_t *Devices,
 }
 
 Error olDestroyContext_impl(ol_context_handle_t Context) {
+  if (auto Err = Context->drainOutstandingQueues())
+    return Err;
   return olDestroy(Context);
 }
 
@@ -845,10 +857,16 @@ Error olGetMemInfoSize_impl(const void *Ptr, ol_mem_info_t PropName,
   return olGetMemInfoImplDetail(Ptr, PropName, 0, nullptr, PropSizeRet);
 }
 
-Error olCreateQueue_impl(ol_device_handle_t Device, ol_queue_handle_t *Queue) {
-  auto CreatedQueue = std::make_unique<ol_queue_impl_t>(nullptr, Device);
+Error olCreateQueue_impl(ol_context_handle_t Context, ol_device_handle_t Device,
+                         ol_queue_handle_t *Queue) {
+  if (!Context->contains(Device))
+    return createOffloadError(ErrorCode::INVALID_DEVICE,
+                              "device does not belong to the given context");
+
+  auto CreatedQueue =
+      std::make_unique<ol_queue_impl_t>(nullptr, Context, Device);
 
-  auto OutstandingQueue = Device->getOutstandingQueue();
+  auto OutstandingQueue = Context->getOutstandingQueue(Device);
   if (OutstandingQueue) {
     // The queue is empty, but we still need to sync it to release any temporary
     // memory allocations or do other cleanup.
@@ -856,8 +874,8 @@ Error olCreateQueue_impl(ol_device_handle_t Device, ol_queue_handle_t *Queue) {
             Device->Device->synchronize(OutstandingQueue, /*Release=*/false))
       return Err;
     CreatedQueue->AsyncInfo = OutstandingQueue;
-  } else if (auto Err =
-                 Device->Device->initAsyncInfo(&(CreatedQueue->AsyncInfo))) {
+  } else if (auto Err = Device->Device->initAsyncInfo(
+                 &(CreatedQueue->AsyncInfo), Context->PluginCtx.get())) {
     return Err;
   }
 
@@ -867,6 +885,7 @@ Error olCreateQueue_impl(ol_device_handle_t Device, ol_queue_handle_t *Queue) {
 
 Error olDestroyQueue_impl(ol_queue_handle_t Queue) {
   auto *Device = Queue->Device;
+  auto *Context = Queue->Context;
   // This is safe; as soon as olDestroyQueue is called it is not possible to add
   // any more work to the queue, so if it's finished now it will remain finished
   // forever.
@@ -881,8 +900,7 @@ Error olDestroyQueue_impl(ol_queue_handle_t Queue) {
       return Err;
   } else {
     // The queue still has outstanding work. Store it so we can check it later.
-    std::lock_guard<std::mutex> Lock(Device->OutstandingQueuesMutex);
-    Device->OutstandingQueues.push_back(Queue->AsyncInfo);
+    Context->addOutstandingQueue(Device, Queue->AsyncInfo);
   }
 
   return olDestroy(Queue);
@@ -933,6 +951,8 @@ Error olGetQueueInfoImplDetail(ol_queue_handle_t Queue,
   switch (PropName) {
   case OL_QUEUE_INFO_DEVICE:
     return Info.write<ol_device_handle_t>(Queue->Device);
+  case OL_QUEUE_INFO_CONTEXT:
+    return Info.write<ol_context_handle_t>(Queue->Context);
   case OL_QUEUE_INFO_EMPTY: {
     auto Pending = Queue->Device->Device->hasPendingWork(Queue->AsyncInfo);
     if (auto Err = Pending.takeError())
diff --git a/offload/plugins-nextgen/common/include/PluginInterface.h b/offload/plugins-nextgen/common/include/PluginInterface.h
index 67ebfbc943fdc..250f9d66a1afd 100644
--- a/offload/plugins-nextgen/common/include/PluginInterface.h
+++ b/offload/plugins-nextgen/common/include/PluginInterface.h
@@ -109,7 +109,8 @@ template <typename... ArgsTy>
 /// operations when calling AsyncInfoWrapperTy::finalize(). This latter function
 /// must be called before destroying the wrapper object.
 struct AsyncInfoWrapperTy {
-  AsyncInfoWrapperTy(GenericDeviceTy &Device, __tgt_async_info *AsyncInfoPtr);
+  AsyncInfoWrapperTy(GenericDeviceTy &Device, __tgt_async_info *AsyncInfoPtr,
+                     PluginContextTy *Context = nullptr);
 
   ~AsyncInfoWrapperTy() {
     assert(!AsyncInfoPtr && "AsyncInfoWrapperTy not finalized");
@@ -118,6 +119,10 @@ struct AsyncInfoWrapperTy {
   /// Get the raw __tgt_async_info pointer.
   operator __tgt_async_info *() const { return AsyncInfoPtr; }
 
+  /// Optional plugin-side context this async info is scoped to; null on the
+  /// libomptarget path.
+  PluginContextTy *getContext() const { return Context; }
+
   /// Indicate whether there is queue.
   bool hasQueue() const { return (AsyncInfoPtr->Queue != nullptr); }
 
@@ -177,6 +182,7 @@ struct AsyncInfoWrapperTy {
   GenericDeviceTy &Device;
   __tgt_async_info LocalAsyncInfo;
   __tgt_async_info *AsyncInfoPtr;
+  PluginContextTy *Context = nullptr;
 };
 
 enum class DeviceInfo {
@@ -1104,8 +1110,10 @@ struct GenericDeviceTy : public DeviceAllocatorTy {
                      KernelExtraArgsTy *KernelExtraArgs,
                      __tgt_async_info *AsyncInfo);
 
-  /// Initialize a __tgt_async_info structure.
-  Error initAsyncInfo(__tgt_async_info **AsyncInfoPtr);
+  /// Initialize a __tgt_async_info structure. \p Context is optional and is
+  /// forwarded on the wrapper for plugin impls that need it.
+  Error initAsyncInfo(__tgt_async_info **AsyncInfoPtr,
+                      PluginContextTy *Context = nullptr);
   virtual Error initAsyncInfoImpl(AsyncInfoWrapperTy &AsyncInfoWrapper) = 0;
 
   /// Enqueue a host call to AsyncInfo
diff --git a/offload/plugins-nextgen/common/src/PluginInterface.cpp b/offload/plugins-nextgen/common/src/PluginInterface.cpp
index 7b821e77df179..4c0f639e475a3 100644
--- a/offload/plugins-nextgen/common/src/PluginInterface.cpp
+++ b/offload/plugins-nextgen/common/src/PluginInterface.cpp
@@ -45,9 +45,11 @@ using namespace error;
 using namespace llvm::offload::debug;
 
 AsyncInfoWrapperTy::AsyncInfoWrapperTy(GenericDeviceTy &Device,
-                                       __tgt_async_info *AsyncInfoPtr)
+                                       __tgt_async_info *AsyncInfoPtr,
+                                       PluginContextTy *Context)
     : Device(Device),
-      AsyncInfoPtr(AsyncInfoPtr ? AsyncInfoPtr : &LocalAsyncInfo) {}
+      AsyncInfoPtr(AsyncInfoPtr ? AsyncInfoPtr : &LocalAsyncInfo),
+      Context(Context) {}
 
 Error AsyncInfoWrapperTy::synchronize() {
   assert(AsyncInfoPtr && "AsyncInfoWrapperTy already finalized");
@@ -1206,12 +1208,13 @@ Error GenericDeviceTy::launchKernel(void *EntryPtr, void **ArgPtrs,
   return Err;
 }
 
-Error GenericDeviceTy::initAsyncInfo(__tgt_async_info **AsyncInfoPtr) {
+Error GenericDeviceTy::initAsyncInfo(__tgt_async_info **AsyncInfoPtr,
+                                     PluginContextTy *Context) {
   assert(AsyncInfoPtr && "Invalid async info");
 
   *AsyncInfoPtr = new __tgt_async_info();
 
-  AsyncInfoWrapperTy AsyncInfoWrapper(*this, *AsyncInfoPtr);
+  AsyncInfoWrapperTy AsyncInfoWrapper(*this, *AsyncInfoPtr, Context);
 
   auto Err = initAsyncInfoImpl(AsyncInfoWrapper);
   AsyncInfoWrapper.finalize(Err);
diff --git a/offload/plugins-nextgen/level_zero/include/L0Context.h b/offload/plugins-nextgen/level_zero/include/L0Context.h
index 7bd082571b1a1..e052a274ec07a 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Context.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Context.h
@@ -20,6 +20,7 @@
 namespace llvm::omp::target::plugin {
 
 class LevelZeroPluginTy;
+class LevelZeroPluginContextTy;
 
 class L0ContextTLSTy {
   StagingBufferTy StagingBuffer;
@@ -65,6 +66,9 @@ class L0ContextTy {
   /// Host Memory allocator for this driver.
   MemAllocatorTy HostMemAllocator;
 
+  /// Default plugin-side context used by the libomptarget path.
+  std::unique_ptr<LevelZeroPluginContextTy> DefaultUserCtx;
+
 public:
   /// Named constants for checking the imported external pointer regions.
   static constexpr int32_t ImportNotExist = -1;
@@ -73,8 +77,7 @@ class L0ContextTy {
 
   /// Create context, initialize event pool and extension functions.
   L0ContextTy(LevelZeroPluginTy &Plugin, ze_driver_handle_t zeDriver,
-              int32_t DriverId)
-      : Plugin(Plugin), zeDriver(zeDriver) {}
+              int32_t DriverId);
 
   L0ContextTy(const L0ContextTy &) = delete;
   L0ContextTy(L0ContextTy &&) = delete;
@@ -82,7 +85,7 @@ class L0ContextTy {
   L0ContextTy &operator=(const L0ContextTy &&) = delete;
 
   /// Release resources.
-  ~L0ContextTy() = default;
+  ~L0ContextTy();
 
   Error init();
   Error deinit();
@@ -122,6 +125,11 @@ class L0ContextTy {
   /// Return context associated with the driver.
   ze_context_handle_t getZeContext() const { return zeContext; }
 
+  /// Return the default plugin-side context used by the libomptarget path.
+  LevelZeroPluginContextTy &getDefaultUserCtx() const {
+    return *DefaultUserCtx;
+  }
+
   /// Return driver API version.
   ze_api_version_t getDriverAPIVersion() const { return APIVersion; }
 
diff --git a/offload/plugins-nextgen/level_zero/include/L0Device.h b/offload/plugins-nextgen/level_zero/include/L0Device.h
index 3e8129d057095..314b2df3e9ddd 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Device.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Device.h
@@ -143,9 +143,6 @@ class L0DeviceTy final : public GenericDeviceTy {
   /// MemAllocator for this device.
   MemAllocatorTy MemAllocator;
 
-  /// Cache of queues for this device.
-  L0QueueCacheTy QueueCache;
-
   DeviceArchTy computeArch() const;
 
   /// Scan the device's command queue groups, selecting the default compute
@@ -163,7 +160,7 @@ class L0DeviceTy final : public GenericDeviceTy {
              const std::string_view zeId, int32_t ComputeIndex)
       : GenericDeviceTy(Plugin, DeviceId, NumDevices, SPIRVGridValues),
         L0Context(DriverInfo), zeDevice(zeDevice), zeId(zeId),
-        ComputeIndex(ComputeIndex), QueueCache(*this) {
+        ComputeIndex(ComputeIndex) {
     DeviceProperties.stype = ZE_STRUCTURE_TYPE_DEVICE_PROPERTIES;
     DeviceProperties.pNext = nullptr;
     ComputeProperties.stype = ZE_STRUCTURE_TYPE_DEVICE_COMPUTE_PROPERTIES;
@@ -369,11 +366,15 @@ class L0DeviceTy final : public GenericDeviceTy {
 
   /// Create an immediate command list.
   Expected<ze_command_list_handle_t>
-  createImmCmdList(uint32_t Ordinal, uint32_t Index, bool InOrder = false);
+  createImmCmdList(uint32_t Ordinal, uint32_t Index, bool InOrder = false,
+                   ze_context_handle_t UserZeCtx = nullptr);
 
   /// Create an immediate command list for computing.
-  Expected<ze_command_list_handle_t> createImmCmdList(bool InOrder = false) {
-    return createImmCmdList(getComputeEngine(), getComputeIndex(), InOrder);
+  Expected<ze_command_list_handle_t>
+  createImmCmdList(bool InOrder = false,
+                   ze_context_handle_t UserZeCtx = nullptr) {
+    return createImmCmdList(getComputeEngine(), getComputeIndex(), InOrder,
+                            UserZeCtx);
   }
 
   /// Release an immediate command list.
@@ -382,8 +383,10 @@ class L0DeviceTy final : public GenericDeviceTy {
     return Plugin::success();
   }
 
-  Expected<L0CmdListManagerTy *> getCmdListManager(bool InOrder = false) {
-    auto CmdListOrErr = createImmCmdList(InOrder);
+  Expected<L0CmdListManagerTy *>
+  getCmdListManager(bool InOrder = false,
+                    ze_context_handle_t UserZeCtx = nullptr) {
+    auto CmdListOrErr = createImmCmdList(InOrder, UserZeCtx);
     if (!CmdListOrErr)
       return CmdListOrErr.takeError();
     return new L0CmdListManagerTy(*CmdListOrErr, L0Context);
@@ -470,7 +473,7 @@ class L0DeviceTy final : public GenericDeviceTy {
   /// Returns the Queue from an async info object, or creates a new one if
   /// the async info does not have a queue yet.
   Expected<L0QueueTy *> getOrCreateQueue(__tgt_async_info *AsyncInfo);
-  void releaseQueue(L0QueueTy *Queue) { QueueCache.releaseQueue(Queue); }
+  void releaseQueue(L0QueueTy *Queue);
 
   // Allocation related routines.
 
diff --git a/offload/plugins-nextgen/level_zero/include/L0Plugin.h b/offload/plugins-nextgen/level_zero/include/L0Plugin.h
index 56a708b7609a4..f84e7cbeca0d2 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Plugin.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Plugin.h
@@ -17,6 +17,7 @@
 #include "L0Memory.h"
 #include "L0Options.h"
 #include "L0Program.h"
+#include "L0Queue.h"
 
 namespace llvm::omp::target::plugin {
 
@@ -29,17 +30,25 @@ class LevelZeroPluginContextTy final : public PluginContextTy {
                            ze_driver_handle_t Driver,
                            ze_context_handle_t ZeContext, bool OwnsZeContext)
       : PluginContextTy(Plugin, Devices), Driver(Driver), ZeContext(ZeContext),
-        OwnsZeContext(OwnsZeContext) {}
+        OwnsZeContext(OwnsZeContext), QueueCache(*this) {}
 
   ~LevelZeroPluginContextTy() override;
 
   ze_driver_handle_t getZeDriver() const { return Driver; }
   ze_context_handle_t getZeContext() const { return ZeContext; }
 
+  /// Pop an idle queue for \p Device from the cache, or create a new one.
+  Expected<L0QueueTy *> takeCachedQueue(L0DeviceTy *Device);
+
+  /// Return an idle queue to the cache.
+  void returnCachedQueue(L0DeviceTy *Device, L0QueueTy *Queue);
+
 private:
   ze_driver_handle_t Driver;
   ze_context_handle_t ZeContext;
   bool OwnsZeContext;
+
+  L0QueueCacheTy QueueCache;
 };
 
 /// Class implementing the LevelZero specific functionalities of the plugin.
diff --git a/offload/plugins-nextgen/level_zero/include/L0Queue.h b/offload/plugins-nextgen/level_zero/include/L0Queue.h
index 4ef2232c8da06..b027fdea63ced 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Queue.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Queue.h
@@ -25,6 +25,7 @@
 namespace llvm::omp::target::plugin {
 
 class L0DeviceTy;
+class LevelZeroPluginContextTy;
 struct L0LaunchEnvTy;
 
 /// Abstract queue that supports asynchronous command submission.
@@ -36,16 +37,21 @@ class L0QueueTy {
   L0CmdListManagerTy *CmdList = nullptr;
   /// Whether the queue is in-order or out-of-order.
   bool CreateQueueInOrder;
+  /// Owning plugin-side context (never null on an active queue).
+  LevelZeroPluginContextTy *UserCtx = nullptr;
 
 public:
   L0QueueTy(L0DeviceTy &Device, bool IsInorder = true)
       : Device(Device), CreateQueueInOrder(IsInorder) {}
   virtual ~L0QueueTy() {}
 
+  LevelZeroPluginContextTy *getUserCtx() const { return UserCtx; }
+  void setUserCtx(LevelZeroPluginContextTy *Ctx) { UserCtx = Ctx; }
+
   /// Clear data.
   void reset() { resetImpl(); }
 
-  Error init();
+  Error init(ze_context_handle_t UserZeCtx = nullptr);
   Error deinit();
   Error synchronize() { return synchronizeImpl(); }
   Expected<bool> hasPendingWork() { return hasPendingWorkImpl(); }
@@ -271,17 +277,15 @@ class L0SyncQueueTy : public L0InorderQueueTy {
 
 /// Simple cache for queue objects.
 class L0QueueCacheTy {
-  L0DeviceTy &Device;
-  llvm::SmallVector<L0QueueTy *> Queues;
+  LevelZeroPluginContextTy &UserCtx;
+  llvm::DenseMap<L0DeviceTy *, llvm::SmallVector<L0QueueTy *>> Queues;
   std::mutex Mtx;
-  CommandModeTy CachedCmdMode = CommandModeTy::InOrder;
 
 public:
-  L0QueueCacheTy(L0DeviceTy &Device) : Device(Device) {}
-  Expected<L0QueueTy *> getQueue();
-  void releaseQueue(L0QueueTy *Queue);
+  L0QueueCacheTy(LevelZeroPluginContextTy &Ctx) : UserCtx(Ctx) {}
+  Expected<L0QueueTy *> getQueue(L0DeviceTy &Device);
+  void releaseQueue(L0DeviceTy &Device, L0QueueTy *Queue);
   Error deinit();
-  void setCommandMode(CommandModeTy CmdMode) { CachedCmdMode = CmdMode; }
 };
 
 } // namespace llvm::omp::target::plugin
diff --git a/offload/plugins-nextgen/level_zero/src/L0Context.cpp b/offload/plugins-nextgen/level_zero/src/L0Context.cpp
index be21a2fdc22ed..4cab00ff0b5e1 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Context.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Context.cpp
@@ -15,6 +15,12 @@
 
 namespace llvm::omp::target::plugin {
 
+L0ContextTy::L0ContextTy(LevelZeroPluginTy &Plugin, ze_driver_handle_t zeDriver,
+                         int32_t DriverId)
+    : Plugin(Plugin), zeDriver(zeDriver) {}
+
+L0ContextTy::~L0ContextTy() = default;
+
 Error L0ContextTy::init() {
   auto CleanupOnError = [&]() {
     if (zeContext) {
@@ -68,10 +74,16 @@ Error L0ContextTy::init() {
   if (RC != ZE_RESULT_SUCCESS)
     zeDriverGetDefaultContext = nullptr;
 
+  DefaultUserCtx = std::make_unique<LevelZeroPluginContextTy>(
+      Plugin, /*Devices=*/llvm::ArrayRef<GenericDeviceTy *>{}, zeDriver,
+      zeContext, /*OwnsZeContext=*/false);
+
   return Plugin::success();
 }
 
 Error L0ContextTy::deinit() {
+  // Release the default context (drains its queue cache) before zeContext.
+  DefaultUserCtx.reset();
   if (auto Err = EventPool.deinit())
     return Err;
   if (auto Err = HostMemAllocator.deinit())
diff --git a/offload/plugins-nextgen/level_zero/src/L0Device.cpp b/offload/plugins-nextgen/level_zero/src/L0Device.cpp
index 076dfa080f86e..b7350613f7faa 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Device.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Device.cpp
@@ -196,7 +196,6 @@ Error L0DeviceTy::initImpl(GenericPluginTy &Plugin) {
   if (!QueueGroupInfoOrErr)
     return QueueGroupInfoOrErr.takeError();
   QueueConfig = *QueueGroupInfoOrErr;
-  QueueCache.setCommandMode(getPlugin().getOptions().CommandMode);
 
   if (auto Err = MemAllocator.initDevicePools(*this, Options))
     return Err;
@@ -209,8 +208,6 @@ Error L0DeviceTy::deinitImpl() {
   for (auto &PGM : Programs)
     if (auto Err = PGM.deinit())
       return Err;
-  if (auto Err = QueueCache.deinit())
-    return Err;
   return MemAllocator.deinit();
 }
 
@@ -258,11 +255,17 @@ Error L0DeviceTy::unloadBinaryImpl(DeviceImageTy *Image) {
   return Plugin::success();
 }
 
+void L0DeviceTy::releaseQueue(L0QueueTy *Queue) {
+  if (!Queue)
+    return;
+  Queue->getUserCtx()->returnCachedQueue(this, Queue);
+}
+
 Expected<L0QueueTy *>
 L0DeviceTy::getOrCreateQueue(__tgt_async_info *AsyncInfo) {
   L0QueueTy *Queue = static_cast<L0QueueTy *>(AsyncInfo->Queue);
   if (!Queue) {
-    auto NewQueueOrErr = QueueCache.getQueue();
+    auto NewQueueOrErr = L0Context.getDefaultUserCtx().takeCachedQueue(this);
     if (!NewQueueOrErr)
       return NewQueueOrErr.takeError();
     Queue = *NewQueueOrErr;
@@ -398,8 +401,16 @@ Error L0DeviceTy::dataExchangeImpl(const void *SrcPtr, GenericDeviceTy &DstDev,
 }
 
 Error L0DeviceTy::initAsyncInfoImpl(AsyncInfoWrapperTy &AsyncInfoWrapper) {
-  auto QueueOrErr = getOrCreateQueue(AsyncInfoWrapper);
-  return QueueOrErr ? Plugin::success() : QueueOrErr.takeError();
+  __tgt_async_info *AsyncInfo = AsyncInfoWrapper;
+  auto *L0Ctx = AsyncInfoWrapper.getContext()
+                    ? static_cast<LevelZeroPluginContextTy *>(
+                          AsyncInfoWrapper.getContext())
+                    : &L0Context.getDefaultUserCtx();
+  auto NewQueueOrErr = L0Ctx->takeCachedQueue(this);
+  if (!NewQueueOrErr)
+    return NewQueueOrErr.takeError();
+  AsyncInfo->Queue = *NewQueueOrErr;
+  return Plugin::success();
 }
 
 const char *L0DeviceTy::getArchCStr() const {
@@ -733,7 +744,8 @@ Error L0DeviceTy::makeMemoryResident(void *Mem, size_t Size) {
 
 /// Create an immediate command list.
 Expected<ze_command_list_handle_t>
-L0DeviceTy::createImmCmdList(uint32_t Ordinal, uint32_t Index, bool InOrder) {
+L0DeviceTy::createImmCmdList(uint32_t Ordinal, uint32_t Index, bool InOrder,
+                             ze_context_handle_t UserZeCtx) {
   ze_command_queue_flags_t Flags = InOrder ? ZE_COMMAND_QUEUE_FLAG_IN_ORDER : 0;
   if (getPlugin().getOptions().Flags.UseCopyOffloadHint)
     Flags |= ZE_COMMAND_QUEUE_FLAG_COPY_OFFLOAD_HINT;
@@ -746,8 +758,9 @@ L0DeviceTy::createImmCmdList(uint32_t Ordinal, uint32_t Index, bool InOrder) {
                                ZE_COMMAND_QUEUE_MODE_ASYNCHRONOUS,
                                ZE_COMMAND_QUEUE_PRIORITY_NORMAL};
   ze_command_list_handle_t CmdList = nullptr;
-  CALL_ZE_RET_ERROR(zeCommandListCreateImmediate, getZeContext(), getZeDevice(),
-                    &Desc, &CmdList);
+  ze_context_handle_t ZeCtx = UserZeCtx ? UserZeCtx : getZeContext();
+  CALL_ZE_RET_ERROR(zeCommandListCreateImmediate, ZeCtx, getZeDevice(), &Desc,
+                    &CmdList);
   ODBG(OLDT_Device) << "Created an immediate command list " << CmdList
                     << " (Ordinal: " << Ordinal << ", Index: " << Index
                     << ", Flags: " << Flags << ") for device " << getZeIdCStr();
diff --git a/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp b/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp
index 5723644c6763c..77b997d912864 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp
@@ -255,10 +255,25 @@ Error LevelZeroPluginTy::asyncBarrierImpl(omp_interop_val_t *Interop) {
 }
 
 LevelZeroPluginContextTy::~LevelZeroPluginContextTy() {
+  // TODO: this should be moved out of the destructor and into a deinit() method
+  if (auto Err = QueueCache.deinit()) {
+    REPORT() << "Error deinitializing LevelZeroPluginContextTy queue cache: "
+             << toString(std::move(Err));
+  }
   if (OwnsZeContext && ZeContext)
     zeContextDestroy(ZeContext);
 }
 
+Expected<L0QueueTy *>
+LevelZeroPluginContextTy::takeCachedQueue(L0DeviceTy *Device) {
+  return QueueCache.getQueue(*Device);
+}
+
+void LevelZeroPluginContextTy::returnCachedQueue(L0DeviceTy *Device,
+                                                 L0QueueTy *Queue) {
+  QueueCache.releaseQueue(*Device, Queue);
+}
+
 Expected<std::unique_ptr<PluginContextTy>>
 LevelZeroPluginTy::createPluginContext(
     llvm::ArrayRef<GenericDeviceTy *> Devices) {
diff --git a/offload/plugins-nextgen/level_zero/src/L0Queue.cpp b/offload/plugins-nextgen/level_zero/src/L0Queue.cpp
index a27024ad66d19..0a3085642148e 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Queue.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Queue.cpp
@@ -24,8 +24,8 @@ namespace llvm::omp::target::plugin {
 
 /// common methods
 
-Error L0QueueTy::init() {
-  auto CmdListOrErr = Device.getCmdListManager(CreateQueueInOrder);
+Error L0QueueTy::init(ze_context_handle_t UserZeCtx) {
+  auto CmdListOrErr = Device.getCmdListManager(CreateQueueInOrder, UserZeCtx);
   if (!CmdListOrErr)
     return CmdListOrErr.takeError();
   CmdList = *CmdListOrErr;
@@ -458,17 +458,18 @@ Error L0SyncQueueTy::hostCallImpl(void (*Callback)(void *), void *UserData) {
 }
 
 // L0QueueCache implementation.
-Expected<L0QueueTy *> L0QueueCacheTy::getQueue() {
+Expected<L0QueueTy *> L0QueueCacheTy::getQueue(L0DeviceTy &Device) {
   {
     std::lock_guard<std::mutex> Lock(Mtx);
-    if (!Queues.empty()) {
-      L0QueueTy *Queue = Queues.back();
-      Queues.pop_back();
+    auto Itr = Queues.find(&Device);
+    if (Itr != Queues.end() && !Itr->second.empty()) {
+      L0QueueTy *Queue = Itr->second.back();
+      Itr->second.pop_back();
       return Queue;
     }
   }
   L0QueueTy *Queue = nullptr;
-  switch (CachedCmdMode) {
+  switch (Device.getPlugin().getOptions().CommandMode) {
   case CommandModeTy::Async:
     Queue = new L0AsyncQueueTy(Device);
     break;
@@ -482,28 +483,31 @@ Expected<L0QueueTy *> L0QueueCacheTy::getQueue() {
     Queue = new L0InorderQueueTy(Device);
     break;
   }
-  if (auto Err = Queue->init()) {
+  Queue->setUserCtx(&UserCtx);
+  if (auto Err = Queue->init(UserCtx.getZeContext())) {
     delete Queue;
     return std::move(Err);
   }
   return Queue;
 }
 
-void L0QueueCacheTy::releaseQueue(L0QueueTy *Queue) {
+void L0QueueCacheTy::releaseQueue(L0DeviceTy &Device, L0QueueTy *Queue) {
   if (!Queue)
     return;
   Queue->reset();
   std::lock_guard<std::mutex> Lock(Mtx);
-  Queues.push_back(Queue);
+  Queues[&Device].push_back(Queue);
 }
 
 Error L0QueueCacheTy::deinit() {
   Error AllErrors = Error::success();
   std::lock_guard<std::mutex> Lock(Mtx);
-  for (auto *Queue : Queues) {
-    if (auto Err = Queue->deinit())
-      AllErrors = joinErrors(std::move(AllErrors), std::move(Err));
-    delete Queue;
+  for (auto &Bucket : Queues) {
+    for (auto *Queue : Bucket.second) {
+      if (auto Err = Queue->deinit())
+        AllErrors = joinErrors(std::move(AllErrors), std::move(Err));
+      delete Queue;
+    }
   }
   Queues.clear();
   return AllErrors;
diff --git a/offload/unittests/Conformance/include/mathtest/DeviceContext.hpp b/offload/unittests/Conformance/include/mathtest/DeviceContext.hpp
index 17ec7e95fb2be..2e23cd3f4b19a 100644
--- a/offload/unittests/Conformance/include/mathtest/DeviceContext.hpp
+++ b/offload/unittests/Conformance/include/mathtest/DeviceContext.hpp
@@ -56,6 +56,8 @@ class DeviceContext {
 
   explicit DeviceContext(llvm::StringRef Platform, std::size_t DeviceId = 0);
 
+  ~DeviceContext();
+
   template <typename T>
   ManagedBuffer<T> createManagedBuffer(std::size_t Size) const noexcept {
     void *UntypedAddress = nullptr;
@@ -137,6 +139,7 @@ class DeviceContext {
 
   std::size_t GlobalDeviceId;
   ol_device_handle_t DeviceHandle;
+  ol_context_handle_t Context = nullptr;
 };
 } // namespace mathtest
 
diff --git a/offload/unittests/Conformance/include/mathtest/OffloadForward.hpp b/offload/unittests/Conformance/include/mathtest/OffloadForward.hpp
index 788989a0d4211..f8a17cf05e29c 100644
--- a/offload/unittests/Conformance/include/mathtest/OffloadForward.hpp
+++ b/offload/unittests/Conformance/include/mathtest/OffloadForward.hpp
@@ -26,6 +26,9 @@ typedef const ol_error_struct_t *ol_result_t;
 struct ol_device_impl_t;
 typedef struct ol_device_impl_t *ol_device_handle_t;
 
+struct ol_context_impl_t;
+typedef struct ol_context_impl_t *ol_context_handle_t;
+
 struct ol_program_impl_t;
 typedef struct ol_program_impl_t *ol_program_handle_t;
 
diff --git a/offload/unittests/Conformance/lib/DeviceContext.cpp b/offload/unittests/Conformance/lib/DeviceContext.cpp
index 638831e405424..f3f0f8f394323 100644
--- a/offload/unittests/Conformance/lib/DeviceContext.cpp
+++ b/offload/unittests/Conformance/lib/DeviceContext.cpp
@@ -175,6 +175,7 @@ DeviceContext::DeviceContext(std::size_t GlobalDeviceId)
                 llvm::Twine(Devices.size()));
 
   DeviceHandle = Devices[GlobalDeviceId].Handle;
+  OL_CHECK(olCreateContext(1, &DeviceHandle, &Context));
 }
 
 DeviceContext::DeviceContext(llvm::StringRef Platform, std::size_t DeviceId)
@@ -210,6 +211,12 @@ DeviceContext::DeviceContext(llvm::StringRef Platform, std::size_t DeviceId)
 
   GlobalDeviceId = *FoundGlobalDeviceId;
   DeviceHandle = Devices[GlobalDeviceId].Handle;
+  OL_CHECK(olCreateContext(1, &DeviceHandle, &Context));
+}
+
+DeviceContext::~DeviceContext() {
+  if (Context)
+    olDestroyContext(Context);
 }
 
 [[nodiscard]] llvm::Expected<std::shared_ptr<DeviceImage>>
diff --git a/offload/unittests/OffloadAPI/common/Fixtures.hpp b/offload/unittests/OffloadAPI/common/Fixtures.hpp
index f6b7502c41882..0d667db00a152 100644
--- a/offload/unittests/OffloadAPI/common/Fixtures.hpp
+++ b/offload/unittests/OffloadAPI/common/Fixtures.hpp
@@ -190,6 +190,14 @@ struct OffloadDeviceTest
     Device = DeviceParam.Handle;
     if (Device == nullptr)
       GTEST_SKIP() << "No available devices.";
+
+    ASSERT_SUCCESS(olCreateContext(1, &Device, &Context));
+  }
+
+  void TearDown() override {
+    if (Context)
+      olDestroyContext(Context);
+    RETURN_ON_FATAL_FAILURE(OffloadTest::TearDown());
   }
 
   ol_platform_backend_t getPlatformBackend() const {
@@ -205,6 +213,7 @@ struct OffloadDeviceTest
   }
 
   ol_device_handle_t Device = nullptr;
+  ol_context_handle_t Context = nullptr;
 };
 
 struct OffloadPlatformTest : OffloadDeviceTest {
@@ -274,7 +283,7 @@ struct OffloadGlobalTest : OffloadProgramTest {
 struct OffloadQueueTest : OffloadDeviceTest {
   void SetUp() override {
     RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::SetUp());
-    ASSERT_SUCCESS(olCreateQueue(Device, &Queue));
+    ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queue));
   }
 
   void TearDown() override {
diff --git a/offload/unittests/OffloadAPI/context/olCreateContext.cpp b/offload/unittests/OffloadAPI/context/olCreateContext.cpp
index 81777eb21e8ed..eb7dba1fd6196 100644
--- a/offload/unittests/OffloadAPI/context/olCreateContext.cpp
+++ b/offload/unittests/OffloadAPI/context/olCreateContext.cpp
@@ -10,7 +10,21 @@
 #include <OffloadAPI.h>
 #include <gtest/gtest.h>
 
-using olCreateContextTest = OffloadDeviceTest;
+// OffloadDeviceTest creates an ol_context_handle_t in SetUp; this fixture
+// stops at the device so the tests below can exercise olCreateContext.
+struct olCreateContextTest
+    : OffloadTest,
+      ::testing::WithParamInterface<TestEnvironment::Device> {
+  void SetUp() override {
+    RETURN_ON_FATAL_FAILURE(OffloadTest::SetUp());
+    auto DeviceParam = GetParam();
+    Device = DeviceParam.Handle;
+    if (Device == nullptr)
+      GTEST_SKIP() << "No available devices.";
+  }
+
+  ol_device_handle_t Device = nullptr;
+};
 OFFLOAD_TESTS_INSTANTIATE_DEVICE_FIXTURE(olCreateContextTest);
 
 TEST_P(olCreateContextTest, Success) {
diff --git a/offload/unittests/OffloadAPI/context/olGetContextInfo.cpp b/offload/unittests/OffloadAPI/context/olGetContextInfo.cpp
index 14d911f786848..c41d23ce6c49f 100644
--- a/offload/unittests/OffloadAPI/context/olGetContextInfo.cpp
+++ b/offload/unittests/OffloadAPI/context/olGetContextInfo.cpp
@@ -10,20 +10,7 @@
 #include <OffloadAPI.h>
 #include <gtest/gtest.h>
 
-struct olGetContextInfoTest : OffloadDeviceTest {
-  void SetUp() override {
-    RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::SetUp());
-    ASSERT_SUCCESS(olCreateContext(1, &Device, &Context));
-  }
-
-  void TearDown() override {
-    if (Context)
-      olDestroyContext(Context);
-    RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::TearDown());
-  }
-
-  ol_context_handle_t Context = nullptr;
-};
+using olGetContextInfoTest = OffloadDeviceTest;
 OFFLOAD_TESTS_INSTANTIATE_DEVICE_FIXTURE(olGetContextInfoTest);
 
 TEST_P(olGetContextInfoTest, SuccessNumDevices) {
diff --git a/offload/unittests/OffloadAPI/context/olGetContextInfoSize.cpp b/offload/unittests/OffloadAPI/context/olGetContextInfoSize.cpp
index 1af9298717eb1..2c7ad71859793 100644
--- a/offload/unittests/OffloadAPI/context/olGetContextInfoSize.cpp
+++ b/offload/unittests/OffloadAPI/context/olGetContextInfoSize.cpp
@@ -10,20 +10,7 @@
 #include <OffloadAPI.h>
 #include <gtest/gtest.h>
 
-struct olGetContextInfoSizeTest : OffloadDeviceTest {
-  void SetUp() override {
-    RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::SetUp());
-    ASSERT_SUCCESS(olCreateContext(1, &Device, &Context));
-  }
-
-  void TearDown() override {
-    if (Context)
-      olDestroyContext(Context);
-    RETURN_ON_FATAL_FAILURE(OffloadDeviceTest::TearDown());
-  }
-
-  ol_context_handle_t Context = nullptr;
-};
+using olGetContextInfoSizeTest = OffloadDeviceTest;
 OFFLOAD_TESTS_INSTANTIATE_DEVICE_FIXTURE(olGetContextInfoSizeTest);
 
 TEST_P(olGetContextInfoSizeTest, SuccessNumDevices) {
diff --git a/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp b/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp
index c159829e7e40e..a8840f713ca84 100644
--- a/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp
+++ b/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp
@@ -355,7 +355,7 @@ TEST_P(olLaunchKernelSingleCounterSyncEventTest, SuccessTwoQueues) {
   ASSERT_SUCCESS(olSyncQueue(Queue));
 
   ol_queue_handle_t Queue2 = nullptr;
-  ASSERT_SUCCESS(olCreateQueue(Device, &Queue2));
+  ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queue2));
 
   // For the explanation of the reasoning behind particular values assigned to
   // parameters, see the comment in the Success test from the same test suite
diff --git a/offload/unittests/OffloadAPI/memory/olMemcpy.cpp b/offload/unittests/OffloadAPI/memory/olMemcpy.cpp
index 3d210e3d6d015..9ecc8b700b7f5 100644
--- a/offload/unittests/OffloadAPI/memory/olMemcpy.cpp
+++ b/offload/unittests/OffloadAPI/memory/olMemcpy.cpp
@@ -20,7 +20,7 @@ struct olMemcpyGlobalTest : OffloadGlobalTest {
         olGetSymbol(Program, "read", OL_SYMBOL_KIND_KERNEL, &ReadKernel));
     ASSERT_SUCCESS(
         olGetSymbol(Program, "write", OL_SYMBOL_KIND_KERNEL, &WriteKernel));
-    ASSERT_SUCCESS(olCreateQueue(Device, &Queue));
+    ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queue));
     ASSERT_SUCCESS(olGetSymbolInfo(
         Global, OL_SYMBOL_INFO_GLOBAL_VARIABLE_ADDRESS, sizeof(Addr), &Addr));
 
diff --git a/offload/unittests/OffloadAPI/queue/olCreateQueue.cpp b/offload/unittests/OffloadAPI/queue/olCreateQueue.cpp
index 8a2b964c6ed42..986b84702d866 100644
--- a/offload/unittests/OffloadAPI/queue/olCreateQueue.cpp
+++ b/offload/unittests/OffloadAPI/queue/olCreateQueue.cpp
@@ -15,15 +15,31 @@ OFFLOAD_TESTS_INSTANTIATE_DEVICE_FIXTURE(olCreateQueueTest);
 
 TEST_P(olCreateQueueTest, Success) {
   ol_queue_handle_t Queue = nullptr;
-  ASSERT_SUCCESS(olCreateQueue(Device, &Queue));
+  ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queue));
   ASSERT_NE(Queue, nullptr);
+  ASSERT_SUCCESS(olDestroyQueue(Queue));
+}
+
+TEST_P(olCreateQueueTest, InvalidNullHandleContext) {
+  ol_queue_handle_t Queue = nullptr;
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE,
+               olCreateQueue(nullptr, Device, &Queue));
 }
 
 TEST_P(olCreateQueueTest, InvalidNullHandleDevice) {
   ol_queue_handle_t Queue = nullptr;
-  ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE, olCreateQueue(nullptr, &Queue));
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE,
+               olCreateQueue(Context, nullptr, &Queue));
 }
 
 TEST_P(olCreateQueueTest, InvalidNullPointerQueue) {
-  ASSERT_ERROR(OL_ERRC_INVALID_NULL_POINTER, olCreateQueue(Device, nullptr));
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_POINTER,
+               olCreateQueue(Context, Device, nullptr));
+}
+
+TEST_P(olCreateQueueTest, InvalidDeviceNotInContext) {
+  if (Host == Device)
+    GTEST_SKIP() << "Host device is the fixture device; cannot test.";
+  ol_queue_handle_t Queue = nullptr;
+  ASSERT_ERROR(OL_ERRC_INVALID_DEVICE, olCreateQueue(Context, Host, &Queue));
 }
diff --git a/offload/unittests/OffloadAPI/queue/olGetQueueInfo.cpp b/offload/unittests/OffloadAPI/queue/olGetQueueInfo.cpp
index 2dccd33005563..f40319a04a2de 100644
--- a/offload/unittests/OffloadAPI/queue/olGetQueueInfo.cpp
+++ b/offload/unittests/OffloadAPI/queue/olGetQueueInfo.cpp
@@ -20,6 +20,14 @@ TEST_P(olGetQueueInfoTest, SuccessDevice) {
   ASSERT_EQ(Device, RetrievedDevice);
 }
 
+TEST_P(olGetQueueInfoTest, SuccessContext) {
+  ol_context_handle_t RetrievedContext;
+  ASSERT_SUCCESS(olGetQueueInfo(Queue, OL_QUEUE_INFO_CONTEXT,
+                                sizeof(ol_context_handle_t),
+                                &RetrievedContext));
+  ASSERT_EQ(Context, RetrievedContext);
+}
+
 TEST_P(olGetQueueInfoTest, SuccessEmpty) {
   bool Empty;
   ASSERT_SUCCESS(
diff --git a/offload/unittests/OffloadAPI/queue/olGetQueueInfoSize.cpp b/offload/unittests/OffloadAPI/queue/olGetQueueInfoSize.cpp
index 735dad6a29384..3163f74d71d96 100644
--- a/offload/unittests/OffloadAPI/queue/olGetQueueInfoSize.cpp
+++ b/offload/unittests/OffloadAPI/queue/olGetQueueInfoSize.cpp
@@ -19,6 +19,12 @@ TEST_P(olGetQueueInfoSizeTest, SuccessDevice) {
   ASSERT_EQ(Size, sizeof(ol_device_handle_t));
 }
 
+TEST_P(olGetQueueInfoSizeTest, SuccessContext) {
+  size_t Size = 0;
+  ASSERT_SUCCESS(olGetQueueInfoSize(Queue, OL_QUEUE_INFO_CONTEXT, &Size));
+  ASSERT_EQ(Size, sizeof(ol_context_handle_t));
+}
+
 TEST_P(olGetQueueInfoSizeTest, SuccessEmpty) {
   size_t Size = 0;
   ASSERT_SUCCESS(olGetQueueInfoSize(Queue, OL_QUEUE_INFO_EMPTY, &Size));
diff --git a/offload/unittests/OffloadAPI/queue/olLaunchHostFunction.cpp b/offload/unittests/OffloadAPI/queue/olLaunchHostFunction.cpp
index 28d9b78c35f76..1dedf7ecaaefd 100644
--- a/offload/unittests/OffloadAPI/queue/olLaunchHostFunction.cpp
+++ b/offload/unittests/OffloadAPI/queue/olLaunchHostFunction.cpp
@@ -57,7 +57,7 @@ TEST_P(olLaunchHostFunctionKernelTest, SuccessBlocking) {
   LaunchArgs.DynSharedMemory = 0;
 
   ol_queue_handle_t Queue;
-  ASSERT_SUCCESS(olCreateQueue(Device, &Queue));
+  ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queue));
 
   void *Mem;
   ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_MANAGED,
diff --git a/offload/unittests/OffloadAPI/queue/olWaitEvents.cpp b/offload/unittests/OffloadAPI/queue/olWaitEvents.cpp
index 6396b7cd0a748..112785c1a066f 100644
--- a/offload/unittests/OffloadAPI/queue/olWaitEvents.cpp
+++ b/offload/unittests/OffloadAPI/queue/olWaitEvents.cpp
@@ -45,7 +45,7 @@ TEST_P(olWaitEventsTest, Success) {
   for (size_t I = 0; I < NUM_KERNELS; I++) {
     Idx = I;
 
-    ASSERT_SUCCESS(olCreateQueue(Device, &Queues[I]));
+    ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queues[I]));
 
     if (I > 0)
       ASSERT_SUCCESS(olWaitEvents(Queues[I], &Events[I - 1], 1));
@@ -68,7 +68,7 @@ TEST_P(olWaitEventsTest, SuccessSingleQueue) {
   ol_queue_handle_t Queue;
   ol_event_handle_t Events[NUM_KERNELS];
 
-  ASSERT_SUCCESS(olCreateQueue(Device, &Queue));
+  ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queue));
 
   void *Mem;
   ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_MANAGED,
@@ -111,7 +111,7 @@ TEST_P(olWaitEventsTest, SuccessMultipleEvents) {
   for (size_t I = 0; I < NUM_KERNELS; I++) {
     Idx = I;
 
-    ASSERT_SUCCESS(olCreateQueue(Device, &Queues[I]));
+    ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queues[I]));
 
     if (I > 0)
       ASSERT_SUCCESS(olWaitEvents(Queues[I], Events, I));
@@ -136,13 +136,13 @@ TEST_P(olWaitEventsTest, InvalidNullQueue) {
 
 TEST_P(olWaitEventsTest, InvalidNullEvent) {
   ol_queue_handle_t Queue;
-  ASSERT_SUCCESS(olCreateQueue(Device, &Queue));
+  ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queue));
   ASSERT_ERROR(OL_ERRC_INVALID_NULL_POINTER, olWaitEvents(Queue, nullptr, 1));
 }
 
 TEST_P(olWaitEventsTest, InvalidNullInnerEvent) {
   ol_queue_handle_t Queue;
-  ASSERT_SUCCESS(olCreateQueue(Device, &Queue));
+  ASSERT_SUCCESS(olCreateQueue(Context, Device, &Queue));
   ol_event_handle_t Event = nullptr;
   ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE, olWaitEvents(Queue, &Event, 1));
 }

>From 123808069827d7cec9f286fa06de2e35446eaa83 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20Plewa?= <lukasz.plewa at intel.com>
Date: Fri, 31 Jul 2026 16:16:16 +0200
Subject: [PATCH 2/2] review fixes

---
 offload/liboffload/src/OffloadImpl.cpp        |  3 +++
 .../common/include/PluginInterface.h          |  5 ++++
 .../level_zero/include/L0Device.h             |  7 ++++--
 .../level_zero/include/L0Plugin.h             | 12 ++++++---
 .../level_zero/src/L0Context.cpp              |  6 ++++-
 .../level_zero/src/L0Device.cpp               | 20 ++++++---------
 .../level_zero/src/L0Plugin.cpp               | 25 ++++++-------------
 7 files changed, 43 insertions(+), 35 deletions(-)

diff --git a/offload/liboffload/src/OffloadImpl.cpp b/offload/liboffload/src/OffloadImpl.cpp
index d6a2b466cb92d..40f5ef6ce8b27 100644
--- a/offload/liboffload/src/OffloadImpl.cpp
+++ b/offload/liboffload/src/OffloadImpl.cpp
@@ -641,6 +641,9 @@ Error olCreateContext_impl(size_t DevicesCount, ol_device_handle_t *Devices,
 Error olDestroyContext_impl(ol_context_handle_t Context) {
   if (auto Err = Context->drainOutstandingQueues())
     return Err;
+  if (Context->PluginCtx)
+    if (auto Err = Context->PluginCtx->deinit())
+      return Err;
   return olDestroy(Context);
 }
 
diff --git a/offload/plugins-nextgen/common/include/PluginInterface.h b/offload/plugins-nextgen/common/include/PluginInterface.h
index 250f9d66a1afd..ae62b7f12e631 100644
--- a/offload/plugins-nextgen/common/include/PluginInterface.h
+++ b/offload/plugins-nextgen/common/include/PluginInterface.h
@@ -880,6 +880,11 @@ struct PluginContextTy {
 
   virtual ~PluginContextTy() = default;
 
+  /// Release resources owned by this context. Called from olDestroyContext
+  /// before the object is destroyed so that errors are propagated instead of
+  /// being swallowed in the destructor.
+  virtual llvm::Error deinit() { return llvm::Error::success(); }
+
   llvm::ArrayRef<GenericDeviceTy *> getDevices() const { return Devices; }
   GenericPluginTy &getPlugin() const { return Plugin; }
 
diff --git a/offload/plugins-nextgen/level_zero/include/L0Device.h b/offload/plugins-nextgen/level_zero/include/L0Device.h
index 314b2df3e9ddd..71e545ad89231 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Device.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Device.h
@@ -471,8 +471,11 @@ class L0DeviceTy final : public GenericDeviceTy {
   bool supportsLargeMem() const { return L0Context.supportsLargeMem(); }
 
   /// Returns the Queue from an async info object, or creates a new one if
-  /// the async info does not have a queue yet.
-  Expected<L0QueueTy *> getOrCreateQueue(__tgt_async_info *AsyncInfo);
+  /// the async info does not have a queue yet. When \p UserCtx is null the
+  /// driver's default plugin-side context is used.
+  Expected<L0QueueTy *>
+  getOrCreateQueue(__tgt_async_info *AsyncInfo,
+                   LevelZeroPluginContextTy *UserCtx = nullptr);
   void releaseQueue(L0QueueTy *Queue);
 
   // Allocation related routines.
diff --git a/offload/plugins-nextgen/level_zero/include/L0Plugin.h b/offload/plugins-nextgen/level_zero/include/L0Plugin.h
index f84e7cbeca0d2..3909f1a9e48e4 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Plugin.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Plugin.h
@@ -32,16 +32,22 @@ class LevelZeroPluginContextTy final : public PluginContextTy {
       : PluginContextTy(Plugin, Devices), Driver(Driver), ZeContext(ZeContext),
         OwnsZeContext(OwnsZeContext), QueueCache(*this) {}
 
-  ~LevelZeroPluginContextTy() override;
+  ~LevelZeroPluginContextTy() override = default;
+
+  Error deinit() override;
 
   ze_driver_handle_t getZeDriver() const { return Driver; }
   ze_context_handle_t getZeContext() const { return ZeContext; }
 
   /// Pop an idle queue for \p Device from the cache, or create a new one.
-  Expected<L0QueueTy *> takeCachedQueue(L0DeviceTy *Device);
+  Expected<L0QueueTy *> takeCachedQueue(L0DeviceTy *Device) {
+    return QueueCache.getQueue(*Device);
+  }
 
   /// Return an idle queue to the cache.
-  void returnCachedQueue(L0DeviceTy *Device, L0QueueTy *Queue);
+  void returnCachedQueue(L0DeviceTy *Device, L0QueueTy *Queue) {
+    QueueCache.releaseQueue(*Device, Queue);
+  }
 
 private:
   ze_driver_handle_t Driver;
diff --git a/offload/plugins-nextgen/level_zero/src/L0Context.cpp b/offload/plugins-nextgen/level_zero/src/L0Context.cpp
index 4cab00ff0b5e1..cdcd1210cbcf1 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Context.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Context.cpp
@@ -83,7 +83,11 @@ Error L0ContextTy::init() {
 
 Error L0ContextTy::deinit() {
   // Release the default context (drains its queue cache) before zeContext.
-  DefaultUserCtx.reset();
+  if (DefaultUserCtx) {
+    if (auto Err = DefaultUserCtx->deinit())
+      return Err;
+    DefaultUserCtx.reset();
+  }
   if (auto Err = EventPool.deinit())
     return Err;
   if (auto Err = HostMemAllocator.deinit())
diff --git a/offload/plugins-nextgen/level_zero/src/L0Device.cpp b/offload/plugins-nextgen/level_zero/src/L0Device.cpp
index b7350613f7faa..e3460891f3f52 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Device.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Device.cpp
@@ -262,10 +262,12 @@ void L0DeviceTy::releaseQueue(L0QueueTy *Queue) {
 }
 
 Expected<L0QueueTy *>
-L0DeviceTy::getOrCreateQueue(__tgt_async_info *AsyncInfo) {
+L0DeviceTy::getOrCreateQueue(__tgt_async_info *AsyncInfo,
+                             LevelZeroPluginContextTy *UserCtx) {
   L0QueueTy *Queue = static_cast<L0QueueTy *>(AsyncInfo->Queue);
   if (!Queue) {
-    auto NewQueueOrErr = L0Context.getDefaultUserCtx().takeCachedQueue(this);
+    auto &Ctx = UserCtx ? *UserCtx : L0Context.getDefaultUserCtx();
+    auto NewQueueOrErr = Ctx.takeCachedQueue(this);
     if (!NewQueueOrErr)
       return NewQueueOrErr.takeError();
     Queue = *NewQueueOrErr;
@@ -401,16 +403,10 @@ Error L0DeviceTy::dataExchangeImpl(const void *SrcPtr, GenericDeviceTy &DstDev,
 }
 
 Error L0DeviceTy::initAsyncInfoImpl(AsyncInfoWrapperTy &AsyncInfoWrapper) {
-  __tgt_async_info *AsyncInfo = AsyncInfoWrapper;
-  auto *L0Ctx = AsyncInfoWrapper.getContext()
-                    ? static_cast<LevelZeroPluginContextTy *>(
-                          AsyncInfoWrapper.getContext())
-                    : &L0Context.getDefaultUserCtx();
-  auto NewQueueOrErr = L0Ctx->takeCachedQueue(this);
-  if (!NewQueueOrErr)
-    return NewQueueOrErr.takeError();
-  AsyncInfo->Queue = *NewQueueOrErr;
-  return Plugin::success();
+  auto *UserCtx =
+      static_cast<LevelZeroPluginContextTy *>(AsyncInfoWrapper.getContext());
+  auto QueueOrErr = getOrCreateQueue(AsyncInfoWrapper, UserCtx);
+  return QueueOrErr ? Plugin::success() : QueueOrErr.takeError();
 }
 
 const char *L0DeviceTy::getArchCStr() const {
diff --git a/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp b/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp
index 77b997d912864..be6b31eeb1167 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp
@@ -254,24 +254,15 @@ Error LevelZeroPluginTy::asyncBarrierImpl(omp_interop_val_t *Interop) {
   return Plugin::success();
 }
 
-LevelZeroPluginContextTy::~LevelZeroPluginContextTy() {
-  // TODO: this should be moved out of the destructor and into a deinit() method
-  if (auto Err = QueueCache.deinit()) {
-    REPORT() << "Error deinitializing LevelZeroPluginContextTy queue cache: "
-             << toString(std::move(Err));
+Error LevelZeroPluginContextTy::deinit() {
+  if (auto Err = QueueCache.deinit())
+    return Err;
+  if (OwnsZeContext && ZeContext) {
+    CALL_ZE_RET_ERROR(zeContextDestroy, ZeContext);
+    ZeContext = nullptr;
+    OwnsZeContext = false;
   }
-  if (OwnsZeContext && ZeContext)
-    zeContextDestroy(ZeContext);
-}
-
-Expected<L0QueueTy *>
-LevelZeroPluginContextTy::takeCachedQueue(L0DeviceTy *Device) {
-  return QueueCache.getQueue(*Device);
-}
-
-void LevelZeroPluginContextTy::returnCachedQueue(L0DeviceTy *Device,
-                                                 L0QueueTy *Queue) {
-  QueueCache.releaseQueue(*Device, Queue);
+  return Plugin::success();
 }
 
 Expected<std::unique_ptr<PluginContextTy>>



More information about the llvm-commits mailing list