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