diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -72,6 +72,14 @@ "cannot find HIP runtime; provide its path via '--rocm-path', or pass " "'-nogpuinc' to build without HIP runtime">; +def err_drv_no_hipspv_device_lib : Error< + "cannot find HIP device library%select{| for %1}0; provide its path via " + "'--hip-path' or '--hip-device-lib-path', or pass '-nogpulib' to build " + "without HIP device library">; +def err_drv_hipspv_no_hip_path : Error< + "'--hip-path' must be specified when offloading to " + "SPIR-V%select{| unless %1 is given}0.">; + def err_drv_undetermined_amdgpu_arch : Error< "cannot determine AMDGPU architecture: %0; consider passing it via " "'--march'">; diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -982,6 +982,9 @@ BothFlags<[], " that single precision floating-point divide and sqrt used in " "the program source are correctly rounded (HIP device compilation only)">>, ShouldParseIf; +def hipspv_pass_plugin_EQ : Joined<["--"], "hipspv-pass-plugin=">, + Group, MetaVarName<"">, + HelpText<"path to a pass plugin for HIP to SPIR-V passes.">; defm gpu_allow_device_init : BoolFOption<"gpu-allow-device-init", LangOpts<"GPUAllowDeviceInit">, DefaultFalse, PosFlag, NegFlag, @@ -3695,6 +3698,8 @@ MarshallingInfoNegativeFlag>; def nogpuinc : Flag<["-"], "nogpuinc">, HelpText<"Do not add include paths for CUDA/HIP and" " do not include the default CUDA/HIP wrapper headers">; +def nohipwrapperinc : Flag<["-"], "nohipwrapperinc">, + HelpText<"Do not include the default HIP wrapper headers">; def : Flag<["-"], "nocudainc">, Alias; def nogpulib : Flag<["-"], "nogpulib">, HelpText<"Do not link device library for CUDA/HIP device compilation">; diff --git a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt --- a/clang/lib/Driver/CMakeLists.txt +++ b/clang/lib/Driver/CMakeLists.txt @@ -54,6 +54,7 @@ ToolChains/Haiku.cpp ToolChains/HIPUtility.cpp ToolChains/HIPAMD.cpp + ToolChains/HIPSPV.cpp ToolChains/Hexagon.cpp ToolChains/Hurd.cpp ToolChains/Linux.cpp diff --git a/clang/lib/Driver/ToolChains/AMDGPU.cpp b/clang/lib/Driver/ToolChains/AMDGPU.cpp --- a/clang/lib/Driver/ToolChains/AMDGPU.cpp +++ b/clang/lib/Driver/ToolChains/AMDGPU.cpp @@ -478,7 +478,8 @@ void RocmInstallationDetector::AddHIPIncludeArgs(const ArgList &DriverArgs, ArgStringList &CC1Args) const { - bool UsesRuntimeWrapper = VersionMajorMinor > llvm::VersionTuple(3, 5); + bool UsesRuntimeWrapper = VersionMajorMinor > llvm::VersionTuple(3, 5) && + !DriverArgs.hasArg(options::OPT_nohipwrapperinc); if (!DriverArgs.hasArg(options::OPT_nobuiltininc)) { // HIP header includes standard library wrapper headers under clang diff --git a/clang/lib/Driver/ToolChains/HIPSPV.h b/clang/lib/Driver/ToolChains/HIPSPV.h new file mode 100644 --- /dev/null +++ b/clang/lib/Driver/ToolChains/HIPSPV.h @@ -0,0 +1,99 @@ +//===--- HIPSPV.h - HIP ToolChain Implementations ---------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_HIPSPV_H +#define LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_HIPSPV_H + +#include "clang/Driver/Tool.h" +#include "clang/Driver/ToolChain.h" + +namespace clang { +namespace driver { +namespace tools { +namespace HIPSPV { + +// Runs llvm-link/opt/llc/lld, which links multiple LLVM bitcode, together with +// device library, then compiles it to SPIR-V in a shared object. +class LLVM_LIBRARY_VISIBILITY Linker : public Tool { +public: + Linker(const ToolChain &TC) : Tool("HIPSPV::Linker", "hipspv-link", TC) {} + + bool hasIntegratedCPP() const override { return false; } + + void ConstructJob(Compilation &C, const JobAction &JA, + const InputInfo &Output, const InputInfoList &Inputs, + const llvm::opt::ArgList &TCArgs, + const char *LinkingOutput) const override; + +private: + void constructEmitSpirvCommand(Compilation &C, const JobAction &JA, + const InputInfoList &Inputs, + const InputInfo &Output, + const llvm::opt::ArgList &Args) const; +}; + +} // namespace HIPSPV +} // namespace tools + +namespace toolchains { + +class LLVM_LIBRARY_VISIBILITY HIPSPVToolChain final : public ToolChain { +public: + HIPSPVToolChain(const Driver &D, const llvm::Triple &Triple, + const ToolChain &HostTC, const llvm::opt::ArgList &Args); + + const llvm::Triple *getAuxTriple() const override { + return &HostTC.getTriple(); + } + + void + addClangTargetOptions(const llvm::opt::ArgList &DriverArgs, + llvm::opt::ArgStringList &CC1Args, + Action::OffloadKind DeviceOffloadKind) const override; + void addClangWarningOptions(llvm::opt::ArgStringList &CC1Args) const override; + CXXStdlibType GetCXXStdlibType(const llvm::opt::ArgList &Args) const override; + void + AddClangSystemIncludeArgs(const llvm::opt::ArgList &DriverArgs, + llvm::opt::ArgStringList &CC1Args) const override; + void AddClangCXXStdlibIncludeArgs( + const llvm::opt::ArgList &Args, + llvm::opt::ArgStringList &CC1Args) const override; + void AddIAMCUIncludeArgs(const llvm::opt::ArgList &DriverArgs, + llvm::opt::ArgStringList &CC1Args) const override; + void AddHIPIncludeArgs(const llvm::opt::ArgList &DriverArgs, + llvm::opt::ArgStringList &CC1Args) const override; + llvm::SmallVector + getHIPDeviceLibs(const llvm::opt::ArgList &Args) const override; + + SanitizerMask getSupportedSanitizers() const override; + + VersionTuple + computeMSVCVersion(const Driver *D, + const llvm::opt::ArgList &Args) const override; + + unsigned GetDefaultDwarfVersion() const override { return 5; } + bool IsIntegratedAssemblerDefault() const override { return true; } + bool IsMathErrnoDefault() const override { return false; } + bool useIntegratedAs() const override { return true; } + bool isCrossCompiling() const override { return true; } + bool isPICDefault() const override { return false; } + bool isPIEDefault() const override { return false; } + bool isPICDefaultForced() const override { return false; } + bool SupportsProfiling() const override { return false; } + + const ToolChain &HostTC; + +protected: + Tool *buildLinker() const override; +}; + +} // end namespace toolchains +} // end namespace driver +} // end namespace clang + +#endif // LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_HIPSPV_H diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp @@ -0,0 +1,293 @@ +//===--- HIPSPV.cpp - HIPSPV ToolChain Implementation -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "HIPSPV.h" +#include "CommonArgs.h" +#include "HIPUtility.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/InputInfo.h" +#include "clang/Driver/Options.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +using namespace clang::driver; +using namespace clang::driver::toolchains; +using namespace clang::driver::tools; +using namespace clang; +using namespace llvm::opt; + +// Convenience function for creating temporary file for both modes of +// isSaveTempsEnabled(). +static const char *getTempFile(Compilation &C, StringRef Prefix, + StringRef Extension) { + if (C.getDriver().isSaveTempsEnabled()) { + return C.getArgs().MakeArgString(Prefix + "." + Extension); + } else { + auto TmpFile = C.getDriver().GetTemporaryPath(Prefix, Extension); + return C.addTempFile(C.getArgs().MakeArgString(TmpFile)); + } +} + +// Locates HIP pass plugin. +static std::string findPassPlugin(const Driver &D, + const llvm::opt::ArgList &Args) { + StringRef Path = Args.getLastArgValue(options::OPT_hipspv_pass_plugin_EQ); + if (!Path.empty()) { + if (llvm::sys::fs::exists(Path)) + return Path.str(); + D.Diag(diag::err_drv_no_such_file) << Path; + } + + StringRef hipPath = Args.getLastArgValue(options::OPT_hip_path_EQ); + if (!hipPath.empty()) { + SmallString<128> PluginPath(hipPath); + llvm::sys::path::append(PluginPath, "lib", "libLLVMHipSpvPasses.so"); + if (llvm::sys::fs::exists(PluginPath)) + return PluginPath.str().str(); + PluginPath.assign(hipPath); + llvm::sys::path::append(PluginPath, "lib", "llvm", + "libLLVMHipSpvPasses.so"); + if (llvm::sys::fs::exists(PluginPath)) + return PluginPath.str().str(); + } + + return std::string(); +} + +void HIPSPV::Linker::constructEmitSpirvCommand( + Compilation &C, const JobAction &JA, const InputInfoList &Inputs, + const InputInfo &Output, const llvm::opt::ArgList &Args) const { + + assert(!Inputs.empty() && "Must have at least one input."); + std::string Name = std::string(llvm::sys::path::stem(Output.getFilename())); + const char *TempFile = getTempFile(C, Name + "-link", "bc"); + + // Link LLVM bitcode. + ArgStringList LinkArgs{}; + for (auto Input : Inputs) + LinkArgs.push_back(Input.getFilename()); + LinkArgs.append({"-o", TempFile}); + const char *LlvmLink = + Args.MakeArgString(getToolChain().GetProgramPath("llvm-link")); + C.addCommand(std::make_unique(JA, *this, ResponseFileSupport::None(), + LlvmLink, LinkArgs, Inputs, Output)); + + // Post-link HIP lowering. + + // Run LLVM IR passes to lower/expand/emulate HIP code that does not translate + // to SPIR-V (E.g. dynamic shared memory). + auto PassPluginPath = findPassPlugin(C.getDriver(), Args); + if (!PassPluginPath.empty()) { + const char *PassPathCStr = C.getArgs().MakeArgString(PassPluginPath); + const char *OptOutput = getTempFile(C, Name + "-lower", "bc"); + ArgStringList OptArgs{TempFile, "-load-pass-plugin", + PassPathCStr, "-passes=hip-post-link-passes", + "-o", OptOutput}; + const char *Opt = Args.MakeArgString(getToolChain().GetProgramPath("opt")); + C.addCommand(std::make_unique( + JA, *this, ResponseFileSupport::None(), Opt, OptArgs, Inputs, Output)); + TempFile = OptOutput; + } + + // Emit SPIR-V binary. + + ArgStringList LlvmSpirvArgs{"-spirv-max-version=1.1", "--spirv-ext=+all", + TempFile, "-o", Output.getFilename()}; + const char *LlvmSpirv = + Args.MakeArgString(getToolChain().GetProgramPath("llvm-spirv")); + C.addCommand(std::make_unique(JA, *this, ResponseFileSupport::None(), + LlvmSpirv, LlvmSpirvArgs, Inputs, + Output)); +} + +void HIPSPV::Linker::ConstructJob(Compilation &C, const JobAction &JA, + const InputInfo &Output, + const InputInfoList &Inputs, + const ArgList &Args, + const char *LinkingOutput) const { + if (Inputs.size() > 0 && Inputs[0].getType() == types::TY_Image && + JA.getType() == types::TY_Object) + return HIP::constructGenerateObjFileFromHIPFatBinary(C, Output, Inputs, + Args, JA, *this); + + if (JA.getType() == types::TY_HIP_FATBIN) + return HIP::constructHIPFatbinCommand(C, JA, Output.getFilename(), Inputs, + Args, *this); + + constructEmitSpirvCommand(C, JA, Inputs, Output, Args); +} + +HIPSPVToolChain::HIPSPVToolChain(const Driver &D, const llvm::Triple &Triple, + const ToolChain &HostTC, const ArgList &Args) + : ToolChain(D, Triple, Args), HostTC(HostTC) { + // Lookup binaries into the driver directory, this is used to + // discover the clang-offload-bundler executable. + getProgramPaths().push_back(getDriver().Dir); +} + +void HIPSPVToolChain::addClangTargetOptions( + const llvm::opt::ArgList &DriverArgs, + llvm::opt::ArgStringList &CC1Args, + Action::OffloadKind DeviceOffloadingKind) const { + HostTC.addClangTargetOptions(DriverArgs, CC1Args, DeviceOffloadingKind); + + assert(DeviceOffloadingKind == Action::OFK_HIP && + "Only HIP offloading kinds are supported for GPUs."); + + CC1Args.push_back("-fcuda-is-device"); + + if (DriverArgs.hasFlag(options::OPT_fcuda_approx_transcendentals, + options::OPT_fno_cuda_approx_transcendentals, false)) + CC1Args.push_back("-fcuda-approx-transcendentals"); + + CC1Args.push_back("-fcuda-allow-variadic-functions"); + + // Default to "hidden" visibility, as object level linking will not be + // supported for the foreseeable future. + if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ, + options::OPT_fvisibility_ms_compat)) { + CC1Args.append({"-fvisibility", "hidden"}); + CC1Args.push_back("-fapply-global-visibility-to-externs"); + } + + llvm::for_each(getHIPDeviceLibs(DriverArgs), [&](StringRef BCFile) { + CC1Args.push_back("-mlink-builtin-bitcode"); + CC1Args.push_back(DriverArgs.MakeArgString(BCFile)); + }); + + // A crude workaround for llvm-spirv which does not handle the autovectorized + // code well (vector reductions, non-i{8,16,32,64} types). + // TODO: Allow autovectorization when SPIR-V backend arrives. + CC1Args.append({"-mllvm", "-vectorize-loops=false"}); + CC1Args.append({"-mllvm", "-vectorize-slp=false"}); +} + +Tool *HIPSPVToolChain::buildLinker() const { + assert(getTriple().getArch() == llvm::Triple::spirv64); + return new tools::HIPSPV::Linker(*this); +} + +void HIPSPVToolChain::addClangWarningOptions(ArgStringList &CC1Args) const { + HostTC.addClangWarningOptions(CC1Args); +} + +ToolChain::CXXStdlibType +HIPSPVToolChain::GetCXXStdlibType(const ArgList &Args) const { + return HostTC.GetCXXStdlibType(Args); +} + +void HIPSPVToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs, + ArgStringList &CC1Args) const { + HostTC.AddClangSystemIncludeArgs(DriverArgs, CC1Args); +} + +void HIPSPVToolChain::AddClangCXXStdlibIncludeArgs( + const ArgList &Args, ArgStringList &CC1Args) const { + HostTC.AddClangCXXStdlibIncludeArgs(Args, CC1Args); +} + +void HIPSPVToolChain::AddIAMCUIncludeArgs(const ArgList &Args, + ArgStringList &CC1Args) const { + HostTC.AddIAMCUIncludeArgs(Args, CC1Args); +} + +void HIPSPVToolChain::AddHIPIncludeArgs(const ArgList &DriverArgs, + ArgStringList &CC1Args) const { + if (DriverArgs.hasArg(options::OPT_nogpuinc)) + return; + + StringRef hipPath = DriverArgs.getLastArgValue(options::OPT_hip_path_EQ); + if (hipPath.empty()) { + getDriver().Diag(diag::err_drv_hipspv_no_hip_path) << 1 << "'-nogpuinc'"; + return; + } else { + SmallString<128> P(hipPath); + llvm::sys::path::append(P, "include"); + CC1Args.push_back("-isystem"); + CC1Args.push_back(DriverArgs.MakeArgString(P)); + } +} + +llvm::SmallVector +HIPSPVToolChain::getHIPDeviceLibs(const llvm::opt::ArgList &DriverArgs) const { + llvm::SmallVector BCLibs; + if (DriverArgs.hasArg(options::OPT_nogpulib)) + return {}; + + ArgStringList LibraryPaths; + // Find device libraries in --hip-device-lib-path and HIP_DEVICE_LIB_PATH. + auto HipDeviceLibPathArgs = DriverArgs.getAllArgValues( + // --hip-device-lib-path is alias to this option. + clang::driver::options::OPT_rocm_device_lib_path_EQ); + for (auto Path : HipDeviceLibPathArgs) + LibraryPaths.push_back(DriverArgs.MakeArgString(Path)); + + StringRef HipPath = DriverArgs.getLastArgValue(options::OPT_hip_path_EQ); + if (!HipPath.empty()) { + SmallString<128> Path(HipPath); + llvm::sys::path::append(Path, "lib", "hip-device-lib"); + LibraryPaths.push_back(DriverArgs.MakeArgString(Path)); + } + + addDirectoryList(DriverArgs, LibraryPaths, "", "HIP_DEVICE_LIB_PATH"); + + // Maintain compatability with --hip-device-lib. + auto BCLibArgs = DriverArgs.getAllArgValues(options::OPT_hip_device_lib_EQ); + if (!BCLibArgs.empty()) { + llvm::for_each(BCLibArgs, [&](StringRef BCName) { + StringRef FullName; + for (std::string LibraryPath : LibraryPaths) { + SmallString<128> Path(LibraryPath); + llvm::sys::path::append(Path, BCName); + FullName = Path; + if (llvm::sys::fs::exists(FullName)) { + BCLibs.push_back(FullName.str()); + return; + } + } + getDriver().Diag(diag::err_drv_no_such_file) << BCName; + }); + } else { + // Search device library named as 'hipspv-.bc'. + auto TT = getTriple().normalize(); + std::string BCName = "hipspv-" + TT + ".bc"; + for (auto LibPath : LibraryPaths) { + SmallString<128> Path(LibPath); + llvm::sys::path::append(Path, BCName); + if (llvm::sys::fs::exists(Path)) { + BCLibs.push_back(Path.str().str()); + return BCLibs; + } + } + getDriver().Diag(diag::err_drv_no_hipspv_device_lib) + << 1 << ("'" + TT + "' target"); + return {}; + } + + return BCLibs; +} + +SanitizerMask HIPSPVToolChain::getSupportedSanitizers() const { + // The HIPSPVToolChain only supports sanitizers in the sense that it allows + // sanitizer arguments on the command line if they are supported by the host + // toolchain. The HIPSPVToolChain will actually ignore any command line + // arguments for any of these "supported" sanitizers. That means that no + // sanitization of device code is actually supported at this time. + // + // This behavior is necessary because the host and device toolchains + // invocations often share the command line, so the device toolchain must + // tolerate flags meant only for the host toolchain. + return HostTC.getSupportedSanitizers(); +} + +VersionTuple HIPSPVToolChain::computeMSVCVersion(const Driver *D, + const ArgList &Args) const { + return HostTC.computeMSVCVersion(D, Args); +}