1//===-- clang-nvlink-wrapper/ClangNVLinkWrapper.cpp - NVIDIA linker util --===//
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 tool wraps around the NVIDIA linker called 'nvlink'. The NVIDIA linker
10// is required to create NVPTX applications, but does not support common
11// features like LTO or archives. This utility wraps around the tool to cover
12// its deficiencies. This tool can be removed once NVIDIA improves their linker
13// or ports it to `ld.lld`.
14//
15//===---------------------------------------------------------------------===//
16
17#include "clang/Basic/Version.h"
18
19#include "llvm/ADT/StringExtras.h"
20#include "llvm/BinaryFormat/Magic.h"
21#include "llvm/Bitcode/BitcodeWriter.h"
22#include "llvm/CodeGen/CommandFlags.h"
23#include "llvm/IR/DiagnosticPrinter.h"
24#include "llvm/LTO/LTO.h"
25#include "llvm/Object/Archive.h"
26#include "llvm/Object/ArchiveWriter.h"
27#include "llvm/Object/Binary.h"
28#include "llvm/Object/ELFObjectFile.h"
29#include "llvm/Object/IRObjectFile.h"
30#include "llvm/Object/ObjectFile.h"
31#include "llvm/Object/OffloadBinary.h"
32#include "llvm/Option/ArgList.h"
33#include "llvm/Option/OptTable.h"
34#include "llvm/Option/Option.h"
35#include "llvm/Remarks/HotnessThresholdParser.h"
36#include "llvm/Support/CommandLine.h"
37#include "llvm/Support/FileOutputBuffer.h"
38#include "llvm/Support/FileSystem.h"
39#include "llvm/Support/InitLLVM.h"
40#include "llvm/Support/MemoryBuffer.h"
41#include "llvm/Support/Path.h"
42#include "llvm/Support/Program.h"
43#include "llvm/Support/Signals.h"
44#include "llvm/Support/StringSaver.h"
45#include "llvm/Support/TargetSelect.h"
46#include "llvm/Support/WithColor.h"
47
48using namespace llvm;
49using namespace llvm::opt;
50using namespace llvm::object;
51
52// Various tools (e.g., llc and opt) duplicate this series of declarations for
53// options related to passes and remarks.
54static cl::opt<bool> RemarksWithHotness(
55 "pass-remarks-with-hotness",
56 cl::desc("With PGO, include profile count in optimization remarks"),
57 cl::Hidden);
58
59static cl::opt<std::optional<uint64_t>, false, remarks::HotnessThresholdParser>
60 RemarksHotnessThreshold(
61 "pass-remarks-hotness-threshold",
62 cl::desc("Minimum profile count required for "
63 "an optimization remark to be output. "
64 "Use 'auto' to apply the threshold from profile summary."),
65 cl::value_desc("N or 'auto'"), cl::init(Val: 0), cl::Hidden);
66
67static cl::opt<std::string>
68 RemarksFilename("pass-remarks-output",
69 cl::desc("Output filename for pass remarks"),
70 cl::value_desc("filename"));
71
72static cl::opt<std::string>
73 RemarksPasses("pass-remarks-filter",
74 cl::desc("Only record optimization remarks from passes whose "
75 "names match the given regular expression"),
76 cl::value_desc("regex"));
77
78static cl::opt<std::string> RemarksFormat(
79 "pass-remarks-format",
80 cl::desc("The format used for serializing remarks (default: YAML)"),
81 cl::value_desc("format"), cl::init(Val: "yaml"));
82
83static cl::list<std::string>
84 PassPlugins("load-pass-plugin",
85 cl::desc("Load passes from plugin library"));
86
87static void printVersion(raw_ostream &OS) {
88 OS << clang::getClangToolFullVersion(ToolName: "clang-nvlink-wrapper") << '\n';
89}
90
91/// The value of `argv[0]` when run.
92static const char *Executable;
93
94/// Temporary files to be cleaned up.
95static SmallVector<SmallString<128>> TempFiles;
96
97/// Codegen flags for LTO backend.
98static codegen::RegisterCodeGenFlags CodeGenFlags;
99
100namespace {
101// Must not overlap with llvm::opt::DriverFlag.
102enum WrapperFlags { WrapperOnlyOption = (1 << 4) };
103
104enum ID {
105 OPT_INVALID = 0, // This is not an option ID.
106#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
107#include "NVLinkOpts.inc"
108 LastOption
109#undef OPTION
110};
111
112#define OPTTABLE_STR_TABLE_CODE
113#include "NVLinkOpts.inc"
114#undef OPTTABLE_STR_TABLE_CODE
115
116#define OPTTABLE_PREFIXES_TABLE_CODE
117#include "NVLinkOpts.inc"
118#undef OPTTABLE_PREFIXES_TABLE_CODE
119
120static constexpr OptTable::Info InfoTable[] = {
121#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
122#include "NVLinkOpts.inc"
123#undef OPTION
124};
125
126class WrapperOptTable : public opt::GenericOptTable {
127public:
128 WrapperOptTable()
129 : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {}
130};
131
132const OptTable &getOptTable() {
133 static const WrapperOptTable *Table = []() {
134 auto Result = std::make_unique<WrapperOptTable>();
135 return Result.release();
136 }();
137 return *Table;
138}
139
140[[noreturn]] void reportError(Error E) {
141 outs().flush();
142 logAllUnhandledErrors(E: std::move(E), OS&: WithColor::error(OS&: errs(), Prefix: Executable));
143 exit(EXIT_FAILURE);
144}
145
146void diagnosticHandler(const DiagnosticInfo &DI) {
147 std::string ErrStorage;
148 raw_string_ostream OS(ErrStorage);
149 DiagnosticPrinterRawOStream DP(OS);
150 DI.print(DP);
151
152 switch (DI.getSeverity()) {
153 case DS_Error:
154 WithColor::error(OS&: errs(), Prefix: Executable) << ErrStorage << "\n";
155 break;
156 case DS_Warning:
157 WithColor::warning(OS&: errs(), Prefix: Executable) << ErrStorage << "\n";
158 break;
159 case DS_Note:
160 WithColor::note(OS&: errs(), Prefix: Executable) << ErrStorage << "\n";
161 break;
162 case DS_Remark:
163 WithColor::remark(OS&: errs()) << ErrStorage << "\n";
164 break;
165 }
166}
167
168bool hasFatBinary(const ArgList &Args, MemoryBufferRef Buffer) {
169 if (Args.hasArg(Ids: OPT_dry_run) && Args.hasArg(Ids: OPT_assume_device_object))
170 return false;
171 if (identify_magic(magic: Buffer.getBuffer()) != file_magic::elf_relocatable)
172 return false;
173 Expected<std::unique_ptr<ObjectFile>> ObjFile =
174 ObjectFile::createObjectFile(Object: Buffer);
175 if (!ObjFile) // Assume fatbin if the object creation fails.
176 return !errorToBool(Err: ObjFile.takeError());
177 return (*ObjFile)->getArch() != Triple::nvptx &&
178 (*ObjFile)->getArch() != Triple::nvptx64;
179}
180
181Expected<StringRef> createTempFile(const ArgList &Args, const Twine &Prefix,
182 StringRef Extension) {
183 SmallString<128> OutputFile;
184 if (Args.hasArg(Ids: OPT_save_temps)) {
185 (Prefix + "." + Extension).toNullTerminatedStringRef(Out&: OutputFile);
186 } else {
187 if (std::error_code EC =
188 sys::fs::createTemporaryFile(Prefix, Suffix: Extension, ResultPath&: OutputFile))
189 return createFileError(F: OutputFile, EC);
190 }
191
192 TempFiles.emplace_back(Args: std::move(OutputFile));
193 return TempFiles.back();
194}
195
196Expected<std::string> findProgram(const ArgList &Args, StringRef Name,
197 ArrayRef<StringRef> Paths) {
198 if (Args.hasArg(Ids: OPT_dry_run))
199 return Name.str();
200 ErrorOr<std::string> Path = sys::findProgramByName(Name, Paths);
201 if (!Path)
202 Path = sys::findProgramByName(Name);
203 if (!Path)
204 return createStringError(EC: Path.getError(),
205 S: "Unable to find '" + Name + "' in path");
206 return *Path;
207}
208
209std::optional<std::string> findFile(StringRef Dir, StringRef Root,
210 const Twine &Name) {
211 SmallString<128> Path;
212 if (Dir.starts_with(Prefix: "="))
213 sys::path::append(path&: Path, a: Root, b: Dir.substr(Start: 1), c: Name);
214 else
215 sys::path::append(path&: Path, a: Dir, b: Name);
216
217 if (sys::fs::exists(Path))
218 return static_cast<std::string>(Path);
219 return std::nullopt;
220}
221
222std::optional<std::string>
223findFromSearchPaths(StringRef Name, StringRef Root,
224 ArrayRef<StringRef> SearchPaths) {
225 for (StringRef Dir : SearchPaths)
226 if (std::optional<std::string> File = findFile(Dir, Root, Name))
227 return File;
228 return std::nullopt;
229}
230
231std::optional<std::string>
232searchLibraryBaseName(StringRef Name, StringRef Root,
233 ArrayRef<StringRef> SearchPaths) {
234 for (StringRef Dir : SearchPaths)
235 if (std::optional<std::string> File =
236 findFile(Dir, Root, Name: "lib" + Name + ".a"))
237 return File;
238 return std::nullopt;
239}
240
241/// Search for static libraries in the linker's library path given input like
242/// `-lfoo` or `-l:libfoo.a`.
243std::optional<std::string> searchLibrary(StringRef Input, StringRef Root,
244 ArrayRef<StringRef> SearchPaths) {
245 if (Input.starts_with(Prefix: ":"))
246 return findFromSearchPaths(Name: Input.drop_front(), Root, SearchPaths);
247 return searchLibraryBaseName(Name: Input, Root, SearchPaths);
248}
249
250void printCommands(ArrayRef<StringRef> CmdArgs) {
251 if (CmdArgs.empty())
252 return;
253
254 errs() << " \"" << CmdArgs.front() << "\" ";
255 errs() << join(Begin: std::next(x: CmdArgs.begin()), End: CmdArgs.end(), Separator: " ") << "\n";
256}
257
258/// A minimum symbol interface that provides the necessary information to
259/// extract archive members and resolve LTO symbols.
260struct Symbol {
261 enum Flags {
262 None = 0,
263 Undefined = 1 << 0,
264 Weak = 1 << 1,
265 };
266
267 Symbol() : File(), Flags(None), UsedInRegularObj(false) {}
268 Symbol(Symbol::Flags Flags) : File(), Flags(Flags), UsedInRegularObj(true) {}
269
270 Symbol(MemoryBufferRef File, const irsymtab::Reader::SymbolRef Sym)
271 : File(File), Flags(0), UsedInRegularObj(false) {
272 if (Sym.isUndefined())
273 Flags |= Undefined;
274 if (Sym.isWeak())
275 Flags |= Weak;
276 }
277
278 Symbol(MemoryBufferRef File, const SymbolRef Sym)
279 : File(File), Flags(0), UsedInRegularObj(false) {
280 auto FlagsOrErr = Sym.getFlags();
281 if (!FlagsOrErr)
282 reportError(E: FlagsOrErr.takeError());
283 if (*FlagsOrErr & SymbolRef::SF_Undefined)
284 Flags |= Undefined;
285 if (*FlagsOrErr & SymbolRef::SF_Weak)
286 Flags |= Weak;
287
288 auto NameOrErr = Sym.getName();
289 if (!NameOrErr)
290 reportError(E: NameOrErr.takeError());
291 }
292
293 bool isWeak() const { return Flags & Weak; }
294 bool isUndefined() const { return Flags & Undefined; }
295
296 MemoryBufferRef File;
297 uint32_t Flags;
298 bool UsedInRegularObj;
299};
300
301Expected<StringRef> runPTXAs(StringRef File, const ArgList &Args) {
302 SmallVector<StringRef, 1> SearchPaths;
303 if (Arg *A = Args.getLastArg(Ids: OPT_cuda_path_EQ))
304 SearchPaths.push_back(Elt: Args.MakeArgString(Str: A->getValue() + Twine("/bin")));
305 if (Arg *A = Args.getLastArg(Ids: OPT_ptxas_path_EQ))
306 SearchPaths.push_back(Elt: Args.MakeArgString(Str: A->getValue()));
307
308 Expected<std::string> PTXAsPath = findProgram(Args, Name: "ptxas", Paths: SearchPaths);
309 if (!PTXAsPath)
310 return PTXAsPath.takeError();
311
312 if (!Args.hasArg(Ids: OPT_arch))
313 return createStringError(
314 Fmt: "must pass in an explicit nvptx64 gpu architecture to 'ptxas'");
315
316 auto TempFileOrErr = createTempFile(
317 Args, Prefix: sys::path::stem(path: Args.getLastArgValue(Id: OPT_o, Default: "a.out")), Extension: "cubin");
318 if (!TempFileOrErr)
319 return TempFileOrErr.takeError();
320
321 SmallVector<StringRef> AssemblerArgs({*PTXAsPath, "-m64", "-c", File});
322 if (Args.hasArg(Ids: OPT_verbose))
323 AssemblerArgs.push_back(Elt: "-v");
324 if (Args.hasArg(Ids: OPT_g)) {
325 if (Args.getLastArgValue(Id: OPT_O, Default: "3") != "0")
326 WithColor::warning(OS&: errs(), Prefix: Executable)
327 << "Optimized debugging not supported, overriding to '-O0'\n";
328 AssemblerArgs.push_back(Elt: "-O0");
329 AssemblerArgs.push_back(Elt: "-g");
330 } else {
331 AssemblerArgs.push_back(
332 Elt: Args.MakeArgString(Str: "-O" + Args.getLastArgValue(Id: OPT_O, Default: "3")));
333 }
334 AssemblerArgs.append(IL: {"-arch", Args.getLastArgValue(Id: OPT_arch)});
335 for (const Arg *A : Args.filtered(Ids: OPT_Xptxas))
336 AssemblerArgs.push_back(Elt: A->getValue());
337 AssemblerArgs.append(IL: {"-o", *TempFileOrErr});
338
339 if (Args.hasArg(Ids: OPT_dry_run) || Args.hasArg(Ids: OPT_verbose))
340 printCommands(CmdArgs: AssemblerArgs);
341 if (Args.hasArg(Ids: OPT_dry_run))
342 return Args.MakeArgString(Str: *TempFileOrErr);
343 if (sys::ExecuteAndWait(Program: *PTXAsPath, Args: AssemblerArgs))
344 return createStringError(S: "'" + sys::path::filename(path: *PTXAsPath) + "'" +
345 " failed");
346 return Args.MakeArgString(Str: *TempFileOrErr);
347}
348
349Expected<std::unique_ptr<lto::LTO>> createLTO(const ArgList &Args) {
350 const llvm::Triple Triple("nvptx64-nvidia-cuda");
351 lto::Config Conf;
352 lto::ThinBackend Backend;
353 unsigned Jobs = 0;
354 if (auto *Arg = Args.getLastArg(Ids: OPT_jobs))
355 if (!to_integer(S: Arg->getValue(), Num&: Jobs) || Jobs == 0)
356 reportError(E: createStringError(Fmt: "%s: expected a positive integer, got '%s'",
357 Vals: Arg->getSpelling().data(),
358 Vals: Arg->getValue()));
359 Backend =
360 lto::createInProcessThinBackend(Parallelism: heavyweight_hardware_concurrency(ThreadCount: Jobs));
361
362 Conf.CPU = Args.getLastArgValue(Id: OPT_arch);
363 Conf.Options = codegen::InitTargetOptionsFromCodeGenFlags(TheTriple: Triple);
364
365 Conf.RemarksFilename =
366 Args.getLastArgValue(Id: OPT_opt_remarks_filename, Default: RemarksFilename);
367 Conf.RemarksPasses =
368 Args.getLastArgValue(Id: OPT_opt_remarks_filter, Default: RemarksPasses);
369 Conf.RemarksFormat =
370 Args.getLastArgValue(Id: OPT_opt_remarks_format, Default: RemarksFormat);
371
372 Conf.RemarksWithHotness =
373 Args.hasArg(Ids: OPT_opt_remarks_with_hotness) || RemarksWithHotness;
374 Conf.RemarksHotnessThreshold = RemarksHotnessThreshold;
375
376 Conf.MAttrs = llvm::codegen::getMAttrs();
377 std::optional<CodeGenOptLevel> CGOptLevelOrNone =
378 CodeGenOpt::parseLevel(C: Args.getLastArgValue(Id: OPT_O, Default: "2")[0]);
379 assert(CGOptLevelOrNone && "Invalid optimization level");
380 Conf.CGOptLevel = *CGOptLevelOrNone;
381 Conf.OptLevel = Args.getLastArgValue(Id: OPT_O, Default: "2")[0] - '0';
382 Conf.DefaultTriple = Triple.getTriple();
383
384 Conf.OptPipeline = Args.getLastArgValue(Id: OPT_lto_newpm_passes, Default: "");
385 Conf.PassPluginFilenames = PassPlugins;
386 Conf.DebugPassManager = Args.hasArg(Ids: OPT_lto_debug_pass_manager);
387
388 Conf.DiagHandler = diagnosticHandler;
389 Conf.CGFileType = CodeGenFileType::AssemblyFile;
390
391 if (Args.hasArg(Ids: OPT_lto_emit_llvm)) {
392 Conf.PreCodeGenModuleHook = [&](size_t, const Module &M) {
393 std::error_code EC;
394 raw_fd_ostream LinkedBitcode(Args.getLastArgValue(Id: OPT_o, Default: "a.out"), EC);
395 if (EC)
396 reportError(E: errorCodeToError(EC));
397 WriteBitcodeToFile(M, Out&: LinkedBitcode);
398 return false;
399 };
400 }
401
402 if (Args.hasArg(Ids: OPT_save_temps))
403 if (Error Err = Conf.addSaveTemps(
404 OutputFileName: (Args.getLastArgValue(Id: OPT_o, Default: "a.out") + ".").str()))
405 return Err;
406
407 unsigned Partitions = 1;
408 if (auto *Arg = Args.getLastArg(Ids: OPT_lto_partitions))
409 if (!to_integer(S: Arg->getValue(), Num&: Partitions) || Partitions == 0)
410 reportError(E: createStringError(Fmt: "%s: expected a positive integer, got '%s'",
411 Vals: Arg->getSpelling().data(),
412 Vals: Arg->getValue()));
413 lto::LTO::LTOKind Kind = Args.hasArg(Ids: OPT_thinlto) ? lto::LTO::LTOK_UnifiedThin
414 : lto::LTO::LTOK_Default;
415 return std::make_unique<lto::LTO>(args: std::move(Conf), args&: Backend, args&: Partitions, args&: Kind);
416}
417
418Expected<bool> getSymbolsFromBitcode(MemoryBufferRef Buffer,
419 StringMap<Symbol> &SymTab, bool IsLazy) {
420 Expected<IRSymtabFile> IRSymtabOrErr = readIRSymtab(MBRef: Buffer);
421 if (!IRSymtabOrErr)
422 return IRSymtabOrErr.takeError();
423 bool Extracted = !IsLazy;
424 StringMap<Symbol> PendingSymbols;
425 for (unsigned I = 0; I != IRSymtabOrErr->Mods.size(); ++I) {
426 for (const auto &IRSym : IRSymtabOrErr->TheReader.module_symbols(I)) {
427 if (IRSym.isFormatSpecific() || !IRSym.isGlobal())
428 continue;
429
430 Symbol &OldSym = !SymTab.count(Key: IRSym.getName()) && IsLazy
431 ? PendingSymbols[IRSym.getName()]
432 : SymTab[IRSym.getName()];
433 Symbol Sym = Symbol(Buffer, IRSym);
434 if (OldSym.File.getBuffer().empty())
435 OldSym = Sym;
436
437 bool ResolvesReference =
438 !Sym.isUndefined() &&
439 (OldSym.isUndefined() || (OldSym.isWeak() && !Sym.isWeak())) &&
440 !(OldSym.isWeak() && OldSym.isUndefined() && IsLazy);
441 Extracted |= ResolvesReference;
442
443 Sym.UsedInRegularObj = OldSym.UsedInRegularObj;
444 if (ResolvesReference)
445 OldSym = Sym;
446 }
447 }
448 if (Extracted)
449 for (const auto &[Name, Symbol] : PendingSymbols)
450 SymTab[Name] = Symbol;
451 return Extracted;
452}
453
454Expected<bool> getSymbolsFromObject(ObjectFile &ObjFile,
455 StringMap<Symbol> &SymTab, bool IsLazy) {
456 bool Extracted = !IsLazy;
457 StringMap<Symbol> PendingSymbols;
458 for (SymbolRef ObjSym : ObjFile.symbols()) {
459 auto NameOrErr = ObjSym.getName();
460 if (!NameOrErr)
461 return NameOrErr.takeError();
462
463 Symbol &OldSym = !SymTab.count(Key: *NameOrErr) && IsLazy
464 ? PendingSymbols[*NameOrErr]
465 : SymTab[*NameOrErr];
466 Symbol Sym = Symbol(ObjFile.getMemoryBufferRef(), ObjSym);
467 if (OldSym.File.getBuffer().empty())
468 OldSym = Sym;
469
470 bool ResolvesReference = OldSym.isUndefined() && !Sym.isUndefined() &&
471 (!OldSym.isWeak() || !IsLazy);
472 Extracted |= ResolvesReference;
473
474 if (ResolvesReference)
475 OldSym = Sym;
476 OldSym.UsedInRegularObj = true;
477 }
478 if (Extracted)
479 for (const auto &[Name, Symbol] : PendingSymbols)
480 SymTab[Name] = Symbol;
481 return Extracted;
482}
483
484Expected<bool> getSymbols(MemoryBufferRef Buffer, StringMap<Symbol> &SymTab,
485 bool IsLazy) {
486 switch (identify_magic(magic: Buffer.getBuffer())) {
487 case file_magic::bitcode: {
488 return getSymbolsFromBitcode(Buffer, SymTab, IsLazy);
489 }
490 case file_magic::elf_relocatable: {
491 Expected<std::unique_ptr<ObjectFile>> ObjFile =
492 ObjectFile::createObjectFile(Object: Buffer);
493 if (!ObjFile)
494 return ObjFile.takeError();
495 return getSymbolsFromObject(ObjFile&: **ObjFile, SymTab, IsLazy);
496 }
497 default:
498 return createStringError(Fmt: "Unsupported file type");
499 }
500}
501
502Expected<SmallVector<StringRef>> getInput(const ArgList &Args) {
503 SmallVector<StringRef> LibraryPaths;
504 for (const opt::Arg *Arg : Args.filtered(Ids: OPT_library_path))
505 LibraryPaths.push_back(Elt: Arg->getValue());
506
507 bool WholeArchive = false;
508 SmallVector<std::pair<std::unique_ptr<MemoryBuffer>, bool>> InputFiles;
509 for (const opt::Arg *Arg : Args.filtered(
510 Ids: OPT_INPUT, Ids: OPT_library, Ids: OPT_whole_archive, Ids: OPT_no_whole_archive)) {
511 if (Arg->getOption().matches(ID: OPT_whole_archive) ||
512 Arg->getOption().matches(ID: OPT_no_whole_archive)) {
513 WholeArchive = Arg->getOption().matches(ID: OPT_whole_archive);
514 continue;
515 }
516
517 std::optional<std::string> Filename =
518 Arg->getOption().matches(ID: OPT_library)
519 ? searchLibrary(Input: Arg->getValue(), /*Root=*/"", SearchPaths: LibraryPaths)
520 : std::string(Arg->getValue());
521
522 if (!Filename && Arg->getOption().matches(ID: OPT_library))
523 return createStringError(Fmt: "unable to find library -l%s", Vals: Arg->getValue());
524
525 if (!Filename || !sys::fs::exists(Path: *Filename) ||
526 sys::fs::is_directory(Path: *Filename))
527 continue;
528
529 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
530 MemoryBuffer::getFileOrSTDIN(Filename: *Filename);
531 if (std::error_code EC = BufferOrErr.getError())
532 return createFileError(F: *Filename, EC);
533
534 MemoryBufferRef Buffer = **BufferOrErr;
535 switch (identify_magic(magic: Buffer.getBuffer())) {
536 case file_magic::bitcode:
537 case file_magic::elf_relocatable:
538 InputFiles.emplace_back(Args: std::move(*BufferOrErr), /*IsLazy=*/Args: false);
539 break;
540 case file_magic::archive: {
541 Expected<std::unique_ptr<object::Archive>> LibFile =
542 object::Archive::create(Source: Buffer);
543 if (!LibFile)
544 return LibFile.takeError();
545 Error Err = Error::success();
546 for (auto Child : (*LibFile)->children(Err)) {
547 auto ChildBufferOrErr = Child.getMemoryBufferRef();
548 if (!ChildBufferOrErr)
549 return ChildBufferOrErr.takeError();
550 std::unique_ptr<MemoryBuffer> ChildBuffer =
551 MemoryBuffer::getMemBufferCopy(
552 InputData: ChildBufferOrErr->getBuffer(),
553 BufferName: ChildBufferOrErr->getBufferIdentifier());
554 InputFiles.emplace_back(Args: std::move(ChildBuffer), Args: !WholeArchive);
555 }
556 if (Err)
557 return Err;
558 break;
559 }
560 default:
561 return createStringError(Fmt: "Unsupported file type");
562 }
563 }
564
565 bool Extracted = true;
566 StringMap<Symbol> SymTab;
567 for (auto &Sym : Args.getAllArgValues(Id: OPT_u))
568 SymTab[Sym] = Symbol(Symbol::Undefined);
569 SmallVector<std::unique_ptr<MemoryBuffer>> LinkerInput;
570 while (Extracted) {
571 Extracted = false;
572 for (auto &[Input, IsLazy] : InputFiles) {
573 if (!Input)
574 continue;
575
576 if (hasFatBinary(Args, Buffer: *Input)) {
577 LinkerInput.emplace_back(Args: std::move(Input));
578 continue;
579 }
580
581 // Archive members only extract if they define needed symbols. We will
582 // re-scan all the inputs if any files were extracted for the link job.
583 Expected<bool> ExtractOrErr = getSymbols(Buffer: *Input, SymTab, IsLazy);
584 if (!ExtractOrErr)
585 return ExtractOrErr.takeError();
586
587 Extracted |= *ExtractOrErr;
588 if (!*ExtractOrErr)
589 continue;
590
591 LinkerInput.emplace_back(Args: std::move(Input));
592 }
593 }
594 InputFiles.clear();
595
596 // Extract any bitcode files to be passed to the LTO pipeline.
597 SmallVector<std::unique_ptr<MemoryBuffer>> BitcodeFiles;
598 for (auto &Input : LinkerInput)
599 if (identify_magic(magic: Input->getBuffer()) == file_magic::bitcode)
600 BitcodeFiles.emplace_back(Args: std::move(Input));
601 erase_if(C&: LinkerInput, P: [](const auto &F) { return !F; });
602
603 // Run the LTO pipeline on the extracted inputs.
604 SmallVector<StringRef> Files;
605 if (!BitcodeFiles.empty()) {
606 auto LTOBackendOrErr = createLTO(Args);
607 if (!LTOBackendOrErr)
608 return LTOBackendOrErr.takeError();
609 lto::LTO &LTOBackend = **LTOBackendOrErr;
610 for (auto &BitcodeFile : BitcodeFiles) {
611 Expected<std::unique_ptr<lto::InputFile>> BitcodeFileOrErr =
612 lto::InputFile::create(Object: *BitcodeFile);
613 if (!BitcodeFileOrErr)
614 return BitcodeFileOrErr.takeError();
615
616 const auto Symbols = (*BitcodeFileOrErr)->symbols();
617 SmallVector<lto::SymbolResolution, 16> Resolutions(Symbols.size());
618 size_t Idx = 0;
619 for (auto &Sym : Symbols) {
620 lto::SymbolResolution &Res = Resolutions[Idx++];
621 Symbol ObjSym = SymTab[Sym.getName()];
622 // We will use this as the prevailing symbol in LTO if it is not
623 // undefined and it is from the file that contained the canonical
624 // definition.
625 Res.Prevailing = !Sym.isUndefined() && ObjSym.File == *BitcodeFile;
626
627 // We need LTO to preseve the following global symbols:
628 // 1) All symbols during a relocatable link.
629 // 2) Symbols used in regular objects.
630 // 3) Prevailing symbols that are needed visible to the gpu runtime.
631 Res.VisibleToRegularObj =
632 Args.hasArg(Ids: OPT_relocatable) || ObjSym.UsedInRegularObj ||
633 (Res.Prevailing &&
634 (Sym.getVisibility() != GlobalValue::HiddenVisibility &&
635 !Sym.canBeOmittedFromSymbolTable()));
636
637 // Identify symbols that must be exported dynamically and can be
638 // referenced by other files, (i.e. the runtime).
639 Res.ExportDynamic =
640 Sym.getVisibility() != GlobalValue::HiddenVisibility &&
641 !Sym.canBeOmittedFromSymbolTable();
642
643 // The NVIDIA platform does not support any symbol preemption.
644 Res.FinalDefinitionInLinkageUnit = true;
645
646 // We do not support linker redefined symbols (e.g. --wrap) for device
647 // image linking, so the symbols will not be changed after LTO.
648 Res.LinkerRedefined = false;
649 }
650
651 // Add the bitcode file with its resolved symbols to the LTO job.
652 if (Error Err = LTOBackend.add(Obj: std::move(*BitcodeFileOrErr), Res: Resolutions))
653 return Err;
654 }
655
656 // Run the LTO job to compile the bitcode.
657 size_t MaxTasks = LTOBackend.getMaxTasks();
658 SmallVector<StringRef> LTOFiles(MaxTasks);
659 auto AddStream =
660 [&](size_t Task,
661 const Twine &ModuleName) -> std::unique_ptr<CachedFileStream> {
662 int FD = -1;
663 auto &TempFile = LTOFiles[Task];
664 if (Args.hasArg(Ids: OPT_lto_emit_asm))
665 TempFile = Args.getLastArgValue(Id: OPT_o, Default: "a.out");
666 else {
667 auto TempFileOrErr = createTempFile(
668 Args, Prefix: sys::path::stem(path: Args.getLastArgValue(Id: OPT_o, Default: "a.out")), Extension: "s");
669 if (!TempFileOrErr)
670 reportError(E: TempFileOrErr.takeError());
671 TempFile = Args.MakeArgString(Str: *TempFileOrErr);
672 }
673 if (std::error_code EC = sys::fs::openFileForWrite(Name: TempFile, ResultFD&: FD))
674 reportError(E: errorCodeToError(EC));
675 return std::make_unique<CachedFileStream>(
676 args: std::make_unique<raw_fd_ostream>(args&: FD, args: true));
677 };
678
679 if (Error Err = LTOBackend.run(AddStream))
680 return Err;
681
682 if (Args.hasArg(Ids: OPT_lto_emit_llvm) || Args.hasArg(Ids: OPT_lto_emit_asm))
683 return Files;
684
685 for (StringRef LTOFile : LTOFiles) {
686 auto FileOrErr = runPTXAs(File: LTOFile, Args);
687 if (!FileOrErr)
688 return FileOrErr.takeError();
689 Files.emplace_back(Args&: *FileOrErr);
690 }
691 }
692
693 // Create a copy for each file to a new file ending in `.cubin`. The 'nvlink'
694 // linker requires all NVPTX inputs to have this extension for some reason.
695 // We don't use a symbolic link because it's not supported on Windows and some
696 // of this input files could be extracted from an archive.
697 for (auto &Input : LinkerInput) {
698 auto TempFileOrErr = createTempFile(
699 Args, Prefix: sys::path::stem(path: Input->getBufferIdentifier()),
700 Extension: hasFatBinary(Args, Buffer: Input->getMemBufferRef()) ? "o" : "cubin");
701 if (!TempFileOrErr)
702 return TempFileOrErr.takeError();
703 Expected<std::unique_ptr<FileOutputBuffer>> OutputOrErr =
704 FileOutputBuffer::create(FilePath: *TempFileOrErr, Size: Input->getBuffer().size());
705 if (!OutputOrErr)
706 return OutputOrErr.takeError();
707 std::unique_ptr<FileOutputBuffer> Output = std::move(*OutputOrErr);
708 copy(Range: Input->getBuffer(), Out: Output->getBufferStart());
709 if (Error E = Output->commit())
710 return E;
711 Files.emplace_back(Args: Args.MakeArgString(Str: *TempFileOrErr));
712 }
713
714 return Files;
715}
716
717Error runNVLink(ArrayRef<StringRef> Files, const ArgList &Args) {
718 if (Args.hasArg(Ids: OPT_lto_emit_asm) || Args.hasArg(Ids: OPT_lto_emit_llvm))
719 return Error::success();
720
721 SmallVector<StringRef, 1> SearchPaths;
722 if (Arg *A = Args.getLastArg(Ids: OPT_cuda_path_EQ))
723 SearchPaths.push_back(Elt: Args.MakeArgString(Str: A->getValue() + Twine("/bin")));
724
725 Expected<std::string> NVLinkPath = findProgram(Args, Name: "nvlink", Paths: SearchPaths);
726 if (!NVLinkPath)
727 return NVLinkPath.takeError();
728
729 if (!Args.hasArg(Ids: OPT_arch))
730 return createStringError(
731 Fmt: "must pass in an explicit nvptx64 gpu architecture to 'nvlink'");
732
733 ArgStringList NewLinkerArgs;
734 for (const opt::Arg *Arg : Args) {
735 // Do not forward arguments only intended for the linker wrapper.
736 if (Arg->getOption().hasFlag(Val: WrapperOnlyOption))
737 continue;
738
739 // Do not forward any inputs that we have processed.
740 if (Arg->getOption().matches(ID: OPT_INPUT) ||
741 Arg->getOption().matches(ID: OPT_library))
742 continue;
743
744 Arg->render(Args, Output&: NewLinkerArgs);
745 }
746
747 transform(Range&: Files, d_first: std::back_inserter(x&: NewLinkerArgs),
748 F: [&](StringRef Arg) { return Args.MakeArgString(Str: Arg); });
749
750 SmallVector<StringRef> LinkerArgs({*NVLinkPath});
751 if (!Args.hasArg(Ids: OPT_o))
752 LinkerArgs.append(IL: {"-o", "a.out"});
753 for (StringRef Arg : NewLinkerArgs)
754 LinkerArgs.push_back(Elt: Arg);
755
756 if (Args.hasArg(Ids: OPT_dry_run) || Args.hasArg(Ids: OPT_verbose))
757 printCommands(CmdArgs: LinkerArgs);
758 if (Args.hasArg(Ids: OPT_dry_run))
759 return Error::success();
760 if (sys::ExecuteAndWait(Program: *NVLinkPath, Args: LinkerArgs))
761 return createStringError(S: "'" + sys::path::filename(path: *NVLinkPath) + "'" +
762 " failed");
763 return Error::success();
764}
765
766} // namespace
767
768int main(int argc, char **argv) {
769 InitLLVM X(argc, argv);
770 InitializeAllTargetInfos();
771 InitializeAllTargets();
772 InitializeAllTargetMCs();
773 InitializeAllAsmParsers();
774 InitializeAllAsmPrinters();
775
776 Executable = argv[0];
777 sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]);
778
779 const OptTable &Tbl = getOptTable();
780 BumpPtrAllocator Alloc;
781 StringSaver Saver(Alloc);
782 auto Args = Tbl.parseArgs(Argc: argc, Argv: argv, Unknown: OPT_INVALID, Saver, ErrorFn: [&](StringRef Err) {
783 reportError(E: createStringError(EC: inconvertibleErrorCode(), S: Err));
784 });
785
786 if (Args.hasArg(Ids: OPT_help) || Args.hasArg(Ids: OPT_help_hidden)) {
787 Tbl.printHelp(
788 OS&: outs(), Usage: "clang-nvlink-wrapper [options] <options to passed to nvlink>",
789 Title: "A utility that wraps around the NVIDIA 'nvlink' linker.\n"
790 "This enables static linking and LTO handling for NVPTX targets.",
791 ShowHidden: Args.hasArg(Ids: OPT_help_hidden), ShowAllAliases: Args.hasArg(Ids: OPT_help_hidden));
792 return EXIT_SUCCESS;
793 }
794
795 if (Args.hasArg(Ids: OPT_version))
796 printVersion(OS&: outs());
797
798 // This forwards '-mllvm' arguments to LLVM if present.
799 SmallVector<const char *> NewArgv = {argv[0]};
800 for (const opt::Arg *Arg : Args.filtered(Ids: OPT_mllvm))
801 NewArgv.push_back(Elt: Arg->getValue());
802 for (const opt::Arg *Arg : Args.filtered(Ids: OPT_plugin_opt))
803 NewArgv.push_back(Elt: Arg->getValue());
804 cl::ParseCommandLineOptions(argc: NewArgv.size(), argv: &NewArgv[0]);
805
806 // Get the input files to pass to 'nvlink'.
807 auto FilesOrErr = getInput(Args);
808 if (!FilesOrErr)
809 reportError(E: FilesOrErr.takeError());
810
811 // Run 'nvlink' on the generated inputs.
812 if (Error Err = runNVLink(Files: *FilesOrErr, Args))
813 reportError(E: std::move(Err));
814
815 // Remove the temporary files created.
816 if (!Args.hasArg(Ids: OPT_save_temps))
817 for (const auto &TempFile : TempFiles)
818 if (std::error_code EC = sys::fs::remove(path: TempFile))
819 reportError(E: createFileError(F: TempFile, EC));
820
821 return EXIT_SUCCESS;
822}
823