Skip to content

[SYCL] Add clang-linker-wrapper changes to call clang-sycl-linker for SYCL offloads #135683

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 17, 2025
Merged
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
2 changes: 2 additions & 0 deletions clang/docs/ClangOffloadPackager.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,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
4 changes: 2 additions & 2 deletions clang/test/Driver/clang-sycl-linker-test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
// RUN: clang-sycl-linker --dry-run -v -triple=spirv64 %t_1.bc %t_2.bc -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=SIMPLE-FO
// SIMPLE-FO: sycl-device-link: inputs: {{.*}}.bc, {{.*}}.bc libfiles: output: [[LLVMLINKOUT:.*]].bc
// SIMPLE-FO-NEXT: SPIR-V Backend: input: [[LLVMLINKOUT]].bc, output: a.spv
// SIMPLE-FO-NEXT: SPIR-V Backend: input: [[LLVMLINKOUT]].bc, output: a_0.spv
//
// Test the dry run of a simple case with device library files specified.
// RUN: touch %T/lib1.bc
// RUN: touch %T/lib2.bc
// RUN: clang-sycl-linker --dry-run -v -triple=spirv64 %t_1.bc %t_2.bc --library-path=%T --device-libs=lib1.bc,lib2.bc -o a.spv 2>&1 \
// RUN: | FileCheck %s --check-prefix=DEVLIBS
// DEVLIBS: sycl-device-link: inputs: {{.*}}.bc libfiles: {{.*}}lib1.bc, {{.*}}lib2.bc output: [[LLVMLINKOUT:.*]].bc
// DEVLIBS-NEXT: SPIR-V Backend: input: [[LLVMLINKOUT]].bc, output: a.spv
// DEVLIBS-NEXT: SPIR-V Backend: input: [[LLVMLINKOUT]].bc, output: a_0.spv
//
// Test a simple case with a random file (not bitcode) as input.
// RUN: touch %t.o
Expand Down
10 changes: 10 additions & 0 deletions clang/test/Driver/linker-wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
// REQUIRES: x86-registered-target
// REQUIRES: nvptx-registered-target
// REQUIRES: amdgpu-registered-target
// REQUIRES: spirv-registered-target

// An externally visible variable so static libraries extract.
__attribute__((visibility("protected"), used)) int x;

// RUN: %clang -cc1 %s -triple x86_64-unknown-linux-gnu -emit-obj -o %t.elf.o
// RUN: %clang -cc1 %s -triple nvptx64-nvidia-cuda -emit-llvm-bc -o %t.nvptx.bc
// RUN: %clang -cc1 %s -triple amdgcn-amd-amdhsa -emit-llvm-bc -o %t.amdgpu.bc
// RUN: %clang -cc1 %s -triple spirv64-unknown-unknown -emit-llvm-bc -o %t.spirv.bc

// RUN: clang-offload-packager -o %t.out \
// RUN: --image=file=%t.elf.o,kind=openmp,triple=nvptx64-nvidia-cuda,arch=sm_70 \
Expand Down Expand Up @@ -49,6 +51,14 @@ __attribute__((visibility("protected"), used)) int x;

// AMDGPU-LTO-TEMPS: clang{{.*}} --target=amdgcn-amd-amdhsa -mcpu=gfx1030 -flto {{.*}}-save-temps

// RUN: clang-offload-packager -o %t.out \
// RUN: --image=file=%t.spirv.bc,kind=sycl,triple=spirv64-unknown-unknown,arch=generic
// RUN: %clang -cc1 %s -triple x86_64-unknown-linux-gnu -emit-obj -o %t.o -fembed-offload-object=%t.out
// RUN: not clang-linker-wrapper --host-triple=x86_64-unknown-linux-gnu --dry-run \
// RUN: --linker-path=/usr/bin/ld %t.o -o a.out 2>&1 | FileCheck %s --check-prefix=SPIRV-LINK

// SPIRV-LINK: clang{{.*}} -o {{.*}}.img --target=spirv64-unknown-unknown {{.*}}.o --sycl-link -Xlinker -triple=spirv64-unknown-unknown -Xlinker -arch=

// RUN: clang-offload-packager -o %t.out \
// RUN: --image=file=%t.elf.o,kind=openmp,triple=x86_64-unknown-linux-gnu \
// RUN: --image=file=%t.elf.o,kind=openmp,triple=x86_64-unknown-linux-gnu
Expand Down
31 changes: 27 additions & 4 deletions clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,8 @@ fatbinary(ArrayRef<std::pair<StringRef, StringRef>> InputFiles,
} // namespace amdgcn

namespace generic {
Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args) {
Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
uint16_t ActiveOffloadKindMask) {
llvm::TimeTraceScope TimeScope("Clang");
// Use `clang` to invoke the appropriate device tools.
Expected<std::string> ClangPath =
Expand Down Expand Up @@ -554,6 +555,17 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args) {
if (Args.hasArg(OPT_embed_bitcode))
CmdArgs.push_back("-Wl,--lto-emit-llvm");

// For linking device code with the SYCL offload kind, special handling is
// required. Passing --sycl-link to clang results in a call to
// clang-sycl-linker. Additional linker flags required by clang-sycl-linker
// will be communicated via the -Xlinker option.
if (ActiveOffloadKindMask & OFK_SYCL) {
CmdArgs.push_back("--sycl-link");
CmdArgs.append(
{"-Xlinker", Args.MakeArgString("-triple=" + Triple.getTriple())});
CmdArgs.append({"-Xlinker", Args.MakeArgString("-arch=" + Arch)});
}

for (StringRef Arg : Args.getAllArgValues(OPT_linker_arg_EQ))
CmdArgs.append({"-Xlinker", Args.MakeArgString(Arg)});
for (StringRef Arg : Args.getAllArgValues(OPT_compiler_arg_EQ))
Expand All @@ -567,7 +579,8 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args) {
} // namespace generic

