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