[llvm] [libsycl] Add context to exception's throw. (PR #224668)

Kseniya Tikhomirova via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 24 09:52:07 PDT 2026


https://github.com/KseniyaTikhomirova updated https://github.com/llvm/llvm-project/pull/224668

>From 503fe1d6c08e95424b05b488e6a47d3dee0996ec Mon Sep 17 00:00:00 2001
From: "Tikhomirova, Kseniya" <kseniya.tikhomirova at intel.com>
Date: Fri, 18 Sep 2026 07:18:00 -0700
Subject: [PATCH 1/4] [libsycl] Add context to exception's throw

Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova at intel.com>
---
 libsycl/include/sycl/__impl/exception.hpp     |   4 +-
 libsycl/src/detail/context_impl.cpp           |  10 +-
 libsycl/src/detail/device_image_wrapper.cpp   |  17 +--
 libsycl/src/detail/device_image_wrapper.hpp   |   7 +-
 libsycl/src/detail/global_objects.cpp         |   7 ++
 libsycl/src/detail/offload/offload_utils.hpp  |  36 +++++++
 libsycl/src/detail/queue_impl.cpp             |  66 +++++++-----
 libsycl/src/handler.cpp                       |   9 +-
 libsycl/src/usm_functions.cpp                 |  18 ++--
 libsycl/unittests/common/unittests_helper.hpp |  12 +--
 libsycl/unittests/handler/semantics.cpp       |   4 +
 libsycl/unittests/mock/helpers.hpp            |  11 +-
 libsycl/unittests/queue/memcpy.cpp            | 101 ++++++++++++++++++
 libsycl/unittests/queue/prefetch.cpp          |  18 ++++
 libsycl/unittests/queue/queue.cpp             |  44 ++++++++
 libsycl/unittests/usm/alloc.cpp               |  76 +++++++++++++
 16 files changed, 381 insertions(+), 59 deletions(-)

diff --git a/libsycl/include/sycl/__impl/exception.hpp b/libsycl/include/sycl/__impl/exception.hpp
index c3c8ff32a1e05..588083a45a19b 100644
--- a/libsycl/include/sycl/__impl/exception.hpp
+++ b/libsycl/include/sycl/__impl/exception.hpp
@@ -165,9 +165,11 @@ class _LIBSYCL_EXPORT exception : public virtual std::exception {
 private:
   exception(std::error_code EC, std::shared_ptr<context> SharedPtrCtx,
             const char *WhatArg);
+
   // Exceptions must be noexcept copy constructible, so cannot use std::string
-  // or context directly.
+  // directly.
   std::shared_ptr<std::string> MMessage;
+
   std::shared_ptr<context> MContext;
   std::error_code MErrC = make_error_code(sycl::errc::invalid);
 };
