blob: 1d364bbe2418f599fb4c1e1ca55df00cec41c915 [file]
//===- llvm-omp-kernel-replay.cpp - Replay OpenMP offload kernel ----------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This is a command line utility to replay the execution of recorded OpenMP
// offload kernels.
//
//===----------------------------------------------------------------------===//
#include "Shared/Utils.h"
#include "omptarget.h"
#include "llvm/Frontend/Offloading/Utility.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBuffer.h"
#include <cstdint>
#include <cstdlib>
#include <filesystem>
using namespace llvm;
cl::OptionCategory ReplayOptions("llvm-omp-kernel-replay Options");
// InputFilename - The filename to read the json description of the kernel.
static cl::opt<std::string> InputFilename(cl::Positional,
cl::desc("<input kernel json file>"),
cl::Required);
static cl::opt<bool> VerifyOpt(
"verify",
cl::desc(
"Verify device memory post execution against the original output."),
cl::init(false), cl::cat(ReplayOptions));
static cl::opt<bool> SaveOutputOpt(
"save-output",
cl::desc("Save the device memory output of the replayed kernel execution."),
cl::init(false), cl::cat(ReplayOptions));
static cl::opt<uint32_t> NumTeamsOpt("num-teams",
cl::desc("Set the number of teams."),
cl::init(0), cl::cat(ReplayOptions));
static cl::opt<uint32_t> NumThreadsOpt("num-threads",
cl::desc("Set the number of threads."),
cl::init(0), cl::cat(ReplayOptions));
static cl::opt<int32_t> DeviceIdOpt("device-id", cl::desc("Set the device id."),
cl::init(-1), cl::cat(ReplayOptions));
int main(int argc, char **argv) {
cl::HideUnrelatedOptions(ReplayOptions);
cl::ParseCommandLineOptions(argc, argv, "llvm-omp-kernel-replay\n");
ErrorOr<std::unique_ptr<MemoryBuffer>> KernelInfoMB =
MemoryBuffer::getFile(InputFilename, /*isText=*/true,
/*RequiresNullTerminator=*/true);
if (!KernelInfoMB)
reportFatalUsageError("Error reading the kernel info json file");
Expected<json::Value> JsonKernelInfo =
json::parse(KernelInfoMB.get()->getBuffer());
if (auto Err = JsonKernelInfo.takeError())
reportFatalUsageError("Cannot parse the kernel info json file");
auto NumTeamsJson =
JsonKernelInfo->getAsObject()->getInteger("NumTeamsClause");
uint32_t NumTeams = (NumTeamsOpt > 0 ? NumTeamsOpt : NumTeamsJson.value());
auto NumThreadsJson =
JsonKernelInfo->getAsObject()->getInteger("ThreadLimitClause");
uint32_t NumThreads =
(NumThreadsOpt > 0 ? NumThreadsOpt : NumThreadsJson.value());
uint32_t SharedMemorySize =
JsonKernelInfo->getAsObject()->getInteger("SharedMemorySize").value();
// TODO: Print a warning if number of teams/threads is explicitly set in the
// kernel info but overridden through command line options.
auto LoopTripCount =
JsonKernelInfo->getAsObject()->getInteger("LoopTripCount");
auto KernelFunc = JsonKernelInfo->getAsObject()->getString("Name");
std::string KernelName = KernelFunc.value().str();
SmallVector<void *> TgtArgs;
SmallVector<ptrdiff_t> TgtArgOffsets;
auto NumArgs = JsonKernelInfo->getAsObject()->getInteger("NumArgs");
auto *TgtArgsArray = JsonKernelInfo->getAsObject()->getArray("ArgPtrs");
for (auto It : *TgtArgsArray)
TgtArgs.push_back(reinterpret_cast<void *>(It.getAsInteger().value()));
auto *TgtArgOffsetsArray =
JsonKernelInfo->getAsObject()->getArray("ArgOffsets");
for (auto It : *TgtArgOffsetsArray)
TgtArgOffsets.push_back(static_cast<ptrdiff_t>(It.getAsInteger().value()));
void *VAllocAddr = reinterpret_cast<void *>(
JsonKernelInfo->getAsObject()->getInteger("VAllocAddr").value());
uint64_t VAllocSize =
JsonKernelInfo->getAsObject()->getInteger("VAllocSize").value();
auto Filepath = std::filesystem::path(InputFilename.getValue());
auto Directory = Filepath.parent_path();
Filepath.replace_extension("globals");
ErrorOr<std::unique_ptr<MemoryBuffer>> GlobalsMB =
MemoryBuffer::getFile(Filepath.string(), /*isText=*/false,
/*RequiresNullTerminator=*/false);
if (!GlobalsMB)
reportFatalUsageError("Error reading the globals file");
// On AMD for currently unknown reasons we cannot copy memory mapped data to
// device. This is a work-around.
uint8_t *RecordedGlobals = new uint8_t[GlobalsMB.get()->getBufferSize()];
std::memcpy(RecordedGlobals,
const_cast<char *>(GlobalsMB.get()->getBuffer().data()),
GlobalsMB.get()->getBufferSize());
void *BufferPtr = (void *)RecordedGlobals;
uint32_t NumGlobals = *((uint32_t *)(BufferPtr));
BufferPtr = utils::advancePtr(BufferPtr, sizeof(uint32_t));
llvm::SmallVector<llvm::offloading::EntryTy> OffloadEntries(
NumGlobals + 1, {0x0, 0x1, object::OffloadKind::OFK_OpenMP, 0, nullptr,
nullptr, 0, 0, nullptr});
OffloadEntries[0].SymbolName = const_cast<char *>(KernelName.c_str());
// Anything non-zero works to uniquely identify the kernel.
OffloadEntries[0].Address = (void *)0x1;
for (uint32_t I = 0; I < NumGlobals; ++I) {
auto &Global = OffloadEntries[I + 1];
// Use a unique identifier.
Global.Address = static_cast<char *>(OffloadEntries[0].Address) + I + 1;
uint32_t NameSize = *((uint32_t *)(BufferPtr));
BufferPtr = utils::advancePtr(BufferPtr, sizeof(uint32_t));
uint64_t Size = *((uint64_t *)(BufferPtr));
BufferPtr = utils::advancePtr(BufferPtr, sizeof(uint64_t));
Global.Size = Size;
Global.SymbolName = (char *)BufferPtr;
BufferPtr = utils::advancePtr(BufferPtr, NameSize);
Global.AuxAddr = BufferPtr;
BufferPtr = utils::advancePtr(BufferPtr, Size);
}
Filepath.replace_extension("image");
ErrorOr<std::unique_ptr<MemoryBuffer>> ImageMB =
MemoryBuffer::getFile(Filepath.string(), /*isText=*/false,
/*RequiresNullTerminator=*/false);
if (!ImageMB)
reportFatalUsageError("Error reading the kernel image file");
__tgt_device_image DeviceImage;
DeviceImage.ImageStart = const_cast<char *>(ImageMB.get()->getBufferStart());
DeviceImage.ImageEnd = const_cast<char *>(ImageMB.get()->getBufferEnd());
DeviceImage.EntriesBegin = &OffloadEntries[0];
DeviceImage.EntriesEnd = &OffloadEntries[OffloadEntries.size() - 1] + 1;
__tgt_bin_desc Desc;
Desc.NumDeviceImages = 1;
Desc.HostEntriesBegin = &OffloadEntries[0];
Desc.HostEntriesEnd = &OffloadEntries[OffloadEntries.size() - 1] + 1;
Desc.DeviceImages = &DeviceImage;
auto DeviceIdJson = JsonKernelInfo->getAsObject()->getInteger("DeviceId");
// TODO: Print warning if the user overrides the device id in the json file.
int32_t DeviceId = (DeviceIdOpt > -1 ? DeviceIdOpt : DeviceIdJson.value());
// TODO: do we need requires?
//__tgt_register_requires(/*Flags=*/1);
__tgt_register_lib(&Desc);
int Rc = __tgt_activate_record_replay(
DeviceId, VAllocSize, VAllocAddr, /*IsRecord=*/false, VerifyOpt,
/*EmitReport=*/false, Directory.c_str());
if (Rc != OMP_TGT_SUCCESS)
reportFatalUsageError("Error activating record replay");
Filepath.replace_extension("record_input");
ErrorOr<std::unique_ptr<MemoryBuffer>> DeviceMemoryMB =
MemoryBuffer::getFile(Filepath.string(), /*isText=*/false,
/*RequiresNullTerminator=*/false);
if (!DeviceMemoryMB)
reportFatalUsageError("Error reading the kernel record input file");
// On AMD for currently unknown reasons we cannot copy memory mapped data to
// device. This is a work-around.
uint8_t *RecordedData = new uint8_t[DeviceMemoryMB.get()->getBufferSize()];
std::memcpy(RecordedData,
const_cast<char *>(DeviceMemoryMB.get()->getBuffer().data()),
DeviceMemoryMB.get()->getBufferSize());
Rc = __tgt_target_kernel_replay(
/*Loc=*/nullptr, DeviceId, OffloadEntries[0].Address,
(char *)RecordedData, DeviceMemoryMB.get()->getBufferSize(),
NumGlobals ? &OffloadEntries[1] : nullptr, NumGlobals, TgtArgs.data(),
TgtArgOffsets.data(), NumArgs.value(), NumTeams, NumThreads,
SharedMemorySize, LoopTripCount.value());
if (Rc != OMP_TGT_SUCCESS)
reportFatalUsageError("Error replaying kernel");
int ErrorDetected = 0;
if (VerifyOpt) {
Filepath.replace_extension("record_output");
ErrorOr<std::unique_ptr<MemoryBuffer>> OriginalOutputMB =
MemoryBuffer::getFile(Filepath.string(),
/*isText=*/false,
/*RequiresNullTerminator=*/false);
if (!OriginalOutputMB)
reportFatalUsageError(
"Error reading the kernel record output file. Make sure "
"LIBOMPTARGET_RECORD_OUTPUT is set when recording");
Filepath.replace_extension("replay_output");
ErrorOr<std::unique_ptr<MemoryBuffer>> ReplayOutputMB =
MemoryBuffer::getFile(Filepath.string(),
/*isText=*/false,
/*RequiresNullTerminator=*/false);
if (!ReplayOutputMB)
reportFatalUsageError("Error reading the kernel replay output file");
StringRef OriginalOutput = OriginalOutputMB.get()->getBuffer();
StringRef ReplayOutput = ReplayOutputMB.get()->getBuffer();
if (OriginalOutput == ReplayOutput) {
outs() << "[llvm-omp-kernel-replay] Replay device memory verified!\n";
} else {
ErrorDetected = 1;
outs() << "[llvm-omp-kernel-replay] Replay device memory failed to "
"verify!\n";
}
}
delete[] RecordedData;
return ErrorDetected;
}