[clang] [HIP] Support device-only linking of bitcode offload binaries (PR #212342)
Yaxun Liu via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 27 20:42:20 PDT 2026
https://github.com/yxsamliu updated https://github.com/llvm/llvm-project/pull/212342
>From 0d62f82e60c2d7a1030796b855df0a3eafd4a13c Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Mon, 27 Jul 2026 15:07:16 -0400
Subject: [PATCH 1/2] [HIP] Support device-only linking of bitcode offload
binaries
The new offload driver packages multi-architecture bitcode in LLVM
offload binaries. A later `--hip-link --offload-device-only`
invocation treated these `.bc` inputs as host LLVM IR and dropped
them before linking.
Pass these inputs directly to clang-linker-wrapper when producing a
device fat binary. The wrapper can then extract and link each
requested GPU architecture.
---
clang/lib/Driver/Driver.cpp | 18 ++++++-
clang/lib/Driver/ToolChains/Clang.cpp | 16 +++----
clang/test/Driver/hip-link-offload-binary.c | 53 +++++++++++++++++++++
3 files changed, 78 insertions(+), 9 deletions(-)
create mode 100644 clang/test/Driver/hip-link-offload-binary.c
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 2ef9ffe0b9426..38795f7c2ae7a 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -4542,6 +4542,14 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
bool UseNewOffloadingDriver = Args.hasFlag(
options::OPT_offload_new_driver, options::OPT_no_offload_new_driver,
C.getActiveOffloadKinds() != Action::OFK_None);
+ bool HIPRDCDeviceOnlyFatBin =
+ UseNewOffloadingDriver && C.isOffloadingHostKind(Action::OFK_HIP) &&
+ offloadDeviceOnly() && Args.hasArg(options::OPT_hip_link) &&
+ Args.hasFlag(options::OPT_fgpu_rdc, options::OPT_fno_gpu_rdc, false) &&
+ getFinalPhase(Args) == phases::Link &&
+ !Args.hasArg(options::OPT_emit_llvm) &&
+ Args.hasFlag(options::OPT_gpu_bundle_output,
+ options::OPT_no_gpu_bundle_output, true);
// Builder to be used to build offloading actions.
std::unique_ptr<OffloadingActionBuilder> OffloadBuilder =
@@ -4567,6 +4575,12 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
// Build the pipeline for this file.
Action *Current = C.MakeAction<InputAction>(*InputArg, InputType);
+ // Device-only HIP links consume packaged offload bitcode directly.
+ if (HIPRDCDeviceOnlyFatBin && InputType == types::TY_LLVM_BC) {
+ LinkerInputs.push_back(Current);
+ continue;
+ }
+
std::string CUID;
if (CUIDOpts.isEnabled() && types::isSrcFile(InputType)) {
CUID = CUIDOpts.getCUID(InputArg->getValue(), Args);
@@ -4693,7 +4707,9 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args,
LA = C.MakeAction<StaticLibJobAction>(LinkerInputs, types::TY_Image);
} else if (UseNewOffloadingDriver ||
Args.hasArg(options::OPT_offload_link)) {
- LA = C.MakeAction<LinkerWrapperJobAction>(LinkerInputs, types::TY_Image);
+ LA = C.MakeAction<LinkerWrapperJobAction>(
+ LinkerInputs,
+ HIPRDCDeviceOnlyFatBin ? types::TY_HIP_FATBIN : types::TY_Image);
LA->propagateHostOffloadInfo(C.getActiveOffloadKinds(),
/*BA=*/{});
} else {
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 0904d116b5cba..94f9a26aac39f 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -9839,6 +9839,11 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
OPT_fno_slp_vectorize,
OPT_hipstdpar};
const llvm::DenseSet<unsigned> LinkerOptions{OPT_mllvm, OPT_Zlinker_input};
+ // Suppress verbose output for HIP non-RDC fat binaries because it confuses
+ // CMake implicit linker argument parsing.
+ bool SuppressHIPNoRDCVerbose =
+ JA.getType() == types::TY_HIP_FATBIN &&
+ !Args.hasFlag(options::OPT_fgpu_rdc, options::OPT_fno_gpu_rdc, false);
auto ToolChainHasRT = [&](const ToolChain &TC, StringRef Name) {
return TC.getVFS().exists(
TC.getCompilerRT(Args, Name, ToolChain::FT_Static));
@@ -9860,8 +9865,7 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
};
auto ShouldForward = [&](const llvm::DenseSet<unsigned> &Set, Arg *A,
const ToolChain &TC) {
- // CMake hack to avoid printing verbose informatoin for HIP non-RDC mode.
- if (A->getOption().matches(OPT_v) && JA.getType() == types::TY_HIP_FATBIN)
+ if (A->getOption().matches(OPT_v) && SuppressHIPNoRDCVerbose)
return false;
return (Set.contains(A->getOption().getID()) ||
(A->getOption().getGroup().isValid() &&
@@ -9979,11 +9983,7 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back(Args.MakeArgString("--host-triple=" +
getToolChain().getTripleString()));
- // CMake hack, suppress passing verbose arguments for the special-case HIP
- // non-RDC mode compilation. This confuses default CMake implicit linker
- // argument parsing when the language is set to HIP and the system linker is
- // also `ld.lld`.
- if (Args.hasArg(options::OPT_v) && JA.getType() != types::TY_HIP_FATBIN)
+ if (Args.hasArg(options::OPT_v) && !SuppressHIPNoRDCVerbose)
CmdArgs.push_back("--wrapper-verbose");
if (Arg *A = Args.getLastArg(options::OPT_cuda_path_EQ)) {
CmdArgs.push_back(
@@ -10071,7 +10071,7 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
// We use action type to differentiate two use cases of the linker wrapper.
// TY_Image for normal linker wrapper work.
- // TY_HIP_FATBIN for HIP fno-gpu-rdc emitting a fat binary without wrapping.
+ // TY_HIP_FATBIN for HIP device-only links emitting a fat binary directly.
assert(JA.getType() == types::TY_HIP_FATBIN ||
JA.getType() == types::TY_Image);
if (JA.getType() == types::TY_HIP_FATBIN) {
diff --git a/clang/test/Driver/hip-link-offload-binary.c b/clang/test/Driver/hip-link-offload-binary.c
new file mode 100644
index 0000000000000..5cb3b84677f67
--- /dev/null
+++ b/clang/test/Driver/hip-link-offload-binary.c
@@ -0,0 +1,53 @@
+// REQUIRES: amdgpu-registered-target
+// REQUIRES: lld
+
+// RUN: %clang --target=amdgcn-amd-amdhsa -emit-llvm -c -nogpulib -DVAR=x %s -o %t.x.bc
+// RUN: %clang --target=amdgcn-amd-amdhsa -emit-llvm -c -nogpulib -DVAR=y %s -o %t.y.bc
+// RUN: llvm-offload-binary -o %t.x.bundle.bc \
+// RUN: --image=file=%t.x.bc,triple=amdgcn-amd-amdhsa,arch=gfx906,kind=hip \
+// RUN: --image=file=%t.x.bc,triple=amdgcn-amd-amdhsa,arch=gfx942,kind=hip
+// RUN: llvm-offload-binary -o %t.y.bundle.bc \
+// RUN: --image=file=%t.y.bc,triple=amdgcn-amd-amdhsa,arch=gfx906,kind=hip \
+// RUN: --image=file=%t.y.bc,triple=amdgcn-amd-amdhsa,arch=gfx942,kind=hip
+
+// RUN: %clang -### -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: --offload-arch=gfx906 --offload-arch=gfx942 \
+// RUN: %t.x.bundle.bc %t.y.bundle.bc -o %t.hipfb 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DRIVER
+// DRIVER: "{{.*}}clang-linker-wrapper"
+// DRIVER-SAME: "--should-extract=gfx906"
+// DRIVER-SAME: "--should-extract=gfx942"
+// DRIVER-SAME: "--emit-fatbin-only" "-o" "{{.*}}.hipfb"
+// DRIVER-SAME: "{{.*}}.x.bundle.bc" "{{.*}}.y.bundle.bc"
+
+// RUN: %clang -### -v -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: --offload-arch=gfx906 %t.x.bundle.bc -o %t.hipfb 2>&1 \
+// RUN: | FileCheck %s --check-prefix=VERBOSE
+// VERBOSE: "{{.*}}clang-linker-wrapper"
+// VERBOSE-SAME: "--device-compiler=amdgcn-amd-amdhsa=-v"
+// VERBOSE-SAME: "--wrapper-verbose"
+// VERBOSE-SAME: "--emit-fatbin-only"
+
+// RUN: %clang -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: --offload-arch=gfx906 --offload-arch=gfx942 \
+// RUN: %t.x.bundle.bc %t.y.bundle.bc -o %t.hipfb
+// RUN: clang-offload-bundler -type=o -list -input=%t.hipfb \
+// RUN: | FileCheck %s --check-prefix=ARCH
+// ARCH-DAG: hip-amdgcn-amd-amdhsa--gfx906
+// ARCH-DAG: hip-amdgcn-amd-amdhsa--gfx942
+
+// RUN: %clang -### -c -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: --offload-arch=gfx906 %t.x.bundle.bc -o %t.o 2>&1 \
+// RUN: | FileCheck %s --check-prefix=COMPILE
+// COMPILE: "-cc1"
+// COMPILE-NOT: "{{.*}}clang-linker-wrapper"
+
+// RUN: %clang -### -emit-llvm -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: --offload-arch=gfx906 %t.x.bundle.bc -o %t.linked.bc 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-FATBIN --allow-empty
+// RUN: %clang -### --no-gpu-bundle-output -fgpu-rdc --hip-link \
+// RUN: --cuda-device-only --offload-arch=gfx906 %t.x.bundle.bc 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-FATBIN --allow-empty
+// NO-FATBIN-NOT: "--emit-fatbin-only"
+
+__attribute__((visibility("protected"), used)) int VAR;
>From e818de91ebab0ca6d0f27fc2612be4767a5d899d Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Mon, 27 Jul 2026 23:41:36 -0400
Subject: [PATCH 2/2] [HIP] Specify Linux target in offload binary test
---
clang/test/Driver/hip-link-offload-binary.c | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/clang/test/Driver/hip-link-offload-binary.c b/clang/test/Driver/hip-link-offload-binary.c
index 5cb3b84677f67..b318074130abd 100644
--- a/clang/test/Driver/hip-link-offload-binary.c
+++ b/clang/test/Driver/hip-link-offload-binary.c
@@ -10,7 +10,8 @@
// RUN: --image=file=%t.y.bc,triple=amdgcn-amd-amdhsa,arch=gfx906,kind=hip \
// RUN: --image=file=%t.y.bc,triple=amdgcn-amd-amdhsa,arch=gfx942,kind=hip
-// RUN: %clang -### -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: %clang -### --target=x86_64-unknown-linux-gnu \
+// RUN: -fgpu-rdc --hip-link --cuda-device-only \
// RUN: --offload-arch=gfx906 --offload-arch=gfx942 \
// RUN: %t.x.bundle.bc %t.y.bundle.bc -o %t.hipfb 2>&1 \
// RUN: | FileCheck %s --check-prefix=DRIVER
@@ -20,7 +21,8 @@
// DRIVER-SAME: "--emit-fatbin-only" "-o" "{{.*}}.hipfb"
// DRIVER-SAME: "{{.*}}.x.bundle.bc" "{{.*}}.y.bundle.bc"
-// RUN: %clang -### -v -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: %clang -### -v --target=x86_64-unknown-linux-gnu \
+// RUN: -fgpu-rdc --hip-link --cuda-device-only \
// RUN: --offload-arch=gfx906 %t.x.bundle.bc -o %t.hipfb 2>&1 \
// RUN: | FileCheck %s --check-prefix=VERBOSE
// VERBOSE: "{{.*}}clang-linker-wrapper"
@@ -28,7 +30,8 @@
// VERBOSE-SAME: "--wrapper-verbose"
// VERBOSE-SAME: "--emit-fatbin-only"
-// RUN: %clang -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: %clang --target=x86_64-unknown-linux-gnu \
+// RUN: -fgpu-rdc --hip-link --cuda-device-only \
// RUN: --offload-arch=gfx906 --offload-arch=gfx942 \
// RUN: %t.x.bundle.bc %t.y.bundle.bc -o %t.hipfb
// RUN: clang-offload-bundler -type=o -list -input=%t.hipfb \
@@ -36,16 +39,19 @@
// ARCH-DAG: hip-amdgcn-amd-amdhsa--gfx906
// ARCH-DAG: hip-amdgcn-amd-amdhsa--gfx942
-// RUN: %clang -### -c -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: %clang -### -c --target=x86_64-unknown-linux-gnu \
+// RUN: -fgpu-rdc --hip-link --cuda-device-only \
// RUN: --offload-arch=gfx906 %t.x.bundle.bc -o %t.o 2>&1 \
// RUN: | FileCheck %s --check-prefix=COMPILE
// COMPILE: "-cc1"
// COMPILE-NOT: "{{.*}}clang-linker-wrapper"
-// RUN: %clang -### -emit-llvm -fgpu-rdc --hip-link --cuda-device-only \
+// RUN: %clang -### -emit-llvm --target=x86_64-unknown-linux-gnu \
+// RUN: -fgpu-rdc --hip-link --cuda-device-only \
// RUN: --offload-arch=gfx906 %t.x.bundle.bc -o %t.linked.bc 2>&1 \
// RUN: | FileCheck %s --check-prefix=NO-FATBIN --allow-empty
-// RUN: %clang -### --no-gpu-bundle-output -fgpu-rdc --hip-link \
+// RUN: %clang -### --no-gpu-bundle-output \
+// RUN: --target=x86_64-unknown-linux-gnu -fgpu-rdc --hip-link \
// RUN: --cuda-device-only --offload-arch=gfx906 %t.x.bundle.bc 2>&1 \
// RUN: | FileCheck %s --check-prefix=NO-FATBIN --allow-empty
// NO-FATBIN-NOT: "--emit-fatbin-only"
More information about the cfe-commits
mailing list