Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion clang/docs/ClangOffloadPackager.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ the following values for the :ref:`offload kind<table-offload_kind>` and the
+---------------+-------+---------------------------------------+
| IMG_PTX | 0x05 | The image is a CUDA PTX file |
+---------------+-------+---------------------------------------+

| IMG_SPV | 0x06 | The image is a SPIR-V binary file |
+---------------+-------+---------------------------------------+
.. table:: Offload Kind
:name: table-offload_kind

Expand All @@ -112,6 +113,8 @@ the following values for the :ref:`offload kind<table-offload_kind>` and the
+------------+-------+---------------------------------------+
| OFK_HIP | 0x03 | The producer was HIP |
+------------+-------+---------------------------------------+
| OFK_SYCL | 0x04 | The producer was SYCL |
+------------+-------+---------------------------------------+

The flags are used to signify certain conditions, such as the presence of
debugging information or whether or not LTO was used. The string entry table is
Expand Down
217 changes: 209 additions & 8 deletions clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include "llvm/CodeGen/CommandFlags.h"
#include "llvm/IR/DiagnosticPrinter.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Verifier.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Linker/Linker.h"
Expand Down Expand Up @@ -50,6 +52,9 @@
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Transforms/IPO/GlobalDCE.h"
#include "llvm/Transforms/Utils/SYCLSplitModule.h"
#include "llvm/Transforms/Utils/SYCLUtils.h"

