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" <[email protected]> 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">; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
