Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 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
43 changes: 15 additions & 28 deletions clang/test/Driver/clang-sycl-linker-test.cpp
Original file line number Diff line number Diff line change
@@ -1,48 +1,35 @@
// Tests the clang-sycl-linker tool.
//
// Test a simple case without arguments.
// RUN: %clangxx -emit-llvm -c %s -o %t_1.bc
// RUN: %clangxx -emit-llvm -c %s -o %t_2.bc
// RUN: clang-sycl-linker --dry-run -triple spirv64 %t_1.bc %t_2.bc -o a.spv 2>&1 \
// RUN: %clangxx -c -target spirv64 %s -o %t_1.o
// RUN: %clangxx -c -target spirv64 %s -o %t_2.o
// RUN: clang-sycl-linker --dry-run %t_1.o %t_2.o -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=SIMPLE
// SIMPLE: "{{.*}}llvm-link{{.*}}" {{.*}}.bc {{.*}}.bc -o [[FIRSTLLVMLINKOUT:.*]].bc --suppress-warnings
// SIMPLE-NEXT: "{{.*}}llvm-spirv{{.*}}" {{.*}}-o a.spv [[FIRSTLLVMLINKOUT]].bc
//
// Test that llvm-link is not called when only one input is present.
// RUN: clang-sycl-linker --dry-run -triple spirv64 %t_1.bc -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=SIMPLE-NO-LINK
// SIMPLE-NO-LINK: "{{.*}}llvm-spirv{{.*}}" {{.*}}-o a.spv {{.*}}.bc
// SIMPLE: sycl-device-link: input: {{.*}}.o; {{.*}}.o; libfiles: output: [[LLVMLINKOUT:.*]].bc
// SIMPLE-NEXT: "{{.*}}llvm-spirv{{.*}}" {{.*}}-o a.spv [[LLVMLINKOUT]].bc
//
// Test a simple case with device library files specified.
// RUN: touch %T/lib1.bc
// RUN: touch %T/lib2.bc
// RUN: clang-sycl-linker --dry-run -triple spirv64 %t_1.bc %t_2.bc --library-path=%T --device-libs=lib1.bc,lib2.bc -o a.spv 2>&1 \
// RUN: touch %T/lib1.o
// RUN: touch %T/lib2.o
// RUN: clang-sycl-linker --dry-run %t_1.o %t_2.o --library-path=%T --device-libs=lib1.o,lib2.o -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=DEVLIBS
// DEVLIBS: "{{.*}}llvm-link{{.*}}" {{.*}}.bc {{.*}}.bc -o [[FIRSTLLVMLINKOUT:.*]].bc --suppress-warnings
// DEVLIBS-NEXT: "{{.*}}llvm-link{{.*}}" -only-needed [[FIRSTLLVMLINKOUT]].bc {{.*}}lib1.bc {{.*}}lib2.bc -o [[SECONDLLVMLINKOUT:.*]].bc --suppress-warnings
// DEVLIBS-NEXT: "{{.*}}llvm-spirv{{.*}}" {{.*}}-o a.spv [[SECONDLLVMLINKOUT]].bc
//
// Test a simple case with .o (fat object) as input.
// TODO: Remove this test once fat object support is added.
// RUN: %clangxx -c %s -o %t.o
// RUN: not clang-sycl-linker --dry-run -triple spirv64 %t.o -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=FILETYPEERROR
// FILETYPEERROR: Unsupported file type
// DEVLIBS: sycl-device-link: input: {{.*}}.o; libfiles: {{.*}}lib1.o; {{.*}}lib2.o; output: [[LLVMLINKOUT:.*]].bc
// DEVLIBS-NEXT: "{{.*}}llvm-spirv{{.*}}" {{.*}}-o a.spv [[LLVMLINKOUT]].bc
//
// Test to see if device library related errors are emitted.
// RUN: not clang-sycl-linker --dry-run -triple spirv64 %t_1.bc %t_2.bc --library-path=%T --device-libs= -o a.spv 2>&1 \
// RUN: not clang-sycl-linker --dry-run %t_1.o %t_2.o --library-path=%T --device-libs= -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=DEVLIBSERR1
// DEVLIBSERR1: Number of device library files cannot be zero
// RUN: not clang-sycl-linker --dry-run -triple spirv64 %t_1.bc %t_2.bc --library-path=%T --device-libs=lib1.bc,lib2.bc,lib3.bc -o a.spv 2>&1 \
// RUN: not clang-sycl-linker --dry-run %t_1.o %t_2.o --library-path=%T --device-libs=lib1.o,lib2.o,lib3.o -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=DEVLIBSERR2
// DEVLIBSERR2: '{{.*}}lib3.bc' SYCL device library file is not found
// DEVLIBSERR2: '{{.*}}lib3.o' SYCL device library file is not found
//
// Test if correct set of llvm-spirv options are emitted for windows environment.
// RUN: clang-sycl-linker --dry-run -triple spirv64 --is-windows-msvc-env %t_1.bc %t_2.bc -o a.spv 2>&1 \
// RUN: clang-sycl-linker --dry-run --is-windows-msvc-env %t_1.o %t_2.o -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=LLVMOPTSWIN
// LLVMOPTSWIN: -spirv-debug-info-version=ocl-100 -spirv-allow-extra-diexpressions -spirv-allow-unknown-intrinsics=llvm.genx. -spirv-ext=
//
// Test if correct set of llvm-spirv options are emitted for linux environment.
// RUN: clang-sycl-linker --dry-run -triple spirv64 %t_1.bc %t_2.bc -o a.spv 2>&1 \
// RUN: clang-sycl-linker --dry-run %t_1.o %t_2.o -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=LLVMOPTSLIN
// LLVMOPTSLIN: -spirv-debug-info-version=nonsemantic-shader-200 -spirv-allow-unknown-intrinsics=llvm.genx. -spirv-ext=
6 changes: 3 additions & 3 deletions clang/test/Driver/sycl-link-spirv-target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// architecture.
//
// Test that -Xlinker options are being passed to clang-sycl-linker.
// RUN: touch %t.bc
// RUN: touch %t.o
// RUN: %clangxx -### --target=spirv64 --sycl-link -Xlinker --llvm-spirv-path=/tmp \
// RUN: -Xlinker --library-path=/tmp -Xlinker --device-libs=lib1.bc,lib2.bc %t.bc 2>&1 \
// RUN: -Xlinker --library-path=/tmp -Xlinker --device-libs=lib1.o,lib2.o %t.o 2>&1 \
// RUN: | FileCheck %s -check-prefix=XLINKEROPTS
// XLINKEROPTS: "{{.*}}clang-sycl-linker{{.*}}" "--llvm-spirv-path=/tmp" "--library-path=/tmp" "--device-libs=lib1.bc,lib2.bc" "{{.*}}.bc" "-o" "a.out"
// XLINKEROPTS: "{{.*}}clang-sycl-linker{{.*}}" "--llvm-spirv-path=/tmp" "--library-path=/tmp" "--device-libs=lib1.o,lib2.o" "{{.*}}.o" "-o" "a.out"
253 changes: 153 additions & 100 deletions clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/CodeGen/CommandFlags.h"
#include "llvm/IR/DiagnosticPrinter.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"
#include "llvm/Object/Archive.h"
#include "llvm/Object/ArchiveWriter.h"
#include "llvm/Object/Binary.h"
Expand Down Expand Up @@ -179,60 +182,64 @@ Error executeCommands(StringRef ExecutablePath, ArrayRef<StringRef> Args) {
return Error::success();
}