using namespace llvm;
using namespace llvm::opt;
Expand Down Expand Up @@ -77,9 +82,14 @@ static void printVersion(raw_ostream &OS) {
/// The value of `argv[0]` when run.
static const char *Executable;

/// Mutex lock to protect writes to shared TempFiles in parallel.
static std::mutex TempFilesMutex;

/// Temporary files to be cleaned up.
static SmallVector<SmallString<128>> TempFiles;

using OffloadingImage = OffloadBinary::OffloadingImage;

namespace {
// Must not overlap with llvm::opt::DriverFlag.
enum LinkerFlags { LinkerOnlyOption = (1 << 4) };
Expand Down Expand Up @@ -143,6 +153,59 @@ Expected<StringRef> createTempFile(const ArgList &Args, const Twine &Prefix,
return TempFiles.back();
}

/// Get a temporary filename suitable for output.
Expected<StringRef> createOutputFile(const Twine &Prefix, StringRef Extension) {
std::scoped_lock<decltype(TempFilesMutex)> Lock(TempFilesMutex);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary mutex?

SmallString<128> OutputFile;
if (SaveTemps) {
(Prefix + "." + Extension).toNullTerminatedStringRef(OutputFile);
} 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<StringRef> writeOffloadFile(const OffloadFile &File) {
const OffloadBinary &Binary = *File.getBinary();

StringRef Prefix =
sys::path::stem(Binary.getMemoryBufferRef().getBufferIdentifier());
SmallString<128> Filename;
(Prefix + "-" + Binary.getTriple() + "-" + Binary.getArch())
.toVector(Filename);
llvm::replace(Filename, ':', '-');
auto TempFileOrErr = createOutputFile(Filename, "o");
if (!TempFileOrErr)
return TempFileOrErr.takeError();

Expected<std::unique_ptr<FileOutputBuffer>> OutputOrErr =
FileOutputBuffer::create(*TempFileOrErr, Binary.getImage().size());
if (!OutputOrErr)
return OutputOrErr.takeError();
std::unique_ptr<FileOutputBuffer> Output = std::move(*OutputOrErr);
llvm::copy(Binary.getImage(), Output->getBufferStart());
if (Error E = Output->commit())
return std::move(E);

return *TempFileOrErr;
}

static Error writeFile(StringRef Filename, StringRef Data) {
Expected<std::unique_ptr<FileOutputBuffer>> OutputOrErr =
FileOutputBuffer::create(Filename, Data.size());
if (!OutputOrErr)
return OutputOrErr.takeError();
std::unique_ptr<FileOutputBuffer> Output = std::move(*OutputOrErr);
llvm::copy(Data, Output->getBufferStart());
if (Error E = Output->commit())
return E;
return Error::success();
}

Expected<SmallVector<std::string>> getInput(const ArgList &Args) {
// Collect all input bitcode files to be passed to the device linking stage.
SmallVector<std::string> BitcodeFiles;
Expand Down Expand Up @@ -274,12 +337,79 @@ Expected<StringRef> linkDeviceCode(ArrayRef<std::string> InputFiles,
return *BitcodeOutput;
}

void cleanupModule(Module &M) {
ModuleAnalysisManager MAM;
MAM.registerPass([&] { return PassInstrumentationAnalysis(); });
ModulePassManager MPM;
MPM.addPass(GlobalDCEPass()); // Delete unreachable globals.
MPM.run(M, MAM);
}

void writeModuleToFile(const Module &M, StringRef Path, bool OutputAssembly) {
int FD = -1;
if (std::error_code EC = sys::fs::openFileForWrite(Path, FD)) {
errs() << formatv("error opening file: {0}, error: {1}", Path, EC.message())
<< '\n';
exit(1);
}

raw_fd_ostream OS(FD, /*ShouldClose*/ true);
if (OutputAssembly)
M.print(OS, /*AssemblyAnnotationWriter*/ nullptr);
else
WriteBitcodeToFile(M, OS);
}

Expected<SmallVector<ModuleAndSYCLMetadata>> runSYCLSplitModule(std::unique_ptr<Module> M, const ArgList &Args) {
SmallVector<ModuleAndSYCLMetadata> SplitModules;
if (Error Err = M->materializeAll())
return std::move(Err);
auto PostSYCLSplitCallback = [&](std::unique_ptr<Module> MPart,
std::string Symbols) {
if (verifyModule(*MPart)) {
errs() << "Broken Module!\n";
exit(1);
}
if (Error Err = MPart->materializeAll()) {
errs() << "Broken Module!\n";
exit(1);
}
// TODO: DCE is a crucial pass in a SYCL post-link pipeline.
// At the moment, LIT checking can't be perfomed without DCE.
cleanupModule(*MPart);
size_t ID = SplitModules.size();
StringRef ModuleSuffix = ".bc";
std::string ModulePath =
(Twine(OutputFile) + "_post_link_" + Twine(ID) + ModuleSuffix).str();
writeModuleToFile(*MPart, ModulePath, /* OutputAssembly */ false);
SplitModules.emplace_back(std::move(ModulePath), std::move(Symbols));
};

StringRef Mode = Args.getLastArgValue(OPT_sycl_split_mode_EQ);
auto SYCLSplitMode = StringSwitch<IRSplitMode>(Mode)
.Case("per_source", IRSplitMode::IRSM_PER_TU)
.Case("per_kernel", IRSplitMode::IRSM_PER_KERNEL)
.Case("none", IRSplitMode::IRSM_NONE)
.Default(IRSplitMode::IRSM_NONE);
SYCLSplitModule(std::move(M), SYCLSplitMode, PostSYCLSplitCallback);

if (Verbose) {
std::string OutputFiles;
for (size_t I = 0, E = SplitModules.size(); I != E; ++I) {
OutputFiles.append(SplitModules[I].ModuleFilePath);
OutputFiles.append("\n");
}
errs() << formatv("sycl-module-split: outputs:\n{0}\n", OutputFiles);
}
return SplitModules;
}

/// Run LLVM to SPIR-V translation.
/// Converts 'File' from LLVM bitcode to SPIR-V format using SPIR-V backend.
/// 'Args' encompasses all arguments required for linking device code and will
/// be parsed to generate options required to be passed into the backend.
static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args,
LLVMContext &C) {
static Error runSPIRVCodeGen(StringRef File, const ArgList &Args,
StringRef SPVFile, LLVMContext &C) {
llvm::TimeTraceScope TimeScope("SPIR-V code generation");

// Parse input module.
Expand All @@ -288,6 +418,9 @@ static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args,
if (!M)
return createStringError(Err.getMessage());

if (Error Err = M->materializeAll())
return std::move(Err);

Triple TargetTriple(Args.getLastArgValue(OPT_triple_EQ));
M->setTargetTriple(TargetTriple);

Expand All @@ -313,7 +446,7 @@ static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args,

// Open output file for writing.
int FD = -1;
if (std::error_code EC = sys::fs::openFileForWrite(OutputFile, FD))
if (std::error_code EC = sys::fs::openFileForWrite(SPVFile, FD))
return errorCodeToError(EC);
auto OS = std::make_unique<llvm::raw_fd_ostream>(FD, true);

Expand All @@ -328,9 +461,9 @@ static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args,

if (Verbose)
errs() << formatv("SPIR-V Backend: input: {0}, output: {1}\n", File,
OutputFile);
SPVFile);

return OutputFile;
return Error::success();
}

/// Performs the following steps:
Expand All @@ -339,17 +472,85 @@ static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args,
Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
llvm::TimeTraceScope TimeScope("SYCL device linking");

std::mutex ImageMtx;
LLVMContext C;

// Link all input bitcode files and SYCL device library files, if any.
auto LinkedFile = linkDeviceCode(Files, Args, C);
if (!LinkedFile)
reportError(LinkedFile.takeError());

auto LinkedModule = getBitcodeModule(*LinkedFile, C);
if (!LinkedModule)
return LinkedModule.takeError();
// sycl-post-link step
auto SplitModules = runSYCLSplitModule(std::move(*LinkedModule), Args);
if (!SplitModules)
reportError(SplitModules.takeError());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
reportError(SplitModules.takeError());
return SplitModules.takeError();


// SPIR-V code generation step.
auto SPVFile = runSPIRVCodeGen(*LinkedFile, Args, C);
if (!SPVFile)
return SPVFile.takeError();
for (size_t I = 0, E = (*SplitModules).size(); I != E; ++I) {
std::string SPVFile(OutputFile);
SPVFile.append(utostr(I));
auto Err = runSPIRVCodeGen((*SplitModules)[I].ModuleFilePath, Args, SPVFile, C);
if (Err)
return std::move(Err);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return std::move(Err);
return Err;

(*SplitModules)[I].ModuleFilePath = SPVFile;
}

SmallVector<char, 1024> BinaryData;
raw_svector_ostream OS(BinaryData);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use raw_fd_ostream and write out to a file incrementally instead of holding all the data in memory?

for (size_t I = 0, E = (*SplitModules).size(); I != E; ++I) {
auto File = (*SplitModules)[I].ModuleFilePath;
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileOrErr =
llvm::MemoryBuffer::getFileOrSTDIN(File);
if (std::error_code EC = FileOrErr.getError()) {
if (DryRun)
FileOrErr = MemoryBuffer::getMemBuffer("");
else
return createFileError(File, EC);
}

std::scoped_lock<decltype(ImageMtx)> Guard(ImageMtx);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary mutex?

OffloadingImage TheImage{};
TheImage.TheImageKind = IMG_Object;
TheImage.TheOffloadKind = OFK_SYCL;
TheImage.StringData["triple"] =
Args.MakeArgString(Args.getLastArgValue(OPT_triple_EQ));
TheImage.StringData["arch"] =
Args.MakeArgString(Args.getLastArgValue(OPT_arch_EQ));
TheImage.Image = std::move(*FileOrErr);

llvm::SmallString<0> Buffer = OffloadBinary::write(TheImage);
if (Buffer.size() % OffloadBinary::getAlignment() != 0)
return createStringError(inconvertibleErrorCode(),
"Offload binary has invalid size alignment");
OS << Buffer;
}
if (Error E = writeFile(OutputFile,
StringRef(BinaryData.begin(), BinaryData.size())))
return E;

{
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block of code unbundles the bundled file and then dumps each of the offload binary into a file. Thic block exists here only for testing and a similar code will reside in clang-linker-wrapper.

ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
MemoryBuffer::getFileOrSTDIN(OutputFile);
if (std::error_code EC = BufferOrErr.getError())
return createFileError(OutputFile, EC);

MemoryBufferRef Buffer = **BufferOrErr;
SmallVector<OffloadFile> Binaries;
if (Error Err = extractOffloadBinaries(Buffer, Binaries))
return std::move(Err);

unsigned I = 1;
for (auto &OffloadFile : Binaries) {
auto FileNameOrErr = writeOffloadFile(OffloadFile);
if (!FileNameOrErr)
return FileNameOrErr.takeError();
llvm::errs() << I++ << ". " << *FileNameOrErr << "\n";
}
}

return Error::success();
}

Expand Down
3 changes: 3 additions & 0 deletions clang/tools/clang-sycl-linker/SYCLLinkOpts.td
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def save_temps : Flag<["--", "-"], "save-temps">,
def dry_run : Flag<["--", "-"], "dry-run">, Flags<[LinkerOnlyOption]>,
HelpText<"Print generated commands without running.">;

def sycl_split_mode_EQ : Joined<["--", "-"], "sycl-split-mode=">,
HelpText<"Mode of splitting performed by SYCL splitting algorithm. Options are 'per_source', 'per_kernel' and 'none'.">;

def spirv_dump_device_code_EQ : Joined<["--", "-"], "spirv-dump-device-code=">,
Flags<[LinkerOnlyOption]>,
HelpText<"Path to the folder where the tool dumps SPIR-V device code. Other formats aren't dumped.">;
Expand Down
2 changes: 2 additions & 0 deletions llvm/include/llvm/Object/OffloadBinary.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum OffloadKind : uint16_t {
OFK_OpenMP,
OFK_Cuda,
OFK_HIP,
OFK_SYCL,
OFK_LAST,
};

Expand All @@ -46,6 +47,7 @@ enum ImageKind : uint16_t {
IMG_Cubin,
IMG_Fatbinary,
IMG_PTX,
IMG_SPV,
IMG_LAST,
};

Expand Down
Loading