349 lines
13 KiB
C++
349 lines
13 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 "cif/import/library_api.h"
|
|
#include "ocl_igc_interface/igc_ocl_device_ctx.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, std::make_unique<OclocIgcFacade>(argHelper)}};
|
|
errorCode = linker->initialize(argsCount, args);
|
|
|
|
return linker;
|
|
}
|
|
|
|
OfflineLinker::OfflineLinker(OclocArgHelper *argHelper, std::unique_ptr<OclocIgcFacade> igcFacade)
|
|
: argHelper{argHelper}, operationMode{OperationMode::SKIP_EXECUTION}, outputFilename{"linker_output"}, outputFormat{IGC::CodeType::llvmBc}, igcFacade{std::move(igcFacade)} {}
|
|
|
|
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{igcFacade->initialize(hwInfo)};
|
|
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::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 = igcFacade->createConstBuffer(elfInput.data(), elfInput.size());
|
|
auto igcOptions = igcFacade->createConstBuffer(options.c_str(), options.size());
|
|
auto igcInternalOptions = igcFacade->createConstBuffer(internalOptions.c_str(), internalOptions.size());
|
|
auto igcTranslationCtx = igcFacade->createTranslationContext(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
|