[clang] [HIP] Use linker wrapper for device-only code object links (PR #211675)
Yaxun Liu via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 28 18:56:00 PDT 2026
https://github.com/yxsamliu updated https://github.com/llvm/llvm-project/pull/211675
>From 63f63083dc448731be8c6417f9314c524e8ff09c Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Tue, 28 Jul 2026 12:56:50 -0400
Subject: [PATCH] [HIP] Use linker wrapper for device-only code object links
HIP device-only code object builds used a separate AMDGPU linker path.
This bypassed compiler options and runtime library handling in
clang-linker-wrapper, including the device profile runtime.
Package device inputs and route final links through
clang-linker-wrapper. Default output uses the existing fat binary mode.
Add a raw image mode for `--no-gpu-bundle-output`, and keep LTO enabled
when the device profile runtime is required.
---
clang/include/clang/Driver/Action.h | 21 +++
clang/include/clang/Driver/Compilation.h | 15 +-
clang/include/clang/Driver/Driver.h | 6 +-
clang/include/clang/Driver/Job.h | 7 +
clang/include/clang/Driver/Util.h | 4 +
clang/lib/Driver/Compilation.cpp | 8 +-
clang/lib/Driver/Driver.cpp | 155 ++++++++++++------
clang/lib/Driver/ToolChains/AMDGPU.cpp | 1 +
clang/lib/Driver/ToolChains/Clang.cpp | 77 ++++++++-
clang/lib/Driver/ToolChains/Clang.h | 10 ++
clang/test/Driver/hip-binding.hip | 33 +++-
clang/test/Driver/hip-phases.hip | 18 +-
.../test/Driver/hip-profile-rocm-runtime.hip | 60 ++++++-
.../test/Driver/hip-toolchain-device-only.hip | 18 +-
.../linker-wrapper-hip-no-rdc.c | 69 ++++++++
.../ClangLinkerWrapper.cpp | 98 ++++++++---
.../clang-linker-wrapper/LinkerWrapperOpts.td | 10 ++
17 files changed, 494 insertions(+), 116 deletions(-)
diff --git a/clang/include/clang/Driver/Action.h b/clang/include/clang/Driver/Action.h
index 306db1be15910..a943e03945742 100644
--- a/clang/include/clang/Driver/Action.h
+++ b/clang/include/clang/Driver/Action.h
@@ -650,9 +650,30 @@ class OffloadPackagerJobAction : public JobAction {
class LinkerWrapperJobAction : public JobAction {
void anchor() override;
+public:
+ struct DeviceOutputInfo final {
+ const ToolChain *DeviceToolChain = nullptr;
+ BoundArch DeviceBoundArch;
+
+ DeviceOutputInfo(const ToolChain *DeviceToolChain,
+ BoundArch DeviceBoundArch)
+ : DeviceToolChain(DeviceToolChain), DeviceBoundArch(DeviceBoundArch) {}
+ };
+
+private:
+ SmallVector<DeviceOutputInfo, 4> DeviceOutputInfoArray;
+
public:
LinkerWrapperJobAction(ActionList &Inputs, types::ID Type);
+ void registerDeviceOutputInfo(const ToolChain *TC, BoundArch BA) {
+ DeviceOutputInfoArray.emplace_back(TC, BA);
+ }
+
+ ArrayRef<DeviceOutputInfo> getDeviceOutputInfo() const {
+ return DeviceOutputInfoArray;
+ }
+
static bool classof(const Action *A) {
return A->getKind() == LinkerWrapperJobClass;
}
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/Job.h b/clang/include/clang/Driver/Job.h
index 03779be5b5a6a..6b104b854c478 100644
--- a/clang/include/clang/Driver/Job.h
+++ b/clang/include/clang/Driver/Job.h
@@ -238,6 +238,13 @@ class Command {
void replaceExecutable(const char *Exe) { Executable = Exe; }
+ void replaceOutputFilenames(ArrayRef<InputInfo> Outputs) {
+ OutputFilenames.clear();
+ for (const InputInfo &Output : Outputs)
+ if (Output.isFilename())
+ OutputFilenames.emplace_back(Output.getFilename());
+ }
+
const char *getExecutable() const { return Executable; }
const llvm::opt::ArgStringList &getArguments() const { return Arguments; }
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/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..3813b48000d5e 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,35 +5236,49 @@ 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,
+ 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};
+ types::ID WrapperOutput = offloadDeviceOnly() && !ShouldBundleHIP
+ ? types::TY_Image
+ : types::TY_HIP_FATBIN;
+ DeviceOutputAction =
+ C.MakeAction<LinkerWrapperJobAction>(WrapperInputs, WrapperOutput);
+ if (WrapperOutput == types::TY_Image) {
+ auto *WrapperAction = cast<LinkerWrapperJobAction>(DeviceOutputAction);
+ 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");
+ WrapperAction->registerDeviceOutputInfo(DeviceTC, DeviceArch);
+ }
+ }
+ }
+ DDep.add(*DeviceOutputAction,
*C.getOffloadToolChains<Action::OFK_HIP>().first->second,
/*BA=*/{}, Action::OFK_HIP);
} else {
@@ -5535,6 +5563,12 @@ void Driver::BuildJobs(Compilation &C) const {
if (FinalOutput) {
unsigned NumOutputs = 0;
unsigned NumIfsOutputs = 0;
+ auto CountActionOutputs = [](const Action *A) {
+ const auto *LWA = dyn_cast<LinkerWrapperJobAction>(A);
+ return LWA && !LWA->getDeviceOutputInfo().empty()
+ ? static_cast<unsigned>(LWA->getDeviceOutputInfo().size())
+ : 1u;
+ };
for (const Action *A : C.getActions()) {
// The actions below do not increase the number of outputs.
if (A->getKind() == clang::driver::Action::BinaryAnalyzeJobClass ||
@@ -5556,11 +5590,13 @@ void Driver::BuildJobs(Compilation &C) const {
0 == NumIfsOutputs++) ||
(A->getKind() == Action::BindArchClass && A->getInputs().size() &&
A->getInputs().front()->getKind() == Action::IfsMergeJobClass)))
- ++NumOutputs;
+ NumOutputs += CountActionOutputs(A);
else if (A->getKind() == Action::OffloadClass &&
A->getType() == types::TY_Nothing &&
- !C.getArgs().hasArg(options::OPT_fsyntax_only))
- NumOutputs += A->size();
+ !C.getArgs().hasArg(options::OPT_fsyntax_only)) {
+ for (const Action *Input : A->getInputs())
+ NumOutputs += CountActionOutputs(Input);
+ }
}
if (NumOutputs > 1) {
@@ -6336,7 +6372,7 @@ 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
@@ -6361,7 +6397,7 @@ InputInfoList Driver::BuildJobsForActionNoCache(
OffloadingPrefix),
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.
@@ -6386,6 +6422,23 @@ InputInfoList Driver::BuildJobsForActionNoCache(
assert(CachedResults.find(ActionTC) != CachedResults.end() &&
"Result does not exist??");
Result = CachedResults[ActionTC].front();
+ } else if (auto *LWA = dyn_cast<LinkerWrapperJobAction>(JA);
+ LWA && !LWA->getDeviceOutputInfo().empty()) {
+ ArrayRef<LinkerWrapperJobAction::DeviceOutputInfo> OutputInfo =
+ LWA->getDeviceOutputInfo();
+ for (const auto &Info : OutputInfo) {
+ std::string OffloadingPrefix = Action::GetOffloadingFileNamePrefix(
+ Action::OFK_HIP, Info.DeviceToolChain->getTripleString(),
+ /*CreatePrefixForHost=*/false);
+ MultipleResults.emplace_back(
+ LWA,
+ GetNamedOutputPath(C, *LWA, BaseInput, Info.DeviceBoundArch,
+ /*AtTopLevel=*/true, /*MultipleArchs=*/true,
+ OffloadingPrefix,
+ /*UseFinalOutput=*/OutputInfo.size() == 1),
+ BaseInput);
+ }
+ Result = MultipleResults.front();
} else if (JA->getType() == types::TY_Nothing)
Result = {InputInfo(A, BaseInput)};
else {
@@ -6412,24 +6465,26 @@ 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);
}
+ if (isa<LinkerWrapperJobAction>(JA) && !MultipleResults.empty())
+ return MultipleResults;
return {Result};
}
@@ -6547,7 +6602,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 +6634,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..d1bddc840dd55 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:
@@ -9781,7 +9786,24 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
const InputInfoList &Inputs,
const ArgList &Args,
const char *LinkingOutput) const {
+ constructJob(C, JA, InputInfoList{Output}, Inputs, Args, LinkingOutput);
+}
+
+void LinkerWrapper::ConstructJobMultipleOutputs(
+ Compilation &C, const JobAction &JA, const InputInfoList &Outputs,
+ const InputInfoList &Inputs, const ArgList &Args,
+ const char *LinkingOutput) const {
+ constructJob(C, JA, Outputs, Inputs, Args, LinkingOutput);
+}
+
+void LinkerWrapper::constructJob(Compilation &C, const JobAction &JA,
+ const InputInfoList &Outputs,
+ const InputInfoList &Inputs,
+ const ArgList &Args,
+ const char *LinkingOutput) const {
using namespace options;
+ assert(!Outputs.empty() && "expected at least one output");
+ const InputInfo &Output = Outputs.front();
// A list of permitted options that will be forwarded to the embedded device
// compilation job.
@@ -9822,8 +9844,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 +9878,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 +9896,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 +9921,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);
}
@@ -9999,6 +10031,7 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
// 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();
+ LinkCommand->replaceOutputFilenames(Outputs);
// Forward -Xoffload-{compiler,linker}<-triple> arguments to the linker
// wrapper.
@@ -10070,11 +10103,39 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
LinkCommand->getExecutable()));
// We use action type to differentiate two use cases of the linker wrapper.
- // TY_Image for normal linker wrapper work.
+ // TY_Image for normal linker wrapper work or unbundled device images.
// TY_HIP_FATBIN for HIP device-only links emitting a fat binary directly.
assert(JA.getType() == types::TY_HIP_FATBIN ||
JA.getType() == types::TY_Image);
- if (JA.getType() == types::TY_HIP_FATBIN) {
+ bool EmitDeviceImagesOnly = JA.getType() == types::TY_Image &&
+ JA.isDeviceOffloading(Action::OFK_HIP) &&
+ C.getDriver().offloadDeviceOnly();
+ if (EmitDeviceImagesOnly) {
+ CmdArgs.push_back("--emit-device-images-only");
+ if (Outputs.size() == 1 && Args.hasArg(OPT_o)) {
+ CmdArgs.append({"-o", Output.getFilename()});
+ } else {
+ auto OutputInfo = cast<LinkerWrapperJobAction>(JA).getDeviceOutputInfo();
+ assert(OutputInfo.size() == Outputs.size() &&
+ "device output metadata does not match outputs");
+ for (auto [Info, DeviceOutput] : llvm::zip_equal(OutputInfo, Outputs)) {
+ const ArgList &DeviceArgs = C.getArgsForToolChain(
+ Info.DeviceToolChain, Info.DeviceBoundArch, Action::OFK_HIP);
+ BoundArch Arch = Info.DeviceBoundArch;
+ if (Arch.empty())
+ Arch = BoundArch(DeviceArgs.getLastArgValue(OPT_march_EQ));
+ std::string Triple =
+ Info.DeviceToolChain->ComputeEffectiveClangTriple(DeviceArgs, Arch);
+ StringRef ArchName =
+ Arch.empty() ? StringRef("generic") : Arch.ArchName;
+ CmdArgs.push_back(Args.MakeArgString(Twine("--device-image-output=") +
+ Triple + "," + ArchName));
+ CmdArgs.push_back(DeviceOutput.getFilename());
+ }
+ }
+ for (auto Input : Inputs)
+ CmdArgs.push_back(Input.getFilename());
+ } else if (JA.getType() == types::TY_HIP_FATBIN) {
CmdArgs.push_back("--emit-fatbin-only");
CmdArgs.append({"-o", Output.getFilename()});
for (auto Input : Inputs)
diff --git a/clang/lib/Driver/ToolChains/Clang.h b/clang/lib/Driver/ToolChains/Clang.h
index 23f270ffb76a3..6860cfe574135 100644
--- a/clang/lib/Driver/ToolChains/Clang.h
+++ b/clang/lib/Driver/ToolChains/Clang.h
@@ -178,6 +178,11 @@ class LLVM_LIBRARY_VISIBILITY OffloadPackager final : public Tool {
class LLVM_LIBRARY_VISIBILITY LinkerWrapper final : public Tool {
const Tool *Linker;
+ void constructJob(Compilation &C, const JobAction &JA,
+ const InputInfoList &Outputs, const InputInfoList &Inputs,
+ const llvm::opt::ArgList &TCArgs,
+ const char *LinkingOutput) const;
+
public:
LinkerWrapper(const ToolChain &TC, const Tool *Linker)
: Tool("Offload::Linker", "linker", TC), Linker(Linker) {}
@@ -187,6 +192,11 @@ class LLVM_LIBRARY_VISIBILITY LinkerWrapper final : public Tool {
const InputInfo &Output, const InputInfoList &Inputs,
const llvm::opt::ArgList &TCArgs,
const char *LinkingOutput) const override;
+ void ConstructJobMultipleOutputs(Compilation &C, const JobAction &JA,
+ const InputInfoList &Outputs,
+ const InputInfoList &Inputs,
+ const llvm::opt::ArgList &TCArgs,
+ const char *LinkingOutput) const override;
};
// Calculate the output path of the module file when compiling a module unit
diff --git a/clang/test/Driver/hip-binding.hip b/clang/test/Driver/hip-binding.hip
index b0839021aa1d9..cc98c3f2d0026 100644
--- a/clang/test/Driver/hip-binding.hip
+++ b/clang/test/Driver/hip-binding.hip
@@ -57,24 +57,43 @@
// 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]]"], outputs: ["{{.*}}hip-binding-hip-amdgcn-amd-amdhsa-gfx908.out", "{{.*}}hip-binding-hip-amdgcn-amd-amdhsa-gfx90a.out"]
+//
+// 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: ["{{.+}}"], outputs: ["{{.*}}hip-binding-hip-amdgcn-amd-amdhsa-gfx90a.out"]
+
// 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..2a9ed0eee78ba 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,60 @@
// 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 raw image mode.
+// 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-device-images-only"
+
+// 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..a313b3011563f 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
@@ -12,6 +12,8 @@ __attribute__((visibility("protected"), used)) int x;
// RUN: llvm-offload-binary -o %t.out \
// RUN: --image=file=%t.amdgpu.bc,kind=hip,triple=amdgpu9.4-amd-amdhsa,arch=gfx9-4-generic:xnack+ \
// RUN: --image=file=%t.amdgpu.bc,kind=hip,triple=amdgpu12.00-amd-amdhsa,arch=gfx1200
+// RUN: llvm-offload-binary -o %t.single.out \
+// RUN: --image=file=%t.amdgpu.bc,kind=hip,triple=amdgpu12.00-amd-amdhsa,arch=gfx1200
// Test that linker wrapper outputs .hipfb file without -r option for HIP non-RDC
// The linker wrapper is called directly with the packaged device binary (not embedded in host object)
@@ -55,3 +57,70 @@ __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
+
+// Emit one linked code object without bundling it into a HIP fat binary.
+// RUN: clang-linker-wrapper --host-triple=x86_64-unknown-linux-gnu \
+// RUN: --emit-device-images-only --linker-path=/usr/bin/ld %t.single.out \
+// RUN: -o %t.raw.co
+// RUN: llvm-readobj --file-headers %t.raw.co | FileCheck %s --check-prefix=RAW
+
+// RAW: Format: elf64-amdgpu
+
+// Emit each linked code object to the filename selected by the driver.
+// RUN: clang-linker-wrapper --emit-device-images-only \
+// RUN: --device-image-output=amdgpu9.4-amd-amdhsa,gfx9-4-generic:xnack+ %t.raw-gfx9-4-generic-xnack+.co \
+// RUN: --device-image-output=amdgpu12.00-amd-amdhsa,gfx1200 %t.raw-gfx1200.co \
+// RUN: --linker-path=/usr/bin/ld %t.out
+// RUN: test -f %t.raw-gfx1200.co
+// RUN: test -f %t.raw-gfx9-4-generic-xnack+.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
+
+// An explicit output name is invalid for multiple device images.
+// RUN: not clang-linker-wrapper --emit-device-images-only \
+// RUN: --linker-path=/usr/bin/ld %t.out -o %t.multiple 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MULTIPLE
+
+// MULTIPLE: error: cannot specify -o when emitting multiple device images
+
+// Every device image needs exactly one output mapping.
+// RUN: not clang-linker-wrapper --emit-device-images-only \
+// RUN: --device-image-output=amdgpu12.00-amd-amdhsa,gfx1200 %t.raw-gfx1200.co \
+// RUN: --linker-path=/usr/bin/ld %t.out 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MISSING-OUTPUT
+
+// MISSING-OUTPUT: error: expected an output file for each linked device image
+
+// RUN: not clang-linker-wrapper --emit-device-images-only \
+// RUN: --device-image-output=amdgpu12.00-amd-amdhsa,gfx1200 %t.raw-gfx1200.co \
+// RUN: --device-image-output=amdgpu12.00-amd-amdhsa,gfx1200 %t.raw-gfx1200-duplicate.co \
+// RUN: --linker-path=/usr/bin/ld %t.single.out 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DUPLICATE-OUTPUT
+
+// DUPLICATE-OUTPUT: error: duplicate output for device image 'amdgpu12.00-amd-amdhsa,gfx1200'
+
+// RUN: not clang-linker-wrapper --emit-device-images-only \
+// RUN: --device-image-output=amdgpu12.00-amd-amdhsa,gfx1200 %t.raw-gfx1200.co \
+// RUN: --linker-path=/usr/bin/ld %t.single.out -o %t.raw.co 2>&1 \
+// RUN: | FileCheck %s --check-prefix=OUTPUT-CONFLICT
+
+// OUTPUT-CONFLICT: error: cannot combine -o with explicit device image outputs
+
+// RUN: not clang-linker-wrapper --emit-fatbin-only \
+// RUN: --emit-device-images-only --linker-path=/usr/bin/ld %t.single.out \
+// RUN: -o %t.invalid 2>&1 | FileCheck %s --check-prefix=CONFLICT
+
+// CONFLICT: error: cannot emit a fat binary and raw device images together
diff --git a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
index c2de6578773c7..42c71791fbc96 100644
--- a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
+++ b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
@@ -17,6 +17,7 @@
#include "clang/Basic/TargetID.h"
#include "clang/Basic/Version.h"
#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/CodeGen/CommandFlags.h"
@@ -1079,13 +1080,24 @@ Error handleOverrideImages(
return Error::success();
}
+enum class DeviceOutputMode { Wrapped, FatBinary, Images };
+
+static Error writeOutputFile(StringRef Filename, StringRef Contents) {
+ Expected<std::unique_ptr<FileOutputBuffer>> FOBOrErr =
+ FileOutputBuffer::create(Filename, Contents.size());
+ if (!FOBOrErr)
+ return FOBOrErr.takeError();
+ std::unique_ptr<FileOutputBuffer> FOB = std::move(*FOBOrErr);
+ llvm::copy(Contents, FOB->getBufferStart());
+ return FOB->commit();
+}
+
/// Transforms all the extracted offloading input files into an image that can
-/// be registered by the runtime. If NeedsWrapping is false, writes bundled
-/// output directly without wrapping or host linking.
+/// be registered by the runtime.
Expected<SmallVector<StringRef>>
linkAndWrapDeviceFiles(ArrayRef<SmallVector<OffloadFile>> LinkerInputFiles,
const InputArgList &Args, char **Argv, int Argc,
- bool NeedsWrapping) {
+ DeviceOutputMode OutputMode) {
llvm::TimeTraceScope TimeScope("Handle all device input");
std::mutex ImageMtx;
@@ -1173,6 +1185,51 @@ linkAndWrapDeviceFiles(ArrayRef<SmallVector<OffloadFile>> LinkerInputFiles,
if (Err)
return std::move(Err);
+ if (OutputMode == DeviceOutputMode::Images) {
+ SmallVector<const OffloadingImage *> OutputImages;
+ for (auto &Entry : Images) {
+ for (const OffloadingImage &Image : Entry.second)
+ OutputImages.push_back(&Image);
+ }
+ if (OutputImages.empty())
+ return createStringError(
+ "expected at least one linked image for direct device image output");
+
+ bool HasExplicitOutput = Args.hasArg(OPT_o, OPT_out);
+ if (HasExplicitOutput && OutputImages.size() != 1)
+ return createStringError(
+ "cannot specify -o when emitting multiple device images");
+
+ StringMap<StringRef> DeviceOutputs;
+ for (const opt::Arg *Arg : Args.filtered(OPT_device_image_output)) {
+ if (!DeviceOutputs.try_emplace(Arg->getValue(0), Arg->getValue(1)).second)
+ return createStringError("duplicate output for device image '%s'",
+ Arg->getValue(0));
+ }
+ if (HasExplicitOutput && !DeviceOutputs.empty())
+ return createStringError(
+ "cannot combine -o with explicit device image outputs");
+ if (!HasExplicitOutput && DeviceOutputs.size() != OutputImages.size())
+ return createStringError(
+ "expected an output file for each linked device image");
+
+ for (const OffloadingImage *Image : OutputImages) {
+ SmallString<128> Target(Image->StringData.lookup("triple"));
+ Target += ",";
+ Target += Image->StringData.lookup("arch");
+ StringRef OutputFilename = HasExplicitOutput
+ ? StringRef(ExecutableName)
+ : DeviceOutputs.lookup(Target);
+ if (OutputFilename.empty())
+ return createStringError("missing output for device image '%s'",
+ Target.c_str());
+ if (Error E = writeOutputFile(OutputFilename, Image->Image->getBuffer()))
+ return std::move(E);
+ }
+
+ return SmallVector<StringRef>();
+ }
+
// Create a binary image of each offloading image and either embed it into a
// new object file, or if all inputs were direct offload binaries, emit the
// fat binary directly (e.g. .hipfb / .fatbin).
@@ -1195,20 +1252,13 @@ linkAndWrapDeviceFiles(ArrayRef<SmallVector<OffloadFile>> LinkerInputFiles,
if (!BundledImagesOrErr)
return BundledImagesOrErr.takeError();
- if (!NeedsWrapping) {
+ if (OutputMode == DeviceOutputMode::FatBinary) {
if (BundledImagesOrErr->size() != 1)
return createStringError(
"Expected a single bundled image for direct fat binary output");
- Expected<std::unique_ptr<FileOutputBuffer>> FOBOrErr =
- FileOutputBuffer::create(
- ExecutableName, BundledImagesOrErr->front()->getBufferSize());
- if (!FOBOrErr)
- return FOBOrErr.takeError();
- std::unique_ptr<FileOutputBuffer> FOB = std::move(*FOBOrErr);
- llvm::copy(BundledImagesOrErr->front()->getBuffer(),
- FOB->getBufferStart());
- if (Error E = FOB->commit())
+ if (Error E = writeOutputFile(ExecutableName,
+ BundledImagesOrErr->front()->getBuffer()))
return std::move(E);
continue;
@@ -1571,19 +1621,25 @@ int main(int Argc, char **Argv) {
if (!DeviceInputFiles)
reportError(DeviceInputFiles.takeError());
- // Check if we should emit fat binary directly without wrapping or host
- // linking.
+ // Check if we should emit device output directly without host linking.
bool EmitFatbinOnly = Args.hasArg(OPT_emit_fatbin_only);
-
- // Link and process the device images. The function may emit a direct fat
- // binary if --emit-fatbin-only is specified.
- auto FilesOrErr = linkAndWrapDeviceFiles(*DeviceInputFiles, Args, Argv,
- Argc, !EmitFatbinOnly);
+ bool EmitDeviceImagesOnly = Args.hasArg(OPT_emit_device_images_only);
+ if (EmitFatbinOnly && EmitDeviceImagesOnly)
+ reportError(createStringError(
+ "cannot emit a fat binary and raw device images together"));
+
+ DeviceOutputMode OutputMode = EmitFatbinOnly ? DeviceOutputMode::FatBinary
+ : EmitDeviceImagesOnly
+ ? DeviceOutputMode::Images
+ : DeviceOutputMode::Wrapped;
+
+ auto FilesOrErr =
+ linkAndWrapDeviceFiles(*DeviceInputFiles, Args, Argv, Argc, OutputMode);
if (!FilesOrErr)
reportError(FilesOrErr.takeError());
// Run the host linking job with the rendered arguments.
- if (!EmitFatbinOnly) {
+ if (OutputMode == DeviceOutputMode::Wrapped) {
if (Error Err = runLinker(*FilesOrErr, Args))
reportError(std::move(Err));
}
diff --git a/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td b/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
index 53b6c596de291..b34790b4a2350 100644
--- a/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
+++ b/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
@@ -69,6 +69,16 @@ def emit_fatbin_only
Flags<[WrapperOnlyOption]>,
HelpText<"Emit fat binary directly without wrapping or host linking">;
+def emit_device_images_only
+ : Flag<["--"], "emit-device-images-only">,
+ Flags<[WrapperOnlyOption]>,
+ HelpText<"Emit linked device images without bundling or host linking">;
+
+def device_image_output
+ : JoinedAndSeparate<["--"], "device-image-output=">,
+ Flags<[WrapperOnlyOption, HelpHidden]>,
+ MetaVarName<"<triple>,<arch> <file>">;
+
def no_canonical_prefixes : Flag<["--"], "no-canonical-prefixes">,
Flags<[WrapperOnlyOption]>,
HelpText<"Do not resolve symbolic links, turn relative paths into absolute ones, or do anything else to identify the executable">;
More information about the cfe-commits
mailing list