[llvm] [libsycl] add heuristics to prefer devices with compatible images (PR #203248)

Kseniya Tikhomirova via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 12 02:19:12 PDT 2026


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

>From d797ec4b7ec2a71f9d4b540f50dea8a4e074bc68 Mon Sep 17 00:00:00 2001
From: "Tikhomirova, Kseniya" <kseniya.tikhomirova at intel.com>
Date: Tue, 26 May 2026 06:05:13 -0700
Subject: [PATCH 1/2] [libsycl] add heuristics to prefer devices with
 compatible images

Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova at intel.com>
---
 libsycl/src/detail/program_manager.cpp        |  13 ++
 libsycl/src/detail/program_manager.hpp        |   4 +
 libsycl/src/device_selector.cpp               |   5 +-
 libsycl/unittests/CMakeLists.txt              |   1 +
 .../unittests/device_selector/CMakeLists.txt  |   3 +
 .../device_selector/get_device_preference.cpp | 177 ++++++++++++++++++
 libsycl/unittests/mock/helpers.hpp            |   4 +
 7 files changed, 206 insertions(+), 1 deletion(-)
 create mode 100644 libsycl/unittests/device_selector/CMakeLists.txt
 create mode 100644 libsycl/unittests/device_selector/get_device_preference.cpp

diff --git a/libsycl/src/detail/program_manager.cpp b/libsycl/src/detail/program_manager.cpp
index 79134ffa7a0b0..459860384b6fb 100644
--- a/libsycl/src/detail/program_manager.cpp
+++ b/libsycl/src/detail/program_manager.cpp
@@ -152,6 +152,19 @@ ProgramAndKernelManager::getOrCreateKernel(DeviceKernelInfo &KernelInfo,
   return Kernel;
 }
 
+bool ProgramAndKernelManager::hasCompatibleImage(const DeviceImpl &Device) {
+  std::lock_guard<std::mutex> Guard(MDataCollectionMutex);
+
+  for (const auto &BinaryImagesPair : MDeviceImageManagers) {
+    for (const auto &Image : BinaryImagesPair.second) {
+      if (isImageCompatible(*Image, Device))
+        return true;
+    }
+  }
+
+  return false;
+}
+
 } // namespace detail
 _LIBSYCL_END_NAMESPACE_SYCL
 
