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