blob: 4fd7af098fa1f09c796909c5deb2fb01e69e60f1 [file] [log] [blame] [edit]
//===--- 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 <fstream>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#else
#include <dlfcn.h>
#include <sys/stat.h>
#include <unistd.h>
#endif // !_WIN32
#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();
L0ProgramTy &Program = L0ProgramTy::makeL0Program(Image);
auto AddrOrErr = Program.getSymbolDeviceAddr(GlobalName);
if (!AddrOrErr)
return AddrOrErr.takeError();
// Save the pointer to the symbol allowing nullptr.
DeviceGlobal.setPtr(*AddrOrErr);
return Plugin::success();
}
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();
}
Error L0ProgramBuilderTy::addModule(size_t Size, const uint8_t *Image,
const std::string_view CommonBuildOptions,
ze_module_format_t Format) {
const ze_module_constants_t SpecConstants =
LevelZeroPluginTy::getOptions().CommonSpecConstants.getModuleConstants();
auto &l0Device = getL0Device();
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;
CALL_ZE_RET_ERROR(zeModuleCreate, l0Device.getZeContext(),
l0Device.getZeDevice(), &ModuleDesc, &Module, &BuildLog);
// 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};
CALL_ZE_RET_ERROR(zeModuleGetProperties, Module, &Properties);
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;
CALL_ZE_RET_ERROR(zeModuleDynamicLink,
static_cast<uint32_t>(l0Device.getNumGlobalModules()),
l0Device.getGlobalModulesArray(), &LinkLog);
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);
}
}
// FIXME: move this to llvm/BinaryFormat/ELF.h and elf.h:
#define NT_INTEL_ONEOMP_OFFLOAD_VERSION 1
#define NT_INTEL_ONEOMP_OFFLOAD_IMAGE_COUNT 2
#define NT_INTEL_ONEOMP_OFFLOAD_IMAGE_AUX 3
bool isValidOneOmpImage(StringRef Image, uint64_t &MajorVer,
uint64_t &MinorVer) {
const auto MB = MemoryBuffer::getMemBuffer(Image,
/*BufferName=*/"",
/*RequiresNullTerminator=*/false);
auto ExpectedNewE =
ELFObjectFileBase::createELFObjectFile(MB->getMemBufferRef());
if (!ExpectedNewE) {
ODBG(OLDT_Module) << "Warning: unable to get ELF handle!";
return false;
}
bool Res = false;
auto processObjF = [&](const auto ELFObjF) {
if (!ELFObjF) {
ODBG(OLDT_Module) << "Warning: Unexpected ELF type!";
return false;
}
const auto &ELFF = ELFObjF->getELFFile();
auto Sections = ELFF.sections();
if (!Sections) {
ODBG(OLDT_Module) << "Warning: unable to get ELF sections!";
return false;
}
bool SeenOffloadSection = false;
for (auto Sec : *Sections) {
if (Sec.sh_type != ELF::SHT_NOTE)
continue;
Error Err = Plugin::success();
for (auto Note : ELFF.notes(Sec, Err)) {
if (Err) {
ODBG(OLDT_Module) << "Warning: unable to get ELF notes handle!";
return false;
}
if (Note.getName() != "INTELONEOMPOFFLOAD")
continue;
SeenOffloadSection = true;
if (Note.getType() != NT_INTEL_ONEOMP_OFFLOAD_VERSION)
continue;
std::string DescStr(std::move(Note.getDescAsStringRef(4).str()));
const auto DelimPos = DescStr.find('.');
if (DelimPos == std::string::npos) {
// The version has to look like "Major#.Minor#".
ODBG(OLDT_Module)
<< "Invalid NT_INTEL_ONEOMP_OFFLOAD_VERSION: '" << DescStr << "'";
return false;
}
const std::string MajorVerStr = DescStr.substr(0, DelimPos);
DescStr.erase(0, DelimPos + 1);
MajorVer = std::stoull(MajorVerStr);
MinorVer = std::stoull(DescStr);
return (MajorVer == 1 && MinorVer == 0);
}
}
return SeenOffloadSection;
};
if (const auto *O = dyn_cast<ELF64LEObjectFile>((*ExpectedNewE).get())) {
Res = processObjF(O);
} else if (const auto *O =
dyn_cast<ELF32LEObjectFile>((*ExpectedNewE).get())) {
Res = processObjF(O);
} else {
assert(false && "Unexpected ELF format");
}
return Res;
}
Error L0ProgramBuilderTy::buildModules(const std::string_view BuildOptions) {
auto &l0Device = getL0Device();
auto Image = getMemoryBuffer();
if (identify_magic(Image.getBuffer()) == file_magic::spirv_object) {
// Handle legacy plain SPIR-V image.
const uint8_t *ImgBegin =
reinterpret_cast<const uint8_t *>(Image.getBufferStart());
return addModule(Image.getBufferSize(), ImgBegin, BuildOptions,
ZE_MODULE_FORMAT_IL_SPIRV);
}
uint64_t MajorVer, MinorVer;
if (!isValidOneOmpImage(Image.getBuffer(), MajorVer, MinorVer)) {
ODBG(OLDT_Module) << "Warning: image is not a valid oneAPI OpenMP image.";
return Plugin::error(ErrorCode::UNKNOWN, "Invalid oneAPI OpenMP image");
}
// Iterate over the images and pick the first one that fits.
uint64_t ImageCount = 0;
struct V1ImageInfo {
// 0 - native, 1 - SPIR-V.
uint64_t Format = std::numeric_limits<uint64_t>::max();
std::string CompileOpts;
std::string LinkOpts;
// We may have multiple sections created from split-kernel mode.
std::vector<const uint8_t *> PartBegin;
std::vector<uint64_t> PartSize;
V1ImageInfo(uint64_t Format, std::string CompileOpts, std::string LinkOpts)
: Format(Format), CompileOpts(std::move(CompileOpts)),
LinkOpts(std::move(LinkOpts)) {}
};
std::unordered_map<uint64_t, V1ImageInfo> AuxInfo;
auto ExpectedNewE = ELFObjectFileBase::createELFObjectFile(Image);
assert(ExpectedNewE &&
"isValidOneOmpImage() returns true for invalid ELF image");
auto processELF = [&](auto *EObj) {
assert(EObj && "isValidOneOmpImage() returns true for invalid ELF image.");
const auto &E = EObj->getELFFile();
// Collect auxiliary information.
uint64_t MaxImageIdx = 0;
auto Sections = E.sections();
assert(Sections && "isValidOneOmpImage() returns true for ELF image with "
"invalid sections.");
for (auto Sec : *Sections) {
if (Sec.sh_type != ELF::SHT_NOTE)
continue;
Error Err = Plugin::success();
for (auto Note : E.notes(Sec, Err)) {
assert(!Err && "isValidOneOmpImage() returns true for ELF image with "
"invalid notes.");
if (Note.getName().str() != "INTELONEOMPOFFLOAD")
continue;
const uint64_t Type = Note.getType();
auto DescStrRef = Note.getDescAsStringRef(4);
switch (Type) {
default:
ODBG(OLDT_Module) << "Warning: unrecognized INTELONEOMPOFFLOAD note.";
break;
case NT_INTEL_ONEOMP_OFFLOAD_VERSION:
break;
case NT_INTEL_ONEOMP_OFFLOAD_IMAGE_COUNT:
if (DescStrRef.getAsInteger(10, ImageCount)) {
ODBG(OLDT_Module) << "Warning: invalid "
<< "NT_INTEL_ONEOMP_OFFLOAD_IMAGE_COUNT: '"
<< DescStrRef.str() << "'";
ImageCount = 0;
}
break;
case NT_INTEL_ONEOMP_OFFLOAD_IMAGE_AUX:
llvm::SmallVector<llvm::StringRef, 4> Parts;
DescStrRef.split(Parts, '\0', /* MaxSplit = */ 4,
/* KeepEmpty = */ true);
// Ignore records with less than 4 strings.
if (Parts.size() != 4) {
ODBG(OLDT_Module) << "Warning: short "
<< "NT_INTEL_ONEOMP_OFFLOAD_IMAGE_AUX "
<< "record is ignored.";
continue;
}
uint64_t Idx = 0;
if (Parts[0].getAsInteger(10, Idx)) {
ODBG(OLDT_Module) << "Warning: ignoring auxiliary information "
<< "(invalid index '" << Parts[0].str() << "').";
continue;
}
MaxImageIdx = (std::max)(MaxImageIdx, Idx);
if (AuxInfo.find(Idx) != AuxInfo.end()) {
ODBG(OLDT_Module) << "Warning: duplicate auxiliary information for "
<< "image " << Idx << " is ignored.";
continue;
}
uint64_t Part1Id;
if (Parts[1].getAsInteger(10, Part1Id)) {
ODBG(OLDT_Module)
<< "Warning: ignoring auxiliary information "
<< "(invalid part id '" << Parts[1].str() << "').";
continue;
}
AuxInfo.emplace(
std::piecewise_construct, std::forward_as_tuple(Idx),
std::forward_as_tuple(Part1Id, Parts[2].str(), Parts[3].str()));
// Image pointer and size will be initialized later.
}
}
}
if (MaxImageIdx >= ImageCount)
ODBG(OLDT_Module) << "Warning: invalid image index found in auxiliary "
<< "information.";
for (auto Sec : *Sections) {
const char *Prefix = "__openmp_offload_spirv_";
auto ExpectedSectionName = E.getSectionName(Sec);
assert(ExpectedSectionName && "isValidOneOmpImage() returns true for ELF "
"image with invalid section names");
auto &SectionNameRef = *ExpectedSectionName;
if (!SectionNameRef.consume_front(Prefix))
continue;
// Expected section name in split-kernel mode with the following pattern:
// __openmp_offload_spirv_<image_id>_<part_id>
auto Parts = SectionNameRef.split('_');
// It seems that we do not need part ID as long as they are ordered
// in the image and we keep the ordering in the runtime.
SectionNameRef = Parts.first;
if (Parts.second.empty()) {
ODBG(OLDT_Module) << "Found a single section in the image";
} else {
ODBG(OLDT_Module) << "Found a split section in the image";
}
uint64_t Idx = 0;
if (SectionNameRef.getAsInteger(10, Idx)) {
ODBG(OLDT_Module) << "Warning: ignoring image section (invalid index '"
<< SectionNameRef.str() << "').";
continue;
}
if (Idx >= ImageCount) {
ODBG(OLDT_Module) << "Warning: ignoring image section (index " << Idx
<< " is out of range).";
continue;
}
auto AuxInfoIt = AuxInfo.find(Idx);
if (AuxInfoIt == AuxInfo.end()) {
ODBG(OLDT_Module) << "Warning: ignoring image section (no aux info).";
continue;
}
auto Contents = E.getSectionContents(Sec);
assert(Contents);
AuxInfoIt->second.PartBegin.push_back((*Contents).data());
AuxInfoIt->second.PartSize.push_back(Sec.sh_size);
}
};
if (auto *O = dyn_cast<ELF64LEObjectFile>((*ExpectedNewE).get())) {
processELF(O);
} else if (auto *O = dyn_cast<ELF32LEObjectFile>((*ExpectedNewE).get())) {
processELF(O);
} else {
assert(false && "Unexpected ELF format");
}
for (uint64_t Idx = 0; Idx < ImageCount; ++Idx) {
const auto It = AuxInfo.find(Idx);
if (It == AuxInfo.end()) {
ODBG(OLDT_Module) << "Warning: image " << Idx
<< " without auxiliary information is ingored.";
continue;
}
const auto NumParts = It->second.PartBegin.size();
// Split-kernel is not supported in SPIRV format.
if (NumParts > 1 && It->second.Format != 0) {
ODBG(OLDT_Module) << "Warning: split-kernel images are not supported in "
<< "SPIRV format";
continue;
}
// Skip unknown image format.
if (It->second.Format != 0 && It->second.Format != 1) {
ODBG(OLDT_Module) << "Warning: image " << Idx << " is ignored due to "
<< "unknown format.";
continue;
}
const bool IsBinary = (It->second.Format == 0);
const auto ModuleFormat =
IsBinary ? ZE_MODULE_FORMAT_NATIVE : ZE_MODULE_FORMAT_IL_SPIRV;
std::string Options(BuildOptions);
{
Options += " " + It->second.CompileOpts + " " + It->second.LinkOpts;
replaceDriverOptsWithBackendOpts(l0Device, Options);
}
for (size_t I = 0; I < NumParts; I++) {
const unsigned char *ImgBegin =
reinterpret_cast<const unsigned char *>(It->second.PartBegin[I]);
size_t ImgSize = It->second.PartSize[I];
ODBG(OLDT_Module) << "Creating module from "
<< (IsBinary ? "Binary" : "SPIR-V") << " image part #"
<< Idx << "-" << I << ".";
if (auto Err = addModule(ImgSize, ImgBegin, Options, ModuleFormat))
return Err;
}
ODBG(OLDT_Module) << "Created module from image #" << Idx << ".";
if (RequiresModuleLink) {
ODBG(OLDT_Module) << "Linking modules after adding image #" << Idx << ".";
if (auto Err = linkModules())
return Err;
}
return Plugin::success();
}
return Plugin::error(ErrorCode::UNKNOWN, "Failed to create program modules.");
}
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");
}
Expected<void *> L0ProgramTy::getSymbolDeviceAddr(const char *CName) const {
ODBG(OLDT_Module) << "Looking up OpenMP global variable '" << CName << "'.";
if (!GlobalModule || !CName)
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"Invalid arguments to getSymbolDeviceAddr");
size_t SizeDummy = 0;
void *DevicePtr = nullptr;
ze_result_t RC;
for (auto Module : Modules) {
CALL_ZE(RC, zeModuleGetGlobalPointer, Module, CName, &SizeDummy,
&DevicePtr);
if (RC == ZE_RESULT_SUCCESS && DevicePtr)
return DevicePtr;
CALL_ZE(RC, zeModuleGetFunctionPointer, Module, CName, &DevicePtr);
if (RC == ZE_RESULT_SUCCESS && DevicePtr)
return DevicePtr;
}
return Plugin::error(ErrorCode::INVALID_ARGUMENT,
"Symbol '%s' not found on device", CName);
}
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().enqueueMemCopy(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().enqueueMemCopy(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