[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