https://github.com/yxsamliu updated 
https://github.com/llvm/llvm-project/pull/211675

>From c2262ae93c337583a9780e05cb823eaae28c45f3 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <[email protected]>
Date: Fri, 14 Aug 2026 10:25:03 -0400
Subject: [PATCH 1/4] [HIP] Use linker wrapper for device-only code object
 links

HIP device-only non-RDC links should use the linker wrapper path so device 
libraries and offload profile options are handled consistently.

When --no-gpu-bundle-output asks for raw images, the driver now runs the 
wrapper to produce a fat binary and then adds an unbundling job. This keeps raw 
image output out of clang-linker-wrapper and reuses the existing 
multiple-output naming path.
---
 clang/include/clang/Driver/Action.h           |   3 +-
 clang/include/clang/Driver/Compilation.h      |  15 +-
 clang/include/clang/Driver/Driver.h           |   6 +-
 clang/include/clang/Driver/Util.h             |   4 +
 clang/lib/Driver/Action.cpp                   |   4 +
 clang/lib/Driver/Compilation.cpp              |   8 +-
 clang/lib/Driver/Driver.cpp                   | 158 +++++++++++-------
 clang/lib/Driver/ToolChains/AMDGPU.cpp        |   1 +
 clang/lib/Driver/ToolChains/Clang.cpp         |  57 +++++--
 clang/test/Driver/hip-binding.hip             |  35 +++-
 clang/test/Driver/hip-phases.hip              |  18 +-
 .../test/Driver/hip-profile-rocm-runtime.hip  |  65 ++++++-
 .../test/Driver/hip-toolchain-device-only.hip |  18 +-
 .../linker-wrapper-hip-no-rdc.c               |  15 ++
 14 files changed, 296 insertions(+), 111 deletions(-)

