| // Copyright 2021 The IREE Authors | |
| // | |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| #include "compiler/plugins/target/ROCM/ROCMTargetUtils.h" | |
| #include "iree/compiler/Codegen/Utils/GPUUtils.h" | |
| #include "iree/compiler/Dialect/HAL/Target/LLVMLinkerUtils.h" | |
| #include "iree/compiler/Utils/ToolUtils.h" | |
| #include "llvm/IR/Constants.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/IRReader/IRReader.h" | |
| #include "llvm/Linker/Linker.h" | |
| #include "llvm/Support/FileUtilities.h" | |
| #include "llvm/Support/Path.h" | |
| #include "llvm/Support/Process.h" | |
| #include "llvm/Support/Program.h" | |
| #include "llvm/Support/SourceMgr.h" | |
| #include "llvm/Support/WithColor.h" | |
| #include "llvm/Transforms/IPO/Internalize.h" | |
| #include "mlir/Support/FileUtilities.h" | |
| #include "mlir/Support/LogicalResult.h" | |
| namespace mlir::iree_compiler::IREE::HAL { | |
| static std::unique_ptr<llvm::Module> | |
| loadIRModule(Location loc, const std::string &filename, | |
| llvm::LLVMContext *llvm_context) { | |
| llvm::SMDiagnostic diagnostic; | |
| std::unique_ptr<llvm::Module> module( | |
| llvm::parseIRFile(llvm::StringRef(filename.data(), filename.size()), | |
| diagnostic, *llvm_context)); | |
| if (!module) { | |
| mlir::emitError(loc) << "error loading HIP LLVM module: " | |
| << diagnostic.getFilename().str() << ":" | |
| << diagnostic.getLineNo() << ":" | |
| << diagnostic.getColumnNo() << ": " | |
| << diagnostic.getMessage().str(); | |
| return {}; | |
| } | |
| return module; | |
| } | |
| static LogicalResult linkWithBitcodeFiles(Location loc, llvm::Module *module, | |
| ArrayRef<std::string> bitcodePaths) { | |
| if (bitcodePaths.empty()) | |
| return success(); | |
| llvm::Linker linker(*module); | |
| for (auto &bitcodePath : bitcodePaths) { | |
| if (!llvm::sys::fs::exists(bitcodePath)) { | |
| return mlir::emitError(loc) | |
| << "AMD bitcode module is required by this module but was " | |
| "not found at " | |
| << bitcodePath; | |
| } | |
| std::unique_ptr<llvm::Module> bitcodeModule = | |
| loadIRModule(loc, bitcodePath, &module->getContext()); | |
| if (!bitcodeModule) | |
| return failure(); | |
| // Ignore the data layout of the module we're importing. This avoids a | |
| // warning from the linker. | |
| bitcodeModule->setDataLayout(module->getDataLayout()); | |
| if (linker.linkInModule( | |
| std::move(bitcodeModule), llvm::Linker::Flags::LinkOnlyNeeded, | |
| [](llvm::Module &M, const llvm::StringSet<> &GVS) { | |
| llvm::internalizeModule(M, [&GVS](const llvm::GlobalValue &GV) { | |
| return !GV.hasName() || (GVS.count(GV.getName()) == 0); | |
| }); | |
| })) { | |
| return mlir::emitError(loc) << "llvm link of AMD bitcode failed"; | |
| } | |
| } | |
| return success(); | |
| } | |
| static LogicalResult linkBitcodeFile(Location loc, llvm::Linker &linker, | |
| unsigned linkerFlags, StringRef path, | |
| llvm::TargetMachine &targetMachine, | |
| llvm::LLVMContext &context) { | |
| auto bitcodeBufferRef = llvm::MemoryBuffer::getFile(path); | |
| if (auto ec = bitcodeBufferRef.getError()) { | |
| return mlir::emitError(loc) << "failed reading user bitcode file `" << path | |
| << "`: " << ec.message(); | |
| } | |
| auto setAlwaysInline = [&](llvm::Module &module) { | |
| if (targetMachine.getTargetCPU().contains("gfx10") || | |
| targetMachine.getTargetCPU().contains("gfx11")) { | |
| // some ROCM/HIP functions for gfx10 or gfx11 has accuracy issue if | |
| // inlined. | |
| return; | |
| } | |
| for (auto &func : module.getFunctionList()) { | |
| // Some ROCM/HIP builtin functions have Optnone and NoInline for default. | |
| if (targetMachine.getTargetTriple().isAMDGCN()) { | |
| if (func.hasFnAttribute(llvm::Attribute::OptimizeNone)) { | |
| func.removeFnAttr(llvm::Attribute::OptimizeNone); | |
| } | |
| if (targetMachine.getTargetTriple().isAMDGCN() && | |
| func.hasFnAttribute(llvm::Attribute::NoInline)) { | |
| func.removeFnAttr(llvm::Attribute::NoInline); | |
| } | |
| } | |
| func.addFnAttr(llvm::Attribute::AlwaysInline); | |
| } | |
| }; | |
| if (failed(linkBitcodeModule( | |
| loc, linker, linkerFlags, targetMachine, path, | |
| llvm::parseBitcodeFile(*bitcodeBufferRef->get(), context), | |
| setAlwaysInline))) { | |
| return mlir::emitError(loc) << "failed linking in user bitcode file `" | |
| << path << "` for target triple '" | |
| << targetMachine.getTargetTriple().str() << "'"; | |
| } | |
| return success(); | |
| } | |
| static std::vector<std::string> getUkernelPaths(StringRef enabledUkernelsStr, | |
| StringRef targetChip, | |
| StringRef bitcodePath) { | |
| std::vector<std::string> selectedUkernelNames; | |
| if (enabledUkernelsStr == "all") { | |
| const char *allUkernelNames[] = {"argmax"}; | |
| size_t numUkernels = sizeof(allUkernelNames) / sizeof(allUkernelNames[0]); | |
| for (int i = 0; i < numUkernels; i++) { | |
| selectedUkernelNames.push_back(allUkernelNames[i]); | |
| } | |
| } else { | |
| while (!enabledUkernelsStr.empty()) { | |
| auto split = enabledUkernelsStr.split(','); | |
| selectedUkernelNames.push_back(split.first.str()); | |
| enabledUkernelsStr = split.second; | |
| } | |
| } | |
| // Construct full path to ROCDL bitcode libraries. | |
| std::vector<std::string> result; | |
| std::string app = "/"; | |
| for (auto &kernelName : selectedUkernelNames) { | |
| std::string filename = | |
| "rocm_" + kernelName + "_ukernel_" + targetChip.str(); | |
| result.push_back(bitcodePath.str() + app + filename + ".bc"); | |
| } | |
| return result; | |
| } | |
| static void overridePlatformGlobal(llvm::Module *module, StringRef globalName, | |
| uint32_t newValue, llvm::Type *globalTy) { | |
| // NOTE: the global will not be defined if it is not used in the module. | |
| auto *globalValue = module->getNamedGlobal(globalName); | |
| if (!globalValue) | |
| return; | |
| globalValue->setDSOLocal(true); | |
| globalValue->setConstant(true); | |
| globalValue->setInitializer(llvm::ConstantInt::get( | |
| globalValue->getValueType(), | |
| APInt(globalValue->getValueType()->getIntegerBitWidth(), newValue))); | |
| } | |
| LogicalResult setHIPGlobals(Location loc, llvm::Module *module, | |
| StringRef targetChip) { | |
| // Link target chip ISA version as global. | |
| const int kLenOfChipPrefix = 3; | |
| auto chipId = targetChip.substr(kLenOfChipPrefix); | |
| // i.e gfx90a -> 9000 series. | |
| int chipArch = stoi(chipId.substr(0, chipId.size() - 1).str()) * 100; | |
| // Oldest GFX arch supported is gfx60x. | |
| if (chipArch < 6000) | |
| return failure(); | |
| // Latest GFX arch supported is gfx115x. | |
| if (chipArch > 11000) | |
| return failure(); | |
| // Get chip code from suffix. i.e gfx1103 -> `3`. | |
| // gfx90a -> `a` == `10`. | |
| // gfx90c -> `c` == `12`. | |
| auto chipSuffix = chipId.substr(chipId.size() - 1); | |
| uint32_t chipCode = 0; | |
| if (chipSuffix == "a") { | |
| chipCode = chipArch + 10; | |
| } else if (chipSuffix == "c") { | |
| chipCode = chipArch + 12; | |
| } else { | |
| if (!std::isdigit(chipSuffix[0])) | |
| return mlir::emitError(loc) | |
| << "error linking module with globals: unrecognized chip suffix '" | |
| << chipSuffix << "' for " << targetChip; | |
| chipCode = chipArch + stoi(chipSuffix.str()); | |
| } | |
| auto *int32Type = llvm::Type::getInt32Ty(module->getContext()); | |
| overridePlatformGlobal(module, "__oclc_ISA_version", chipCode, int32Type); | |
| // Link oclc configurations as globals. | |
| auto *boolType = llvm::Type::getInt8Ty(module->getContext()); | |
| static const std::vector<std::pair<std::string, bool>> rocdlGlobalParams( | |
| {{"__oclc_finite_only_opt", false}, | |
| {"__oclc_daz_opt", false}, | |
| {"__oclc_correctly_rounded_sqrt32", true}, | |
| {"__oclc_unsafe_math_opt", false}, | |
| {"__oclc_wavefrontsize64", true}}); | |
| for (auto &globalParam : rocdlGlobalParams) { | |
| overridePlatformGlobal(module, globalParam.first, globalParam.second, | |
| boolType); | |
| } | |
| return success(); | |
| } | |
| LogicalResult linkHIPBitcodeIfNeeded(Location loc, llvm::Module *module, | |
| StringRef targetChip, | |
| StringRef bitcodePath) { | |
| bool usesOCML = false; | |
| bool usesOCKL = false; | |
| for (const llvm::Function &function : module->functions()) { | |
| if (!function.isIntrinsic() && function.isDeclaration()) { | |
| auto functionName = function.getName(); | |
| if (functionName.starts_with("__ocml_")) | |
| usesOCML = true; | |
| else if (functionName.starts_with("__ockl_")) | |
| usesOCKL = true; | |
| } | |
| } | |
| // Link externally-provided bitcode files when used. | |
| SmallVector<std::string> bitcodePaths; | |
| if (usesOCML) { | |
| bitcodePaths.push_back( | |
| (bitcodePath + llvm::sys::path::get_separator() + "ocml.bc").str()); | |
| } | |
| if (usesOCKL) { | |
| bitcodePaths.push_back( | |
| (bitcodePath + llvm::sys::path::get_separator() + "ockl.bc").str()); | |
| } | |
| return linkWithBitcodeFiles(loc, module, bitcodePaths); | |
| } | |
| // Links optimized Ukernel bitcode into the given module if the module needs it. | |
| LogicalResult linkUkernelBitcodeFiles(Location loc, llvm::Module *module, | |
| StringRef enabledUkernelsStr, | |
| StringRef targetChip, | |
| StringRef bitcodePath, | |
| unsigned linkerFlags, | |
| llvm::TargetMachine &targetMachine) { | |
| // Early exit if Ukernel not supported on target chip. | |
| if (!iree_compiler::hasUkernelSupportedRocmArch(targetChip)) { | |
| return mlir::emitError(loc) | |
| << "ukernel '" << enabledUkernelsStr | |
| << "' not supported on target chip: " << targetChip; | |
| } | |
| std::vector<std::string> ukernelPaths = | |
| getUkernelPaths(enabledUkernelsStr, targetChip, bitcodePath); | |
| llvm::Linker linker(*module); | |
| for (auto &path : ukernelPaths) { | |
| if (failed(linkBitcodeFile(loc, linker, linkerFlags, StringRef(path), | |
| targetMachine, module->getContext()))) | |
| return failure(); | |
| } | |
| return success(); | |
| } | |
| // Link object file using lld lnker to generate code object | |
| // Inspiration from this section comes from LLVM-PROJECT-MLIR by | |
| // ROCmSoftwarePlatform | |
| // https://github.com/ROCmSoftwarePlatform/rocMLIR/blob/0ec7b2176308229ac05f1594f5b5019d58cd9e15/mlir/lib/ExecutionEngine/ROCm/BackendUtils.cpp | |
| std::string createHsaco(Location loc, const std::string isa, StringRef name) { | |
| // Save the ISA binary to a temp file. | |
| int tempIsaBinaryFd = -1; | |
| SmallString<128> tempIsaBinaryFilename; | |
| std::error_code ec = llvm::sys::fs::createTemporaryFile( | |
| "kernel", "o", tempIsaBinaryFd, tempIsaBinaryFilename); | |
| if (ec) { | |
| mlir::emitError(loc) << "temporary file for ISA binary creation error"; | |
| return {}; | |
| } | |
| llvm::FileRemover cleanupIsaBinary(tempIsaBinaryFilename); | |
| llvm::raw_fd_ostream tempIsaBinaryOs(tempIsaBinaryFd, true); | |
| tempIsaBinaryOs << isa; | |
| tempIsaBinaryOs.close(); | |
| // Create a temp file for HSA code object. | |
| int tempHsacoFD = -1; | |
| SmallString<128> tempHsacoFilename; | |
| ec = llvm::sys::fs::createTemporaryFile("kernel", "hsaco", tempHsacoFD, | |
| tempHsacoFilename); | |
| if (ec) { | |
| mlir::emitError(loc) << "temporary file for HSA code object creation error"; | |
| return {}; | |
| } | |
| llvm::FileRemover cleanupHsaco(tempHsacoFilename); | |
| // Invoke lld. Expect a true return value from lld. | |
| const SmallVector<std::string> &toolNames{"iree-lld", "lld"}; | |
| std::string lldProgram = findTool(toolNames); | |
| if (lldProgram.empty()) { | |
| mlir::emitError(loc) << "unable to find iree-lld"; | |
| return {}; | |
| } | |
| std::vector<llvm::StringRef> lldArgs{ | |
| lldProgram, | |
| llvm::StringRef("-flavor"), | |
| llvm::StringRef("gnu"), | |
| llvm::StringRef("-shared"), | |
| tempIsaBinaryFilename.str(), | |
| llvm::StringRef("-o"), | |
| tempHsacoFilename.str(), | |
| }; | |
| // Execute LLD. | |
| std::string errorMessage; | |
| int lldResult = llvm::sys::ExecuteAndWait( | |
| unescapeCommandLineComponent(lldProgram), | |
| llvm::ArrayRef<llvm::StringRef>(lldArgs), | |
| llvm::StringRef("LLD_VERSION=IREE"), {}, 0, 0, &errorMessage); | |
| if (lldResult) { | |
| mlir::emitError(loc) << "iree-lld execute fail:" << errorMessage | |
| << "Error Code:" << lldResult; | |
| return {}; | |
| } | |
| // Load the HSA code object. | |
| auto hsacoFile = mlir::openInputFile(tempHsacoFilename); | |
| if (!hsacoFile) { | |
| mlir::emitError(loc) << "read HSA code object from temp file error"; | |
| return {}; | |
| } | |
| std::string strHSACO(hsacoFile->getBuffer().begin(), | |
| hsacoFile->getBuffer().end()); | |
| return strHSACO; | |
| } | |
| } // namespace mlir::iree_compiler::IREE::HAL |