================ @@ -0,0 +1,530 @@ +//=-- clang-sycl-link-wrapper/ClangSYCLLinkWrapper.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 wraps around the sequence of steps required to link device code in +// SYCL fat objects. 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. This tool can be removed once SYCL +// linking is ported to `ld.lld`. +// +//===---------------------------------------------------------------------===// + +#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-link-wrapper") << '\n'; +} + +/// The value of `argv[0]` when run. +static const char *Executable; + +/// Temporary files to be cleaned up. +static SmallVector<SmallString<128>> TempFiles; + +namespace { +// Must not overlap with llvm::opt::DriverFlag. +enum WrapperFlags { WrapperOnlyOption = (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 PREFIX(NAME, VALUE) \ + static constexpr StringLiteral NAME##_init[] = VALUE; \ + static constexpr ArrayRef<StringLiteral> NAME(NAME##_init, \ + std::size(NAME##_init) - 1); +#include "SYCLLinkOpts.inc" +#undef PREFIX + +static constexpr OptTable::Info InfoTable[] = { +#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), +#include "SYCLLinkOpts.inc" +#undef OPTION +}; + +class WrapperOptTable : public opt::GenericOptTable { +public: + WrapperOptTable() : opt::GenericOptTable(InfoTable) {} +}; + +const OptTable &getOptTable() { + static const WrapperOptTable *Table = []() { + auto Result = std::make_unique<WrapperOptTable>(); + 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<StringRef> 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<std::string> findProgram(const ArgList &Args, StringRef Name, + ArrayRef<StringRef> Paths) { + if (Args.hasArg(OPT_dry_run)) + return Name.str(); + ErrorOr<std::string> 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; +} + +std::optional<std::string> findFile(StringRef Dir, StringRef Root, + const Twine &Name) { + SmallString<128> Path; + if (Dir.starts_with("=")) + sys::path::append(Path, Root, Dir.substr(1), Name); + else + sys::path::append(Path, Dir, Name); + + if (sys::fs::exists(Path)) + return static_cast<std::string>(Path); + return std::nullopt; +} + +void printCommands(ArrayRef<StringRef> 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<StringRef> 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<SmallVector<std::string>> getInput(const ArgList &Args) { + // Collect all input bitcode files to be passed to llvm-link. + SmallVector<std::string> BitcodeFiles; + for (const opt::Arg *Arg : Args.filtered(OPT_INPUT)) { + std::optional<std::string> 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); + 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 and wrapping device +/// code and will be parsed to generate options required to be passed into the +/// llvm-link tool. +Expected<StringRef> linkDeviceInputFiles(ArrayRef<std::string> InputFiles, + const ArgList &Args) { + llvm::TimeTraceScope TimeScope("SYCL LinkDeviceInputFiles"); + Expected<std::string> LLVMLinkPath = + findProgram(Args, "llvm-link", {getMainExecutable("llvm-link")}); + if (!LLVMLinkPath) + return LLVMLinkPath.takeError(); + + SmallVector<StringRef> 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 *OutFileOrErr; +} + +const SmallVector<std::string> SYCLDeviceLibNames = { + "libsycl-crt.bc", + "libsycl-complex.bc", + "libsycl-complex-fp64.bc", + "libsycl-cmath.bc", + "libsycl-cmath-fp64.bc", + "libsycl-imf.bc", + "libsycl-imf-fp64.bc", + "libsycl-imf-bf16.bc", + "libsycl-fallback-cassert.bc", + "libsycl-fallback-cstring.bc", + "libsycl-fallback-complex.bc", + "libsycl-fallback-complex-fp64.bc", + "libsycl-fallback-cmath.bc", + "libsycl-fallback-cmath-fp64.bc", + "libsycl-fallback-imf.bc", + "libsycl-fallback-imf-fp64.bc", + "libsycl-fallback-imf-bf16.bc", + "libsycl-fallback-bfloat16.bc", + "libsycl-native-bfloat16.bc", + "libsycl-itt-user-wrappers.bc", + "libsycl-itt-compiler-wrappers.bc", + "libsycl-itt-stubs.bc", + "libsycl-sanitizer.bc"}; + +Expected<SmallVector<std::string>> getSYCLDeviceLibFiles(const ArgList &Args) { + SmallVector<std::string> DeviceLibFiles; + StringRef LibraryPath; + if (Arg *A = Args.getLastArg(OPT_library_path_EQ)) + LibraryPath = A->getValue(); + if (LibraryPath.empty()) + return DeviceLibFiles; + for (auto &DeviceLibName : SYCLDeviceLibNames) { + std::optional<std::string> Filename = + findFile(LibraryPath, /*Root=*/"", DeviceLibName); + if (Filename) + DeviceLibFiles.push_back(*Filename); + } + 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 and wrapping device +/// code and will be parsed to generate options required to be passed into the +/// llvm-link tool. +static Expected<StringRef> linkDeviceLibFiles(StringRef InputFile, + const ArgList &Args) { + llvm::TimeTraceScope TimeScope("LinkDeviceLibraryFiles"); + + auto SYCLDeviceLibFiles = getSYCLDeviceLibFiles(Args); + if (!SYCLDeviceLibFiles) + return SYCLDeviceLibFiles.takeError(); + if ((*SYCLDeviceLibFiles).empty()) + return InputFile; + + Expected<std::string> 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<StringRef, 8> 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. +/// NOTE: Any changes made here should be reflected in the similarly named +/// function in clang/lib/Driver/ToolChains/Clang.cpp. +static void getSPIRVTransOpts(const ArgList &Args, + SmallVector<StringRef, 8> &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 ---------------- asudarsa wrote:
List of SPIR-V extensions is 'fluid' and will be updated in an upcoming commit. Thanks https://github.com/llvm/llvm-project/pull/112245 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits