xref: /llvm-project/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp (revision dd647e3e608ed0b2bac7c588d5859b80ef4a5976)
1 //=-------- clang-sycl-linker/ClangSYCLLinker.cpp - SYCL 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 executes a sequence of steps required to link device code in SYCL
10 // device images. SYCL device code linking requires a complex sequence of steps
11 // that include linking of llvm bitcode files, linking device library files
12 // with the fully linked source bitcode file(s), running several SYCL specific
13 // post-link steps on the fully linked bitcode file(s), and finally generating
14 // target-specific device code.
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/IRReader/IRReader.h"
25 #include "llvm/LTO/LTO.h"
26 #include "llvm/Object/Archive.h"
27 #include "llvm/Object/ArchiveWriter.h"
28 #include "llvm/Object/Binary.h"
29 #include "llvm/Object/ELFObjectFile.h"
30 #include "llvm/Object/IRObjectFile.h"
31 #include "llvm/Object/ObjectFile.h"
32 #include "llvm/Object/OffloadBinary.h"
33 #include "llvm/Option/ArgList.h"
34 #include "llvm/Option/OptTable.h"
35 #include "llvm/Option/Option.h"
36 #include "llvm/Remarks/HotnessThresholdParser.h"
37 #include "llvm/Support/CommandLine.h"
38 #include "llvm/Support/FileOutputBuffer.h"
39 #include "llvm/Support/FileSystem.h"
40 #include "llvm/Support/InitLLVM.h"
41 #include "llvm/Support/MemoryBuffer.h"
42 #include "llvm/Support/Path.h"
43 #include "llvm/Support/Program.h"
44 #include "llvm/Support/Signals.h"
45 #include "llvm/Support/StringSaver.h"
46 #include "llvm/Support/TargetSelect.h"
47 #include "llvm/Support/TimeProfiler.h"
48 #include "llvm/Support/WithColor.h"
49 
50 using namespace llvm;
51 using namespace llvm::opt;
52 using namespace llvm::object;
53 
54 /// Save intermediary results.
55 static bool SaveTemps = false;
56 
57 /// Print arguments without executing.
58 static bool DryRun = false;
59 
60 /// Print verbose output.
61 static bool Verbose = false;
62 
63 /// Filename of the output being created.
64 static StringRef OutputFile;
65 
66 /// Directory to dump SPIR-V IR if requested by user.
67 static SmallString<128> SPIRVDumpDir;
68 
69 static void printVersion(raw_ostream &OS) {
70   OS << clang::getClangToolFullVersion("clang-sycl-linker") << '\n';
71 }
72 
73 /// The value of `argv[0]` when run.
74 static const char *Executable;
75 
76 /// Temporary files to be cleaned up.
77 static SmallVector<SmallString<128>> TempFiles;
78 
79 namespace {
80 // Must not overlap with llvm::opt::DriverFlag.
81 enum LinkerFlags { LinkerOnlyOption = (1 << 4) };
82 
83 enum ID {
84   OPT_INVALID = 0, // This is not an option ID.
85 #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
86 #include "SYCLLinkOpts.inc"
87   LastOption
88 #undef OPTION
89 };
90 
91 #define OPTTABLE_STR_TABLE_CODE
92 #include "SYCLLinkOpts.inc"
93 #undef OPTTABLE_STR_TABLE_CODE
94 
95 #define OPTTABLE_PREFIXES_TABLE_CODE
96 #include "SYCLLinkOpts.inc"
97 #undef OPTTABLE_PREFIXES_TABLE_CODE
98 
99 static constexpr OptTable::Info InfoTable[] = {
100 #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
101 #include "SYCLLinkOpts.inc"
102 #undef OPTION
103 };
104 
105 class LinkerOptTable : public opt::GenericOptTable {
106 public:
107   LinkerOptTable()
108       : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {}
109 };
110 
111 const OptTable &getOptTable() {
112   static const LinkerOptTable *Table = []() {
113     auto Result = std::make_unique<LinkerOptTable>();
114     return Result.release();
115   }();
116   return *Table;
117 }
118 
119 [[noreturn]] void reportError(Error E) {
120   outs().flush();
121   logAllUnhandledErrors(std::move(E), WithColor::error(errs(), Executable));
122   exit(EXIT_FAILURE);
123 }
124 
125 std::string getMainExecutable(const char *Name) {
126   void *Ptr = (void *)(intptr_t)&getMainExecutable;
127   auto COWPath = sys::fs::getMainExecutable(Name, Ptr);
128   return sys::path::parent_path(COWPath).str();
129 }
130 
131 Expected<StringRef> createTempFile(const ArgList &Args, const Twine &Prefix,
132                                    StringRef Extension) {
133   SmallString<128> OutputFile;
134   if (Args.hasArg(OPT_save_temps)) {
135     // Generate a unique path name without creating a file
136     sys::fs::createUniquePath(Prefix + "-%%%%%%." + Extension, OutputFile,
137                               /*MakeAbsolute=*/false);
138   } else {
139     if (std::error_code EC =
140             sys::fs::createTemporaryFile(Prefix, Extension, OutputFile))
141       return createFileError(OutputFile, EC);
142   }
143 
144   TempFiles.emplace_back(std::move(OutputFile));
145   return TempFiles.back();
146 }
147 
148 Expected<std::string> findProgram(const ArgList &Args, StringRef Name,
149                                   ArrayRef<StringRef> Paths) {
150   if (Args.hasArg(OPT_dry_run))
151     return Name.str();
152   ErrorOr<std::string> Path = sys::findProgramByName(Name, Paths);
153   if (!Path)
154     Path = sys::findProgramByName(Name);
155   if (!Path)
156     return createStringError(Path.getError(),
157                              "Unable to find '" + Name + "' in path");
158   return *Path;
159 }
160 
161 void printCommands(ArrayRef<StringRef> CmdArgs) {
162   if (CmdArgs.empty())
163     return;
164 
165   llvm::errs() << " \"" << CmdArgs.front() << "\" ";
166   llvm::errs() << llvm::join(std::next(CmdArgs.begin()), CmdArgs.end(), " ")
167                << "\n";
168 }
169 
170 /// Execute the command \p ExecutablePath with the arguments \p Args.
171 Error executeCommands(StringRef ExecutablePath, ArrayRef<StringRef> Args) {
172   if (Verbose || DryRun)
173     printCommands(Args);
174 
175   if (!DryRun)
176     if (sys::ExecuteAndWait(ExecutablePath, Args))
177       return createStringError(
178           "'%s' failed", sys::path::filename(ExecutablePath).str().c_str());
179   return Error::success();
180 }
181 
182 Expected<SmallVector<std::string>> getInput(const ArgList &Args) {
183   // Collect all input bitcode files to be passed to llvm-link.
184   SmallVector<std::string> BitcodeFiles;
185   for (const opt::Arg *Arg : Args.filtered(OPT_INPUT)) {
186     std::optional<std::string> Filename = std::string(Arg->getValue());
187     if (!Filename || !sys::fs::exists(*Filename) ||
188         sys::fs::is_directory(*Filename))
189       continue;
190     file_magic Magic;
191     if (auto EC = identify_magic(*Filename, Magic))
192       return createStringError("Failed to open file " + *Filename);
193     // TODO: Current use case involves LLVM IR bitcode files as input.
194     // This will be extended to support objects and SPIR-V IR files.
195     if (Magic != file_magic::bitcode)
196       return createStringError("Unsupported file type");
197     BitcodeFiles.push_back(*Filename);
198   }
199   return BitcodeFiles;
200 }
201 
202 /// Link all SYCL device input files into one before adding device library
203 /// files. Device linking is performed using llvm-link tool.
204 /// 'InputFiles' is the list of all LLVM IR device input files.
205 /// 'Args' encompasses all arguments required for linking device code and will
206 /// be parsed to generate options required to be passed into llvm-link.
207 Expected<StringRef> linkDeviceInputFiles(ArrayRef<std::string> InputFiles,
208                                          const ArgList &Args) {
209   llvm::TimeTraceScope TimeScope("SYCL LinkDeviceInputFiles");
210 
211   assert(InputFiles.size() && "No inputs to llvm-link");
212   // Early check to see if there is only one input.
213   if (InputFiles.size() < 2)
214     return InputFiles[0];
215 
216   Expected<std::string> LLVMLinkPath =
217       findProgram(Args, "llvm-link", {getMainExecutable("llvm-link")});
218   if (!LLVMLinkPath)
219     return LLVMLinkPath.takeError();
220 
221   SmallVector<StringRef> CmdArgs;
222   CmdArgs.push_back(*LLVMLinkPath);
223   for (auto &File : InputFiles)
224     CmdArgs.push_back(File);
225   // Create a new file to write the linked device file to.
226   auto OutFileOrErr =
227       createTempFile(Args, sys::path::filename(OutputFile), "bc");
228   if (!OutFileOrErr)
229     return OutFileOrErr.takeError();
230   CmdArgs.push_back("-o");
231   CmdArgs.push_back(*OutFileOrErr);
232   CmdArgs.push_back("--suppress-warnings");
233   if (Error Err = executeCommands(*LLVMLinkPath, CmdArgs))
234     return std::move(Err);
235   return Args.MakeArgString(*OutFileOrErr);
236 }
237 
238 // This utility function is used to gather all SYCL device library files that
239 // will be linked with input device files.
240 // The list of files and its location are passed from driver.
241 Expected<SmallVector<std::string>> getSYCLDeviceLibs(const ArgList &Args) {
242   SmallVector<std::string> DeviceLibFiles;
243   StringRef LibraryPath;
244   if (Arg *A = Args.getLastArg(OPT_library_path_EQ))
245     LibraryPath = A->getValue();
246   if (LibraryPath.empty())
247     return DeviceLibFiles;
248   if (Arg *A = Args.getLastArg(OPT_device_libs_EQ)) {
249     if (A->getValues().size() == 0)
250       return createStringError(
251           inconvertibleErrorCode(),
252           "Number of device library files cannot be zero.");
253     for (StringRef Val : A->getValues()) {
254       SmallString<128> LibName(LibraryPath);
255       llvm::sys::path::append(LibName, Val);
256       if (llvm::sys::fs::exists(LibName))
257         DeviceLibFiles.push_back(std::string(LibName));
258       else
259         return createStringError(inconvertibleErrorCode(),
260                                  "\'" + std::string(LibName) + "\'" +
261                                      " SYCL device library file is not found.");
262     }
263   }
264   return DeviceLibFiles;
265 }
266 
267 /// Link all device library files and input file into one LLVM IR file. This
268 /// linking is performed using llvm-link tool.
269 /// 'InputFiles' is the list of all LLVM IR device input files.
270 /// 'Args' encompasses all arguments required for linking device code and will
271 /// be parsed to generate options required to be passed into llvm-link tool.
272 static Expected<StringRef> linkDeviceLibFiles(StringRef InputFile,
273                                               const ArgList &Args) {
274   llvm::TimeTraceScope TimeScope("LinkDeviceLibraryFiles");
275 
276   auto SYCLDeviceLibFiles = getSYCLDeviceLibs(Args);
277   if (!SYCLDeviceLibFiles)
278     return SYCLDeviceLibFiles.takeError();
279   if ((*SYCLDeviceLibFiles).empty())
280     return InputFile;
281 
282   Expected<std::string> LLVMLinkPath =
283       findProgram(Args, "llvm-link", {getMainExecutable("llvm-link")});
284   if (!LLVMLinkPath)
285     return LLVMLinkPath.takeError();
286 
287   // Create a new file to write the linked device file to.
288   auto OutFileOrErr =
289       createTempFile(Args, sys::path::filename(OutputFile), "bc");
290   if (!OutFileOrErr)
291     return OutFileOrErr.takeError();
292 
293   SmallVector<StringRef, 8> CmdArgs;
294   CmdArgs.push_back(*LLVMLinkPath);
295   CmdArgs.push_back("-only-needed");
296   CmdArgs.push_back(InputFile);
297   for (auto &File : *SYCLDeviceLibFiles)
298     CmdArgs.push_back(File);
299   CmdArgs.push_back("-o");
300   CmdArgs.push_back(*OutFileOrErr);
301   CmdArgs.push_back("--suppress-warnings");
302   if (Error Err = executeCommands(*LLVMLinkPath, CmdArgs))
303     return std::move(Err);
304   return *OutFileOrErr;
305 }
306 
307 /// Add any llvm-spirv option that relies on a specific Triple in addition
308 /// to user supplied options.
309 static void getSPIRVTransOpts(const ArgList &Args,
310                               SmallVector<StringRef, 8> &TranslatorArgs,
311                               const llvm::Triple Triple) {
312   // Enable NonSemanticShaderDebugInfo.200 for non-Windows
313   const bool IsWindowsMSVC =
314       Triple.isWindowsMSVCEnvironment() || Args.hasArg(OPT_is_windows_msvc_env);
315   const bool EnableNonSemanticDebug = !IsWindowsMSVC;
316   if (EnableNonSemanticDebug) {
317     TranslatorArgs.push_back(
318         "-spirv-debug-info-version=nonsemantic-shader-200");
319   } else {
320     TranslatorArgs.push_back("-spirv-debug-info-version=ocl-100");
321     // Prevent crash in the translator if input IR contains DIExpression
322     // operations which don't have mapping to OpenCL.DebugInfo.100 spec.
323     TranslatorArgs.push_back("-spirv-allow-extra-diexpressions");
324   }
325   std::string UnknownIntrinsics("-spirv-allow-unknown-intrinsics=llvm.genx.");
326 
327   TranslatorArgs.push_back(Args.MakeArgString(UnknownIntrinsics));
328 
329   // Disable all the extensions by default
330   std::string ExtArg("-spirv-ext=-all");
331   std::string DefaultExtArg =
332       ",+SPV_EXT_shader_atomic_float_add,+SPV_EXT_shader_atomic_float_min_max"
333       ",+SPV_KHR_no_integer_wrap_decoration,+SPV_KHR_float_controls"
334       ",+SPV_KHR_expect_assume,+SPV_KHR_linkonce_odr";
335   std::string INTELExtArg =
336       ",+SPV_INTEL_subgroups,+SPV_INTEL_media_block_io"
337       ",+SPV_INTEL_device_side_avc_motion_estimation"
338       ",+SPV_INTEL_fpga_loop_controls,+SPV_INTEL_unstructured_loop_controls"
339       ",+SPV_INTEL_fpga_reg,+SPV_INTEL_blocking_pipes"
340       ",+SPV_INTEL_function_pointers,+SPV_INTEL_kernel_attributes"
341       ",+SPV_INTEL_io_pipes,+SPV_INTEL_inline_assembly"
342       ",+SPV_INTEL_arbitrary_precision_integers"
343       ",+SPV_INTEL_float_controls2,+SPV_INTEL_vector_compute"
344       ",+SPV_INTEL_fast_composite"
345       ",+SPV_INTEL_arbitrary_precision_fixed_point"
346       ",+SPV_INTEL_arbitrary_precision_floating_point"
347       ",+SPV_INTEL_variable_length_array,+SPV_INTEL_fp_fast_math_mode"
348       ",+SPV_INTEL_long_constant_composite"
349       ",+SPV_INTEL_arithmetic_fence"
350       ",+SPV_INTEL_global_variable_decorations"
351       ",+SPV_INTEL_cache_controls"
352       ",+SPV_INTEL_fpga_buffer_location"
353       ",+SPV_INTEL_fpga_argument_interfaces"
354       ",+SPV_INTEL_fpga_invocation_pipelining_attributes"
355       ",+SPV_INTEL_fpga_latency_control"
356       ",+SPV_INTEL_task_sequence"
357       ",+SPV_KHR_shader_clock"
358       ",+SPV_INTEL_bindless_images";
359   ExtArg = ExtArg + DefaultExtArg + INTELExtArg;
360   ExtArg += ",+SPV_INTEL_token_type"
361             ",+SPV_INTEL_bfloat16_conversion"
362             ",+SPV_INTEL_joint_matrix"
363             ",+SPV_INTEL_hw_thread_queries"
364             ",+SPV_KHR_uniform_group_instructions"
365             ",+SPV_INTEL_masked_gather_scatter"
366             ",+SPV_INTEL_tensor_float32_conversion"
367             ",+SPV_INTEL_optnone"
368             ",+SPV_KHR_non_semantic_info"
369             ",+SPV_KHR_cooperative_matrix";
370   TranslatorArgs.push_back(Args.MakeArgString(ExtArg));
371 }
372 
373 /// Run LLVM to SPIR-V translation.
374 /// Converts 'File' from LLVM bitcode to SPIR-V format using llvm-spirv tool.
375 /// 'Args' encompasses all arguments required for linking device code and will
376 /// be parsed to generate options required to be passed into llvm-spirv tool.
377 static Expected<StringRef> runLLVMToSPIRVTranslation(StringRef File,
378                                                      const ArgList &Args) {
379   llvm::TimeTraceScope TimeScope("LLVMToSPIRVTranslation");
380   StringRef LLVMSPIRVPath = Args.getLastArgValue(OPT_llvm_spirv_path_EQ);
381   Expected<std::string> LLVMToSPIRVProg =
382       findProgram(Args, "llvm-spirv", {LLVMSPIRVPath});
383   if (!LLVMToSPIRVProg)
384     return LLVMToSPIRVProg.takeError();
385 
386   SmallVector<StringRef, 8> CmdArgs;
387   CmdArgs.push_back(*LLVMToSPIRVProg);
388   const llvm::Triple Triple(Args.getLastArgValue(OPT_triple));
389   getSPIRVTransOpts(Args, CmdArgs, Triple);
390   StringRef LLVMToSPIRVOptions;
391   if (Arg *A = Args.getLastArg(OPT_llvm_spirv_options_EQ))
392     LLVMToSPIRVOptions = A->getValue();
393   LLVMToSPIRVOptions.split(CmdArgs, " ", /* MaxSplit = */ -1,
394                            /* KeepEmpty = */ false);
395   CmdArgs.append({"-o", OutputFile});
396   CmdArgs.push_back(File);
397   if (Error Err = executeCommands(*LLVMToSPIRVProg, CmdArgs))
398     return std::move(Err);
399 
400   if (!SPIRVDumpDir.empty()) {
401     std::error_code EC =
402         llvm::sys::fs::create_directory(SPIRVDumpDir, /*IgnoreExisting*/ true);
403     if (EC)
404       return createStringError(
405           EC,
406           formatv("failed to create dump directory. path: {0}, error_code: {1}",
407                   SPIRVDumpDir, EC.value()));
408 
409     StringRef Path = OutputFile;
410     StringRef Filename = llvm::sys::path::filename(Path);
411     SmallString<128> CopyPath = SPIRVDumpDir;
412     CopyPath.append(Filename);
413     EC = llvm::sys::fs::copy_file(Path, CopyPath);
414     if (EC)
415       return createStringError(
416           EC,
417           formatv(
418               "failed to copy file. original: {0}, copy: {1}, error_code: {2}",
419               Path, CopyPath, EC.value()));
420   }
421 
422   return OutputFile;
423 }
424 
425 Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
426   llvm::TimeTraceScope TimeScope("SYCLDeviceLink");
427   // First llvm-link step
428   auto LinkedFile = linkDeviceInputFiles(Files, Args);
429   if (!LinkedFile)
430     reportError(LinkedFile.takeError());
431 
432   // second llvm-link step
433   auto DeviceLinkedFile = linkDeviceLibFiles(*LinkedFile, Args);
434   if (!DeviceLinkedFile)
435     reportError(DeviceLinkedFile.takeError());
436 
437   // LLVM to SPIR-V translation step
438   auto SPVFile = runLLVMToSPIRVTranslation(*DeviceLinkedFile, Args);
439   if (!SPVFile)
440     return SPVFile.takeError();
441   return Error::success();
442 }
443 
444 } // namespace
445 
446 int main(int argc, char **argv) {
447   InitLLVM X(argc, argv);
448 
449   Executable = argv[0];
450   sys::PrintStackTraceOnErrorSignal(argv[0]);
451 
452   const OptTable &Tbl = getOptTable();
453   BumpPtrAllocator Alloc;
454   StringSaver Saver(Alloc);
455   auto Args = Tbl.parseArgs(argc, argv, OPT_INVALID, Saver, [&](StringRef Err) {
456     reportError(createStringError(inconvertibleErrorCode(), Err));
457   });
458 
459   if (Args.hasArg(OPT_help) || Args.hasArg(OPT_help_hidden)) {
460     Tbl.printHelp(
461         outs(), "clang-sycl-linker [options] <options to sycl link steps>",
462         "A utility that wraps around several steps required to link SYCL "
463         "device files.\n"
464         "This enables LLVM IR linking, post-linking and code generation for "
465         "SYCL targets.",
466         Args.hasArg(OPT_help_hidden), Args.hasArg(OPT_help_hidden));
467     return EXIT_SUCCESS;
468   }
469 
470   if (Args.hasArg(OPT_version))
471     printVersion(outs());
472 
473   Verbose = Args.hasArg(OPT_verbose);
474   DryRun = Args.hasArg(OPT_dry_run);
475   SaveTemps = Args.hasArg(OPT_save_temps);
476 
477   OutputFile = "a.spv";
478   if (Args.hasArg(OPT_o))
479     OutputFile = Args.getLastArgValue(OPT_o);
480 
481   if (Args.hasArg(OPT_spirv_dump_device_code_EQ)) {
482     Arg *A = Args.getLastArg(OPT_spirv_dump_device_code_EQ);
483     SmallString<128> Dir(A->getValue());
484     if (Dir.empty())
485       llvm::sys::path::native(Dir = "./");
486     else
487       Dir.append(llvm::sys::path::get_separator());
488 
489     SPIRVDumpDir = Dir;
490   }
491 
492   // Get the input files to pass to the linking stage.
493   auto FilesOrErr = getInput(Args);
494   if (!FilesOrErr)
495     reportError(FilesOrErr.takeError());
496 
497   // Run SYCL linking process on the generated inputs.
498   if (Error Err = runSYCLLink(*FilesOrErr, Args))
499     reportError(std::move(Err));
500 
501   // Remove the temporary files created.
502   if (!Args.hasArg(OPT_save_temps))
503     for (const auto &TempFile : TempFiles)
504       if (std::error_code EC = sys::fs::remove(TempFile))
505         reportError(createFileError(TempFile, EC));
506 
507   return EXIT_SUCCESS;
508 }
509