[llvm] [Offload] add 'olIterateSymbols' runtime function (PR #213036)

Joseph Huber via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 30 08:47:39 PDT 2026


https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/213036

>From 8b39ee55cda0ad4e663f4503eb80d481d2870574 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Thu, 30 Jul 2026 09:14:45 -0500
Subject: [PATCH 1/2] [Offload] add 'olIterateSymbols' runtime function

Summary:
This provides users with a way to check all symbols present in a given
program. This is useful for many cases where a power user wants to do
something dynamic with the objects present.
---
 offload/liboffload/API/Symbol.td              |  32 +++++
 offload/liboffload/src/OffloadImpl.cpp        |  75 ++++++++---
 offload/plugins-nextgen/amdgpu/src/rtl.cpp    |  33 ++++-
 .../common/include/GlobalHandler.h            |  18 ++-
 .../common/include/PluginInterface.h          |   4 +-
 .../common/src/GlobalHandler.cpp              |  46 +++++++
 offload/plugins-nextgen/cuda/src/rtl.cpp      |   4 +-
 offload/plugins-nextgen/host/src/rtl.cpp      |   4 +-
 .../level_zero/include/L0Device.h             |   2 +-
 .../level_zero/include/L0Kernel.h             |   2 +-
 .../level_zero/include/L0Program.h            |   8 ++
 .../level_zero/src/L0Device.cpp               |   2 +-
 .../level_zero/src/L0Program.cpp              |  15 +++
 offload/unittests/OffloadAPI/CMakeLists.txt   |   3 +-
 .../OffloadAPI/symbol/olGetSymbolInfo.cpp     |  18 +++
 .../OffloadAPI/symbol/olIterateSymbols.cpp    | 118 ++++++++++++++++++
 16 files changed, 355 insertions(+), 29 deletions(-)
 create mode 100644 offload/unittests/OffloadAPI/symbol/olIterateSymbols.cpp

diff --git a/offload/liboffload/API/Symbol.td b/offload/liboffload/API/Symbol.td
index c57a2e1b83632..9e2f92fbc3e16 100644
--- a/offload/liboffload/API/Symbol.td
+++ b/offload/liboffload/API/Symbol.td
@@ -32,6 +32,37 @@ def olGetSymbol : Function {
     let returns = [];
 }
 
+def ol_symbol_iterate_cb_t : FptrTypedef {
+  let desc = "User-provided function to be used with `olIterateSymbols`";
+  let params = [
+    Param<"ol_symbol_handle_t", "Symbol", "the symbol handle of the current iteration", PARAM_IN>,
+    Param<"void*", "UserData", "optional user data", PARAM_IN_OPTIONAL>
+  ];
+  let return = "bool";
+}
+
+def olIterateSymbols : Function {
+    let desc = "Iterates over all symbols of the given kind in the program, calling the callback for each symbol.";
+    let details = [
+        "If the user-provided callback returns `false`, the iteration is stopped.",
+        "Symbol handles are owned by the program and do not need to be manually destroyed."
+    ];
+    let params = [
+        Param<"ol_program_handle_t", "Program", "handle of the program", PARAM_IN>,
+        Param<"ol_symbol_kind_t", "Kind", "symbol kind to iterate over", PARAM_IN>,
+        Param<"ol_symbol_iterate_cb_t", "Callback", "User-provided function called for each symbol", PARAM_IN>,
+        Param<"void*", "UserData", "Optional user data to pass to the callback", PARAM_IN_OPTIONAL>
+    ];
+    let returns = [
+      Return<"OL_ERRC_UNSUPPORTED", [
+        "The platform could not enumerate symbols of the given kind."
+      ]>,
+      Return<"OL_ERRC_INVALID_BINARY", [
+        "The program's image could not be parsed to enumerate its symbols."
+      ]>
+    ];
+}
+
 def ol_symbol_info_t : Enum {
   let desc = "Supported symbol info.";
   let is_typed = 1;
@@ -39,6 +70,7 @@ def ol_symbol_info_t : Enum {
     TaggedEtor<"KIND", "ol_symbol_kind_t", "The kind of this symbol.">,
     TaggedEtor<"GLOBAL_VARIABLE_ADDRESS", "void *", "The address in memory for this global variable.">,
     TaggedEtor<"GLOBAL_VARIABLE_SIZE", "size_t", "The size in bytes for this global variable.">,
+    TaggedEtor<"NAME", "char[]", "The name of this symbol.">,
   ];
 }
 
diff --git a/offload/liboffload/src/OffloadImpl.cpp b/offload/liboffload/src/OffloadImpl.cpp
index 63fe726fbe544..a57e34c7fb650 100644
--- a/offload/liboffload/src/OffloadImpl.cpp
+++ b/offload/liboffload/src/OffloadImpl.cpp
@@ -199,10 +199,12 @@ struct ol_program_impl_t {
 };
 
 struct ol_symbol_impl_t {
-  ol_symbol_impl_t(const char *Name, GenericKernelTy *Kernel)
-      : PluginImpl(Kernel), Kind(OL_SYMBOL_KIND_KERNEL), Name(Name) {}
-  ol_symbol_impl_t(const char *Name, GlobalTy &&Global)
-      : PluginImpl(Global), Kind(OL_SYMBOL_KIND_GLOBAL_VARIABLE), Name(Name) {}
+  ol_symbol_impl_t(GenericKernelTy *Kernel)
+      : PluginImpl(Kernel), Kind(OL_SYMBOL_KIND_KERNEL),
+        Name(Kernel->getName()) {}
+  ol_symbol_impl_t(GlobalTy &&Global)
+      : PluginImpl(std::move(Global)), Kind(OL_SYMBOL_KIND_GLOBAL_VARIABLE),
+        Name(std::get<GlobalTy>(PluginImpl).getName()) {}
   std::variant<GenericKernelTy *, GlobalTy> PluginImpl;
   ol_symbol_kind_t Kind;
   llvm::StringRef Name;
@@ -1281,12 +1283,11 @@ Error olLaunchKernel_impl(ol_queue_handle_t Queue, ol_device_handle_t Device,
   return Error::success();
 }
 
-Error olGetSymbol_impl(ol_program_handle_t Program, const char *Name,
-                       ol_symbol_kind_t Kind, ol_symbol_handle_t *Symbol) {
+Expected<ol_symbol_handle_t> getSymbolImplDetail(ol_program_handle_t Program,
+                                                 StringRef Name,
+                                                 ol_symbol_kind_t Kind) {
   auto &Device = Program->Image->getDevice();
 
-  std::lock_guard<std::mutex> Lock(Program->SymbolListMutex);
-
   switch (Kind) {
   case OL_SYMBOL_KIND_KERNEL: {
     auto &Kernel = Program->KernelSymbols[Name];
@@ -1298,12 +1299,10 @@ Error olGetSymbol_impl(ol_program_handle_t Program, const char *Name,
       if (auto Err = KernelImpl->init(Device, *Program->Image))
         return Err;
 
-      Kernel = std::make_unique<ol_symbol_impl_t>(KernelImpl->getName(),
-                                                  &*KernelImpl);
+      Kernel = std::make_unique<ol_symbol_impl_t>(&*KernelImpl);
     }
 
-    *Symbol = Kernel.get();
-    return Error::success();
+    return Kernel.get();
   }
   case OL_SYMBOL_KIND_GLOBAL_VARIABLE: {
     auto &Global = Program->GlobalSymbols[Name];
@@ -1314,12 +1313,10 @@ Error olGetSymbol_impl(ol_program_handle_t Program, const char *Name,
                   Device, *Program->Image, GlobalObj))
         return Res;
 
-      Global = std::make_unique<ol_symbol_impl_t>(GlobalObj.getName().c_str(),
-                                                  std::move(GlobalObj));
+      Global = std::make_unique<ol_symbol_impl_t>(std::move(GlobalObj));
     }
 
-    *Symbol = Global.get();
-    return Error::success();
+    return Global.get();
   }
   default:
     return createOffloadError(ErrorCode::INVALID_ENUMERATION,
@@ -1327,6 +1324,50 @@ Error olGetSymbol_impl(ol_program_handle_t Program, const char *Name,
   }
 }
 
+Error olGetSymbol_impl(ol_program_handle_t Program, const char *Name,
+                       ol_symbol_kind_t Kind, ol_symbol_handle_t *Symbol) {
+  std::lock_guard<std::mutex> Lock(Program->SymbolListMutex);
+
+  auto SymbolOrErr = getSymbolImplDetail(Program, Name, Kind);
+  if (!SymbolOrErr)
+    return SymbolOrErr.takeError();
+
+  *Symbol = *SymbolOrErr;
+  return Error::success();
+}
+
+Error olIterateSymbols_impl(ol_program_handle_t Program, ol_symbol_kind_t Kind,
+                            ol_symbol_iterate_cb_t Callback, void *UserData) {
+  SymbolKindTy PluginKind;
+  switch (Kind) {
+  case OL_SYMBOL_KIND_KERNEL:
+    PluginKind = SymbolKindTy::Kernel;
+    break;
+  case OL_SYMBOL_KIND_GLOBAL_VARIABLE:
+    PluginKind = SymbolKindTy::GlobalVariable;
+    break;
+  default:
+    return createOffloadError(ErrorCode::INVALID_ENUMERATION,
+                              "iterateSymbols kind enum '%i' is invalid", Kind);
+  }
+
+  auto &Device = Program->Image->getDevice();
+  std::lock_guard<std::mutex> Lock(Program->SymbolListMutex);
+
+  Error SymbolErr = Error::success();
+  Error IterateErr = Device.Plugin.getGlobalHandler().iterateSymbols(
+      *Program->Image, PluginKind, [&](StringRef Name) {
+        auto SymbolOrErr = getSymbolImplDetail(Program, Name, Kind);
+        if (!SymbolOrErr) {
+          SymbolErr = SymbolOrErr.takeError();
+          return false;
+        }
+        return Callback(*SymbolOrErr, UserData);
+      });
+
+  return joinErrors(std::move(IterateErr), std::move(SymbolErr));
+}
+
 Error olGetSymbolInfoImplDetail(ol_symbol_handle_t Symbol,
                                 ol_symbol_info_t PropName, size_t PropSize,
                                 void *PropValue, size_t *PropSizeRet) {
@@ -1346,6 +1387,8 @@ Error olGetSymbolInfoImplDetail(ol_symbol_handle_t Symbol,
   switch (PropName) {
   case OL_SYMBOL_INFO_KIND:
     return Info.write<ol_symbol_kind_t>(Symbol->Kind);
+  case OL_SYMBOL_INFO_NAME:
+    return Info.writeString(Symbol->Name);
   case OL_SYMBOL_INFO_GLOBAL_VARIABLE_ADDRESS:
     if (auto Err = CheckKind(OL_SYMBOL_KIND_GLOBAL_VARIABLE))
       return Err;
diff --git a/offload/plugins-nextgen/amdgpu/src/rtl.cpp b/offload/plugins-nextgen/amdgpu/src/rtl.cpp
index 65ef83c394f6a..8fcbfbf69447b 100644
--- a/offload/plugins-nextgen/amdgpu/src/rtl.cpp
+++ b/offload/plugins-nextgen/amdgpu/src/rtl.cpp
@@ -586,7 +586,7 @@ struct AMDGPUDeviceImageTy : public DeviceImageTy {
 /// generic kernel class.
 struct AMDGPUKernelTy : public GenericKernelTy {
   /// Create an AMDGPU kernel with a name and an execution mode.
-  AMDGPUKernelTy(const char *Name) : GenericKernelTy(Name) {}
+  AMDGPUKernelTy(StringRef Name) : GenericKernelTy(Name) {}
 
   /// Initialize the AMDGPU kernel.
   Error initImpl(GenericDeviceTy &Device, DeviceImageTy &Image) override {
@@ -2636,7 +2636,7 @@ struct AMDGPUDeviceTy : public GenericDeviceTy, AMDGenericDeviceTy {
   }
 
   /// Allocate and construct an AMDGPU kernel.
-  Expected<GenericKernelTy &> constructKernel(const char *Name) override {
+  Expected<GenericKernelTy &> constructKernel(StringRef Name) override {
     // Allocate and construct the AMDGPU kernel.
     AMDGPUKernelTy *AMDGPUKernel = Plugin.allocate<AMDGPUKernelTy>();
     if (!AMDGPUKernel)
@@ -3920,6 +3920,35 @@ struct AMDGPUGlobalHandlerTy final : public GenericGlobalHandlerTy {
 
     return Plugin::success();
   }
+
+protected:
+  /// Kernels are represented by a kernel descriptor, which is an object named
+  /// after the function it describes with a '.kd' suffix.
+  std::optional<StringRef> matchSymbol(const ELFSymbolRef &Symbol,
+                                       StringRef Name,
+                                       SymbolKindTy Kind) override {
+    if (Symbol.getELFType() != ELF::STT_OBJECT)
+      return std::nullopt;
+
+    StringRef Function = Name;
+    bool IsDescriptor =
+        Function.consume_back(".kd") && isFunction(Symbol, Function);
+    if (IsDescriptor != (Kind == SymbolKindTy::Kernel))
+      return std::nullopt;
+
+    return IsDescriptor ? Function : Name;
+  }
+
+private:
+  /// Returns whether \p Name is a function in the image containing \p Symbol.
+  static bool isFunction(const ELFSymbolRef &Symbol, StringRef Name) {
+    auto SymbolOrErr = utils::elf::getSymbol(*Symbol.getObject(), Name);
+    if (!SymbolOrErr) {
+      consumeError(SymbolOrErr.takeError());
+      return false;
+    }
+    return *SymbolOrErr && (*SymbolOrErr)->getELFType() == ELF::STT_FUNC;
+  }
 };
 
 /// Class implementing the AMDGPU-specific functionalities of the plugin.
diff --git a/offload/plugins-nextgen/common/include/GlobalHandler.h b/offload/plugins-nextgen/common/include/GlobalHandler.h
index 782f7ecba8361..482923dccf242 100644
--- a/offload/plugins-nextgen/common/include/GlobalHandler.h
+++ b/offload/plugins-nextgen/common/include/GlobalHandler.h
@@ -13,9 +13,11 @@
 #ifndef LLVM_OPENMP_LIBOMPTARGET_PLUGINS_NEXTGEN_COMMON_GLOBALHANDLER_H
 #define LLVM_OPENMP_LIBOMPTARGET_PLUGINS_NEXTGEN_COMMON_GLOBALHANDLER_H
 
+#include <optional>
 #include <type_traits>
 
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/Object/ELFObjectFile.h"
 #include "llvm/ProfileData/InstrProf.h"
 #include "llvm/Support/Compiler.h"
@@ -35,6 +37,9 @@ struct GenericDeviceTy;
 
 using namespace llvm::object;
 
+/// The kinds of symbols that can be enumerated in a device image.
+enum class SymbolKindTy { Kernel, GlobalVariable };
+
 /// Common abstraction for globals that live on the host and device.
 /// It simply encapsulates the symbol name, symbol size, and symbol address
 /// (which might be host or device depending on the context).
@@ -48,7 +53,7 @@ class GlobalTy {
   void *Ptr;
 
 public:
-  GlobalTy(const std::string &Name, uint32_t Size = 0, void *Ptr = nullptr)
+  GlobalTy(StringRef Name, uint32_t Size = 0, void *Ptr = nullptr)
       : Name(Name), Size(Size), Ptr(Ptr) {}
 
   const std::string &getName() const { return Name; }
@@ -216,6 +221,17 @@ class GenericGlobalHandlerTy {
   /// with profiling prefixes.
   Expected<GPUProfGlobals> readProfilingGlobals(GenericDeviceTy &Device,
                                                 DeviceImageTy &Image);
+
+  /// Enumerate the names of the symbols of the given \p Kind in \p Image,
+  /// stopping early if \p Callback returns false.
+  virtual Error iterateSymbols(DeviceImageTy &Image, SymbolKindTy Kind,
+                               function_ref<bool(StringRef)> Callback);
+
+protected:
+  /// Returns the name \p Symbol is known by if it identifies a symbol of the
+  /// given \p Kind, otherwise std::nullopt.
+  virtual std::optional<StringRef>
+  matchSymbol(const ELFSymbolRef &Symbol, StringRef Name, SymbolKindTy Kind);
 };
 
 } // namespace plugin
diff --git a/offload/plugins-nextgen/common/include/PluginInterface.h b/offload/plugins-nextgen/common/include/PluginInterface.h
index 67ebfbc943fdc..8753b05973342 100644
--- a/offload/plugins-nextgen/common/include/PluginInterface.h
+++ b/offload/plugins-nextgen/common/include/PluginInterface.h
@@ -429,7 +429,7 @@ class DeviceImageTy {
 /// implement the necessary virtual function members.
 struct GenericKernelTy {
   /// Construct a kernel with a name and a execution mode.
-  GenericKernelTy(const char *Name)
+  GenericKernelTy(StringRef Name)
       : Name(Name), PreferredNumThreads(0), MaxNumThreads(0) {}
 
   virtual ~GenericKernelTy() {}
@@ -1266,7 +1266,7 @@ struct GenericDeviceTy : public DeviceAllocatorTy {
   }
 
   /// Allocate and construct a kernel object.
-  virtual Expected<GenericKernelTy &> constructKernel(const char *Name) = 0;
+  virtual Expected<GenericKernelTy &> constructKernel(StringRef Name) = 0;
 
   /// Reference to the underlying plugin that created this device.
   GenericPluginTy &Plugin;
diff --git a/offload/plugins-nextgen/common/src/GlobalHandler.cpp b/offload/plugins-nextgen/common/src/GlobalHandler.cpp
index 1611a7a6b1ce2..f78b347323c60 100644
--- a/offload/plugins-nextgen/common/src/GlobalHandler.cpp
+++ b/offload/plugins-nextgen/common/src/GlobalHandler.cpp
@@ -193,6 +193,52 @@ Error GenericGlobalHandlerTy::readGlobalFromImage(GenericDeviceTy &Device,
   return Plugin::success();
 }
 
+std::optional<StringRef>
+GenericGlobalHandlerTy::matchSymbol(const ELFSymbolRef &Symbol, StringRef Name,
+                                    SymbolKindTy Kind) {
+  uint8_t Type = Kind == SymbolKindTy::Kernel ? ELF::STT_FUNC : ELF::STT_OBJECT;
+  if (Symbol.getELFType() != Type)
+    return std::nullopt;
+  return Name;
+}
+
+Error GenericGlobalHandlerTy::iterateSymbols(
+    DeviceImageTy &Image, SymbolKindTy Kind,
+    function_ref<bool(StringRef)> Callback) {
+  auto ELFObjOrErr = getELFObjectFile(Image);
+  if (!ELFObjOrErr)
+    return ELFObjOrErr.takeError();
+
+  auto &ELFObj = cast<ELFObjectFileBase>(**ELFObjOrErr);
+  auto Symbols = ELFObj.symbols();
+  if (Symbols.empty())
+    Symbols = ELFObj.getDynamicSymbolIterators();
+
+  for (ELFSymbolRef Symbol : Symbols) {
+    auto FlagsOrErr = Symbol.getFlags();
+    if (!FlagsOrErr)
+      return Plugin::error(ErrorCode::INVALID_BINARY, FlagsOrErr.takeError(),
+                           "error reading ELF symbol");
+
+    // Only symbols that are defined by and exported from the image can be used.
+    uint32_t Ignored = SymbolRef::SF_Undefined | SymbolRef::SF_Hidden |
+                       SymbolRef::SF_FormatSpecific;
+    if (!(*FlagsOrErr & SymbolRef::SF_Global) || (*FlagsOrErr & Ignored))
+      continue;
+
+    auto NameOrErr = Symbol.getName();
+    if (!NameOrErr)
+      return Plugin::error(ErrorCode::INVALID_BINARY, NameOrErr.takeError(),
+                           "error reading ELF symbol name");
+
+    if (auto Name = matchSymbol(Symbol, *NameOrErr, Kind))
+      if (!Callback(*Name))
+        break;
+  }
+
+  return Plugin::success();
+}
+
 Expected<GPUProfGlobals>
 GenericGlobalHandlerTy::readProfilingGlobals(GenericDeviceTy &Device,
                                              DeviceImageTy &Image) {
diff --git a/offload/plugins-nextgen/cuda/src/rtl.cpp b/offload/plugins-nextgen/cuda/src/rtl.cpp
index 9bf835b63813c..4c8af289d8b6f 100644
--- a/offload/plugins-nextgen/cuda/src/rtl.cpp
+++ b/offload/plugins-nextgen/cuda/src/rtl.cpp
@@ -96,7 +96,7 @@ struct CUDADeviceImageTy : public DeviceImageTy {
 /// generic kernel class.
 struct CUDAKernelTy : public GenericKernelTy {
   /// Create a CUDA kernel with a name and an execution mode.
-  CUDAKernelTy(const char *Name) : GenericKernelTy(Name), Func(nullptr) {}
+  CUDAKernelTy(StringRef Name) : GenericKernelTy(Name), Func(nullptr) {}
 
   /// Initialize the CUDA kernel.
   Error initImpl(GenericDeviceTy &GenericDevice,
@@ -512,7 +512,7 @@ struct CUDADeviceTy : public GenericDeviceTy {
   }
 
   /// Allocate and construct a CUDA kernel.
-  Expected<GenericKernelTy &> constructKernel(const char *Name) override {
+  Expected<GenericKernelTy &> constructKernel(StringRef Name) override {
     // Allocate and construct the CUDA kernel.
     CUDAKernelTy *CUDAKernel = Plugin.allocate<CUDAKernelTy>();
     if (!CUDAKernel)
diff --git a/offload/plugins-nextgen/host/src/rtl.cpp b/offload/plugins-nextgen/host/src/rtl.cpp
index 95ec3820f2657..b08595c258a19 100644
--- a/offload/plugins-nextgen/host/src/rtl.cpp
+++ b/offload/plugins-nextgen/host/src/rtl.cpp
@@ -56,7 +56,7 @@ using namespace error;
 /// Class implementing kernel functionalities for GenELF64.
 struct GenELF64KernelTy : public GenericKernelTy {
   /// Construct the kernel with a name and an execution mode.
-  GenELF64KernelTy(const char *Name) : GenericKernelTy(Name), Func(nullptr) {}
+  GenELF64KernelTy(StringRef Name) : GenericKernelTy(Name), Func(nullptr) {}
 
   /// Initialize the kernel.
   Error initImpl(GenericDeviceTy &Device, DeviceImageTy &Image) override {
@@ -165,7 +165,7 @@ struct GenELF64DeviceTy : public GenericDeviceTy {
   std::string getComputeUnitKind() const override { return "generic-64bit"; }
 
   /// Construct the kernel for a specific image on the device.
-  Expected<GenericKernelTy &> constructKernel(const char *Name) override {
+  Expected<GenericKernelTy &> constructKernel(StringRef Name) override {
     // Allocate and construct the kernel.
     GenELF64KernelTy *GenELF64Kernel = Plugin.allocate<GenELF64KernelTy>();
     if (!GenELF64Kernel)
diff --git a/offload/plugins-nextgen/level_zero/include/L0Device.h b/offload/plugins-nextgen/level_zero/include/L0Device.h
index 3e8129d057095..88332f3818dfe 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Device.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Device.h
@@ -576,7 +576,7 @@ class L0DeviceTy final : public GenericDeviceTy {
     V = 0;
     return Plugin::success();
   }
-  Expected<GenericKernelTy &> constructKernel(const char *Name) override;
+  Expected<GenericKernelTy &> constructKernel(StringRef Name) override;
 
   Error callGlobalConstructors(GenericPluginTy &Plugin,
                                DeviceImageTy &Image) override;
diff --git a/offload/plugins-nextgen/level_zero/include/L0Kernel.h b/offload/plugins-nextgen/level_zero/include/L0Kernel.h
index 5630dfe4ba585..bbb1ce1046949 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Kernel.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Kernel.h
@@ -67,7 +67,7 @@ class L0KernelTy : public GenericKernelTy {
 
 public:
   /// Create a L0 kernel with a name and an execution mode.
-  L0KernelTy(const char *Name) : GenericKernelTy(Name), zeKernel(nullptr) {}
+  L0KernelTy(StringRef Name) : GenericKernelTy(Name), zeKernel(nullptr) {}
   ~L0KernelTy() = default;
   L0KernelTy(const L0KernelTy &) = delete;
   L0KernelTy(L0KernelTy &&) = delete;
diff --git a/offload/plugins-nextgen/level_zero/include/L0Program.h b/offload/plugins-nextgen/level_zero/include/L0Program.h
index af2d0a05f7e3b..3a78ddeeb81de 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Program.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Program.h
@@ -110,6 +110,11 @@ class L0ProgramTy : public DeviceImageTy {
   Error getSymbolMetadata(const char *Name, void **AddrPtr,
                           size_t *SizePtr) const;
 
+  /// Returns the names of every kernel in this program.
+  auto getKernelNames() const {
+    return llvm::make_first_range(KernelsToModuleMap);
+  }
+
   /// Returns the handle of a module that contains a given Kernel name.
   ze_module_handle_t findModuleFromKernelName(const char *KernelName) const {
     auto K = KernelsToModuleMap.find(std::string(KernelName));
@@ -126,6 +131,9 @@ struct L0GlobalHandlerTy final : public GenericGlobalHandlerTy {
   Error getGlobalMetadataFromDevice(GenericDeviceTy &Device,
                                     DeviceImageTy &Image,
                                     GlobalTy &DeviceGlobal) override;
+
+  Error iterateSymbols(DeviceImageTy &Image, SymbolKindTy Kind,
+                       function_ref<bool(StringRef)> Callback) override;
 };
 
 bool isValidOneOmpImage(StringRef Image, uint64_t &MajorVer,
diff --git a/offload/plugins-nextgen/level_zero/src/L0Device.cpp b/offload/plugins-nextgen/level_zero/src/L0Device.cpp
index 076dfa080f86e..9b39f49b0db58 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Device.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Device.cpp
@@ -554,7 +554,7 @@ Expected<InfoTreeNode> L0DeviceTy::obtainInfoImpl() {
   return Info;
 }
 
-Expected<GenericKernelTy &> L0DeviceTy::constructKernel(const char *Name) {
+Expected<GenericKernelTy &> L0DeviceTy::constructKernel(StringRef Name) {
   // Allocate and construct the L0 kernel.
   L0KernelTy *L0Kernel = getPlugin().allocate<L0KernelTy>();
   if (!L0Kernel)
diff --git a/offload/plugins-nextgen/level_zero/src/L0Program.cpp b/offload/plugins-nextgen/level_zero/src/L0Program.cpp
index 08c784a758367..7b821eeea14ee 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Program.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Program.cpp
@@ -43,6 +43,21 @@ Error L0GlobalHandlerTy::getGlobalMetadataFromDevice(GenericDeviceTy &Device,
   return Plugin::success();
 }
 
+Error L0GlobalHandlerTy::iterateSymbols(
+    DeviceImageTy &Image, SymbolKindTy Kind,
+    function_ref<bool(StringRef)> Callback) {
+  // The images are SPIR-V, so only the kernels the module reports are known.
+  if (Kind != SymbolKindTy::Kernel)
+    return Plugin::error(ErrorCode::UNSUPPORTED,
+                         "cannot enumerate global variables in a module");
+
+  for (StringRef Name : L0ProgramTy::makeL0Program(Image).getKernelNames())
+    if (!Callback(Name))
+      break;
+
+  return Plugin::success();
+}
+
 inline L0DeviceTy &L0ProgramTy::getL0Device() const {
   return L0DeviceTy::makeL0Device(getDevice());
 }
diff --git a/offload/unittests/OffloadAPI/CMakeLists.txt b/offload/unittests/OffloadAPI/CMakeLists.txt
index c967df1ae6518..c0dbc31a3a1ca 100644
--- a/offload/unittests/OffloadAPI/CMakeLists.txt
+++ b/offload/unittests/OffloadAPI/CMakeLists.txt
@@ -65,4 +65,5 @@ add_offload_unittest("queue"
 add_offload_unittest("symbol"
     symbol/olGetSymbol.cpp
     symbol/olGetSymbolInfo.cpp
-    symbol/olGetSymbolInfoSize.cpp)
+    symbol/olGetSymbolInfoSize.cpp
+    symbol/olIterateSymbols.cpp)
diff --git a/offload/unittests/OffloadAPI/symbol/olGetSymbolInfo.cpp b/offload/unittests/OffloadAPI/symbol/olGetSymbolInfo.cpp
index ed8f4716974cd..55b0eed095790 100644
--- a/offload/unittests/OffloadAPI/symbol/olGetSymbolInfo.cpp
+++ b/offload/unittests/OffloadAPI/symbol/olGetSymbolInfo.cpp
@@ -30,6 +30,24 @@ TEST_P(olGetSymbolInfoGlobalTest, SuccessKind) {
   ASSERT_EQ(RetrievedKind, OL_SYMBOL_KIND_GLOBAL_VARIABLE);
 }
 
+TEST_P(olGetSymbolInfoKernelTest, SuccessName) {
+  size_t Size = 0;
+  ASSERT_SUCCESS(olGetSymbolInfoSize(Kernel, OL_SYMBOL_INFO_NAME, &Size));
+  std::vector<char> Name(Size);
+  ASSERT_SUCCESS(
+      olGetSymbolInfo(Kernel, OL_SYMBOL_INFO_NAME, Size, Name.data()));
+  ASSERT_STREQ(Name.data(), "foo");
+}
+
+TEST_P(olGetSymbolInfoGlobalTest, SuccessName) {
+  size_t Size = 0;
+  ASSERT_SUCCESS(olGetSymbolInfoSize(Global, OL_SYMBOL_INFO_NAME, &Size));
+  std::vector<char> Name(Size);
+  ASSERT_SUCCESS(
+      olGetSymbolInfo(Global, OL_SYMBOL_INFO_NAME, Size, Name.data()));
+  ASSERT_STREQ(Name.data(), "global");
+}
+
 TEST_P(olGetSymbolInfoKernelTest, InvalidAddress) {
   void *RetrievedAddr;
   ASSERT_ERROR(OL_ERRC_SYMBOL_KIND,
diff --git a/offload/unittests/OffloadAPI/symbol/olIterateSymbols.cpp b/offload/unittests/OffloadAPI/symbol/olIterateSymbols.cpp
new file mode 100644
index 0000000000000..aba850d5bc684
--- /dev/null
+++ b/offload/unittests/OffloadAPI/symbol/olIterateSymbols.cpp
@@ -0,0 +1,118 @@
+//===------- Offload API tests - olIterateSymbols -------------------------===//
+//
+// 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 "../common/Fixtures.hpp"
+#include <OffloadAPI.h>
+#include <gtest/gtest.h>
+
+// The 'global' program contains the 'read' and 'write' kernels as well as the
+// 'global' global variable.
+struct olIterateSymbolsTest : OffloadProgramTest {
+  void SetUp() override { SetUpWith("global"); }
+};
+OFFLOAD_TESTS_INSTANTIATE_DEVICE_FIXTURE(olIterateSymbolsTest);
+
+using SymbolVecT = std::vector<ol_symbol_handle_t>;
+
+static bool collectSymbol(ol_symbol_handle_t Symbol, void *Data) {
+  static_cast<SymbolVecT *>(Data)->push_back(Symbol);
+  return true;
+}
+
+static std::string getName(ol_symbol_handle_t Symbol) {
+  size_t Size = 0;
+  if (olGetSymbolInfoSize(Symbol, OL_SYMBOL_INFO_NAME, &Size))
+    return {};
+  std::vector<char> Name(Size);
+  if (olGetSymbolInfo(Symbol, OL_SYMBOL_INFO_NAME, Size, Name.data()))
+    return {};
+  return std::string{Name.data()};
+}
+
+static std::vector<std::string> getNames(const SymbolVecT &Symbols,
+                                         ol_symbol_kind_t Expected) {
+  std::vector<std::string> Names;
+  for (auto *Symbol : Symbols) {
+    ol_symbol_kind_t Kind;
+    if (olGetSymbolInfo(Symbol, OL_SYMBOL_INFO_KIND, sizeof(Kind), &Kind))
+      return {};
+    EXPECT_EQ(Kind, Expected);
+    Names.push_back(getName(Symbol));
+  }
+  return Names;
+}
+
+static bool contains(const std::vector<std::string> &Names,
+                     const std::string &Name) {
+  return std::find(Names.begin(), Names.end(), Name) != Names.end();
+}
+
+TEST_P(olIterateSymbolsTest, SuccessKernels) {
+  SymbolVecT Symbols;
+  ASSERT_SUCCESS(olIterateSymbols(Program, OL_SYMBOL_KIND_KERNEL, collectSymbol,
+                                  &Symbols));
+
+  auto Names = getNames(Symbols, OL_SYMBOL_KIND_KERNEL);
+  ASSERT_TRUE(contains(Names, "read"));
+  ASSERT_TRUE(contains(Names, "write"));
+}
+
+TEST_P(olIterateSymbolsTest, SuccessGlobals) {
+  SymbolVecT Symbols;
+  ASSERT_SUCCESS_OR_UNSUPPORTED(olIterateSymbols(
+      Program, OL_SYMBOL_KIND_GLOBAL_VARIABLE, collectSymbol, &Symbols));
+
+  auto Names = getNames(Symbols, OL_SYMBOL_KIND_GLOBAL_VARIABLE);
+  ASSERT_TRUE(contains(Names, "global"));
+}
+
+TEST_P(olIterateSymbolsTest, SuccessSameSymbol) {
+  SymbolVecT Symbols;
+  ASSERT_SUCCESS(olIterateSymbols(Program, OL_SYMBOL_KIND_KERNEL, collectSymbol,
+                                  &Symbols));
+  ASSERT_FALSE(Symbols.empty());
+
+  for (auto *Symbol : Symbols) {
+    ol_symbol_handle_t Looked = nullptr;
+    ASSERT_SUCCESS(olGetSymbol(Program, getName(Symbol).c_str(),
+                               OL_SYMBOL_KIND_KERNEL, &Looked));
+    ASSERT_EQ(Symbol, Looked);
+  }
+}
+
+TEST_P(olIterateSymbolsTest, SuccessStopIteration) {
+  size_t Count = 0;
+  ASSERT_SUCCESS(olIterateSymbols(
+      Program, OL_SYMBOL_KIND_KERNEL,
+      [](ol_symbol_handle_t, void *Data) {
+        (*static_cast<size_t *>(Data))++;
+        return false;
+      },
+      &Count));
+  ASSERT_EQ(Count, 1u);
+}
+
+TEST_P(olIterateSymbolsTest, InvalidNullProgram) {
+  SymbolVecT Symbols;
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE,
+               olIterateSymbols(nullptr, OL_SYMBOL_KIND_KERNEL, collectSymbol,
+                                &Symbols));
+}
+
+TEST_P(olIterateSymbolsTest, InvalidNullCallback) {
+  ASSERT_ERROR(
+      OL_ERRC_INVALID_NULL_POINTER,
+      olIterateSymbols(Program, OL_SYMBOL_KIND_KERNEL, nullptr, nullptr));
+}
+
+TEST_P(olIterateSymbolsTest, InvalidKind) {
+  SymbolVecT Symbols;
+  ASSERT_ERROR(OL_ERRC_INVALID_ENUMERATION,
+               olIterateSymbols(Program, OL_SYMBOL_KIND_FORCE_UINT32,
+                                collectSymbol, &Symbols));
+}

>From 78724f9429f191d6a68807c48ad8127a9cd254d1 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Thu, 30 Jul 2026 10:47:26 -0500
Subject: [PATCH 2/2] Remove special L0 handling

---
 .../level_zero/include/L0Program.h                |  8 --------
 .../plugins-nextgen/level_zero/src/L0Program.cpp  | 15 ---------------
 2 files changed, 23 deletions(-)

diff --git a/offload/plugins-nextgen/level_zero/include/L0Program.h b/offload/plugins-nextgen/level_zero/include/L0Program.h
index 3a78ddeeb81de..af2d0a05f7e3b 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Program.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Program.h
@@ -110,11 +110,6 @@ class L0ProgramTy : public DeviceImageTy {
   Error getSymbolMetadata(const char *Name, void **AddrPtr,
                           size_t *SizePtr) const;
 
-  /// Returns the names of every kernel in this program.
-  auto getKernelNames() const {
-    return llvm::make_first_range(KernelsToModuleMap);
-  }
-
   /// Returns the handle of a module that contains a given Kernel name.
   ze_module_handle_t findModuleFromKernelName(const char *KernelName) const {
     auto K = KernelsToModuleMap.find(std::string(KernelName));
@@ -131,9 +126,6 @@ struct L0GlobalHandlerTy final : public GenericGlobalHandlerTy {
   Error getGlobalMetadataFromDevice(GenericDeviceTy &Device,
                                     DeviceImageTy &Image,
                                     GlobalTy &DeviceGlobal) override;
-
-  Error iterateSymbols(DeviceImageTy &Image, SymbolKindTy Kind,
-                       function_ref<bool(StringRef)> Callback) override;
 };
 
 bool isValidOneOmpImage(StringRef Image, uint64_t &MajorVer,
diff --git a/offload/plugins-nextgen/level_zero/src/L0Program.cpp b/offload/plugins-nextgen/level_zero/src/L0Program.cpp
index 7b821eeea14ee..08c784a758367 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Program.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Program.cpp
@@ -43,21 +43,6 @@ Error L0GlobalHandlerTy::getGlobalMetadataFromDevice(GenericDeviceTy &Device,
   return Plugin::success();
 }
 
-Error L0GlobalHandlerTy::iterateSymbols(
-    DeviceImageTy &Image, SymbolKindTy Kind,
-    function_ref<bool(StringRef)> Callback) {
-  // The images are SPIR-V, so only the kernels the module reports are known.
-  if (Kind != SymbolKindTy::Kernel)
-    return Plugin::error(ErrorCode::UNSUPPORTED,
-                         "cannot enumerate global variables in a module");
-
-  for (StringRef Name : L0ProgramTy::makeL0Program(Image).getKernelNames())
-    if (!Callback(Name))
-      break;
-
-  return Plugin::success();
-}
-
 inline L0DeviceTy &L0ProgramTy::getL0Device() const {
   return L0DeviceTy::makeL0Device(getDevice());
 }



More information about the llvm-commits mailing list