xref: /llvm-project/clang/lib/Driver/ToolChains/HIPUtility.cpp (revision 974f678d31969cf83a5b2828cc63120734ac82f3)
1 //===--- HIPUtility.cpp - Common HIP Tool Chain Utilities -------*- C++ -*-===//
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 #include "HIPUtility.h"
10 #include "Clang.h"
11 #include "CommonArgs.h"
12 #include "clang/Driver/Compilation.h"
13 #include "clang/Driver/Options.h"
14 #include "llvm/ADT/StringExtras.h"
15 #include "llvm/ADT/StringRef.h"
16 #include "llvm/Object/Archive.h"
17 #include "llvm/Object/ObjectFile.h"
18 #include "llvm/Support/MD5.h"
19 #include "llvm/Support/MemoryBuffer.h"
20 #include "llvm/Support/Path.h"
21 #include "llvm/Support/raw_ostream.h"
22 #include "llvm/TargetParser/Triple.h"
23 #include <deque>
24 #include <set>
25 
26 using namespace clang;
27 using namespace clang::driver;
28 using namespace clang::driver::tools;
29 using namespace llvm::opt;
30 using llvm::dyn_cast;
31 
32 #if defined(_WIN32) || defined(_WIN64)
33 #define NULL_FILE "nul"
34 #else
35 #define NULL_FILE "/dev/null"
36 #endif
37 
38 namespace {
39 const unsigned HIPCodeObjectAlign = 4096;
40 } // namespace
41 
42 // Constructs a triple string for clang offload bundler.
43 static std::string normalizeForBundler(const llvm::Triple &T,
44                                        bool HasTargetID) {
45   return HasTargetID ? (T.getArchName() + "-" + T.getVendorName() + "-" +
46                         T.getOSName() + "-" + T.getEnvironmentName())
47                            .str()
48                      : T.normalize();
49 }
50 
51 // Collect undefined __hip_fatbin* and __hip_gpubin_handle* symbols from all
52 // input object or archive files.
53 class HIPUndefinedFatBinSymbols {
54 public:
55   HIPUndefinedFatBinSymbols(const Compilation &C,
56                             const llvm::opt::ArgList &Args_)
57       : C(C), Args(Args_),
58         DiagID(C.getDriver().getDiags().getCustomDiagID(
59             DiagnosticsEngine::Error,
60             "Error collecting HIP undefined fatbin symbols: %0")),
61         Quiet(C.getArgs().hasArg(options::OPT__HASH_HASH_HASH)),
62         Verbose(C.getArgs().hasArg(options::OPT_v)) {
63     populateSymbols();
64     processStaticLibraries();
65     if (Verbose) {
66       for (const auto &Name : FatBinSymbols)
67         llvm::errs() << "Found undefined HIP fatbin symbol: " << Name << "\n";
68       for (const auto &Name : GPUBinHandleSymbols)
69         llvm::errs() << "Found undefined HIP gpubin handle symbol: " << Name
70                      << "\n";
71     }
72   }
73 
74   const std::set<std::string> &getFatBinSymbols() const {
75     return FatBinSymbols;
76   }
77 
78   const std::set<std::string> &getGPUBinHandleSymbols() const {
79     return GPUBinHandleSymbols;
80   }
81 
82   // Collect symbols from static libraries specified by -l options.
83   void processStaticLibraries() {
84     llvm::SmallVector<llvm::StringRef, 16> LibNames;
85     llvm::SmallVector<llvm::StringRef, 16> LibPaths;
86     llvm::SmallVector<llvm::StringRef, 16> ExactLibNames;
87     llvm::Triple Triple(C.getDriver().getTargetTriple());
88     bool IsMSVC = Triple.isWindowsMSVCEnvironment();
89     llvm::StringRef Ext = IsMSVC ? ".lib" : ".a";
90 
91     for (const auto *Arg : Args.filtered(options::OPT_l)) {
92       llvm::StringRef Value = Arg->getValue();
93       if (Value.starts_with(":"))
94         ExactLibNames.push_back(Value.drop_front());
95       else
96         LibNames.push_back(Value);
97     }
98     for (const auto *Arg : Args.filtered(options::OPT_L)) {
99       auto Path = Arg->getValue();
100       LibPaths.push_back(Path);
101       if (Verbose)
102         llvm::errs() << "HIP fatbin symbol search uses library path:  " << Path
103                      << "\n";
104     }
105 
106     auto ProcessLib = [&](llvm::StringRef LibName, bool IsExact) {
107       llvm::SmallString<256> FullLibName(
108           IsExact  ? Twine(LibName).str()
109           : IsMSVC ? (Twine(LibName) + Ext).str()
110                    : (Twine("lib") + LibName + Ext).str());
111 
112       bool Found = false;
113       for (const auto Path : LibPaths) {
114         llvm::SmallString<256> FullPath = Path;
115         llvm::sys::path::append(FullPath, FullLibName);
116 
117         if (llvm::sys::fs::exists(FullPath)) {
118           if (Verbose)
119             llvm::errs() << "HIP fatbin symbol search found library: "
120                          << FullPath << "\n";
121           auto BufferOrErr = llvm::MemoryBuffer::getFile(FullPath);
122           if (!BufferOrErr) {
123             errorHandler(llvm::errorCodeToError(BufferOrErr.getError()));
124             continue;
125           }
126           processInput(BufferOrErr.get()->getMemBufferRef());
127           Found = true;
128           break;
129         }
130       }
131       if (!Found && Verbose)
132         llvm::errs() << "HIP fatbin symbol search could not find library: "
133                      << FullLibName << "\n";
134     };
135 
136     for (const auto LibName : ExactLibNames)
137       ProcessLib(LibName, true);
138 
139     for (const auto LibName : LibNames)
140       ProcessLib(LibName, false);
141   }
142 
143 private:
144   const Compilation &C;
145   const llvm::opt::ArgList &Args;
146   unsigned DiagID;
147   bool Quiet;
148   bool Verbose;
149   std::set<std::string> FatBinSymbols;
150   std::set<std::string> GPUBinHandleSymbols;
151   std::set<std::string, std::less<>> DefinedFatBinSymbols;
152   std::set<std::string, std::less<>> DefinedGPUBinHandleSymbols;
153   const std::string FatBinPrefix = "__hip_fatbin";
154   const std::string GPUBinHandlePrefix = "__hip_gpubin_handle";
155 
156   void populateSymbols() {
157     std::deque<const Action *> WorkList;
158     std::set<const Action *> Visited;
159 
160     for (const auto &Action : C.getActions())
161       WorkList.push_back(Action);
162 
163     while (!WorkList.empty()) {
164       const Action *CurrentAction = WorkList.front();
165       WorkList.pop_front();
166 
167       if (!CurrentAction || !Visited.insert(CurrentAction).second)
168         continue;
169 
170       if (const auto *IA = dyn_cast<InputAction>(CurrentAction)) {
171         std::string ID = IA->getId().str();
172         if (!ID.empty()) {
173           ID = llvm::utohexstr(llvm::MD5Hash(ID), /*LowerCase=*/true);
174           FatBinSymbols.insert((FatBinPrefix + Twine('_') + ID).str());
175           GPUBinHandleSymbols.insert(
176               (GPUBinHandlePrefix + Twine('_') + ID).str());
177           continue;
178         }
179         if (IA->getInputArg().getNumValues() == 0)
180           continue;
181         const char *Filename = IA->getInputArg().getValue();
182         if (!Filename)
183           continue;
184         auto BufferOrErr = llvm::MemoryBuffer::getFile(Filename);
185         // Input action could be options to linker, therefore, ignore it
186         // if cannot read it. If it turns out to be a file that cannot be read,
187         // the error will be caught by the linker.
188         if (!BufferOrErr)
189           continue;
190 
191         processInput(BufferOrErr.get()->getMemBufferRef());
192       } else
193         WorkList.insert(WorkList.end(), CurrentAction->getInputs().begin(),
194                         CurrentAction->getInputs().end());
195     }
196   }
197 
198   void processInput(const llvm::MemoryBufferRef &Buffer) {
199     // Try processing as object file first.
200     auto ObjFileOrErr = llvm::object::ObjectFile::createObjectFile(Buffer);
201     if (ObjFileOrErr) {
202       processSymbols(**ObjFileOrErr);
203       return;
204     }
205 
206     // Then try processing as archive files.
207     llvm::consumeError(ObjFileOrErr.takeError());
208     auto ArchiveOrErr = llvm::object::Archive::create(Buffer);
209     if (ArchiveOrErr) {
210       llvm::Error Err = llvm::Error::success();
211       llvm::object::Archive &Archive = *ArchiveOrErr.get();
212       for (auto &Child : Archive.children(Err)) {
213         auto ChildBufOrErr = Child.getMemoryBufferRef();
214         if (ChildBufOrErr)
215           processInput(*ChildBufOrErr);
216         else
217           errorHandler(ChildBufOrErr.takeError());
218       }
219 
220       if (Err)
221         errorHandler(std::move(Err));
222       return;
223     }
224 
225     // Ignore other files.
226     llvm::consumeError(ArchiveOrErr.takeError());
227   }
228 
229   void processSymbols(const llvm::object::ObjectFile &Obj) {
230     for (const auto &Symbol : Obj.symbols()) {
231       auto FlagOrErr = Symbol.getFlags();
232       if (!FlagOrErr) {
233         errorHandler(FlagOrErr.takeError());
234         continue;
235       }
236 
237       auto NameOrErr = Symbol.getName();
238       if (!NameOrErr) {
239         errorHandler(NameOrErr.takeError());
240         continue;
241       }
242       llvm::StringRef Name = *NameOrErr;
243 
244       bool isUndefined =
245           FlagOrErr.get() & llvm::object::SymbolRef::SF_Undefined;
246       bool isFatBinSymbol = Name.starts_with(FatBinPrefix);
247       bool isGPUBinHandleSymbol = Name.starts_with(GPUBinHandlePrefix);
248 
249       // Handling for defined symbols
250       if (!isUndefined) {
251         if (isFatBinSymbol) {
252           DefinedFatBinSymbols.insert(Name.str());
253           FatBinSymbols.erase(Name.str());
254         } else if (isGPUBinHandleSymbol) {
255           DefinedGPUBinHandleSymbols.insert(Name.str());
256           GPUBinHandleSymbols.erase(Name.str());
257         }
258         continue;
259       }
260 
261       // Add undefined symbols if they are not in the defined sets
262       if (isFatBinSymbol &&
263           DefinedFatBinSymbols.find(Name) == DefinedFatBinSymbols.end())
264         FatBinSymbols.insert(Name.str());
265       else if (isGPUBinHandleSymbol && DefinedGPUBinHandleSymbols.find(Name) ==
266                                            DefinedGPUBinHandleSymbols.end())
267         GPUBinHandleSymbols.insert(Name.str());
268     }
269   }
270 
271   void errorHandler(llvm::Error Err) {
272     if (Quiet)
273       return;
274     C.getDriver().Diag(DiagID) << llvm::toString(std::move(Err));
275   }
276 };
277 
278 // Construct a clang-offload-bundler command to bundle code objects for
279 // different devices into a HIP fat binary.
280 void HIP::constructHIPFatbinCommand(Compilation &C, const JobAction &JA,
281                                     llvm::StringRef OutputFileName,
282                                     const InputInfoList &Inputs,
283                                     const llvm::opt::ArgList &Args,
284                                     const Tool &T) {
285   // Construct clang-offload-bundler command to bundle object files for
286   // for different GPU archs.
287   ArgStringList BundlerArgs;
288   BundlerArgs.push_back(Args.MakeArgString("-type=o"));
289   BundlerArgs.push_back(
290       Args.MakeArgString("-bundle-align=" + Twine(HIPCodeObjectAlign)));
291 
292   // ToDo: Remove the dummy host binary entry which is required by
293   // clang-offload-bundler.
294   std::string BundlerTargetArg = "-targets=host-x86_64-unknown-linux-gnu";
295   // AMDGCN:
296   // For code object version 2 and 3, the offload kind in bundle ID is 'hip'
297   // for backward compatibility. For code object version 4 and greater, the
298   // offload kind in bundle ID is 'hipv4'.
299   std::string OffloadKind = "hip";
300   auto &TT = T.getToolChain().getTriple();
301   if (TT.isAMDGCN() && getAMDGPUCodeObjectVersion(C.getDriver(), Args) >= 4)
302     OffloadKind = OffloadKind + "v4";
303   for (const auto &II : Inputs) {
304     const auto *A = II.getAction();
305     auto ArchStr = llvm::StringRef(A->getOffloadingArch());
306     BundlerTargetArg += ',' + OffloadKind + '-';
307     if (ArchStr == "amdgcnspirv")
308       BundlerTargetArg +=
309           normalizeForBundler(llvm::Triple("spirv64-amd-amdhsa"), true);
310     else
311       BundlerTargetArg += normalizeForBundler(TT, !ArchStr.empty());
312     if (!ArchStr.empty())
313       BundlerTargetArg += '-' + ArchStr.str();
314   }
315   BundlerArgs.push_back(Args.MakeArgString(BundlerTargetArg));
316 
317   // Use a NULL file as input for the dummy host binary entry
318   std::string BundlerInputArg = "-input=" NULL_FILE;
319   BundlerArgs.push_back(Args.MakeArgString(BundlerInputArg));
320   for (const auto &II : Inputs) {
321     BundlerInputArg = std::string("-input=") + II.getFilename();
322     BundlerArgs.push_back(Args.MakeArgString(BundlerInputArg));
323   }
324 
325   std::string Output = std::string(OutputFileName);
326   auto *BundlerOutputArg =
327       Args.MakeArgString(std::string("-output=").append(Output));
328   BundlerArgs.push_back(BundlerOutputArg);
329 
330   addOffloadCompressArgs(Args, BundlerArgs);
331 
332   const char *Bundler = Args.MakeArgString(
333       T.getToolChain().GetProgramPath("clang-offload-bundler"));
334   C.addCommand(std::make_unique<Command>(
335       JA, T, ResponseFileSupport::None(), Bundler, BundlerArgs, Inputs,
336       InputInfo(&JA, Args.MakeArgString(Output))));
337 }
338 
339 /// Add Generated HIP Object File which has device images embedded into the
340 /// host to the argument list for linking. Using MC directives, embed the
341 /// device code and also define symbols required by the code generation so that
342 /// the image can be retrieved at runtime.
343 void HIP::constructGenerateObjFileFromHIPFatBinary(
344     Compilation &C, const InputInfo &Output, const InputInfoList &Inputs,
345     const ArgList &Args, const JobAction &JA, const Tool &T) {
346   const Driver &D = C.getDriver();
347   std::string Name = std::string(llvm::sys::path::stem(Output.getFilename()));
348 
349   // Create Temp Object File Generator,
350   // Offload Bundled file and Bundled Object file.
351   // Keep them if save-temps is enabled.
352   const char *ObjinFile;
353   const char *BundleFile;
354   if (D.isSaveTempsEnabled()) {
355     ObjinFile = C.getArgs().MakeArgString(Name + ".mcin");
356     BundleFile = C.getArgs().MakeArgString(Name + ".hipfb");
357   } else {
358     auto TmpNameMcin = D.GetTemporaryPath(Name, "mcin");
359     ObjinFile = C.addTempFile(C.getArgs().MakeArgString(TmpNameMcin));
360     auto TmpNameFb = D.GetTemporaryPath(Name, "hipfb");
361     BundleFile = C.addTempFile(C.getArgs().MakeArgString(TmpNameFb));
362   }
363   HIP::constructHIPFatbinCommand(C, JA, BundleFile, Inputs, Args, T);
364 
365   // Create a buffer to write the contents of the temp obj generator.
366   std::string ObjBuffer;
367   llvm::raw_string_ostream ObjStream(ObjBuffer);
368 
369   auto HostTriple =
370       C.getSingleOffloadToolChain<Action::OFK_Host>()->getTriple();
371 
372   HIPUndefinedFatBinSymbols Symbols(C, Args);
373 
374   std::string PrimaryHipFatbinSymbol;
375   std::string PrimaryGpuBinHandleSymbol;
376   bool FoundPrimaryHipFatbinSymbol = false;
377   bool FoundPrimaryGpuBinHandleSymbol = false;
378 
379   std::vector<std::string> AliasHipFatbinSymbols;
380   std::vector<std::string> AliasGpuBinHandleSymbols;
381 
382   // Iterate through symbols to find the primary ones and collect others for
383   // aliasing
384   for (const auto &Symbol : Symbols.getFatBinSymbols()) {
385     if (!FoundPrimaryHipFatbinSymbol) {
386       PrimaryHipFatbinSymbol = Symbol;
387       FoundPrimaryHipFatbinSymbol = true;
388     } else
389       AliasHipFatbinSymbols.push_back(Symbol);
390   }
391 
392   for (const auto &Symbol : Symbols.getGPUBinHandleSymbols()) {
393     if (!FoundPrimaryGpuBinHandleSymbol) {
394       PrimaryGpuBinHandleSymbol = Symbol;
395       FoundPrimaryGpuBinHandleSymbol = true;
396     } else
397       AliasGpuBinHandleSymbols.push_back(Symbol);
398   }
399 
400   // Add MC directives to embed target binaries. We ensure that each
401   // section and image is 16-byte aligned. This is not mandatory, but
402   // increases the likelihood of data to be aligned with a cache block
403   // in several main host machines.
404   ObjStream << "#       HIP Object Generator\n";
405   ObjStream << "# *** Automatically generated by Clang ***\n";
406   if (FoundPrimaryGpuBinHandleSymbol) {
407     // Define the first gpubin handle symbol
408     if (HostTriple.isWindowsMSVCEnvironment())
409       ObjStream << "  .section .hip_gpubin_handle,\"dw\"\n";
410     else {
411       ObjStream << "  .protected " << PrimaryGpuBinHandleSymbol << "\n";
412       ObjStream << "  .type " << PrimaryGpuBinHandleSymbol << ",@object\n";
413       ObjStream << "  .section .hip_gpubin_handle,\"aw\"\n";
414     }
415     ObjStream << "  .globl " << PrimaryGpuBinHandleSymbol << "\n";
416     ObjStream << "  .p2align 3\n"; // Align 8
417     ObjStream << PrimaryGpuBinHandleSymbol << ":\n";
418     ObjStream << "  .zero 8\n"; // Size 8
419 
420     // Generate alias directives for other gpubin handle symbols
421     for (const auto &AliasSymbol : AliasGpuBinHandleSymbols) {
422       ObjStream << "  .globl " << AliasSymbol << "\n";
423       ObjStream << "  .set " << AliasSymbol << "," << PrimaryGpuBinHandleSymbol
424                 << "\n";
425     }
426   }
427   if (FoundPrimaryHipFatbinSymbol) {
428     // Define the first fatbin symbol
429     if (HostTriple.isWindowsMSVCEnvironment())
430       ObjStream << "  .section .hip_fatbin,\"dw\"\n";
431     else {
432       ObjStream << "  .protected " << PrimaryHipFatbinSymbol << "\n";
433       ObjStream << "  .type " << PrimaryHipFatbinSymbol << ",@object\n";
434       ObjStream << "  .section .hip_fatbin,\"a\",@progbits\n";
435     }
436     ObjStream << "  .globl " << PrimaryHipFatbinSymbol << "\n";
437     ObjStream << "  .p2align " << llvm::Log2(llvm::Align(HIPCodeObjectAlign))
438               << "\n";
439     // Generate alias directives for other fatbin symbols
440     for (const auto &AliasSymbol : AliasHipFatbinSymbols) {
441       ObjStream << "  .globl " << AliasSymbol << "\n";
442       ObjStream << "  .set " << AliasSymbol << "," << PrimaryHipFatbinSymbol
443                 << "\n";
444     }
445     ObjStream << PrimaryHipFatbinSymbol << ":\n";
446     ObjStream << "  .incbin ";
447     llvm::sys::printArg(ObjStream, BundleFile, /*Quote=*/true);
448     ObjStream << "\n";
449   }
450   if (HostTriple.isOSLinux() && HostTriple.isOSBinFormatELF())
451     ObjStream << "  .section .note.GNU-stack, \"\", @progbits\n";
452 
453   // Dump the contents of the temp object file gen if the user requested that.
454   // We support this option to enable testing of behavior with -###.
455   if (C.getArgs().hasArg(options::OPT_fhip_dump_offload_linker_script))
456     llvm::errs() << ObjBuffer;
457 
458   // Open script file and write the contents.
459   std::error_code EC;
460   llvm::raw_fd_ostream Objf(ObjinFile, EC, llvm::sys::fs::OF_None);
461 
462   if (EC) {
463     D.Diag(clang::diag::err_unable_to_make_temp) << EC.message();
464     return;
465   }
466 
467   Objf << ObjBuffer;
468 
469   ArgStringList ClangArgs{"-target", Args.MakeArgString(HostTriple.normalize()),
470                        "-o",      Output.getFilename(),
471                        "-x",      "assembler",
472                        ObjinFile, "-c"};
473   C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(),
474                                          D.getClangProgramPath(), ClangArgs,
475                                          Inputs, Output, D.getPrependArg()));
476 }
477