[clang] [Clang][Driver][SPIRV] Support LTO through the llvm-lto tool (PR #182347)
Nick Sarnie via cfe-commits
cfe-commits at lists.llvm.org
Thu Feb 19 11:30:40 PST 2026
https://github.com/sarnex created https://github.com/llvm/llvm-project/pull/182347
There is no SPIR-V linker that supports LTO and a proposal to support basic SPIR-V linking in `lld` was rejected, so support a basic version of LTO just by calling `llvm-lto` directly from the SPIR-V Toolchain. `-Xlinker` can be used to specify flags to `llvm-lto`.
This should be enough for our use case.
There is also the `llvm-lto2` tool, but that requires a list of symbol resolutions in the command line, and we can't compute that in the driver.
>From e73e52d14cee7a44fe27fa15b381039f2a1ea232 Mon Sep 17 00:00:00 2001
From: Nick Sarnie <nick.sarnie at intel.com>
Date: Thu, 19 Feb 2026 11:15:17 -0800
Subject: [PATCH] [Clang][Driver][SPIRV] Support LTO through the llvm-lto tool
Signed-off-by: Nick Sarnie <nick.sarnie at intel.com>
---
clang/lib/Driver/ToolChains/Clang.cpp | 3 ++-
clang/lib/Driver/ToolChains/SPIRV.cpp | 27 +++++++++++++++------
clang/test/Driver/spirv-lto.c | 35 +++++++++++++++++++++++++++
3 files changed, 57 insertions(+), 8 deletions(-)
create mode 100644 clang/test/Driver/spirv-lto.c
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 8aa2c595e2dea..03995f538d33a 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -9307,7 +9307,8 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
// For SPIR-V some functions will be defined by the runtime so allow
// unresolved symbols.
- if (TC->getTriple().isSPIRV())
+ if (TC->getTriple().isSPIRV() && !C.getDriver().isUsingLTO() &&
+ !C.getDriver().isUsingOffloadLTO())
LinkerArgs.emplace_back("--allow-partial-linkage");
// Forward all of these to the appropriate toolchain.
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index c1ccb1e7d8508..762f5d70ad4b6 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -147,17 +147,30 @@ void SPIRV::Linker::ConstructJob(Compilation &C, const JobAction &JA,
const ToolChain &ToolChain = getToolChain();
std::string Linker = ToolChain.GetProgramPath(getShortName());
ArgStringList CmdArgs;
- AddLinkerInputs(getToolChain(), Inputs, Args, CmdArgs, JA);
+ AddLinkerInputs(ToolChain, Inputs, Args, CmdArgs, JA);
CmdArgs.push_back("-o");
CmdArgs.push_back(Output.getFilename());
- // Use of --sycl-link will call the clang-sycl-linker instead of
- // the default linker (spirv-link).
- if (Args.hasArg(options::OPT_sycl_link))
+ if (C.getDriver().isUsingLTO()) {
+ // Implement limited LTO support through llvm-lto.
+ if (Args.hasArg(options::OPT_sycl_link)) {
+ // For unsupported cases, throw the same error as when LTO isn't supported
+ // at all.
+ C.getDriver().Diag(clang::diag::err_drv_no_linker_llvm_support)
+ << ToolChain.getTriple().getTriple();
+ return;
+ }
+ Linker = ToolChain.GetProgramPath("llvm-lto");
+ // Disable internalization, otherwise GlobalDCE will optimize everything
+ // out.
+ CmdArgs.push_back("-enable-lto-internalization=false");
+ } else if (Args.hasArg(options::OPT_sycl_link)) {
+ // Use of --sycl-link will call the clang-sycl-linker instead of
+ // the default linker (spirv-link).
Linker = ToolChain.GetProgramPath("clang-sycl-linker");
- else if (!llvm::sys::fs::can_execute(Linker) &&
- !C.getArgs().hasArg(clang::options::OPT__HASH_HASH_HASH)) {
+ } else if (!llvm::sys::fs::can_execute(Linker) &&
+ !C.getArgs().hasArg(clang::options::OPT__HASH_HASH_HASH)) {
C.getDriver().Diag(clang::diag::err_drv_no_spv_tools) << getShortName();
return;
}
@@ -171,7 +184,7 @@ SPIRVToolChain::SPIRVToolChain(const Driver &D, const llvm::Triple &Triple,
: ToolChain(D, Triple, Args) {
// TODO: Revisit need/use of --sycl-link option once SYCL toolchain is
// available and SYCL linking support is moved there.
- NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link);
+ NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || D.isUsingLTO();
// Lookup binaries into the driver directory.
getProgramPaths().push_back(getDriver().Dir);
diff --git a/clang/test/Driver/spirv-lto.c b/clang/test/Driver/spirv-lto.c
new file mode 100644
index 0000000000000..e85fb643351a9
--- /dev/null
+++ b/clang/test/Driver/spirv-lto.c
@@ -0,0 +1,35 @@
+// Check SPIR-V support for LTO
+// RUN: mkdir -p %t
+// RUN: touch %t/a.cpp
+// RUN: touch %t/b.cpp
+// RUN: touch %t/a.o
+// RUN: touch %t/b.o
+
+// RUN: %clang -### --target=spirv64 -flto %t/a.cpp %t/b.cpp -Xlinker --disable-verify 2>&1 | FileCheck --check-prefix=CHECK-POSITIVE-TOOL %s
+// RUN: %clang -ccc-print-phases --target=spirv64 -flto %t/a.cpp %t/b.cpp 2>&1 | FileCheck --check-prefix=CHECK-POSITIVE-PHASES %s
+// RUN: not %clang -### --target=spirv64 -flto %t/a.cpp %t/b.cpp --sycl-link 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s
+
+// RUN: %clang -### --target=spirv64 -flto %t/a.o %t/b.o -Xlinker --disable-verify 2>&1 | FileCheck --check-prefix=CHECK-POSITIVE-TOOL-OBJ %s
+// RUN: %clang -ccc-print-phases --target=spirv64 -flto %t/a.o %t/b.o 2>&1 | FileCheck --check-prefix=CHECK-POSITIVE-PHASES-OBJ %s
+// RUN: not %clang -### --target=spirv64 -flto %t/a.o %t/b.o --sycl-link 2>&1 | FileCheck --check-prefix=CHECK-ERROR %s
+
+// CHECK-POSITIVE-TOOL: llvm-lto{{.*}} "{{.*}}/a-{{.*}}.o" "{{.*}}/b-{{.*}}.o" "--disable-verify" "-o" "a.out" "-enable-lto-internalization=false"
+
+// CHECK-POSITIVE-PHASES: 0: input, "{{.*}}/a.cpp", c++
+// CHECK-POSITIVE-PHASES: 1: preprocessor, {0}, c++-cpp-output
+// CHECK-POSITIVE-PHASES: 2: compiler, {1}, ir
+// CHECK-POSITIVE-PHASES: 3: backend, {2}, lto-bc
+// CHECK-POSITIVE-PHASES: 4: input, "{{.*}}/b.cpp", c++
+// CHECK-POSITIVE-PHASES: 5: preprocessor, {4}, c++-cpp-output
+// CHECK-POSITIVE-PHASES: 6: compiler, {5}, ir
+// CHECK-POSITIVE-PHASES: 7: backend, {6}, lto-bc
+// CHECK-POSITIVE-PHASES: 8: linker, {3, 7}, image
+
+// CHECK-POSITIVE-TOOL-OBJ: llvm-lto{{.*}} "{{.*}}/a.o" "{{.*}}/b.o" "--disable-verify" "-o" "a.out" "-enable-lto-internalization=false"
+
+// CHECK-POSITIVE-PHASES-OBJ: 0: input, "{{.*}}/a.o", object
+// CHECK-POSITIVE-PHASES-OBJ: 1: input, "{{.*}}/b.o", object
+// CHECK-POSITIVE-PHASES-OBJ: 2: linker, {0, 1}, image
+
+// CHECK-ERROR: 'spirv64': unable to pass LLVM bit-code files to linker
+
More information about the cfe-commits
mailing list