Expected<SmallVector<std::string>> getInput(const ArgList &Args) {
// Collect all input bitcode files to be passed to llvm-link.
/// Returns a new ArgList containg arguments used for the device linking phase.
DerivedArgList getLinkerArgs(ArrayRef<OffloadFile> Input,
const InputArgList &Args) {
DerivedArgList DAL = DerivedArgList(DerivedArgList(Args));
for (Arg *A : Args)
DAL.append(A);
if (DryRun)
return DAL;

// Set the subarchitecture and target triple for this compilation.
const OptTable &Tbl = getOptTable();
DAL.AddJoinedArg(nullptr, Tbl.getOption(OPT_arch),
Args.MakeArgString(Input.front().getBinary()->getArch()));
DAL.AddJoinedArg(nullptr, Tbl.getOption(OPT_triple),
Args.MakeArgString(Input.front().getBinary()->getTriple()));

return DAL;
}

/// Collect all fat objects from user input.
Expected<SmallVector<std::string>> getObjectsFromArgs(const ArgList &Args) {
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);
// 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);
BitcodeFiles.emplace_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<StringRef> linkDeviceInputFiles(ArrayRef<std::string> 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<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 Args.MakeArgString(*OutFileOrErr);
/// Collect all input bitcode images from a list of fat objects.
Expected<SmallVector<OffloadFile>>
getBitcodeInputs(SmallVector<std::string> &Filenames) {
// Collect all input bitcode files to be passed to llvm bitcode linking.
SmallVector<OffloadFile> BitcodeFiles;
for (auto Filename : Filenames) {
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
MemoryBuffer::getFile(Filename);
if (std::error_code EC = BufferOrErr.getError())
return createFileError(Filename, EC);
MemoryBufferRef Buffer = **BufferOrErr;
SmallVector<OffloadFile> Binaries;
if (Error Err = extractOffloadBinaries(Buffer, Binaries))
return std::move(Err);

auto ContainsBitcode = [](const OffloadFile &F) {
return identify_magic(F.getBinary()->getImage()) == file_magic::bitcode;
};
for (auto &OffloadFile : Binaries) {
if (ContainsBitcode(OffloadFile))
BitcodeFiles.emplace_back(std::move(OffloadFile));
else
return createStringError("Unsupported file type");
}
}
return BitcodeFiles;
}

// This utility function is used to gather all SYCL device library files that
Expand Down Expand Up @@ -264,44 +271,86 @@ Expected<SmallVector<std::string>> getSYCLDeviceLibs(const ArgList &Args) {
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<StringRef> 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<std::string> LLVMLinkPath =
findProgram(Args, "llvm-link", {getMainExecutable("llvm-link")});
if (!LLVMLinkPath)
return LLVMLinkPath.takeError();

/// Following tasks are performed:
/// 1. Link all SYCL device bitcode images into one image. Device linking is
/// performed using the linkModules API.
/// 2. Gather all SYCL device library bitcode images.
/// 3. Link all the images gathered in Step 2 with the output of Step 1 using
/// linkModules API. LinkOnlyNeeded flag is used.
Expected<StringRef> linkDeviceCode(ArrayRef<OffloadFile> BitcodeInputFiles,
const ArgList &Args, LLVMContext &C) {
llvm::TimeTraceScope TimeScope("SYCL Link Device Code");
// Gather SYCL device library files.
auto SYCLDeviceLibFilenames = getSYCLDeviceLibs(Args);
if (!SYCLDeviceLibFilenames)
return SYCLDeviceLibFilenames.takeError();
// Create a new file to write the linked device file to.
auto OutFileOrErr =
auto BitcodeOutput =
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;
if (!BitcodeOutput)
return BitcodeOutput.takeError();
int FD = -1;
if (std::error_code EC = sys::fs::openFileForWrite(*BitcodeOutput, FD))
return errorCodeToError(EC);
if (DryRun || Verbose) {
std::for_each((*SYCLDeviceLibFilenames).begin(),
(*SYCLDeviceLibFilenames).end(),
[](std::string &Filename) { Filename += "; "; });
std::string LibInputs =
std::accumulate((*SYCLDeviceLibFilenames).begin(),
(*SYCLDeviceLibFilenames).end(), std::string(""));

auto FilenamesOrErr = getObjectsFromArgs(Args);
if (!FilenamesOrErr)
reportError(FilenamesOrErr.takeError());
std::for_each((*FilenamesOrErr).begin(), (*FilenamesOrErr).end(),
[](std::string &Filename) { Filename += "; "; });
std::string Inputs = std::accumulate(
(*FilenamesOrErr).begin(), (*FilenamesOrErr).end(), std::string(""));

errs() << formatv(
"sycl-device-link: input: {0} libfiles: {1} output: {2}\n", Inputs,
LibInputs, *BitcodeOutput);
if (DryRun)
return *BitcodeOutput;
}
SMDiagnostic Err;
assert(BitcodeInputFiles.size() && "No inputs to link");
auto Output = std::make_unique<Module>("sycl-device-link", C);
Linker L(*Output);
// Link SYCL device input files.
for (auto &File : BitcodeInputFiles) {
auto TheImage = File.getBinary()->getImage();
std::unique_ptr<Module> ModOrErr =
parseIR(MemoryBufferRef(TheImage, ""), Err, C);
if (!ModOrErr || verifyModule(*ModOrErr, &errs()))
return createStringError("Could not parse IR");
if (L.linkInModule(std::move(ModOrErr)))
return createStringError("Could not link IR");
}
auto SYCLDeviceLibFiles = getBitcodeInputs(*SYCLDeviceLibFilenames);
if (!SYCLDeviceLibFiles)
return SYCLDeviceLibFiles.takeError();
if (!(*SYCLDeviceLibFiles).empty()) {
const llvm::Triple Triple(Args.getLastArgValue(OPT_triple));
// Link in SYCL device library files.
for (auto &File : *SYCLDeviceLibFiles) {
auto TheImage = File.getBinary()->getImage();
std::unique_ptr<Module> M =
parseIR(MemoryBufferRef(TheImage, ""), Err, C);
if (!M || verifyModule(*M, &errs()))
return createStringError("Could not parse IR in SYCL device lib file");
if (M->getTargetTriple() == Triple) {
unsigned Flags = Linker::Flags::None;
Flags |= Linker::Flags::LinkOnlyNeeded;
if (L.linkInModule(std::move(M), Flags))
return createStringError("Could not link IR");
}
}
}
llvm::raw_fd_ostream OS(FD, true);
WriteBitcodeToFile(*Output, OS);
return *BitcodeOutput;
}

/// Add any llvm-spirv option that relies on a specific Triple in addition
Expand Down Expand Up @@ -345,7 +394,6 @@ static void getSPIRVTransOpts(const ArgList &Args,
",+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"
Expand Down Expand Up @@ -422,37 +470,39 @@ static Expected<StringRef> runLLVMToSPIRVTranslation(StringRef File,
return OutputFile;
}

Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
Error runSYCLLink(ArrayRef<OffloadFile> Files, const InputArgList &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);

LLVMContext C;

// Update args list with triple and arch information for SPIR-V device
auto LinkerArgs = getLinkerArgs(Files, Args);

// Linking device bitcode files
auto LinkedFileOrErr = linkDeviceCode(Files, LinkerArgs, C);
if (!LinkedFileOrErr)
reportError(LinkedFileOrErr.takeError());

// LLVM to SPIR-V translation
auto SPVFile = runLLVMToSPIRVTranslation(*LinkedFileOrErr, LinkerArgs);
if (!SPVFile)
return SPVFile.takeError();

return Error::success();
}

} // namespace

int main(int argc, char **argv) {
InitLLVM X(argc, argv);
int main(int Argc, char **Argv) {
InitLLVM X(Argc, Argv);

Executable = argv[0];
sys::PrintStackTraceOnErrorSignal(argv[0]);
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) {
auto Args = Tbl.parseArgs(Argc, Argv, OPT_INVALID, Saver, [&](StringRef Err) {
reportError(createStringError(inconvertibleErrorCode(), Err));
});

Expand Down Expand Up @@ -490,7 +540,10 @@ int main(int argc, char **argv) {
}

// Get the input files to pass to the linking stage.
auto FilesOrErr = getInput(Args);
auto FilenamesOrErr = getObjectsFromArgs(Args);
if (!FilenamesOrErr)
reportError(FilenamesOrErr.takeError());
auto FilesOrErr = getBitcodeInputs(*FilenamesOrErr);
if (!FilesOrErr)
reportError(FilesOrErr.takeError());

Expand Down