https://github.com/sarnex updated https://github.com/llvm/llvm-project/pull/169572
>From 89b0fe0a8cc57d27c69d18927c1ae10833d82a29 Mon Sep 17 00:00:00 2001 From: Nick Sarnie <[email protected]> Date: Tue, 25 Nov 2025 13:23:36 -0800 Subject: [PATCH 1/2] [clang][Driver][SPIR-V] Allow linking IR using llvm-link Signed-off-by: Nick Sarnie <[email protected]> --- clang/lib/Driver/Driver.cpp | 16 +++++++++++--- clang/lib/Driver/ToolChains/SPIRV.cpp | 26 ++++++++++++++++++++++ clang/lib/Driver/ToolChains/SPIRV.h | 5 +++++ clang/test/Driver/spirv-llvm-link.c | 31 +++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 clang/test/Driver/spirv-llvm-link.c diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index de8d4601210ae..27d5bcd4290c6 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -4265,8 +4265,11 @@ void Driver::handleArguments(Compilation &C, DerivedArgList &Args, Args.AddFlagArg(nullptr, getOpts().getOption(options::OPT_frtlib_add_rpath)); } - // Emitting LLVM while linking disabled except in HIPAMD Toolchain - if (Args.hasArg(options::OPT_emit_llvm) && !Args.hasArg(options::OPT_hip_link)) + // Emitting LLVM while linking disabled except in the HIPAMD or SPIR-V + // Toolchains + if (Args.hasArg(options::OPT_emit_llvm) && + !Args.hasArg(options::OPT_hip_link) && + !C.getDefaultToolChain().getTriple().isSPIRV()) Diag(clang::diag::err_drv_emit_llvm_link); if (C.getDefaultToolChain().getTriple().isWindowsMSVCEnvironment() && LTOMode != LTOK_None && @@ -4595,7 +4598,14 @@ void Driver::BuildDefaultActions(Compilation &C, DerivedArgList &Args, LA->propagateHostOffloadInfo(C.getActiveOffloadKinds(), /*BoundArch=*/nullptr); } else { - LA = C.MakeAction<LinkJobAction>(LinkerInputs, types::TY_Image); + // If we are linking but were passed -emit-llvm, we will be calling + // llvm-link, so set the output type accordingly. This is only allowed in + // rare cases, so make sure we aren't going to error about it. + types::ID LT = + Args.hasArg(options::OPT_emit_llvm) && !Diags.hasErrorOccurred() + ? types::TY_LLVM_BC + : types::TY_Image; + LA = C.MakeAction<LinkJobAction>(LinkerInputs, LT); } if (!UseNewOffloadingDriver) LA = OffloadBuilder->processHostLinkAction(LA); diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp index 27de55cfebfc1..ddb2a0bbb5058 100644 --- a/clang/lib/Driver/ToolChains/SPIRV.cpp +++ b/clang/lib/Driver/ToolChains/SPIRV.cpp @@ -70,6 +70,28 @@ void SPIRV::constructAssembleCommand(Compilation &C, const Tool &T, Exec, CmdArgs, Input, Output)); } +void SPIRV::constructLLVMLinkCommand(Compilation &C, const Tool &T, + const JobAction &JA, + const InputInfo &Output, + const InputInfoList &Inputs, + const llvm::opt::ArgStringList &Args) { + // Construct llvm-link command. + // The output from llvm-link is a bitcode file. + ArgStringList LlvmLinkArgs; + + assert(!Inputs.empty() && "Must have at least one input."); + + LlvmLinkArgs.append({"-o", Output.getFilename()}); + for (auto Input : Inputs) + LlvmLinkArgs.push_back(Input.getFilename()); + + const char *LlvmLink = + C.getArgs().MakeArgString(T.getToolChain().GetProgramPath("llvm-link")); + C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(), + LlvmLink, LlvmLinkArgs, Inputs, + Output)); +} + void SPIRV::Translator::ConstructJob(Compilation &C, const JobAction &JA, const InputInfo &Output, const InputInfoList &Inputs, @@ -121,6 +143,10 @@ void SPIRV::Linker::ConstructJob(Compilation &C, const JobAction &JA, const InputInfoList &Inputs, const ArgList &Args, const char *LinkingOutput) const { + if (JA.getType() == types::TY_LLVM_BC) { + constructLLVMLinkCommand(C, *this, JA, Output, Inputs, {}); + return; + } const ToolChain &ToolChain = getToolChain(); std::string Linker = ToolChain.GetProgramPath(getShortName()); ArgStringList CmdArgs; diff --git a/clang/lib/Driver/ToolChains/SPIRV.h b/clang/lib/Driver/ToolChains/SPIRV.h index 924eb01adcbbf..249053c23b792 100644 --- a/clang/lib/Driver/ToolChains/SPIRV.h +++ b/clang/lib/Driver/ToolChains/SPIRV.h @@ -27,6 +27,11 @@ void constructAssembleCommand(Compilation &C, const Tool &T, const InputInfo &Input, const llvm::opt::ArgStringList &Args); +void constructLLVMLinkCommand(Compilation &C, const Tool &T, + const JobAction &JA, const InputInfo &Output, + const InputInfoList &Inputs, + const llvm::opt::ArgStringList &Args); + class LLVM_LIBRARY_VISIBILITY Translator : public Tool { public: Translator(const ToolChain &TC) diff --git a/clang/test/Driver/spirv-llvm-link.c b/clang/test/Driver/spirv-llvm-link.c new file mode 100644 index 0000000000000..9c30654707016 --- /dev/null +++ b/clang/test/Driver/spirv-llvm-link.c @@ -0,0 +1,31 @@ +// Check BC input +// RUN: mkdir -p %t +// RUN: touch %t/a.bc +// RUN: touch %t/b.bc +// RUN: %clang -### --target=spirv64 -emit-llvm %t/a.bc %t/b.bc 2>&1 | FileCheck --check-prefix=CHECK-TOOL-BC %s + +// CHECK-TOOL-BC: "-cc1" {{.*}} "-o" "[[TMP1_BC:.+]]" "-x" "ir" "{{.*}}.bc" +// CHECK-TOOL-BC: "-cc1" {{.*}} "-o" "[[TMP2_BC:.+]]" "-x" "ir" "{{.*}}.bc" +// CHECK-TOOL-BC: llvm-link{{.*}} "-o" {{.*}} "[[TMP1_BC]]" "[[TMP2_BC]]" + +// RUN: %clang -ccc-print-bindings --target=spirv64 -emit-llvm %t/a.bc %t/b.bc 2>&1 | FileCheck -check-prefix=CHECK-BINDINGS-BC %s + +// CHECK-BINDINGS-BC: "spirv64" - "clang", inputs: ["{{.*}}.bc"], output: "[[TMP1_BINDINGS_BC:.+]]" +// CHECK-BINDINGS-BC: "spirv64" - "clang", inputs: ["{{.*}}.bc"], output: "[[TMP2_BINDINGS_BC:.+]]" +// CHECK-BINDINGS-BC: "spirv64" - "SPIR-V::Linker", inputs: ["[[TMP1_BINDINGS_BC]]", "[[TMP2_BINDINGS_BC]]"], output: "{{.*}}.bc" + +// Check source input +// RUN: touch %t/foo.c +// RUN: touch %t/bar.c + +// RUN: %clang -### --target=spirv64 -emit-llvm %t/foo.c %t/bar.c 2>&1 | FileCheck --check-prefix=CHECK-TOOL-SRC %s + +// CHECK-TOOL-SRC: "-cc1" {{.*}} "-o" "[[TMP1_SRC_BC:.+]]" "-x" "c" "{{.*}}foo.c" +// CHECK-TOOL-SRC: "-cc1" {{.*}} "-o" "[[TMP2_SRC_BC:.+]]" "-x" "c" "{{.*}}bar.c" +// CHECK-TOOL-SRC: llvm-link{{.*}} "-o" {{.*}} "[[TMP1_SRC_BC]]" "[[TMP2_SRC_BC]]" + +// RUN: %clang -ccc-print-bindings --target=spirv64 -emit-llvm %t/foo.c %t/bar.c 2>&1 | FileCheck -check-prefix=CHECK-BINDINGS-SRC %s + +// CHECK-BINDINGS-SRC: "spirv64" - "clang", inputs: ["{{.*}}foo.c"], output: "[[TMP1_BINDINGS_SRC_BC:.+]]" +// CHECK-BINDINGS-SRC: "spirv64" - "clang", inputs: ["{{.*}}bar.c"], output: "[[TMP2_BINDINGS_SRC_BC:.+]]" +// CHECK-BINDINGS-SRC: "spirv64" - "SPIR-V::Linker", inputs: ["[[TMP1_BINDINGS_SRC_BC]]", "[[TMP2_BINDINGS_SRC_BC]]"], output: "{{.*}}.bc" >From 4af3b7e2503f0500c588f96b69c697ce282ffa60 Mon Sep 17 00:00:00 2001 From: Nick Sarnie <[email protected]> Date: Tue, 2 Dec 2025 14:21:21 -0800 Subject: [PATCH 2/2] add utility Signed-off-by: Nick Sarnie <[email protected]> --- clang/include/clang/Driver/CommonArgs.h | 8 ++++++ clang/lib/Driver/ToolChains/CommonArgs.cpp | 25 ++++++++++++++++++ clang/lib/Driver/ToolChains/HIPAMD.cpp | 26 ++++++------------- clang/lib/Driver/ToolChains/HIPSPV.cpp | 7 ++--- clang/lib/Driver/ToolChains/SPIRV.cpp | 12 ++++----- clang/lib/Driver/ToolChains/SPIRV.h | 2 +- .../Driver/hipspv-link-static-library.hip | 4 +-- clang/test/Driver/hipspv-toolchain-rdc.hip | 2 +- clang/test/Driver/hipspv-toolchain.hip | 2 +- 9 files changed, 53 insertions(+), 35 deletions(-) diff --git a/clang/include/clang/Driver/CommonArgs.h b/clang/include/clang/Driver/CommonArgs.h index ac17d6211d882..f86ad4b758436 100644 --- a/clang/include/clang/Driver/CommonArgs.h +++ b/clang/include/clang/Driver/CommonArgs.h @@ -312,6 +312,14 @@ void setComplexRange(const Driver &D, StringRef NewOpt, LangOptions::ComplexRangeKind NewRange, StringRef &LastOpt, LangOptions::ComplexRangeKind &Range); +void constructLlvmLinkCommand(Compilation &C, const Tool &T, + const JobAction &JA, + const InputInfoList &JobInputs, + const llvm::opt::ArgStringList &LinkerInputs, + const InputInfo &Output, + const llvm::opt::ArgList &Args, + const char *OutputFilename = nullptr); + } // end namespace tools } // end namespace driver } // end namespace clang diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp index 4c036f0f8dee3..8c922f47eeb8d 100644 --- a/clang/lib/Driver/ToolChains/CommonArgs.cpp +++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp @@ -3637,3 +3637,28 @@ void tools::setComplexRange(const Driver &D, StringRef NewOpt, LastOpt = NewOpt; Range = NewRange; } + +void tools::constructLlvmLinkCommand(Compilation &C, const Tool &T, + const JobAction &JA, + const InputInfoList &JobInputs, + const ArgStringList &LinkerInputs, + const InputInfo &Output, + const llvm::opt::ArgList &Args, + const char *OutputFilename) { + // Construct llvm-link command. + // The output from llvm-link is a bitcode file. + + assert(!LinkerInputs.empty() && !JobInputs.empty() && + "Must have at least one input."); + + ArgStringList LlvmLinkArgs( + {"-o", OutputFilename ? OutputFilename : Output.getFilename()}); + + LlvmLinkArgs.append(LinkerInputs); + + const ToolChain &TC = T.getToolChain(); + const char *LlvmLink = Args.MakeArgString(TC.GetProgramPath("llvm-link")); + C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(), + LlvmLink, LlvmLinkArgs, JobInputs, + Output)); +} diff --git a/clang/lib/Driver/ToolChains/HIPAMD.cpp b/clang/lib/Driver/ToolChains/HIPAMD.cpp index 231a38c2d3717..d51a4c86cd7c2 100644 --- a/clang/lib/Driver/ToolChains/HIPAMD.cpp +++ b/clang/lib/Driver/ToolChains/HIPAMD.cpp @@ -33,32 +33,22 @@ using namespace llvm::opt; #define NULL_FILE "/dev/null" #endif -void AMDGCN::Linker::constructLlvmLinkCommand(Compilation &C, - const JobAction &JA, - const InputInfoList &Inputs, - const InputInfo &Output, - const llvm::opt::ArgList &Args) const { - // Construct llvm-link command. - // The output from llvm-link is a bitcode file. - ArgStringList LlvmLinkArgs; +void AMDGCN::Linker::constructLlvmLinkCommand( + Compilation &C, const JobAction &JA, const InputInfoList &Inputs, + const InputInfo &Output, const llvm::opt::ArgList &Args) const { - assert(!Inputs.empty() && "Must have at least one input."); + ArgStringList LinkerInputs; - LlvmLinkArgs.append({"-o", Output.getFilename()}); for (auto Input : Inputs) - LlvmLinkArgs.push_back(Input.getFilename()); + LinkerInputs.push_back(Input.getFilename()); // Look for archive of bundled bitcode in arguments, and add temporary files // for the extracted archive of bitcode to inputs. auto TargetID = Args.getLastArgValue(options::OPT_mcpu_EQ); - AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LlvmLinkArgs, "amdgcn", + AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LinkerInputs, "amdgcn", TargetID, /*IsBitCodeSDL=*/true); - - const char *LlvmLink = - Args.MakeArgString(getToolChain().GetProgramPath("llvm-link")); - C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(), - LlvmLink, LlvmLinkArgs, Inputs, - Output)); + tools::constructLlvmLinkCommand(C, *this, JA, Inputs, LinkerInputs, Output, + Args); } void AMDGCN::Linker::constructLldCommand(Compilation &C, const JobAction &JA, diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp index be0f49d8e1497..18d7636c37a6d 100644 --- a/clang/lib/Driver/ToolChains/HIPSPV.cpp +++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp @@ -69,11 +69,8 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand( "generic"; // SPIR-V is generic, no specific target ID like -mcpu tools::AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LinkArgs, Arch, Target, /*IsBitCodeSDL=*/true); - LinkArgs.append({"-o", TempFile}); - const char *LlvmLink = - Args.MakeArgString(getToolChain().GetProgramPath("llvm-link")); - C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(), - LlvmLink, LinkArgs, Inputs, Output)); + tools::constructLlvmLinkCommand(C, *this, JA, Inputs, LinkArgs, Output, Args, + TempFile); // Post-link HIP lowering. diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp index ddb2a0bbb5058..44f7a917aa84a 100644 --- a/clang/lib/Driver/ToolChains/SPIRV.cpp +++ b/clang/lib/Driver/ToolChains/SPIRV.cpp @@ -74,17 +74,15 @@ void SPIRV::constructLLVMLinkCommand(Compilation &C, const Tool &T, const JobAction &JA, const InputInfo &Output, const InputInfoList &Inputs, - const llvm::opt::ArgStringList &Args) { - // Construct llvm-link command. - // The output from llvm-link is a bitcode file. - ArgStringList LlvmLinkArgs; + const llvm::opt::ArgList &Args) { - assert(!Inputs.empty() && "Must have at least one input."); + ArgStringList LlvmLinkArgs; - LlvmLinkArgs.append({"-o", Output.getFilename()}); for (auto Input : Inputs) LlvmLinkArgs.push_back(Input.getFilename()); + tools::constructLlvmLinkCommand(C, T, JA, Inputs, LlvmLinkArgs, Output, Args); + const char *LlvmLink = C.getArgs().MakeArgString(T.getToolChain().GetProgramPath("llvm-link")); C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(), @@ -144,7 +142,7 @@ void SPIRV::Linker::ConstructJob(Compilation &C, const JobAction &JA, const ArgList &Args, const char *LinkingOutput) const { if (JA.getType() == types::TY_LLVM_BC) { - constructLLVMLinkCommand(C, *this, JA, Output, Inputs, {}); + constructLLVMLinkCommand(C, *this, JA, Output, Inputs, Args); return; } const ToolChain &ToolChain = getToolChain(); diff --git a/clang/lib/Driver/ToolChains/SPIRV.h b/clang/lib/Driver/ToolChains/SPIRV.h index 249053c23b792..018e9fa99607c 100644 --- a/clang/lib/Driver/ToolChains/SPIRV.h +++ b/clang/lib/Driver/ToolChains/SPIRV.h @@ -30,7 +30,7 @@ void constructAssembleCommand(Compilation &C, const Tool &T, void constructLLVMLinkCommand(Compilation &C, const Tool &T, const JobAction &JA, const InputInfo &Output, const InputInfoList &Inputs, - const llvm::opt::ArgStringList &Args); + const llvm::opt::ArgList &Args); class LLVM_LIBRARY_VISIBILITY Translator : public Tool { public: diff --git a/clang/test/Driver/hipspv-link-static-library.hip b/clang/test/Driver/hipspv-link-static-library.hip index 03126ae589a09..cf16236738c12 100644 --- a/clang/test/Driver/hipspv-link-static-library.hip +++ b/clang/test/Driver/hipspv-link-static-library.hip @@ -22,7 +22,7 @@ // Verify that the input files are added before the SDL files in llvm-link command // This tests the ordering fix to match HIPAMD behavior // SDL-LINK: "{{.*}}clang-offload-bundler" "-unbundle" "-type=a" "-input={{.*}}libSDL.a" "-targets=hip-spirv64-unknown-unknown-unknown-generic" "-output=[[SDL_A:.*\.a]]" "-allow-missing-bundles" -// SDL-LINK: "{{.*}}llvm-link" "{{.*}}.bc" "[[SDL_A]]" "-o" +// SDL-LINK: "{{.*}}llvm-link" "-o" "{{.*}}.bc" "{{.*}}.bc" "[[SDL_A]]" // SDL-ARCHIVE: "{{.*}}clang-offload-bundler" "-unbundle" "-type=a" "-input={{.*}}libSDL.a" "-targets=hip-spirv64-unknown-unknown-unknown-generic" "-output=[[SDL_A:.*\.a]]" "-allow-missing-bundles" -// SDL-ARCHIVE: "{{.*}}llvm-link" "{{.*}}.bc" "[[SDL_A]]" "-o" +// SDL-ARCHIVE: "{{.*}}llvm-link" "-o" "{{.*}}.bc" "{{.*}}.bc" "[[SDL_A]]" diff --git a/clang/test/Driver/hipspv-toolchain-rdc.hip b/clang/test/Driver/hipspv-toolchain-rdc.hip index acdadacc49064..05d9079f8dc18 100644 --- a/clang/test/Driver/hipspv-toolchain-rdc.hip +++ b/clang/test/Driver/hipspv-toolchain-rdc.hip @@ -41,7 +41,7 @@ // CHECK-SAME: {{.*}} [[B_SRC]] // Link device code, lower it with HIPSPV passes and emit SPIR-V binary. -// CHECK: {{".*llvm-link.*"}} [[A_BC1]] [[B_BC1]] "-o" [[AB_LINK:".*bc"]] +// CHECK: {{".*llvm-link.*"}} "-o" [[AB_LINK:".*bc"]] [[A_BC1]] [[B_BC1]] // CHECK: {{".*opt.*"}} [[AB_LINK]] "-load-pass-plugin" // CHECK-SAME: "{{.*}}/Inputs/hipspv/lib/libLLVMHipSpvPasses.so" // CHECK-SAME: "-o" [[AB_LOWER:".*bc"]] diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip index 3c175ebf433cc..85c4333e877f2 100644 --- a/clang/test/Driver/hipspv-toolchain.hip +++ b/clang/test/Driver/hipspv-toolchain.hip @@ -14,7 +14,7 @@ // CHECK-SAME: "-o" [[DEV_BC:".*bc"]] // CHECK-SAME: "-x" "hip" -// CHECK: {{".*llvm-link"}} [[DEV_BC]] "-o" [[LINK_BC:".*bc"]] +// CHECK: {{".*llvm-link"}} "-o" [[LINK_BC:".*bc"]] [[DEV_BC]] // CHECK: {{".*opt"}} [[LINK_BC]] "-load-pass-plugin" // CHECK-SAME: {{".*/hipspv/lib/libLLVMHipSpvPasses.so"}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
