[llvm] [Offload] Add GenericProfilerTy abstraction and APITypes extensions (PR #225639)

Jan Patrick Lehr via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 29 01:56:46 PDT 2026


https://github.com/jplehr updated https://github.com/llvm/llvm-project/pull/225639

>From 802d3a9e7cd10322b14f9804a1e77b8ea9964b27 Mon Sep 17 00:00:00 2001
From: JP Lehr <JanPatrick.Lehr at amd.com>
Date: Thu, 2 Apr 2026 07:27:58 -0500
Subject: [PATCH 1/2] [Offload] Add GenericProfilerTy abstraction and APITypes
 extensions

Introduce GenericProfilerTy alongside the existing OMPT callback dispatch.
The weak profiler factory returns a no-op implementation, so the new hooks
are silent while the established callback path continues to handle OMPT
device events.

Co-Authored-By: Dhruva Chakrabarti <dhruva.chakrabarti at amd.com>
Co-Authored-By: Michael Halkenhauser <michaelgerald.halkenhauser at amd.com>
Assisted-by: Claude Code
---
 offload/include/Shared/APITypes.h             |   5 +
 offload/plugins-nextgen/amdgpu/src/rtl.cpp    |  27 +--
 offload/plugins-nextgen/common/CMakeLists.txt |   1 +
 .../common/include/GenericProfiler.h          | 197 ++++++++++++++++++
 .../common/include/PluginInterface.h          |  69 +++---
 .../common/src/GenericProfiler.cpp            |  36 ++++
 .../common/src/PluginInterface.cpp            | 119 +++++++----
 offload/plugins-nextgen/cuda/src/rtl.cpp      |   3 +-
 offload/plugins-nextgen/host/src/rtl.cpp      |  19 +-
 .../level_zero/include/L0Device.h             |   3 +-
 .../level_zero/include/L0Plugin.h             |   7 +-
 .../level_zero/src/L0Device.cpp               |   3 +-
 .../level_zero/src/L0Plugin.cpp               |  13 +-
 13 files changed, 408 insertions(+), 94 deletions(-)
 create mode 100644 offload/plugins-nextgen/common/include/GenericProfiler.h
 create mode 100644 offload/plugins-nextgen/common/src/GenericProfiler.cpp

diff --git a/offload/include/Shared/APITypes.h b/offload/include/Shared/APITypes.h
index 7a151ab4ee612..7ded974294239 100644
--- a/offload/include/Shared/APITypes.h
+++ b/offload/include/Shared/APITypes.h
@@ -88,6 +88,11 @@ struct __tgt_async_info {
   /// ensure it is a valid location while the transfer to the device is
   /// happening.
   KernelLaunchEnvironmentTy KernelLaunchEnvironment;
+
+  /// Opaque handle for profiler-specific data (e.g., OMPT trace record info).
+  /// Owned by the profiler; the runtime threads this pointer through the plugin
+  /// layer to associate async operations with trace records.
+  void *ProfilerData = nullptr;
 };
 
 /// This struct contains all of the arguments to a target kernel region launch.
diff --git a/offload/plugins-nextgen/amdgpu/src/rtl.cpp b/offload/plugins-nextgen/amdgpu/src/rtl.cpp
index de38c2a68e4ab..98ee066bf4b69 100644
--- a/offload/plugins-nextgen/amdgpu/src/rtl.cpp
+++ b/offload/plugins-nextgen/amdgpu/src/rtl.cpp
@@ -2281,7 +2281,8 @@ struct AMDGPUDeviceTy : public GenericDeviceTy, AMDGenericDeviceTy {
   ~AMDGPUDeviceTy() {}
 
   /// Initialize the device, its resources and get its properties.
-  Error initImpl(GenericPluginTy &Plugin) override {
+  Error initImpl(GenericPluginTy &Plugin,
+                 GenericProfilerTy *ProfilerPtr) override {
     // First setup all the memory pools.
     if (auto Err = initMemoryPools())
       return Err;
@@ -3987,10 +3988,10 @@ struct AMDGPUPluginContextTy final : public PluginContextTy {
   }
 
   Expected<void *> allocate(GenericDeviceTy &Device, int64_t Size,
-                            void *HostPtr, TargetAllocTy Kind,
-                            size_t Alignment) override;
-  Error deallocate(GenericDeviceTy &Device, void *Ptr,
-                   TargetAllocTy Kind) override;
+                            void *HostPtr, TargetAllocTy Kind, size_t Alignment,
+                            GenericProfilerTy *ProfilerPtr) override;
+  Error deallocate(GenericDeviceTy &Device, void *Ptr, TargetAllocTy Kind,
+                   GenericProfilerTy *ProfilerPtr) override;
   Expected<PluginAllocInfoTy> getAllocInfo(const void *Ptr) override;
 
 private:
@@ -4312,12 +4313,11 @@ struct AMDGPUPluginTy final : public GenericPluginTy {
   AMDHostDeviceTy *HostDevice;
 };
 
-Expected<void *> AMDGPUPluginContextTy::allocate(GenericDeviceTy &Device,
-                                                 int64_t Size, void *HostPtr,
-                                                 TargetAllocTy Kind,
-                                                 size_t Alignment) {
-  auto PtrOrErr =
-      PluginContextTy::allocate(Device, Size, HostPtr, Kind, Alignment);
+Expected<void *> AMDGPUPluginContextTy::allocate(
+    GenericDeviceTy &Device, int64_t Size, void *HostPtr, TargetAllocTy Kind,
+    size_t Alignment, GenericProfilerTy *ProfilerPtr) {
+  auto PtrOrErr = PluginContextTy::allocate(Device, Size, HostPtr, Kind,
+                                            Alignment, ProfilerPtr);
   if (!PtrOrErr || !*PtrOrErr)
     return PtrOrErr;
   std::lock_guard<std::mutex> Lock(AllocationsMutex);
@@ -4326,7 +4326,8 @@ Expected<void *> AMDGPUPluginContextTy::allocate(GenericDeviceTy &Device,
 }
 
 Error AMDGPUPluginContextTy::deallocate(GenericDeviceTy &Device, void *Ptr,
-                                        TargetAllocTy Kind) {
+                                        TargetAllocTy Kind,
+                                        GenericProfilerTy *ProfilerPtr) {
   // Erase before base deallocate: once Ptr returns to the MM freelist a
   // concurrent alloc could reuse it and re-populate Allocations. On failure
   // Ptr is in an undetermined state (maybe freed, maybe not) so we don't
@@ -4335,7 +4336,7 @@ Error AMDGPUPluginContextTy::deallocate(GenericDeviceTy &Device, void *Ptr,
     std::lock_guard<std::mutex> Lock(AllocationsMutex);
     Allocations.erase(Ptr);
   }
-  return PluginContextTy::deallocate(Device, Ptr, Kind);
+  return PluginContextTy::deallocate(Device, Ptr, Kind, ProfilerPtr);
 }
 
 Expected<PluginAllocInfoTy>
diff --git a/offload/plugins-nextgen/common/CMakeLists.txt b/offload/plugins-nextgen/common/CMakeLists.txt
index 6ad20796c3ca4..19ce102309a72 100644
--- a/offload/plugins-nextgen/common/CMakeLists.txt
+++ b/offload/plugins-nextgen/common/CMakeLists.txt
@@ -11,6 +11,7 @@ add_public_tablegen_target(PluginErrcodes)
 # don't want to export `PluginInterface` while `add_llvm_library` requires that.
 add_library(PluginCommon OBJECT
   src/PluginInterface.cpp
+  src/GenericProfiler.cpp
   src/GlobalHandler.cpp
   src/JIT.cpp
   src/RecordReplay.cpp
diff --git a/offload/plugins-nextgen/common/include/GenericProfiler.h b/offload/plugins-nextgen/common/include/GenericProfiler.h
new file mode 100644
index 0000000000000..57bb0847f32d9
--- /dev/null
+++ b/offload/plugins-nextgen/common/include/GenericProfiler.h
@@ -0,0 +1,197 @@
+//===- GenericProfiler.h - GenericProfiler interface for use in Plugins ---===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// The GenericProfiler interface allows to implement profiler logic for various
+// backends, such as OMPT or other tracing mechanisms.
+// This enables the plugins to be agnostic of the actual high-level language
+// that is implemented.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef OFFLOAD_PLUGINS_NEXTGEN_COMMON_INCLUDE_GENERICPROFILER_H
+#define OFFLOAD_PLUGINS_NEXTGEN_COMMON_INCLUDE_GENERICPROFILER_H
+
+#include "Shared/APITypes.h"
+
+#include <cstdint>
+#include <functional>
+#include <tuple>
+
+class OmptTracingBufferMgr;
+
+namespace llvm {
+namespace omp {
+namespace target {
+namespace plugin {
+
+struct GenericDeviceTy;
+struct GenericPluginTy;
+class GenericProfilerTy;
+
+template <typename FunT, typename... ArgsT, size_t... IdxSequence>
+void callViaIndexSeq(FunT F, GenericProfilerTy *P, uint64_t StartNanos,
+                     uint64_t EndNanos, std::tuple<ArgsT...> Args,
+                     std::index_sequence<IdxSequence...>) {
+  F(P, StartNanos, EndNanos, std::get<IdxSequence>(Args)...);
+}
+
+template <typename FunT, typename... ArgsT>
+void callViaUnpack(FunT F, GenericProfilerTy *P, uint64_t StartNanos,
+                   uint64_t EndNanos, std::tuple<ArgsT...> Tup) {
+  callViaIndexSeq(F, P, StartNanos, EndNanos, Tup,
+                  std::index_sequence_for<ArgsT...>{});
+}
+
+/// Abstraction layer to implement different profiler backends.
+///
+/// The plugins call into the GenericProfilerTy to handle the specific events
+/// with whatever specific backend was instantiated. For now, the supported
+/// backends are limited to an OMPT implementation.
+class GenericProfilerTy {
+public:
+  GenericProfilerTy() = default;
+  virtual ~GenericProfilerTy() = default;
+
+  /// Obtain a pointer to profiler-specific data, if any.
+  virtual void *getProfilerSpecificData() { return nullptr; }
+
+  /// Obtain the trace record manager owned by this profiler, if any. Profilers
+  /// that produce trace records own the manager so that the records handed out
+  /// cannot outlive the buffers they point into.
+  virtual OmptTracingBufferMgr *getTraceRecordManager() { return nullptr; }
+
+  virtual bool isProfilingEnabled() { return false; }
+
+  /// Set the factors which are used to interpolate the device clock compared to
+  /// the host clock. This follows a simple linear interpolation: Slope * <time>
+  /// + Offset.
+  void setTimeConversionFactors(double Slope, double Offset) {
+    HostToDeviceSlope = Slope;
+    HostToDeviceOffset = Offset;
+    setTimeConversionFactorsImpl(HostToDeviceSlope, HostToDeviceOffset);
+  }
+
+  /// Hook that is called when the plugin is initialized.
+  virtual void handleInit(GenericDeviceTy *Device, GenericPluginTy *Plugin) {}
+
+  /// Hook that is called when the plugin is de-initialized.
+  virtual void handleDeinit(GenericDeviceTy *Device, GenericPluginTy *Plugin) {}
+
+  /// Hook that is called when the device image is loaded.
+  virtual void handleLoadBinary(GenericDeviceTy *Device,
+                                GenericPluginTy *Plugin,
+                                const StringRef InputTgtImage) {}
+
+  /// Hook that is called when memory is allocated on the device.
+  virtual void handleDataAlloc(uint64_t StartNanos, uint64_t EndNanos,
+                               void *HostPtr, uint64_t Size, void *Data) {}
+
+  /// Hook that is called when memory is freed on the device.
+  virtual void handleDataDelete(uint64_t StartNanos, uint64_t EndNanos,
+                                void *TgtPtr, void *Data) {}
+
+  /// Hook that is called before launching a kernel.
+  virtual void handlePreKernelLaunch(GenericDeviceTy *Device,
+                                     uint32_t NumBlocks[3],
+                                     __tgt_async_info *AI) {}
+
+  /// Hook that is called when the kernel is finished to extract the specific
+  /// timing info for that kernel execution.
+  virtual void handleKernelCompletion(uint64_t StartNanos, uint64_t EndNanos,
+                                      void *Data) {}
+
+  /// Hook that is called when a data transfer happens to extract timing info
+  /// for that transfer.
+  virtual void handleDataTransfer(uint64_t StartNanos, uint64_t EndNanos,
+                                  void *Data) {}
+
+  /// Allow factors for time conversion between host and device.
+  virtual void setTimeConversionFactorsImpl(double Slope, double Offset) {}
+
+  /// RAII style timer that measures the elapsed time between construction and
+  /// destruction, then invokes a callback with the profiler, start/end times,
+  /// and any captured arguments.
+  template <typename FnT, typename... ArgsT> class ProfTimerTy {
+  public:
+    ProfTimerTy(FnT &&F, GenericProfilerTy *P, GenericDeviceTy *D, ArgsT... As)
+        : Fun(F), Prof(P), Dev(D), Args(As...) {
+      assert(Prof && "GenericProfilerTy is null");
+      assert(Dev && "GenericDeviceTy is null");
+      if (Prof)
+        StartTime = Prof->getDeviceTimeStamp(Dev);
+    }
+
+    ~ProfTimerTy() {
+      assert(Prof && "GenericProfilerTy is null");
+      assert(Dev && "GenericDeviceTy is null");
+      if (Prof) {
+        uint64_t EndTime = Prof->getDeviceTimeStamp(Dev);
+        callViaUnpack(Fun, Prof, StartTime, EndTime, Args);
+      }
+    }
+
+  private:
+    FnT Fun;
+    GenericProfilerTy *Prof;
+    GenericDeviceTy *Dev;
+    uint64_t StartTime = 0;
+    std::tuple<ArgsT...> Args;
+  };
+
+  template <typename FnT, typename... ArgsT>
+  [[maybe_unused]]
+  ProfTimerTy(FnT &&, GenericProfilerTy *, ArgsT...)
+      -> ProfTimerTy<FnT, ArgsT...>;
+
+  template <typename FnT, typename... ArgsT> friend class ProfTimerTy;
+
+  /// Returns an RAII style timer, which will handle data allocation timing.
+  [[nodiscard]] auto getScopedDataAllocTimer(GenericDeviceTy *Dev,
+                                             void *HostPtr, uint64_t Size,
+                                             void *ProfData = nullptr) {
+    return ProfTimerTy(
+        [](GenericProfilerTy *P, auto... args) {
+          assert(P && "P was null");
+          P->handleDataAlloc(args...);
+        },
+        this, Dev, HostPtr, Size, ProfData);
+  }
+
+  /// Returns an RAII style timer, which will handle data deletion timing.
+  [[nodiscard]] auto getScopedDataDeleteTimer(GenericDeviceTy *Dev,
+                                              void *TgtPtr,
+                                              void *ProfData = nullptr) {
+    return ProfTimerTy(
+        [](GenericProfilerTy *P, auto... args) {
+          assert(P && "P was null");
+          P->handleDataDelete(args...);
+        },
+        this, Dev, TgtPtr, ProfData);
+  }
+
+protected:
+  /// Factors to convert host time to device time. The default values are
+  /// Computed in a way Slope * Time + Offset (inspired by Score-P).
+  double HostToDeviceSlope = 1.0;
+  double HostToDeviceOffset = .0;
+
+private:
+  /// Vendor-specific implementation to obtain device time.
+  uint64_t getDeviceTimeStamp(GenericDeviceTy *D);
+};
+
+/// Null-object used to normalize an absent profiler. Callers that do not have a
+/// profiler pass none, and the receiving method resolves to this instance, so
+/// profiling logic never has to test for a null pointer.
+GenericProfilerTy &getNoOpProfiler();
+} // namespace plugin
+} // namespace target
+} // namespace omp
+} // namespace llvm
+
+#endif // OFFLOAD_PLUGINS_NEXTGEN_COMMON_INCLUDE_GENERICPROFILER_H
diff --git a/offload/plugins-nextgen/common/include/PluginInterface.h b/offload/plugins-nextgen/common/include/PluginInterface.h
index ca05ff32f06e8..b0a2239b1825b 100644
--- a/offload/plugins-nextgen/common/include/PluginInterface.h
+++ b/offload/plugins-nextgen/common/include/PluginInterface.h
@@ -29,6 +29,7 @@
 #include "Shared/Requirements.h"
 #include "Shared/Utils.h"
 
+#include "GenericProfiler.h"
 #include "GlobalHandler.h"
 #include "JIT.h"
 #include "MemoryManager.h"
@@ -489,7 +490,8 @@ struct GenericKernelTy {
   /// of it reserved for the kernel launch environment (dyn_ptr); the caller
   /// owns the storage it points into.
   Error launch(GenericDeviceTy &GenericDevice, KernelLaunchArgsTy &LaunchArgs,
-               AsyncInfoWrapperTy &AsyncInfoWrapper) const;
+               AsyncInfoWrapperTy &AsyncInfoWrapper,
+               GenericProfilerTy *ProfilerPtr = nullptr) const;
   virtual Error launchImpl(GenericDeviceTy &GenericDevice,
                            uint32_t NumThreads[3], uint32_t NumBlocks[3],
                            uint32_t DynBlockMemSize,
@@ -933,19 +935,22 @@ struct PluginContextTy {
   /// Allocate Size bytes of Kind memory accessible from Device. HostPtr is an
   /// optional hint (e.g. for pinned-buffer registration); pass nullptr when
   /// unused.
-  virtual llvm::Expected<void *> allocate(GenericDeviceTy &Device, int64_t Size,
-                                          void *HostPtr, TargetAllocTy Kind,
-                                          size_t Alignment);
+  virtual llvm::Expected<void *>
+  allocate(GenericDeviceTy &Device, int64_t Size, void *HostPtr,
+           TargetAllocTy Kind, size_t Alignment,
+           GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Free a pointer returned by allocate; resolves owner/kind via
   /// getAllocInfo. Requires a non-empty device set, so this is only valid on
   /// user-created contexts (not on the per-plugin default context, which
   /// carries no devices).
-  virtual llvm::Error deallocate(void *Ptr);
+  virtual llvm::Error deallocate(void *Ptr,
+                                 GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Free a pointer when the caller already knows the owning device and kind.
   virtual llvm::Error deallocate(GenericDeviceTy &Device, void *Ptr,
-                                 TargetAllocTy Kind);
+                                 TargetAllocTy Kind,
+                                 GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Look up the allocation containing Ptr. Returns NOT_FOUND when Ptr is not
   /// known to this context. Only valid on user-created contexts.
@@ -1038,20 +1043,23 @@ struct GenericDeviceTy : public DeviceAllocatorTy {
 
   /// Initialize the device. After this call, the device should be already
   /// working and ready to accept queries or modifications.
-  Error init(GenericPluginTy &Plugin);
-  virtual Error initImpl(GenericPluginTy &Plugin) = 0;
+  Error init(GenericPluginTy &Plugin, GenericProfilerTy *ProfilerPtr = nullptr);
+  virtual Error initImpl(GenericPluginTy &Plugin,
+                         GenericProfilerTy *ProfilerPtr) = 0;
 
   /// Deinitialize the device and free all its resources. After this call, the
   /// device is no longer considered ready, so no queries or modifications are
   /// allowed.
-  Error deinit(GenericPluginTy &Plugin);
+  Error deinit(GenericPluginTy &Plugin,
+               GenericProfilerTy *ProfilerPtr = nullptr);
   virtual Error deinitImpl() = 0;
 
   /// Load the binary image into the device and return the target table. When
   /// \p Context is null the plugin's driver-scoped default context is used.
-  Expected<DeviceImageTy *> loadBinary(GenericPluginTy &Plugin,
-                                       StringRef TgtImage,
-                                       PluginContextTy *Context);
+  Expected<DeviceImageTy *>
+  loadBinary(GenericPluginTy &Plugin, StringRef TgtImage,
+             PluginContextTy *Context,
+             GenericProfilerTy *ProfilerPtr = nullptr);
   virtual Expected<DeviceImageTy *>
   loadBinaryImpl(std::unique_ptr<MemoryBuffer> &&TgtImage, int32_t ImageId,
                  PluginContextTy *Context) = 0;
@@ -1111,10 +1119,12 @@ struct GenericDeviceTy : public DeviceAllocatorTy {
 
   /// Allocate data on the device or involving the device.
   Expected<void *> dataAlloc(int64_t Size, void *HostPtr, TargetAllocTy Kind,
-                             size_t Alignment);
+                             size_t Alignment,
+                             GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Deallocate data from the device or involving the device.
-  Error dataDelete(void *TgtPtr, TargetAllocTy Kind);
+  Error dataDelete(void *TgtPtr, TargetAllocTy Kind,
+                   GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Pin or register host memory to optimize transfers and return the device
   /// accessible pointer that devices should use for memory transfers involving
@@ -1218,7 +1228,8 @@ struct GenericDeviceTy : public DeviceAllocatorTy {
 
   /// Run the kernel associated with \p EntryPtr
   Error launchKernel(void *EntryPtr, KernelLaunchArgsTy &LaunchArgs,
-                     __tgt_async_info *AsyncInfo);
+                     __tgt_async_info *AsyncInfo,
+                     GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Enqueue a host call to AsyncInfo
   Error enqueueHostCall(void (*Callback)(void *), void *UserData,
@@ -1297,6 +1308,11 @@ struct GenericDeviceTy : public DeviceAllocatorTy {
   uint32_t getDebugKind() const { return OMPX_DebugKind; }
   virtual uint64_t getClockFrequency() const { return CLOCKS_PER_SEC; }
 
+  /// Get a device-specific timestamp in nanoseconds, used by the profiler
+  /// for timing device operations. Subclasses should override this to provide
+  /// hardware-accurate timestamps (e.g., via HSA system info).
+  virtual uint64_t getDeviceTimeStamp() { return 0; }
+
   /// Get target compute unit kind (e.g., sm_80, or gfx908).
   virtual std::string getComputeUnitKind() const { return "unknown"; }
 
@@ -1574,7 +1590,7 @@ struct GenericPluginTy {
   virtual Expected<int32_t> initImpl() = 0;
 
   /// Deinitialize the plugin and release the resources.
-  Error deinit();
+  Error deinit(GenericProfilerTy *ProfilerPtr = nullptr);
   virtual Error deinitImpl() = 0;
 
   /// Create a new device for the underlying plugin.
@@ -1648,12 +1664,12 @@ struct GenericPluginTy {
   /// Tear down any target-specific doorbell resources.
   virtual Error deinitRPCDoorbell() { return Plugin::success(); }
 
-  /// Get a reference to the record and replay interface for the plugin.
   /// Initialize a device within the plugin.
-  Error initDevice(int32_t DeviceId);
+  Error initDevice(int32_t DeviceId, GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Deinitialize a device within the plugin and release its resources.
-  Error deinitDevice(int32_t DeviceId);
+  Error deinitDevice(int32_t DeviceId,
+                     GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Indicate whether data can be exchanged directly between two devices under
   /// this same plugin. If this function returns true, it's safe to call the
@@ -1745,7 +1761,8 @@ struct GenericPluginTy {
   int32_t is_device_initialized(int32_t DeviceId) const;
 
   /// Initialize the device inside of the plugin.
-  int32_t init_device(int32_t DeviceId);
+  int32_t init_device(int32_t DeviceId,
+                      GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Return the number of devices this plugin can support.
   int32_t number_of_devices();
@@ -1762,13 +1779,16 @@ struct GenericPluginTy {
 
   /// Loads the associated binary into the plugin and returns a handle to it.
   int32_t load_binary(int32_t DeviceId, __tgt_device_image *TgtImage,
-                      __tgt_device_binary *Binary);
+                      __tgt_device_binary *Binary,
+                      GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Allocates memory that is accessively to the given device.
-  void *data_alloc(int32_t DeviceId, int64_t Size, void *HostPtr, int32_t Kind);
+  void *data_alloc(int32_t DeviceId, int64_t Size, void *HostPtr, int32_t Kind,
+                   GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Deallocates memory on the given device.
-  int32_t data_delete(int32_t DeviceId, void *TgtPtr, int32_t Kind);
+  int32_t data_delete(int32_t DeviceId, void *TgtPtr, int32_t Kind,
+                      GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Locks / pins host memory using the plugin runtime.
   int32_t data_lock(int32_t DeviceId, void *Ptr, int64_t Size,
@@ -1815,7 +1835,8 @@ struct GenericPluginTy {
   /// Begin executing a kernel on the given device.
   int32_t launch_kernel(int32_t DeviceId, void *TgtEntryPtr,
                         KernelLaunchArgsTy &LaunchArgs,
-                        __tgt_async_info *AsyncInfoPtr);
+                        __tgt_async_info *AsyncInfoPtr,
+                        GenericProfilerTy *ProfilerPtr = nullptr);
 
   /// Synchronize an asyncrhonous queue with the plugin runtime.
   int32_t synchronize(int32_t DeviceId, __tgt_async_info *AsyncInfoPtr);
diff --git a/offload/plugins-nextgen/common/src/GenericProfiler.cpp b/offload/plugins-nextgen/common/src/GenericProfiler.cpp
new file mode 100644
index 0000000000000..5486c40a7a9d2
--- /dev/null
+++ b/offload/plugins-nextgen/common/src/GenericProfiler.cpp
@@ -0,0 +1,36 @@
+//===- GenericProfiler.cpp - GenericProfiler implementation ---------------===//
+//
+// 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 "GenericProfiler.h"
+#include "PluginInterface.h"
+
+#include <cstdint>
+#include <memory>
+
+namespace llvm {
+namespace omp {
+namespace target {
+namespace plugin {
+
+uint64_t GenericProfilerTy::getDeviceTimeStamp(GenericDeviceTy *D) {
+  if (!D)
+    return 0;
+
+  return D->getDeviceTimeStamp();
+}
+
+GenericProfilerTy &getNoOpProfiler() {
+  static GenericProfilerTy NoOpProfiler;
+  return NoOpProfiler;
+}
+} // namespace plugin
+} // namespace target
+} // namespace omp
+} // namespace llvm
diff --git a/offload/plugins-nextgen/common/src/PluginInterface.cpp b/offload/plugins-nextgen/common/src/PluginInterface.cpp
index 118df2e3e6506..9b49f1598a3bd 100644
--- a/offload/plugins-nextgen/common/src/PluginInterface.cpp
+++ b/offload/plugins-nextgen/common/src/PluginInterface.cpp
@@ -21,6 +21,9 @@
 #include "Utils/ELF.h"
 #include "omptarget.h"
 
+
+#include "GenericProfiler.h"
+
 #include "llvm/Bitcode/BitcodeReader.h"
 #include "llvm/Frontend/OpenMP/OMPConstants.h"
 #include "llvm/Support/Error.h"
@@ -42,7 +45,9 @@ using namespace llvm::offload::debug;
 AsyncInfoWrapperTy::AsyncInfoWrapperTy(GenericDeviceTy &Device,
                                        __tgt_async_info *AsyncInfoPtr)
     : Device(Device),
-      AsyncInfoPtr(AsyncInfoPtr ? AsyncInfoPtr : &LocalAsyncInfo) {}
+      AsyncInfoPtr(AsyncInfoPtr ? AsyncInfoPtr : &LocalAsyncInfo) {
+  LocalAsyncInfo.ProfilerData = nullptr;
+}
 
 Error AsyncInfoWrapperTy::synchronize() {
   assert(AsyncInfoPtr && "AsyncInfoWrapperTy already finalized");
@@ -165,9 +170,15 @@ GenericKernelTy::getKernelLaunchEnvironment(
        DPxPTR(&LocalKLE), DPxPTR(*AllocOrErr),
        sizeof(KernelLaunchEnvironmentTy));
 
+  // Temporarily suppress ProfilerData so the KLE upload is not traced as
+  // a user data operation.
+  __tgt_async_info *AI = AsyncInfoWrapper;
+  void *SavedProfilerData = AI->ProfilerData;
+  AI->ProfilerData = nullptr;
   auto Err = GenericDevice.dataSubmit(*AllocOrErr, &LocalKLE,
                                       sizeof(KernelLaunchEnvironmentTy),
                                       AsyncInfoWrapper);
+  AI->ProfilerData = SavedProfilerData;
   if (Err)
     return Err;
   return static_cast<KernelLaunchEnvironmentTy *>(*AllocOrErr);
@@ -241,7 +252,9 @@ GenericKernelTy::prepareBlockMemory(GenericDeviceTy &GenericDevice,
 
 Error GenericKernelTy::launch(GenericDeviceTy &GenericDevice,
                               KernelLaunchArgsTy &LaunchArgs,
-                              AsyncInfoWrapperTy &AsyncInfoWrapper) const {
+                              AsyncInfoWrapperTy &AsyncInfoWrapper,
+                              GenericProfilerTy *ProfilerPtr) const {
+  GenericProfilerTy &Profiler = ProfilerPtr ? *ProfilerPtr : getNoOpProfiler();
   uint32_t EffectiveNumThreads[3] = {LaunchArgs.UserThreadLimit[0],
                                      LaunchArgs.UserThreadLimit[1],
                                      LaunchArgs.UserThreadLimit[2]};
@@ -319,6 +332,9 @@ Error GenericKernelTy::launch(GenericDeviceTy &GenericDevice,
     RRHandle = *RRHandleOrErr;
   }
 
+  Profiler.handlePreKernelLaunch(&GenericDevice, EffectiveNumBlocks,
+                                 AsyncInfoWrapper);
+
   if (auto Err =
           launchImpl(GenericDevice, EffectiveNumThreads, EffectiveNumBlocks,
                      DynBlockMemConf.NativeSize, LaunchArgs, AsyncInfoWrapper))
@@ -488,8 +504,11 @@ GenericDeviceTy::GenericDeviceTy(GenericPluginTy &Plugin, int32_t DeviceId,
   }
 }
 
-Error GenericDeviceTy::init(GenericPluginTy &Plugin) {
-  if (auto Err = initImpl(Plugin))
+Error GenericDeviceTy::init(GenericPluginTy &Plugin,
+                            GenericProfilerTy *ProfilerPtr) {
+  GenericProfilerTy &Profiler = ProfilerPtr ? *ProfilerPtr : getNoOpProfiler();
+
+  if (auto Err = initImpl(Plugin, ProfilerPtr))
     return Err;
 
   // Read and reinitialize the envars that depend on the device initialization.
@@ -546,7 +565,10 @@ Error GenericDeviceTy::unloadBinary(DeviceImageTy *Image) {
   return unloadBinaryImpl(Image);
 }
 
-Error GenericDeviceTy::deinit(GenericPluginTy &Plugin) {
+Error GenericDeviceTy::deinit(GenericPluginTy &Plugin,
+                              GenericProfilerTy *ProfilerPtr) {
+  GenericProfilerTy &Profiler = ProfilerPtr ? *ProfilerPtr : getNoOpProfiler();
+
   // Run the global destructors first in case they required the RPC server.
   for (auto &I : LoadedImages) {
     if (auto Err = callGlobalDestructors(Plugin, *I))
@@ -575,7 +597,10 @@ Error GenericDeviceTy::deinit(GenericPluginTy &Plugin) {
 }
 Expected<DeviceImageTy *>
 GenericDeviceTy::loadBinary(GenericPluginTy &Plugin, StringRef InputTgtImage,
-                            PluginContextTy *Context) {
+                            PluginContextTy *Context,
+                            GenericProfilerTy *ProfilerPtr) {
+  GenericProfilerTy &Profiler = ProfilerPtr ? *ProfilerPtr : getNoOpProfiler();
+
   ODBG(OLDT_Init) << "Load data from image "
                   << static_cast<const void *>(InputTgtImage.bytes_begin());
 
@@ -925,7 +950,12 @@ Error GenericDeviceTy::getDeviceMemorySize(uint64_t &DSize) {
 
 Expected<void *> GenericDeviceTy::dataAlloc(int64_t Size, void *HostPtr,
                                             TargetAllocTy Kind,
-                                            size_t Alignment) {
+                                            size_t Alignment,
+                                            GenericProfilerTy *ProfilerPtr) {
+  GenericProfilerTy &Profiler = ProfilerPtr ? *ProfilerPtr : getNoOpProfiler();
+
+  auto ProfTimer = Profiler.getScopedDataAllocTimer(this, HostPtr, Size);
+
   void *Alloc = nullptr;
 
   // TODO Check alignment.
@@ -977,7 +1007,12 @@ Expected<void *> GenericDeviceTy::dataAlloc(int64_t Size, void *HostPtr,
   return Alloc;
 }
 
-Error GenericDeviceTy::dataDelete(void *TgtPtr, TargetAllocTy Kind) {
+Error GenericDeviceTy::dataDelete(void *TgtPtr, TargetAllocTy Kind,
+                                  GenericProfilerTy *ProfilerPtr) {
+  GenericProfilerTy &Profiler = ProfilerPtr ? *ProfilerPtr : getNoOpProfiler();
+
+  auto ProfTimer = Profiler.getScopedDataDeleteTimer(this, TgtPtr);
+
   // Free is a noop when recording or replaying.
   if (RecordReplay && RecordReplay->isRecordingOrReplaying())
     return RecordReplay->deallocate(TgtPtr);
@@ -1078,7 +1113,8 @@ Error GenericDeviceTy::dataPrefetch(size_t Count, const void **Mems,
 
 Error GenericDeviceTy::launchKernel(void *EntryPtr,
                                     KernelLaunchArgsTy &LaunchArgs,
-                                    __tgt_async_info *AsyncInfo) {
+                                    __tgt_async_info *AsyncInfo,
+                                    GenericProfilerTy *ProfilerPtr) {
   AsyncInfoWrapperTy AsyncInfoWrapper(*this, AsyncInfo);
 
   GenericKernelTy &GenericKernel =
@@ -1096,7 +1132,8 @@ Error GenericDeviceTy::launchKernel(void *EntryPtr,
         .emplace(&GenericKernel, std::move(StackTrace), AsyncInfo);
   }
 
-  auto Err = GenericKernel.launch(*this, LaunchArgs, AsyncInfoWrapper);
+  auto Err =
+      GenericKernel.launch(*this, LaunchArgs, AsyncInfoWrapper, ProfilerPtr);
 
   AsyncInfoWrapper.finalize(Err);
 
@@ -1171,23 +1208,23 @@ MemoryManagerTy *PluginContextTy::getHostMemoryManager() {
 
 Expected<void *> PluginContextTy::allocate(GenericDeviceTy &Device,
                                            int64_t Size, void *HostPtr,
-                                           TargetAllocTy Kind,
-                                           size_t Alignment) {
+                                           TargetAllocTy Kind, size_t Alignment,
+                                           GenericProfilerTy *ProfilerPtr) {
   // Record-replay hands out interior pointers into a preallocated slab so
   // recorded kernels can re-execute at their original addresses; the MM pool
   // must be bypassed for those allocations to reach the RR bump allocator.
   if (auto *RR = Device.getRecordReplay(); RR && RR->isRecordingOrReplaying())
-    return Device.dataAlloc(Size, HostPtr, Kind, Alignment);
+    return Device.dataAlloc(Size, HostPtr, Kind, Alignment, ProfilerPtr);
 
   MemoryManagerTy *MM = (Kind == TARGET_ALLOC_HOST)
                             ? getHostMemoryManager()
                             : getDeviceMemoryManagerFor(Device, Kind);
   if (MM)
     return MM->allocate(Size, HostPtr, Alignment);
-  return Device.dataAlloc(Size, HostPtr, Kind, Alignment);
+  return Device.dataAlloc(Size, HostPtr, Kind, Alignment, ProfilerPtr);
 }
 
-Error PluginContextTy::deallocate(void *Ptr) {
+Error PluginContextTy::deallocate(void *Ptr, GenericProfilerTy *ProfilerPtr) {
   assert(!Devices.empty() && "context constructed without devices");
   auto InfoOrErr = getAllocInfo(Ptr);
   if (!InfoOrErr)
@@ -1195,22 +1232,23 @@ Error PluginContextTy::deallocate(void *Ptr) {
   GenericDeviceTy *OwnerDevice = InfoOrErr->Device;
   if (!OwnerDevice)
     OwnerDevice = Devices.front();
-  return deallocate(*OwnerDevice, Ptr, InfoOrErr->Kind);
+  return deallocate(*OwnerDevice, Ptr, InfoOrErr->Kind, ProfilerPtr);
 }
 
 Error PluginContextTy::deallocate(GenericDeviceTy &Device, void *Ptr,
-                                  TargetAllocTy Kind) {
+                                  TargetAllocTy Kind,
+                                  GenericProfilerTy *ProfilerPtr) {
   // Symmetric with allocate: record-replay allocations never entered the MM
   // pool, so route their free through dataDelete's RR shortcut.
   if (auto *RR = Device.getRecordReplay(); RR && RR->isRecordingOrReplaying())
-    return Device.dataDelete(Ptr, Kind);
+    return Device.dataDelete(Ptr, Kind, ProfilerPtr);
 
   MemoryManagerTy *MM = (Kind == TARGET_ALLOC_HOST)
                             ? getHostMemoryManager()
                             : getDeviceMemoryManagerFor(Device, Kind);
   if (MM)
     return MM->free(Ptr);
-  return Device.dataDelete(Ptr, Kind);
+  return Device.dataDelete(Ptr, Kind, ProfilerPtr);
 }
 
 PluginContextTy &
@@ -1359,7 +1397,7 @@ Error GenericPluginTy::init() {
   return Plugin::success();
 }
 
-Error GenericPluginTy::deinit() {
+Error GenericPluginTy::deinit(GenericProfilerTy *ProfilerPtr) {
   assert(Initialized && "Plugin was not initialized!");
 
   // Release context-held resources before the devices that back them.
@@ -1368,7 +1406,7 @@ Error GenericPluginTy::deinit() {
   // Deinitialize all active devices.
   for (int32_t DeviceId = 0; DeviceId < NumDevices; ++DeviceId) {
     if (Devices[DeviceId]) {
-      if (auto Err = deinitDevice(DeviceId))
+      if (auto Err = deinitDevice(DeviceId, ProfilerPtr))
         return Err;
     }
     assert(!Devices[DeviceId] && "Device was not deinitialized");
@@ -1392,7 +1430,8 @@ Error GenericPluginTy::deinit() {
   return Plugin::success();
 }
 
-Error GenericPluginTy::initDevice(int32_t DeviceId) {
+Error GenericPluginTy::initDevice(int32_t DeviceId,
+                                  GenericProfilerTy *ProfilerPtr) {
   assert(!Devices[DeviceId] && "Device already initialized");
 
   // Create the device and save the reference.
@@ -1403,16 +1442,17 @@ Error GenericPluginTy::initDevice(int32_t DeviceId) {
   Devices[DeviceId] = Device;
 
   // Initialize the device and its resources.
-  return Device->init(*this);
+  return Device->init(*this, ProfilerPtr);
 }
 
-Error GenericPluginTy::deinitDevice(int32_t DeviceId) {
+Error GenericPluginTy::deinitDevice(int32_t DeviceId,
+                                    GenericProfilerTy *ProfilerPtr) {
   // The device may be already deinitialized.
   if (Devices[DeviceId] == nullptr)
     return Plugin::success();
 
   // Deinitialize the device and release its resources.
-  if (auto Err = Devices[DeviceId]->deinit(*this))
+  if (auto Err = Devices[DeviceId]->deinit(*this, ProfilerPtr))
     return Err;
 
   // Delete the device and invalidate its reference.
@@ -1528,8 +1568,9 @@ int32_t GenericPluginTy::is_device_initialized(int32_t DeviceId) const {
   return isValidDeviceId(DeviceId) && Devices[DeviceId] != nullptr;
 }
 
-int32_t GenericPluginTy::init_device(int32_t DeviceId) {
-  auto Err = initDevice(DeviceId);
+int32_t GenericPluginTy::init_device(int32_t DeviceId,
+                                     GenericProfilerTy *ProfilerPtr) {
+  auto Err = initDevice(DeviceId, ProfilerPtr);
   if (Err) {
     REPORT() << "Failure to initialize device " << DeviceId << ": "
              << toString(std::move(Err));
@@ -1565,12 +1606,14 @@ int32_t GenericPluginTy::initialize_record_replay(
 
 int32_t GenericPluginTy::load_binary(int32_t DeviceId,
                                      __tgt_device_image *TgtImage,
-                                     __tgt_device_binary *Binary) {
+                                     __tgt_device_binary *Binary,
+                                     GenericProfilerTy *ProfilerPtr) {
   GenericDeviceTy &Device = getDevice(DeviceId);
 
   StringRef Buffer(reinterpret_cast<const char *>(TgtImage->ImageStart),
                    utils::getPtrDiff(TgtImage->ImageEnd, TgtImage->ImageStart));
-  auto ImageOrErr = Device.loadBinary(*this, Buffer, /*Context=*/nullptr);
+  auto ImageOrErr =
+      Device.loadBinary(*this, Buffer, /*Context=*/nullptr, ProfilerPtr);
   if (!ImageOrErr) {
     auto Err = ImageOrErr.takeError();
     REPORT() << "Failure to load binary image " << TgtImage << " on device "
@@ -1587,11 +1630,13 @@ int32_t GenericPluginTy::load_binary(int32_t DeviceId,
 }
 
 void *GenericPluginTy::data_alloc(int32_t DeviceId, int64_t Size, void *HostPtr,
-                                  int32_t Kind) {
+                                  int32_t Kind,
+                                  GenericProfilerTy *ProfilerPtr) {
   auto &Device = getDevice(DeviceId);
   auto AllocOrErr = getDefaultContext(Device).allocate(
       Device, Size, HostPtr, static_cast<TargetAllocTy>(Kind),
-      /*Alignment=*/0);
+      /*Alignment=*/0, ProfilerPtr);
+
   if (!AllocOrErr) {
     REPORT() << "Failure to allocate device memory: "
              << toString(AllocOrErr.takeError());
@@ -1602,10 +1647,11 @@ void *GenericPluginTy::data_alloc(int32_t DeviceId, int64_t Size, void *HostPtr,
 }
 
 int32_t GenericPluginTy::data_delete(int32_t DeviceId, void *TgtPtr,
-                                     int32_t Kind) {
+                                     int32_t Kind,
+                                     GenericProfilerTy *ProfilerPtr) {
   auto &Device = getDevice(DeviceId);
   if (auto Err = getDefaultContext(Device).deallocate(
-          Device, TgtPtr, static_cast<TargetAllocTy>(Kind))) {
+          Device, TgtPtr, static_cast<TargetAllocTy>(Kind), ProfilerPtr)) {
     REPORT() << "Failure to deallocate device pointer " << TgtPtr << ": "
              << toString(std::move(Err));
     return OFFLOAD_FAIL;
@@ -1735,9 +1781,10 @@ int32_t GenericPluginTy::data_exchange_async(int32_t SrcDeviceId, void *SrcPtr,
 
 int32_t GenericPluginTy::launch_kernel(int32_t DeviceId, void *TgtEntryPtr,
                                        KernelLaunchArgsTy &LaunchArgs,
-                                       __tgt_async_info *AsyncInfoPtr) {
-  auto Err =
-      getDevice(DeviceId).launchKernel(TgtEntryPtr, LaunchArgs, AsyncInfoPtr);
+                                       __tgt_async_info *AsyncInfoPtr,
+                                       GenericProfilerTy *ProfilerPtr) {
+  auto Err = getDevice(DeviceId).launchKernel(TgtEntryPtr, LaunchArgs,
+                                              AsyncInfoPtr, ProfilerPtr);
   if (Err) {
     REPORT() << "Failure to run target region " << TgtEntryPtr << " in device "
              << DeviceId << ": " << toString(std::move(Err));
diff --git a/offload/plugins-nextgen/cuda/src/rtl.cpp b/offload/plugins-nextgen/cuda/src/rtl.cpp
index f349974763614..59e1ef2b46ea8 100644
--- a/offload/plugins-nextgen/cuda/src/rtl.cpp
+++ b/offload/plugins-nextgen/cuda/src/rtl.cpp
@@ -278,7 +278,8 @@ struct CUDADeviceTy : public GenericDeviceTy {
   ~CUDADeviceTy() {}
 
   /// Initialize the device, its resources and get its properties.
-  Error initImpl(GenericPluginTy &Plugin) override {
+  Error initImpl(GenericPluginTy &Plugin,
+                 GenericProfilerTy *ProfilerPtr) override {
     CUresult Res = cuDeviceGet(&Device, DeviceId);
     if (auto Err = Plugin::check(Res, "error in cuDeviceGet: %s"))
       return Err;
diff --git a/offload/plugins-nextgen/host/src/rtl.cpp b/offload/plugins-nextgen/host/src/rtl.cpp
index 37f3214e0b4d1..f1a97ac5d6116 100644
--- a/offload/plugins-nextgen/host/src/rtl.cpp
+++ b/offload/plugins-nextgen/host/src/rtl.cpp
@@ -146,7 +146,10 @@ struct GenELF64DeviceTy : public GenericDeviceTy {
   ~GenELF64DeviceTy() {}
 
   /// Initialize the device, which is a no-op
-  Error initImpl(GenericPluginTy &Plugin) override { return Plugin::success(); }
+  Error initImpl(GenericPluginTy &Plugin,
+                 GenericProfilerTy *ProfilerPtr) override {
+    return Plugin::success();
+  }
 
   /// Unload the binary image
   ///
@@ -491,10 +494,10 @@ struct GenELF64PluginContextTy final : public PluginContextTy {
   }
 
   Expected<void *> allocate(GenericDeviceTy &Device, int64_t Size,
-                            void *HostPtr, TargetAllocTy Kind,
-                            size_t Alignment) override {
-    auto PtrOrErr =
-        PluginContextTy::allocate(Device, Size, HostPtr, Kind, Alignment);
+                            void *HostPtr, TargetAllocTy Kind, size_t Alignment,
+                            GenericProfilerTy *ProfilerPtr) override {
+    auto PtrOrErr = PluginContextTy::allocate(Device, Size, HostPtr, Kind,
+                                              Alignment, ProfilerPtr);
     if (!PtrOrErr)
       return PtrOrErr.takeError();
     void *Ptr = *PtrOrErr;
@@ -503,13 +506,13 @@ struct GenELF64PluginContextTy final : public PluginContextTy {
     return Ptr;
   }
 
-  Error deallocate(GenericDeviceTy &Device, void *Ptr,
-                   TargetAllocTy Kind) override {
+  Error deallocate(GenericDeviceTy &Device, void *Ptr, TargetAllocTy Kind,
+                   GenericProfilerTy *ProfilerPtr) override {
     {
       std::lock_guard<std::mutex> Lock(AllocsMutex);
       Allocs.erase(Ptr);
     }
-    return PluginContextTy::deallocate(Device, Ptr, Kind);
+    return PluginContextTy::deallocate(Device, Ptr, Kind, ProfilerPtr);
   }
 
   Expected<PluginAllocInfoTy> getAllocInfo(const void *Ptr) override {
diff --git a/offload/plugins-nextgen/level_zero/include/L0Device.h b/offload/plugins-nextgen/level_zero/include/L0Device.h
index 3341f261deaf9..aa102af83ff34 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Device.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Device.h
@@ -181,7 +181,8 @@ class L0DeviceTy final : public GenericDeviceTy {
   }
 
   Error setContext() override { return Plugin::success(); }
-  Error initImpl(GenericPluginTy &Plugin) override;
+  Error initImpl(GenericPluginTy &Plugin,
+                 GenericProfilerTy *ProfilerPtr) override;
   Error deinitImpl() override;
   ze_device_handle_t getZeDevice() const { return zeDevice; }
 
diff --git a/offload/plugins-nextgen/level_zero/include/L0Plugin.h b/offload/plugins-nextgen/level_zero/include/L0Plugin.h
index d6656cb4cf792..7573b2088fe20 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Plugin.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Plugin.h
@@ -44,9 +44,10 @@ class LevelZeroPluginContextTy final : public PluginContextTy {
 
   llvm::Expected<void *> allocate(GenericDeviceTy &Device, int64_t Size,
                                   void *HostPtr, TargetAllocTy Kind,
-                                  size_t Alignment) override;
-  llvm::Error deallocate(GenericDeviceTy &Device, void *Ptr,
-                         TargetAllocTy Kind) override;
+                                  size_t Alignment,
+                                  GenericProfilerTy *ProfilerPtr) override;
+  llvm::Error deallocate(GenericDeviceTy &Device, void *Ptr, TargetAllocTy Kind,
+                         GenericProfilerTy *ProfilerPtr) override;
   Expected<PluginAllocInfoTy> getAllocInfo(const void *Ptr) override;
 
   /// Initialize per-plugin-context memory allocators. Runs the pool
diff --git a/offload/plugins-nextgen/level_zero/src/L0Device.cpp b/offload/plugins-nextgen/level_zero/src/L0Device.cpp
index e41268d7eb843..c539e7a23e523 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Device.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Device.cpp
@@ -159,7 +159,8 @@ void L0DeviceTy::reportDeviceInfo() const {
   });
 }
 
-Error L0DeviceTy::initImpl(GenericPluginTy &Plugin) {
+Error L0DeviceTy::initImpl(GenericPluginTy &Plugin,
+                           GenericProfilerTy *ProfilerPtr) {
   const auto &Options = getPlugin().getOptions();
 
   uint32_t Count = 1;
diff --git a/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp b/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp
index a09ca28ad6df8..7eb60e336f5af 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Plugin.cpp
@@ -305,11 +305,9 @@ Error LevelZeroPluginContextTy::initAllocators() {
   return Plugin::success();
 }
 
-Expected<void *> LevelZeroPluginContextTy::allocate(GenericDeviceTy &Device,
-                                                    int64_t Size,
-                                                    void * /*HostPtr*/,
-                                                    TargetAllocTy Kind,
-                                                    size_t Alignment) {
+Expected<void *> LevelZeroPluginContextTy::allocate(
+    GenericDeviceTy &Device, int64_t Size, void * /*HostPtr*/,
+    TargetAllocTy Kind, size_t Alignment, GenericProfilerTy * /*ProfilerPtr*/) {
   MemAllocatorTy *Allocator = nullptr;
   int32_t ResolvedKind = Kind;
   if (Kind == TARGET_ALLOC_HOST) {
@@ -334,8 +332,9 @@ Expected<void *> LevelZeroPluginContextTy::allocate(GenericDeviceTy &Device,
                           AllocOptionTy::ALLOC_OPT_NONE);
 }
 
-Error LevelZeroPluginContextTy::deallocate(GenericDeviceTy &Device, void *Ptr,
-                                           TargetAllocTy Kind) {
+Error LevelZeroPluginContextTy::deallocate(
+    GenericDeviceTy &Device, void *Ptr, TargetAllocTy Kind,
+    GenericProfilerTy * /*ProfilerPtr*/) {
   if (Kind == TARGET_ALLOC_HOST) {
     if (!HostAllocator)
       return Plugin::error(ErrorCode::NOT_FOUND,

>From 7a2eb58c910fe0c2a423da4e1d16f704de4f817b Mon Sep 17 00:00:00 2001
From: JP Lehr <JanPatrick.Lehr at amd.com>
Date: Wed, 23 Sep 2026 08:06:02 -0500
Subject: [PATCH 2/2] Review: Improve naming and documentation

---
 .../common/include/GenericProfiler.h          | 71 +++++++++++++------
 .../common/src/PluginInterface.cpp            |  7 +-
 2 files changed, 49 insertions(+), 29 deletions(-)

diff --git a/offload/plugins-nextgen/common/include/GenericProfiler.h b/offload/plugins-nextgen/common/include/GenericProfiler.h
index 57bb0847f32d9..c7010347858fc 100644
--- a/offload/plugins-nextgen/common/include/GenericProfiler.h
+++ b/offload/plugins-nextgen/common/include/GenericProfiler.h
@@ -33,6 +33,8 @@ struct GenericDeviceTy;
 struct GenericPluginTy;
 class GenericProfilerTy;
 
+/// Helper function to unpack a tuple of arguments and call a function with
+/// them.
 template <typename FunT, typename... ArgsT, size_t... IdxSequence>
 void callViaIndexSeq(FunT F, GenericProfilerTy *P, uint64_t StartNanos,
                      uint64_t EndNanos, std::tuple<ArgsT...> Args,
@@ -40,6 +42,8 @@ void callViaIndexSeq(FunT F, GenericProfilerTy *P, uint64_t StartNanos,
   F(P, StartNanos, EndNanos, std::get<IdxSequence>(Args)...);
 }
 
+/// Helper function to unpack a tuple of arguments and call a function with
+/// them.
 template <typename FunT, typename... ArgsT>
 void callViaUnpack(FunT F, GenericProfilerTy *P, uint64_t StartNanos,
                    uint64_t EndNanos, std::tuple<ArgsT...> Tup) {
@@ -65,6 +69,7 @@ class GenericProfilerTy {
   /// cannot outlive the buffers they point into.
   virtual OmptTracingBufferMgr *getTraceRecordManager() { return nullptr; }
 
+  /// Returns true if profiling is enabled, false otherwise.
   virtual bool isProfilingEnabled() { return false; }
 
   /// Set the factors which are used to interpolate the device clock compared to
@@ -115,42 +120,62 @@ class GenericProfilerTy {
 
   /// RAII style timer that measures the elapsed time between construction and
   /// destruction, then invokes a callback with the profiler, start/end times,
-  /// and any captured arguments.
-  template <typename FnT, typename... ArgsT> class ProfTimerTy {
+  /// and any captured arguments. It wraps an existing GenericProfiler instance
+  /// to delay the call of the ProfilerFunction (F) until this object's dtor is
+  /// called.
+  template <typename ProfilerFuncTy, typename... ProfilerFuncArgsTy>
+  class ProfTimerTy {
   public:
-    ProfTimerTy(FnT &&F, GenericProfilerTy *P, GenericDeviceTy *D, ArgsT... As)
-        : Fun(F), Prof(P), Dev(D), Args(As...) {
-      assert(Prof && "GenericProfilerTy is null");
-      assert(Dev && "GenericDeviceTy is null");
-      if (Prof)
-        StartTime = Prof->getDeviceTimeStamp(Dev);
+    /// On creation, saves the timestamp from the profiler and device, and
+    /// stores the callback function and its arguments.
+    ProfTimerTy(ProfilerFuncTy &&F, GenericProfilerTy *P, GenericDeviceTy *D,
+                ProfilerFuncArgsTy... As)
+        : ProfilerFunction(F), ProfilerInstance(P), Device(D),
+          ProfilerFuncArgs(As...) {
+      assert(ProfilerInstance && "GenericProfilerTy is null");
+      assert(Device && "GenericDeviceTy is null");
+      if (ProfilerInstance)
+        StartTime = ProfilerInstance->getDeviceTimeStamp(Device);
     }
 
+    /// On destruction, saves the end timestamp and invokes the callback
+    /// function with the profiler, start/end times, and captured arguments.
     ~ProfTimerTy() {
-      assert(Prof && "GenericProfilerTy is null");
-      assert(Dev && "GenericDeviceTy is null");
-      if (Prof) {
-        uint64_t EndTime = Prof->getDeviceTimeStamp(Dev);
-        callViaUnpack(Fun, Prof, StartTime, EndTime, Args);
+      assert(ProfilerInstance && "GenericProfilerTy is null");
+      assert(Device && "GenericDeviceTy is null");
+      if (ProfilerInstance) {
+        uint64_t EndTime = ProfilerInstance->getDeviceTimeStamp(Device);
+        callViaUnpack(ProfilerFunction, ProfilerInstance, StartTime, EndTime,
+                      ProfilerFuncArgs);
       }
     }
 
   private:
-    FnT Fun;
-    GenericProfilerTy *Prof;
-    GenericDeviceTy *Dev;
+    /// The callback function to be invoked on destruction, which takes the
+    /// profiler, start/end times, and captured arguments.
+    ProfilerFuncTy ProfilerFunction;
+    /// The profiler instance to be used for timing and invoking the callback
+    /// function.
+    GenericProfilerTy *ProfilerInstance;
+    /// The device instance to be used for timing and invoking the callback
+    /// function.
+    GenericDeviceTy *Device;
+    /// The start time of the timer, captured at construction.
     uint64_t StartTime = 0;
-    std::tuple<ArgsT...> Args;
+    /// The captured arguments to be passed to the callback function on
+    /// destruction.
+    std::tuple<ProfilerFuncArgsTy...> ProfilerFuncArgs;
   };
 
-  template <typename FnT, typename... ArgsT>
+  template <typename ProfilerFuncTy, typename... ProfilerFuncArgsTy>
   [[maybe_unused]]
-  ProfTimerTy(FnT &&, GenericProfilerTy *, ArgsT...)
-      -> ProfTimerTy<FnT, ArgsT...>;
+  ProfTimerTy(ProfilerFuncTy &&, GenericProfilerTy *, ProfilerFuncArgsTy...)
+      -> ProfTimerTy<ProfilerFuncTy, ProfilerFuncArgsTy...>;
 
-  template <typename FnT, typename... ArgsT> friend class ProfTimerTy;
+  template <typename ProfilerFuncTy, typename... ProfilerFuncArgsTy>
+  friend class ProfTimerTy;
 
-  /// Returns an RAII style timer, which will handle data allocation timing.
+  /// Returns an RAII style timer, which handles data allocation timing.
   [[nodiscard]] auto getScopedDataAllocTimer(GenericDeviceTy *Dev,
                                              void *HostPtr, uint64_t Size,
                                              void *ProfData = nullptr) {
@@ -162,7 +187,7 @@ class GenericProfilerTy {
         this, Dev, HostPtr, Size, ProfData);
   }
 
-  /// Returns an RAII style timer, which will handle data deletion timing.
+  /// Returns an RAII style timer, which handles data deletion timing.
   [[nodiscard]] auto getScopedDataDeleteTimer(GenericDeviceTy *Dev,
                                               void *TgtPtr,
                                               void *ProfData = nullptr) {
diff --git a/offload/plugins-nextgen/common/src/PluginInterface.cpp b/offload/plugins-nextgen/common/src/PluginInterface.cpp
index 9b49f1598a3bd..b158110df8b1a 100644
--- a/offload/plugins-nextgen/common/src/PluginInterface.cpp
+++ b/offload/plugins-nextgen/common/src/PluginInterface.cpp
@@ -15,15 +15,13 @@
 #include "Shared/Environment.h"
 
 #include "ErrorReporting.h"
+#include "GenericProfiler.h"
 #include "GlobalHandler.h"
 #include "JIT.h"
 #include "Shared/Utils.h"
 #include "Utils/ELF.h"
 #include "omptarget.h"
 
-
-#include "GenericProfiler.h"
-
 #include "llvm/Bitcode/BitcodeReader.h"
 #include "llvm/Frontend/OpenMP/OMPConstants.h"
 #include "llvm/Support/Error.h"
@@ -506,7 +504,6 @@ GenericDeviceTy::GenericDeviceTy(GenericPluginTy &Plugin, int32_t DeviceId,
 
 Error GenericDeviceTy::init(GenericPluginTy &Plugin,
                             GenericProfilerTy *ProfilerPtr) {
-  GenericProfilerTy &Profiler = ProfilerPtr ? *ProfilerPtr : getNoOpProfiler();
 
   if (auto Err = initImpl(Plugin, ProfilerPtr))
     return Err;
@@ -567,7 +564,6 @@ Error GenericDeviceTy::unloadBinary(DeviceImageTy *Image) {
 
 Error GenericDeviceTy::deinit(GenericPluginTy &Plugin,
                               GenericProfilerTy *ProfilerPtr) {
-  GenericProfilerTy &Profiler = ProfilerPtr ? *ProfilerPtr : getNoOpProfiler();
 
   // Run the global destructors first in case they required the RPC server.
   for (auto &I : LoadedImages) {
@@ -599,7 +595,6 @@ Expected<DeviceImageTy *>
 GenericDeviceTy::loadBinary(GenericPluginTy &Plugin, StringRef InputTgtImage,
                             PluginContextTy *Context,
                             GenericProfilerTy *ProfilerPtr) {
-  GenericProfilerTy &Profiler = ProfilerPtr ? *ProfilerPtr : getNoOpProfiler();
 
   ODBG(OLDT_Init) << "Load data from image "
                   << static_cast<const void *>(InputTgtImage.bytes_begin());



More information about the llvm-commits mailing list