diff --git a/libsycl/src/detail/context_impl.cpp b/libsycl/src/detail/context_impl.cpp
index 0525cee4535c7..7469491281242 100644
--- a/libsycl/src/detail/context_impl.cpp
+++ b/libsycl/src/detail/context_impl.cpp
@@ -75,10 +75,10 @@ ContextImpl::getOrCreateKernel(const DeviceImageManager &DeviceImage,
     // used rather than emplace: the latter would build a program even when one
     // is already cached, only to destroy it again.
     try {
-      ProgramIt = ProgramsForImage
-                      .try_emplace(DeviceHandle, MOffloadContext, DeviceHandle,
-                                   DeviceImage)
-                      .first;
+      ProgramIt =
+          ProgramsForImage
+              .try_emplace(DeviceHandle, *this, DeviceHandle, DeviceImage)
+              .first;
     } catch (...) {
       // Do not leave an empty entry behind if program creation failed.
       if (ProgramsForImage.empty())
@@ -87,7 +87,7 @@ ContextImpl::getOrCreateKernel(const DeviceImageManager &DeviceImage,
     }
   }
 
-  return ProgramIt->second.getOrCreateKernel(KernelName);
+  return ProgramIt->second.getOrCreateKernel(KernelName, *this);
 }
 
 void ContextImpl::releaseProgramsForImage(
diff --git a/libsycl/src/detail/device_image_wrapper.cpp b/libsycl/src/detail/device_image_wrapper.cpp
index 7f5582f3b680b..76b759eec11c7 100644
--- a/libsycl/src/detail/device_image_wrapper.cpp
+++ b/libsycl/src/detail/device_image_wrapper.cpp
@@ -8,20 +8,20 @@
 
 #include <detail/device_image_wrapper.hpp>
 
+#include <detail/context_impl.hpp>
 #include <detail/offload/offload_utils.hpp>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace detail {
 
-ProgramWrapper::ProgramWrapper(ol_context_handle_t Context,
-                               ol_device_handle_t Device,
+ProgramWrapper::ProgramWrapper(ContextImpl &Context, ol_device_handle_t Device,
                                const DeviceImageManager &DevImage) {
-  assert(Context);
+  assert(Context.getOLHandleRef());
   assert(Device);
 
   llvm::StringRef Image = DevImage.getOffloadBinary().getImage();
-  callAndThrow(olCreateProgram, Context, Device, Image.data(), Image.size(),
-               &MProgram);
+  callAndThrow(Context, olCreateProgram, Context.getOLHandleRef(), Device,
+               Image.data(), Image.size(), &MProgram);
 }
 
 ProgramWrapper::~ProgramWrapper() {
@@ -31,14 +31,15 @@ ProgramWrapper::~ProgramWrapper() {
 }
 
 ol_symbol_handle_t
-ProgramWrapper::getOrCreateKernel(std::string_view KernelName) {
+ProgramWrapper::getOrCreateKernel(std::string_view KernelName,
+                                  ContextImpl &Context) {
   auto It = MKernels.find(KernelName);
   if (It != MKernels.end())
     return It->second;
 
   ol_symbol_handle_t Kernel{};
-  callAndThrow(olGetSymbol, MProgram, KernelName.data(), OL_SYMBOL_KIND_KERNEL,
-               &Kernel);
+  callAndThrow(Context, olGetSymbol, MProgram, KernelName.data(),
+               OL_SYMBOL_KIND_KERNEL, &Kernel);
   MKernels.emplace(KernelName, Kernel);
   return Kernel;
 }
diff --git a/libsycl/src/detail/device_image_wrapper.hpp b/libsycl/src/detail/device_image_wrapper.hpp
index 5d639a2fe2970..e67ee77208d07 100644
--- a/libsycl/src/detail/device_image_wrapper.hpp
+++ b/libsycl/src/detail/device_image_wrapper.hpp
@@ -28,6 +28,7 @@
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace detail {
 
+class ContextImpl;
 class DeviceImageManager;
 
 /// A wrapper of liboffload program handle to manage its lifetime.
@@ -41,7 +42,7 @@ class ProgramWrapper {
   /// \param DevImage is the device image to use for program creation.
   /// \throw sycl::exception with sycl::errc::runtime when failed to create the
   /// program.
-  ProgramWrapper(ol_context_handle_t Context, ol_device_handle_t Device,
+  ProgramWrapper(ContextImpl &Context, ol_device_handle_t Device,
                  const DeviceImageManager &DevImage);
 
   /// Releases the corresponding liboffload program handle by calling
@@ -65,10 +66,12 @@ class ProgramWrapper {
   /// for a program it does not belong to.
   ///
   /// \param KernelName the name of the kernel to look up.
+  /// \param Context is the context this program belongs to.
   /// \throw sycl::exception with sycl::errc::runtime when the symbol lookup
   /// fails.
   /// \return the liboffload symbol handle of the kernel.
-  ol_symbol_handle_t getOrCreateKernel(std::string_view KernelName);
+  ol_symbol_handle_t getOrCreateKernel(std::string_view KernelName,
+                                       ContextImpl &Context);
 
 private:
   ol_program_handle_t MProgram{};
diff --git a/libsycl/src/detail/global_objects.cpp b/libsycl/src/detail/global_objects.cpp
index aa45961a1bee0..9ebd306364b4c 100644
--- a/libsycl/src/detail/global_objects.cpp
+++ b/libsycl/src/detail/global_objects.cpp
@@ -35,6 +35,13 @@ struct StaticVarShutdownHandler {
   operator=(const StaticVarShutdownHandler &) = delete;
   ~StaticVarShutdownHandler() {
     ProgramAndKernelManager::getInstance().releaseResources();
+    {
+      auto &[AsyncExceptions, AsyncExceptionsMutex] = getAsyncExceptionList();
+      {
+        std::lock_guard<SpinLock> Lock(AsyncExceptionsMutex);
+        AsyncExceptions.clear();
+      }
+    }
     // No error reporting in shutdown
     std::ignore = olShutDown();
   }
diff --git a/libsycl/src/detail/offload/offload_utils.hpp b/libsycl/src/detail/offload/offload_utils.hpp
index f565ca86aef4d..45c6274aa6732 100644
--- a/libsycl/src/detail/offload/offload_utils.hpp
+++ b/libsycl/src/detail/offload/offload_utils.hpp
@@ -16,6 +16,7 @@
 #define _LIBSYCL_OFFLOAD_UTILS
 
 #include <sycl/__impl/backend.hpp>
+#include <sycl/__impl/context.hpp>
 #include <sycl/__impl/detail/config.hpp>
 #include <sycl/__impl/detail/unified_range_view.hpp>
 #include <sycl/__impl/exception.hpp>
@@ -28,6 +29,8 @@ _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 namespace detail {
 
+class ContextImpl;
+
 /// Converts liboffload error code to C-string.
 ///
 /// \param Error liboffload error code.
@@ -64,6 +67,25 @@ void checkAndThrow(ol_result_t Result) {
   }
 }
 
+/// Checks liboffload API call result, attaches the context to the exception.
+///
+/// Used after calling the API without check.
+/// To be called when specific handling is needed and explicitly done by
+/// developer before throwing exception.
+///
+/// \param Context context the failed API call was made for.
+/// \param Result liboffload result of calling API.
+///
+/// \throw sycl::exception if the call was not successful.
+template <sycl::errc errc = sycl::errc::runtime>
+void checkAndThrow(ContextImpl &Context, ol_result_t Result) {
+  if (isFailed(Result)) {
+    throw sycl::exception(createSyclObjFromImpl<sycl::context>(Context),
+                          sycl::make_error_code(errc),
+                          detail::formatCodeString(Result));
+  }
+}
+
 /// Calls the API, doesn't check result.
 /// To be called when specific handling is needed and explicitly done by
 /// developer after.
@@ -89,6 +111,20 @@ void callAndThrow(FunctionType &Function, ArgsT &&...Args) {
   checkAndThrow(Err);
 }
 
+/// Calls the API and checks result, attaches the context to the exception.
+///
+/// \param Context context the API call is made for.
+/// \param Function liboffload API function to be called.
+/// \param Args arguments to be passed to the liboffload API function.
+///
+/// \throw sycl::exception if the call was not successful.
+template <typename FunctionType, typename... ArgsT>
+void callAndThrow(ContextImpl &Context, FunctionType &Function,
+                  ArgsT &&...Args) {
+  auto Err = callNoCheck(Function, std::forward<ArgsT>(Args)...);
+  checkAndThrow(Context, Err);
+}
+
 /// Converts liboffload backend to SYCL backend.
 ///
 /// \param Backend liboffload backend.
diff --git a/libsycl/src/detail/queue_impl.cpp b/libsycl/src/detail/queue_impl.cpp
index c6f3b2bd86cbc..31419254a0c01 100644
--- a/libsycl/src/detail/queue_impl.cpp
+++ b/libsycl/src/detail/queue_impl.cpp
@@ -24,9 +24,10 @@ namespace detail {
 thread_local bool NestedCallsDetector = false;
 class NestedCallsTracker {
 public:
-  NestedCallsTracker() {
+  NestedCallsTracker(ContextImpl &QueueContext) {
     if (NestedCallsDetectorRef)
       throw sycl::exception(
+          createSyclObjFromImpl<context>(QueueContext),
           make_error_code(errc::invalid),
           "Calls to sycl::queue::submit cannot be nested. Command group "
           "function objects should use the sycl::handler API instead.");
@@ -52,9 +53,10 @@ QueueImpl::QueueImpl(const std::shared_ptr<ContextImpl> &contextImpl,
   // liboffload guarantees OL_ERRC_INVALID_DEVICE when the device does not
   // belong to the context.
   if (isFailed(Err) && Err->Code == OL_ERRC_INVALID_DEVICE)
-    throw sycl::exception(sycl::make_error_code(sycl::errc::invalid),
+    throw sycl::exception(createSyclObjFromImpl<context>(*MContext),
+                          sycl::make_error_code(sycl::errc::invalid),
                           "The device is not associated with the context.");
-  checkAndThrow(Err);
+  checkAndThrow(*MContext, Err);
 }
 
 QueueImpl::~QueueImpl() {
@@ -77,7 +79,10 @@ static ol_device_handle_t getHostOLDevice() {
   return HostDevice;
 }
 
-void QueueImpl::wait() { callAndThrow(olSyncQueue, MOffloadQueue); }
+void QueueImpl::wait() {
+  assert(MContext && "Context impl ptr can't be nullptr");
+  callAndThrow(*MContext, olSyncQueue, MOffloadQueue);
+}
 
 void QueueImpl::waitAndThrow() {
   wait();
@@ -87,7 +92,8 @@ void QueueImpl::waitAndThrow() {
 void QueueImpl::throwAsynchronous() { flushAsyncExceptions(); }
 
 static void checkEventsPlatformMatch(const std::vector<EventImplPtr> &Events,
-                                     const PlatformImpl &QueuePlatform) {
+                                     const PlatformImpl &QueuePlatform,
+                                     ContextImpl &QueueContext) {
   // liboffload limitation to olWaitEvents. We can't do any extra handling for
   // cross context/platform events without host task support now.
   //   "The input events can be from any queue on any device provided by the
@@ -97,6 +103,7 @@ static void checkEventsPlatformMatch(const std::vector<EventImplPtr> &Events,
                      return &Event->getPlatformImpl() == &QueuePlatform;
                    })) {
     throw sycl::exception(
+        createSyclObjFromImpl<context>(QueueContext),
         sycl::make_error_code(sycl::errc::feature_not_supported),
         "libsycl doesn't support cross-context/platform event dependencies "
         "yet.");
@@ -111,13 +118,15 @@ void QueueImpl::setKernelLaunchParams(std::vector<EventImplPtr> &&Events,
 void QueueImpl::setKernelLaunchParams(
     std::vector<EventImplPtr> &&Events,
     const ol_kernel_launch_size_args_t &Range) {
-  checkEventsPlatformMatch(Events, MDevice.getPlatformImpl());
+  assert(MContext && "Context impl ptr can't be nullptr");
+  checkEventsPlatformMatch(Events, MDevice.getPlatformImpl(), *MContext);
   MCurrentSubmitInfo.DepEvents = std::move(Events);
   MCurrentSubmitInfo.Range = Range;
 }
 
 void QueueImpl::submitKernelImpl(DeviceKernelInfo &KernelInfo, void *ArgData,
                                  size_t ArgSize) {
+  assert(MContext && "Context impl ptr can't be nullptr");
   ol_symbol_handle_t Kernel =
       detail::ProgramAndKernelManager::getInstance().getOrCreateKernel(
           KernelInfo, MContext, MDevice);
@@ -135,7 +144,8 @@ void QueueImpl::submitKernelImpl(DeviceKernelInfo &KernelInfo, void *ArgData,
                      &MCurrentSubmitInfo.Range, NULL, 1, ArgPtrs, ArgSizes);
 
   if (isFailed(Result))
-    throw sycl::exception(sycl::make_error_code(sycl::errc::runtime),
+    throw sycl::exception(createSyclObjFromImpl<context>(*MContext),
+                          sycl::make_error_code(sycl::errc::runtime),
                           std::string("Kernel submission (") +
                               KernelInfo.getName().data() + ") failed with " +
                               formatCodeString(Result));
@@ -144,13 +154,13 @@ void QueueImpl::submitKernelImpl(DeviceKernelInfo &KernelInfo, void *ArgData,
       createEvent(std::move(MCurrentSubmitInfo.DepEvents));
 }
 
-static ol_device_handle_t getAllocDevice(ol_context_handle_t Context,
+static ol_device_handle_t getAllocDevice(ContextImpl &Context,
                                          const void *ptr) {
   // TODO: consider caching this information to avoid querying it every time.
   ol_device_handle_t Device{};
   [[maybe_unused]] ol_result_t Result =
-      callNoCheck(olGetMemInfo, Context, ptr, OL_MEM_INFO_DEVICE,
-                  sizeof(ol_device_handle_t), &Device);
+      callNoCheck(olGetMemInfo, Context.getOLHandleRef(), ptr,
+                  OL_MEM_INFO_DEVICE, sizeof(ol_device_handle_t), &Device);
   if (detail::isFailed(Result)) {
     // NOT_FOUND: the pointer isn't a liboffload allocation at all (plain host
     // malloc). INVALID_ARGUMENT: it's a liboffload host allocation, which has
@@ -159,7 +169,7 @@ static ol_device_handle_t getAllocDevice(ol_context_handle_t Context,
         Result->Code == OL_ERRC_INVALID_ARGUMENT) {
       return getHostOLDevice();
     }
-    checkAndThrow(Result);
+    checkAndThrow(Context, Result);
   }
 
   assert(Device);
@@ -169,37 +179,39 @@ static ol_device_handle_t getAllocDevice(ol_context_handle_t Context,
 std::shared_ptr<EventImpl>
 QueueImpl::memcpy(void *Dest, const void *Src, std::size_t NumBytes,
                   const std::vector<EventImplPtr> &DepEvents) {
-  checkEventsPlatformMatch(DepEvents, MDevice.getPlatformImpl());
+  assert(MContext && "Context impl ptr can't be nullptr");
+  checkEventsPlatformMatch(DepEvents, MDevice.getPlatformImpl(), *MContext);
   if (NumBytes == 0) {
     return submitWait(DepEvents);
   }
 
   if (!Dest || !Src) {
-    throw sycl::exception(sycl::make_error_code(sycl::errc::invalid),
+    throw sycl::exception(createSyclObjFromImpl<context>(*MContext),
+                          sycl::make_error_code(sycl::errc::invalid),
                           "Nullptr argument in memcpy operation");
   }
 
-  ol_device_handle_t DestOLDevice =
-      getAllocDevice(MContext->getOLHandleRef(), Dest);
-  ol_device_handle_t SrcOLDevice =
-      getAllocDevice(MContext->getOLHandleRef(), Src);
+  ol_device_handle_t DestOLDevice = getAllocDevice(*MContext, Dest);
+  ol_device_handle_t SrcOLDevice = getAllocDevice(*MContext, Src);
 
   handleEventDependencies(DepEvents);
-  callAndThrow(olMemcpy, MOffloadQueue, Dest, DestOLDevice, Src, SrcOLDevice,
-               NumBytes);
+  callAndThrow(*MContext, olMemcpy, MOffloadQueue, Dest, DestOLDevice, Src,
+               SrcOLDevice, NumBytes);
   return createEvent();
 }
 
 EventImplPtr QueueImpl::prefetch(void *Ptr, std::size_t NumBytes,
                                  const std::vector<EventImplPtr> &DepEvents) {
-  checkEventsPlatformMatch(DepEvents, MDevice.getPlatformImpl());
+  assert(MContext && "Context impl ptr can't be nullptr");
+  checkEventsPlatformMatch(DepEvents, MDevice.getPlatformImpl(), *MContext);
 
   if (NumBytes == 0) {
     handleEventDependencies(DepEvents);
     return createEvent();
   }
   if (!Ptr) {
-    throw sycl::exception(sycl::make_error_code(sycl::errc::invalid),
+    throw sycl::exception(createSyclObjFromImpl<context>(*MContext),
+                          sycl::make_error_code(sycl::errc::invalid),
                           "Nullptr argument in prefetch operation");
   }
 
@@ -211,12 +223,14 @@ EventImplPtr QueueImpl::prefetch(void *Ptr, std::size_t NumBytes,
       OL_MEM_MIGRATION_FLAG_HOST_TO_DEVICE;
 
   handleEventDependencies(DepEvents);
-  callAndThrow(olMemPrefetch, MOffloadQueue, Count, Mems, Sizes, Flag);
+  callAndThrow(*MContext, olMemPrefetch, MOffloadQueue, Count, Mems, Sizes,
+               Flag);
 
   return createEvent();
 }
 
 void QueueImpl::handleEventDependencies(const std::vector<EventImplPtr> &Deps) {
+  assert(MContext && "Context impl ptr can't be nullptr");
   // TODO: liboffload supports only in-order queues and no cross context waiting
   // is available now that means that this code is excessive but correct. I
   // don't want to skip it and rely on default liboffload behaviour that is
@@ -225,24 +239,26 @@ void QueueImpl::handleEventDependencies(const std::vector<EventImplPtr> &Deps) {
   // context dependencies should be enabled and checked as well.
   if (!Deps.empty()) {
     auto EventHandles = getSyclObjHandles(Deps);
-    callAndThrow(olWaitEvents, MOffloadQueue, EventHandles.data(),
+    callAndThrow(*MContext, olWaitEvents, MOffloadQueue, EventHandles.data(),
                  EventHandles.size());
   }
 }
 
 EventImplPtr QueueImpl::createEvent(std::vector<EventImplPtr> &&Deps) {
+  assert(MContext && "Context impl ptr can't be nullptr");
   ol_event_handle_t NewEvent{};
   ol_event_flags_t Flags{};
-  callAndThrow(olCreateEvent, MOffloadQueue, Flags, &NewEvent);
+  callAndThrow(*MContext, olCreateEvent, MOffloadQueue, Flags, &NewEvent);
   return EventImpl::createEventWithHandle(NewEvent, MDevice.getPlatformImpl(),
                                           std::move(Deps));
 }
 
 EventImplPtr QueueImpl::submitWithHandler(const TypelessCGF &CGF) {
+  assert(MContext && "Context impl ptr can't be nullptr");
   detail::HandlerImpl HandlerImplVal(*this);
   handler Handler(HandlerImplVal);
   {
-    NestedCallsTracker tracker;
+    NestedCallsTracker tracker(*MContext);
     CGF(Handler);
   }
 
diff --git a/libsycl/src/handler.cpp b/libsycl/src/handler.cpp
index a8cc6004098fe..a73fafd398b11 100644
--- a/libsycl/src/handler.cpp
+++ b/libsycl/src/handler.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <detail/context_impl.hpp>
 #include <detail/handler_impl.hpp>
 #include <detail/offload/offload_utils.hpp>
 #include <detail/queue_impl.hpp>
@@ -14,9 +15,11 @@
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 static void checkCommandGroupFunction(
-    const std::function<std::shared_ptr<detail::EventImpl>()> &CGF) {
+    const std::function<std::shared_ptr<detail::EventImpl>()> &CGF,
+    detail::ContextImpl &QueueContext) {
   if (CGF) {
     throw sycl::exception(
+        detail::createSyclObjFromImpl<context>(QueueContext),
         sycl::make_error_code(sycl::errc::invalid),
         "Attempt to set multiple actions for the command group");
   }
@@ -24,7 +27,7 @@ static void checkCommandGroupFunction(
 
 void handler::submitKernelImpl(detail::DeviceKernelInfo &KernelInfo,
                                void *ArgData, size_t ArgSize) {
-  checkCommandGroupFunction(MImpl.MCGF);
+  checkCommandGroupFunction(MImpl.MCGF, MImpl.MQueue.getContext());
   MImpl.MArgData.resize(ArgSize);
   std::memcpy(MImpl.MArgData.data(), ArgData, ArgSize);
   MImpl.MCGF = [this, &KernelInfo]() {
@@ -41,7 +44,7 @@ void handler::setKernelRange(const detail::UnifiedRangeView &Range) {
 }
 
 void handler::memcpy(void *dest, const void *src, std::size_t numBytes) {
-  checkCommandGroupFunction(MImpl.MCGF);
+  checkCommandGroupFunction(MImpl.MCGF, MImpl.MQueue.getContext());
   MImpl.MCGF = [this, dest, src, numBytes]() {
     return MImpl.MQueue.memcpy(dest, src, numBytes,
                                detail::getSyclObjImpls(MDepEvents));
diff --git a/libsycl/src/usm_functions.cpp b/libsycl/src/usm_functions.cpp
index 699fdeec79e22..c513aca9e38db 100644
--- a/libsycl/src/usm_functions.cpp
+++ b/libsycl/src/usm_functions.cpp
@@ -57,7 +57,7 @@ static device getHostAllocDevice(const context &syclContext) {
 
   if (It == ContextDevices.end()) {
     throw sycl::exception(
-        sycl::errc::feature_not_supported,
+        syclContext, sycl::errc::feature_not_supported,
         "None of the context's devices support host USM allocations.");
   }
   return *It;
@@ -118,7 +118,8 @@ void *malloc_shared(std::size_t numBytes, const queue &syclQueue,
 
 // SYCL 2020 4.8.3.5. Parameterized allocation functions.
 
-static aspect getAspectByAllocationKind(usm::alloc kind) {
+static aspect getAspectByAllocationKind(usm::alloc kind,
+                                        const context &syclContext) {
   switch (kind) {
   case usm::alloc::host:
     return aspect::usm_host_allocations;
@@ -129,7 +130,7 @@ static aspect getAspectByAllocationKind(usm::alloc kind) {
   case usm::alloc::unknown:
     // usm::alloc::unknown can be returned to user from get_pointer_type but
     // it can't be converted to a valid backend type.
-    throw exception(sycl::make_error_code(sycl::errc::invalid),
+    throw exception(syclContext, sycl::make_error_code(sycl::errc::invalid),
                     "Invalid USM allocation kind requested");
   }
 }
@@ -141,12 +142,12 @@ void *aligned_alloc(std::size_t alignment, std::size_t numBytes,
   auto ContextDevices = syclContext.get_devices();
   if (std::none_of(ContextDevices.begin(), ContextDevices.end(),
                    [&syclDevice](device Dev) { return Dev == syclDevice; }))
-    throw exception(make_error_code(errc::invalid),
+    throw exception(syclContext, make_error_code(errc::invalid),
                     "Specified device is not contained by specified context.");
 
-  if (!syclDevice.has(getAspectByAllocationKind(kind)))
+  if (!syclDevice.has(getAspectByAllocationKind(kind, syclContext)))
     throw sycl::exception(
-        sycl::errc::feature_not_supported,
+        syclContext, sycl::errc::feature_not_supported,
         "Device doesn't support requested kind of USM allocation");
 
   if (!numBytes)
@@ -197,8 +198,9 @@ void *malloc(std::size_t numBytes, const queue &syclQueue, usm::alloc kind,
 // SYCL 2020 4.8.3.6. Memory deallocation functions.
 
 void free(void *ptr, const context &ctxt) {
-  auto OLContext = detail::getSyclObjImpl(ctxt)->getOLHandleRef();
-  detail::callAndThrow(olMemFree, OLContext, ptr);
+  detail::ContextImpl &ContextImplRef = *detail::getSyclObjImpl(ctxt);
+  detail::callAndThrow(ContextImplRef, olMemFree,
+                       ContextImplRef.getOLHandleRef(), ptr);
 }
 
 void free(void *ptr, const queue &q) { return free(ptr, q.get_context()); }
diff --git a/libsycl/unittests/common/unittests_helper.hpp b/libsycl/unittests/common/unittests_helper.hpp
index f35684b36cdcd..8f1ba5bad0316 100644
--- a/libsycl/unittests/common/unittests_helper.hpp
+++ b/libsycl/unittests/common/unittests_helper.hpp
@@ -18,6 +18,8 @@
 #include <detail/platform_impl.hpp>
 #include <mock/helpers.hpp>
 
+#include <utility>
+
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace unittests {
 
@@ -29,17 +31,15 @@ namespace unittests {
 // allows to call global state reset and platforms initialization methods to be
 // able to set expectations on devices enumeration calls in a proper way.
 struct UnittestsHelper {
-  UnittestsHelper() { detail::PlatformImpl::rediscoverIfEmpty = true; }
-
-  ~UnittestsHelper() { resetPlatformState(); }
+  UnittestsHelper() {
+    detail::PlatformImpl::rediscoverIfEmpty = true;
+  }
 
-private:
-  static void resetPlatformState() {
+  ~UnittestsHelper() {
     detail::getPlatformCache().clear();
     detail::getOffloadTopologies() = {};
   }
 
-public:
   mock::MockWrapper Mock;
 };
 
diff --git a/libsycl/unittests/handler/semantics.cpp b/libsycl/unittests/handler/semantics.cpp
index 3bd5d2bba4360..b4324a508554b 100644
--- a/libsycl/unittests/handler/semantics.cpp
+++ b/libsycl/unittests/handler/semantics.cpp
@@ -30,6 +30,8 @@ TEST(Handler, MultipleActionsRejected) {
     Thrown = true;
     EXPECT_NE(std::string(E.what()).find("multiple actions"),
               std::string::npos);
+    EXPECT_TRUE(E.has_context());
+    EXPECT_EQ(E.get_context(), Q.get_context());
   }
 
   EXPECT_TRUE(Thrown);
@@ -122,6 +124,8 @@ TEST(Queue, SubmitCannotBeNested) {
     Thrown = true;
     EXPECT_NE(std::string(E.what()).find("cannot be nested"),
               std::string::npos);
+    EXPECT_TRUE(E.has_context());
+    EXPECT_EQ(E.get_context(), Q.get_context());
   }
 
   EXPECT_TRUE(Thrown);
diff --git a/libsycl/unittests/mock/helpers.hpp b/libsycl/unittests/mock/helpers.hpp
index 46e9c7e70b4d5..c8b0a16f1dc64 100644
--- a/libsycl/unittests/mock/helpers.hpp
+++ b/libsycl/unittests/mock/helpers.hpp
@@ -157,8 +157,11 @@ class MockLiboffload {
   ol_device_handle_t getHostOLDevice() { return HostDevice; }
 
 private:
+  /// Installs the default single gpu-device configuration.
   void initDefault();
 
+  friend class MockWrapper;
+
   std::unordered_map<ol_errc_t, ol_error_struct_t> Errors;
   ol_platform_handle_t DefaultPlatform{};
   ol_device_handle_t DefaultDevice{};
@@ -179,7 +182,13 @@ _LIB_EXPORT MockLiboffload &getMockLiboffload();
 class MockWrapper {
 public:
   MockWrapper() : Mock(getMockLiboffload()) {}
-  ~MockWrapper() { ::testing::Mock::VerifyAndClearExpectations(&Mock); }
+
+  // VerifyAndClear verifies all expectations and drops the default actions.
+  ~MockWrapper() {
+    EXPECT_TRUE(::testing::Mock::VerifyAndClear(&Mock));
+    Mock.initDefault();
+  }
+
   MockLiboffload &get() { return Mock; };
 
 private:
diff --git a/libsycl/unittests/queue/memcpy.cpp b/libsycl/unittests/queue/memcpy.cpp
index 8184e5808e06e..9413c9cd5b63f 100644
--- a/libsycl/unittests/queue/memcpy.cpp
+++ b/libsycl/unittests/queue/memcpy.cpp
@@ -1,6 +1,8 @@
+#include <common/unittests_helper.hpp>
 #include <mock/helpers.hpp>
 
 #include <sycl/__impl/device.hpp>
+#include <sycl/__impl/platform.hpp>
 #include <sycl/__impl/queue.hpp>
 
 #include <detail/device_impl.hpp>
@@ -9,6 +11,9 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <array>
+#include <cstdint>
+
 using namespace sycl;
 using namespace ::testing;
 
@@ -80,3 +85,99 @@ TEST(Queue, MemcpyZeroBytes) {
   event Event = Q.memcpy(nullptr, nullptr, 0);
   Q.memcpy(nullptr, nullptr, 0, Event);
 }
+
+TEST(Queue, MemcpyNullptrThrows) {
+  constexpr int NumBytes = 32;
+
+  mock::MockWrapper Mock;
+  queue Q;
+  int Src = 0;
+
+  EXPECT_CALL(Mock.get(), olGetMemInfo(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(Mock.get(), olMemcpy(_, _, _, _, _, _)).Times(0);
+
+  try {
+    Q.memcpy(nullptr, &Src, NumBytes);
+    FAIL() << "Expected sycl::exception";
+  } catch (const sycl::exception &E) {
+    EXPECT_EQ(E.code(), make_error_code(errc::invalid));
+    EXPECT_TRUE(E.has_context());
+    EXPECT_EQ(E.get_context(), Q.get_context());
+  }
+}
+
+namespace {
+
+// The default mock exposes a single platform, so device enumeration has to be
+// mocked to get events that belong to different platforms. Platforms are formed
+// per liboffload driver id.
+class QueueTwoPlatformsTest : public Test {
+protected:
+  void SetUp() override {
+    Platform = mock::createDummyHandle<ol_platform_handle_t>();
+    for (ol_device_handle_t &Device : Devices)
+      Device = mock::createDummyHandleWithData<ol_device_handle_t>(
+          reinterpret_cast<unsigned char *>(&Platform), sizeof(Platform));
+
+    EXPECT_CALL(Helper.Mock.get(), olIterateDevices(_, _))
+        .WillRepeatedly([this](ol_device_iterate_cb_t Callback,
+                               void *UserData) -> ol_result_t {
+          for (ol_device_handle_t Device : Devices)
+            std::ignore = Callback(Device, UserData);
+          return OL_SUCCESS;
+        });
+
+    ON_CALL(Helper.Mock.get(),
+            olGetDeviceInfo(_, OL_DEVICE_INFO_DRIVER_ID, _, _))
+        .WillByDefault([this](ol_device_handle_t Device,
+                              ol_device_info_t /*PropName*/, size_t PropSize,
+                              void *PropValue) -> ol_result_t {
+          EXPECT_EQ(PropSize, sizeof(uint32_t));
+          *static_cast<uint32_t *>(PropValue) = getDriverId(Device);
+          return OL_SUCCESS;
+        });
+  }
+
+  void TearDown() override {
+    mock::releaseDummyHandles(Devices[0], Devices[1], Platform);
+  }
+
+  uint32_t getDriverId(ol_device_handle_t Device) const {
+    if (Device == Devices[0])
+      return 0;
+    if (Device == Devices[1])
+      return 1;
+    ADD_FAILURE() << "Unexpected device";
+    return 0;
+  }
+
+  unittests::UnittestsHelper Helper;
+  ol_platform_handle_t Platform{};
+  std::array<ol_device_handle_t, 2> Devices{};
+};
+
+} // namespace
+
+TEST_F(QueueTwoPlatformsTest, CrossPlatformDependencyThrows) {
+  std::vector<platform> Platforms = platform::get_platforms();
+  ASSERT_EQ(Platforms.size(), 2u);
+
+  std::vector<device> Devices0 = Platforms[0].get_devices();
+  std::vector<device> Devices1 = Platforms[1].get_devices();
+  ASSERT_EQ(Devices0.size(), 1u);
+  ASSERT_EQ(Devices1.size(), 1u);
+
+  queue Q0(Devices0.front());
+  queue Q1(Devices1.front());
+
+  event OtherPlatformEvent = Q1.memcpy(nullptr, nullptr, 0);
+
+  try {
+    Q0.memcpy(nullptr, nullptr, 0, OtherPlatformEvent);
+    FAIL() << "Expected sycl::exception";
+  } catch (const sycl::exception &E) {
+    EXPECT_EQ(E.code(), make_error_code(errc::feature_not_supported));
+    EXPECT_TRUE(E.has_context());
+    EXPECT_EQ(E.get_context(), Q0.get_context());
+  }
+}
diff --git a/libsycl/unittests/queue/prefetch.cpp b/libsycl/unittests/queue/prefetch.cpp
index 0abed1dd3f8f5..6bedd4ba8337b 100644
--- a/libsycl/unittests/queue/prefetch.cpp
+++ b/libsycl/unittests/queue/prefetch.cpp
@@ -50,3 +50,21 @@ TEST(Queue, PrefetchZeroBytes) {
   event Event = Q.prefetch(nullptr, 0);
   Q.prefetch(nullptr, 0, Event);
 }
+
+TEST(Queue, PrefetchNullptrThrows) {
+  constexpr std::size_t NumBytes = 1024;
+
+  mock::MockWrapper Mock;
+  queue Q;
+
+  EXPECT_CALL(Mock.get(), olMemPrefetch(_, _, _, _, _)).Times(0);
+
+  try {
+    Q.prefetch(nullptr, NumBytes);
+    FAIL() << "Expected sycl::exception";
+  } catch (const sycl::exception &E) {
+    EXPECT_EQ(E.code(), make_error_code(errc::invalid));
+    EXPECT_TRUE(E.has_context());
+    EXPECT_EQ(E.get_context(), Q.get_context());
+  }
+}
diff --git a/libsycl/unittests/queue/queue.cpp b/libsycl/unittests/queue/queue.cpp
index 37174e571fd93..2182a588ffb30 100644
--- a/libsycl/unittests/queue/queue.cpp
+++ b/libsycl/unittests/queue/queue.cpp
@@ -52,3 +52,47 @@ TEST(Queue, ContextAndDeviceConstructor) {
   EXPECT_EQ(AsyncSelectorQueue.get_context(), Context);
   EXPECT_EQ(AsyncSelectorQueue.get_device(), Device);
 }
+
+// The device does belong to the context here: the runtime relies on liboffload
+// to report OL_ERRC_INVALID_DEVICE, so the error is forced through the mock.
+TEST(Queue, CreateQueueInvalidDeviceErrorThrows) {
+  mock::MockWrapper Mock;
+
+  const device Device;
+  const context Context = Device.get_platform().khr_get_default_context();
+
+  EXPECT_CALL(Mock.get(), olCreateQueue(_, _, _))
+      .Times(1)
+      .WillOnce(Return(
+          mock::getMockLiboffload().makeEmptyStrError(OL_ERRC_INVALID_DEVICE)));
+
+  try {
+    queue Queue(Context, Device);
+    FAIL() << "Expected sycl::exception";
+  } catch (const sycl::exception &E) {
+    EXPECT_EQ(E.code(), make_error_code(errc::invalid));
+    EXPECT_TRUE(E.has_context());
+    EXPECT_EQ(E.get_context(), Context);
+  }
+}
+
+// Failures reported by liboffload and translated by checkAndThrow must carry
+// the context too.
+TEST(Queue, WaitFailureThrowsWithContext) {
+  mock::MockWrapper Mock;
+  queue Q;
+
+  EXPECT_CALL(Mock.get(), olSyncQueue(_))
+      .Times(1)
+      .WillOnce(Return(mock::getMockLiboffload().makeEmptyStrError(
+          OL_ERRC_OUT_OF_RESOURCES)));
+
+  try {
+    Q.wait();
+    FAIL() << "Expected sycl::exception";
+  } catch (const sycl::exception &E) {
+    EXPECT_EQ(E.code(), make_error_code(errc::runtime));
+    EXPECT_TRUE(E.has_context());
+    EXPECT_EQ(E.get_context(), Q.get_context());
+  }
+}
diff --git a/libsycl/unittests/usm/alloc.cpp b/libsycl/unittests/usm/alloc.cpp
index 1e80c336d18c6..b2c763ec17621 100644
--- a/libsycl/unittests/usm/alloc.cpp
+++ b/libsycl/unittests/usm/alloc.cpp
@@ -6,15 +6,18 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <common/unittests_helper.hpp>
 #include <mock/helpers.hpp>
 
 #include <sycl/__impl/device.hpp>
+#include <sycl/__impl/platform.hpp>
 #include <sycl/__impl/queue.hpp>
 #include <sycl/__impl/usm_functions.hpp>
 
 #include <detail/device_impl.hpp>
 #include <detail/queue_impl.hpp>
 
+#include <array>
 #include <cstddef>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -188,6 +191,79 @@ TEST(USMFunctions, ZeroAlignmentSucceeds) {
   free(Ptr3, Ctx);
 }
 
+TEST(USMFunctions, UnknownAllocationKindThrows) {
+  mock::MockWrapper Mock;
+  queue Q;
+  device Dev = Q.get_device();
+  context Ctx = Q.get_context();
+
+  EXPECT_CALL(Mock.get(), olMemAlloc(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(Mock.get(), olMemAllocHost(_, _, _, _)).Times(0);
+
+  try {
+    sycl::malloc(NumBytes, Dev, Ctx, usm::alloc::unknown);
+    FAIL() << "Expected sycl::exception";
+  } catch (const sycl::exception &E) {
+    EXPECT_EQ(E.code(), make_error_code(errc::invalid));
+    EXPECT_TRUE(E.has_context());
+    EXPECT_EQ(E.get_context(), Ctx);
+  }
+}
+
+namespace {
+
+// The default mock exposes a single device, so device enumeration has to be
+// mocked to get a context that doesn't contain a device.
+class USMTwoDevicesTest : public Test {
+protected:
+  void SetUp() override {
+    Platform = mock::createDummyHandle<ol_platform_handle_t>();
+    for (ol_device_handle_t &Device : Devices)
+      Device = mock::createDummyHandleWithData<ol_device_handle_t>(
+          reinterpret_cast<unsigned char *>(&Platform), sizeof(Platform));
+
+    EXPECT_CALL(Helper.Mock.get(), olIterateDevices(_, _))
+        .WillRepeatedly([this](ol_device_iterate_cb_t Callback,
+                               void *UserData) -> ol_result_t {
+          for (ol_device_handle_t Device : Devices)
+            std::ignore = Callback(Device, UserData);
+          return OL_SUCCESS;
+        });
+  }
+
+  void TearDown() override {
+    mock::releaseDummyHandles(Devices[0], Devices[1], Platform);
+  }
+
+  unittests::UnittestsHelper Helper;
+  ol_platform_handle_t Platform{};
+  std::array<ol_device_handle_t, 2> Devices{};
+};
+
+} // namespace
+
+TEST_F(USMTwoDevicesTest, DeviceNotInContextThrows) {
+  std::vector<platform> Platforms = platform::get_platforms();
+  ASSERT_EQ(Platforms.size(), 1u);
+
+  std::vector<device> PlatformDevices = Platforms[0].get_devices();
+  ASSERT_EQ(PlatformDevices.size(), 2u);
+
+  context Ctx(PlatformDevices[0]);
+
+  EXPECT_CALL(Helper.Mock.get(), olMemAlloc(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(Helper.Mock.get(), olMemAllocAligned(_, _, _, _, _, _)).Times(0);
+
+  try {
+    malloc_device(NumBytes, PlatformDevices[1], Ctx);
+    FAIL() << "Expected sycl::exception";
+  } catch (const sycl::exception &E) {
+    EXPECT_EQ(E.code(), make_error_code(errc::invalid));
+    EXPECT_TRUE(E.has_context());
+    EXPECT_EQ(E.get_context(), Ctx);
+  }
+}
+
 struct alignas(64) Over {
   char c;
 };

>From 5f3027410be9d72b15a6ab1466e64d3337c51e89 Mon Sep 17 00:00:00 2001
From: "Tikhomirova, Kseniya" <kseniya.tikhomirova at intel.com>
Date: Tue, 22 Sep 2026 09:19:18 -0700
Subject: [PATCH 2/4] fix comments

Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova at intel.com>
---
 libsycl/include/sycl/__impl/exception.hpp     | 4 +---
 libsycl/src/detail/global_objects.cpp         | 7 -------
 libsycl/unittests/common/unittests_helper.hpp | 4 +---
 3 files changed, 2 insertions(+), 13 deletions(-)

diff --git a/libsycl/include/sycl/__impl/exception.hpp b/libsycl/include/sycl/__impl/exception.hpp
index 588083a45a19b..c3c8ff32a1e05 100644
--- a/libsycl/include/sycl/__impl/exception.hpp
+++ b/libsycl/include/sycl/__impl/exception.hpp
@@ -165,11 +165,9 @@ class _LIBSYCL_EXPORT exception : public virtual std::exception {
 private:
   exception(std::error_code EC, std::shared_ptr<context> SharedPtrCtx,
             const char *WhatArg);
-
   // Exceptions must be noexcept copy constructible, so cannot use std::string
-  // directly.
+  // or context directly.
   std::shared_ptr<std::string> MMessage;
-
   std::shared_ptr<context> MContext;
   std::error_code MErrC = make_error_code(sycl::errc::invalid);
 };
diff --git a/libsycl/src/detail/global_objects.cpp b/libsycl/src/detail/global_objects.cpp
index b0f7559dc359d..0ef13295d513d 100644
--- a/libsycl/src/detail/global_objects.cpp
+++ b/libsycl/src/detail/global_objects.cpp
@@ -38,13 +38,6 @@ struct StaticVarShutdownHandler {
   operator=(const StaticVarShutdownHandler &) = delete;
   ~StaticVarShutdownHandler() {
     ProgramAndKernelManager::getInstance().releaseResources();
-    {
-      auto &[AsyncExceptions, AsyncExceptionsMutex] = getAsyncExceptionList();
-      {
-        std::lock_guard<SpinLock> Lock(AsyncExceptionsMutex);
-        AsyncExceptions.clear();
-      }
-    }
     // No error reporting in shutdown
     std::ignore = olShutDown();
   }
diff --git a/libsycl/unittests/common/unittests_helper.hpp b/libsycl/unittests/common/unittests_helper.hpp
index 8f1ba5bad0316..cdf2c863f0302 100644
--- a/libsycl/unittests/common/unittests_helper.hpp
+++ b/libsycl/unittests/common/unittests_helper.hpp
@@ -31,9 +31,7 @@ namespace unittests {
 // allows to call global state reset and platforms initialization methods to be
 // able to set expectations on devices enumeration calls in a proper way.
 struct UnittestsHelper {
-  UnittestsHelper() {
-    detail::PlatformImpl::rediscoverIfEmpty = true;
-  }
+  UnittestsHelper() { detail::PlatformImpl::rediscoverIfEmpty = true; }
 
   ~UnittestsHelper() {
     detail::getPlatformCache().clear();

>From 50f3d5777d1579af79bbc043fd6b8191a1b2349d Mon Sep 17 00:00:00 2001
From: "Tikhomirova, Kseniya" <kseniya.tikhomirova at intel.com>
Date: Thu, 24 Sep 2026 07:01:39 -0700
Subject: [PATCH 3/4] fix comments

Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova at intel.com>
---
 libsycl/src/detail/context_impl.cpp           |  2 +-
 libsycl/src/detail/device_image_wrapper.cpp   | 14 +++++++-------
 libsycl/src/detail/device_image_wrapper.hpp   |  6 +++---
 libsycl/src/detail/queue_impl.cpp             | 10 +++++-----
 libsycl/src/handler.cpp                       |  4 ++--
 libsycl/src/usm_functions.cpp                 |  5 ++---
 libsycl/unittests/common/unittests_helper.hpp |  2 --
 libsycl/unittests/usm/alloc.cpp               |  6 ++++--
 8 files changed, 24 insertions(+), 25 deletions(-)

diff --git a/libsycl/src/detail/context_impl.cpp b/libsycl/src/detail/context_impl.cpp
index 7469491281242..0362c3d9241f0 100644
--- a/libsycl/src/detail/context_impl.cpp
+++ b/libsycl/src/detail/context_impl.cpp
@@ -87,7 +87,7 @@ ContextImpl::getOrCreateKernel(const DeviceImageManager &DeviceImage,
     }
   }
 
-  return ProgramIt->second.getOrCreateKernel(KernelName, *this);
+  return ProgramIt->second.getOrCreateKernel(KernelName);
 }
 
 void ContextImpl::releaseProgramsForImage(
diff --git a/libsycl/src/detail/device_image_wrapper.cpp b/libsycl/src/detail/device_image_wrapper.cpp
index 76b759eec11c7..9133a28035524 100644
--- a/libsycl/src/detail/device_image_wrapper.cpp
+++ b/libsycl/src/detail/device_image_wrapper.cpp
@@ -15,12 +15,13 @@ _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace detail {
 
 ProgramWrapper::ProgramWrapper(ContextImpl &Context, ol_device_handle_t Device,
-                               const DeviceImageManager &DevImage) {
-  assert(Context.getOLHandleRef());
-  assert(Device);
+                               const DeviceImageManager &DevImage)
+    : MContext(Context) {
+  assert(MContext.getOLHandleRef() && "Context handle can't be nullptr");
+  assert(Device && "Device handle can't be nullptr");
 
   llvm::StringRef Image = DevImage.getOffloadBinary().getImage();
-  callAndThrow(Context, olCreateProgram, Context.getOLHandleRef(), Device,
+  callAndThrow(MContext, olCreateProgram, MContext.getOLHandleRef(), Device,
                Image.data(), Image.size(), &MProgram);
 }
 
@@ -31,14 +32,13 @@ ProgramWrapper::~ProgramWrapper() {
 }
 
 ol_symbol_handle_t
-ProgramWrapper::getOrCreateKernel(std::string_view KernelName,
-                                  ContextImpl &Context) {
+ProgramWrapper::getOrCreateKernel(std::string_view KernelName) {
   auto It = MKernels.find(KernelName);
   if (It != MKernels.end())
     return It->second;
 
   ol_symbol_handle_t Kernel{};
-  callAndThrow(Context, olGetSymbol, MProgram, KernelName.data(),
+  callAndThrow(MContext, olGetSymbol, MProgram, KernelName.data(),
                OL_SYMBOL_KIND_KERNEL, &Kernel);
   MKernels.emplace(KernelName, Kernel);
   return Kernel;
diff --git a/libsycl/src/detail/device_image_wrapper.hpp b/libsycl/src/detail/device_image_wrapper.hpp
index e67ee77208d07..b7191e4484e28 100644
--- a/libsycl/src/detail/device_image_wrapper.hpp
+++ b/libsycl/src/detail/device_image_wrapper.hpp
@@ -66,14 +66,14 @@ class ProgramWrapper {
   /// for a program it does not belong to.
   ///
   /// \param KernelName the name of the kernel to look up.
-  /// \param Context is the context this program belongs to.
   /// \throw sycl::exception with sycl::errc::runtime when the symbol lookup
   /// fails.
   /// \return the liboffload symbol handle of the kernel.
-  ol_symbol_handle_t getOrCreateKernel(std::string_view KernelName,
-                                       ContextImpl &Context);
+  ol_symbol_handle_t getOrCreateKernel(std::string_view KernelName);
 
 private:
+  // Programs are owned by their context, so the context outlives them.
+  ContextImpl &MContext;
   ol_program_handle_t MProgram{};
 
   // Kernel names are backed by the "symbols" string of the device image this
diff --git a/libsycl/src/detail/queue_impl.cpp b/libsycl/src/detail/queue_impl.cpp
index 0dcfa6223c09e..5163d8e795673 100644
--- a/libsycl/src/detail/queue_impl.cpp
+++ b/libsycl/src/detail/queue_impl.cpp
@@ -93,8 +93,8 @@ void QueueImpl::waitAndThrow() {
 void QueueImpl::throwAsynchronous() { flushAsyncExceptions(); }
 
 static void checkEventsPlatformMatch(const std::vector<EventImplPtr> &Events,
-                                     const PlatformImpl &QueuePlatform,
                                      ContextImpl &QueueContext) {
+  const PlatformImpl &QueuePlatform = QueueContext.getPlatformImpl();
   // liboffload limitation to olWaitEvents. We can't do any extra handling for
   // cross context/platform events without host task support now.
   //   "The input events can be from any queue on any device provided by the
@@ -120,7 +120,7 @@ void QueueImpl::setKernelLaunchParams(
     std::vector<EventImplPtr> &&Events,
     const ol_kernel_launch_size_args_t &Range) {
   assert(MContext && "Context impl ptr can't be nullptr");
-  checkEventsPlatformMatch(Events, MDevice.getPlatformImpl(), *MContext);
+  checkEventsPlatformMatch(Events, *MContext);
   MCurrentSubmitInfo.DepEvents = std::move(Events);
   MCurrentSubmitInfo.Range = Range;
 }
@@ -181,7 +181,7 @@ std::shared_ptr<EventImpl>
 QueueImpl::memcpy(void *Dest, const void *Src, std::size_t NumBytes,
                   const std::vector<EventImplPtr> &DepEvents) {
   assert(MContext && "Context impl ptr can't be nullptr");
-  checkEventsPlatformMatch(DepEvents, MDevice.getPlatformImpl(), *MContext);
+  checkEventsPlatformMatch(DepEvents, *MContext);
   if (NumBytes == 0)
     return submitWait(DepEvents);
 
@@ -204,7 +204,7 @@ EventImplPtr QueueImpl::fill(void *Ptr, const void *Pattern,
                              const std::vector<EventImplPtr> &DepEvents) {
   assert(PatternSize > 0 && "Pattern size has to be greater than zero");
   assert(MContext && "Context impl ptr can't be nullptr");
-  checkEventsPlatformMatch(DepEvents, MDevice.getPlatformImpl(), *MContext);
+  checkEventsPlatformMatch(DepEvents, *MContext);
   if (Count == 0)
     return submitWait(DepEvents);
 
@@ -225,7 +225,7 @@ EventImplPtr QueueImpl::fill(void *Ptr, const void *Pattern,
 EventImplPtr QueueImpl::prefetch(void *Ptr, std::size_t NumBytes,
                                  const std::vector<EventImplPtr> &DepEvents) {
   assert(MContext && "Context impl ptr can't be nullptr");
-  checkEventsPlatformMatch(DepEvents, MDevice.getPlatformImpl(), *MContext);
+  checkEventsPlatformMatch(DepEvents, *MContext);
 
   if (NumBytes == 0)
     return submitWait(DepEvents);
diff --git a/libsycl/src/handler.cpp b/libsycl/src/handler.cpp
index a73fafd398b11..72f05f51ca661 100644
--- a/libsycl/src/handler.cpp
+++ b/libsycl/src/handler.cpp
@@ -16,10 +16,10 @@ _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 static void checkCommandGroupFunction(
     const std::function<std::shared_ptr<detail::EventImpl>()> &CGF,
-    detail::ContextImpl &QueueContext) {
+    detail::ContextImpl &Context) {
   if (CGF) {
     throw sycl::exception(
-        detail::createSyclObjFromImpl<context>(QueueContext),
+        detail::createSyclObjFromImpl<context>(Context),
         sycl::make_error_code(sycl::errc::invalid),
         "Attempt to set multiple actions for the command group");
   }
diff --git a/libsycl/src/usm_functions.cpp b/libsycl/src/usm_functions.cpp
index c513aca9e38db..f6278bd43df71 100644
--- a/libsycl/src/usm_functions.cpp
+++ b/libsycl/src/usm_functions.cpp
@@ -198,9 +198,8 @@ void *malloc(std::size_t numBytes, const queue &syclQueue, usm::alloc kind,
 // SYCL 2020 4.8.3.6. Memory deallocation functions.
 
 void free(void *ptr, const context &ctxt) {
-  detail::ContextImpl &ContextImplRef = *detail::getSyclObjImpl(ctxt);
-  detail::callAndThrow(ContextImplRef, olMemFree,
-                       ContextImplRef.getOLHandleRef(), ptr);
+  detail::ContextImpl &Context = *detail::getSyclObjImpl(ctxt);
+  detail::callAndThrow(Context, olMemFree, Context.getOLHandleRef(), ptr);
 }
 
 void free(void *ptr, const queue &q) { return free(ptr, q.get_context()); }
diff --git a/libsycl/unittests/common/unittests_helper.hpp b/libsycl/unittests/common/unittests_helper.hpp
index cdf2c863f0302..e47a3eddc50e1 100644
--- a/libsycl/unittests/common/unittests_helper.hpp
+++ b/libsycl/unittests/common/unittests_helper.hpp
@@ -18,8 +18,6 @@
 #include <detail/platform_impl.hpp>
 #include <mock/helpers.hpp>
 
-#include <utility>
-
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace unittests {
 
diff --git a/libsycl/unittests/usm/alloc.cpp b/libsycl/unittests/usm/alloc.cpp
index b2c763ec17621..28dd14b81c387 100644
--- a/libsycl/unittests/usm/alloc.cpp
+++ b/libsycl/unittests/usm/alloc.cpp
@@ -17,11 +17,13 @@
 #include <detail/device_impl.hpp>
 #include <detail/queue_impl.hpp>
 
-#include <array>
-#include <cstddef>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <array>
+#include <cstddef>
+#include <vector>
+
 using namespace sycl;
 using namespace ::testing;
 

>From b4b46b2320c620da59978c33a5cd3a198be3f534 Mon Sep 17 00:00:00 2001
From: "Tikhomirova, Kseniya" <kseniya.tikhomirova at intel.com>
Date: Thu, 24 Sep 2026 09:51:47 -0700
Subject: [PATCH 4/4] fix tests

Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova at intel.com>
---
 libsycl/unittests/common/unittests_helper.hpp | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/libsycl/unittests/common/unittests_helper.hpp b/libsycl/unittests/common/unittests_helper.hpp
index e47a3eddc50e1..49a16d5c5fb68 100644
--- a/libsycl/unittests/common/unittests_helper.hpp
+++ b/libsycl/unittests/common/unittests_helper.hpp
@@ -29,14 +29,22 @@ namespace unittests {
 // allows to call global state reset and platforms initialization methods to be
 // able to set expectations on devices enumeration calls in a proper way.
 struct UnittestsHelper {
-  UnittestsHelper() { detail::PlatformImpl::rediscoverIfEmpty = true; }
+  // Platforms cached by earlier tests would hide the device enumeration mocked
+  // by the fixture, so the global state is reset on both ends.
+  UnittestsHelper() {
+    detail::PlatformImpl::rediscoverIfEmpty = true;
+    resetGlobalState();
+  }
+
+  ~UnittestsHelper() { resetGlobalState(); }
+
+  mock::MockWrapper Mock;
 
-  ~UnittestsHelper() {
+private:
+  static void resetGlobalState() {
     detail::getPlatformCache().clear();
     detail::getOffloadTopologies() = {};
   }
-
-  mock::MockWrapper Mock;
 };
 
 } // namespace unittests



More information about the llvm-commits mailing list