/* * Copyright (c) Advanced Micro Devices, Inc., or its affiliates. * * SPDX-License-Identifier: MIT */ #include "platform/command.hpp" #include "platform/commandqueue.hpp" #include "platform/runtime.hpp" #include "platform/program.hpp" #include "platform/ndrange.hpp" #include "devprogram.hpp" #include "devkernel.hpp" #include "hotswap.hpp" #include "utils/macros.hpp" #include "utils/options.hpp" #include "comgrctx.hpp" #include #include #include #include #include #include #include #include #include #if defined(ATI_OS_LINUX) #include #include #endif // defined(ATI_OS_LINUX) namespace amd::device { inline static std::vector splitSpaceSeparatedString(const char* str) { std::string s(str); std::stringstream ss(s); std::istream_iterator beg(ss), end; std::vector vec(beg, end); return vec; } // ================================================================================================ Program::Program(amd::Device& device, amd::Program& owner) : device_(device), owner_(owner), type_(TYPE_NONE), initKernels_(), finiKernels_(), flags_(0), clBinary_(nullptr), llvmBinary_(), elfSectionType_(amd::Elf::LLVMIR), compileOptions_(), linkOptions_(), lastBuildOptionsArg_(), buildStatus_(CL_BUILD_NONE), buildError_(CL_SUCCESS), globalVariableTotalSize_(0), programOptions_(nullptr) {} // ================================================================================================ Program::~Program() { clear(); for (auto const& kernelMeta : kernelMetadataMap_) { amd::Comgr::destroy_metadata(kernelMeta.second); } amd::Comgr::destroy_metadata(metadata_); } // ================================================================================================ void Program::clear() { initKernels_.clear(); finiKernels_.clear(); // Destroy all device kernels for (const auto& it : kernels_) { delete it.second; } kernels_.clear(); } // ================================================================================================ // If buildLog is not null, and dataSet contains a log object, extract the // first log data object from dataSet and process it with // extractByteCodeBinary. void Program::extractBuildLog(amd_comgr_data_set_t dataSet) { amd_comgr_status_t status = AMD_COMGR_STATUS_SUCCESS; size_t count; status = amd::Comgr::action_data_count(dataSet, AMD_COMGR_DATA_KIND_LOG, &count); if (status == AMD_COMGR_STATUS_SUCCESS && count > 0) { char* logData = nullptr; size_t logSize; status = extractByteCodeBinary(dataSet, AMD_COMGR_DATA_KIND_LOG, "", &logData, &logSize); buildLog_ += logData; delete[] logData; } if (status != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Warning: extracting build log failed.\n"; } } // Extract the byte code binary from the data set. The binary will be saved to an output // file if the file name is provided. If buffer pointer, outBinary, is provided, the // binary will be passed back to the caller. // amd_comgr_status_t Program::extractByteCodeBinary(const amd_comgr_data_set_t inDataSet, const amd_comgr_data_kind_t dataKind, const std::string& outFileName, char* outBinary[], size_t* outSize) { amd_comgr_data_t binaryData; amd_comgr_status_t status = amd::Comgr::action_data_get_data(inDataSet, dataKind, 0, &binaryData); size_t binarySize = 0; if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::get_data(binaryData, &binarySize, NULL); } size_t bufSize = (dataKind == AMD_COMGR_DATA_KIND_LOG) ? binarySize + 1 : binarySize; char* binary = new char[bufSize]; if (binary == nullptr) { amd::Comgr::release_data(binaryData); return AMD_COMGR_STATUS_ERROR; } if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::get_data(binaryData, &binarySize, binary); } if (dataKind == AMD_COMGR_DATA_KIND_LOG) { binary[binarySize] = '\0'; } amd::Comgr::release_data(binaryData); if (status != AMD_COMGR_STATUS_SUCCESS) { delete[] binary; return status; } // save the binary to the file as output file name is specified if (!outFileName.empty()) { std::ofstream f(outFileName.c_str(), std::ios::trunc | std::ios::binary); if (f.is_open()) { f.write(binary, binarySize); f.close(); } else { buildLog_ += "Warning: opening the file to dump the code failed.\n"; } } if (outBinary != nullptr) { // Pass the dump binary and its size back to the caller *outBinary = binary; *outSize = binarySize; } else { delete[] binary; } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t Program::addCodeObjData(const char* source, const size_t size, const amd_comgr_data_kind_t type, const char* name, amd_comgr_data_set_t* dataSet) { amd_comgr_data_t data; amd_comgr_status_t status; status = amd::Comgr::create_data(type, &data); if (status != AMD_COMGR_STATUS_SUCCESS) { return status; } status = amd::Comgr::set_data(data, size, source); if ((name != nullptr) && (status == AMD_COMGR_STATUS_SUCCESS)) { status = amd::Comgr::set_data_name(data, name); } if ((dataSet != nullptr) && (status == AMD_COMGR_STATUS_SUCCESS)) { status = amd::Comgr::data_set_add(*dataSet, data); } amd::Comgr::release_data(data); return status; } static amd_comgr_language_t getCOMGRLanguage(bool isHIP, const amd::option::Options& amdOptions) { if (isHIP) { return AMD_COMGR_LANGUAGE_HIP; } else { const char* clStd = amdOptions.oVariables->CLStd; uint clcStd = (clStd[2] - '0') * 100 + (clStd[4] - '0') * 10; switch (clcStd) { case 100: case 110: case 120: return AMD_COMGR_LANGUAGE_OPENCL_1_2; case 200: return AMD_COMGR_LANGUAGE_OPENCL_2_0; default: break; } } ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_COMGR, "Cannot set Language version for %s \n", amdOptions.oVariables->CLStd); return AMD_COMGR_LANGUAGE_NONE; } amd_comgr_status_t Program::createAction(const amd_comgr_language_t oclver, const std::vector& options, amd_comgr_action_info_t* action, bool* hasAction) { *hasAction = false; amd_comgr_status_t status = amd::Comgr::create_action_info(action); if (status == AMD_COMGR_STATUS_SUCCESS) { *hasAction = true; if (oclver != AMD_COMGR_LANGUAGE_NONE) { status = amd::Comgr::action_info_set_language(*action, oclver); } } if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::action_info_set_isa_name(*action, device().isa().isaName().c_str()); } if (status == AMD_COMGR_STATUS_SUCCESS) { std::vector optionsArgv; optionsArgv.reserve(options.size()); for (auto& option : options) { optionsArgv.push_back(option.c_str()); } status = amd::Comgr::action_info_set_option_list(*action, optionsArgv.data(), optionsArgv.size()); } if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::action_info_set_logging(*action, true); } return status; } bool Program::linkLLVMBitcode(const amd_comgr_data_set_t inputs, const std::vector& options, amd::option::Options* amdOptions, amd_comgr_data_set_t* output, char* binaryData[], size_t* binarySize) { amd_comgr_language_t langver = getCOMGRLanguage(isHIP(), *amdOptions); if (langver == AMD_COMGR_LANGUAGE_NONE) { return false; } // Create the action for linking amd_comgr_action_info_t action; bool hasAction = false; amd_comgr_status_t status = createAction(langver, options, &action, &hasAction); if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::do_action(AMD_COMGR_ACTION_LINK_BC_TO_BC, action, inputs, *output); extractBuildLog(*output); } if (status == AMD_COMGR_STATUS_SUCCESS) { std::string dumpFileName; if (amdOptions->isDumpFlagSet(amd::option::DUMP_BC_LINKED)) { dumpFileName = amdOptions->getDumpFileName("_linked.bc"); } status = extractByteCodeBinary(*output, AMD_COMGR_DATA_KIND_BC, dumpFileName, binaryData, binarySize); } if (hasAction) { amd::Comgr::destroy_action_info(action); } return (status == AMD_COMGR_STATUS_SUCCESS); } bool Program::compileToLLVMBitcode(const amd_comgr_data_set_t compileInputs, const std::vector& options, amd::option::Options* amdOptions, char* binaryData[], size_t* binarySize, const bool link_dev_libs) { amd_comgr_language_t langver = getCOMGRLanguage(isHIP(), *amdOptions); if (langver == AMD_COMGR_LANGUAGE_NONE) { return false; } // Create the output data set amd_comgr_action_info_t action{}; amd_comgr_data_set_t output{}; amd_comgr_data_set_t input = compileInputs; bool hasAction = false; bool hasOutput = false; amd_comgr_status_t status = createAction(langver, options, &action, &hasAction); if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::create_data_set(&output); } if (status == AMD_COMGR_STATUS_SUCCESS) { hasOutput = true; } // Preprocess the source if (status == AMD_COMGR_STATUS_SUCCESS) { if (amdOptions->isDumpFlagSet(amd::option::DUMP_I)) { amd_comgr_data_set_t dataSetPreprocessor; bool hasDataSetPreprocessor = false; status = amd::Comgr::create_data_set(&dataSetPreprocessor); if (status == AMD_COMGR_STATUS_SUCCESS) { hasDataSetPreprocessor = true; status = amd::Comgr::do_action(AMD_COMGR_ACTION_SOURCE_TO_PREPROCESSOR, action, input, dataSetPreprocessor); extractBuildLog(dataSetPreprocessor); } if (status == AMD_COMGR_STATUS_SUCCESS) { std::string outFileName = amdOptions->getDumpFileName(".i"); status = extractByteCodeBinary(dataSetPreprocessor, AMD_COMGR_DATA_KIND_SOURCE, outFileName); } if (hasDataSetPreprocessor) { amd::Comgr::destroy_data_set(dataSetPreprocessor); } } } // Compiling the source codes if (status == AMD_COMGR_STATUS_SUCCESS) { if (link_dev_libs) { status = amd::Comgr::do_action(AMD_COMGR_ACTION_COMPILE_SOURCE_WITH_DEVICE_LIBS_TO_BC, action, input, output); } else { status = amd::Comgr::do_action(AMD_COMGR_ACTION_COMPILE_SOURCE_TO_BC, action, input, output); } extractBuildLog(output); } if (status == AMD_COMGR_STATUS_SUCCESS) { std::string outFileName; if (amdOptions->isDumpFlagSet(amd::option::DUMP_BC_OPTIMIZED)) { outFileName = amdOptions->getDumpFileName("_optimized.bc"); } status = extractByteCodeBinary(output, AMD_COMGR_DATA_KIND_BC, outFileName, binaryData, binarySize); } if (hasAction) { amd::Comgr::destroy_action_info(action); } if (hasOutput) { amd::Comgr::destroy_data_set(output); } return (status == AMD_COMGR_STATUS_SUCCESS); } // Create an executable from an input data set. To generate the executable, // the input data set is converted to relocatable code, then executable binary. // If assembly code is required, the input data set is converted to assembly. bool Program::compileAndLinkExecutable(const amd_comgr_data_set_t inputs, const std::vector& options, amd::option::Options* amdOptions, char* executable[], size_t* executableSize, file_type_t continueCompileFrom) { // create the linked output amd_comgr_action_info_t action; amd_comgr_data_set_t output; amd_comgr_data_set_t relocatableData; bool hasAction = false; bool hasOutput = false; bool hasRelocatableData = false; amd_comgr_status_t status = createAction(AMD_COMGR_LANGUAGE_NONE, options, &action, &hasAction); if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::create_data_set(&output); } if (status == AMD_COMGR_STATUS_SUCCESS) { hasOutput = true; if ((amdOptions->isDumpFlagSet(amd::option::DUMP_ISA)) || (isHIP() && amdOptions->origOptionStr.find("-save-temps") != std::string::npos)) { // create the assembly data set amd_comgr_data_set_t assemblyData; bool hasAssemblyData = false; status = amd::Comgr::create_data_set(&assemblyData); if (status == AMD_COMGR_STATUS_SUCCESS) { hasAssemblyData = true; status = amd::Comgr::do_action(AMD_COMGR_ACTION_CODEGEN_BC_TO_ASSEMBLY, action, inputs, assemblyData); extractBuildLog(assemblyData); } // dump the ISA if (status == AMD_COMGR_STATUS_SUCCESS) { std::string dumpIsaName = amdOptions->getDumpFileName(".s"); status = extractByteCodeBinary(assemblyData, AMD_COMGR_DATA_KIND_SOURCE, dumpIsaName); } if (hasAssemblyData) { amd::Comgr::destroy_data_set(assemblyData); } } } // Create the relocatable data set if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::create_data_set(&relocatableData); } if (status == AMD_COMGR_STATUS_SUCCESS) { hasRelocatableData = true; amd_comgr_action_kind_t kind = (continueCompileFrom == FILE_TYPE_ASM_TEXT) ? AMD_COMGR_ACTION_ASSEMBLE_SOURCE_TO_RELOCATABLE : AMD_COMGR_ACTION_CODEGEN_BC_TO_RELOCATABLE; status = amd::Comgr::do_action(kind, action, inputs, relocatableData); extractBuildLog(relocatableData); } // Create executable from the relocatable data set amd::Comgr::action_info_set_option_list(action, nullptr, 0); if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::do_action(AMD_COMGR_ACTION_LINK_RELOCATABLE_TO_EXECUTABLE, action, relocatableData, output); extractBuildLog(output); } if (status == AMD_COMGR_STATUS_SUCCESS) { // Extract the executable binary std::string outFileName; if (amdOptions->isDumpFlagSet(amd::option::DUMP_O)) { outFileName = amdOptions->getDumpFileName(".so"); } status = extractByteCodeBinary(output, AMD_COMGR_DATA_KIND_EXECUTABLE, outFileName, executable, executableSize); } if (hasAction) { amd::Comgr::destroy_action_info(action); } if (hasRelocatableData) { amd::Comgr::destroy_data_set(relocatableData); } if (hasOutput) { amd::Comgr::destroy_data_set(output); } return (status == AMD_COMGR_STATUS_SUCCESS); } static std::size_t getOCLSourceHash(const std::string& sourceCode) { return std::hash()(sourceCode); } static std::size_t getOCLOptionsHash(const amd::option::Options& options) { std::string opts; for (const std::string& S : options.clangOptions) opts.append(S); return std::hash()(opts); } bool Program::compileImpl(const std::string& sourceCode, const std::vector& headers, const char** headerIncludeNames, amd::option::Options* options) { const char* xLang = options->oVariables->XLang; if (xLang != nullptr) { if (strcmp(xLang, "asm") == 0) { clBinary()->elfOut()->addSection(amd::Elf::SOURCE, sourceCode.data(), sourceCode.size()); return true; } else if (!strcmp(xLang, "cl")) { buildLog_ += "Unsupported language: \"" + std::string(xLang) + "\".\n"; return false; } } // add CL source to input data set amd_comgr_data_set_t inputs; if (amd::Comgr::create_data_set(&inputs) != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Error: COMGR fails to create output buffer for LLVM bitcode.\n"; return false; } if (addCodeObjData(sourceCode.c_str(), sourceCode.length(), AMD_COMGR_DATA_KIND_SOURCE, "CompileSource", &inputs) != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Error: COMGR fails to create data from source.\n"; amd::Comgr::destroy_data_set(inputs); return false; } std::vector driverOptions; // Set the -O# std::ostringstream optLevel; optLevel << "-O" << options->oVariables->OptLevel; driverOptions.push_back(optLevel.str()); if (!isHIP()) { driverOptions.insert(driverOptions.end(), options->clangOptions.begin(), options->clangOptions.end()); // TODO: Can this be fixed at the source? options->llvmOptions is a flat // string, but should really be a vector of strings. std::vector splitLlvmOptions = splitSpaceSeparatedString(options->llvmOptions.c_str()); driverOptions.insert(driverOptions.end(), splitLlvmOptions.begin(), splitLlvmOptions.end()); } std::vector processedOptions = ProcessOptions(options); driverOptions.insert(driverOptions.end(), processedOptions.begin(), processedOptions.end()); // Set whole program mode driverOptions.push_back("-mllvm"); driverOptions.push_back("-amdgpu-prelink"); if (!device().settings().enableWgpMode_) { driverOptions.push_back("-mcumode"); } if (device().settings().lcWavefrontSize64_) { driverOptions.push_back("-mwavefrontsize64"); } driverOptions.push_back("-mcode-object-version=" + std::to_string(options->oVariables->LCCodeObjectVersion)); // Iterate through each source code and dump it into tmp std::fstream f; std::vector headerFileNames(headers.size()); if (!headers.empty()) { for (size_t i = 0; i < headers.size(); ++i) { std::string headerIncludeName(headerIncludeNames[i]); // replace / in path with current os's file separator if (amd::Os::fileSeparator() != '/') { for (auto& it : headerIncludeName) { if (it == '/') it = amd::Os::fileSeparator(); } } if (addCodeObjData(headers[i]->c_str(), headers[i]->length(), AMD_COMGR_DATA_KIND_INCLUDE, headerIncludeName.c_str(), &inputs) != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Error: COMGR fails to add headers into inputs.\n"; amd::Comgr::destroy_data_set(inputs); return false; } } } if (!isHIP() && options->isDumpFlagSet(amd::option::DUMP_CL)) { std::ostringstream driverOptionsOStrStr; std::copy(driverOptions.begin(), driverOptions.end(), std::ostream_iterator(driverOptionsOStrStr, " ")); std::ofstream f(options->getDumpFileName(".cl").c_str(), std::ios::trunc); if (f.is_open()) { auto srcHash = getOCLSourceHash(sourceCode); auto optHash = getOCLOptionsHash(*options); f << "/* Compiler options:\n" "-c -emit-llvm -target amdgcn-amd-amdhsa -x cl " << driverOptionsOStrStr.str() << " -include opencl-c.h " << "\nHash to override:" << "\n Source: 0x" << std::setbase(16) << srcHash << "\n Source + clang options: 0x" << (srcHash ^ optHash) << "\n*/\n\n" << sourceCode; f.close(); } else { buildLog_ += "Warning: opening the file to dump the OpenCL source failed.\n"; } } // Append Options provided by user to driver options if (isHIP()) { if (options->origOptionStr.size()) { std::istringstream userOptions{options->origOptionStr}; std::copy(std::istream_iterator(userOptions), std::istream_iterator(), std::back_inserter(driverOptions)); } } // Compile source to IR char* binaryData = nullptr; size_t binarySize = 0; bool ret = compileToLLVMBitcode(inputs, driverOptions, options, &binaryData, &binarySize); if (ret) { llvmBinary_.assign(binaryData, binarySize); // Destroy the original LLVM binary, received after compilation delete[] binaryData; elfSectionType_ = amd::Elf::LLVMIR; if (clBinary()->saveSOURCE()) { clBinary()->elfOut()->addSection(amd::Elf::SOURCE, sourceCode.data(), sourceCode.size()); } if (clBinary()->saveLLVMIR()) { clBinary()->elfOut()->addSection(amd::Elf::LLVMIR, llvmBinary_.data(), llvmBinary_.size()); compileOptions_.clear(); } } else { buildLog_ += "Error: Failed to compile source (from CL or HIP source to LLVM IR).\n"; } amd::Comgr::destroy_data_set(inputs); return ret; } // ================================================================================================ bool Program::linkImpl(const std::vector& inputPrograms, amd::option::Options* options, bool createLibrary) { amd_comgr_data_set_t inputs; if (amd::Comgr::create_data_set(&inputs) != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Error: COMGR fails to create data set.\n"; return false; } size_t idx = 0; for (auto program : inputPrograms) { bool result = true; if (program->llvmBinary_.empty()) { result = (program->clBinary() != nullptr); if (result) { // We are using CL binary directly. // Setup elfIn() and try to load llvmIR from binary // This elfIn() will be released at the end of build by finiBuild(). result = program->clBinary()->setElfIn(); } if (result) { result = program->clBinary()->loadLlvmBinary(program->llvmBinary_, program->elfSectionType_); } } if (result) { result = (program->elfSectionType_ == amd::Elf::LLVMIR); } if (result) { std::string llvmName = "LLVM Binary " + std::to_string(idx); result = (addCodeObjData(program->llvmBinary_.data(), program->llvmBinary_.size(), AMD_COMGR_DATA_KIND_BC, llvmName.c_str(), &inputs) == AMD_COMGR_STATUS_SUCCESS); } if (!result) { amd::Comgr::destroy_data_set(inputs); buildLog_ += "Error: Linking bitcode failed: failing to generate LLVM binary.\n"; return false; } idx++; // release elfIn() for the program program->clBinary()->resetElfIn(); } // create the linked output amd_comgr_data_set_t output; if (amd::Comgr::create_data_set(&output) != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Error: COMGR fails to create output buffer for LLVM bitcode.\n"; amd::Comgr::destroy_data_set(inputs); return false; } // NOTE: The options parameter is also used to identy cached code object. // This parameter should not contain any dyanamically generated filename. char* binaryData = nullptr; size_t binarySize = 0; std::vector linkOptions; bool ret = linkLLVMBitcode(inputs, linkOptions, options, &output, &binaryData, &binarySize); amd::Comgr::destroy_data_set(output); amd::Comgr::destroy_data_set(inputs); if (!ret) { buildLog_ += "Error: Linking bitcode failed: linking source & IR libraries.\n"; return false; } llvmBinary_.assign(binaryData, binarySize); // Destroy llvm binary, received after compilation delete[] binaryData; elfSectionType_ = amd::Elf::LLVMIR; if (clBinary()->saveLLVMIR()) { clBinary()->elfOut()->addSection(amd::Elf::LLVMIR, llvmBinary_.data(), llvmBinary_.size()); } // skip the rest if we are building an opencl library if (createLibrary) { setType(TYPE_LIBRARY); if (!createBinary(options)) { buildLog_ += "Internal error: creating OpenCL binary failed\n"; return false; } return true; } return linkImpl(options); } // ================================================================================================ static void dumpCodeObject(const std::string& image) { char fname[30]; static std::atomic index; sprintf(fname, "_code_object%04d.o", index++); ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_CODE, "Code object saved in %s\n", fname); std::ofstream ofs; ofs.open(fname, std::ios::binary); ofs << image; ofs.close(); } // ================================================================================================ bool Program::linkImpl(amd::option::Options* options) { file_type_t continueCompileFrom = FILE_TYPE_LLVMIR_BINARY; internal_ = (compileOptions_.find("-cl-internal-kernel") != std::string::npos) ? true : false; amd_comgr_data_set_t inputs; if (amd::Comgr::create_data_set(&inputs) != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Error: COMGR fails to create data set for linking.\n"; return false; } bool bLinkLLVMBitcode = true; if (llvmBinary_.empty()) { continueCompileFrom = getNextCompilationStageFromBinary(options); } switch (continueCompileFrom) { case FILE_TYPE_CG: case FILE_TYPE_LLVMIR_BINARY: { break; } case FILE_TYPE_ASM_TEXT: { char* section; size_t sz; clBinary()->elfOut()->getSection(amd::Elf::SOURCE, §ion, &sz); if (addCodeObjData(section, sz, AMD_COMGR_DATA_KIND_BC, "Assembly Text", &inputs) != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Error: COMGR fails to create assembly input.\n"; amd::Comgr::destroy_data_set(inputs); return false; } bLinkLLVMBitcode = false; break; } case FILE_TYPE_ISA: { amd::Comgr::destroy_data_set(inputs); binary_t isaBinary = binary(); if (GPU_DUMP_CODE_OBJECT) { dumpCodeObject(std::string{(const char*)isaBinary.first, isaBinary.second}); } if (!createKernels(const_cast(isaBinary.first), isaBinary.second, options->oVariables->UniformWorkGroupSize, internal_)) { buildLog_ += "Error: Cannot create kernels.\n"; return false; } return true; } default: buildLog_ += "Error while Codegen phase: the binary is incomplete \n"; amd::Comgr::destroy_data_set(inputs); return false; } // call LinkLLVMBitcode if (bLinkLLVMBitcode) { // open the bitcode libraries std::vector linkOptions; if (options->oVariables->FP32RoundDivideSqrt) { linkOptions.push_back("correctly_rounded_sqrt"); } if (options->oVariables->FiniteMathOnly || options->oVariables->FastRelaxedMath) { linkOptions.push_back("finite_only"); } if (options->oVariables->UnsafeMathOpt || options->oVariables->FastRelaxedMath) { linkOptions.push_back("unsafe_math"); } if (device().settings().lcWavefrontSize64_) { linkOptions.push_back("wavefrontsize64"); } linkOptions.push_back("code_object_v" + std::to_string(options->oVariables->LCCodeObjectVersion)); amd_comgr_status_t status = addCodeObjData(llvmBinary_.data(), llvmBinary_.size(), AMD_COMGR_DATA_KIND_BC, "LLVM Binary", &inputs); amd_comgr_data_set_t linked_bc; bool hasLinkedBC = false; if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::create_data_set(&linked_bc); } bool ret = (status == AMD_COMGR_STATUS_SUCCESS); if (ret) { hasLinkedBC = true; ret = linkLLVMBitcode(inputs, linkOptions, options, &linked_bc); } amd::Comgr::destroy_data_set(inputs); if (!ret) { if (hasLinkedBC) { amd::Comgr::destroy_data_set(linked_bc); } buildLog_ += "Error: Linking bitcode failed: linking source & IR libraries.\n"; return false; } inputs = linked_bc; } std::vector codegenOptions; // TODO: Can this be fixed at the source? options->llvmOptions is a flat // string, but should really be a vector of strings. std::vector splitLlvmOptions = splitSpaceSeparatedString(options->llvmOptions.c_str()); codegenOptions.insert(codegenOptions.end(), splitLlvmOptions.begin(), splitLlvmOptions.end()); // Set the -O# std::ostringstream optLevel; optLevel << "-O" << options->oVariables->OptLevel; codegenOptions.push_back(optLevel.str()); // Pass clang options if (continueCompileFrom != FILE_TYPE_ASM_TEXT) { std::copy_if(options->clangOptions.begin(), options->clangOptions.end(), std::back_inserter(codegenOptions), [](const std::string& opt) { return opt.rfind("-I", 0) != 0; }); } else { codegenOptions.insert(codegenOptions.end(), options->clangOptions.begin(), options->clangOptions.end()); } // Set whole program mode codegenOptions.push_back("-mllvm"); codegenOptions.push_back("-amdgpu-internalize-symbols"); if (!device().settings().enableWgpMode_) { codegenOptions.push_back("-mcumode"); } if (device().settings().lcWavefrontSize64_) { codegenOptions.push_back("-mwavefrontsize64"); } codegenOptions.push_back("-mcode-object-version=" + std::to_string(options->oVariables->LCCodeObjectVersion)); // NOTE: The params is also used to identy cached code object. This parameter // should not contain any dyanamically generated filename. char* executable = nullptr; size_t executableSize = 0; bool ret = compileAndLinkExecutable(inputs, codegenOptions, options, &executable, &executableSize, continueCompileFrom); amd::Comgr::destroy_data_set(inputs); if (!ret) { if (continueCompileFrom == FILE_TYPE_ASM_TEXT) { buildLog_ += "Error: Creating the executable from ISA assembly text failed.\n"; } else { buildLog_ += "Error: Creating the executable from LLVM IRs failed.\n"; } return false; } // Save the binary and type clBinary()->saveBIFBinary(executable, executableSize); // Destroy original memory with executable after compilation delete[] executable; if (!createKernels(const_cast(clBinary()->data().first), clBinary()->data().second, options->oVariables->UniformWorkGroupSize, internal_)) { buildLog_ += "Error: Cannot create kernels.\n"; return false; } setType(TYPE_EXECUTABLE); return true; } // ================================================================================================ bool Program::initClBinary() { if (clBinary_ == nullptr) { clBinary_ = new ClBinary(device()); if (clBinary_ == nullptr) { return false; } } return true; } // ================================================================================================ void Program::releaseClBinary() { delete clBinary_; clBinary_ = nullptr; } // ================================================================================================ bool Program::initBuild(amd::option::Options* options) { compileOptions_ = options->origOptionStr; programOptions_ = options; if (options->oVariables->DumpFlags > 0) { static std::atomic build_num{0}; options->setBuildNo(build_num++); } buildLog_.clear(); if (!initClBinary()) { ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_KERN, "Init CL Binary failed \n"); return false; } if (!amd::IS_HIP) { std::string targetID = device().isa().targetId(); #if defined(_WIN32) // Replace special charaters that are not supported by Windows FS. std::replace(targetID.begin(), targetID.end(), ':', '@'); #endif options->setPerBuildInfo(targetID.c_str(), clBinary()->getEncryptCode(), true); } // Elf Binary setup std::string outFileName; bool tempFile = false; // true means hsail required clBinary()->init(options); if (options->isDumpFlagSet(amd::option::DUMP_BIF)) { outFileName = options->getDumpFileName(".bin"); } else { // elf lib needs a writable temp file outFileName = amd::Os::getTempFileName(); tempFile = true; } if (!clBinary()->setElfOut(LP64_SWITCH(ELFCLASS32, ELFCLASS64), (outFileName.size() > 0) ? outFileName.c_str() : nullptr, tempFile)) { LogError("Setup elf out for gpu failed"); return false; } return true; } // ================================================================================================ bool Program::finiBuild(bool isBuildGood) { clBinary()->resetElfOut(); clBinary()->resetElfIn(); if (!isBuildGood) { // Prevent the encrypted binary form leaking out clBinary()->setBinary(nullptr, 0); } return true; } // ================================================================================================ int32_t Program::compile(const std::string& sourceCode, const std::vector& headers, const char** headerIncludeNames, const char* origOptions, amd::option::Options* options) { uint64_t start_time = 0; if (options->oVariables->EnableBuildTiming) { buildLog_ = "\nStart timing major build components.....\n\n"; start_time = amd::Os::timeNanos(); } lastBuildOptionsArg_ = origOptions ? origOptions : ""; if (options) { compileOptions_ = options->origOptionStr; } buildStatus_ = CL_BUILD_IN_PROGRESS; if (!initBuild(options)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ = "Internal error: Compilation init failed."; } } if (options->oVariables->FP32RoundDivideSqrt && !(device().info().singleFPConfig_ & CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT)) { buildStatus_ = CL_BUILD_ERROR; buildLog_ += "Error: -cl-fp32-correctly-rounded-divide-sqrt " "specified without device support"; } // Compile the source code if any if ((buildStatus_ == CL_BUILD_IN_PROGRESS) && !sourceCode.empty() && !compileImpl(sourceCode, headers, headerIncludeNames, options)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ = "Internal error: Compilation failed."; } } setType(TYPE_COMPILED); if ((buildStatus_ == CL_BUILD_IN_PROGRESS) && !createBinary(options)) { buildLog_ += "Internal Error: creating OpenCL binary failed!\n"; } if (!finiBuild(buildStatus_ == CL_BUILD_IN_PROGRESS)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ = "Internal error: Compilation fini failed."; } } if (buildStatus_ == CL_BUILD_IN_PROGRESS) { buildStatus_ = CL_BUILD_SUCCESS; } else { buildError_ = CL_COMPILE_PROGRAM_FAILURE; } if (options->oVariables->EnableBuildTiming) { std::stringstream tmp_ss; tmp_ss << "\nTotal Compile Time: " << (amd::Os::timeNanos() - start_time) / 1000ULL << " us\n"; buildLog_ += tmp_ss.str(); } if (options->oVariables->BuildLog && !buildLog_.empty()) { if (strcmp(options->oVariables->BuildLog, "stderr") == 0) { fprintf(stderr, "%s\n", options->optionsLog().c_str()); fprintf(stderr, "%s\n", buildLog_.c_str()); } else if (strcmp(options->oVariables->BuildLog, "stdout") == 0) { printf("%s\n", options->optionsLog().c_str()); printf("%s\n", buildLog_.c_str()); } else { std::fstream f; std::stringstream tmp_ss; std::string logs = options->optionsLog() + buildLog_; tmp_ss << options->oVariables->BuildLog << "." << options->getBuildNo(); f.open(tmp_ss.str().c_str(), (std::fstream::out | std::fstream::binary)); f.write(logs.data(), logs.size()); f.close(); } LogError(buildLog_.c_str()); } return buildError(); } // ================================================================================================ int32_t Program::link(const std::vector& inputPrograms, const char* origLinkOptions, amd::option::Options* linkOptions) { lastBuildOptionsArg_ = origLinkOptions ? origLinkOptions : ""; if (linkOptions) { linkOptions_ = linkOptions->origOptionStr; } buildStatus_ = CL_BUILD_IN_PROGRESS; amd::option::Options options; if (!getCompileOptionsAtLinking(inputPrograms, linkOptions)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ += "Internal error: Get compile options failed."; } } else { if (!amd::option::parseAllOptions(compileOptions_, options, false)) { buildStatus_ = CL_BUILD_ERROR; buildLog_ += options.optionsLog(); LogError("Parsing compile options failed."); } } uint64_t start_time = 0; if (options.oVariables->EnableBuildTiming) { buildLog_ = "\nStart timing major build components.....\n\n"; start_time = amd::Os::timeNanos(); } // initBuild() will clear buildLog_, so store it in a temporary variable std::string tmpBuildLog = buildLog_; if ((buildStatus_ == CL_BUILD_IN_PROGRESS) && !initBuild(&options)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ += "Internal error: Compilation init failed."; } } buildLog_ += tmpBuildLog; if (options.oVariables->FP32RoundDivideSqrt && !(device().info().singleFPConfig_ & CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT)) { buildStatus_ = CL_BUILD_ERROR; buildLog_ += "Error: -cl-fp32-correctly-rounded-divide-sqrt " "specified without device support"; } bool createLibrary = linkOptions ? linkOptions->oVariables->clCreateLibrary : false; if ((buildStatus_ == CL_BUILD_IN_PROGRESS) && !linkImpl(inputPrograms, &options, createLibrary)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ += "Internal error: Link failed.\n"; buildLog_ += "Make sure the system setup is correct."; } } if (!finiBuild(buildStatus_ == CL_BUILD_IN_PROGRESS)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ = "Internal error: Compilation fini failed."; } } if (buildStatus_ == CL_BUILD_IN_PROGRESS) { buildStatus_ = CL_BUILD_SUCCESS; } else { buildError_ = CL_LINK_PROGRAM_FAILURE; } if (options.oVariables->EnableBuildTiming) { std::stringstream tmp_ss; tmp_ss << "\nTotal Link Time: " << (amd::Os::timeNanos() - start_time) / 1000ULL << " us\n"; buildLog_ += tmp_ss.str(); } if (options.oVariables->BuildLog && !buildLog_.empty()) { if (strcmp(options.oVariables->BuildLog, "stderr") == 0) { fprintf(stderr, "%s\n", options.optionsLog().c_str()); fprintf(stderr, "%s\n", buildLog_.c_str()); } else if (strcmp(options.oVariables->BuildLog, "stdout") == 0) { printf("%s\n", options.optionsLog().c_str()); printf("%s\n", buildLog_.c_str()); } else { std::fstream f; std::stringstream tmp_ss; std::string logs = options.optionsLog() + buildLog_; tmp_ss << options.oVariables->BuildLog << "." << options.getBuildNo(); f.open(tmp_ss.str().c_str(), (std::fstream::out | std::fstream::binary)); f.write(logs.data(), logs.size()); f.close(); } } if (!buildLog_.empty()) { LogError(buildLog_.c_str()); } return buildError(); } // ================================================================================================ static std::pair getSubstBinFileName(const char* SubstCfgFile, size_t srcHash, size_t optHash) { using namespace std; const size_t srcAndOptHash = srcHash ^ optHash; ifstream cfgFile(SubstCfgFile); if (cfgFile.good()) { string line; while (getline(cfgFile, line)) { istringstream ss(line); size_t hash; ss >> setbase(16) >> hash; if (ss.fail() || !isspace(ss.peek())) continue; if (hash == srcAndOptHash || hash == srcHash) { ss >> ws; string objFileName; getline(ss, objFileName); // get the rest of line with spaces return make_pair(objFileName, hash); } } } else return make_pair(string(), (size_t)1); return make_pair(string(), (size_t)0); } bool Program::trySubstObjFile(const char* SubstCfgFile, const std::string& sourceCode, const amd::option::Options* options) { std::string buffer; std::ostringstream str(buffer); size_t srcHash = getOCLSourceHash(sourceCode); size_t optHash = getOCLOptionsHash(*options); auto substRes = getSubstBinFileName(SubstCfgFile, srcHash, optHash); if (substRes.first.empty()) { switch (substRes.second) { default: break; case 1: str << "Subst failure: cannot open config file " << SubstCfgFile << std::endl; break; } buildLog_ += str.str(); return false; } uint8_t* binary = nullptr; size_t binSize = 0; std::ifstream binFile(substRes.first, std::ios::binary | std::ios::ate); if (binFile.good()) { binSize = binFile.tellg(); binFile.seekg(0, std::ios::beg); binary = new (std::nothrow) uint8_t[binSize]; if (binary && !binFile.read(reinterpret_cast(binary), binSize)) { delete[] binary; binary = nullptr; } } if (!binary) { buildStatus_ = CL_BUILD_ERROR; buildError_ = CL_BUILD_PROGRAM_FAILURE; str << "Subst failure: cannot read binary file " << substRes.first << '\n'; } else { if (setKernels(binary, binSize)) { buildStatus_ = CL_BUILD_SUCCESS; buildError_ = 0; str << "Substituted program hash 0x" << std::setbase(16) << substRes.second << " with " << substRes.first << '\n'; } } buildLog_ += str.str(); return true; } int32_t Program::build(const std::string& sourceCode, const char* origOptions, amd::option::Options* options) { if (AMD_OCL_SUBST_OBJFILE != NULL && trySubstObjFile(AMD_OCL_SUBST_OBJFILE, sourceCode, options)) { return buildError(); } uint64_t start_time = 0; if (options->oVariables->EnableBuildTiming) { buildLog_ = "\nStart timing major build components.....\n\n"; start_time = amd::Os::timeNanos(); } lastBuildOptionsArg_ = origOptions ? origOptions : ""; if (options) { compileOptions_ = options->origOptionStr; } buildStatus_ = CL_BUILD_IN_PROGRESS; if (!initBuild(options)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ = "Internal error: Compilation init failed."; } } if (options->oVariables->FP32RoundDivideSqrt && !(device().info().singleFPConfig_ & CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT)) { buildStatus_ = CL_BUILD_ERROR; buildLog_ += "Error: -cl-fp32-correctly-rounded-divide-sqrt " "specified without device support"; } std::vector headers; std::vector headerIncludeNames; const std::vector& tmpHeaderNames = owner()->headerNames(); const std::vector& tmpHeaders = owner()->headers(); headers.reserve(tmpHeaders.size()); headerIncludeNames.reserve(tmpHeaderNames.size()); for (size_t i = 0; i < tmpHeaders.size(); ++i) { headers.push_back(&tmpHeaders[i]); headerIncludeNames.push_back(tmpHeaderNames[i].c_str()); } // Compile the source code if any bool compileStatus = true; if ((buildStatus_ == CL_BUILD_IN_PROGRESS) && !sourceCode.empty()) { if (!headerIncludeNames.empty()) { compileStatus = compileImpl(sourceCode, headers, &headerIncludeNames[0], options); } else { compileStatus = compileImpl(sourceCode, headers, nullptr, options); } } if (!compileStatus) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ = "Internal error: Compilation failed."; } } if ((buildStatus_ == CL_BUILD_IN_PROGRESS) && !linkImpl(options)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ += "Internal error: Link failed.\n"; buildLog_ += "Make sure the system setup is correct."; } } if (!finiBuild(buildStatus_ == CL_BUILD_IN_PROGRESS)) { buildStatus_ = CL_BUILD_ERROR; if (buildLog_.empty()) { buildLog_ = "Internal error: Compilation fini failed."; } } if (buildStatus_ == CL_BUILD_IN_PROGRESS) { buildStatus_ = CL_BUILD_SUCCESS; } else { buildError_ = CL_BUILD_PROGRAM_FAILURE; } if (options->oVariables->EnableBuildTiming) { std::stringstream tmp_ss; tmp_ss << "\nTotal Build Time: " << (amd::Os::timeNanos() - start_time) / 1000ULL << " us\n"; buildLog_ += tmp_ss.str(); } if (options->oVariables->BuildLog && !buildLog_.empty()) { if (strcmp(options->oVariables->BuildLog, "stderr") == 0) { fprintf(stderr, "%s\n", options->optionsLog().c_str()); fprintf(stderr, "%s\n", buildLog_.c_str()); } else if (strcmp(options->oVariables->BuildLog, "stdout") == 0) { printf("%s\n", options->optionsLog().c_str()); printf("%s\n", buildLog_.c_str()); } else { std::fstream f; std::stringstream tmp_ss; std::string logs = options->optionsLog() + buildLog_; tmp_ss << options->oVariables->BuildLog << "." << options->getBuildNo(); f.open(tmp_ss.str().c_str(), (std::fstream::out | std::fstream::binary)); f.write(logs.data(), logs.size()); f.close(); } } if (!buildLog_.empty()) { LogError(buildLog_.c_str()); } return buildError(); } // ================================================================================================ bool Program::load() { coLoaded_ = setKernels(const_cast(binary().first), binary().second, BinaryFd().first, BinaryFd().second, BinaryURI()); return coLoaded_; } // ================================================================================================ std::vector Program::ProcessOptions(amd::option::Options* options) { std::vector optionsVec; if (!isHIP()) { int major, minor; ::sscanf(device().info().version_, "OpenCL %d.%d ", &major, &minor); std::stringstream ss; ss << "-D__OPENCL_VERSION__=" << (major * 100 + minor * 10); optionsVec.push_back(ss.str()); } if (!isHIP()) { if (device().info().imageSupport_ && options->oVariables->ImageSupport) { optionsVec.push_back("-D__IMAGE_SUPPORT__=1"); } uint clcStd = (options->oVariables->CLStd[2] - '0') * 100 + (options->oVariables->CLStd[4] - '0') * 10; if (clcStd >= 200) { std::stringstream opts; // Add only for CL2.0 and later opts << "-D" << "CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE=" << device().info().maxGlobalVariableSize_; optionsVec.push_back(opts.str()); } else { options->oVariables->UniformWorkGroupSize = true; } // Tokenize the extensions string into a vector of strings std::istringstream istrstr(device().info().extensions_); std::istream_iterator sit(istrstr), end; std::vector extensions(sit, end); if (!extensions.empty()) { std::ostringstream clext; clext << "-cl-ext=+"; std::copy(extensions.begin(), extensions.end() - 1, std::ostream_iterator(clext, ",+")); clext << extensions.back(); optionsVec.push_back("-Xclang"); optionsVec.push_back(clext.str()); } // ROCM-24914 - Convert incompatible pointer types error to warning for some Adobe apps. // This change was made upstream but the kernels used by these apps are still affected. // Refer: https://github.com/llvm/llvm-project/pull/157364 std::string appName = {}; std::string appPathAndName = {}; amd::Os::getAppPathAndFileName(appName, appPathAndName); if ((appName == "Indigo Benchmark.exe") || (appName == "Adobe Premiere Pro.exe") || (appName == "AfterFX.exe")) { optionsVec.push_back("-Xclang"); optionsVec.push_back("-Wno-error=incompatible-pointer-types"); } } return optionsVec; } std::string Program::ProcessOptionsFlattened(amd::option::Options* options) { std::vector processOptions = ProcessOptions(options); std::ostringstream processOptionsOStrStr; processOptionsOStrStr << " "; std::copy(processOptions.begin(), processOptions.end(), std::ostream_iterator(processOptionsOStrStr, " ")); return processOptionsOStrStr.str(); } // ================================================================================================ bool Program::getCompileOptionsAtLinking(const std::vector& inputPrograms, const amd::option::Options* linkOptions) { amd::option::Options compileOptions; auto it = inputPrograms.cbegin(); const auto itEnd = inputPrograms.cend(); for (size_t i = 0; it != itEnd; ++it, ++i) { Program* program = *it; amd::option::Options compileOptions2; amd::option::Options* thisCompileOptions = i == 0 ? &compileOptions : &compileOptions2; if (!amd::option::parseAllOptions(program->compileOptions_, *thisCompileOptions, false)) { buildLog_ += thisCompileOptions->optionsLog(); LogError("Parsing compile options failed."); return false; } if (i == 0) compileOptions_ = program->compileOptions_; // if we are linking a program executable, and if "program" is a // compiled module or a library created with "-enable-link-options", // we can overwrite "program"'s compile options with linking options if (!linkOptions_.empty() && !linkOptions->oVariables->clCreateLibrary) { bool linkOptsCanOverwrite = false; if (program->type() != TYPE_LIBRARY) { linkOptsCanOverwrite = true; } else { amd::option::Options thisLinkOptions; if (!amd::option::parseLinkOptions(program->linkOptions_, thisLinkOptions)) { buildLog_ += thisLinkOptions.optionsLog(); LogError("Parsing link options failed."); return false; } if (thisLinkOptions.oVariables->clEnableLinkOptions) linkOptsCanOverwrite = true; } if (linkOptsCanOverwrite) { if (!thisCompileOptions->setOptionVariablesAs(*linkOptions)) { buildLog_ += thisCompileOptions->optionsLog(); LogError("Setting link options failed."); return false; } } if (i == 0) compileOptions_ += " " + linkOptions_; } // warn if input modules have inconsistent compile options if (i > 0) { if (!compileOptions.equals(*thisCompileOptions, true /*ignore clc options*/)) { buildLog_ += "Warning: Input OpenCL binaries has inconsistent" " compile options. Using compile options from" " the first input binary!\n"; } } } return true; } // ================================================================================================ bool Program::initClBinary(const char* binaryIn, size_t size, amd::Os::FileDesc fdesc, size_t foffset, std::string uri) { if (!initClBinary()) { ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_KERN, "Init CL Binary failed \n"); return false; } // Save the original binary that isn't owned by ClBinary clBinary()->saveOrigBinary(binaryIn, size); const char* bin = binaryIn; size_t sz = size; // unencrypted int encryptCode = 0; char* decryptedBin = nullptr; size_t decryptedSize; if (!clBinary()->decryptElf(binaryIn, size, &decryptedBin, &decryptedSize, &encryptCode)) { ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_KERN, "Bin is not ELF \n"); return false; } if (decryptedBin != nullptr) { // It is decrypted binary. bin = decryptedBin; sz = decryptedSize; } if (!isElf(bin)) { // Invalid binary. delete[] decryptedBin; ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_KERN, "Bin is not ELF \n"); return false; } clBinary()->setFlags(encryptCode); return clBinary()->setBinary(bin, sz, (decryptedBin != nullptr), fdesc, foffset, uri); } // ================================================================================================ void Program::addKernel(Kernel* k) { kernels_[k->name()] = k; if (k->isInitKernel()) { initKernels_.push_back(k); } else if (k->isFiniKernel()) { finiKernels_.push_back(k); } } // ================================================================================================ bool Program::setBinary(const char* binaryIn, size_t size, const device::Program* same_dev_prog, amd::Os::FileDesc fdesc, size_t foffset, std::string uri) { if (!initClBinary(binaryIn, size, fdesc, foffset, uri)) { ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_KERN, "Init CL Binary failed \n"); return false; } if (!clBinary()->setElfIn()) { LogError("Setting input OCL binary failed"); return false; } uint16_t type; if (!clBinary()->elfIn()->getType(type)) { LogError("Bad OCL Binary: error loading ELF type!"); return false; } switch (type) { case ET_NONE: { setType(TYPE_NONE); break; } case ET_REL: { if (clBinary()->isSPIR() || clBinary()->isSPIRV()) { setType(TYPE_INTERMEDIATE); } else { setType(TYPE_COMPILED); } break; } case ET_DYN: { char* sect = nullptr; size_t sz = 0; if (clBinary()->elfIn()->isHsaCo()) { setType(TYPE_EXECUTABLE); } else { setType(TYPE_LIBRARY); } break; } case ET_EXEC: { setType(TYPE_EXECUTABLE); break; } default: LogError("Bad OCL Binary: bad ELF type!"); return false; } if (same_dev_prog != nullptr) { compileOptions_ = same_dev_prog->compileOptions(); linkOptions_ = same_dev_prog->linkOptions(); } else if (!amd::IS_HIP) { compileOptions_.clear(); linkOptions_.clear(); } clBinary()->resetElfIn(); return true; } // ================================================================================================ Program::file_type_t Program::getCompilationStagesFromBinary( std::vector& completeStages, bool& needOptionsCheck) { Program::file_type_t from = FILE_TYPE_DEFAULT; completeStages.clear(); needOptionsCheck = true; //! @todo Should we also check for ACL_TYPE_OPENCL & ACL_TYPE_LLVMIR_TEXT? // Checking llvmir in .llvmir section bool containsLlvmirText = (type() == TYPE_COMPILED); bool containsShaderIsa = (type() == TYPE_EXECUTABLE); bool containsOpts = !(compileOptions_.empty() && linkOptions_.empty()); if (containsLlvmirText && containsOpts) { completeStages.push_back(from); from = FILE_TYPE_LLVMIR_BINARY; } if (containsShaderIsa) { completeStages.push_back(from); from = FILE_TYPE_ISA; } std::string sCurOptions = compileOptions_ + linkOptions_; amd::option::Options curOptions; if (!amd::option::parseAllOptions(sCurOptions, curOptions, false)) { buildLog_ += curOptions.optionsLog(); LogError("Parsing compile options failed."); return FILE_TYPE_DEFAULT; } switch (from) { case FILE_TYPE_CG: case FILE_TYPE_ISA: // do not check options, if LLVMIR is absent or might be absent or options are absent if (!curOptions.oVariables->BinLLVMIR || !containsLlvmirText || !containsOpts) { needOptionsCheck = false; } break; // recompilation might be needed case FILE_TYPE_LLVMIR_BINARY: case FILE_TYPE_DEFAULT: default: break; } return from; } // ================================================================================================ Program::file_type_t Program::getNextCompilationStageFromBinary(amd::option::Options* options) { Program::file_type_t continueCompileFrom = FILE_TYPE_DEFAULT; binary_t binary = this->binary(); finfo_t finfo = this->BinaryFd(); std::string uri = this->BinaryURI(); // If the binary already exists if ((binary.first != nullptr) && (binary.second > 0)) { // save the current options std::string sCurCompileOptions = compileOptions_; std::string sCurLinkOptions = linkOptions_; std::string sCurOptions = compileOptions_ + linkOptions_; // Saving binary in the interface class, // which also load compile & link options from binary setBinary(static_cast(binary.first), binary.second, nullptr, finfo.first, finfo.second, uri); // Calculate the next stage to compile from, based on sections in binaryElf_; // No any validity checks here std::vector completeStages; bool needOptionsCheck = true; continueCompileFrom = getCompilationStagesFromBinary(completeStages, needOptionsCheck); if (!options || !needOptionsCheck) { return continueCompileFrom; } bool recompile = false; //! @todo Should we also check for ACL_TYPE_OPENCL & ACL_TYPE_LLVMIR_TEXT? switch (continueCompileFrom) { case FILE_TYPE_CG: case FILE_TYPE_ISA: { // Compare options loaded from binary with current ones, recompile if differ; // If compile options are absent in binary, do not compare and recompile if (compileOptions_.empty()) break; compileOptions_ = sCurCompileOptions; linkOptions_ = sCurLinkOptions; amd::option::Options curOptions; if (!amd::option::parseAllOptions(sCurOptions, curOptions, false)) { buildLog_ += curOptions.optionsLog(); LogError("Parsing compile options failed."); return FILE_TYPE_DEFAULT; } break; } default: break; } if (recompile) { while (!completeStages.empty()) { continueCompileFrom = completeStages.back(); if (continueCompileFrom == FILE_TYPE_SPIRV_BINARY || continueCompileFrom == FILE_TYPE_LLVMIR_BINARY || continueCompileFrom == FILE_TYPE_SPIR_BINARY || continueCompileFrom == FILE_TYPE_DEFAULT) { break; } completeStages.pop_back(); } } } else { const char* xLang = options->oVariables->XLang; if (xLang != nullptr && strcmp(xLang, "asm") == 0) { continueCompileFrom = FILE_TYPE_ASM_TEXT; } } return continueCompileFrom; } // ================================================================================================ bool ComgrBinaryData::create(amd_comgr_data_kind_t kind, void* binary, size_t binSize) { amd_comgr_status_t status = amd::Comgr::create_data(kind, &binaryData_); if (status != AMD_COMGR_STATUS_SUCCESS) { return false; } created_ = true; status = amd::Comgr::set_data(binaryData_, binSize, reinterpret_cast(binary)); if (status != AMD_COMGR_STATUS_SUCCESS) { return false; } return true; } amd_comgr_data_t& ComgrBinaryData::data() { assert(created_); return binaryData_; } ComgrBinaryData::~ComgrBinaryData() { if (created_) { amd::Comgr::release_data(binaryData_); } } bool Program::createKernelMetadataMap(void* binary, size_t binSize) { ComgrBinaryData binaryData; if (!binaryData.create(AMD_COMGR_DATA_KIND_EXECUTABLE, binary, binSize)) { buildLog_ += "Error: COMGR failed to create code object data object.\n"; return false; } amd_comgr_status_t status; if (device().isOnline()) { size_t requiredSize = 0; status = amd::Comgr::get_data_isa_name(binaryData.data(), &requiredSize, nullptr); if (status != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Error: COMGR failed to get code object ISA name.\n"; return false; } std::vector binaryIsaName(requiredSize); status = amd::Comgr::get_data_isa_name(binaryData.data(), &requiredSize, binaryIsaName.data()); if ((status != AMD_COMGR_STATUS_SUCCESS) || (requiredSize != binaryIsaName.size())) { buildLog_ += "Error: COMGR failed to get code object ISA name.\n"; return false; } const amd::Isa* binaryIsa = amd::Isa::findIsa(binaryIsaName.data()); if (!binaryIsa) { buildLog_ += "Error: Could not find the program ISA " + std::string(binaryIsaName.data()) + "\n"; return false; } if (!amd::Isa::isCompatible(*binaryIsa, device().isa())) { // HotSwap: let a supported foreign source ISA past the gate; loader transpiles downstream. const std::string binaryIsaNameStr(binaryIsaName.data()); const bool hotswap_ok = amd::hotswap::Enabled() && amd::hotswap::IsSupportedPair(binaryIsa->processorName(), device().isa().processorName()); if (!hotswap_ok) { buildLog_ += "Error: The program ISA " + binaryIsaNameStr; buildLog_ += " is not compatible with the device ISA " + device().isa().isaName() + "\n"; return false; } buildLog_ += "HotSwap: allowing foreign program ISA " + binaryIsaNameStr + " for transpilation to " + device().isa().isaName() + "\n"; } } status = amd::Comgr::get_data_metadata(binaryData.data(), &metadata_); if (status != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "Error: COMGR failed to get the metadata.\n"; return false; } amd_comgr_metadata_node_t kernelsMD; bool hasKernelMD = false; size_t size = 0; status = amd::Comgr::metadata_lookup(metadata_, "Kernels", &kernelsMD); if (status == AMD_COMGR_STATUS_SUCCESS) { ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_CODE, "Using Code Object V2."); hasKernelMD = true; codeObjectVer_ = 2; } else { amd_comgr_metadata_node_t versionMD, versionNode; char major_version, minor_version; status = amd::Comgr::metadata_lookup(metadata_, "amdhsa.version", &versionMD); if (status != AMD_COMGR_STATUS_SUCCESS) { ClPrint(amd::LOG_ERROR, amd::LOG_CODE, "No amdhsa.version metadata found."); return false; } status = amd::Comgr::index_list_metadata(versionMD, 0, &versionNode); if (status != AMD_COMGR_STATUS_SUCCESS) { ClPrint(amd::LOG_ERROR, amd::LOG_CODE, "Cannot get code object metadata major version node."); amd::Comgr::destroy_metadata(versionMD); return false; } size = 1; status = amd::Comgr::get_metadata_string(versionNode, &size, &major_version); if (status != AMD_COMGR_STATUS_SUCCESS) { ClPrint(amd::LOG_ERROR, amd::LOG_CODE, "Cannot get code object metadata major version."); amd::Comgr::destroy_metadata(versionNode); amd::Comgr::destroy_metadata(versionMD); return false; } amd::Comgr::destroy_metadata(versionNode); status = amd::Comgr::index_list_metadata(versionMD, 1, &versionNode); if (status != AMD_COMGR_STATUS_SUCCESS) { ClPrint(amd::LOG_ERROR, amd::LOG_CODE, "Cannot get code object metadata minor version node."); amd::Comgr::destroy_metadata(versionMD); return false; } size = 1; status = amd::Comgr::get_metadata_string(versionNode, &size, &minor_version); if (status != AMD_COMGR_STATUS_SUCCESS) { ClPrint(amd::LOG_ERROR, amd::LOG_CODE, "Cannot get code object metadata minor version."); amd::Comgr::destroy_metadata(versionNode); amd::Comgr::destroy_metadata(versionMD); return false; } amd::Comgr::destroy_metadata(versionNode); amd::Comgr::destroy_metadata(versionMD); if (major_version == '1') { if (minor_version == '0') { ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_CODE, "Using Code Object V3."); codeObjectVer_ = 3; } else if (minor_version == '1') { ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_CODE, "Using Code Object V4."); codeObjectVer_ = 4; } else if (minor_version == '2') { ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_CODE, "Using Code Object V5."); codeObjectVer_ = 5; } else { ClPrint(amd::LOG_ERROR, amd::LOG_CODE, "Unknown code object metadata minor version [%s.%s].", major_version, minor_version); } } else { ClPrint(amd::LOG_ERROR, amd::LOG_CODE, "Unknown code object metadata major version [%s.%s].", major_version, minor_version); } status = amd::Comgr::metadata_lookup(metadata_, "amdhsa.kernels", &kernelsMD); if (status == AMD_COMGR_STATUS_SUCCESS) { hasKernelMD = true; } } if (status == AMD_COMGR_STATUS_SUCCESS) { status = amd::Comgr::get_metadata_list_size(kernelsMD, &size); } else if (amd::IS_HIP) { // Assume an empty binary. HIP may have binaries with just global variables return true; } for (size_t i = 0; i < size && status == AMD_COMGR_STATUS_SUCCESS; i++) { amd_comgr_metadata_node_t nameMeta; bool hasNameMeta = false; bool hasKernelNode = false; amd_comgr_metadata_node_t kernelNode; std::string kernelName; status = amd::Comgr::index_list_metadata(kernelsMD, i, &kernelNode); if (status == AMD_COMGR_STATUS_SUCCESS) { hasKernelNode = true; status = amd::Comgr::metadata_lookup(kernelNode, (codeObjectVer() == 2) ? "Name" : ".name", &nameMeta); } if (status == AMD_COMGR_STATUS_SUCCESS) { hasNameMeta = true; status = getMetaBuf(nameMeta, &kernelName); } if (status == AMD_COMGR_STATUS_SUCCESS) { kernelMetadataMap_[std::move(kernelName)] = kernelNode; } else { if (hasKernelNode) { amd::Comgr::destroy_metadata(kernelNode); } for (auto const& kernelMeta : kernelMetadataMap_) { amd::Comgr::destroy_metadata(kernelMeta.second); } kernelMetadataMap_.clear(); } if (hasNameMeta) { amd::Comgr::destroy_metadata(nameMeta); } } if (hasKernelMD) { amd::Comgr::destroy_metadata(kernelsMD); } return (status == AMD_COMGR_STATUS_SUCCESS); } bool Program::FindGlobalVarSize(void* binary, size_t binSize) { // HIP doesn't need information about global variable size. // Hence runtime can skip expensive Elf object creation for parsing if (!amd::IS_HIP) { size_t progvarsTotalSize = 0; size_t dynamicSize = 0; size_t progvarsWriteSize = 0; amd::Elf elfIn(ELFCLASSNONE, reinterpret_cast(binary), binSize, nullptr, amd::Elf::ELF_C_READ); if (!elfIn.isSuccessful()) { buildLog_ += "Creating input amd::Elf object failed\n"; return false; } auto numpHdrs = elfIn.getSegmentNum(); for (unsigned int i = 0; i < numpHdrs; ++i) { amd::ELFIO::segment* seg = nullptr; if (!elfIn.getSegment(i, seg)) { continue; } // Accumulate the size of R & !X loadable segments if (seg->get_type() == PT_LOAD && !(seg->get_flags() & PF_X)) { if (seg->get_flags() & PF_R) { progvarsTotalSize += seg->get_memory_size(); } if (seg->get_flags() & PF_W) { progvarsWriteSize += seg->get_memory_size(); } } else if (seg->get_type() == PT_DYNAMIC) { dynamicSize += seg->get_memory_size(); } } progvarsTotalSize -= dynamicSize; setGlobalVariableTotalSize(progvarsTotalSize); if (progvarsWriteSize != dynamicSize) { hasGlobalStores_ = true; } } if (!createKernelMetadataMap(binary, binSize)) { buildLog_ += "Error: create kernel metadata map using COMgr\n"; return false; } return true; } amd_comgr_status_t getSymbolFromModule(amd_comgr_symbol_t symbol, void* userData) { size_t nlen = 0; size_t* userDataInfo = nullptr; amd_comgr_status_t status; amd_comgr_symbol_type_t type; std::vector* var_names = nullptr; /* Unpack the user data */ SymbolInfo* sym_info = reinterpret_cast(userData); if (!sym_info) { return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; } /* Retrieve the symbol info */ status = amd::Comgr::symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_NAME_LENGTH, &nlen); if (status != AMD_COMGR_STATUS_SUCCESS) { return status; } /* Retrieve the symbol name */ char* name = new char[nlen + 1]; status = amd::Comgr::symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_NAME, name); if (status != AMD_COMGR_STATUS_SUCCESS) { return status; } /* Retrieve the symbol type*/ status = amd::Comgr::symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_TYPE, &type); if (status != AMD_COMGR_STATUS_SUCCESS) { return status; } /* If symbol type is object(Variable) add it to vector */ if ((std::strcmp(name, "") != 0) && (type == sym_info->sym_type)) { sym_info->var_names->push_back(std::string(name)); } delete[] name; return status; } bool Program::getSymbolsFromCodeObj(std::vector* var_names, amd_comgr_symbol_type_t sym_type) const { amd_comgr_status_t status = AMD_COMGR_STATUS_SUCCESS; amd_comgr_data_t dataObject; SymbolInfo sym_info; bool ret_val = true; do { /* Create comgr data */ status = amd::Comgr::create_data(AMD_COMGR_DATA_KIND_EXECUTABLE, &dataObject); if (status != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "COMGR: Cannot create comgr data \n"; ret_val = false; break; } /* Set the binary as a dataObject */ status = amd::Comgr::set_data(dataObject, static_cast(clBinary_->data().second), reinterpret_cast(clBinary_->data().first)); if (status != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "COMGR: Cannot set comgr data \n"; ret_val = false; break; } /* Pack the user data */ sym_info.sym_type = sym_type; sym_info.var_names = var_names; /* Iterate through list of symbols */ status = amd::Comgr::iterate_symbols(dataObject, getSymbolFromModule, &sym_info); if (status != AMD_COMGR_STATUS_SUCCESS) { buildLog_ += "COMGR: Cannot iterate comgr symbols \n"; ret_val = false; break; } amd::Comgr::release_data(dataObject); } while (0); return ret_val; } const bool Program::getLoweredNames(std::vector* mangledNames) const { /* Iterate thru kernel names first */ mangledNames->reserve(mangledNames->size() + kernelMetadataMap_.size()); for (auto const& kernelMeta : kernelMetadataMap_) { mangledNames->emplace_back(kernelMeta.first); } /* Itrate thru global vars */ if (!getSymbolsFromCodeObj(mangledNames, AMD_COMGR_SYMBOL_TYPE_OBJECT)) { ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_COMGR, "Cannot get Symbols from Code Obj \n"); return false; } return true; } bool Program::getDemangledName(const std::string& mangledName, std::string& demangledName) const { amd_comgr_data_t mangled_data; amd_comgr_data_t demangled_data; if (AMD_COMGR_STATUS_SUCCESS != amd::Comgr::create_data(AMD_COMGR_DATA_KIND_BYTES, &mangled_data)) return false; if (AMD_COMGR_STATUS_SUCCESS != amd::Comgr::set_data(mangled_data, mangledName.size(), mangledName.c_str())) { amd::Comgr::release_data(mangled_data); return false; } if (AMD_COMGR_STATUS_SUCCESS != amd::Comgr::demangle_symbol_name(mangled_data, &demangled_data)) { amd::Comgr::release_data(mangled_data); return false; } size_t demangled_size = 0; if (AMD_COMGR_STATUS_SUCCESS != amd::Comgr::get_data(demangled_data, &demangled_size, NULL)) { amd::Comgr::release_data(mangled_data); amd::Comgr::release_data(demangled_data); return false; } demangledName.resize(demangled_size); if (AMD_COMGR_STATUS_SUCCESS != amd::Comgr::get_data(demangled_data, &demangled_size, const_cast(demangledName.data()))) { amd::Comgr::release_data(mangled_data); amd::Comgr::release_data(demangled_data); return false; } amd::Comgr::release_data(mangled_data); amd::Comgr::release_data(demangled_data); return true; } bool Program::getGlobalFuncFromCodeObj(std::vector* func_names) const { return getSymbolsFromCodeObj(func_names, AMD_COMGR_SYMBOL_TYPE_FUNC); } bool Program::getGlobalVarFromCodeObj(std::vector* var_names) const { return getSymbolsFromCodeObj(var_names, AMD_COMGR_SYMBOL_TYPE_OBJECT); } // Init Fini Launch Lock std::recursive_mutex Program::initFiniLock_; bool Program::runInitFiniKernel(const std::vector& kernels) const { amd::HostQueue* queue = nullptr; for (const auto& kernel : kernels) { std::scoped_lock sl(initFiniLock_); if (queue == nullptr) { queue = new amd::HostQueue(device_().context(), device_(), 0); if (queue == nullptr) { LogError("Unable to create queue"); return false; } queue->create(); } LogPrintfInfo("%s is marked init/fini", kernel->name().c_str()); size_t globalWorkOffset[3] = {0}; size_t globalWorkSize[3] = {1, 1, 1}; size_t localWorkSize[3] = {1, 1, 1}; amd::NDRangeContainer ndrange(3, globalWorkOffset, globalWorkSize, localWorkSize); amd::Command::EventWaitList waitList; auto symbol = owner_.findSymbol(kernel->name().c_str()); amd::Kernel* k = new amd::Kernel(owner_, *symbol, kernel->name().c_str()); if (!k) { queue->release(); LogError("Unable to create kernel"); return false; } amd::NDRangeKernelCommand* kernelCommand = new amd::NDRangeKernelCommand(*queue, waitList, *k, ndrange); if (!kernelCommand) { LogError("Unale to allocate memory to launch kernel"); k->release(); queue->release(); return false; } if (CL_SUCCESS != kernelCommand->captureOpenCLArgsAndValidate()) { LogError("Kernel Capture and Validate failed"); kernelCommand->release(); k->release(); queue->release(); return false; } kernelCommand->enqueue(); queue->finish(); k->release(); kernelCommand->release(); } if (queue != nullptr) { queue->release(); } return true; } bool Program::runInitKernels() { return runInitFiniKernel(initKernels_); } bool Program::runFiniKernels() { return runInitFiniKernel(finiKernels_); } } /* namespace amd::device*/