[llvm] [libsycl] Add liboffload context handler to default context (PR #213249)

Kseniya Tikhomirova via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 31 04:23:16 PDT 2026


https://github.com/KseniyaTikhomirova created https://github.com/llvm/llvm-project/pull/213249

None

>From b8c8d23eab43a45b59c3a962782ea96d9bcef984 Mon Sep 17 00:00:00 2001
From: "Tikhomirova, Kseniya" <kseniya.tikhomirova at intel.com>
Date: Fri, 31 Jul 2026 04:21:52 -0700
Subject: [PATCH] [libsycl] Add liboffload context handler to default context

Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova at intel.com>
---
 libsycl/src/detail/context_impl.cpp  | 41 ++++++++++++++++++++++++----
 libsycl/src/detail/context_impl.hpp  | 41 ++++++++++++++++++++--------
 libsycl/src/detail/platform_impl.cpp |  9 +++++-
 libsycl/src/detail/queue_impl.cpp    |  3 ++
 libsycl/unittests/mock/helpers.cpp   | 27 ++++++++++++++++++
 libsycl/unittests/mock/helpers.hpp   |  4 +++
 libsycl/unittests/mock/mock.cpp      | 10 +++++++
 7 files changed, 118 insertions(+), 17 deletions(-)

diff --git a/libsycl/src/detail/context_impl.cpp b/libsycl/src/detail/context_impl.cpp
index 264716fbdfad4..58aa55187f339 100644
--- a/libsycl/src/detail/context_impl.cpp
+++ b/libsycl/src/detail/context_impl.cpp
@@ -13,15 +13,46 @@ _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 namespace detail {
 
+ContextImpl::ContextImpl(std::vector<DeviceImpl *> &&DeviceList,
+                         const async_handler &AsyncHandler,
+                         const property_list &PropList, Private)
+    : MAsyncHandler(AsyncHandler), MDevices(DeviceList) {
+  (void)PropList;
+
+  assert(!MDevices.empty() && "Device list must not be empty");
+  const PlatformImpl &RefPlatform = MDevices[0]->getPlatformImpl();
+
+  std::vector<ol_device_handle_t> DeviceIds;
+  DeviceIds.reserve(MDevices.size());
+  for (DeviceImpl *D : MDevices) {
+    assert(D && "Device list must not contain null entries");
+    if (D->getPlatformImpl().getOLHandleRef() != RefPlatform.getOLHandleRef())
+      throw exception(
+          make_error_code(errc::invalid),
+          "Can't add devices across platforms to a single context.");
+    DeviceIds.push_back(D->getOLHandle());
+  }
+
+  callAndThrow(olCreateContext, DeviceIds.size(), DeviceIds.data(),
+               &MOffloadContext);
+}
+
+ContextImpl::~ContextImpl() {
+  assert(MOffloadContext && "Context must be created in ctor");
+  std::ignore = olDestroyContext(MOffloadContext);
+}
+
+PlatformImpl &ContextImpl::getPlatformImpl() const {
+  return MDevices[0]->getPlatformImpl();
+}
+
 void ContextImpl::iterateDevices(
     const std::function<void(DeviceImpl *)> &callback) const {
-  // TODO: Intentionally don't store devices in context now. This class should
-  // be reimplemented once liboffload adds context support. Treat context as
-  // default context that is associated with all devices in the platform.
-  return MPlatform.iterateDevices(info::device_type::all, callback);
+  for (DeviceImpl *Device : MDevices)
+    callback(Device);
 }
 
-backend ContextImpl::getBackend() const { return MPlatform.getBackend(); }
+backend ContextImpl::getBackend() const { return MDevices[0]->getBackend(); }
 
 } // namespace detail
 _LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/detail/context_impl.hpp b/libsycl/src/detail/context_impl.hpp
index 4dcbda6c95f82..ec9c8d10aca7e 100644
--- a/libsycl/src/detail/context_impl.hpp
+++ b/libsycl/src/detail/context_impl.hpp
@@ -15,6 +15,7 @@
 #ifndef _LIBSYCL_CONTEXT_IMPL
 #define _LIBSYCL_CONTEXT_IMPL
 
+#include <sycl/__impl/async_handler.hpp>
 #include <sycl/__impl/context.hpp>
 #include <sycl/__impl/detail/config.hpp>
 
@@ -23,16 +24,14 @@
 #include <functional>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+class property_list;
+
 namespace detail {
 
 class PlatformImpl;
 class DeviceImpl;
 
-// TODO: Presence of context object is essential for many APIs. Current
-// implementation of this class is a way to support them in case of absence of
-// context support in liboffload. For backends where context exists and
-// participates in operations, liboffload plugins create and use default context
-// that represents all devices in that platform. Duplicating this logic here.
 /// Context represents the runtime data structures and state required by a SYCL
 /// backend API to interact with a group of devices associated with a platform.
 class ContextImpl : public std::enable_shared_from_this<ContextImpl> {
@@ -41,12 +40,22 @@ class ContextImpl : public std::enable_shared_from_this<ContextImpl> {
   };
 
 public:
-  /// Constructs a ContextImpl using a platform.
+  /// Constructs a context implementation for the provided devices.
   ///
-  /// Newly created instance represents all devices in platform.
+  /// \param DeviceList is the list of devices associated with this context.
+  /// \param AsyncHandler is a SYCL asynchronous exception handler.
+  /// \param PropList is a list of context properties.
+  ContextImpl(std::vector<DeviceImpl *> &&DeviceList,
+              const async_handler &AsyncHandler, const property_list &PropList,
+              Private);
+
+  /// Releases the underlying offload context handle.
+  ~ContextImpl();
+
+  /// Gets asynchronous exception handler.
   ///
-  /// \param Platform is a platform to associate this context with.
-  ContextImpl(PlatformImpl &Platform, Private) : MPlatform(Platform) {}
+  /// \return an instance of SYCL async_handler.
+  const async_handler &get_async_handler() const { return MAsyncHandler; }
 
   /// Constructs a ContextImpl with a provided arguments. Variadic helper.
   /// Restrics ways of ContextImpl creation.
@@ -55,8 +64,16 @@ class ContextImpl : public std::enable_shared_from_this<ContextImpl> {
     return std::make_shared<ContextImpl>(std::forward<Ts>(args)..., Private{});
   }
 
+  /// Returns the raw underlying offload context handle.
+  ///
+  /// The caller is responsible for ensuring that the returned handle is only
+  /// used while this ContextImpl object is alive.
+  ///
+  /// \return the raw offload context handle.
+  const ol_context_handle_t &getOLHandleRef() const { return MOffloadContext; }
+
   /// \return the platform this context is associated with.
-  PlatformImpl &getPlatformImpl() const { return MPlatform; }
+  PlatformImpl &getPlatformImpl() const;
 
   /// Calls "callback" with every device associated
   /// with this context.
@@ -66,7 +83,9 @@ class ContextImpl : public std::enable_shared_from_this<ContextImpl> {
   backend getBackend() const;
 
 private:
-  PlatformImpl &MPlatform;
+  const async_handler MAsyncHandler;
+  const std::vector<DeviceImpl *> MDevices;
+  ol_context_handle_t MOffloadContext{};
 };
 
 } // namespace detail
diff --git a/libsycl/src/detail/platform_impl.cpp b/libsycl/src/detail/platform_impl.cpp
index 96d52d1363a9b..1d81b848a21d3 100644
--- a/libsycl/src/detail/platform_impl.cpp
+++ b/libsycl/src/detail/platform_impl.cpp
@@ -8,6 +8,7 @@
 
 #include <sycl/__impl/detail/config.hpp>
 #include <sycl/__impl/detail/obj_utils.hpp>
+#include <sycl/__impl/property_list.hpp>
 
 #include <detail/context_impl.hpp>
 #include <detail/device_impl.hpp>
@@ -92,7 +93,13 @@ PlatformImpl::PlatformImpl(ol_platform_handle_t Platform, size_t PlatformIndex,
                       Device, *this, DeviceImpl::PrivateTag{}));
                 });
 
