Files
TheRock/compiler/amd-llvm/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

885 lines
31 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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 bitcode 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/OffloadArch.h"
#include "clang/Basic/Version.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/CodeGen/CommandFlags.h"
#include "llvm/Frontend/Offloading/Utility.h"
#include "llvm/IR/DiagnosticPrinter.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Linker/Linker.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Object/Binary.h"
#include "llvm/Object/IRObjectFile.h"
#include "llvm/Object/OffloadBinary.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/InitLLVM.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"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Transforms/Utils/SplitModuleByCategory.h"
using namespace llvm;
using namespace llvm::opt;
using namespace llvm::object;
using namespace clang;
/// Print commands with 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;
using OffloadingImage = OffloadBinary::OffloadingImage;
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<SmallString<128>> 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
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) {}
};
} // namespace
static const OptTable &getOptTable() {
static const LinkerOptTable *Table = []() {
auto Result = std::make_unique<LinkerOptTable>();
return Result.release();
}();
return *Table;
}
[[noreturn]] static void reportError(Error E) {
outs().flush();
logAllUnhandledErrors(std::move(E), WithColor::error(errs(), Executable));
exit(EXIT_FAILURE);
}
static 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();
}
static Expected<StringRef>
createTempFile(const ArgList &Args, const Twine &Prefix, StringRef Extension) {
SmallString<128> Path;
if (Args.hasArg(OPT_save_temps) || DryRun) {
// Generate a unique path name without creating a file
sys::fs::createUniquePath(Prefix + "-%%%%%%." + Extension, Path,
/*MakeAbsolute=*/false);
} else {
if (std::error_code EC =
sys::fs::createTemporaryFile(Prefix, Extension, Path))
return createFileError(Path, EC);
}
TempFiles.emplace_back(std::move(Path));
return TempFiles.back();
}
static Expected<std::string> findProgram(const ArgList &Args, StringRef Name,
ArrayRef<StringRef> Paths) {
if (DryRun)
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;
}
static 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.
static Error executeCommands(StringRef ExecutablePath,
ArrayRef<StringRef> Args) {
if (Verbose || DryRun)
printCommands(Args);
if (DryRun)
return Error::success();
if (sys::ExecuteAndWait(ExecutablePath, Args))
return createStringError("'%s' failed",
sys::path::filename(ExecutablePath).str().c_str());
return Error::success();
}
static Expected<SmallVector<std::string>> getInput(const ArgList &Args) {
// Collect all input bitcode files to be passed to the linking stage.
SmallVector<std::string> BitcodeFiles;
auto Inputs = Args.filtered(OPT_INPUT);
if (Inputs.empty())
return createStringError("No input files provided");
for (const opt::Arg *Arg : Inputs) {
StringRef Filename = Arg->getValue();
if (!sys::fs::exists(Filename) || sys::fs::is_directory(Filename))
return createStringError("Input file '" + Filename + "' does not exist");
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 SPIR-V IR files.
if (Magic != file_magic::bitcode)
return createStringError("Unsupported file type for '" + Filename + "'");
BitcodeFiles.push_back(std::string(Filename));
}
return BitcodeFiles;
}
/// Handle cases where input file is a LLVM IR bitcode file.
/// When clang-sycl-linker is called via clang-linker-wrapper tool, input files
/// are LLVM IR bitcode files.
// TODO: Support SPIR-V IR files.
static Expected<std::unique_ptr<Module>> getBitcodeModule(StringRef File,
LLVMContext &C) {
SMDiagnostic Err;
auto M = getLazyIRFileModule(File, Err, C);
if (M)
return std::move(M);
return createStringError(Err.getMessage());
}
static std::optional<std::string> findFile(StringRef Dir, const Twine &Name) {
SmallString<128> Path(Dir);
llvm::sys::path::append(Path, Name);
if (sys::fs::exists(Path) && !sys::fs::is_directory(Path))
return std::string(Path);
return std::nullopt;
}
static std::optional<std::string>
searchLibrary(StringRef Name, ArrayRef<StringRef> SearchPaths) {
// An absolute path is taken as-is; -L paths are only consulted for relative
// names.
if (sys::path::is_absolute(Name)) {
if (sys::fs::exists(Name) && !sys::fs::is_directory(Name))
return std::string(Name);
return std::nullopt;
}
for (StringRef Dir : SearchPaths)
if (std::optional<std::string> File = findFile(Dir, Name))
return File;
return std::nullopt;
}
/// Gather all library files. The list of files and its location are passed from
/// driver.
static Expected<SmallVector<std::string>>
getBCLibraryNames(const ArgList &Args) {
SmallVector<StringRef> LibraryPaths;
for (const opt::Arg *Arg : Args.filtered(OPT_library_path))
LibraryPaths.push_back(Arg->getValue());
SmallVector<std::string> LibraryFiles;
for (const opt::Arg *Arg : Args.filtered(OPT_bc_library)) {
std::optional<std::string> LibName =
searchLibrary(Arg->getValue(), LibraryPaths);
if (!LibName)
return createStringError("'" + Twine(Arg->getValue()) +
"' library file not found");
LibraryFiles.push_back(std::move(*LibName));
}
return LibraryFiles;
}
namespace {
struct LinkResult {
std::unique_ptr<Module> LinkedModule;
SmallString<256> BitcodeFile;
llvm::Triple TargetTriple;
};
} // namespace
/// Following tasks are performed:
/// 1. Resolve the target triple: use --triple= when given, otherwise take the
/// first input that supplies a triple as canonical. Issue an error if any
/// triple inputs disagree.
/// 2. Link all input bitcode images into one image using the linkInModule API.
/// 3. Gather all library bitcode images.
/// 4. Link all the images gathered in Step 3 with the output of Step 2 using
/// linkInModule API. LinkOnlyNeeded flag is used.
static Expected<LinkResult> linkInputs(ArrayRef<std::string> InputFiles,
const ArgList &Args, LLVMContext &C) {
llvm::TimeTraceScope TimeScope("Link code");
assert(InputFiles.size() && "No inputs to link");
// Get all library files.
Expected<SmallVector<std::string>> BCLibFiles = getBCLibraryNames(Args);
if (!BCLibFiles)
return BCLibFiles.takeError();
// Create a new file to write the linked file to.
auto BitcodeOutput =
createTempFile(Args, sys::path::filename(OutputFile), "bc");
if (!BitcodeOutput)
return BitcodeOutput.takeError();
if (Verbose) {
std::string Inputs = llvm::join(InputFiles.begin(), InputFiles.end(), ", ");
std::string LibInputs =
llvm::join((*BCLibFiles).begin(), (*BCLibFiles).end(), ", ");
errs() << formatv("link: inputs: {0} libfiles: {1} output: {2}\n", Inputs,
LibInputs, *BitcodeOutput);
}
// Link input files. Resolve the target triple.
llvm::Triple TargetTriple(Args.getLastArgValue(OPT_triple_EQ));
StringRef TripleSource = TargetTriple.empty() ? "" : "--triple=";
auto LinkerOutput = std::make_unique<Module>("linker-output", C);
Linker L(*LinkerOutput);
for (auto &File : InputFiles) {
auto ModOrErr = getBitcodeModule(File, C);
if (!ModOrErr)
return ModOrErr.takeError();
const llvm::Triple &T = (*ModOrErr)->getTargetTriple();
if (!T.empty() && T != TargetTriple) {
if (TargetTriple.empty()) {
TargetTriple = T;
TripleSource = File;
} else {
return createStringError(
"conflicting target triples: '" + TargetTriple.str() + "' (from " +
TripleSource + ") vs '" + T.str() + "' (from " + File + ")");
}
}
if (L.linkInModule(std::move(*ModOrErr)))
return createStringError("Could not link IR");
}
if (TargetTriple.empty())
return createStringError(
"Target triple must be specified or inferable from inputs");
// Link in library files.
for (auto &File : *BCLibFiles) {
auto LibMod = getBitcodeModule(File, C);
if (!LibMod)
return LibMod.takeError();
if ((*LibMod)->getTargetTriple() == TargetTriple) {
unsigned Flags = Linker::Flags::LinkOnlyNeeded;
if (L.linkInModule(std::move(*LibMod), Flags))
return createStringError("Could not link IR");
}
}
// Dump linked output for testing.
if (Args.hasArg(OPT_print_linked_module))
outs() << *LinkerOutput;
// Write the final output into 'BitcodeOutput' file.
if (!DryRun) {
int FD = -1;
if (std::error_code EC = sys::fs::openFileForWrite(*BitcodeOutput, FD))
return errorCodeToError(EC);
llvm::raw_fd_ostream OS(FD, true);
WriteBitcodeToFile(*LinkerOutput, OS);
}
return LinkResult{std::move(LinkerOutput), SmallString<256>(*BitcodeOutput),
std::move(TargetTriple)};
}
/// Run Code Generation using LLVM backend.
/// \param File The input LLVM IR bitcode file.
/// \param TargetTriple The resolved target triple.
/// \param Args encompasses all arguments required for linking device code and
/// will be parsed to generate options required to be passed into the backend.
/// \param OutputFile The output file name.
/// \param C The LLVM context.
static Error runCodeGen(StringRef File, const llvm::Triple &TargetTriple,
const ArgList &Args, StringRef OutputFile,
LLVMContext &C) {
llvm::TimeTraceScope TimeScope("Code generation");
if (Verbose || DryRun)
errs() << formatv("LLVM backend: input: {0}, output: {1}\n", File,
OutputFile);
if (DryRun)
return Error::success();
// Parse input module.
SMDiagnostic Err;
std::unique_ptr<Module> M = parseIRFile(File, Err, C);
if (!M)
return createStringError(Err.getMessage());
if (Error MatErr = M->materializeAll())
return MatErr;
M->setTargetTriple(TargetTriple);
// Get a handle to a target backend.
std::string Msg;
const Target *T = TargetRegistry::lookupTarget(M->getTargetTriple(), Msg);
if (!T)
return createStringError(Msg + ": " + M->getTargetTriple().str());
// Allocate target machine.
TargetOptions Options;
std::optional<Reloc::Model> RM;
std::optional<CodeModel::Model> CM;
std::unique_ptr<TargetMachine> TM(
T->createTargetMachine(M->getTargetTriple(), /*CPU=*/"",
/*Features=*/"", Options, RM, CM));
if (!TM)
return createStringError("Could not allocate target machine!");
// Set data layout if needed.
if (M->getDataLayout().isDefault())
M->setDataLayout(TM->createDataLayout());
// Open output file for writing.
int FD = -1;
if (std::error_code EC = sys::fs::openFileForWrite(OutputFile, FD))
return errorCodeToError(EC);
auto OS = std::make_unique<llvm::raw_fd_ostream>(FD, true);
legacy::PassManager CodeGenPasses;
TargetLibraryInfoImpl TLII(M->getTargetTriple());
CodeGenPasses.add(new TargetLibraryInfoWrapperPass(TLII));
if (TM->addPassesToEmitFile(CodeGenPasses, *OS, nullptr,
CodeGenFileType::ObjectFile))
return createStringError("Failed to execute LLVM backend");
CodeGenPasses.run(*M);
return Error::success();
}
/// Run AOT compilation for Intel CPU.
/// Calls opencl-aot tool to generate device code for the Intel OpenCL CPU
/// Runtime.
/// \param InputFile The input SPIR-V file.
/// \param OutputFile The output file name.
/// \param Args Encompasses all arguments required for linking and wrapping
/// device code and will be parsed to generate options required to be passed
/// into the AOT compilation step.
static Error runAOTCompileIntelCPU(StringRef InputFile, StringRef OutputFile,
const ArgList &Args) {
SmallVector<StringRef, 8> CmdArgs;
Expected<std::string> OpenCLAOTPath =
findProgram(Args, "opencl-aot", {getMainExecutable("opencl-aot")});
if (!OpenCLAOTPath)
return OpenCLAOTPath.takeError();
CmdArgs.push_back(*OpenCLAOTPath);
CmdArgs.push_back("--device=cpu");
StringRef ExtraArgs = Args.getLastArgValue(OPT_opencl_aot_options_EQ);
ExtraArgs.split(CmdArgs, " ", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
CmdArgs.push_back("-o");
CmdArgs.push_back(OutputFile);
CmdArgs.push_back(InputFile);
if (Error Err = executeCommands(*OpenCLAOTPath, CmdArgs))
return Err;
return Error::success();
}
/// Run AOT compilation for Intel GPU.
/// Calls ocloc tool to generate device code for the Intel Graphics Compute
/// Runtime.
/// \param InputFile The input SPIR-V file.
/// \param OutputFile The output file name.
/// \param Args Encompasses all arguments required for linking and wrapping
/// device code and will be parsed to generate options required to be passed
/// into the AOT compilation step.
static Error runAOTCompileIntelGPU(StringRef InputFile, StringRef OutputFile,
const ArgList &Args) {
SmallVector<StringRef, 8> CmdArgs;
Expected<std::string> OclocPath =
findProgram(Args, "ocloc", {getMainExecutable("ocloc")});
if (!OclocPath)
return OclocPath.takeError();
CmdArgs.push_back(*OclocPath);
// The next line prevents ocloc from modifying the image name
CmdArgs.push_back("-output_no_suffix");
CmdArgs.push_back("-spirv_input");
StringRef Arch(Args.getLastArgValue(OPT_arch_EQ));
assert(!Arch.empty() && "Arch must be specified for AOT compilation");
CmdArgs.push_back("-device");
CmdArgs.push_back(Arch);
StringRef ExtraArgs = Args.getLastArgValue(OPT_ocloc_options_EQ);
ExtraArgs.split(CmdArgs, " ", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
CmdArgs.push_back("-output");
CmdArgs.push_back(OutputFile);
CmdArgs.push_back("-file");
CmdArgs.push_back(InputFile);
if (Error Err = executeCommands(*OclocPath, CmdArgs))
return Err;
return Error::success();
}
/// Run AOT compilation for Intel CPU/GPU.
/// \param InputFile The input SPIR-V file.
/// \param OutputFile The output file name.
/// \param Args Encompasses all arguments required for linking and wrapping
/// device code and will be parsed to generate options required to be passed
/// into the AOT compilation step.
static Error runAOTCompile(StringRef InputFile, StringRef OutputFile,
const ArgList &Args) {
StringRef Arch = Args.getLastArgValue(OPT_arch_EQ);
OffloadArch OA = StringToOffloadArch(Arch);
if (IsIntelGPUOffloadArch(OA))
return runAOTCompileIntelGPU(InputFile, OutputFile, Args);
if (IsIntelCPUOffloadArch(OA))
return runAOTCompileIntelCPU(InputFile, OutputFile, Args);
llvm_unreachable("runAOTCompile dispatched on unsupported arch");
}
static constexpr char AttrSYCLModuleId[] = "sycl-module-id";
namespace {
/// SYCL device code module split mode.
enum class IRSplitMode {
SPLIT_PER_TU, // one module per translation unit
SPLIT_PER_KERNEL, // one module per kernel
SPLIT_NONE // no splitting
};
} // namespace
/// Parses the value of \p --module-split-mode.
static std::optional<IRSplitMode> convertStringToSplitMode(StringRef S) {
return StringSwitch<std::optional<IRSplitMode>>(S)
.Case("source", IRSplitMode::SPLIT_PER_TU)
.Case("kernel", IRSplitMode::SPLIT_PER_KERNEL)
.Case("none", IRSplitMode::SPLIT_NONE)
.Default(std::nullopt);
}
static StringRef splitModeToString(IRSplitMode Mode) {
switch (Mode) {
case IRSplitMode::SPLIT_PER_TU:
return "source";
case IRSplitMode::SPLIT_PER_KERNEL:
return "kernel";
case IRSplitMode::SPLIT_NONE:
return "none";
}
llvm_unreachable("bad split mode");
}
namespace {
/// Result of splitting a device module: the bitcode file path and the
/// serialized symbol table for each device image.
struct SplitModule {
SmallString<256> ModuleFilePath;
SmallString<0> Symbols;
};
} // namespace
static bool isEntryPoint(const Function &F, bool EmitOnlyKernelsAsEntryPoints) {
if (F.isDeclaration())
return false;
if (F.hasKernelCallingConv())
return true;
if (EmitOnlyKernelsAsEntryPoints)
return false;
// sycl_external functions carry the "sycl-module-id" attribute.
return F.hasFnAttribute(AttrSYCLModuleId);
}
/// Collect entry point names from \p M and serialize them into a symbol table.
static SmallString<0> collectEntryPoints(const Module &M,
bool EmitOnlyKernelsAsEntryPoints) {
SmallVector<StringRef> Names;
for (const Function &F : M)
if (isEntryPoint(F, EmitOnlyKernelsAsEntryPoints))
Names.push_back(F.getName());
SmallString<0> SymbolData;
llvm::offloading::sycl::writeSymbolTable(Names, SymbolData);
return SymbolData;
}
namespace {
/// Functor passed to splitModuleTransitiveFromEntryPoints. For each input
/// function \p F, returns a numeric group ID (if \p F is an entry point)
/// determining which device image it lands in, or std::nullopt (for
/// non-entry-points). SPLIT_PER_KERNEL \p Mode gives each kernel its own ID;
/// SPLIT_PER_TU \p Mode groups kernels by their "sycl-module-id" attribute
/// value.
class EntryPointCategorizer {
public:
EntryPointCategorizer(IRSplitMode Mode, bool EmitOnlyKernelsAsEntryPoints)
: Mode(Mode), OnlyKernelsAreEntryPoints(EmitOnlyKernelsAsEntryPoints) {}
std::optional<int> operator()(const Function &F) {
if (!isEntryPoint(F, OnlyKernelsAreEntryPoints))
return std::nullopt;
std::string Key;
switch (Mode) {
case IRSplitMode::SPLIT_PER_KERNEL:
Key = F.getName().str();
break;
case IRSplitMode::SPLIT_PER_TU:
Key = F.getFnAttribute(AttrSYCLModuleId).getValueAsString().str();
break;
case IRSplitMode::SPLIT_NONE:
llvm_unreachable("categorizer cannot be used for SPLIT_NONE");
}
auto [It, Inserted] =
StrToId.try_emplace(std::move(Key), static_cast<int>(StrToId.size()));
return It->second;
}
private:
IRSplitMode Mode;
bool OnlyKernelsAreEntryPoints;
llvm::StringMap<int> StrToId;
};
} // namespace
/// Splits the fully linked device \p M into one bitcode file per device image
/// according to \p Mode and returns the list of split images with their symbol
/// tables. The module is split transitively from entry points; each part is
/// written to a fresh temporary bitcode file.
static Expected<SmallVector<SplitModule, 0>>
splitDeviceCode(std::unique_ptr<Module> M, StringRef LinkedBitcodeFile,
IRSplitMode Mode, bool EmitOnlyKernelsAsEntryPoints,
const ArgList &Args) {
assert(Mode != IRSplitMode::SPLIT_NONE && "SPLIT_NONE is unsupported");
SmallVector<SplitModule, 0> SplitModules;
EntryPointCategorizer Categorizer(Mode, EmitOnlyKernelsAsEntryPoints);
auto SplitCallback = [&](std::unique_ptr<Module> Part) -> Error {
Expected<StringRef> BitcodeFileOrErr =
createTempFile(Args, sys::path::filename(OutputFile), "bc");
if (!BitcodeFileOrErr)
return BitcodeFileOrErr.takeError();
if (!DryRun) {
int FD = -1;
if (std::error_code EC = sys::fs::openFileForWrite(*BitcodeFileOrErr, FD))
return errorCodeToError(EC);
raw_fd_ostream OS(FD, /*shouldClose=*/true);
WriteBitcodeToFile(*Part, OS);
}
SplitModules.push_back(
{SmallString<256>(*BitcodeFileOrErr),
collectEntryPoints(*Part, EmitOnlyKernelsAsEntryPoints)});
return Error::success();
};
if (Error Err = splitModuleTransitiveFromEntryPoints(
std::move(M), Categorizer, SplitCallback))
return Err;
if (Verbose) {
errs() << formatv("sycl-module-split: input: {0}, mode: {1}\n",
LinkedBitcodeFile, splitModeToString(Mode));
for (const SplitModule &SI : SplitModules) {
errs() << formatv("{0} [", SI.ModuleFilePath);
llvm::offloading::sycl::forEachSymbol(
SI.Symbols, [](StringRef Name) { errs() << Name << " "; });
errs() << "]\n";
}
}
return SplitModules;
}
/// Returns true if module splitting can be skipped: either \p Mode is
/// SPLIT_NONE, or \p M contains no entry points (nothing to split from).
static bool canSkipModuleSplit(IRSplitMode Mode, const Module &M,
bool EmitOnlyKernelsAsEntryPoints) {
if (Mode == IRSplitMode::SPLIT_NONE)
return true;
return llvm::none_of(M.functions(), [&](const Function &F) {
return isEntryPoint(F, EmitOnlyKernelsAsEntryPoints);
});
}
/// Performs the following steps:
/// 1. Link all input bitcode files together with library files.
/// 2. Optionally split the linked module according to the requested
/// IRSplitMode.
/// 3. Run SPIR-V code generation on each (split) module.
/// 4. Optionally run AOT compilation when targeting an Intel HW arch.
/// 5. Pack the resulting images into a single OffloadBinary written to the
/// output file.
static Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
llvm::TimeTraceScope TimeScope("SYCL linking");
LLVMContext C;
// Link all input bitcode files and library files.
Expected<LinkResult> LinkedOrErr = linkInputs(Files, Args, C);
if (!LinkedOrErr)
return LinkedOrErr.takeError();
LinkResult &Result = *LinkedOrErr;
// Determine the requested module split mode.
IRSplitMode SplitMode = IRSplitMode::SPLIT_PER_TU;
if (Arg *A = Args.getLastArg(OPT_module_split_mode_EQ)) {
std::optional<IRSplitMode> ModeOrNone =
convertStringToSplitMode(A->getValue());
if (!ModeOrNone)
return createStringError(formatv(
"module-split-mode value isn't recognized: {0}", A->getValue()));
SplitMode = *ModeOrNone;
}
// TODO: Expose this as a command-line option and default it to false when
// device-image dynamic linking is supported, so that sycl_external functions
// can be called across device image boundaries.
bool EmitOnlyKernelsAsEntryPoints = true;
SmallVector<SplitModule, 0> SplitModules;
if (canSkipModuleSplit(SplitMode, *Result.LinkedModule,
EmitOnlyKernelsAsEntryPoints)) {
SplitModules.push_back({SmallString<256>(Result.BitcodeFile),
collectEntryPoints(*Result.LinkedModule,
EmitOnlyKernelsAsEntryPoints)});
} else {
Expected<SmallVector<SplitModule, 0>> SplitModulesOrErr =
splitDeviceCode(std::move(Result.LinkedModule), Result.BitcodeFile,
SplitMode, EmitOnlyKernelsAsEntryPoints, Args);
if (!SplitModulesOrErr)
return SplitModulesOrErr.takeError();
SplitModules = std::move(*SplitModulesOrErr);
}
bool IsAOTCompileNeeded = IsIntelOffloadArch(
StringToOffloadArch(Args.getLastArgValue(OPT_arch_EQ)));
StringRef OutputFileNameExt = ".spv";
// Code generation step.
for (size_t I = 0, E = SplitModules.size(); I != E; ++I) {
StringRef Stem = OutputFile.rsplit('.').first;
std::string CodeGenFile = (Stem + "_" + Twine(I) + OutputFileNameExt).str();
if (Error Err = runCodeGen(SplitModules[I].ModuleFilePath,
Result.TargetTriple, Args, CodeGenFile, C))
return Err;
if (!SPIRVDumpDir.empty() && !DryRun) {
SmallString<128> DumpFile(SPIRVDumpDir);
sys::path::append(DumpFile, sys::path::filename(CodeGenFile));
if (std::error_code EC = sys::fs::copy_file(CodeGenFile, DumpFile))
return createFileError(DumpFile, EC);
}
SplitModules[I].ModuleFilePath = CodeGenFile;
if (IsAOTCompileNeeded) {
std::string AOTFile = (Stem + "_" + Twine(I) + ".out").str();
if (Error Err = runAOTCompile(CodeGenFile, AOTFile, Args))
return Err;
SplitModules[I].ModuleFilePath = AOTFile;
}
}
// Collect all images to be packed into a single OffloadBinary.
SmallVector<OffloadingImage> Images;
for (SplitModule &SI : SplitModules) {
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileOrErr =
DryRun ? llvm::MemoryBuffer::getMemBuffer("")
: llvm::MemoryBuffer::getFileOrSTDIN(SI.ModuleFilePath);
if (!FileOrErr)
return createFileError(SI.ModuleFilePath, FileOrErr.getError());
OffloadingImage TheImage{};
TheImage.TheImageKind = IsAOTCompileNeeded ? IMG_Object : IMG_SPIRV;
TheImage.TheOffloadKind = OFK_SYCL;
TheImage.StringData["triple"] =
Args.MakeArgString(Result.TargetTriple.str());
TheImage.StringData["arch"] =
Args.MakeArgString(Args.getLastArgValue(OPT_arch_EQ));
TheImage.StringData["symbols"] = SI.Symbols;
TheImage.Image = std::move(*FileOrErr);
Images.emplace_back(std::move(TheImage));
}
if (Verbose) {
for (const OffloadingImage &Image : Images)
errs() << formatv(
"sycl-bundle: image kind: {0}, triple: {1}, arch: {2}\n",
getImageKindName(Image.TheImageKind),
Image.StringData.lookup("triple"), Image.StringData.lookup("arch"));
}
llvm::SmallString<0> Buffer = OffloadBinary::write(Images);
if (Buffer.size() % OffloadBinary::getAlignment() != 0)
return createStringError("Offload binary has invalid size alignment");
if (DryRun)
return Error::success();
auto OutputOrErr = FileOutputBuffer::create(OutputFile, Buffer.size());
if (!OutputOrErr)
return OutputOrErr.takeError();
llvm::copy(Buffer, (*OutputOrErr)->getBufferStart());
return (*OutputOrErr)->commit();
}
int main(int argc, char **argv) {
InitLLVM X(argc, argv);
InitializeAllTargetInfos();
InitializeAllTargets();
InitializeAllTargetMCs();
InitializeAllAsmParsers();
InitializeAllAsmPrinters();
Executable = argv[0];
sys::PrintStackTraceOnErrorSignal(argv[0]);
const OptTable &Tbl = getOptTable();
BumpPtrAllocator Alloc;
StringSaver Saver(Alloc);
auto Args = Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [](StringRef Err) {
reportError(createStringError(Err));
});
if (Args.hasArg(OPT_help) || Args.hasArg(OPT_help_hidden)) {
Tbl.printHelp(
outs(), "clang-sycl-linker [options] <input bitcode files>",
"A utility that wraps around the SYCL device code linking process.\n"
"This enables LLVM IR linking, post-linking and code generation for "
"SPIR-V JIT and AOT targets.",
Args.hasArg(OPT_help_hidden), Args.hasArg(OPT_help_hidden));
return EXIT_SUCCESS;
}
if (Args.hasArg(OPT_version)) {
printVersion(outs());
return EXIT_SUCCESS;
}
Verbose = Args.hasArg(OPT_verbose);
DryRun = Args.hasArg(OPT_dry_run);
if (!Args.hasArg(OPT_o))
reportError(createStringError("Output file must be specified"));
OutputFile = Args.getLastArgValue(OPT_o);
// Get the input files to pass to the linking stage.
auto FilesOrErr = getInput(Args);
if (!FilesOrErr)
reportError(FilesOrErr.takeError());
if (auto *A = Args.getLastArg(OPT_spirv_dump_device_code_EQ)) {
StringRef V = A->getValue();
if (V.empty())
reportError(createStringError(
std::make_error_code(std::errc::invalid_argument),
"--spirv-dump-device-code= requires a non-empty path"));
SPIRVDumpDir = V;
// The directory is shared across all split modules, which use the
// "<output-stem>_<index>.spv" naming scheme. Concurrent invocations
// sharing a dump dir may overwrite each other's files.
if (!DryRun)
if (std::error_code EC = sys::fs::create_directories(SPIRVDumpDir))
reportError(createStringError(
EC, "cannot create SPIR-V dump directory '" + SPIRVDumpDir + "'"));
}
// 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) && !DryRun)
for (const auto &TempFile : TempFiles)
if (std::error_code EC = sys::fs::remove(TempFile))
reportError(createFileError(TempFile, EC));
return EXIT_SUCCESS;
}