xref: /freebsd/contrib/llvm-project/clang/lib/Driver/ToolChains/HIPUtility.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
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.
normalizeForBundler(const llvm::Triple & T,bool HasTargetID)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:
HIPUndefinedFatBinSymbols(const Compilation & C)55   HIPUndefinedFatBinSymbols(const Compilation &C)
56       : C(C), DiagID(C.getDriver().getDiags().getCustomDiagID(
57                   DiagnosticsEngine::Error,
58                   "Error collecting HIP undefined fatbin symbols: %0")),
59         Quiet(C.getArgs().hasArg(options::OPT__HASH_HASH_HASH)),
60         Verbose(C.getArgs().hasArg(options::OPT_v)) {
61     populateSymbols();
62     if (Verbose) {
63       for (const auto &Name : FatBinSymbols)
64         llvm::errs() << "Found undefined HIP fatbin symbol: " << Name << "\n";
65       for (const auto &Name : GPUBinHandleSymbols)
66         llvm::errs() << "Found undefined HIP gpubin handle symbol: " << Name
67                      << "\n";
68     }
69   }
70 
getFatBinSymbols() const71   const std::set<std::string> &getFatBinSymbols() const {
72     return FatBinSymbols;
73   }
74 
getGPUBinHandleSymbols() const75   const std::set<std::string> &getGPUBinHandleSymbols() const {
76     return GPUBinHandleSymbols;
77   }
78 
79 private:
80   const Compilation &C;
81   unsigned DiagID;
82   bool Quiet;
83   bool Verbose;
84   std::set<std::string> FatBinSymbols;
85   std::set<std::string> GPUBinHandleSymbols;
86   std::set<std::string> DefinedFatBinSymbols;
87   std::set<std::string> DefinedGPUBinHandleSymbols;
88   const std::string FatBinPrefix = "__hip_fatbin";
89   const std::string GPUBinHandlePrefix = "__hip_gpubin_handle";
90 
populateSymbols()91   void populateSymbols() {
92     std::deque<const Action *> WorkList;
93     std::set<const Action *> Visited;
94 
95     for (const auto &Action : C.getActions())
96       WorkList.push_back(Action);
97 
98     while (!WorkList.empty()) {
99       const Action *CurrentAction = WorkList.front();
100       WorkList.pop_front();
101 
102       if (!CurrentAction || !Visited.insert(CurrentAction).second)
103         continue;
104 
105       if (const auto *IA = dyn_cast<InputAction>(CurrentAction)) {
106         std::string ID = IA->getId().str();
107         if (!ID.empty()) {
108           ID = llvm::utohexstr(llvm::MD5Hash(ID), /*LowerCase=*/true);
109           FatBinSymbols.insert((FatBinPrefix + Twine('_') + ID).str());
110           GPUBinHandleSymbols.insert(
111               (GPUBinHandlePrefix + Twine('_') + ID).str());
112           continue;
113         }
114         if (IA->getInputArg().getNumValues() == 0)
115           continue;
116         const char *Filename = IA->getInputArg().getValue();
117         if (!Filename)
118           continue;
119         auto BufferOrErr = llvm::MemoryBuffer::getFile(Filename);
120         // Input action could be options to linker, therefore, ignore it
121         // if cannot read it. If it turns out to be a file that cannot be read,
122         // the error will be caught by the linker.
123         if (!BufferOrErr)
124           continue;
125 
126         processInput(BufferOrErr.get()->getMemBufferRef());
127       } else
128         WorkList.insert(WorkList.end(), CurrentAction->getInputs().begin(),
129                         CurrentAction->getInputs().end());
130     }
131   }
132 
processInput(const llvm::MemoryBufferRef & Buffer)133   void processInput(const llvm::MemoryBufferRef &Buffer) {
134     // Try processing as object file first.
135     auto ObjFileOrErr = llvm::object::ObjectFile::createObjectFile(Buffer);
136     if (ObjFileOrErr) {
137       processSymbols(**ObjFileOrErr);
138       return;
139     }
140 
141     // Then try processing as archive files.
142     llvm::consumeError(ObjFileOrErr.takeError());
143     auto ArchiveOrErr = llvm::object::Archive::create(Buffer);
144     if (ArchiveOrErr) {
145       llvm::Error Err = llvm::Error::success();
146       llvm::object::Archive &Archive = *ArchiveOrErr.get();
147       for (auto &Child : Archive.children(Err)) {
148         auto ChildBufOrErr = Child.getMemoryBufferRef();
149         if (ChildBufOrErr)
150           processInput(*ChildBufOrErr);
151         else
152           errorHandler(ChildBufOrErr.takeError());
153       }
154 
155       if (Err)
156         errorHandler(std::move(Err));
157       return;
158     }
159 
160     // Ignore other files.
161     llvm::consumeError(ArchiveOrErr.takeError());
162   }
163 
processSymbols(const llvm::object::ObjectFile & Obj)164   void processSymbols(const llvm::object::ObjectFile &Obj) {
165     for (const auto &Symbol : Obj.symbols()) {
166       auto FlagOrErr = Symbol.getFlags();
167       if (!FlagOrErr) {
168         errorHandler(FlagOrErr.takeError());
169         continue;
170       }
171 
172       auto NameOrErr = Symbol.getName();
173       if (!NameOrErr) {
174         errorHandler(NameOrErr.takeError());
175         continue;
176       }
177       llvm::StringRef Name = *NameOrErr;
178 
179       bool isUndefined =
180           FlagOrErr.get() & llvm::object::SymbolRef::SF_Undefined;
181       bool isFatBinSymbol = Name.starts_with(FatBinPrefix);
182       bool isGPUBinHandleSymbol = Name.starts_with(GPUBinHandlePrefix);
183 
184       // Handling for defined symbols
185       if (!isUndefined) {
186         if (isFatBinSymbol) {
187           DefinedFatBinSymbols.insert(Name.str());
188           FatBinSymbols.erase(Name.str());
189         } else if (isGPUBinHandleSymbol) {
190           DefinedGPUBinHandleSymbols.insert(Name.str());
191           GPUBinHandleSymbols.erase(Name.str());
192         }
193         continue;
194       }
195 
196       // Add undefined symbols if they are not in the defined sets
197       if (isFatBinSymbol &&
198           DefinedFatBinSymbols.find(Name.str()) == DefinedFatBinSymbols.end())
199         FatBinSymbols.insert(Name.str());
200       else if (isGPUBinHandleSymbol &&
201                DefinedGPUBinHandleSymbols.find(Name.str()) ==
202                    DefinedGPUBinHandleSymbols.end())
203         GPUBinHandleSymbols.insert(Name.str());
204     }
205   }
206 
errorHandler(llvm::Error Err)207   void errorHandler(llvm::Error Err) {
208     if (Quiet)
209       return;
210     C.getDriver().Diag(DiagID) << llvm::toString(std::move(Err));
211   }
212 };
213 
214 // Construct a clang-offload-bundler command to bundle code objects for
215 // different devices into a HIP fat binary.
constructHIPFatbinCommand(Compilation & C,const JobAction & JA,llvm::StringRef OutputFileName,const InputInfoList & Inputs,const llvm::opt::ArgList & Args,const Tool & T)216 void HIP::constructHIPFatbinCommand(Compilation &C, const JobAction &JA,
217                                     llvm::StringRef OutputFileName,
218                                     const InputInfoList &Inputs,
219                                     const llvm::opt::ArgList &Args,
220                                     const Tool &T) {
221   // Construct clang-offload-bundler command to bundle object files for
222   // for different GPU archs.
223   ArgStringList BundlerArgs;
224   BundlerArgs.push_back(Args.MakeArgString("-type=o"));
225   BundlerArgs.push_back(
226       Args.MakeArgString("-bundle-align=" + Twine(HIPCodeObjectAlign)));
227 
228   // ToDo: Remove the dummy host binary entry which is required by
229   // clang-offload-bundler.
230   std::string BundlerTargetArg = "-targets=host-x86_64-unknown-linux";
231   // AMDGCN:
232   // For code object version 2 and 3, the offload kind in bundle ID is 'hip'
233   // for backward compatibility. For code object version 4 and greater, the
234   // offload kind in bundle ID is 'hipv4'.
235   std::string OffloadKind = "hip";
236   auto &TT = T.getToolChain().getTriple();
237   if (TT.isAMDGCN() && getAMDGPUCodeObjectVersion(C.getDriver(), Args) >= 4)
238     OffloadKind = OffloadKind + "v4";
239   for (const auto &II : Inputs) {
240     const auto *A = II.getAction();
241     auto ArchStr = llvm::StringRef(A->getOffloadingArch());
242     BundlerTargetArg +=
243         "," + OffloadKind + "-" + normalizeForBundler(TT, !ArchStr.empty());
244     if (!ArchStr.empty())
245       BundlerTargetArg += "-" + ArchStr.str();
246   }
247   BundlerArgs.push_back(Args.MakeArgString(BundlerTargetArg));
248 
249   // Use a NULL file as input for the dummy host binary entry
250   std::string BundlerInputArg = "-input=" NULL_FILE;
251   BundlerArgs.push_back(Args.MakeArgString(BundlerInputArg));
252   for (const auto &II : Inputs) {
253     BundlerInputArg = std::string("-input=") + II.getFilename();
254     BundlerArgs.push_back(Args.MakeArgString(BundlerInputArg));
255   }
256 
257   std::string Output = std::string(OutputFileName);
258   auto *BundlerOutputArg =
259       Args.MakeArgString(std::string("-output=").append(Output));
260   BundlerArgs.push_back(BundlerOutputArg);
261 
262   addOffloadCompressArgs(Args, BundlerArgs);
263 
264   const char *Bundler = Args.MakeArgString(
265       T.getToolChain().GetProgramPath("clang-offload-bundler"));
266   C.addCommand(std::make_unique<Command>(
267       JA, T, ResponseFileSupport::None(), Bundler, BundlerArgs, Inputs,
268       InputInfo(&JA, Args.MakeArgString(Output))));
269 }
270 
271 /// Add Generated HIP Object File which has device images embedded into the
272 /// host to the argument list for linking. Using MC directives, embed the
273 /// device code and also define symbols required by the code generation so that
274 /// the image can be retrieved at runtime.
constructGenerateObjFileFromHIPFatBinary(Compilation & C,const InputInfo & Output,const InputInfoList & Inputs,const ArgList & Args,const JobAction & JA,const Tool & T)275 void HIP::constructGenerateObjFileFromHIPFatBinary(
276     Compilation &C, const InputInfo &Output, const InputInfoList &Inputs,
277     const ArgList &Args, const JobAction &JA, const Tool &T) {
278   const ToolChain &TC = T.getToolChain();
279   std::string Name = std::string(llvm::sys::path::stem(Output.getFilename()));
280 
281   // Create Temp Object File Generator,
282   // Offload Bundled file and Bundled Object file.
283   // Keep them if save-temps is enabled.
284   const char *McinFile;
285   const char *BundleFile;
286   if (C.getDriver().isSaveTempsEnabled()) {
287     McinFile = C.getArgs().MakeArgString(Name + ".mcin");
288     BundleFile = C.getArgs().MakeArgString(Name + ".hipfb");
289   } else {
290     auto TmpNameMcin = C.getDriver().GetTemporaryPath(Name, "mcin");
291     McinFile = C.addTempFile(C.getArgs().MakeArgString(TmpNameMcin));
292     auto TmpNameFb = C.getDriver().GetTemporaryPath(Name, "hipfb");
293     BundleFile = C.addTempFile(C.getArgs().MakeArgString(TmpNameFb));
294   }
295   HIP::constructHIPFatbinCommand(C, JA, BundleFile, Inputs, Args, T);
296 
297   // Create a buffer to write the contents of the temp obj generator.
298   std::string ObjBuffer;
299   llvm::raw_string_ostream ObjStream(ObjBuffer);
300 
301   auto HostTriple =
302       C.getSingleOffloadToolChain<Action::OFK_Host>()->getTriple();
303 
304   HIPUndefinedFatBinSymbols Symbols(C);
305 
306   std::string PrimaryHipFatbinSymbol;
307   std::string PrimaryGpuBinHandleSymbol;
308   bool FoundPrimaryHipFatbinSymbol = false;
309   bool FoundPrimaryGpuBinHandleSymbol = false;
310 
311   std::vector<std::string> AliasHipFatbinSymbols;
312   std::vector<std::string> AliasGpuBinHandleSymbols;
313 
314   // Iterate through symbols to find the primary ones and collect others for
315   // aliasing
316   for (const auto &Symbol : Symbols.getFatBinSymbols()) {
317     if (!FoundPrimaryHipFatbinSymbol) {
318       PrimaryHipFatbinSymbol = Symbol;
319       FoundPrimaryHipFatbinSymbol = true;
320     } else
321       AliasHipFatbinSymbols.push_back(Symbol);
322   }
323 
324   for (const auto &Symbol : Symbols.getGPUBinHandleSymbols()) {
325     if (!FoundPrimaryGpuBinHandleSymbol) {
326       PrimaryGpuBinHandleSymbol = Symbol;
327       FoundPrimaryGpuBinHandleSymbol = true;
328     } else
329       AliasGpuBinHandleSymbols.push_back(Symbol);
330   }
331 
332   // Add MC directives to embed target binaries. We ensure that each
333   // section and image is 16-byte aligned. This is not mandatory, but
334   // increases the likelihood of data to be aligned with a cache block
335   // in several main host machines.
336   ObjStream << "#       HIP Object Generator\n";
337   ObjStream << "# *** Automatically generated by Clang ***\n";
338   if (FoundPrimaryGpuBinHandleSymbol) {
339     // Define the first gpubin handle symbol
340     if (HostTriple.isWindowsMSVCEnvironment())
341       ObjStream << "  .section .hip_gpubin_handle,\"dw\"\n";
342     else {
343       ObjStream << "  .protected " << PrimaryGpuBinHandleSymbol << "\n";
344       ObjStream << "  .type " << PrimaryGpuBinHandleSymbol << ",@object\n";
345       ObjStream << "  .section .hip_gpubin_handle,\"aw\"\n";
346     }
347     ObjStream << "  .globl " << PrimaryGpuBinHandleSymbol << "\n";
348     ObjStream << "  .p2align 3\n"; // Align 8
349     ObjStream << PrimaryGpuBinHandleSymbol << ":\n";
350     ObjStream << "  .zero 8\n"; // Size 8
351 
352     // Generate alias directives for other gpubin handle symbols
353     for (const auto &AliasSymbol : AliasGpuBinHandleSymbols) {
354       ObjStream << "  .globl " << AliasSymbol << "\n";
355       ObjStream << "  .set " << AliasSymbol << "," << PrimaryGpuBinHandleSymbol
356                 << "\n";
357     }
358   }
359   if (FoundPrimaryHipFatbinSymbol) {
360     // Define the first fatbin symbol
361     if (HostTriple.isWindowsMSVCEnvironment())
362       ObjStream << "  .section .hip_fatbin,\"dw\"\n";
363     else {
364       ObjStream << "  .protected " << PrimaryHipFatbinSymbol << "\n";
365       ObjStream << "  .type " << PrimaryHipFatbinSymbol << ",@object\n";
366       ObjStream << "  .section .hip_fatbin,\"a\",@progbits\n";
367     }
368     ObjStream << "  .globl " << PrimaryHipFatbinSymbol << "\n";
369     ObjStream << "  .p2align " << llvm::Log2(llvm::Align(HIPCodeObjectAlign))
370               << "\n";
371     // Generate alias directives for other fatbin symbols
372     for (const auto &AliasSymbol : AliasHipFatbinSymbols) {
373       ObjStream << "  .globl " << AliasSymbol << "\n";
374       ObjStream << "  .set " << AliasSymbol << "," << PrimaryHipFatbinSymbol
375                 << "\n";
376     }
377     ObjStream << PrimaryHipFatbinSymbol << ":\n";
378     ObjStream << "  .incbin ";
379     llvm::sys::printArg(ObjStream, BundleFile, /*Quote=*/true);
380     ObjStream << "\n";
381   }
382   if (HostTriple.isOSLinux() && HostTriple.isOSBinFormatELF())
383     ObjStream << "  .section .note.GNU-stack, \"\", @progbits\n";
384   ObjStream.flush();
385 
386   // Dump the contents of the temp object file gen if the user requested that.
387   // We support this option to enable testing of behavior with -###.
388   if (C.getArgs().hasArg(options::OPT_fhip_dump_offload_linker_script))
389     llvm::errs() << ObjBuffer;
390 
391   // Open script file and write the contents.
392   std::error_code EC;
393   llvm::raw_fd_ostream Objf(McinFile, EC, llvm::sys::fs::OF_None);
394 
395   if (EC) {
396     C.getDriver().Diag(clang::diag::err_unable_to_make_temp) << EC.message();
397     return;
398   }
399 
400   Objf << ObjBuffer;
401 
402   ArgStringList McArgs{"-triple", Args.MakeArgString(HostTriple.normalize()),
403                        "-o",      Output.getFilename(),
404                        McinFile,  "--filetype=obj"};
405   const char *Mc = Args.MakeArgString(TC.GetProgramPath("llvm-mc"));
406   C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(), Mc,
407                                          McArgs, Inputs, Output));
408 }
409