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