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 | |
48 | using namespace llvm; |
49 | using namespace llvm::opt; |
50 | using 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. |
54 | static cl::opt<bool> ( |
55 | "pass-remarks-with-hotness" , |
56 | cl::desc("With PGO, include profile count in optimization remarks" ), |
57 | cl::Hidden); |
58 | |
59 | static cl::opt<std::optional<uint64_t>, false, remarks::HotnessThresholdParser> |
60 | ( |
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 | |
67 | static cl::opt<std::string> |
68 | ("pass-remarks-output" , |
69 | cl::desc("Output filename for pass remarks" ), |
70 | cl::value_desc("filename" )); |
71 | |
72 | static cl::opt<std::string> |
73 | ("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 | |
78 | static cl::opt<std::string> ( |
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 | |
83 | static cl::list<std::string> |
84 | PassPlugins("load-pass-plugin" , |
85 | cl::desc("Load passes from plugin library" )); |
86 | |
87 | static 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'." )); |
96 | static cl::alias PassPipeline2("p" , cl::aliasopt(PassPipeline), |
97 | cl::desc("Alias for -passes" )); |
98 | |
99 | static void printVersion(raw_ostream &OS) { |
100 | OS << clang::getClangToolFullVersion(ToolName: "clang-nvlink-wrapper" ) << '\n'; |
101 | } |
102 | |
103 | /// The value of `argv[0]` when run. |
104 | static const char *Executable; |
105 | |
106 | /// Temporary files to be cleaned up. |
107 | static SmallVector<SmallString<128>> TempFiles; |
108 | |
109 | /// Codegen flags for LTO backend. |
110 | static codegen::RegisterCodeGenFlags CodeGenFlags; |
111 | |
112 | namespace { |
113 | // Must not overlap with llvm::opt::DriverFlag. |
114 | enum WrapperFlags { WrapperOnlyOption = (1 << 4) }; |
115 | |
116 | enum 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 | |
131 | static constexpr OptTable::Info InfoTable[] = { |
132 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
133 | #include "NVLinkOpts.inc" |
134 | #undef OPTION |
135 | }; |
136 | |
137 | class WrapperOptTable : public opt::GenericOptTable { |
138 | public: |
139 | WrapperOptTable() : opt::GenericOptTable(InfoTable) {} |
140 | }; |
141 | |
142 | const 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 | |
156 | void 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 | |
178 | Expected<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 | |
193 | Expected<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 | |
206 | std::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 | |
219 | std::optional<std::string> |
220 | findFromSearchPaths(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 | |
228 | std::optional<std::string> |
229 | searchLibraryBaseName(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`. |
240 | std::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 | |
247 | void 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. |
258 | struct 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 | |
298 | Expected<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 | |
335 | Expected<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 | |
398 | Expected<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 = !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 | |
434 | Expected<bool> getSymbolsFromObject(ObjectFile &ObjFile, |
435 | StringMap<Symbol> &SymTab, bool IsLazy) { |
436 | bool = !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 | |
464 | Expected<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 | |
482 | Expected<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 = 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> = 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 <OBackend = **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 | |
686 | Error 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 | |
731 | int 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 | |