[llvm] [offload] Fix kernel record/replay and add extensible mechanism (PR #190588)
Johannes Doerfert via llvm-commits
llvm-commits at lists.llvm.org
Wed Apr 8 15:46:23 PDT 2026
================
@@ -0,0 +1,247 @@
+#include "PluginInterface.h"
+
+#include "Shared/APITypes.h"
+
+#include "ErrorReporting.h"
+#include "Shared/Utils.h"
+
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <cstdint>
+#include <filesystem>
+#include <functional>
+
+using namespace llvm;
+using namespace omp;
+using namespace target;
+using namespace plugin;
+using namespace error;
+
+Error RecordReplayTy::init(uint64_t MemSize, void *VAddr) {
+ if (!VAddr)
+ VAddr = Device.getSuggestedVirtualAddress();
+
+ auto StartAddrOrErr = Device.allocateWithVirtualAddress(MemSize, VAddr);
+ if (!StartAddrOrErr)
+ return StartAddrOrErr.takeError();
+ if (!*StartAddrOrErr)
+ return Plugin::error(ErrorCode::OUT_OF_RESOURCES, "allocating memory");
+
+ StartAddr = *StartAddrOrErr;
+ TotalSize = MemSize;
+
+ INFO(OMP_INFOTYPE_PLUGIN_KERNEL, Device.getDeviceId(),
+ "Record initialized with starting address %p, "
+ "memory size %lu bytes and status %s\n",
+ StartAddr, TotalSize,
+ Status == StatusTy::Recording ? "recording" : "replaying");
+
+ return Plugin::success();
+}
+
+Error RecordReplayTy::deinit() {
+ if (StartAddr)
+ return Device.deallocateWithVirtualAddress(StartAddr, TotalSize);
+ return Plugin::success();
+}
+
+std::pair<const RecordReplayTy::InstanceTy &, bool>
+RecordReplayTy::registerInstance(StringRef KernelName, uint32_t NumTeams,
+ uint32_t NumThreads,
+ uint32_t SharedMemorySize) {
+ std::lock_guard<std::mutex> LG(InstancesLock);
+ auto [It, Inserted] =
+ Instances.emplace(KernelName, NumTeams, NumThreads, SharedMemorySize);
+ // Increase the number of occurrences.
+ It->Occurrences += 1;
+ return {*It, Inserted};
+}
+
+void *RecordReplayTy::allocate(uint64_t Size) {
+ assert(StartAddr && "Expected memory has been pre-allocated");
+ constexpr int Alignment = 16;
+ // Assume alignment is a power of 2.
+ int64_t AlignedSize = (Size + (Alignment - 1)) & (~(Alignment - 1));
+
+ std::lock_guard<std::mutex> LG(AllocationLock);
+ void *Alloc = (char *)StartAddr + CurrentSize;
+ CurrentSize += AlignedSize;
+ return Alloc;
+}
+
+Expected<RecordReplayTy::HandleTy> RecordReplayTy::recordPrologue(
+ const GenericKernelTy &Kernel, const KernelArgsTy &KernelArgs,
+ const KernelLaunchParamsTy &LaunchParams, uint32_t NumTeams[3],
+ uint32_t NumThreads[3], uint32_t SharedMemorySize) {
+ if (!isRecordingOrReplaying())
+ return HandleTy{nullptr, false};
+
+ // Register the instance and avoid recording if it is inactive or replaying.
+ auto [Instance, First] = registerInstance(Kernel.getName(), NumTeams[0],
+ NumThreads[0], SharedMemorySize);
+
+ HandleTy Handle{&Instance, First};
+ if (isReplaying() || !First)
+ return Handle;
+
+ if (auto Err = recordDescImpl(Kernel, Instance, KernelArgs, LaunchParams))
+ return Err;
+
+ if (auto Err = recordPrologueImpl(Kernel, Instance, KernelArgs, LaunchParams))
+ return Err;
+
+ return Handle;
+}
+
+Error RecordReplayTy::recordEpilogue(const GenericKernelTy &Kernel,
+ HandleTy Handle) {
+ if (!shouldRecordEpilogue() || !Handle.Active)
+ return Plugin::success();
+
+ return recordEpilogueImpl(Kernel, *Handle.Instance);
+}
+
+Error NativeRecordReplayTy::recordPrologueImpl(
+ const GenericKernelTy &Kernel, const InstanceTy &Instance,
+ const KernelArgsTy &KernelArgs, const KernelLaunchParamsTy &LaunchParams) {
+ SmallString<128> SnapshotFilename = {Kernel.getName(), ".memory"};
+ if (auto Err = recordSnapshot(SnapshotFilename))
+ return Err;
+
+ SmallString<128> GlobalsFilename = {Kernel.getName(), ".globals"};
+ if (auto Err = recordGlobals(GlobalsFilename))
+ return Err;
+
+ SmallString<128> ImageFilename = {Kernel.getName(), ".image"};
+ return recordImage(Kernel, ImageFilename);
+}
+
+Error NativeRecordReplayTy::recordEpilogueImpl(const GenericKernelTy &Kernel,
+ const InstanceTy &Instance) {
+ SmallString<128> SnapshotFilename = {
+ Kernel.getName(),
+ (isRecording() ? ".original.output" : ".replay.output")};
+ return recordSnapshot(SnapshotFilename);
+}
+
+Error NativeRecordReplayTy::recordDescImpl(
+ const GenericKernelTy &Kernel, const InstanceTy &Instance,
+ const KernelArgsTy &KernelArgs, const KernelLaunchParamsTy &LaunchParams) {
+ json::Object JsonKernelInfo;
+ JsonKernelInfo["Name"] = Kernel.getName();
+ JsonKernelInfo["NumArgs"] = KernelArgs.NumArgs;
+ JsonKernelInfo["NumTeamsClause"] = Instance.NumTeams;
+ JsonKernelInfo["ThreadLimitClause"] = Instance.NumThreads;
+ JsonKernelInfo["SharedMemorySize"] = Instance.SharedMemorySize;
+ JsonKernelInfo["LoopTripCount"] = KernelArgs.Tripcount;
+ JsonKernelInfo["DeviceId"] = Device.getDeviceId();
+ JsonKernelInfo["VAllocAddr"] = (intptr_t)StartAddr;
+ JsonKernelInfo["VAllocSize"] = TotalSize;
+
+ json::Array JsonArgPtrs;
+ for (uint32_t I = 0; I < KernelArgs.NumArgs; ++I)
+ JsonArgPtrs.push_back((intptr_t)(*(void **)LaunchParams.Ptrs[I]));
+ JsonKernelInfo["ArgPtrs"] = json::Value(std::move(JsonArgPtrs));
+
+ json::Array JsonArgOffsets;
+ for (uint32_t I = 0; I < KernelArgs.NumArgs; ++I)
+ JsonArgOffsets.push_back(0);
+ JsonKernelInfo["ArgOffsets"] = json::Value(std::move(JsonArgOffsets));
+
+ SmallString<128> JsonFilename = {Kernel.getName(), ".json"};
+ std::error_code EC;
+ raw_fd_ostream JsonOS(JsonFilename.str(), EC);
+ if (EC)
+ return Plugin::error(ErrorCode::UNKNOWN, "saving kernel json file");
+ JsonOS << json::Value(std::move(JsonKernelInfo));
+ JsonOS.close();
+ return Plugin::success();
+}
+
+Error NativeRecordReplayTy::recordSnapshot(StringRef Filename) {
+ // Another thread may be allocating memory.
+ AllocationLock.lock();
+ uint64_t RecordSize = CurrentSize;
+ AllocationLock.unlock();
+
+ ErrorOr<std::unique_ptr<WritableMemoryBuffer>> DeviceMemoryMB =
+ WritableMemoryBuffer::getNewUninitMemBuffer(RecordSize);
+ if (!DeviceMemoryMB)
+ return Plugin::error(ErrorCode::UNKNOWN,
+ "creating MemoryBuffer for device memory");
+
+ if (auto Err = Device.dataRetrieve(DeviceMemoryMB.get()->getBufferStart(),
+ StartAddr, RecordSize, nullptr))
+ return Err;
+
+ StringRef DeviceMemory(DeviceMemoryMB.get()->getBufferStart(), RecordSize);
+ std::error_code EC;
+ raw_fd_ostream OS(Filename, EC);
+ if (EC)
+ return Plugin::error(ErrorCode::UNKNOWN, "dumping memory to file");
+ OS << DeviceMemory;
+ OS.close();
+ return Plugin::success();
+}
+
+Error NativeRecordReplayTy::recordImage(const GenericKernelTy &Kernel,
+ StringRef Filename) {
+ std::error_code EC;
+ raw_fd_ostream OS(Filename, EC);
+ if (EC)
+ return Plugin::error(ErrorCode::UNKNOWN, "saving image");
+ OS << Kernel.getImage().getMemoryBuffer().getBuffer();
+ OS.close();
+ return Plugin::success();
+}
+
+Error NativeRecordReplayTy::recordGlobals(StringRef Filename) {
+ int32_t Size = 0;
+
+ for (auto &OffloadEntry : GlobalEntries) {
----------------
jdoerfert wrote:
If other threads can allocate memory while we do a dump (see above), couldn't they add global entries? If so, we need to guard the traversals, probably the entire function.
https://github.com/llvm/llvm-project/pull/190588
More information about the llvm-commits
mailing list