//=-------- clang-sycl-linker/ClangSYCLLinker.cpp - SYCL Linker util -------=// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===---------------------------------------------------------------------===// // // This tool executes a sequence of steps required to link device code in SYCL // device images. SYCL device code linking requires a complex sequence of steps // that include linking of llvm bitcode files, linking device library files // with the fully linked source bitcode file(s), running several SYCL specific // post-link steps on the fully linked bitcode file(s), and finally generating // target-specific device code. //===---------------------------------------------------------------------===// #include "clang/Basic/Version.h" #include "llvm/ADT/StringExtras.h" #include "llvm/BinaryFormat/Magic.h" #include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/CodeGen/CommandFlags.h" #include "llvm/IR/DiagnosticPrinter.h" #include "llvm/IRReader/IRReader.h" #include "llvm/LTO/LTO.h" #include "llvm/Object/Archive.h" #include "llvm/Object/ArchiveWriter.h" #include "llvm/Object/Binary.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/IRObjectFile.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Object/OffloadBinary.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/OptTable.h" #include "llvm/Option/Option.h" #include "llvm/Remarks/HotnessThresholdParser.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include "llvm/Support/Signals.h" #include "llvm/Support/StringSaver.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/WithColor.h" using namespace llvm; using namespace llvm::opt; using namespace llvm::object; /// Save intermediary results. static bool SaveTemps = false; /// Print arguments without executing. static bool DryRun = false; /// Print verbose output. static bool Verbose = false; /// Filename of the output being created. static StringRef OutputFile; /// Directory to dump SPIR-V IR if requested by user. static SmallString<128> SPIRVDumpDir; static void printVersion(raw_ostream &OS) { OS << clang::getClangToolFullVersion("clang-sycl-linker") << '\n'; } /// The value of `argv[0]` when run. static const char *Executable; /// Temporary files to be cleaned up. static SmallVector> TempFiles; namespace { // Must not overlap with llvm::opt::DriverFlag. enum LinkerFlags { LinkerOnlyOption = (1 << 4) }; enum ID { OPT_INVALID = 0, // This is not an option ID. #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), #include "SYCLLinkOpts.inc" LastOption #undef OPTION }; #define OPTTABLE_STR_TABLE_CODE #include "SYCLLinkOpts.inc" #undef OPTTABLE_STR_TABLE_CODE #define OPTTABLE_PREFIXES_TABLE_CODE #include "SYCLLinkOpts.inc" #undef OPTTABLE_PREFIXES_TABLE_CODE static constexpr OptTable::Info InfoTable[] = { #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), #include "SYCLLinkOpts.inc" #undef OPTION }; class LinkerOptTable : public opt::GenericOptTable { public: LinkerOptTable() : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {} }; const OptTable &getOptTable() { static const LinkerOptTable *Table = []() { auto Result = std::make_unique(); return Result.release(); }(); return *Table; } [[noreturn]] void reportError(Error E) { outs().flush(); logAllUnhandledErrors(std::move(E), WithColor::error(errs(), Executable)); exit(EXIT_FAILURE); } std::string getMainExecutable(const char *Name) { void *Ptr = (void *)(intptr_t)&getMainExecutable; auto COWPath = sys::fs::getMainExecutable(Name, Ptr); return sys::path::parent_path(COWPath).str(); } Expected createTempFile(const ArgList &Args, const Twine &Prefix, StringRef Extension) { SmallString<128> OutputFile; if (Args.hasArg(OPT_save_temps)) { // Generate a unique path name without creating a file sys::fs::createUniquePath(Prefix + "-%%%%%%." + Extension, OutputFile, /*MakeAbsolute=*/false); } else { if (std::error_code EC = sys::fs::createTemporaryFile(Prefix, Extension, OutputFile)) return createFileError(OutputFile, EC); } TempFiles.emplace_back(std::move(OutputFile)); return TempFiles.back(); } Expected findProgram(const ArgList &Args, StringRef Name, ArrayRef Paths) { if (Args.hasArg(OPT_dry_run)) return Name.str(); ErrorOr Path = sys::findProgramByName(Name, Paths); if (!Path) Path = sys::findProgramByName(Name); if (!Path) return createStringError(Path.getError(), "Unable to find '" + Name + "' in path"); return *Path; } void printCommands(ArrayRef CmdArgs) { if (CmdArgs.empty()) return; llvm::errs() << " \"" << CmdArgs.front() << "\" "; llvm::errs() << llvm::join(std::next(CmdArgs.begin()), CmdArgs.end(), " ") << "\n"; } /// Execute the command \p ExecutablePath with the arguments \p Args. Error executeCommands(StringRef ExecutablePath, ArrayRef Args) { if (Verbose || DryRun) printCommands(Args); if (!DryRun) if (sys::ExecuteAndWait(ExecutablePath, Args)) return createStringError( "'%s' failed", sys::path::filename(ExecutablePath).str().c_str()); return Error::success(); } Expected> getInput(const ArgList &Args) { // Collect all input bitcode files to be passed to llvm-link. SmallVector BitcodeFiles; for (const opt::Arg *Arg : Args.filtered(OPT_INPUT)) { std::optional Filename = std::string(Arg->getValue()); if (!Filename || !sys::fs::exists(*Filename) || sys::fs::is_directory(*Filename)) continue; file_magic Magic; if (auto EC = identify_magic(*Filename, Magic)) return createStringError("Failed to open file " + *Filename); // TODO: Current use case involves LLVM IR bitcode files as input. // This will be extended to support objects and SPIR-V IR files. if (Magic != file_magic::bitcode) return createStringError("Unsupported file type"); BitcodeFiles.push_back(*Filename); } return BitcodeFiles; } /// Link all SYCL device input files into one before adding device library /// files. Device linking is performed using llvm-link tool. /// 'InputFiles' is the list of all LLVM IR device input files. /// 'Args' encompasses all arguments required for linking device code and will /// be parsed to generate options required to be passed into llvm-link. Expected linkDeviceInputFiles(ArrayRef InputFiles, const ArgList &Args) { llvm::TimeTraceScope TimeScope("SYCL LinkDeviceInputFiles"); assert(InputFiles.size() && "No inputs to llvm-link"); // Early check to see if there is only one input. if (InputFiles.size() < 2) return InputFiles[0]; Expected LLVMLinkPath = findProgram(Args, "llvm-link", {getMainExecutable("llvm-link")}); if (!LLVMLinkPath) return LLVMLinkPath.takeError(); SmallVector CmdArgs; CmdArgs.push_back(*LLVMLinkPath); for (auto &File : InputFiles) CmdArgs.push_back(File); // Create a new file to write the linked device file to. auto OutFileOrErr = createTempFile(Args, sys::path::filename(OutputFile), "bc"); if (!OutFileOrErr) return OutFileOrErr.takeError(); CmdArgs.push_back("-o"); CmdArgs.push_back(*OutFileOrErr); CmdArgs.push_back("--suppress-warnings"); if (Error Err = executeCommands(*LLVMLinkPath, CmdArgs)) return std::move(Err); return Args.MakeArgString(*OutFileOrErr); } // This utility function is used to gather all SYCL device library files that // will be linked with input device files. // The list of files and its location are passed from driver. Expected> getSYCLDeviceLibs(const ArgList &Args) { SmallVector DeviceLibFiles; StringRef LibraryPath; if (Arg *A = Args.getLastArg(OPT_library_path_EQ)) LibraryPath = A->getValue(); if (LibraryPath.empty()) return DeviceLibFiles; if (Arg *A = Args.getLastArg(OPT_device_libs_EQ)) { if (A->getValues().size() == 0) return createStringError( inconvertibleErrorCode(), "Number of device library files cannot be zero."); for (StringRef Val : A->getValues()) { SmallString<128> LibName(LibraryPath); llvm::sys::path::append(LibName, Val); if (llvm::sys::fs::exists(LibName)) DeviceLibFiles.push_back(std::string(LibName)); else return createStringError(inconvertibleErrorCode(), "\'" + std::string(LibName) + "\'" + " SYCL device library file is not found."); } } return DeviceLibFiles; } /// Link all device library files and input file into one LLVM IR file. This /// linking is performed using llvm-link tool. /// 'InputFiles' is the list of all LLVM IR device input files. /// 'Args' encompasses all arguments required for linking device code and will /// be parsed to generate options required to be passed into llvm-link tool. static Expected linkDeviceLibFiles(StringRef InputFile, const ArgList &Args) { llvm::TimeTraceScope TimeScope("LinkDeviceLibraryFiles"); auto SYCLDeviceLibFiles = getSYCLDeviceLibs(Args); if (!SYCLDeviceLibFiles) return SYCLDeviceLibFiles.takeError(); if ((*SYCLDeviceLibFiles).empty()) return InputFile; Expected LLVMLinkPath = findProgram(Args, "llvm-link", {getMainExecutable("llvm-link")}); if (!LLVMLinkPath) return LLVMLinkPath.takeError(); // Create a new file to write the linked device file to. auto OutFileOrErr = createTempFile(Args, sys::path::filename(OutputFile), "bc"); if (!OutFileOrErr) return OutFileOrErr.takeError(); SmallVector CmdArgs; CmdArgs.push_back(*LLVMLinkPath); CmdArgs.push_back("-only-needed"); CmdArgs.push_back(InputFile); for (auto &File : *SYCLDeviceLibFiles) CmdArgs.push_back(File); CmdArgs.push_back("-o"); CmdArgs.push_back(*OutFileOrErr); CmdArgs.push_back("--suppress-warnings"); if (Error Err = executeCommands(*LLVMLinkPath, CmdArgs)) return std::move(Err); return *OutFileOrErr; } /// Add any llvm-spirv option that relies on a specific Triple in addition /// to user supplied options. static void getSPIRVTransOpts(const ArgList &Args, SmallVector &TranslatorArgs, const llvm::Triple Triple) { // Enable NonSemanticShaderDebugInfo.200 for non-Windows const bool IsWindowsMSVC = Triple.isWindowsMSVCEnvironment() || Args.hasArg(OPT_is_windows_msvc_env); const bool EnableNonSemanticDebug = !IsWindowsMSVC; if (EnableNonSemanticDebug) { TranslatorArgs.push_back( "-spirv-debug-info-version=nonsemantic-shader-200"); } else { TranslatorArgs.push_back("-spirv-debug-info-version=ocl-100"); // Prevent crash in the translator if input IR contains DIExpression // operations which don't have mapping to OpenCL.DebugInfo.100 spec. TranslatorArgs.push_back("-spirv-allow-extra-diexpressions"); } std::string UnknownIntrinsics("-spirv-allow-unknown-intrinsics=llvm.genx."); TranslatorArgs.push_back(Args.MakeArgString(UnknownIntrinsics)); // Disable all the extensions by default std::string ExtArg("-spirv-ext=-all"); std::string DefaultExtArg = ",+SPV_EXT_shader_atomic_float_add,+SPV_EXT_shader_atomic_float_min_max" ",+SPV_KHR_no_integer_wrap_decoration,+SPV_KHR_float_controls" ",+SPV_KHR_expect_assume,+SPV_KHR_linkonce_odr"; std::string INTELExtArg = ",+SPV_INTEL_subgroups,+SPV_INTEL_media_block_io" ",+SPV_INTEL_device_side_avc_motion_estimation" ",+SPV_INTEL_fpga_loop_controls,+SPV_INTEL_unstructured_loop_controls" ",+SPV_INTEL_fpga_reg,+SPV_INTEL_blocking_pipes" ",+SPV_INTEL_function_pointers,+SPV_INTEL_kernel_attributes" ",+SPV_INTEL_io_pipes,+SPV_INTEL_inline_assembly" ",+SPV_INTEL_arbitrary_precision_integers" ",+SPV_INTEL_float_controls2,+SPV_INTEL_vector_compute" ",+SPV_INTEL_fast_composite" ",+SPV_INTEL_arbitrary_precision_fixed_point" ",+SPV_INTEL_arbitrary_precision_floating_point" ",+SPV_INTEL_variable_length_array,+SPV_INTEL_fp_fast_math_mode" ",+SPV_INTEL_long_constant_composite" ",+SPV_INTEL_arithmetic_fence" ",+SPV_INTEL_global_variable_decorations" ",+SPV_INTEL_cache_controls" ",+SPV_INTEL_fpga_buffer_location" ",+SPV_INTEL_fpga_argument_interfaces" ",+SPV_INTEL_fpga_invocation_pipelining_attributes" ",+SPV_INTEL_fpga_latency_control" ",+SPV_INTEL_task_sequence" ",+SPV_KHR_shader_clock" ",+SPV_INTEL_bindless_images"; ExtArg = ExtArg + DefaultExtArg + INTELExtArg; ExtArg += ",+SPV_INTEL_token_type" ",+SPV_INTEL_bfloat16_conversion" ",+SPV_INTEL_joint_matrix" ",+SPV_INTEL_hw_thread_queries" ",+SPV_KHR_uniform_group_instructions" ",+SPV_INTEL_masked_gather_scatter" ",+SPV_INTEL_tensor_float32_conversion" ",+SPV_INTEL_optnone" ",+SPV_KHR_non_semantic_info" ",+SPV_KHR_cooperative_matrix"; TranslatorArgs.push_back(Args.MakeArgString(ExtArg)); } /// Run LLVM to SPIR-V translation. /// Converts 'File' from LLVM bitcode to SPIR-V format using llvm-spirv tool. /// 'Args' encompasses all arguments required for linking device code and will /// be parsed to generate options required to be passed into llvm-spirv tool. static Expected runLLVMToSPIRVTranslation(StringRef File, const ArgList &Args) { llvm::TimeTraceScope TimeScope("LLVMToSPIRVTranslation"); StringRef LLVMSPIRVPath = Args.getLastArgValue(OPT_llvm_spirv_path_EQ); Expected LLVMToSPIRVProg = findProgram(Args, "llvm-spirv", {LLVMSPIRVPath}); if (!LLVMToSPIRVProg) return LLVMToSPIRVProg.takeError(); SmallVector CmdArgs; CmdArgs.push_back(*LLVMToSPIRVProg); const llvm::Triple Triple(Args.getLastArgValue(OPT_triple)); getSPIRVTransOpts(Args, CmdArgs, Triple); StringRef LLVMToSPIRVOptions; if (Arg *A = Args.getLastArg(OPT_llvm_spirv_options_EQ)) LLVMToSPIRVOptions = A->getValue(); LLVMToSPIRVOptions.split(CmdArgs, " ", /* MaxSplit = */ -1, /* KeepEmpty = */ false); CmdArgs.append({"-o", OutputFile}); CmdArgs.push_back(File); if (Error Err = executeCommands(*LLVMToSPIRVProg, CmdArgs)) return std::move(Err); if (!SPIRVDumpDir.empty()) { std::error_code EC = llvm::sys::fs::create_directory(SPIRVDumpDir, /*IgnoreExisting*/ true); if (EC) return createStringError( EC, formatv("failed to create dump directory. path: {0}, error_code: {1}", SPIRVDumpDir, EC.value())); StringRef Path = OutputFile; StringRef Filename = llvm::sys::path::filename(Path); SmallString<128> CopyPath = SPIRVDumpDir; CopyPath.append(Filename); EC = llvm::sys::fs::copy_file(Path, CopyPath); if (EC) return createStringError( EC, formatv( "failed to copy file. original: {0}, copy: {1}, error_code: {2}", Path, CopyPath, EC.value())); } return OutputFile; } Error runSYCLLink(ArrayRef Files, const ArgList &Args) { llvm::TimeTraceScope TimeScope("SYCLDeviceLink"); // First llvm-link step auto LinkedFile = linkDeviceInputFiles(Files, Args); if (!LinkedFile) reportError(LinkedFile.takeError()); // second llvm-link step auto DeviceLinkedFile = linkDeviceLibFiles(*LinkedFile, Args); if (!DeviceLinkedFile) reportError(DeviceLinkedFile.takeError()); // LLVM to SPIR-V translation step auto SPVFile = runLLVMToSPIRVTranslation(*DeviceLinkedFile, Args); if (!SPVFile) return SPVFile.takeError(); return Error::success(); } } // namespace int main(int argc, char **argv) { InitLLVM X(argc, argv); Executable = argv[0]; sys::PrintStackTraceOnErrorSignal(argv[0]); const OptTable &Tbl = getOptTable(); BumpPtrAllocator Alloc; StringSaver Saver(Alloc); auto Args = Tbl.parseArgs(argc, argv, OPT_INVALID, Saver, [&](StringRef Err) { reportError(createStringError(inconvertibleErrorCode(), Err)); }); if (Args.hasArg(OPT_help) || Args.hasArg(OPT_help_hidden)) { Tbl.printHelp( outs(), "clang-sycl-linker [options] ", "A utility that wraps around several steps required to link SYCL " "device files.\n" "This enables LLVM IR linking, post-linking and code generation for " "SYCL targets.", Args.hasArg(OPT_help_hidden), Args.hasArg(OPT_help_hidden)); return EXIT_SUCCESS; } if (Args.hasArg(OPT_version)) printVersion(outs()); Verbose = Args.hasArg(OPT_verbose); DryRun = Args.hasArg(OPT_dry_run); SaveTemps = Args.hasArg(OPT_save_temps); OutputFile = "a.spv"; if (Args.hasArg(OPT_o)) OutputFile = Args.getLastArgValue(OPT_o); if (Args.hasArg(OPT_spirv_dump_device_code_EQ)) { Arg *A = Args.getLastArg(OPT_spirv_dump_device_code_EQ); SmallString<128> Dir(A->getValue()); if (Dir.empty()) llvm::sys::path::native(Dir = "./"); else Dir.append(llvm::sys::path::get_separator()); SPIRVDumpDir = Dir; } // Get the input files to pass to the linking stage. auto FilesOrErr = getInput(Args); if (!FilesOrErr) reportError(FilesOrErr.takeError()); // Run SYCL linking process on the generated inputs. if (Error Err = runSYCLLink(*FilesOrErr, Args)) reportError(std::move(Err)); // Remove the temporary files created. if (!Args.hasArg(OPT_save_temps)) for (const auto &TempFile : TempFiles) if (std::error_code EC = sys::fs::remove(TempFile)) reportError(createFileError(TempFile, EC)); return EXIT_SUCCESS; }