[clang] [HIP] Use linker wrapper for device-only code object links (PR #211675)
Yaxun Liu via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 14 07:46:02 PDT 2026
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" <yaxun.liu at amd.com>
Date: Fri, 14 Aug 2026 10:25:03 -0400
Subject: [PATCH] [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
More information about the cfe-commits
mailing list