Expected<StringRef> linkDevice(ArrayRef<StringRef> InputFiles,
const ArgList &Args) {
const ArgList &Args,
uint16_t ActiveOffloadKindMask) {
const llvm::Triple Triple(Args.getLastArgValue(OPT_triple_EQ));
switch (Triple.getArch()) {
case Triple::nvptx:
Expand All @@ -582,7 +595,7 @@ Expected<StringRef> linkDevice(ArrayRef<StringRef> InputFiles,
case Triple::spirv64:
case Triple::systemz:
case Triple::loongarch64:
return generic::clang(InputFiles, Args);
return generic::clang(InputFiles, Args, ActiveOffloadKindMask);
default:
return createStringError(Triple.getArchName() +
" linking is not supported");
Expand Down Expand Up @@ -792,6 +805,7 @@ bundleLinkedOutput(ArrayRef<OffloadingImage> Images, const ArgList &Args,
llvm::TimeTraceScope TimeScope("Bundle linked output");
switch (Kind) {
case OFK_OpenMP:
case OFK_SYCL:
return bundleOpenMP(Images);
case OFK_Cuda:
return bundleCuda(Images, Args);
Expand Down Expand Up @@ -927,6 +941,14 @@ Expected<SmallVector<StringRef>> linkAndWrapDeviceFiles(
for (const auto &File : Input)
ActiveOffloadKindMask |= File.getBinary()->getOffloadKind();

// Linking images of SYCL offload kind with images of other kind is not
// supported.
// TODO: Remove the above limitation.
if ((ActiveOffloadKindMask & OFK_SYCL) &&
((ActiveOffloadKindMask ^ OFK_SYCL) != 0))
return createStringError("Linking images of SYCL offload kind with "
"images of any other kind is not supported");

// Write any remaining device inputs to an output file.
SmallVector<StringRef> InputFiles;
for (const OffloadFile &File : Input) {
Expand All @@ -937,7 +959,8 @@ Expected<SmallVector<StringRef>> linkAndWrapDeviceFiles(
}

// Link the remaining device files using the device linker.
auto OutputOrErr = linkDevice(InputFiles, LinkerArgs);
auto OutputOrErr =
linkDevice(InputFiles, LinkerArgs, ActiveOffloadKindMask);
if (!OutputOrErr)
return OutputOrErr.takeError();

Expand Down
62 changes: 54 additions & 8 deletions clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ 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';
}
Expand Down Expand Up @@ -278,8 +280,8 @@ Expected<StringRef> linkDeviceCode(ArrayRef<std::string> InputFiles,
/// 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 OutputFile, LLVMContext &C) {
llvm::TimeTraceScope TimeScope("SPIR-V code generation");

// Parse input module.
Expand All @@ -289,7 +291,7 @@ static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args,
return createStringError(Err.getMessage());

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

Triple TargetTriple(Args.getLastArgValue(OPT_triple_EQ));
M->setTargetTriple(TargetTriple);
Expand Down Expand Up @@ -333,7 +335,7 @@ static Expected<StringRef> runSPIRVCodeGen(StringRef File, const ArgList &Args,
errs() << formatv("SPIR-V Backend: input: {0}, output: {1}\n", File,
OutputFile);

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

/// Performs the following steps:
Expand All @@ -349,10 +351,54 @@ Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
if (!LinkedFile)
reportError(LinkedFile.takeError());

// TODO: SYCL post link functionality involves device code splitting and will
// result in multiple bitcode codes.
// The following lines are placeholders to represent multiple files and will
// be refactored once SYCL post link support is available.
SmallVector<std::string> SplitModules;
SplitModules.emplace_back(*LinkedFile);

// 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) {
auto Stem = OutputFile.rsplit('.').first;
std::string SPVFile(Stem);
SPVFile.append("_" + utostr(I) + ".spv");
auto Err = runSPIRVCodeGen(SplitModules[I], Args, SPVFile, C);
if (Err)
return std::move(Err);
SplitModules[I] = SPVFile;
}

// Write the final output into file.
int FD = -1;
if (std::error_code EC = sys::fs::openFileForWrite(OutputFile, FD))
return errorCodeToError(EC);
llvm::raw_fd_ostream FS(FD, /*shouldClose=*/true);

for (size_t I = 0, E = SplitModules.size(); I != E; ++I) {
auto File = SplitModules[I];
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);
}
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("Offload binary has invalid size alignment");
FS << Buffer;
}
return Error::success();
}

Expand Down Expand Up @@ -394,7 +440,7 @@ int main(int argc, char **argv) {
DryRun = Args.hasArg(OPT_dry_run);
SaveTemps = Args.hasArg(OPT_save_temps);

OutputFile = "a.spv";
OutputFile = "a.out";
if (Args.hasArg(OPT_o))
OutputFile = Args.getLastArgValue(OPT_o);

Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/Object/OffloadBinary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ OffloadKind object::getOffloadKind(StringRef Name) {
.Case("openmp", OFK_OpenMP)
.Case("cuda", OFK_Cuda)
.Case("hip", OFK_HIP)
.Case("sycl", OFK_SYCL)
.Default(OFK_None);
}

Expand All @@ -312,6 +313,8 @@ StringRef object::getOffloadKindName(OffloadKind Kind) {
return "cuda";
case OFK_HIP:
return "hip";
case OFK_SYCL:
return "sycl";
default:
return "none";
}
Expand Down
Loading