diff --git a/libsycl/src/detail/program_manager.hpp b/libsycl/src/detail/program_manager.hpp
index 9afd6b49bbfd6..828ef9b93aff1 100644
--- a/libsycl/src/detail/program_manager.hpp
+++ b/libsycl/src/detail/program_manager.hpp
@@ -92,6 +92,10 @@ class ProgramAndKernelManager {
   /// Release device image managers and corresponding resources.
   void releaseResources();
 
+  /// \return true if at least one registered device image is compatible with
+  /// the given device.
+  bool hasCompatibleImage(const DeviceImpl &Device);
+
 protected:
   ProgramAndKernelManager() = default;
   ~ProgramAndKernelManager() = default;
diff --git a/libsycl/src/device_selector.cpp b/libsycl/src/device_selector.cpp
index 86e5f5657c6b5..2744cf7afe35c 100644
--- a/libsycl/src/device_selector.cpp
+++ b/libsycl/src/device_selector.cpp
@@ -10,6 +10,7 @@
 #include <sycl/__impl/device_selector.hpp>
 
 #include <detail/device_impl.hpp>
+#include <detail/program_manager.hpp>
 
 #include <algorithm>
 
@@ -25,7 +26,9 @@ static int getDevicePreference(const device &Device) {
   int Score = 0;
   const auto &DeviceImpl = detail::getSyclObjImpl(Device);
 
-  // TODO: increase score for devices with compatible program  images.
+  auto &ProgramManager = detail::ProgramAndKernelManager::getInstance();
+  if (ProgramManager.hasCompatibleImage(*DeviceImpl))
+    Score += 1000;
 
   if (DeviceImpl->getBackend() == backend::level_zero)
     Score += 50;
diff --git a/libsycl/unittests/CMakeLists.txt b/libsycl/unittests/CMakeLists.txt
index 5fa7b6ada0cb4..56bb28161737c 100644
--- a/libsycl/unittests/CMakeLists.txt
+++ b/libsycl/unittests/CMakeLists.txt
@@ -5,6 +5,7 @@ add_custom_target(LibsyclUnitTests)
 add_custom_target(check-sycl-unittests)
 
 add_subdirectory(mock)
+add_subdirectory(device_selector)
 add_subdirectory(platform)
 add_subdirectory(program_manager)
 add_subdirectory(queue)
diff --git a/libsycl/unittests/device_selector/CMakeLists.txt b/libsycl/unittests/device_selector/CMakeLists.txt
new file mode 100644
index 0000000000000..195ba15dca1e8
--- /dev/null
+++ b/libsycl/unittests/device_selector/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_sycl_unittest(DeviceSelectorTests
+    get_device_preference.cpp
+)
diff --git a/libsycl/unittests/device_selector/get_device_preference.cpp b/libsycl/unittests/device_selector/get_device_preference.cpp
new file mode 100644
index 0000000000000..a23ecadba0f98
--- /dev/null
+++ b/libsycl/unittests/device_selector/get_device_preference.cpp
@@ -0,0 +1,177 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <mock/helpers.hpp>
+
+#include <common/device_images.hpp>
+
+#include <detail/program_manager.hpp>
+
+#include <detail/device_impl.hpp>
+
+#include <sycl/__impl/detail/obj_utils.hpp>
+#include <sycl/__impl/device_selector.hpp>
+#include <sycl/sycl.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace sycl;
+using namespace ::testing;
+
+namespace {
+
+class ScopedBinaryRegistration {
+public:
+  explicit ScopedBinaryRegistration(llvm::ArrayRef<llvm::StringRef> KernelNames)
+      : MBinary(sycl::unittest::createSYCLDeviceBinary(KernelNames)) {
+    sycl::detail::ProgramAndKernelManager::getInstance().registerFatBin(
+        MBinary.data(), MBinary.size());
+  }
+
+  ~ScopedBinaryRegistration() {
+    sycl::detail::ProgramAndKernelManager::getInstance().unregisterFatBin(
+        MBinary.data(), MBinary.size());
+  }
+
+private:
+  llvm::SmallString<0> MBinary;
+};
+
+class DeviceSelectorScoreTest : public ::testing::Test {
+protected:
+  void SetUp() override {
+    // In reality gpu and cpu devices relate to different platforms. These
+    // tests don't have to follow this rule since selectors work with types.
+    Platform = mock::createDummyHandle<ol_platform_handle_t>();
+    Device1 = mock::createDummyHandleWithData<ol_device_handle_t>(
+        reinterpret_cast<unsigned char *>(&Platform), sizeof(Platform));
+    Device2 = mock::createDummyHandleWithData<ol_device_handle_t>(
+        reinterpret_cast<unsigned char *>(&Platform), sizeof(Platform));
+
+    EXPECT_CALL(Mock.get(), olIterateDevices(_, _))
+        .WillRepeatedly([this](ol_device_iterate_cb_t Callback,
+                               void *UserData) -> ol_result_t {
+          std::ignore = Callback(Device1, UserData);
+          std::ignore = Callback(Device2, UserData);
+          return OL_SUCCESS;
+        });
+
+    EXPECT_CALL(Mock.get(), olGetDeviceInfo(_, OL_DEVICE_INFO_PLATFORM, _, _))
+        .WillRepeatedly([this](ol_device_handle_t Device,
+                               ol_device_info_t /*PropName*/, size_t PropSize,
+                               void *PropValue) -> ol_result_t {
+          *static_cast<ol_platform_handle_t *>(PropValue) = Platform;
+          return OL_SUCCESS;
+        });
+  }
+
+  void TearDown() override {
+    mock::releaseDummyHandles(Platform, Device1, Device2);
+  }
+
+  mock::MockWrapper Mock;
+  ol_platform_handle_t Platform{};
+  ol_device_handle_t Device1{};
+  ol_device_handle_t Device2{};
+};
+
+TEST_F(DeviceSelectorScoreTest, CPUAndGPU) {
+  EXPECT_CALL(Mock.get(), olGetDeviceInfo(_, OL_DEVICE_INFO_TYPE, _, _))
+      .WillRepeatedly([this](ol_device_handle_t Device,
+                             ol_device_info_t /*PropName*/, size_t PropSize,
+                             void *PropValue) -> ol_result_t {
+        if (Device == Device1)
+          *static_cast<ol_device_type_t *>(PropValue) = OL_DEVICE_TYPE_GPU;
+        else if (Device == Device2)
+          *static_cast<ol_device_type_t *>(PropValue) = OL_DEVICE_TYPE_CPU;
+        else
+          return mock::getMockLiboffload().makeEmptyStrError(
+              OL_ERRC_INVALID_NULL_HANDLE);
+
+        return OL_SUCCESS;
+      });
+
+  auto Devices = sycl::device::get_devices();
+  ASSERT_EQ(Devices.size(), 2u);
+
+  for (const auto &Dev : Devices) {
+    if (Dev.is_gpu()) {
+      EXPECT_EQ(sycl::default_selector_v(Dev), 550);
+      EXPECT_EQ(sycl::gpu_selector_v(Dev), 1050);
+      EXPECT_EQ(sycl::cpu_selector_v(Dev), -1);
+      EXPECT_EQ(sycl::accelerator_selector_v(Dev), -1);
+    } else if (Dev.is_cpu()) {
+      EXPECT_EQ(sycl::default_selector_v(Dev), 350);
+      EXPECT_EQ(sycl::gpu_selector_v(Dev), -1);
+      EXPECT_EQ(sycl::cpu_selector_v(Dev), 1050);
+      EXPECT_EQ(sycl::accelerator_selector_v(Dev), -1);
+    } else
+      FAIL() << "Unexpected device type";
+  }
+}
+
+TEST_F(DeviceSelectorScoreTest, TwoGpusOneCompatibleImage) {
+  EXPECT_CALL(Mock.get(), olGetDeviceInfo(_, OL_DEVICE_INFO_TYPE, _, _))
+      .WillRepeatedly([](ol_device_handle_t Device,
+                         ol_device_info_t /*PropName*/, size_t PropSize,
+                         void *PropValue) -> ol_result_t {
+        *static_cast<ol_device_type_t *>(PropValue) = OL_DEVICE_TYPE_GPU;
+        return OL_SUCCESS;
+      });
+
+  EXPECT_CALL(Mock.get(), olIsValidBinary(_, _, _, _))
+      .WillRepeatedly([this](ol_device_handle_t Device,
+                             const void * /*ProgData*/, size_t /*ProgDataSize*/,
+                             bool *Valid) -> ol_result_t {
+        *Valid = (Device == Device2);
+        return OL_SUCCESS;
+      });
+
+  std::array<llvm::StringRef, 1> KernelNames = {"kernel"};
+  ScopedBinaryRegistration Registration{KernelNames};
+
+  auto Devices = sycl::device::get_devices();
+  ASSERT_EQ(Devices.size(), 2u);
+
+  auto DeviceNative = sycl::detail::getSyclObjImpl(Devices[0])->getOLHandle();
+  int Score = sycl::default_selector_v(Devices[0]);
+  if (DeviceNative == Device1)
+    EXPECT_EQ(Score, 550);
+  else if (DeviceNative == Device2)
+    EXPECT_EQ(Score, 1550);
+  else
+    FAIL() << "Unexpected device handle: ";
+
+  sycl::device DefaultDevice{sycl::default_selector_v};
+  auto DeviceDefaultNative =
+      sycl::detail::getSyclObjImpl(DefaultDevice)->getOLHandle();
+  EXPECT_EQ(DeviceDefaultNative, Device2);
+}
+
+TEST(DeviceSelector, AspectSelector) {
+  auto Devices = sycl::device::get_devices();
+  ASSERT_FALSE(Devices.empty());
+
+  const sycl::device &Dev = Devices.front();
+
+  const std::vector<sycl::aspect> EmptyAspects{};
+  const std::vector<sycl::aspect> RequireGpu{sycl::aspect::gpu};
+  const std::vector<sycl::aspect> DenyGpu{sycl::aspect::gpu};
+
+  auto FallbackSelector = sycl::aspect_selector(EmptyAspects, EmptyAspects);
+  EXPECT_EQ(FallbackSelector(Dev), sycl::default_selector_v(Dev));
+
+  auto RequireGpuSelector = sycl::aspect_selector(RequireGpu, EmptyAspects);
+  EXPECT_EQ(RequireGpuSelector(Dev), 1050);
+
+  auto DenyGpuSelector = sycl::aspect_selector(EmptyAspects, DenyGpu);
+  EXPECT_EQ(DenyGpuSelector(Dev), -1);
+}
+
+} // namespace
diff --git a/libsycl/unittests/mock/helpers.hpp b/libsycl/unittests/mock/helpers.hpp
index 77a4b39ca8ced..5de2ac0098f13 100644
--- a/libsycl/unittests/mock/helpers.hpp
+++ b/libsycl/unittests/mock/helpers.hpp
@@ -65,6 +65,10 @@ template <class T> void releaseDummyHandle(T Handle) {
   delete DummyHandlePtr;
 }
 
+template <typename... HandleT> void releaseDummyHandles(HandleT... Handles) {
+  (releaseDummyHandle(Handles), ...);
+}
+
 class MockLiboffload {
 public:
   MockLiboffload() { initDefault(); }

>From 2289b7ccccd3e84bf59b3cb986df7ccbdbd6f58c Mon Sep 17 00:00:00 2001
From: "Tikhomirova, Kseniya" <kseniya.tikhomirova at intel.com>
Date: Fri, 12 Jun 2026 02:18:58 -0700
Subject: [PATCH 2/2] reset internal state

Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova at intel.com>
---
 libsycl/src/detail/global_objects.cpp         | 57 +++++++++-------
 libsycl/src/detail/global_objects.hpp         | 66 ++++++++++++++-----
 .../src/detail/offload/offload_topology.cpp   |  2 +-
 libsycl/src/detail/platform_impl.cpp          | 22 ++-----
 libsycl/src/detail/platform_impl.hpp          | 10 +--
 libsycl/unittests/common/unittests_helper.hpp | 40 +++++++++++
 .../device_selector/get_device_preference.cpp | 12 +++-
 libsycl/unittests/mock/helpers.cpp            | 25 +++----
 8 files changed, 159 insertions(+), 75 deletions(-)
 create mode 100644 libsycl/unittests/common/unittests_helper.hpp

diff --git a/libsycl/src/detail/global_objects.cpp b/libsycl/src/detail/global_objects.cpp
index fd94d772337d6..719a621e5dcaf 100644
--- a/libsycl/src/detail/global_objects.cpp
+++ b/libsycl/src/detail/global_objects.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include <detail/global_objects.hpp>
+#include <detail/offload/offload_utils.hpp>
 #include <detail/platform_impl.hpp>
 #include <detail/program_manager.hpp>
 
@@ -18,37 +19,43 @@
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace detail {
-// libsycl follows SYCL 2020 specification that doesn't declare any
-// init/shutdown methods that can help to avoid usage of static variables.
-// liboffload uses static variables too. In the first call of get_platforms
-// we call liboffload's iterateDevices that leads to liboffload static
-// storage initialization. Then we initialize our own local static var of
-// StaticVarShutdownHandler type to be able to call our shutdown methods
-// earlier and before the liboffload objects are destructed at the end of
-// program. See documentation of std::exit for local objects with static
-// storage duration.
-struct StaticVarShutdownHandler {
-  StaticVarShutdownHandler(const StaticVarShutdownHandler &) = delete;
-  StaticVarShutdownHandler &
-  operator=(const StaticVarShutdownHandler &) = delete;
-  ~StaticVarShutdownHandler() {
-    ProgramAndKernelManager::getInstance().releaseResources();
-    // No error reporting in shutdown
-    std::ignore = olShutDown();
-  }
-};
 
-void registerStaticVarShutdownHandler() {
-  static StaticVarShutdownHandler handler{};
+GlobalHandler::StaticVarShutdownHandler::~StaticVarShutdownHandler() {
+  ProgramAndKernelManager::getInstance().releaseResources();
+  GlobalHandler::resetGlobalObjects();
+}
+
+void GlobalHandler::resetGlobalObjects() {
+  getPlatformCache().clear();
+  getOffloadTopologies() = {};
+
+  // No error reporting in shutdown
+  std::ignore = olShutDown();
+}
+
+void GlobalHandler::initPlatforms() {
+  discoverOffloadDevices();
+
+  registerStaticVarShutdownHandler();
+
+  auto &PlatformCache = getPlatformCache();
+  for (const auto &Topo : getOffloadTopologies()) {
+    size_t PlatformIndex = 0;
+    for (const auto &OffloadPlatform : Topo.getPlatforms()) {
+      PlatformCache.emplace_back(std::make_unique<PlatformImpl>(
+          OffloadPlatform, PlatformIndex++, PlatformImpl::PrivateTag{}));
+    }
+  }
 }
 
-std::vector<detail::OffloadTopology> &getOffloadTopologies() {
-  static std::vector<detail::OffloadTopology> Topologies(
-      OL_PLATFORM_BACKEND_LAST);
+std::array<detail::OffloadTopology, OL_PLATFORM_BACKEND_LAST> &
+GlobalHandler::getOffloadTopologies() {
+  static std::array<detail::OffloadTopology, OL_PLATFORM_BACKEND_LAST>
+      Topologies{};
   return Topologies;
 }
 
-std::vector<PlatformImplUPtr> &getPlatformCache() {
+std::vector<PlatformImplUPtr> &GlobalHandler::getPlatformCache() {
   static std::vector<PlatformImplUPtr> PlatformCache{};
   return PlatformCache;
 }
diff --git a/libsycl/src/detail/global_objects.hpp b/libsycl/src/detail/global_objects.hpp
index 4d3106aacd560..2511acf31a92b 100644
--- a/libsycl/src/detail/global_objects.hpp
+++ b/libsycl/src/detail/global_objects.hpp
@@ -26,24 +26,58 @@ _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace detail {
 class PlatformImpl;
 
-/// Returns offload topologies (one per backend) discovered from liboffload.
-///
-/// This vector is populated only once at the first call of get_platforms().
-///
-/// \returns std::vector of all offload topologies.
-std::vector<detail::OffloadTopology> &getOffloadTopologies();
+class GlobalHandler {
+public:
+  /// Returns offload topologies (one per backend) discovered from liboffload.
+  ///
+  /// This array is populated only once at the first call of get_platforms().
+  ///
+  /// \returns std::array of all offload topologies.
+  static std::array<detail::OffloadTopology, OL_PLATFORM_BACKEND_LAST> &
+  getOffloadTopologies();
 
-/// Returns implementation class objects for all platforms discovered from
-/// liboffload.
-///
-/// This vector is populated only once at the first call of get_platforms().
-///
-/// \returns std::vector of implementation objects for all platforms.
-std::vector<std::unique_ptr<PlatformImpl>> &getPlatformCache();
+  /// Returns implementation class objects for all platforms discovered from
+  /// liboffload.
+  ///
+  /// This vector is populated only once at the first call of get_platforms().
+  ///
+  /// \returns std::vector of implementation objects for all platforms.
+  static std::vector<std::unique_ptr<PlatformImpl>> &getPlatformCache();
+
+protected:
+  GlobalHandler() = delete;
+
+  // libsycl follows SYCL 2020 specification that doesn't declare any
+  // init/shutdown methods that can help to avoid usage of static variables.
+  // liboffload uses static variables too. In the first call of get_platforms
+  // we call liboffload's iterateDevices that leads to liboffload static
+  // storage initialization. Then we initialize our own local static var of
+  // StaticVarShutdownHandler type to be able to call our shutdown methods
+  // earlier and before the liboffload objects are destructed at the end of
+  // program. See documentation of std::exit for local objects with static
+  // storage duration.
+  struct StaticVarShutdownHandler {
+    StaticVarShutdownHandler(const StaticVarShutdownHandler &) = delete;
+    StaticVarShutdownHandler &
+    operator=(const StaticVarShutdownHandler &) = delete;
+    ~StaticVarShutdownHandler();
+  };
+
+  static void registerStaticVarShutdownHandler() {
+    static StaticVarShutdownHandler handler{};
+  }
+
+  // These methods and 2 friends declarations below are needed to be able to
+  // call them in tests to reset global state of libsycl and liboffload between
+  // tests. During normal execution initPlatforms is called once from
+  // get_platforms and resetGlobalObjects is called once during static variables
+  // destruction at the end of program.
+  static void initPlatforms();
+  static void resetGlobalObjects();
 
-// This initializes a function-local variable whose destructor is invoked as
-// the SYCL shared library is first being unloaded.
-void registerStaticVarShutdownHandler();
+  friend class PlatformImpl;
+  friend class UnittestsHelper;
+};
 
 } // namespace detail
 _LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/detail/offload/offload_topology.cpp b/libsycl/src/detail/offload/offload_topology.cpp
index ab4c57ecf37eb..be1c32e938da3 100644
--- a/libsycl/src/detail/offload/offload_topology.cpp
+++ b/libsycl/src/detail/offload/offload_topology.cpp
@@ -104,7 +104,7 @@ void discoverOffloadDevices() {
       },
       &Mapping);
   // Now register all platforms and devices into the topologies
-  auto &OffloadTopologies = getOffloadTopologies();
+  auto &OffloadTopologies = GlobalHandler::getOffloadTopologies();
   for (size_t I = 0; I < OL_PLATFORM_BACKEND_LAST; ++I) {
     OffloadTopology &Topo = OffloadTopologies[I];
     Topo.setBackend(static_cast<ol_platform_backend_t>(I));
diff --git a/libsycl/src/detail/platform_impl.cpp b/libsycl/src/detail/platform_impl.cpp
index 932f282619b0e..338475dbfb7b3 100644
--- a/libsycl/src/detail/platform_impl.cpp
+++ b/libsycl/src/detail/platform_impl.cpp
@@ -12,6 +12,7 @@
 #include <detail/context_impl.hpp>
 #include <detail/device_impl.hpp>
 #include <detail/global_objects.hpp>
+#include <detail/offload/offload_utils.hpp>
 #include <detail/platform_impl.hpp>
 
 #include <algorithm>
@@ -22,7 +23,7 @@ _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace detail {
 
 PlatformImpl &PlatformImpl::getPlatformImpl(ol_platform_handle_t Platform) {
-  auto &PlatformCache = getPlatformCache();
+  auto &PlatformCache = GlobalHandler::getPlatformCache();
   for (auto &PlatImpl : PlatformCache) {
     assert(PlatImpl && "Platform impl can not be nullptr");
     if (PlatImpl->getOLHandleRef() == Platform)
@@ -36,22 +37,11 @@ PlatformImpl &PlatformImpl::getPlatformImpl(ol_platform_handle_t Platform) {
 }
 
 const std::vector<PlatformImplUPtr> &PlatformImpl::getPlatforms() {
-  [[maybe_unused]] static auto InitPlatformsOnce = []() {
-    discoverOffloadDevices();
-
-    registerStaticVarShutdownHandler();
-
-    auto &PlatformCache = getPlatformCache();
-    for (const auto &Topo : getOffloadTopologies()) {
-      size_t PlatformIndex = 0;
-      for (const auto &OffloadPlatform : Topo.getPlatforms()) {
-        PlatformCache.emplace_back(std::make_unique<PlatformImpl>(
-            OffloadPlatform, PlatformIndex++, PrivateTag{}));
-      }
-    }
+  [[maybe_unused]] static auto InitPlatformsOnce = [] {
+    GlobalHandler::initPlatforms();
     return true;
   }();
-  return getPlatformCache();
+  return GlobalHandler::getPlatformCache();
 }
 
 PlatformImpl::PlatformImpl(ol_platform_handle_t Platform, size_t PlatformIndex,
@@ -63,7 +53,7 @@ PlatformImpl::PlatformImpl(ol_platform_handle_t Platform, size_t PlatformIndex,
   MBackend = convertBackend(Backend);
   MOffloadBackend = Backend;
 
-  const auto &Topologies = getOffloadTopologies();
+  const auto &Topologies = GlobalHandler::getOffloadTopologies();
   auto RootTopologyIt = std::find_if(
       Topologies.begin(), Topologies.end(), [&](const OffloadTopology &Topo) {
         return Topo.getBackend() == MOffloadBackend;
diff --git a/libsycl/src/detail/platform_impl.hpp b/libsycl/src/detail/platform_impl.hpp
index 6e6f52b0bbaef..195d87242e224 100644
--- a/libsycl/src/detail/platform_impl.hpp
+++ b/libsycl/src/detail/platform_impl.hpp
@@ -20,7 +20,6 @@
 #include <sycl/__impl/platform.hpp>
 
 #include <detail/device_impl.hpp>
-#include <detail/offload/offload_utils.hpp>
 
 #include <OffloadAPI.h>
 
@@ -36,14 +35,15 @@ namespace detail {
 
 class DeviceImpl;
 class ContextImpl;
+class GlobalHandler;
 
 using PlatformImplUPtr = std::unique_ptr<PlatformImpl>;
 using DeviceImplUPtr = std::unique_ptr<DeviceImpl>;
 
 class PlatformImpl {
-  // Helper to limit PlatformImpl creation. It must be created in getPlatforms
-  // only. Using tag instead of private ctor + friend class to allow make_unique
-  // usage and to align with classes which impl is shared_ptr<>.
+  // Helper to limit PlatformImpl creation. It must be created in GlobalHandler
+  // only. Using tag instead of private ctor + friend declaration to allow
+  // make_unique usage and to align with classes whose impl is shared_ptr<>.
   struct PrivateTag {
     explicit PrivateTag() = default;
   };
@@ -147,6 +147,8 @@ class PlatformImpl {
   std::vector<DeviceImplUPtr> MRootDevices;
 
   std::shared_ptr<ContextImpl> MDefaultContext;
+
+  friend class GlobalHandler;
 };
 
 } // namespace detail
diff --git a/libsycl/unittests/common/unittests_helper.hpp b/libsycl/unittests/common/unittests_helper.hpp
new file mode 100644
index 0000000000000..43fc1e293850f
--- /dev/null
+++ b/libsycl/unittests/common/unittests_helper.hpp
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Helper utilities for libsycl unit tests.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL_UNITTESTS_COMMON_UNITTESTS_HELPER_HPP
+#define _LIBSYCL_UNITTESTS_COMMON_UNITTESTS_HELPER_HPP
+
+#include <detail/global_objects.hpp>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+namespace detail {
+
+// This helper is not included to LiboffloadMock to keep LiboffloadMock isolated
+// from libsycl implementation and to not introduce extra operations for tests
+// where devices enumeration logic is not important. LiboffloadMock provides
+// default single gpu-device configuration that is enough for most of the tests.
+// For tests where devices enumeration logic is important, UnittestsHelper
+// allows to call global state reset and platforms initialization methods to be
+// able to set expectations on devices enumeration calls in a proper way.
+class UnittestsHelper {
+public:
+  static void initPlatforms() { GlobalHandler::initPlatforms(); }
+
+  static void resetGlobalObjects() { GlobalHandler::resetGlobalObjects(); }
+};
+
+} // namespace detail
+_LIBSYCL_END_NAMESPACE_SYCL
+
+#endif // _LIBSYCL_UNITTESTS_COMMON_UNITTESTS_HELPER_HPP
diff --git a/libsycl/unittests/device_selector/get_device_preference.cpp b/libsycl/unittests/device_selector/get_device_preference.cpp
index a23ecadba0f98..4ba22369941c3 100644
--- a/libsycl/unittests/device_selector/get_device_preference.cpp
+++ b/libsycl/unittests/device_selector/get_device_preference.cpp
@@ -9,10 +9,10 @@
 #include <mock/helpers.hpp>
 
 #include <common/device_images.hpp>
-
-#include <detail/program_manager.hpp>
+#include <common/unittests_helper.hpp>
 
 #include <detail/device_impl.hpp>
+#include <detail/program_manager.hpp>
 
 #include <sycl/__impl/detail/obj_utils.hpp>
 #include <sycl/__impl/device_selector.hpp>
@@ -69,9 +69,17 @@ class DeviceSelectorScoreTest : public ::testing::Test {
           *static_cast<ol_platform_handle_t *>(PropValue) = Platform;
           return OL_SUCCESS;
         });
+
+    // Each test case here uses its own device iteration expectations. So it is
+    // necessary to clear internal platform & device caches before each test to
+    // avoid using cached data that doesn't correspond to current expectations.
+    // In real life this is done in shutdown method that is called at the end of
+    // program.
+    sycl::detail::UnittestsHelper::initPlatforms();
   }
 
   void TearDown() override {
+    sycl::detail::UnittestsHelper::resetGlobalObjects();
     mock::releaseDummyHandles(Platform, Device1, Device2);
   }
 
diff --git a/libsycl/unittests/mock/helpers.cpp b/libsycl/unittests/mock/helpers.cpp
index abc68fd2a29d3..6b5f36bb355bb 100644
--- a/libsycl/unittests/mock/helpers.cpp
+++ b/libsycl/unittests/mock/helpers.cpp
@@ -41,20 +41,18 @@ void mock::MockLiboffload::initDefault() {
         if (InitArgs && (*InitArgs != DefaultArgs))
           return makeEmptyStrError(OL_ERRC_UNIMPLEMENTED);
 
-        assert(!DefaultDevice);
-        DefaultPlatform = mock::createDummyHandle<ol_platform_handle_t>();
-        DefaultDevice = mock::createDummyHandleWithData<ol_device_handle_t>(
-            reinterpret_cast<unsigned char *>(&DefaultPlatform),
-            sizeof(DefaultPlatform));
-
         return OL_SUCCESS;
       });
 
   ON_CALL(*this, olShutDown).WillByDefault([this]() -> ol_result_t {
-    assert(DefaultDevice);
-
-    mock::releaseDummyHandle(DefaultPlatform);
-    mock::releaseDummyHandle(DefaultDevice);
+    if (DefaultDevice) {
+      mock::releaseDummyHandle(DefaultDevice);
+      DefaultDevice = nullptr;
+    }
+    if (DefaultPlatform) {
+      mock::releaseDummyHandle(DefaultPlatform);
+      DefaultPlatform = nullptr;
+    }
 
     return OL_SUCCESS;
   });
@@ -157,7 +155,12 @@ void mock::MockLiboffload::initDefault() {
         if (!Callback)
           return makeEmptyStrError(OL_ERRC_INVALID_NULL_POINTER);
 
-        assert(DefaultDevice);
+        if (!DefaultDevice) {
+          DefaultPlatform = mock::createDummyHandle<ol_platform_handle_t>();
+          DefaultDevice = mock::createDummyHandleWithData<ol_device_handle_t>(
+              reinterpret_cast<unsigned char *>(&DefaultPlatform),
+              sizeof(DefaultPlatform));
+        }
         std::ignore = Callback(DefaultDevice, UserData);
 
         return OL_SUCCESS;



More information about the llvm-commits mailing list