-  MDefaultContext = ContextImpl::create(*this);
+  std::vector<DeviceImpl *> DeviceImpls;
+  DeviceImpls.reserve(MRootDevices.size());
+  for (const auto &Device : MRootDevices)
+    DeviceImpls.push_back(Device.get());
+
+  MDefaultContext = ContextImpl::create(std::move(DeviceImpls),
+                                        defaultAsyncHandler, property_list{});
 }
 
 const std::vector<DeviceImplUPtr> &PlatformImpl::getRootDevices() const {
diff --git a/libsycl/src/detail/queue_impl.cpp b/libsycl/src/detail/queue_impl.cpp
index 6116e2d787e6f..430c0eb302f72 100644
--- a/libsycl/src/detail/queue_impl.cpp
+++ b/libsycl/src/detail/queue_impl.cpp
@@ -8,6 +8,7 @@
 
 #include <detail/queue_impl.hpp>
 
+#include <detail/context_impl.hpp>
 #include <detail/device_impl.hpp>
 #include <detail/event_impl.hpp>
 #include <detail/global_objects.hpp>
@@ -66,6 +67,8 @@ QueueImpl::QueueImpl(DeviceImpl &deviceImpl, const async_handler &asyncHandler,
     : MIsInorder(false), MAsyncHandler(asyncHandler), MPropList(propList),
       MDevice(deviceImpl),
       MContext(MDevice.getPlatformImpl().getDefaultContext()) {
+  assert(MContext.getOLHandleRef() &&
+         "Queue must be associated with a valid offload context");
   callAndThrow(olCreateQueue, MDevice.getOLHandle(), &MOffloadQueue);
 }
 
diff --git a/libsycl/unittests/mock/helpers.cpp b/libsycl/unittests/mock/helpers.cpp
index 6b4460cd66113..b430be7b35395 100644
--- a/libsycl/unittests/mock/helpers.cpp
+++ b/libsycl/unittests/mock/helpers.cpp
@@ -181,6 +181,33 @@ void mock::MockLiboffload::initDefault() {
         return OL_SUCCESS;
       });
 
+  ON_CALL(*this, olCreateContext)
+      .WillByDefault([this](size_t NumDevices, ol_device_handle_t *Devices,
+                            ol_context_handle_t *Context) -> ol_result_t {
+        if (!Devices || !Context)
+          return makeEmptyStrError(OL_ERRC_INVALID_NULL_POINTER);
+        if (NumDevices == 0)
+          return makeEmptyStrError(OL_ERRC_INVALID_SIZE);
+        for (size_t I = 0; I < NumDevices; ++I) {
+          if (!Devices[I])
+            return makeEmptyStrError(OL_ERRC_INVALID_NULL_HANDLE);
+        }
+
+        // Preserve the first device in payload for tests that may need to
+        // inspect what device set the context was created from.
+        *Context = mock::createDummyHandleWithData<ol_context_handle_t>(
+            reinterpret_cast<unsigned char *>(&Devices[0]), sizeof(Devices[0]));
+        return OL_SUCCESS;
+      });
+
+  ON_CALL(*this, olDestroyContext)
+      .WillByDefault([this](ol_context_handle_t Context) -> ol_result_t {
+        if (!Context)
+          return makeEmptyStrError(OL_ERRC_INVALID_NULL_HANDLE);
+        mock::releaseDummyHandle(Context);
+        return OL_SUCCESS;
+      });
+
   ON_CALL(*this, olCreateProgram)
       .WillByDefault([this](ol_device_handle_t Device, const void *ProgData,
                             size_t ProgDataSize,
diff --git a/libsycl/unittests/mock/helpers.hpp b/libsycl/unittests/mock/helpers.hpp
index 0690970f1f99b..2435a49b22f29 100644
--- a/libsycl/unittests/mock/helpers.hpp
+++ b/libsycl/unittests/mock/helpers.hpp
@@ -89,6 +89,10 @@ class MockLiboffload {
   MOCK_METHOD(ol_result_t, olIterateDevices,
               (ol_device_iterate_cb_t Callback, void *UserData));
   MOCK_METHOD(ol_result_t, olDestroyProgram, (ol_program_handle_t Program));
+  MOCK_METHOD(ol_result_t, olCreateContext,
+              (size_t NumDevices, ol_device_handle_t *Devices,
+               ol_context_handle_t *Context));
+  MOCK_METHOD(ol_result_t, olDestroyContext, (ol_context_handle_t Context));
   MOCK_METHOD(ol_result_t, olCreateQueue,
               (ol_device_handle_t Device, ol_queue_handle_t *Queue));
   MOCK_METHOD(ol_result_t, olDestroyQueue, (ol_queue_handle_t Queue));
diff --git a/libsycl/unittests/mock/mock.cpp b/libsycl/unittests/mock/mock.cpp
index 8f5db4b5cb6ec..0a2eefb887c4b 100644
--- a/libsycl/unittests/mock/mock.cpp
+++ b/libsycl/unittests/mock/mock.cpp
@@ -50,6 +50,16 @@ ol_result_t olDestroyProgram(ol_program_handle_t Program) {
   return mock::getMockLiboffload().olDestroyProgram(Program);
 }
 
+ol_result_t olCreateContext(size_t NumDevices, ol_device_handle_t *Devices,
+                            ol_context_handle_t *Context) {
+  return mock::getMockLiboffload().olCreateContext(NumDevices, Devices,
+                                                   Context);
+}
+
+ol_result_t olDestroyContext(ol_context_handle_t Context) {
+  return mock::getMockLiboffload().olDestroyContext(Context);
+}
+
 ol_result_t olCreateQueue(ol_device_handle_t Device, ol_queue_handle_t *Queue) {
   return mock::getMockLiboffload().olCreateQueue(Device, Queue);
 }



More information about the llvm-commits mailing list