mirror of
https://github.com/intel/compute-runtime.git
synced 2025-12-20 00:24:58 +08:00
This change prevents embedding identical SPIR-V section for each target requested in fatbinary build. Instead of duplicating SPIR-V, a new file called 'generic_ir' is added to AR archive. It contains SPIR-V, which was used to build fatbinary. Build fallback in runtime has been also adjusted - if 'generic_ir' file is defined in fatbinary and there is no matching binary, then this generic SPIR-V is used to rebuild for the requested target. Additionally, MockOclocArgumentHelper::loadDataFromFile() was adjusted to ensure null-termination of returned strings. This change also removes possible undefined behavior, which was related to reading names of files from AR archive. Previously, if filename was shorter than requested target name, we tried to read more memory than allowed. Related-To: NEO-6490 Signed-off-by: Patryk Wrobel <patryk.wrobel@intel.com>
409 lines
15 KiB
C++
409 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2022 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
*/
|
|
|
|
#include "offline_linker.h"
|
|
|
|
#include "shared/offline_compiler/source/ocloc_arg_helper.h"
|
|
#include "shared/offline_compiler/source/ocloc_error_code.h"
|
|
#include "shared/source/compiler_interface/intermediate_representations.h"
|
|
#include "shared/source/device_binary_format/elf/elf_encoder.h"
|
|
#include "shared/source/device_binary_format/elf/ocl_elf.h"
|
|
#include "shared/source/helpers/compiler_hw_info_config.h"
|
|
#include "shared/source/helpers/string.h"
|
|
#include "shared/source/os_interface/os_inc_base.h"
|
|
#include "shared/source/os_interface/os_library.h"
|
|
|
|
#include "cif/common/cif_main.h"
|
|
#include "ocl_igc_interface/platform_helper.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
|
|
namespace NEO {
|
|
|
|
CIF::CIFMain *createMainNoSanitize(CIF::CreateCIFMainFunc_t createFunc);
|
|
|
|
std::unique_ptr<OfflineLinker> OfflineLinker::create(size_t argsCount, const std::vector<std::string> &args, int &errorCode, OclocArgHelper *argHelper) {
|
|
std::unique_ptr<OfflineLinker> linker{new OfflineLinker{argHelper}};
|
|
errorCode = linker->initialize(argsCount, args);
|
|
|
|
return linker;
|
|
}
|
|
|
|
OfflineLinker::OfflineLinker(OclocArgHelper *argHelper)
|
|
: argHelper{argHelper}, operationMode{OperationMode::SKIP_EXECUTION}, outputFilename{"linker_output"}, outputFormat{IGC::CodeType::llvmBc} {}
|
|
|
|
OfflineLinker::~OfflineLinker() = default;
|
|
|
|
int OfflineLinker::initialize(size_t argsCount, const std::vector<std::string> &args) {
|
|
const auto parsingResult{parseCommand(argsCount, args)};
|
|
if (parsingResult != OclocErrorCode::SUCCESS) {
|
|
return parsingResult;
|
|
}
|
|
|
|
// If a user requested help, then stop here.
|
|
if (operationMode == OperationMode::SHOW_HELP) {
|
|
return OclocErrorCode::SUCCESS;
|
|
}
|
|
|
|
const auto verificationResult{verifyLinkerCommand()};
|
|
if (verificationResult != OclocErrorCode::SUCCESS) {
|
|
return verificationResult;
|
|
}
|
|
|
|
const auto loadingResult{loadInputFilesContent()};
|
|
if (loadingResult != OclocErrorCode::SUCCESS) {
|
|
return loadingResult;
|
|
}
|
|
|
|
const auto hwInfoInitializationResult{initHardwareInfo()};
|
|
if (hwInfoInitializationResult != OclocErrorCode::SUCCESS) {
|
|
return hwInfoInitializationResult;
|
|
}
|
|
|
|
const auto igcPreparationResult{prepareIgc()};
|
|
if (igcPreparationResult != OclocErrorCode::SUCCESS) {
|
|
return igcPreparationResult;
|
|
}
|
|
|
|
operationMode = OperationMode::LINK_FILES;
|
|
return OclocErrorCode::SUCCESS;
|
|
}
|
|
|
|
int OfflineLinker::parseCommand(size_t argsCount, const std::vector<std::string> &args) {
|
|
if (argsCount < 2u) {
|
|
operationMode = OperationMode::SHOW_HELP;
|
|
return OclocErrorCode::INVALID_COMMAND_LINE;
|
|
}
|
|
|
|
for (size_t argIndex = 1u; argIndex < argsCount; ++argIndex) {
|
|
const auto ¤tArg{args[argIndex]};
|
|
const auto hasMoreArgs{argIndex + 1 < argsCount};
|
|
|
|
if (currentArg == "link") {
|
|
continue;
|
|
} else if ((currentArg == "-file") && hasMoreArgs) {
|
|
inputFilenames.push_back(args[argIndex + 1]);
|
|
++argIndex;
|
|
} else if (currentArg == "-out" && hasMoreArgs) {
|
|
outputFilename = args[argIndex + 1];
|
|
++argIndex;
|
|
} else if ((currentArg == "-out_format") && hasMoreArgs) {
|
|
outputFormat = parseOutputFormat(args[argIndex + 1]);
|
|
++argIndex;
|
|
} else if ((currentArg == "-options") && hasMoreArgs) {
|
|
options = args[argIndex + 1];
|
|
++argIndex;
|
|
} else if ((currentArg == "-internal_options") && hasMoreArgs) {
|
|
internalOptions = args[argIndex + 1];
|
|
++argIndex;
|
|
} else if (currentArg == "--help") {
|
|
operationMode = OperationMode::SHOW_HELP;
|
|
return OclocErrorCode::SUCCESS;
|
|
} else {
|
|
argHelper->printf("Invalid option (arg %zd): %s\n", argIndex, currentArg.c_str());
|
|
return OclocErrorCode::INVALID_COMMAND_LINE;
|
|
}
|
|
}
|
|
|
|
return OclocErrorCode::SUCCESS;
|
|
}
|
|
|
|
IGC::CodeType::CodeType_t OfflineLinker::parseOutputFormat(const std::string &outputFormatName) {
|
|
constexpr static std::array supportedFormatNames = {
|
|
std::pair{"ELF", IGC::CodeType::elf},
|
|
std::pair{"LLVM_BC", IGC::CodeType::llvmBc}};
|
|
|
|
for (const auto &[name, format] : supportedFormatNames) {
|
|
if (name == outputFormatName) {
|
|
return format;
|
|
}
|
|
}
|
|
|
|
return IGC::CodeType::invalid;
|
|
}
|
|
|
|
int OfflineLinker::verifyLinkerCommand() {
|
|
if (inputFilenames.empty()) {
|
|
argHelper->printf("Error: Input name is missing! At least one input file is required!\n");
|
|
return OclocErrorCode::INVALID_COMMAND_LINE;
|
|
}
|
|
|
|
for (const auto &filename : inputFilenames) {
|
|
if (filename.empty()) {
|
|
argHelper->printf("Error: Empty filename cannot be used!\n");
|
|
return OclocErrorCode::INVALID_COMMAND_LINE;
|
|
}
|
|
|
|
if (!argHelper->fileExists(filename)) {
|
|
argHelper->printf("Error: Input file %s missing.\n", filename.c_str());
|
|
return OclocErrorCode::INVALID_FILE;
|
|
}
|
|
}
|
|
|
|
if (outputFormat == IGC::CodeType::invalid) {
|
|
argHelper->printf("Error: Invalid output type!\n");
|
|
return OclocErrorCode::INVALID_COMMAND_LINE;
|
|
}
|
|
|
|
return OclocErrorCode::SUCCESS;
|
|
}
|
|
|
|
int OfflineLinker::loadInputFilesContent() {
|
|
std::unique_ptr<char[]> bytes{};
|
|
size_t size{};
|
|
IGC::CodeType::CodeType_t codeType{};
|
|
|
|
inputFilesContent.reserve(inputFilenames.size());
|
|
|
|
for (const auto &filename : inputFilenames) {
|
|
size = 0;
|
|
bytes = argHelper->loadDataFromFile(filename, size);
|
|
if (size == 0) {
|
|
argHelper->printf("Error: Cannot read input file: %s\n", filename.c_str());
|
|
return OclocErrorCode::INVALID_FILE;
|
|
}
|
|
|
|
codeType = detectCodeType(bytes.get(), size);
|
|
if (codeType == IGC::CodeType::invalid) {
|
|
argHelper->printf("Error: Unsupported format of input file: %s\n", filename.c_str());
|
|
return OclocErrorCode::INVALID_PROGRAM;
|
|
}
|
|
|
|
inputFilesContent.emplace_back(std::move(bytes), size, codeType);
|
|
}
|
|
|
|
return OclocErrorCode::SUCCESS;
|
|
}
|
|
|
|
IGC::CodeType::CodeType_t OfflineLinker::detectCodeType(char *bytes, size_t size) const {
|
|
const auto bytesArray = ArrayRef<const uint8_t>::fromAny(bytes, size);
|
|
if (isSpirVBitcode(bytesArray)) {
|
|
return IGC::CodeType::spirV;
|
|
}
|
|
|
|
if (isLlvmBitcode(bytesArray)) {
|
|
return IGC::CodeType::llvmBc;
|
|
}
|
|
|
|
return IGC::CodeType::invalid;
|
|
}
|
|
|
|
int OfflineLinker::initHardwareInfo() {
|
|
// In spite of linking input files to intermediate representation instead of native binaries,
|
|
// we have to initialize hardware info. Without that, initialization of IGC fails.
|
|
// Therefore, we select the first valid hardware info entry and use it.
|
|
const auto hwInfoTable{getHardwareInfoTable()};
|
|
for (auto productId = 0u; productId < hwInfoTable.size(); ++productId) {
|
|
if (hwInfoTable[productId]) {
|
|
hwInfo = *hwInfoTable[productId];
|
|
|
|
const auto hwInfoConfig = defaultHardwareInfoConfigTable[hwInfo.platform.eProductFamily];
|
|
setHwInfoValuesFromConfig(hwInfoConfig, hwInfo);
|
|
hardwareInfoSetup[hwInfo.platform.eProductFamily](&hwInfo, true, hwInfoConfig);
|
|
|
|
return OclocErrorCode::SUCCESS;
|
|
}
|
|
}
|
|
|
|
argHelper->printf("Error! Cannot retrieve any valid hardware information!\n");
|
|
return OclocErrorCode::INVALID_DEVICE;
|
|
}
|
|
|
|
ArrayRef<const HardwareInfo *> OfflineLinker::getHardwareInfoTable() const {
|
|
return {hardwareInfoTable};
|
|
}
|
|
|
|
int OfflineLinker::prepareIgc() {
|
|
igcLib = loadIgcLibrary();
|
|
if (!igcLib) {
|
|
argHelper->printf("Error! Loading of IGC library has failed! Filename: %s\n", Os::igcDllName);
|
|
return OclocErrorCode::OUT_OF_HOST_MEMORY;
|
|
}
|
|
|
|
const auto igcCreateMainFunction = loadCreateIgcMainFunction();
|
|
if (!igcCreateMainFunction) {
|
|
argHelper->printf("Error! Cannot load required functions from IGC library.\n");
|
|
return OclocErrorCode::OUT_OF_HOST_MEMORY;
|
|
}
|
|
|
|
igcMain = createIgcMain(igcCreateMainFunction);
|
|
if (!igcMain) {
|
|
argHelper->printf("Error! Cannot create IGC main component!\n");
|
|
return OclocErrorCode::OUT_OF_HOST_MEMORY;
|
|
}
|
|
|
|
igcDeviceCtx = createIgcDeviceContext();
|
|
if (!igcDeviceCtx) {
|
|
argHelper->printf("Error! Cannot create IGC device context!\n");
|
|
return OclocErrorCode::OUT_OF_HOST_MEMORY;
|
|
}
|
|
|
|
const auto igcPlatform = getIgcPlatformHandle();
|
|
const auto igcGtSystemInfo = getGTSystemInfoHandle();
|
|
if (!igcPlatform || !igcGtSystemInfo) {
|
|
argHelper->printf("Error! IGC device context has not been properly created!\n");
|
|
return OclocErrorCode::OUT_OF_HOST_MEMORY;
|
|
}
|
|
|
|
IGC::PlatformHelper::PopulateInterfaceWith(*igcPlatform.get(), hwInfo.platform);
|
|
IGC::GtSysInfoHelper::PopulateInterfaceWith(*igcGtSystemInfo.get(), hwInfo.gtSystemInfo);
|
|
|
|
return OclocErrorCode::SUCCESS;
|
|
}
|
|
|
|
std::unique_ptr<OsLibrary> OfflineLinker::loadIgcLibrary() const {
|
|
return std::unique_ptr<OsLibrary>{OsLibrary::load(Os::igcDllName)};
|
|
}
|
|
|
|
CIF::CreateCIFMainFunc_t OfflineLinker::loadCreateIgcMainFunction() const {
|
|
return reinterpret_cast<CIF::CreateCIFMainFunc_t>(igcLib->getProcAddress(CIF::CreateCIFMainFuncName));
|
|
}
|
|
|
|
CIF::RAII::UPtr_t<CIF::CIFMain> OfflineLinker::createIgcMain(CIF::CreateCIFMainFunc_t createMainFunction) const {
|
|
return CIF::RAII::UPtr(createMainNoSanitize(createMainFunction));
|
|
}
|
|
|
|
CIF::RAII::UPtr_t<IGC::IgcOclDeviceCtxTagOCL> OfflineLinker::createIgcDeviceContext() const {
|
|
return igcMain->CreateInterface<IGC::IgcOclDeviceCtxTagOCL>();
|
|
}
|
|
|
|
CIF::RAII::UPtr_t<IGC::PlatformTagOCL> OfflineLinker::getIgcPlatformHandle() const {
|
|
return igcDeviceCtx->GetPlatformHandle();
|
|
}
|
|
|
|
CIF::RAII::UPtr_t<IGC::GTSystemInfoTagOCL> OfflineLinker::getGTSystemInfoHandle() const {
|
|
return igcDeviceCtx->GetGTSystemInfoHandle();
|
|
}
|
|
|
|
int OfflineLinker::execute() {
|
|
switch (operationMode) {
|
|
case OperationMode::SHOW_HELP:
|
|
return showHelp();
|
|
case OperationMode::LINK_FILES:
|
|
return link();
|
|
case OperationMode::SKIP_EXECUTION:
|
|
[[fallthrough]];
|
|
default:
|
|
argHelper->printf("Error: Linker cannot be executed due to unsuccessful initialization!\n");
|
|
return OclocErrorCode::INVALID_COMMAND_LINE;
|
|
}
|
|
}
|
|
|
|
int OfflineLinker::showHelp() {
|
|
constexpr auto help{R"===(Links several IR files to selected output format (LLVM BC, ELF).
|
|
Input files can be given in SPIR-V or LLVM BC.
|
|
|
|
Usage: ocloc link [-file <filename>]... -out <filename> [-out_format <format>] [-options <options>] [-internal_options <options>] [--help]
|
|
|
|
-file <filename> The input file to be linked.
|
|
Multiple files can be passed using repetition of this arguments.
|
|
Please see examples below.
|
|
|
|
-out <filename> Output filename.
|
|
|
|
-out_format <format> Output file format. Supported ones are ELF and LLVM_BC.
|
|
When not specified, LLVM_BC is used.
|
|
|
|
-options <options> Optional OpenCL C compilation options
|
|
as defined by OpenCL specification.
|
|
|
|
-internal_options <options> Optional compiler internal options
|
|
as defined by compilers used underneath.
|
|
Check intel-graphics-compiler (IGC) project
|
|
for details on available internal options.
|
|
You also may provide explicit --help to inquire
|
|
information about option, mentioned in -options.
|
|
|
|
--help Print this usage message.
|
|
|
|
Examples:
|
|
Link two SPIR-V files to LLVM BC output
|
|
ocloc link -file first_file.spv -file second_file.spv -out linker_output.llvmbc
|
|
|
|
Link two LLVM BC files to ELF output
|
|
ocloc link -file first_file.llvmbc -file second_file.llvmbc -out_format ELF -out translated.elf
|
|
)==="};
|
|
|
|
argHelper->printf(help);
|
|
|
|
return OclocErrorCode::SUCCESS;
|
|
}
|
|
|
|
int OfflineLinker::link() {
|
|
const auto encodedElfFile{createSingleInputFile()};
|
|
if (outputFormat == IGC::CodeType::elf) {
|
|
argHelper->saveOutput(outputFilename, encodedElfFile.data(), encodedElfFile.size());
|
|
return OclocErrorCode::SUCCESS;
|
|
}
|
|
|
|
const auto [translationResult, translatedBitcode] = translateToOutputFormat(encodedElfFile);
|
|
if (translationResult == OclocErrorCode::SUCCESS) {
|
|
argHelper->saveOutput(outputFilename, translatedBitcode.data(), translatedBitcode.size());
|
|
}
|
|
|
|
return translationResult;
|
|
}
|
|
|
|
std::vector<uint8_t> OfflineLinker::createSingleInputFile() const {
|
|
NEO::Elf::ElfEncoder<> elfEncoder{true, false, 1U};
|
|
elfEncoder.getElfFileHeader().type = Elf::ET_OPENCL_OBJECTS;
|
|
|
|
for (const auto &[bytes, size, codeType] : inputFilesContent) {
|
|
const auto isSpirv = codeType == IGC::CodeType::spirV;
|
|
const auto sectionType = isSpirv ? Elf::SHT_OPENCL_SPIRV : Elf::SHT_OPENCL_LLVM_BINARY;
|
|
const auto sectionName = isSpirv ? Elf::SectionNamesOpenCl::spirvObject : Elf::SectionNamesOpenCl::llvmObject;
|
|
const auto bytesArray = ArrayRef<const uint8_t>::fromAny(bytes.get(), size);
|
|
|
|
elfEncoder.appendSection(sectionType, sectionName, bytesArray);
|
|
}
|
|
|
|
return elfEncoder.encode();
|
|
}
|
|
|
|
std::pair<int, std::vector<uint8_t>> OfflineLinker::translateToOutputFormat(const std::vector<uint8_t> &elfInput) {
|
|
auto igcSrc = CIF::Builtins::CreateConstBuffer(igcMain.get(), elfInput.data(), elfInput.size());
|
|
auto igcOptions = CIF::Builtins::CreateConstBuffer(igcMain.get(), options.c_str(), options.size());
|
|
auto igcInternalOptions = CIF::Builtins::CreateConstBuffer(igcMain.get(), internalOptions.c_str(), internalOptions.size());
|
|
auto igcTranslationCtx = igcDeviceCtx->CreateTranslationCtx(IGC::CodeType::elf, outputFormat);
|
|
|
|
const auto tracingOptions{nullptr};
|
|
const auto tracingOptionsSize{0};
|
|
const auto igcOutput = igcTranslationCtx->Translate(igcSrc.get(), igcOptions.get(), igcInternalOptions.get(), tracingOptions, tracingOptionsSize);
|
|
|
|
std::vector<uint8_t> outputFileContent{};
|
|
if (!igcOutput) {
|
|
argHelper->printf("Error: Translation has failed! IGC output is nullptr!\n");
|
|
return {OclocErrorCode::OUT_OF_HOST_MEMORY, std::move(outputFileContent)};
|
|
}
|
|
|
|
if (igcOutput->GetOutput()->GetSizeRaw() != 0) {
|
|
outputFileContent.resize(igcOutput->GetOutput()->GetSizeRaw());
|
|
memcpy_s(outputFileContent.data(), outputFileContent.size(), igcOutput->GetOutput()->GetMemory<char>(), igcOutput->GetOutput()->GetSizeRaw());
|
|
}
|
|
|
|
tryToStoreBuildLog(igcOutput->GetBuildLog()->GetMemory<char>(), igcOutput->GetBuildLog()->GetSizeRaw());
|
|
|
|
const auto errorCode{igcOutput->Successful() ? OclocErrorCode::SUCCESS : OclocErrorCode::BUILD_PROGRAM_FAILURE};
|
|
if (errorCode != OclocErrorCode::SUCCESS) {
|
|
argHelper->printf("Error: Translation has failed! IGC returned empty output.\n");
|
|
}
|
|
|
|
return {errorCode, std::move(outputFileContent)};
|
|
}
|
|
|
|
std::string OfflineLinker::getBuildLog() const {
|
|
return buildLog;
|
|
}
|
|
|
|
void OfflineLinker::tryToStoreBuildLog(const char *buildLogRaw, size_t size) {
|
|
if (buildLogRaw && size != 0) {
|
|
buildLog = std::string{buildLogRaw, buildLogRaw + size};
|
|
}
|
|
}
|
|
|
|
} // namespace NEO
|