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