1 //===- LowerGPUToHSACO.cpp - Convert GPU kernel to HSACO blob -------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file implements a pass that serializes a gpu module into HSAco blob and 10 // adds that blob as a string attribute of the module. 11 // 12 //===----------------------------------------------------------------------===// 13 #include "mlir/Dialect/GPU/Passes.h" 14 15 #if MLIR_GPU_TO_HSACO_PASS_ENABLE 16 #include "mlir/Pass/Pass.h" 17 #include "mlir/Support/FileUtilities.h" 18 #include "mlir/Target/LLVMIR/Dialect/ROCDL/ROCDLToLLVMIRTranslation.h" 19 #include "mlir/Target/LLVMIR/Export.h" 20 21 #include "llvm/MC/MCAsmBackend.h" 22 #include "llvm/MC/MCAsmInfo.h" 23 #include "llvm/MC/MCCodeEmitter.h" 24 #include "llvm/MC/MCContext.h" 25 #include "llvm/MC/MCObjectFileInfo.h" 26 #include "llvm/MC/MCObjectWriter.h" 27 #include "llvm/MC/MCParser/MCTargetAsmParser.h" 28 #include "llvm/MC/MCStreamer.h" 29 #include "llvm/MC/MCSubtargetInfo.h" 30 31 #include "llvm/MC/TargetRegistry.h" 32 #include "llvm/Support/FileUtilities.h" 33 #include "llvm/Support/LineIterator.h" 34 #include "llvm/Support/Program.h" 35 #include "llvm/Support/TargetSelect.h" 36 #include "llvm/Support/WithColor.h" 37 #include "llvm/Target/TargetOptions.h" 38 39 #include "lld/Common/Driver.h" 40 41 #include "hip/hip_version.h" 42 43 #include <mutex> 44 45 using namespace mlir; 46 47 namespace { 48 class SerializeToHsacoPass 49 : public PassWrapper<SerializeToHsacoPass, gpu::SerializeToBlobPass> { 50 public: 51 SerializeToHsacoPass(); 52 53 StringRef getArgument() const override { return "gpu-to-hsaco"; } 54 StringRef getDescription() const override { 55 return "Lower GPU kernel function to HSACO binary annotations"; 56 } 57 58 private: 59 void getDependentDialects(DialectRegistry ®istry) const override; 60 61 // Serializes ROCDL to HSACO. 62 std::unique_ptr<std::vector<char>> 63 serializeISA(const std::string &isa) override; 64 65 std::unique_ptr<SmallVectorImpl<char>> assembleIsa(const std::string &isa); 66 std::unique_ptr<std::vector<char>> 67 createHsaco(const SmallVectorImpl<char> &isaBinary); 68 }; 69 } // namespace 70 71 static std::string getDefaultChip() { 72 const char kDefaultChip[] = "gfx900"; 73 74 // Locate rocm_agent_enumerator. 75 const char kRocmAgentEnumerator[] = "rocm_agent_enumerator"; 76 llvm::ErrorOr<std::string> rocmAgentEnumerator = llvm::sys::findProgramByName( 77 kRocmAgentEnumerator, {__ROCM_PATH__ "/bin"}); 78 if (!rocmAgentEnumerator) { 79 llvm::WithColor::warning(llvm::errs()) 80 << kRocmAgentEnumerator << "couldn't be located under " << __ROCM_PATH__ 81 << "/bin\n"; 82 return kDefaultChip; 83 } 84 85 // Prepare temp file to hold the outputs. 86 int tempFd = -1; 87 SmallString<128> tempFilename; 88 if (llvm::sys::fs::createTemporaryFile("rocm_agent", "txt", tempFd, 89 tempFilename)) { 90 llvm::WithColor::warning(llvm::errs()) 91 << "temporary file for " << kRocmAgentEnumerator << " creation error\n"; 92 return kDefaultChip; 93 } 94 llvm::FileRemover cleanup(tempFilename); 95 96 // Invoke rocm_agent_enumerator. 97 std::string errorMessage; 98 SmallVector<StringRef, 2> args{"-t", "GPU"}; 99 Optional<StringRef> redirects[3] = {{""}, tempFilename.str(), {""}}; 100 int result = 101 llvm::sys::ExecuteAndWait(rocmAgentEnumerator.get(), args, llvm::None, 102 redirects, 0, 0, &errorMessage); 103 if (result) { 104 llvm::WithColor::warning(llvm::errs()) 105 << kRocmAgentEnumerator << " invocation error: " << errorMessage 106 << "\n"; 107 return kDefaultChip; 108 } 109 110 // Load and parse the result. 111 auto gfxIsaList = openInputFile(tempFilename); 112 if (!gfxIsaList) { 113 llvm::WithColor::error(llvm::errs()) 114 << "read ROCm agent list temp file error\n"; 115 return kDefaultChip; 116 } 117 for (llvm::line_iterator lines(*gfxIsaList); !lines.is_at_end(); ++lines) { 118 // Skip the line with content "gfx000". 119 if (*lines == "gfx000") 120 continue; 121 // Use the first ISA version found. 122 return lines->str(); 123 } 124 125 return kDefaultChip; 126 } 127 128 // Sets the 'option' to 'value' unless it already has a value. 129 static void maybeSetOption(Pass::Option<std::string> &option, 130 function_ref<std::string()> getValue) { 131 if (!option.hasValue()) 132 option = getValue(); 133 } 134 135 SerializeToHsacoPass::SerializeToHsacoPass() { 136 maybeSetOption(this->triple, [] { return "amdgcn-amd-amdhsa"; }); 137 maybeSetOption(this->chip, [] { 138 static auto chip = getDefaultChip(); 139 return chip; 140 }); 141 } 142 143 void SerializeToHsacoPass::getDependentDialects( 144 DialectRegistry ®istry) const { 145 registerROCDLDialectTranslation(registry); 146 gpu::SerializeToBlobPass::getDependentDialects(registry); 147 } 148 149 std::unique_ptr<SmallVectorImpl<char>> 150 SerializeToHsacoPass::assembleIsa(const std::string &isa) { 151 auto loc = getOperation().getLoc(); 152 153 SmallVector<char, 0> result; 154 llvm::raw_svector_ostream os(result); 155 156 llvm::Triple triple(llvm::Triple::normalize(this->triple)); 157 std::string error; 158 const llvm::Target *target = 159 llvm::TargetRegistry::lookupTarget(triple.normalize(), error); 160 if (!target) { 161 emitError(loc, Twine("failed to lookup target: ") + error); 162 return {}; 163 } 164 165 llvm::SourceMgr srcMgr; 166 srcMgr.AddNewSourceBuffer(llvm::MemoryBuffer::getMemBuffer(isa), 167 llvm::SMLoc()); 168 169 const llvm::MCTargetOptions mcOptions; 170 std::unique_ptr<llvm::MCRegisterInfo> mri( 171 target->createMCRegInfo(this->triple)); 172 std::unique_ptr<llvm::MCAsmInfo> mai( 173 target->createMCAsmInfo(*mri, this->triple, mcOptions)); 174 mai->setRelaxELFRelocations(true); 175 176 llvm::MCContext ctx(triple, mai.get(), mri.get(), &srcMgr, &mcOptions); 177 std::unique_ptr<llvm::MCObjectFileInfo> mofi(target->createMCObjectFileInfo( 178 ctx, /*PIC=*/false, /*LargeCodeModel=*/false)); 179 ctx.setObjectFileInfo(mofi.get()); 180 181 SmallString<128> cwd; 182 if (!llvm::sys::fs::current_path(cwd)) 183 ctx.setCompilationDir(cwd); 184 185 std::unique_ptr<llvm::MCStreamer> mcStreamer; 186 std::unique_ptr<llvm::MCInstrInfo> mcii(target->createMCInstrInfo()); 187 std::unique_ptr<llvm::MCSubtargetInfo> sti( 188 target->createMCSubtargetInfo(this->triple, this->chip, this->features)); 189 190 llvm::MCCodeEmitter *ce = target->createMCCodeEmitter(*mcii, *mri, ctx); 191 llvm::MCAsmBackend *mab = target->createMCAsmBackend(*sti, *mri, mcOptions); 192 mcStreamer.reset(target->createMCObjectStreamer( 193 triple, ctx, std::unique_ptr<llvm::MCAsmBackend>(mab), 194 mab->createObjectWriter(os), std::unique_ptr<llvm::MCCodeEmitter>(ce), 195 *sti, mcOptions.MCRelaxAll, mcOptions.MCIncrementalLinkerCompatible, 196 /*DWARFMustBeAtTheEnd*/ false)); 197 mcStreamer->setUseAssemblerInfoForParsing(true); 198 199 std::unique_ptr<llvm::MCAsmParser> parser( 200 createMCAsmParser(srcMgr, ctx, *mcStreamer, *mai)); 201 std::unique_ptr<llvm::MCTargetAsmParser> tap( 202 target->createMCAsmParser(*sti, *parser, *mcii, mcOptions)); 203 204 if (!tap) { 205 emitError(loc, "assembler initialization error"); 206 return {}; 207 } 208 209 parser->setTargetParser(*tap); 210 parser->Run(false); 211 212 return std::make_unique<SmallVector<char, 0>>(std::move(result)); 213 } 214 215 std::unique_ptr<std::vector<char>> 216 SerializeToHsacoPass::createHsaco(const SmallVectorImpl<char> &isaBinary) { 217 auto loc = getOperation().getLoc(); 218 219 // Save the ISA binary to a temp file. 220 int tempIsaBinaryFd = -1; 221 SmallString<128> tempIsaBinaryFilename; 222 if (llvm::sys::fs::createTemporaryFile("kernel", "o", tempIsaBinaryFd, 223 tempIsaBinaryFilename)) { 224 emitError(loc, "temporary file for ISA binary creation error"); 225 return {}; 226 } 227 llvm::FileRemover cleanupIsaBinary(tempIsaBinaryFilename); 228 llvm::raw_fd_ostream tempIsaBinaryOs(tempIsaBinaryFd, true); 229 tempIsaBinaryOs << StringRef(isaBinary.data(), isaBinary.size()); 230 tempIsaBinaryOs.close(); 231 232 // Create a temp file for HSA code object. 233 int tempHsacoFD = -1; 234 SmallString<128> tempHsacoFilename; 235 if (llvm::sys::fs::createTemporaryFile("kernel", "hsaco", tempHsacoFD, 236 tempHsacoFilename)) { 237 emitError(loc, "temporary file for HSA code object creation error"); 238 return {}; 239 } 240 llvm::FileRemover cleanupHsaco(tempHsacoFilename); 241 242 { 243 static std::mutex mutex; 244 const std::lock_guard<std::mutex> lock(mutex); 245 // Invoke lld. Expect a true return value from lld. 246 if (!lld::elf::link({"ld.lld", "-shared", tempIsaBinaryFilename.c_str(), 247 "-o", tempHsacoFilename.c_str()}, 248 /*canEarlyExit=*/false, llvm::outs(), llvm::errs())) { 249 emitError(loc, "lld invocation error"); 250 return {}; 251 } 252 } 253 254 // Load the HSA code object. 255 auto hsacoFile = openInputFile(tempHsacoFilename); 256 if (!hsacoFile) { 257 emitError(loc, "read HSA code object from temp file error"); 258 return {}; 259 } 260 261 StringRef buffer = hsacoFile->getBuffer(); 262 return std::make_unique<std::vector<char>>(buffer.begin(), buffer.end()); 263 } 264 265 std::unique_ptr<std::vector<char>> 266 SerializeToHsacoPass::serializeISA(const std::string &isa) { 267 auto isaBinary = assembleIsa(isa); 268 if (!isaBinary) 269 return {}; 270 return createHsaco(*isaBinary); 271 } 272 273 // Register pass to serialize GPU kernel functions to a HSACO binary annotation. 274 void mlir::registerGpuSerializeToHsacoPass() { 275 PassRegistration<SerializeToHsacoPass> registerSerializeToHSACO( 276 [] { 277 // Initialize LLVM AMDGPU backend. 278 LLVMInitializeAMDGPUAsmParser(); 279 LLVMInitializeAMDGPUAsmPrinter(); 280 LLVMInitializeAMDGPUTarget(); 281 LLVMInitializeAMDGPUTargetInfo(); 282 LLVMInitializeAMDGPUTargetMC(); 283 284 return std::make_unique<SerializeToHsacoPass>(); 285 }); 286 } 287 #else // MLIR_GPU_TO_HSACO_PASS_ENABLE 288 void mlir::registerGpuSerializeToHsacoPass() {} 289 #endif // MLIR_GPU_TO_HSACO_PASS_ENABLE 290