diff --git a/clang/include/clang/Driver/Action.h 
b/clang/include/clang/Driver/Action.h
index 306db1be15910..018e267bec137 100644
--- a/clang/include/clang/Driver/Action.h
+++ b/clang/include/clang/Driver/Action.h
@@ -617,8 +617,9 @@ class OffloadUnbundlingJobAction final : public JobAction {
   SmallVector<DependentActionInfo, 6> DependentActionInfoArray;
 
 public:
-  // Offloading unbundling doesn't change the type of output.
+  // Offloading unbundling usually doesn't change the type of output.
   OffloadUnbundlingJobAction(Action *Input);
+  OffloadUnbundlingJobAction(Action *Input, types::ID OutputType);
 
   /// Register information about a dependent action.
   void registerDependentActionInfo(const ToolChain *TC, BoundArch BA,
diff --git a/clang/include/clang/Driver/Compilation.h 
b/clang/include/clang/Driver/Compilation.h
index 825806b6cfe33..01eff0af7852f 100644
--- a/clang/include/clang/Driver/Compilation.h
+++ b/clang/include/clang/Driver/Compilation.h
@@ -101,11 +101,11 @@ class Compilation {
   llvm::opt::ArgStringList TempFiles;
 
   /// Result files which should be removed on failure.
-  ArgStringMap ResultFiles;
+  ActionFileMap ResultFiles;
 
   /// Result files which are generated correctly on failure, and which should
   /// only be removed if we crash.
-  ArgStringMap FailureResultFiles;
+  ActionFileMap FailureResultFiles;
 
   /// -ftime-trace result files.
   ArgStringMap TimeTraceFiles;
@@ -227,9 +227,9 @@ class Compilation {
   llvm::opt::ArgStringList &getTempFiles() { return TempFiles; }
   const llvm::opt::ArgStringList &getTempFiles() const { return TempFiles; }
 
-  const ArgStringMap &getResultFiles() const { return ResultFiles; }
+  const ActionFileMap &getResultFiles() const { return ResultFiles; }
 
-  const ArgStringMap &getFailureResultFiles() const {
+  const ActionFileMap &getFailureResultFiles() const {
     return FailureResultFiles;
   }
 
@@ -266,14 +266,14 @@ class Compilation {
   /// addResultFile - Add a file to remove on failure, and returns its
   /// argument.
   const char *addResultFile(const char *Name, const JobAction *JA) {
-    ResultFiles[JA] = Name;
+    ResultFiles[JA].push_back(Name);
     return Name;
   }
 
   /// addFailureResultFile - Add a file to remove if we crash, and returns its
   /// argument.
   const char *addFailureResultFile(const char *Name, const JobAction *JA) {
-    FailureResultFiles[JA] = Name;
+    FailureResultFiles[JA].push_back(Name);
     return Name;
   }
 
@@ -304,8 +304,7 @@ class Compilation {
   /// JobAction.  Otherwise, delete all files in the map.
   /// \param IssueErrors - Report failures as errors.
   /// \return Whether all files were removed successfully.
-  bool CleanupFileMap(const ArgStringMap &Files,
-                      const JobAction *JA,
+  bool CleanupFileMap(const ActionFileMap &Files, const JobAction *JA,
                       bool IssueErrors = false) const;
 
   /// ExecuteCommand - Execute an actual command.
diff --git a/clang/include/clang/Driver/Driver.h 
b/clang/include/clang/Driver/Driver.h
index eece9ac5293f0..8db71b01195d9 100644
--- a/clang/include/clang/Driver/Driver.h
+++ b/clang/include/clang/Driver/Driver.h
@@ -693,11 +693,13 @@ class Driver {
   /// \param BA - The bound architecture.
   /// \param AtTopLevel - Whether this is a "top-level" action.
   /// \param MultipleArchs - Whether multiple -arch options were supplied.
-  /// \param NormalizedTriple - The normalized triple of the relevant target.
+  /// \param OffloadingPrefix - The filename prefix for an offload target.
+  /// \param UseFinalOutput - Whether to use the filename from -o.
   const char *GetNamedOutputPath(Compilation &C, const JobAction &JA,
                                  const char *BaseInput, BoundArch BA,
                                  bool AtTopLevel, bool MultipleArchs,
-                                 StringRef NormalizedTriple) const;
+                                 StringRef OffloadingPrefix,
+                                 bool UseFinalOutput = true) const;
 
   /// GetTemporaryPath - Return the pathname of a temporary file to use
   /// as part of compilation; the file will have the given prefix and suffix.
diff --git a/clang/include/clang/Driver/Util.h 
b/clang/include/clang/Driver/Util.h
index 92d3d40433a32..d57a861833f08 100644
--- a/clang/include/clang/Driver/Util.h
+++ b/clang/include/clang/Driver/Util.h
@@ -21,6 +21,10 @@ namespace driver {
   /// ArgStringMap - Type used to map a JobAction to its result file.
   typedef llvm::DenseMap<const JobAction*, const char*> ArgStringMap;
 
+  /// ActionFileMap - Type used to map a JobAction to its result files.
+  typedef llvm::DenseMap<const JobAction *, SmallVector<const char *, 1>>
+      ActionFileMap;
+
   /// ActionList - Type used for lists of actions.
   typedef SmallVector<Action*, 3> ActionList;
 
diff --git a/clang/lib/Driver/Action.cpp b/clang/lib/Driver/Action.cpp
index b74ae925e922c..3b71bfb7b53b1 100644
--- a/clang/lib/Driver/Action.cpp
+++ b/clang/lib/Driver/Action.cpp
@@ -440,6 +440,10 @@ void OffloadUnbundlingJobAction::anchor() {}
 OffloadUnbundlingJobAction::OffloadUnbundlingJobAction(Action *Input)
     : JobAction(OffloadUnbundlingJobClass, Input, Input->getType()) {}
 
+OffloadUnbundlingJobAction::OffloadUnbundlingJobAction(Action *Input,
+                                                       types::ID OutputType)
+    : JobAction(OffloadUnbundlingJobClass, Input, OutputType) {}
+
 void OffloadPackagerJobAction::anchor() {}
 
 OffloadPackagerJobAction::OffloadPackagerJobAction(ActionList &Inputs,
diff --git a/clang/lib/Driver/Compilation.cpp b/clang/lib/Driver/Compilation.cpp
index c81c4445a29f9..f3fa01fa9e104 100644
--- a/clang/lib/Driver/Compilation.cpp
+++ b/clang/lib/Driver/Compilation.cpp
@@ -149,16 +149,16 @@ bool Compilation::CleanupFileList(const 
llvm::opt::ArgStringList &Files,
   return Success;
 }
 
-bool Compilation::CleanupFileMap(const ArgStringMap &Files,
-                                 const JobAction *JA,
-                                 bool IssueErrors) const {
+bool Compilation::CleanupFileMap(const ActionFileMap &Files,
+                                 const JobAction *JA, bool IssueErrors) const {
   bool Success = true;
   for (const auto &File : Files) {
     // If specified, only delete the files associated with the JobAction.
     // Otherwise, delete all files in the map.
     if (JA && File.first != JA)
       continue;
-    Success &= CleanupFile(File.second, IssueErrors);
+    for (const char *Filename : File.second)
+      Success &= CleanupFile(Filename, IssueErrors);
   }
   return Success;
 }
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 38795f7c2ae7a..9efaf2277d461 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -5053,6 +5053,11 @@ Driver::BuildOffloadingActions(Compilation &C, 
llvm::opt::DerivedArgList &Args,
       Args.hasFlag(options::OPT_fhip_emit_relocatable,
                    options::OPT_fno_hip_emit_relocatable, false);
 
+  bool HasGPUOutputBundleOption = Args.hasArg(
+      options::OPT_gpu_bundle_output, options::OPT_no_gpu_bundle_output);
+  bool BundleGPUOutput = Args.hasFlag(options::OPT_gpu_bundle_output,
+                                      options::OPT_no_gpu_bundle_output, true);
+
   if (!HIPNoRDC && HIPRelocatableObj)
     C.getDriver().Diag(diag::err_opt_not_valid_with_opt)
         << "-fhip-emit-relocatable"
@@ -5071,6 +5076,7 @@ Driver::BuildOffloadingActions(Compilation &C, 
llvm::opt::DerivedArgList &Args,
 
   ActionList OffloadActions;
   OffloadAction::DeviceDependences DDeps;
+  bool HIPDeviceOnlyNeedsLink = false;
 
   const Action::OffloadKind OffloadKinds[] = {
       Action::OFK_OpenMP, Action::OFK_Cuda, Action::OFK_HIP, Action::OFK_SYCL};
@@ -5159,8 +5165,9 @@ Driver::BuildOffloadingActions(Compilation &C, 
llvm::opt::DerivedArgList &Args,
       }
     }
 
-    // Compiling HIP in device-only non-RDC mode requires linking each action
-    // individually.
+    // Compiling HIP in device-only non-RDC mode requires linking each action.
+    // Keep the inputs unlinked so the linker wrapper can link all device
+    // images in one invocation.
     for (Action *&A : DeviceActions) {
       auto *OffloadTriple = A->getOffloadingToolChain()
                                 ? &A->getOffloadingToolChain()->getTriple()
@@ -5170,12 +5177,19 @@ Driver::BuildOffloadingActions(Compilation &C, 
llvm::opt::DerivedArgList &Args,
           (OffloadTriple->getOS() == llvm::Triple::OSType::AMDHSA ||
            OffloadTriple->getOS() == llvm::Triple::OSType::ChipStar);
 
-      if ((A->getType() != types::TY_Object && !IsHIPSPV &&
-           A->getType() != types::TY_LTO_BC) ||
-          HIPRelocatableObj || !HIPNoRDC || !offloadDeviceOnly())
+      if (HIPRelocatableObj || !HIPNoRDC || !offloadDeviceOnly())
+        continue;
+
+      if (IsHIPSPV) {
+        ActionList LinkerInput = {A};
+        A = C.MakeAction<LinkJobAction>(LinkerInput, types::TY_Image);
+        continue;
+      }
+
+      if (A->getType() != types::TY_Object && A->getType() != types::TY_LTO_BC)
         continue;
-      ActionList LinkerInput = {A};
-      A = C.MakeAction<LinkJobAction>(LinkerInput, types::TY_Image);
+
+      HIPDeviceOnlyNeedsLink = true;
     }
 
     auto *TCAndArch = TCAndArchs.begin();
@@ -5196,18 +5210,18 @@ Driver::BuildOffloadingActions(Compilation &C, 
llvm::opt::DerivedArgList &Args,
     }
   }
 
-  // HIP code in device-only non-RDC mode will bundle the output if it invoked
-  // the linker or if the user explicitly requested it.
+  // HIP code in device-only non-RDC mode will bundle linked output by default
+  // or bundle any output if the user explicitly requested it.
   bool ShouldBundleHIP =
-      Args.hasFlag(options::OPT_gpu_bundle_output,
-                   options::OPT_no_gpu_bundle_output, false) ||
-      (!Args.getLastArg(options::OPT_no_gpu_bundle_output) && HIPNoRDC &&
-       offloadDeviceOnly() && llvm::none_of(OffloadActions, [](Action *A) {
-         return A->getType() != types::TY_Image;
-       }));
+      BundleGPUOutput &&
+      (HasGPUOutputBundleOption ||
+       (HIPNoRDC && offloadDeviceOnly() &&
+        (HIPDeviceOnlyNeedsLink || llvm::none_of(OffloadActions, [](Action *A) 
{
+           return A->getType() != types::TY_Image;
+         }))));
 
   // All kinds exit now in device-only mode except for non-RDC mode HIP.
-  if (offloadDeviceOnly() && !ShouldBundleHIP)
+  if (offloadDeviceOnly() && !ShouldBundleHIP && !HIPDeviceOnlyNeedsLink)
     return C.MakeAction<OffloadAction>(DDeps, types::TY_Nothing);
 
   if (OffloadActions.empty())
@@ -5222,37 +5236,57 @@ Driver::BuildOffloadingActions(Compilation &C, 
llvm::opt::DerivedArgList &Args,
         C.MakeAction<LinkJobAction>(OffloadActions, types::TY_CUDA_FATBIN);
     DDep.add(*FatbinAction, *C.getSingleOffloadToolChain<Action::OFK_Cuda>(),
              /*BA=*/{}, Action::OFK_Cuda);
-  } else if (HIPNoRDC && offloadDeviceOnly()) {
-    // If we are in device-only non-RDC-mode we just emit the final HIP
-    // fatbinary for each translation unit, linking each input individually.
-    Action *FatbinAction =
-        C.MakeAction<LinkJobAction>(OffloadActions, types::TY_HIP_FATBIN);
-    DDep.add(*FatbinAction,
-             *C.getOffloadToolChains<Action::OFK_HIP>().first->second,
-             /*BA=*/{}, Action::OFK_HIP);
   } else if (HIPNoRDC) {
     // Host + device assembly: defer to clang-offload-bundler (see
     // BuildActions).
-    if (HIPAsmBundleDeviceOut &&
+    if (!offloadDeviceOnly() && HIPAsmBundleDeviceOut &&
         shouldBundleHIPAsmWithNewDriver(C, Args, C.getDriver())) {
       for (Action *OA : OffloadActions)
         HIPAsmBundleDeviceOut->push_back(OA);
       return HostAction;
     }
-    // Package all the offloading actions into a single output that can be
-    // embedded in the host and linked.
-    Action *PackagerAction =
-        C.MakeAction<OffloadPackagerJobAction>(OffloadActions, 
types::TY_Image);
 
-    // For HIP non-RDC compilation, wrap the device binary with linker wrapper
-    // before bundling with host code. Do not bind a specific GPU arch here,
-    // as the packaged image may contain entries for multiple GPUs.
-    ActionList AL{PackagerAction};
-    PackagerAction =
-        C.MakeAction<LinkerWrapperJobAction>(AL, types::TY_HIP_FATBIN);
-    DDep.add(*PackagerAction,
-             *C.getOffloadToolChains<Action::OFK_HIP>().first->second,
-             /*BA=*/{}, Action::OFK_HIP);
+    Action *DeviceOutputAction;
+    if (offloadDeviceOnly() && !HIPDeviceOnlyNeedsLink) {
+      // Non-link outputs use clang-offload-bundler when explicitly requested.
+      DeviceOutputAction =
+          C.MakeAction<LinkJobAction>(OffloadActions, types::TY_HIP_FATBIN);
+    } else {
+      // Package all device inputs so the linker wrapper can link every GPU
+      // architecture in one invocation.
+      Action *Packager = C.MakeAction<OffloadPackagerJobAction>(
+          OffloadActions, types::TY_Image);
+      ActionList WrapperInputs = {Packager};
+      DeviceOutputAction = C.MakeAction<LinkerWrapperJobAction>(
+          WrapperInputs, types::TY_HIP_FATBIN);
+      if (offloadDeviceOnly() && !ShouldBundleHIP) {
+        auto *Unbundler = C.MakeAction<OffloadUnbundlingJobAction>(
+            DeviceOutputAction, types::TY_Image);
+        for (Action *A : OffloadActions) {
+          const ToolChain *DeviceTC = nullptr;
+          BoundArch DeviceArch;
+          cast<OffloadAction>(A)->doOnEachDeviceDependence(
+              [&](Action *, const ToolChain *TC, BoundArch BA) {
+                assert(!DeviceTC && "expected one device dependence");
+                DeviceTC = TC;
+                DeviceArch = BA;
+              });
+          assert(DeviceTC && "expected a device toolchain");
+          Unbundler->registerDependentActionInfo(DeviceTC, DeviceArch,
+                                                 Action::OFK_HIP);
+        }
+        DeviceOutputAction = Unbundler;
+      }
+    }
+    if (auto *UA = dyn_cast<OffloadUnbundlingJobAction>(DeviceOutputAction)) {
+      for (const auto &Info : UA->getDependentActionsInfo())
+        DDep.add(*DeviceOutputAction, *Info.DependentToolChain,
+                 Info.DependentBoundArch, Action::OFK_HIP);
+    } else {
+      DDep.add(*DeviceOutputAction,
+               *C.getOffloadToolChains<Action::OFK_HIP>().first->second,
+               /*BA=*/{}, Action::OFK_HIP);
+    }
   } else {
     // Package all the offloading actions into a single output that can be
     // embedded in the host and linked.
@@ -6336,32 +6370,35 @@ InputInfoList Driver::BuildJobsForActionNoCache(
 
   // Determine the place to write output to, if any.
   InputInfo Result;
-  InputInfoList UnbundlingResults;
+  InputInfoList MultipleResults;
   if (auto *UA = dyn_cast<OffloadUnbundlingJobAction>(JA)) {
     // If we have an unbundling job, we need to create results for all the
     // outputs. We also update the results cache so that other actions using
     // this unbundling action can get the right results.
+    bool ChangesType = UA->getInputs().front()->getType() != UA->getType();
     for (auto &UI : UA->getDependentActionsInfo()) {
       assert(UI.DependentOffloadKind != Action::OFK_None &&
              "Unbundling with no offloading??");
 
-      // Unbundling actions are never at the top level. When we generate the
-      // offloading prefix, we also do that for the host file because the
-      // unbundling action does not change the type of the output which can
-      // cause a overwrite.
+      // Most unbundling actions are internal and keep the input type. The HIP
+      // device-only fatbin-to-image unbundling action is a final output 
action,
+      // so let a single output use -o while keeping generated names for
+      // multiple outputs.
       std::string OffloadingPrefix = Action::GetOffloadingFileNamePrefix(
           UI.DependentOffloadKind, UI.DependentToolChain->getTripleString(),
           /*CreatePrefixForHost=*/true);
       auto CurI = InputInfo(
           UA,
           GetNamedOutputPath(C, *UA, BaseInput, UI.DependentBoundArch,
-                             /*AtTopLevel=*/false,
+                             /*AtTopLevel=*/ChangesType,
                              MultipleArchs ||
                                  UI.DependentOffloadKind == Action::OFK_HIP,
-                             OffloadingPrefix),
+                             OffloadingPrefix,
+                             /*UseFinalOutput=*/ChangesType &&
+                                 UA->getDependentActionsInfo().size() == 1),
           BaseInput);
       // Save the unbundling result.
-      UnbundlingResults.push_back(CurI);
+      MultipleResults.push_back(CurI);
 
       // Get the unique string identifier for this dependence and cache the
       // result.
@@ -6383,9 +6420,14 @@ InputInfoList Driver::BuildJobsForActionNoCache(
     // returned for the current depending action.
     std::pair<const Action *, std::string> ActionTC = {
         A, GetTriplePlusArchString(TC, BA, TargetDeviceOffloadKind)};
-    assert(CachedResults.find(ActionTC) != CachedResults.end() &&
-           "Result does not exist??");
-    Result = CachedResults[ActionTC].front();
+    auto ResultIt = CachedResults.find(ActionTC);
+    if (ResultIt != CachedResults.end()) {
+      Result = ResultIt->second.front();
+    } else {
+      assert(ChangesType &&
+             "unbundling result missing for current toolchain/arch");
+      Result = MultipleResults.front();
+    }
   } else if (JA->getType() == types::TY_Nothing)
     Result = {InputInfo(A, BaseInput)};
   else {
@@ -6412,23 +6454,23 @@ InputInfoList Driver::BuildJobsForActionNoCache(
       if (i + 1 != e)
         llvm::errs() << ", ";
     }
-    if (UnbundlingResults.empty())
+    if (MultipleResults.empty())
       llvm::errs() << "], output: " << Result.getAsString() << "\n";
     else {
       llvm::errs() << "], outputs: [";
-      for (unsigned i = 0, e = UnbundlingResults.size(); i != e; ++i) {
-        llvm::errs() << UnbundlingResults[i].getAsString();
+      for (unsigned i = 0, e = MultipleResults.size(); i != e; ++i) {
+        llvm::errs() << MultipleResults[i].getAsString();
         if (i + 1 != e)
           llvm::errs() << ", ";
       }
       llvm::errs() << "] \n";
     }
   } else {
-    if (UnbundlingResults.empty())
+    if (MultipleResults.empty())
       T->ConstructJob(C, *JA, Result, InputInfos, Args, LinkingOutput);
     else
-      T->ConstructJobMultipleOutputs(C, *JA, UnbundlingResults, InputInfos,
-                                     Args, LinkingOutput);
+      T->ConstructJobMultipleOutputs(C, *JA, MultipleResults, InputInfos, Args,
+                                     LinkingOutput);
   }
   return {Result};
 }
@@ -6547,7 +6589,8 @@ static const char *GetModuleOutputPath(Compilation &C, 
const JobAction &JA,
 const char *Driver::GetNamedOutputPath(Compilation &C, const JobAction &JA,
                                        const char *BaseInput, BoundArch BA,
                                        bool AtTopLevel, bool MultipleArchs,
-                                       StringRef OffloadingPrefix) const {
+                                       StringRef OffloadingPrefix,
+                                       bool UseFinalOutput) const {
   std::string BoundArchStr = sanitizeTargetIDInFileName(BA.ArchName);
 
   llvm::PrettyStackTraceString CrashInfo("Computing output path");
@@ -6578,7 +6621,8 @@ const char *Driver::GetNamedOutputPath(Compilation &C, 
const JobAction &JA,
 
   // Output to a user requested destination?
   if (AtTopLevel && !isa<DsymutilJobAction>(JA) && !isa<VerifyJobAction>(JA)) {
-    if (Arg *FinalOutput = C.getArgs().getLastArg(options::OPT_o))
+    if (Arg *FinalOutput =
+            UseFinalOutput ? C.getArgs().getLastArg(options::OPT_o) : nullptr)
       return C.addResultFile(FinalOutput->getValue(), &JA);
   }
 
diff --git a/clang/lib/Driver/ToolChains/AMDGPU.cpp 
b/clang/lib/Driver/ToolChains/AMDGPU.cpp
index 5893f6f6b2915..b5ce9e0fda9a2 100644
--- a/clang/lib/Driver/ToolChains/AMDGPU.cpp
+++ b/clang/lib/Driver/ToolChains/AMDGPU.cpp
@@ -1282,6 +1282,7 @@ LTOKind AMDGPUToolChain::getLTOMode(const ArgList &Args,
                                     Action::OffloadKind Kind) const {
   if (getTriple().isAMDGCN() && getDriver().offloadDeviceOnly() &&
       !Args.hasFlag(options::OPT_fgpu_rdc, options::OPT_fno_gpu_rdc, false) &&
+      !ToolChain::needsProfileRT(Args) &&
       !Args.hasArg(options::OPT_foffload_lto, options::OPT_foffload_lto_EQ))
     return LTOK_None;
   return ToolChain::getLTOMode(Args, Kind);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 94f9a26aac39f..79905d902374b 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -9750,8 +9750,13 @@ static bool requiresProfileRT(unsigned ID) {
   switch (ID) {
   case options::OPT_fprofile_generate:
   case options::OPT_fprofile_generate_EQ:
+  case options::OPT_fcs_profile_generate:
+  case options::OPT_fcs_profile_generate_EQ:
   case options::OPT_fprofile_instr_generate:
   case options::OPT_fprofile_instr_generate_EQ:
+  case options::OPT_fcreate_profile:
+  case options::OPT_fprofile_generate_cold_function_coverage:
+  case options::OPT_fprofile_generate_cold_function_coverage_EQ:
   case options::OPT_fcoverage_mapping:
   case options::OPT_fno_coverage_mapping:
   case options::OPT_fcoverage_compilation_dir_EQ:
@@ -9822,8 +9827,16 @@ void LinkerWrapper::ConstructJob(Compilation &C, const 
JobAction &JA,
       OPT_fmultilib_flag,
       OPT_fprofile_generate,
       OPT_fprofile_generate_EQ,
+      OPT_fcs_profile_generate,
+      OPT_fcs_profile_generate_EQ,
       OPT_fprofile_instr_generate,
       OPT_fprofile_instr_generate_EQ,
+      OPT_fcreate_profile,
+      OPT_fprofile_generate_cold_function_coverage,
+      OPT_fprofile_generate_cold_function_coverage_EQ,
+      OPT_fno_profile_generate,
+      OPT_fno_profile_instr_generate,
+      OPT_noprofilelib,
       OPT_fcoverage_mapping,
       OPT_fno_coverage_mapping,
       OPT_fcoverage_compilation_dir_EQ,
@@ -9848,13 +9861,15 @@ void LinkerWrapper::ConstructJob(Compilation &C, const 
JobAction &JA,
     return TC.getVFS().exists(
         TC.getCompilerRT(Args, Name, ToolChain::FT_Static));
   };
-  auto ShouldForwardForToolChain = [&](Arg *A, const ToolChain &TC) {
+  auto ShouldForwardForToolChain = [&](Arg *A, const ToolChain &TC,
+                                       const ArgList &TCArgs) {
     unsigned ID = A->getOption().getID();
     // Don't forward profiling arguments if the toolchain doesn't support it.
     // Without this check using it on the host would result in linker errors.
     // Coverage mapping flags require -fprofile-instr-generate, so drop them
     // together to avoid a device cc1 diagnostic.
-    if (requiresProfileRT(ID) && !ToolChainHasRT(TC, "profile"))
+    if (requiresProfileRT(ID) && !TCArgs.hasArg(OPT_noprofilelib) &&
+        !ToolChainHasRT(TC, "profile"))
       return false;
     // Don't forward sanitizer arguments if the toolchain doesn't support it.
     // Without this check using it on the host would result in linker errors.
@@ -9864,13 +9879,13 @@ void LinkerWrapper::ConstructJob(Compilation &C, const 
JobAction &JA,
     return TC.HasNativeLLVMSupport() || ID != OPT_mllvm;
   };
   auto ShouldForward = [&](const llvm::DenseSet<unsigned> &Set, Arg *A,
-                           const ToolChain &TC) {
+                           const ToolChain &TC, const ArgList &TCArgs) {
     if (A->getOption().matches(OPT_v) && SuppressHIPNoRDCVerbose)
       return false;
     return (Set.contains(A->getOption().getID()) ||
             (A->getOption().getGroup().isValid() &&
              Set.contains(A->getOption().getGroup().getID()))) &&
-           ShouldForwardForToolChain(A, TC);
+           ShouldForwardForToolChain(A, TC, TCArgs);
   };
 
   ArgStringList CmdArgs;
@@ -9889,10 +9904,10 @@ void LinkerWrapper::ConstructJob(Compilation &C, const 
JobAction &JA,
       for (Arg *A : ToolChainArgs) {
         if (A->getOption().matches(OPT_Zlinker_input))
           LinkerArgs.emplace_back(A->getValue());
-        else if (ShouldForward(CompilerOptions, A, *TC)) {
+        else if (ShouldForward(CompilerOptions, A, *TC, ToolChainArgs)) {
           A->claim();
           A->render(Args, CompilerArgs);
-        } else if (ShouldForward(LinkerOptions, A, *TC)) {
+        } else if (ShouldForward(LinkerOptions, A, *TC, ToolChainArgs)) {
           A->claim();
           A->render(Args, LinkerArgs);
         }
@@ -9996,9 +10011,12 @@ void LinkerWrapper::ConstructJob(Compilation &C, const 
JobAction &JA,
         Twine("--device-compiler=--rocm-path=") + A->getValue()));
   }
 
-  // Construct the link job so we can wrap around it.
-  Linker->ConstructJob(C, JA, Output, Inputs, Args, LinkingOutput);
-  const auto &LinkCommand = C.getJobs().getJobs().back();
+  Command *LinkCommand = nullptr;
+  if (JA.getType() != types::TY_HIP_FATBIN) {
+    // Construct the link job so we can wrap around it.
+    Linker->ConstructJob(C, JA, Output, Inputs, Args, LinkingOutput);
+    LinkCommand = C.getJobs().getJobs().back().get();
+  }
 
   // Forward -Xoffload-{compiler,linker}<-triple> arguments to the linker
   // wrapper.
@@ -10066,8 +10084,10 @@ void LinkerWrapper::ConstructJob(Compilation &C, const 
JobAction &JA,
   }
 
   // Add the linker arguments to be forwarded by the wrapper.
-  CmdArgs.push_back(Args.MakeArgString(Twine("--linker-path=") +
-                                       LinkCommand->getExecutable()));
+  CmdArgs.push_back(Args.MakeArgString(
+      Twine("--linker-path=") +
+      (LinkCommand ? LinkCommand->getExecutable()
+                   : Args.MakeArgString(getToolChain().GetLinkerPath()))));
 
   // We use action type to differentiate two use cases of the linker wrapper.
   // TY_Image for normal linker wrapper work.
@@ -10080,6 +10100,7 @@ void LinkerWrapper::ConstructJob(Compilation &C, const 
JobAction &JA,
     for (auto Input : Inputs)
       CmdArgs.push_back(Input.getFilename());
   } else {
+    assert(LinkCommand && "expected link command for normal wrapper job");
     for (const char *LinkArg : LinkCommand->getArguments())
       CmdArgs.push_back(LinkArg);
   }
@@ -10106,8 +10127,14 @@ void LinkerWrapper::ConstructJob(Compilation &C, const 
JobAction &JA,
   const char *Exec =
       
Args.MakeArgString(getToolChain().GetProgramPath("clang-linker-wrapper"));
 
-  // Replace the executable and arguments of the link job with the
-  // wrapper.
-  LinkCommand->replaceExecutable(Exec);
-  LinkCommand->replaceArguments(CmdArgs);
+  if (LinkCommand) {
+    // Replace the executable and arguments of the link job with the
+    // wrapper.
+    LinkCommand->replaceExecutable(Exec);
+    LinkCommand->replaceArguments(CmdArgs);
+  } else {
+    C.addCommand(std::make_unique<Command>(JA, *this,
+                                           ResponseFileSupport::AtFileUTF8(),
+                                           Exec, CmdArgs, Inputs, Output));
+  }
 }
diff --git a/clang/test/Driver/hip-binding.hip 
b/clang/test/Driver/hip-binding.hip
index b0839021aa1d9..ea782bdc51173 100644
--- a/clang/test/Driver/hip-binding.hip
+++ b/clang/test/Driver/hip-binding.hip
@@ -57,24 +57,45 @@
 // RUN:        --offload-arch=gfx90a --offload-arch=gfx908 
--offload-device-only -c %s 2>&1 \
 // RUN: | FileCheck -check-prefix=MULTI-D-ONLY %s
 //      MULTI-D-ONLY: # "amdgcn-amd-amdhsa" - "clang", inputs: 
["[[INPUT:.+]]"], output: "[[GFX908:.+]]"
-// MULTI-D-ONLY-NEXT: # "amdgcn-amd-amdhsa" - "AMDGCN::Linker", inputs: 
["[[GFX908]]"], output: "[[GFX908_OUT:.+]]"
 // MULTI-D-ONLY-NEXT: # "amdgcn-amd-amdhsa" - "clang", inputs: ["[[INPUT]]"], 
output: "[[GFX90a:.+]]"
-// MULTI-D-ONLY-NEXT: # "amdgcn-amd-amdhsa" - "AMDGCN::Linker", inputs: 
["[[GFX90a]]"], output: "[[GFX90a_OUT:.+]]"
-// MULTI-D-ONLY-NEXT: # "amdgcn-amd-amdhsa" - "AMDGCN::Linker", inputs: 
["[[GFX908_OUT]]", "[[GFX90a_OUT]]"], output: "{{.+}}"
+// MULTI-D-ONLY-NEXT: # "amdgcn-amd-amdhsa" - "Offload::Packager", inputs: 
["[[GFX908]]", "[[GFX90a]]"], output: "[[PACKAGE:.+]]"
+// MULTI-D-ONLY-NEXT: # "amdgcn-amd-amdhsa" - "Offload::Linker", inputs: 
["[[PACKAGE]]"], output: "{{.+}}"
 //
-// RUN: not %clang -### --target=x86_64-linux-gnu --offload-new-driver 
-ccc-print-bindings -nogpulib -nogpuinc -emit-llvm \
+// RUN: %clang -### --target=x86_64-linux-gnu --offload-new-driver 
-ccc-print-bindings -nogpulib -nogpuinc \
+// RUN:        --no-gpu-bundle-output --gpu-bundle-output 
--offload-arch=gfx90a --offload-arch=gfx908 --offload-device-only -c %s 2>&1 \
+// RUN: | FileCheck -check-prefix=MULTI-D-ONLY %s
+//
+// RUN: %clang -### --target=x86_64-linux-gnu --offload-new-driver 
-ccc-print-bindings -nogpulib -nogpuinc \
+// RUN:        --no-gpu-bundle-output --offload-arch=gfx90a 
--offload-arch=gfx908 --offload-device-only -c %s 2>&1 \
+// RUN: | FileCheck -check-prefix=MULTI-D-ONLY-NO-BUNDLE %s
+//      MULTI-D-ONLY-NO-BUNDLE: # "amdgcn-amd-amdhsa" - "clang", inputs: 
["[[INPUT:.+]]"], output: "[[GFX908:.+]]"
+// MULTI-D-ONLY-NO-BUNDLE-NEXT: # "amdgcn-amd-amdhsa" - "clang", inputs: 
["[[INPUT]]"], output: "[[GFX90A:.+]]"
+// MULTI-D-ONLY-NO-BUNDLE-NEXT: # "amdgcn-amd-amdhsa" - "Offload::Packager", 
inputs: ["[[GFX908]]", "[[GFX90A]]"], output: "[[PACKAGE:.+]]"
+// MULTI-D-ONLY-NO-BUNDLE-NEXT: # "amdgcn-amd-amdhsa" - "Offload::Linker", 
inputs: ["[[PACKAGE]]"], output: "[[FATBIN:.+]]"
+// MULTI-D-ONLY-NO-BUNDLE-NEXT: # "amdgcn-amd-amdhsa" - "offload bundler", 
inputs: ["[[FATBIN]]"], outputs: ["a.out-hip-amdgcn-amd-amdhsa-gfx908", 
"a.out-hip-amdgcn-amd-amdhsa-gfx90a"]
+//
+// RUN: %clang -### --target=x86_64-linux-gnu --offload-new-driver 
-ccc-print-bindings -nogpulib -nogpuinc \
+// RUN:        --gpu-bundle-output --no-gpu-bundle-output 
--offload-arch=gfx90a --offload-arch=gfx908 --offload-device-only -c %s 2>&1 \
+// RUN: | FileCheck -check-prefix=MULTI-D-ONLY-NO-BUNDLE %s
+//
+// RUN: not %clang -### --target=x86_64-linux-gnu --offload-new-driver 
-ccc-print-bindings -nogpulib -nogpuinc \
 // RUN:        --no-gpu-bundle-output --offload-arch=gfx90a 
--offload-arch=gfx908 --offload-device-only -c -o %t %s 2>&1 \
 // RUN: | FileCheck -check-prefix=MULTI-D-ONLY-NO-BUNDLE-O %s
 // MULTI-D-ONLY-NO-BUNDLE-O: error: cannot specify -o when generating multiple 
output files
 
+// RUN: %clang -### --target=x86_64-linux-gnu -ccc-print-bindings -nogpulib 
-nogpuinc \
+// RUN:        --no-gpu-bundle-output --offload-arch=gfx90a 
--offload-device-only -c %s 2>&1 \
+// RUN: | FileCheck -check-prefix=SINGLE-D-ONLY-NO-BUNDLE %s
+// SINGLE-D-ONLY-NO-BUNDLE: # "amdgcn-amd-amdhsa" - "Offload::Linker", inputs: 
["[[PACKAGE:.+]]"], output: "[[FATBIN:.+]]"
+// SINGLE-D-ONLY-NO-BUNDLE-NEXT: # "amdgcn-amd-amdhsa" - "offload bundler", 
inputs: ["[[FATBIN]]"], outputs: ["a.out-hip-amdgcn-amd-amdhsa-gfx90a"]
+
 // RUN: %clang -### --target=x86_64-linux-gnu --offload-new-driver 
-ccc-print-bindings -nogpulib -nogpuinc \
 // RUN:        --gpu-bundle-output --offload-arch=gfx90a --offload-arch=gfx908 
--offload-device-only -c -o a.out %s 2>&1 \
 // RUN: | FileCheck -check-prefix=MULTI-D-ONLY-O %s
 //      MULTI-D-ONLY-O: "amdgcn-amd-amdhsa" - "clang", inputs: 
["[[INPUT:.+]]"], output: "[[GFX908_OBJ:.+]]"
-// MULTI-D-ONLY-O-NEXT: "amdgcn-amd-amdhsa" - "AMDGCN::Linker", inputs: 
["[[GFX908_OBJ]]"], output: "[[GFX908:.+]]"
 // MULTI-D-ONLY-O-NEXT: "amdgcn-amd-amdhsa" - "clang", inputs: ["[[INPUT]]"], 
output: "[[GFX90A_OBJ:.+]]"
-// MULTI-D-ONLY-O-NEXT: "amdgcn-amd-amdhsa" - "AMDGCN::Linker", inputs: 
["[[GFX90A_OBJ]]"], output: "[[GFX90A:.+]]"
-// MULTI-D-ONLY-O-NEXT: "amdgcn-amd-amdhsa" - "AMDGCN::Linker", inputs: 
["[[GFX908]]", "[[GFX90A]]"], output: "a.out"
+// MULTI-D-ONLY-O-NEXT: "amdgcn-amd-amdhsa" - "Offload::Packager", inputs: 
["[[GFX908_OBJ]]", "[[GFX90A_OBJ]]"], output: "[[PACKAGE:.+]]"
+// MULTI-D-ONLY-O-NEXT: "amdgcn-amd-amdhsa" - "Offload::Linker", inputs: 
["[[PACKAGE]]"], output: "a.out"
 
 // RUN: %clang -### --target=x86_64-linux-gnu --offload-new-driver 
-ccc-print-bindings -nogpulib -nogpuinc -emit-llvm \
 // RUN:        --gpu-bundle-output --offload-arch=gfx90a --offload-arch=gfx908 
--offload-device-only -c -o a.out %s 2>&1 \
diff --git a/clang/test/Driver/hip-phases.hip b/clang/test/Driver/hip-phases.hip
index 8fb58dc98cefc..9165a566c8366 100644
--- a/clang/test/Driver/hip-phases.hip
+++ b/clang/test/Driver/hip-phases.hip
@@ -701,13 +701,13 @@
 // SPIRV-ONLY-NEXT: 2: compiler, {1}, ir, (device-hip, gfx1030)
 // SPIRV-ONLY-NEXT: 3: backend, {2}, assembler, (device-hip, gfx1030)
 // SPIRV-ONLY-NEXT: 4: assembler, {3}, object, (device-hip, gfx1030)
-// SPIRV-ONLY-NEXT: 5: linker, {4}, image, (device-hip, gfx1030)
-// SPIRV-ONLY-NEXT: 6: offload, "device-hip (amdgcn-amd-amdhsa:gfx1030)" {5}, 
image
-// SPIRV-ONLY-NEXT: 7: input, "[[INPUT]]", hip, (device-hip, amdgcnspirv)
-// SPIRV-ONLY-NEXT: 8: preprocessor, {7}, hip-cpp-output, (device-hip, 
amdgcnspirv)
-// SPIRV-ONLY-NEXT: 9: compiler, {8}, ir, (device-hip, amdgcnspirv)
-// SPIRV-ONLY-NEXT: 10: backend, {9}, lto-bc, (device-hip, amdgcnspirv)
-// SPIRV-ONLY-NEXT: 11: linker, {10}, image, (device-hip, amdgcnspirv)
-// SPIRV-ONLY-NEXT: 12: offload, "device-hip (spirv64-amd-amdhsa:amdgcnspirv)" 
{11}, image
-// SPIRV-ONLY-NEXT: 13: linker, {6, 12}, hip-fatbin, (device-hip)
+// SPIRV-ONLY-NEXT: 5: offload, "device-hip (amdgcn-amd-amdhsa:gfx1030)" {4}, 
object
+// SPIRV-ONLY-NEXT: 6: input, "[[INPUT]]", hip, (device-hip, amdgcnspirv)
+// SPIRV-ONLY-NEXT: 7: preprocessor, {6}, hip-cpp-output, (device-hip, 
amdgcnspirv)
+// SPIRV-ONLY-NEXT: 8: compiler, {7}, ir, (device-hip, amdgcnspirv)
+// SPIRV-ONLY-NEXT: 9: backend, {8}, lto-bc, (device-hip, amdgcnspirv)
+// SPIRV-ONLY-NEXT: 10: linker, {9}, image, (device-hip, amdgcnspirv)
+// SPIRV-ONLY-NEXT: 11: offload, "device-hip (spirv64-amd-amdhsa:amdgcnspirv)" 
{10}, image
+// SPIRV-ONLY-NEXT: 12: llvm-offload-binary, {5, 11}, image, (device-hip)
+// SPIRV-ONLY-NEXT: 13: clang-linker-wrapper, {12}, hip-fatbin, (device-hip)
 // SPIRV-ONLY-NEXT: 14: offload, "device-hip (amdgcn-amd-amdhsa)" {13}, none
diff --git a/clang/test/Driver/hip-profile-rocm-runtime.hip 
b/clang/test/Driver/hip-profile-rocm-runtime.hip
index fc82db4fc13c0..03190e8dca5d8 100644
--- a/clang/test/Driver/hip-profile-rocm-runtime.hip
+++ b/clang/test/Driver/hip-profile-rocm-runtime.hip
@@ -3,9 +3,10 @@
 
 // Build a fake resource dir containing both the base profile runtime and the
 // ROCm device-profile runtime so the driver's existence check passes.
-// RUN: rm -rf %t && mkdir -p %t/lib/x86_64-unknown-linux
+// RUN: rm -rf %t && mkdir -p %t/lib/x86_64-unknown-linux 
%t/lib/amdgcn-amd-amdhsa
 // RUN: touch %t/lib/x86_64-unknown-linux/libclang_rt.profile.a
 // RUN: touch %t/lib/x86_64-unknown-linux/libclang_rt.profile_rocm.a
+// RUN: touch %t/lib/amdgcn-amd-amdhsa/libclang_rt.profile.a
 // RUN: touch %t.o
 
 // HIP host link with PGO links clang_rt.profile_rocm.
@@ -31,3 +32,65 @@
 // RUN:   | FileCheck -check-prefix=HOST-PGO %s
 // HOST-PGO: "{{.*}}libclang_rt.profile.a"
 // HOST-PGO-NOT: libclang_rt.profile_rocm.a
+
+// HIP device-only code object builds use the linker wrapper so its inner Clang
+// invocation links the AMDGPU profile runtime.
+// RUN: %clang -### -x hip --cuda-device-only \
+// RUN:   --target=x86_64-unknown-linux --offload-arch=gfx906 \
+// RUN:   -fprofile-generate -resource-dir=%t -nogpuinc -nogpulib %s 2>&1 \
+// RUN:   | FileCheck -check-prefix=HIP-DEVICE-PGO %s
+// HIP-DEVICE-PGO-DAG: "-emit-llvm-bc"
+// HIP-DEVICE-PGO-DAG: "-flto=full"
+// HIP-DEVICE-PGO: "{{.*}}llvm-offload-binary"
+// HIP-DEVICE-PGO: "{{.*}}clang-linker-wrapper"
+// HIP-DEVICE-PGO-SAME: 
"--device-compiler=amdgcn-amd-amdhsa=-fprofile-generate"
+// HIP-DEVICE-PGO-SAME: "--emit-fatbin-only"
+
+// Unbundled code object output uses the linker wrapper's fat binary mode, then
+// unbundles that fat binary into raw images.
+// RUN: %clang -### -x hip --cuda-device-only --no-gpu-bundle-output \
+// RUN:   --target=x86_64-unknown-linux --offload-arch=gfx906 \
+// RUN:   -fprofile-generate -resource-dir=%t -nogpuinc -nogpulib %s 2>&1 \
+// RUN:   | FileCheck -check-prefix=HIP-DEVICE-PGO-RAW %s
+// HIP-DEVICE-PGO-RAW: "{{.*}}llvm-offload-binary"
+// HIP-DEVICE-PGO-RAW: "{{.*}}clang-linker-wrapper"
+// HIP-DEVICE-PGO-RAW-SAME: 
"--device-compiler=amdgcn-amd-amdhsa=-fprofile-generate"
+// HIP-DEVICE-PGO-RAW-SAME: "--emit-fatbin-only"
+// HIP-DEVICE-PGO-RAW: "{{.*}}clang-offload-bundler"
+// HIP-DEVICE-PGO-RAW-SAME: "-type=hipfb"
+// HIP-DEVICE-PGO-RAW-SAME: "-output=a.out-hip-amdgcn-amd-amdhsa-gfx906"
+// HIP-DEVICE-PGO-RAW-SAME: "-unbundle"
+
+// Other profile generation modes are also forwarded to the linker wrapper.
+// RUN: %clang -### -x hip --cuda-device-only \
+// RUN:   --target=x86_64-unknown-linux --offload-arch=gfx906 \
+// RUN:   -fcs-profile-generate -resource-dir=%t -nogpuinc -nogpulib %s 2>&1 \
+// RUN:   | FileCheck -check-prefix=HIP-DEVICE-CSPGO %s
+// HIP-DEVICE-CSPGO: "{{.*}}clang-linker-wrapper"
+// HIP-DEVICE-CSPGO-SAME: 
"--device-compiler=amdgcn-amd-amdhsa=-fcs-profile-generate"
+
+// RUN: %clang -### -x hip --cuda-device-only \
+// RUN:   --target=x86_64-unknown-linux --offload-arch=gfx906 \
+// RUN:   -fcreate-profile -resource-dir=%t -nogpuinc -nogpulib %s 2>&1 \
+// RUN:   | FileCheck -check-prefix=HIP-DEVICE-CREATE-PROFILE %s
+// HIP-DEVICE-CREATE-PROFILE: "{{.*}}clang-linker-wrapper"
+// HIP-DEVICE-CREATE-PROFILE-SAME: 
"--device-compiler=amdgcn-amd-amdhsa=-fcreate-profile"
+
+// RUN: %clang -### -x hip --cuda-device-only \
+// RUN:   --target=x86_64-unknown-linux --offload-arch=gfx906 \
+// RUN:   -fprofile-generate-cold-function-coverage -resource-dir=%t \
+// RUN:   -nogpuinc -nogpulib %s 2>&1 \
+// RUN:   | FileCheck -check-prefix=HIP-DEVICE-COLD-PROFILE %s
+// HIP-DEVICE-COLD-PROFILE: "{{.*}}clang-linker-wrapper"
+// HIP-DEVICE-COLD-PROFILE-SAME: 
"--device-compiler=amdgcn-amd-amdhsa=-fprofile-generate-cold-function-coverage"
+
+// -noprofilelib must reach the inner Clang together with the profile option.
+// RUN: rm %t/lib/amdgcn-amd-amdhsa/libclang_rt.profile.a
+// RUN: %clang -### -x hip --cuda-device-only \
+// RUN:   --target=x86_64-unknown-linux --offload-arch=gfx906 \
+// RUN:   -fprofile-generate -noprofilelib -resource-dir=%t \
+// RUN:   -nogpuinc -nogpulib %s 2>&1 \
+// RUN:   | FileCheck -check-prefix=HIP-DEVICE-NOPROFILELIB %s
+// HIP-DEVICE-NOPROFILELIB: "{{.*}}clang-linker-wrapper"
+// HIP-DEVICE-NOPROFILELIB-SAME: 
"--device-compiler=amdgcn-amd-amdhsa=-fprofile-generate"
+// HIP-DEVICE-NOPROFILELIB-SAME: 
"--device-compiler=amdgcn-amd-amdhsa=-noprofilelib"
diff --git a/clang/test/Driver/hip-toolchain-device-only.hip 
b/clang/test/Driver/hip-toolchain-device-only.hip
index c0621854f17ce..279784e5305e5 100644
--- a/clang/test/Driver/hip-toolchain-device-only.hip
+++ b/clang/test/Driver/hip-toolchain-device-only.hip
@@ -6,18 +6,22 @@
 // CHECK-NOT: error:
 
 // CHECK: [[CLANG:".*clang.*"]] "-cc1"{{.*}} "-triple" "amdgcn-amd-amdhsa"
+// CHECK-SAME: "-emit-obj"
 // CHECK-SAME: "-fcuda-is-device"
 // CHECK-SAME: "-target-cpu" "gfx803"
-// CHECK-SAME: {{.*}} "-o" [[OBJ_DEV_A_803:".*o"]] "-x" "hip"
-
-// CHECK: [[LLD: ".*lld.*"]] "-flavor" "gnu" "-m" "elf64_amdgpu" 
"--no-undefined" "-shared"
-// CHECK-SAME: "-o" "[[IMG_DEV_A_803:.*out]]" [[OBJ_DEV_A_803]]
+// CHECK-SAME: {{.*}} "-o" "[[OBJ_DEV_A_803:[^"]+\.o]]" "-x" "hip"
 
 // CHECK: [[CLANG:".*clang.*"]] "-cc1"{{.*}} "-triple" "amdgcn-amd-amdhsa"
 // CHECK-SAME: "-emit-obj"
 // CHECK-SAME: "-fcuda-is-device"
 // CHECK-SAME: "-target-cpu" "gfx900"
-// CHECK-SAME: {{.*}} "-o" [[OBJ_DEV_A_900:".*o"]] "-x" "hip"
+// CHECK-SAME: {{.*}} "-o" "[[OBJ_DEV_A_900:[^"]+\.o]]" "-x" "hip"
+
+// CHECK: [[PACKAGER:"[^"]*llvm-offload-binary[^"]*"]] "-o" "[[PACKAGE:[^"]+]]"
+// CHECK-SAME: 
"--image=file=[[OBJ_DEV_A_803]],triple=amdgcn-amd-amdhsa,arch=gfx803,kind=hip"
+// CHECK-SAME: 
"--image=file=[[OBJ_DEV_A_900]],triple=amdgcn-amd-amdhsa,arch=gfx900,kind=hip"
 
-// CHECK: [[LLD]] "-flavor" "gnu" "-m" "elf64_amdgpu" "--no-undefined" 
"-shared"
-// CHECK-SAME: "-o" "[[IMG_DEV_A_900:.*out]]" [[OBJ_DEV_A_900]]
+// CHECK: [[WRAPPER:"[^"]*clang-linker-wrapper[^"]*"]]
+// CHECK-SAME: "--should-extract=gfx803" "--should-extract=gfx900"
+// CHECK-SAME: "--host-triple=x86_64-unknown-linux-gnu"
+// CHECK-SAME: "--emit-fatbin-only" "-o" "{{[^"]+}}" "[[PACKAGE]]"
diff --git 
a/clang/test/OffloadTools/clang-linker-wrapper/linker-wrapper-hip-no-rdc.c 
b/clang/test/OffloadTools/clang-linker-wrapper/linker-wrapper-hip-no-rdc.c
index 44d0c1e2b7b51..f97a2bba5b31b 100644
--- a/clang/test/OffloadTools/clang-linker-wrapper/linker-wrapper-hip-no-rdc.c
+++ b/clang/test/OffloadTools/clang-linker-wrapper/linker-wrapper-hip-no-rdc.c
@@ -55,3 +55,18 @@ __attribute__((visibility("protected"), used)) int x;
 // RUN: test -s %t.gfx9-4-generic-xnack+.co
 // RUN: test -f %t.gfx1200.co
 // RUN: test -s %t.gfx1200.co
+
+// Clang passes the exact output filename for each device image.
+// RUN: rm -rf %t.dir && mkdir %t.dir
+// RUN: cd %t.dir && %clang -x hip --cuda-device-only \
+// RUN:   --no-gpu-bundle-output --offload-arch=gfx900 \
+// RUN:   --offload-arch=gfx1200 -nogpuinc -nogpulib %s
+// RUN: test -f 
%t.dir/linker-wrapper-hip-no-rdc-hip-amdgcn-amd-amdhsa-gfx900.out
+// RUN: test -f 
%t.dir/linker-wrapper-hip-no-rdc-hip-amdgcn-amd-amdhsa-gfx1200.out
+
+// The implicit filename keeps its target suffix for one architecture too.
+// RUN: rm -rf %t.single.dir && mkdir %t.single.dir
+// RUN: cd %t.single.dir && %clang -x hip --cuda-device-only \
+// RUN:   --no-gpu-bundle-output --offload-arch=gfx1200 \
+// RUN:   -nogpuinc -nogpulib %s
+// RUN: test -f 
%t.single.dir/linker-wrapper-hip-no-rdc-hip-amdgcn-amd-amdhsa-gfx1200.out

>From 02e48ac6639db5808715fce82e2957f9ef651022 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <[email protected]>
Date: Fri, 14 Aug 2026 12:41:20 -0400
Subject: [PATCH 2/4] [clang] Simplify offload unbundling action constructor

---
 clang/include/clang/Driver/Action.h | 4 ++--
 clang/lib/Driver/Action.cpp         | 7 +++----
 2 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/Driver/Action.h 
b/clang/include/clang/Driver/Action.h
index 018e267bec137..5148a2e2b259b 100644
--- a/clang/include/clang/Driver/Action.h
+++ b/clang/include/clang/Driver/Action.h
@@ -618,8 +618,8 @@ class OffloadUnbundlingJobAction final : public JobAction {
 
 public:
   // Offloading unbundling usually doesn't change the type of output.
-  OffloadUnbundlingJobAction(Action *Input);
-  OffloadUnbundlingJobAction(Action *Input, types::ID OutputType);
+  OffloadUnbundlingJobAction(Action *Input,
+                             types::ID OutputType = types::TY_Nothing);
 
   /// Register information about a dependent action.
   void registerDependentActionInfo(const ToolChain *TC, BoundArch BA,
diff --git a/clang/lib/Driver/Action.cpp b/clang/lib/Driver/Action.cpp
index 3b71bfb7b53b1..6b947d1edd92a 100644
--- a/clang/lib/Driver/Action.cpp
+++ b/clang/lib/Driver/Action.cpp
@@ -437,12 +437,11 @@ 
OffloadBundlingJobAction::OffloadBundlingJobAction(ActionList &Inputs)
 
 void OffloadUnbundlingJobAction::anchor() {}
 
-OffloadUnbundlingJobAction::OffloadUnbundlingJobAction(Action *Input)
-    : JobAction(OffloadUnbundlingJobClass, Input, Input->getType()) {}
-
 OffloadUnbundlingJobAction::OffloadUnbundlingJobAction(Action *Input,
                                                        types::ID OutputType)
-    : JobAction(OffloadUnbundlingJobClass, Input, OutputType) {}
+    : JobAction(OffloadUnbundlingJobClass, Input,
+                OutputType == types::TY_Nothing ? Input->getType()
+                                                : OutputType) {}
 
 void OffloadPackagerJobAction::anchor() {}
 

>From c4789775279c6df87092238a872a9545ab2a0506 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <[email protected]>
Date: Fri, 14 Aug 2026 13:41:10 -0400
Subject: [PATCH 3/4] [clang] Clean up linker wrapper driver handling

---
 clang/include/clang/Driver/Compilation.h | 14 +++++++-------
 clang/lib/Driver/Compilation.cpp         |  5 ++---
 clang/lib/Driver/ToolChains/Clang.cpp    |  7 +++----
 3 files changed, 12 insertions(+), 14 deletions(-)

diff --git a/clang/include/clang/Driver/Compilation.h 
b/clang/include/clang/Driver/Compilation.h
index 01eff0af7852f..1991a07daa9a3 100644
--- a/clang/include/clang/Driver/Compilation.h
+++ b/clang/include/clang/Driver/Compilation.h
@@ -101,11 +101,11 @@ class Compilation {
   llvm::opt::ArgStringList TempFiles;
 
   /// Result files which should be removed on failure.
-  ActionFileMap ResultFiles;
+  ArgStringMap ResultFiles;
 
   /// Result files which are generated correctly on failure, and which should
   /// only be removed if we crash.
-  ActionFileMap FailureResultFiles;
+  ArgStringMap FailureResultFiles;
 
   /// -ftime-trace result files.
   ArgStringMap TimeTraceFiles;
@@ -227,9 +227,9 @@ class Compilation {
   llvm::opt::ArgStringList &getTempFiles() { return TempFiles; }
   const llvm::opt::ArgStringList &getTempFiles() const { return TempFiles; }
 
-  const ActionFileMap &getResultFiles() const { return ResultFiles; }
+  const ArgStringMap &getResultFiles() const { return ResultFiles; }
 
-  const ActionFileMap &getFailureResultFiles() const {
+  const ArgStringMap &getFailureResultFiles() const {
     return FailureResultFiles;
   }
 
@@ -266,14 +266,14 @@ class Compilation {
   /// addResultFile - Add a file to remove on failure, and returns its
   /// argument.
   const char *addResultFile(const char *Name, const JobAction *JA) {
-    ResultFiles[JA].push_back(Name);
+    ResultFiles[JA] = Name;
     return Name;
   }
 
   /// addFailureResultFile - Add a file to remove if we crash, and returns its
   /// argument.
   const char *addFailureResultFile(const char *Name, const JobAction *JA) {
-    FailureResultFiles[JA].push_back(Name);
+    FailureResultFiles[JA] = Name;
     return Name;
   }
 
@@ -304,7 +304,7 @@ class Compilation {
   /// JobAction.  Otherwise, delete all files in the map.
   /// \param IssueErrors - Report failures as errors.
   /// \return Whether all files were removed successfully.
-  bool CleanupFileMap(const ActionFileMap &Files, const JobAction *JA,
+  bool CleanupFileMap(const ArgStringMap &Files, const JobAction *JA,
                       bool IssueErrors = false) const;
 
   /// ExecuteCommand - Execute an actual command.
diff --git a/clang/lib/Driver/Compilation.cpp b/clang/lib/Driver/Compilation.cpp
index f3fa01fa9e104..d644071669a3d 100644
--- a/clang/lib/Driver/Compilation.cpp
+++ b/clang/lib/Driver/Compilation.cpp
@@ -149,7 +149,7 @@ bool Compilation::CleanupFileList(const 
llvm::opt::ArgStringList &Files,
   return Success;
 }
 
-bool Compilation::CleanupFileMap(const ActionFileMap &Files,
+bool Compilation::CleanupFileMap(const ArgStringMap &Files,
                                  const JobAction *JA, bool IssueErrors) const {
   bool Success = true;
   for (const auto &File : Files) {
@@ -157,8 +157,7 @@ bool Compilation::CleanupFileMap(const ActionFileMap &Files,
     // Otherwise, delete all files in the map.
     if (JA && File.first != JA)
       continue;
-    for (const char *Filename : File.second)
-      Success &= CleanupFile(Filename, IssueErrors);
+    Success &= CleanupFile(File.second, IssueErrors);
   }
   return Success;
 }
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 79905d902374b..8432c2770de0e 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -10011,11 +10011,13 @@ void LinkerWrapper::ConstructJob(Compilation &C, 
const JobAction &JA,
         Twine("--device-compiler=--rocm-path=") + A->getValue()));
   }
 
+  const char *LinkerPath = Args.MakeArgString(getToolChain().GetLinkerPath());
   Command *LinkCommand = nullptr;
   if (JA.getType() != types::TY_HIP_FATBIN) {
     // Construct the link job so we can wrap around it.
     Linker->ConstructJob(C, JA, Output, Inputs, Args, LinkingOutput);
     LinkCommand = C.getJobs().getJobs().back().get();
+    LinkerPath = LinkCommand->getExecutable();
   }
 
   // Forward -Xoffload-{compiler,linker}<-triple> arguments to the linker
@@ -10084,10 +10086,7 @@ void LinkerWrapper::ConstructJob(Compilation &C, const 
JobAction &JA,
   }
 
   // Add the linker arguments to be forwarded by the wrapper.
-  CmdArgs.push_back(Args.MakeArgString(
-      Twine("--linker-path=") +
-      (LinkCommand ? LinkCommand->getExecutable()
-                   : Args.MakeArgString(getToolChain().GetLinkerPath()))));
+  CmdArgs.push_back(Args.MakeArgString(Twine("--linker-path=") + LinkerPath));
 
   // We use action type to differentiate two use cases of the linker wrapper.
   // TY_Image for normal linker wrapper work.

>From 3046c1dab1cadcb33930f06374d2fbef9fa2e364 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <[email protected]>
Date: Fri, 14 Aug 2026 14:58:29 -0400
Subject: [PATCH 4/4] [clang] Simplify HIP device unbundle target tracking

---
 clang/lib/Driver/Driver.cpp | 22 +++++++++-------------
 1 file changed, 9 insertions(+), 13 deletions(-)

diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 9efaf2277d461..6557ca4d49e8e 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -5075,6 +5075,8 @@ Driver::BuildOffloadingActions(Compilation &C, 
llvm::opt::DerivedArgList &Args,
     return HostAction;
 
   ActionList OffloadActions;
+  SmallVector<OffloadUnbundlingJobAction::DependentActionInfo, 6>
+      HIPUnbundleTargets;
   OffloadAction::DeviceDependences DDeps;
   bool HIPDeviceOnlyNeedsLink = false;
 
@@ -5205,6 +5207,9 @@ Driver::BuildOffloadingActions(Compilation &C, 
llvm::opt::DerivedArgList &Args,
                           false))
           DDep.add(*Input, *TCAndArch->first, TCAndArch->second, Kind);
       OffloadActions.push_back(C.MakeAction<OffloadAction>(DDep, 
A->getType()));
+      if (Kind == Action::OFK_HIP)
+        HIPUnbundleTargets.push_back(
+            {TCAndArch->first, TCAndArch->second, Kind});
 
       ++TCAndArch;
     }
@@ -5262,19 +5267,10 @@ Driver::BuildOffloadingActions(Compilation &C, 
llvm::opt::DerivedArgList &Args,
       if (offloadDeviceOnly() && !ShouldBundleHIP) {
         auto *Unbundler = C.MakeAction<OffloadUnbundlingJobAction>(
             DeviceOutputAction, types::TY_Image);
-        for (Action *A : OffloadActions) {
-          const ToolChain *DeviceTC = nullptr;
-          BoundArch DeviceArch;
-          cast<OffloadAction>(A)->doOnEachDeviceDependence(
-              [&](Action *, const ToolChain *TC, BoundArch BA) {
-                assert(!DeviceTC && "expected one device dependence");
-                DeviceTC = TC;
-                DeviceArch = BA;
-              });
-          assert(DeviceTC && "expected a device toolchain");
-          Unbundler->registerDependentActionInfo(DeviceTC, DeviceArch,
-                                                 Action::OFK_HIP);
-        }
+        for (const auto &Info : HIPUnbundleTargets)
+          Unbundler->registerDependentActionInfo(Info.DependentToolChain,
+                                                 Info.DependentBoundArch,
+                                                 Info.DependentOffloadKind);
         DeviceOutputAction = Unbundler;
       }
     }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to