[llvm] [offload] Initilize Platforms lazily (PR #226438)

Alex Duran via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 25 07:59:45 PDT 2026


https://github.com/adurang updated https://github.com/llvm/llvm-project/pull/226438

>From c3eb05b51860f7ba30e6a6550a100811c1b21f34 Mon Sep 17 00:00:00 2001
From: "Duran, Alex" <alejandro.duran at intel.com>
Date: Fri, 25 Sep 2026 07:24:14 -0700
Subject: [PATCH] [offload] Initilize Platforms lazily

---
 offload/liboffload/API/Common.td              |  13 ++-
 offload/liboffload/src/OffloadImpl.cpp        | 109 +++++++++++++-----
 .../Conformance/lib/DeviceContext.cpp         |   6 +-
 .../OffloadAPI/common/Environment.cpp         |   4 +-
 offload/unittests/OffloadAPI/init/olInit.cpp  |  13 +++
 5 files changed, 111 insertions(+), 34 deletions(-)

diff --git a/offload/liboffload/API/Common.td b/offload/liboffload/API/Common.td
index f09ee57961d67..5ed15cd4aca63 100644
--- a/offload/liboffload/API/Common.td
+++ b/offload/liboffload/API/Common.td
@@ -149,12 +149,17 @@ def ol_dimensions_t : Struct {
   ];
 }
 
+def OL_ALL_PLATFORMS : Macro {
+  let desc = "Value for ol_init_args_t::NumPlatforms that requests all available platforms to be initialized eagerly.";
+  let value = "UINT32_MAX";
+}
+
 def ol_init_args_t : Struct {
   let desc = "Configuration arguments for olInit.";
   let members = [
     StructMember<"size_t", "Size", "Size of this struct, used for ABI compatibility. Must be set to sizeof(ol_init_args_t) by the caller.">,
-    StructMember<"uint32_t", "NumPlatforms", "Number of entries in the Platforms array.">,
-    StructMember<"const ol_platform_backend_t*", "Platforms", "Pointer to an array of platform backends to initialize.">
+    StructMember<"uint32_t", "NumPlatforms", "Number of entries in the Platforms array, or OL_ALL_PLATFORMS to eagerly initialize all available platforms.">,
+    StructMember<"const ol_platform_backend_t*", "Platforms", "Pointer to an array of platform backends to make available and initialize eagerly.">
   ];
 }
 
