blob: 36a7924af4600c4ee3db163e13aba1c2232c5d62 [file]
//===--- Level Zero Target RTL 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
//
//===----------------------------------------------------------------------===//
//
// Level Zero Program abstraction.
//
//===----------------------------------------------------------------------===//
#include "L0Plugin.h"
#include "L0Program.h"
namespace llvm::omp::target::plugin {
Error L0GlobalHandlerTy::getGlobalMetadataFromDevice(GenericDeviceTy &Device,
DeviceImageTy &Image,
GlobalTy &DeviceGlobal) {
const char *GlobalName = DeviceGlobal.getName().data();
size_t SymbolSize = 0;
void *SymbolAddr = nullptr;
L0ProgramTy &Program = L0ProgramTy::makeL0Program(Image);
if (auto Err =
Program.getSymbolMetadata(GlobalName, &SymbolAddr, &SymbolSize))
return Err;
// Save the pointer to the symbol allowing nullptr.
DeviceGlobal.setPtr(SymbolAddr);
DeviceGlobal.setSize(SymbolSize);
return Plugin::success();
}
bool L0GlobalHandlerTy::isExportedSymbol(uint32_t Flags) {
// Images returned by the Level Zero runtime do not correctly expose kernel
// functions as global symbols. Bypass the normal ELF handling.here.
uint32_t Ignored = SymbolRef::SF_Undefined | SymbolRef::SF_Hidden |
SymbolRef::SF_FormatSpecific;
return !(Flags & Ignored);
}
inline L0DeviceTy &L0ProgramTy::getL0Device() const {
return L0DeviceTy::makeL0Device(getDevice());
}
Error L0ProgramTy::deinit() {
for (auto *Kernel : Kernels) {
if (auto Err = Kernel->deinit())
return Err;
getL0Device().getPlugin().free(Kernel);
}
for (auto Module : Modules) {
CALL_ZE_RET_ERROR(zeModuleDestroy, Module);
}
return Plugin::success();
}
/// Print the contents of a module build or link log. Unless \p FullLog is set,
/// only the first few lines are printed.
static void printBuildLog(ze_module_build_log_handle_t Log, const char *Title,
bool FullLog) {
constexpr size_t MaxLines = 10;
MESSAGE("%s:", Title);
size_t LogSize = 0;
ze_result_t RC;
CALL_ZE(RC, zeModuleBuildLogGetString, Log, &LogSize, /*LogString=*/nullptr);
if (RC != ZE_RESULT_SUCCESS) {
MESSAGE0(" <failed to get build log>");
return;
}
if (LogSize <= 1) {
MESSAGE0(" <empty>");
return;
}
std::string LogString(LogSize, '\0');
CALL_ZE(RC, zeModuleBuildLogGetString, Log, &LogSize, LogString.data());
if (RC != ZE_RESULT_SUCCESS) {
MESSAGE0(" <failed to get build log>");
return;
}
StringRef Rest(LogString.c_str());
for (size_t NumLines = 0; !Rest.empty(); ++NumLines) {
if (!FullLog && NumLines >= MaxLines) {
MESSAGE0(" (suppressed remaining log)");
break;
}
StringRef Line;
std::tie(Line, Rest) = Rest.split('\n');
MESSAGE(" '%.*s'", static_cast<int>(Line.size()), Line.data());
}
}
Error L0ProgramBuilderTy::addModule(size_t Size, const uint8_t *Image,
const std::string_view CommonBuildOptions,
ze_module_format_t Format) {
auto &L0Device = getL0Device();
const ze_module_constants_t SpecConstants =
L0Device.getPlugin()
.getOptions()
.CommonSpecConstants.getModuleConstants();
std::string BuildOptions(CommonBuildOptions);
bool IsLibModule =
BuildOptions.find("-library-compilation") != std::string::npos;
ze_module_desc_t ModuleDesc{};
ModuleDesc.stype = ZE_STRUCTURE_TYPE_MODULE_DESC;
ModuleDesc.pNext = nullptr;
ModuleDesc.format = Format;
ze_module_handle_t Module = nullptr;
ze_module_build_log_handle_t BuildLog = nullptr;
// Build a single module from a single image.
ModuleDesc.inputSize = Size;
ModuleDesc.pInputModule = Image;
ModuleDesc.pBuildFlags = BuildOptions.c_str();
ModuleDesc.pConstants = &SpecConstants;
ze_result_t RC;
CALL_ZE(RC, zeModuleCreate, getZeContext(), L0Device.getZeDevice(),
&ModuleDesc, &Module, &BuildLog);
if (BuildLog) {
const bool BuildFailed = RC != ZE_RESULT_SUCCESS;
const bool ShowBuildLog =
L0Device.getPlugin().getOptions().Flags.ShowBuildLog;
// Only show the log of library modules (-library-compilation) if their
// build failed.
if (BuildFailed || (ShowBuildLog && !IsLibModule)) {
if (BuildFailed)
MESSAGE0("Error: module creation failed");
printBuildLog(BuildLog, "Target build log", ShowBuildLog);
}
zeModuleBuildLogDestroy(BuildLog);
}
if (RC != ZE_RESULT_SUCCESS) {
// zeModuleCreate compiles/loads the provided image, so a build failure here
// means the image itself could not be loaded for this device (e.g. a
// truncated or malformed binary) rather than a generic JIT failure of an
// otherwise valid program. Report it as INVALID_BINARY in that case (as
// opposed to the default mapping of ZE_RESULT_ERROR_MODULE_BUILD_FAILURE
// to ErrorCode::COMPILE_FAILURE).
const auto ErrCode = RC == ZE_RESULT_ERROR_MODULE_BUILD_FAILURE
? ErrorCode::INVALID_BINARY
: getOffloadErrorCode(RC);
return Plugin::error(ErrCode, "zeModuleCreate failed with error %d, %s", RC,
getZeErrorName(RC));
}
// Check if module link is required. We do not need this check for
// library module.
if (!RequiresModuleLink && !IsLibModule) {
ze_module_properties_t Properties = {ZE_STRUCTURE_TYPE_MODULE_PROPERTIES,
nullptr, 0};
ze_result_t RC;
CALL_ZE(RC, zeModuleGetProperties, Module, &Properties);
if (RC == ZE_RESULT_SUCCESS)
RequiresModuleLink = Properties.flags & ZE_MODULE_PROPERTY_FLAG_IMPORTS;
}
// For now, assume the first module contains libraries, globals.
if (Modules.empty())
GlobalModule = Module;
Modules.push_back(Module);
L0Device.addGlobalModule(Module);
return Plugin::success();
}
Error L0ProgramBuilderTy::linkModules() {
auto &L0Device = getL0Device();
if (!RequiresModuleLink) {
ODBG(OLDT_Module) << "Module link is not required";
return Plugin::success();
}
if (Modules.empty())
return Plugin::error(ErrorCode::UNKNOWN,
"Invalid number of modules when linking modules");
ze_module_build_log_handle_t LinkLog = nullptr;
ze_result_t RC;
CALL_ZE(RC, zeModuleDynamicLink,
static_cast<uint32_t>(L0Device.getNumGlobalModules()),
L0Device.getGlobalModulesArray(), &LinkLog);
if (LinkLog) {
const bool LinkFailed = RC != ZE_RESULT_SUCCESS;
const bool ShowBuildLog =
L0Device.getPlugin().getOptions().Flags.ShowBuildLog;
if (LinkFailed || ShowBuildLog) {
if (LinkFailed)
MESSAGE0("Error: module link failed");
printBuildLog(LinkLog, "Target link log", ShowBuildLog);
}
zeModuleBuildLogDestroy(LinkLog);
}
if (RC != ZE_RESULT_SUCCESS)
return Plugin::error(getOffloadErrorCode(RC),
"zeModuleDynamicLink failed with error %d, %s", RC,
getZeErrorName(RC));
return Plugin::success();
}
static void replaceDriverOptsWithBackendOpts(const L0DeviceTy &Device,
std::string &Options) {
// Options that need to be replaced with backend-specific options
static const struct {
std::string Option;
std::string BackendOption;
} OptionTranslationTable[] = {
{"-ftarget-compile-fast",
"-igc_opts 'PartitionUnit=1,SubroutineThreshold=50000'"},
{"-foffload-fp32-prec-div", "-ze-fp32-correctly-rounded-divide-sqrt"},
{"-foffload-fp32-prec-sqrt", "-ze-fp32-correctly-rounded-divide-sqrt"},
};
for (const auto &OptPair : OptionTranslationTable) {
const size_t Pos = Options.find(OptPair.Option);
if (Pos != std::string::npos)
Options.replace(Pos, OptPair.Option.length(), OptPair.BackendOption);
}
}
Error L0ProgramBuilderTy::buildModules(const std::string_view BuildOptions) {
auto &L0Device = getL0Device();
auto Image = getMemoryBuffer();
// Check if image is an inner OffloadBinary (nested format)
if (identify_magic(Image.getBuffer()) == file_magic::offload_binary) {
ODBG(OLDT_Module) << "Processing nested OffloadBinary image";
// Parse inner OffloadBinary
auto InnerBinariesOrErr = llvm::object::OffloadBinary::create(Image);
if (!InnerBinariesOrErr)
return Plugin::error(
ErrorCode::INVALID_BINARY, "Failed to parse inner OffloadBinary: %s",
llvm::toString(InnerBinariesOrErr.takeError()).c_str());
auto &InnerBinaries = *InnerBinariesOrErr;
// Should contain exactly one image
if (InnerBinaries.size() != 1)
return Plugin::error(ErrorCode::INVALID_BINARY,
"Expected single inner OffloadBinary entry, got %zu",
InnerBinaries.size());
const llvm::object::OffloadBinary *InnerBinary = InnerBinaries[0].get();
llvm::object::ImageKind ImageKind = InnerBinary->getImageKind();
// Extract image data from inner binary
llvm::StringRef ImageData = InnerBinary->getImage();
const uint8_t *ImgBegin =
reinterpret_cast<const uint8_t *>(ImageData.data());
// Read metadata from inner binary
llvm::StringRef Version = InnerBinary->getString("version");
llvm::StringRef CompileOpts = InnerBinary->getString("compile-opts");
llvm::StringRef LinkOpts = InnerBinary->getString("link-opts");
ODBG(OLDT_Module) << "Inner OffloadBinary metadata: version=" << Version
<< ", kind=" << ImageKind;
// Build options string combining BuildOptions with compile/link opts
std::string Options(BuildOptions);
if (!CompileOpts.empty() || !LinkOpts.empty()) {
if (!CompileOpts.empty())
Options += " " + CompileOpts.str();
if (!LinkOpts.empty())
Options += " " + LinkOpts.str();
replaceDriverOptsWithBackendOpts(L0Device, Options);
ODBG(OLDT_Module) << "Using compile options: " << CompileOpts
<< ", link options: " << LinkOpts;
}
// Determine module format based on image kind
ze_module_format_t ModuleFormat;
if (ImageKind == llvm::object::IMG_SPIRV) {
// SPIR-V intermediate language
ODBG(OLDT_Module) << "Loading SPIR-V module";
ModuleFormat = ZE_MODULE_FORMAT_IL_SPIRV;
} else if (ImageKind == llvm::object::IMG_Object) {
// Native binary format
ODBG(OLDT_Module) << "Loading native binary module";
ModuleFormat = ZE_MODULE_FORMAT_NATIVE;
} else {
return Plugin::error(ErrorCode::INVALID_BINARY,
"Unsupported image kind %d in inner OffloadBinary",
static_cast<int>(ImageKind));
}
// Load module into Level Zero
auto Err = addModule(ImageData.size(), ImgBegin, Options, ModuleFormat);
if (Err)
return Err;
if (RequiresModuleLink) {
ODBG(OLDT_Module) << "Linking modules after adding OffloadBinary image";
if (auto Err = linkModules())
return Err;
}
return Plugin::success();
}
if (identify_magic(Image.getBuffer()) == file_magic::spirv_object) {
ODBG(OLDT_Module) << "Processing raw SPIR-V image";
const uint8_t *ImgBegin =
reinterpret_cast<const uint8_t *>(Image.getBufferStart());
auto Err = addModule(Image.getBufferSize(), ImgBegin, BuildOptions,
ZE_MODULE_FORMAT_IL_SPIRV);
if (Err)
return Err;
if (RequiresModuleLink) {
ODBG(OLDT_Module) << "Linking modules after adding SPIR-V image";
if (auto Err = linkModules())
return Err;
}
return Plugin::success();
}
return Plugin::error(ErrorCode::INVALID_BINARY,
"Unsupported image format for L0 plugin");
}
Expected<std::unique_ptr<MemoryBuffer>> L0ProgramBuilderTy::getELF() {
assert(GlobalModule != nullptr && "GlobalModule is null");
size_t Size = 0;
CALL_ZE_RET_ERROR(zeModuleGetNativeBinary, GlobalModule, &Size, nullptr);
std::vector<uint8_t> ELFData(Size);
CALL_ZE_RET_ERROR(zeModuleGetNativeBinary, GlobalModule, &Size,
ELFData.data());
return MemoryBuffer::getMemBufferCopy(
StringRef(reinterpret_cast<const char *>(ELFData.data()), Size),
/*BufferName=*/"L0Program ELF");
}
Error L0ProgramTy::getSymbolMetadata(const char *Name, void **AddrPtr,
size_t *SizePtr) const {
if (!Name || !AddrPtr || !SizePtr)
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"Invalid arguments to getSymbolDeviceAddr");
size_t SymbolSize = 0;
void *SymbolAddr = nullptr;
ze_result_t RC;
for (auto Module : Modules) {
CALL_ZE(RC, zeModuleGetGlobalPointer, Module, Name, &SymbolSize,
&SymbolAddr);
if (RC == ZE_RESULT_SUCCESS && SymbolAddr) {
*AddrPtr = SymbolAddr;
*SizePtr = SymbolSize;
return Plugin::success();
}
CALL_ZE(RC, zeModuleGetFunctionPointer, Module, Name, &SymbolAddr);
if (RC == ZE_RESULT_SUCCESS && SymbolAddr) {
*AddrPtr = SymbolAddr;
*SizePtr = 0;
return Plugin::success();
}
}
return Plugin::error(ErrorCode::NOT_FOUND, "symbol '%s' not found on device",
Name);
}
Error L0ProgramTy::readGlobalVariable(const char *Name, size_t Size,
void *HostPtr) {
size_t SizeDummy = 0;
void *DevicePtr = nullptr;
ze_result_t RC;
CALL_ZE(RC, zeModuleGetGlobalPointer, GlobalModule, Name, &SizeDummy,
&DevicePtr);
if (RC != ZE_RESULT_SUCCESS || !DevicePtr) {
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"Cannot read from device global variable %s", Name);
}
return getL0Device().enqueueMemCopyAndSync(HostPtr, DevicePtr, Size);
}
Error L0ProgramTy::writeGlobalVariable(const char *Name, size_t Size,
const void *HostPtr) {
size_t SizeDummy = 0;
void *DevicePtr = nullptr;
ze_result_t RC;
CALL_ZE(RC, zeModuleGetGlobalPointer, GlobalModule, Name, &SizeDummy,
&DevicePtr);
if (RC != ZE_RESULT_SUCCESS || !DevicePtr) {
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"Cannot write to device global variable %s", Name);
}
return getL0Device().enqueueMemCopyAndSync(DevicePtr, HostPtr, Size);
}
Error L0ProgramTy::loadModuleKernels() {
// We need to build kernels here before filling the offload entries since we
// don't know which module contains a specific kernel with a name.
for (auto Module : Modules) {
uint32_t Count = 0;
CALL_ZE_RET_ERROR(zeModuleGetKernelNames, Module, &Count,
/*Names=*/nullptr);
if (Count == 0)
continue;
llvm::SmallVector<const char *> Names(Count);
CALL_ZE_RET_ERROR(zeModuleGetKernelNames, Module, &Count, Names.data());
for (auto *Name : Names) {
KernelsToModuleMap.emplace(Name, Module);
}
}
return Plugin::success();
}
} // namespace llvm::omp::target::plugin