@@ -168,7 +173,9 @@ def olInit : Function {
   let details = [
     "This must be the first API call made by a user of the Offload library",
     "Each call will increment an internal reference count that is decremented by `olShutDown`",
-    "If InitArgs is NULL, default configuration is used which initializes all available platforms"
+    "If InitArgs is NULL or NumPlatforms is 0, all available platforms are made available and are lazily initialized on their first use",
+    "If NumPlatforms is OL_ALL_PLATFORMS, all available platforms are made available and are initialized eagerly by this call",
+    "Otherwise only the platforms listed in InitArgs (plus the host platform) are made available and are initialized eagerly by this call"
   ];
   let params = [
     Param<"const ol_init_args_t*", "InitArgs", "Optional pointer to initialization configuration. NULL uses defaults.", PARAM_IN_OPTIONAL>
diff --git a/offload/liboffload/src/OffloadImpl.cpp b/offload/liboffload/src/OffloadImpl.cpp
index 66093d43b56e8..445e486b6b750 100644
--- a/offload/liboffload/src/OffloadImpl.cpp
+++ b/offload/liboffload/src/OffloadImpl.cpp
@@ -31,6 +31,21 @@ struct ol_platform_impl_t {
       : BackendType(BackendType), Plugin(std::move(Plugin)) {}
   ol_platform_backend_t BackendType;
 
+  /// Get the plugin, lazily initializing it if necessary.
+  llvm::Expected<GenericPluginTy *> getPlugin() {
+    if (llvm::Error Err = init())
+      return Err;
+    return Plugin.get();
+  }
+
+  /// Get the device list, lazily initializing it if necessary.
+  llvm::Expected<llvm::SmallVector<std::unique_ptr<ol_device_impl_t>> &>
+  getDevices() {
+    if (llvm::Error Err = init())
+      return Err;
+    return Devices;
+  }
+
   /// Complete all pending work for this platform and perform any needed
   /// cleanup.
   ///
@@ -44,6 +59,11 @@ struct ol_platform_impl_t {
   /// Direct access to the plugin, may be uninitialized if accessed here.
   std::unique_ptr<GenericPluginTy> Plugin;
 
+private:
+  std::once_flag Initialized;
+  /// Most plugins don't allow to retry initialization after failure, so
+  /// remember that it failed.
+  bool InitFailed = false;
   llvm::SmallVector<std::unique_ptr<ol_device_impl_t>> Devices;
 };
 
@@ -62,27 +82,51 @@ struct ol_device_impl_t {
   InfoTreeNode Info;
 };
 
-llvm::Error ol_platform_impl_t::destroy() { return Plugin->deinit(); }
+llvm::Error ol_platform_impl_t::destroy() {
+  if (!Plugin || !Plugin->is_initialized())
+    return llvm::Error::success();
+
+  Devices.clear();
+  return Plugin->deinit();
+}
 
 llvm::Error ol_platform_impl_t::init() {
-  if (!Plugin)
-    return llvm::Error::success();
+  std::unique_ptr<llvm::Error> Storage;
+
+  // This can be called concurrently, make sure we only do the actual
+  // initialization once.
+  std::call_once(Initialized, [&]() {
+    // FIXME: Need better handling for the host platform.
+    if (!Plugin)
+      return;
+
+    auto SetError = [&](llvm::Error Err) {
+      InitFailed = true;
+      Storage = std::make_unique<llvm::Error>(std::move(Err));
+    };
 
-  if (llvm::Error Err = Plugin->init())
-    return Err;
+    if (llvm::Error Err = Plugin->init())
+      return SetError(std::move(Err));
 
-  for (auto Id = 0, End = Plugin->getNumDevices(); Id != End; Id++) {
-    if (llvm::Error Err = Plugin->initDevice(Id))
-      return Err;
+    for (auto Id = 0, End = Plugin->getNumDevices(); Id != End; Id++) {
+      if (llvm::Error Err = Plugin->initDevice(Id))
+        return SetError(std::move(Err));
 
-    GenericDeviceTy *Device = &Plugin->getDevice(Id);
-    llvm::Expected<InfoTreeNode> Info = Device->obtainInfo();
-    if (llvm::Error Err = Info.takeError())
-      return Err;
-    Devices.emplace_back(std::make_unique<ol_device_impl_t>(Id, Device, *this,
-                                                            std::move(*Info)));
-  }
+      GenericDeviceTy *Device = &Plugin->getDevice(Id);
+      llvm::Expected<InfoTreeNode> Info = Device->obtainInfo();
+      if (llvm::Error Err = Info.takeError())
+        return SetError(std::move(Err));
+      Devices.emplace_back(std::make_unique<ol_device_impl_t>(
+          Id, Device, *this, std::move(*Info)));
+    }
+  });
 
+  if (Storage)
+    return std::move(*Storage);
+  if (InitFailed)
+    return createOffloadError(
+        ErrorCode::BACKEND_FAILURE,
+        "platform not available because initialization failure");
   return llvm::Error::success();
 }
 
@@ -304,8 +348,9 @@ ol_platform_backend_t pluginNameToBackend(StringRef Name) {
 #include "Shared/Targets.def"
 
 Error initPlugins(OffloadContext &Context, const ol_init_args_t *InitArgs) {
+  bool InitAll = InitArgs && InitArgs->NumPlatforms == OL_ALL_PLATFORMS;
   SmallSet<ol_platform_backend_t, 0> Requested;
-  if (InitArgs && InitArgs->NumPlatforms > 0)
+  if (InitArgs && !InitAll && InitArgs->NumPlatforms > 0)
     for (uint32_t I = 0; I < InitArgs->NumPlatforms; I++)
       Requested.insert(InitArgs->Platforms[I]);
 
@@ -322,12 +367,14 @@ Error initPlugins(OffloadContext &Context, const ol_init_args_t *InitArgs) {
   } while (false);
 #include "Shared/Targets.def"
 
-  // Eagerly initialize all of the plugins and devices. We need to make sure
-  // that the platform is initialized at a consistent point to maintain the
-  // expected teardown order in the vendor libraries.
-  for (auto &Platform : Context.Platforms) {
-    if (Error Err = Platform->init())
-      return Err;
+  // If platforms were explicitly requested, eagerly initialize all the created
+  // ones (the requested ones and the host). Otherwise they are initialized
+  // lazily on their first use.
+  if (InitAll || !Requested.empty()) {
+    for (auto &Platform : Context.Platforms) {
+      if (Error Err = Platform->init())
+        return Err;
+    }
   }
 
   Context.TracingEnabled = std::getenv("OFFLOAD_TRACE");
@@ -348,7 +395,8 @@ Error olInit_impl(const ol_init_args_t *InitArgs) {
     if (InitArgs->Size < sizeof(ol_init_args_t))
       return createOffloadError(ErrorCode::INVALID_SIZE,
                                 "ol_init_args_t Size field is too small");
-    if (InitArgs->NumPlatforms > 0 && !InitArgs->Platforms)
+    if (InitArgs->NumPlatforms > 0 &&
+        InitArgs->NumPlatforms != OL_ALL_PLATFORMS && !InitArgs->Platforms)
       return createOffloadError(ErrorCode::INVALID_NULL_POINTER,
                                 "NumPlatforms > 0 but Platforms is null");
   }
@@ -373,10 +421,6 @@ Error olShutDown_impl() {
   auto *OldContext = OffloadContextVal.exchange(nullptr);
 
   for (auto &Platform : OldContext->Platforms) {
-    // Host plugin is nullptr and has no deinit
-    if (!Platform->Plugin || !Platform->Plugin->is_initialized())
-      continue;
-
     if (auto Res = Platform->destroy())
       Result = joinErrors(std::move(Result), std::move(Res));
   }
@@ -428,7 +472,11 @@ Error olGetPlatformInfoSize_impl(ol_platform_handle_t Platform,
 
 Error olPlatformRegisterRPCCallback_impl(ol_platform_handle_t Platform,
                                          ol_platform_rpc_cb_t Callback) {
-  Platform->Plugin->getRPCServer().registerCallback(Callback);
+  auto PluginOrErr = Platform->getPlugin();
+  if (!PluginOrErr)
+    return PluginOrErr.takeError();
+
+  (*PluginOrErr)->getRPCServer().registerCallback(Callback);
   return Error::success();
 }
 
@@ -602,7 +650,10 @@ Error olGetDeviceInfoSize_impl(ol_device_handle_t Device,
 
 Error olIterateDevices_impl(ol_device_iterate_cb_t Callback, void *UserData) {
   for (auto &Platform : OffloadContext::get().Platforms) {
-    for (auto &Device : Platform->Devices) {
+    auto DevicesOrErr = Platform->getDevices();
+    if (!DevicesOrErr)
+      return DevicesOrErr.takeError();
+    for (auto &Device : *DevicesOrErr) {
       if (!Callback(Device.get(), UserData)) {
         return Error::success();
       }
diff --git a/offload/unittests/Conformance/lib/DeviceContext.cpp b/offload/unittests/Conformance/lib/DeviceContext.cpp
index 62b265043a2eb..ac963fcc273bd 100644
--- a/offload/unittests/Conformance/lib/DeviceContext.cpp
+++ b/offload/unittests/Conformance/lib/DeviceContext.cpp
@@ -48,7 +48,11 @@ namespace {
 // The static 'Wrapper' instance ensures olInit() is called once at program
 // startup and olShutDown() is called once at program termination
 struct OffloadInitWrapper {
-  OffloadInitWrapper() { OL_CHECK(olInit(nullptr)); }
+  OffloadInitWrapper() {
+    ol_init_args_t Args = OL_INIT_ARGS_INIT;
+    Args.NumPlatforms = OL_ALL_PLATFORMS;
+    OL_CHECK(olInit(&Args));
+  }
   ~OffloadInitWrapper() { OL_CHECK(olShutDown()); }
 };
 static OffloadInitWrapper Wrapper{};
diff --git a/offload/unittests/OffloadAPI/common/Environment.cpp b/offload/unittests/OffloadAPI/common/Environment.cpp
index 89660a5d6a7b4..69bcf3ea4fb75 100644
--- a/offload/unittests/OffloadAPI/common/Environment.cpp
+++ b/offload/unittests/OffloadAPI/common/Environment.cpp
@@ -22,7 +22,9 @@ using namespace llvm;
 #ifndef DISABLE_WRAPPER
 struct OffloadInitWrapper {
   OffloadInitWrapper() {
-    if (ol_result_t Res = olInit(nullptr)) {
+    ol_init_args_t Args = OL_INIT_ARGS_INIT;
+    Args.NumPlatforms = OL_ALL_PLATFORMS;
+    if (ol_result_t Res = olInit(&Args)) {
       errs() << "olInit failed: "
              << (Res->Details ? Res->Details : "(no details)") << " (code "
              << Res->Code << ")\n";
diff --git a/offload/unittests/OffloadAPI/init/olInit.cpp b/offload/unittests/OffloadAPI/init/olInit.cpp
index 4c74122e89d42..d629931f930d2 100644
--- a/offload/unittests/OffloadAPI/init/olInit.cpp
+++ b/offload/unittests/OffloadAPI/init/olInit.cpp
@@ -42,6 +42,19 @@ TEST_F(olInitTest, WithInitArgs) {
   ASSERT_SUCCESS(olShutDown());
 }
 
+TEST_F(olInitTest, WithInitArgsNoPlatforms) {
+  ol_init_args_t Args = OL_INIT_ARGS_INIT;
+  ASSERT_SUCCESS(olInit(&Args));
+  ASSERT_SUCCESS(olShutDown());
+}
+
+TEST_F(olInitTest, WithInitArgsAllPlatforms) {
+  ol_init_args_t Args = OL_INIT_ARGS_INIT;
+  Args.NumPlatforms = OL_ALL_PLATFORMS;
+  ASSERT_SUCCESS(olInit(&Args));
+  ASSERT_SUCCESS(olShutDown());
+}
+
 TEST_F(olInitTest, InvalidSize) {
   ol_init_args_t Args = OL_INIT_ARGS_INIT;
   Args.Size = 0;



More information about the llvm-commits mailing list