Index: include/clang/Basic/DiagnosticDriverKinds.td =================================================================== --- include/clang/Basic/DiagnosticDriverKinds.td +++ include/clang/Basic/DiagnosticDriverKinds.td @@ -26,6 +26,10 @@ def err_drv_no_cuda_installation : Error< "cannot find CUDA installation. Provide its path via --cuda-path, or pass " "-nocudainc to build without CUDA includes.">; +def err_drv_cuda_version_too_low : Error< + "GPU arch %1 requires CUDA version at least %3, but installation at %0 is %2. " + "Use --cuda-path to specify a different CUDA install, or pass " + "--no-cuda-version-check.">; def err_drv_invalid_thread_model_for_target : Error< "invalid thread model '%0' in '%1' for this target">; def err_drv_invalid_linker_name : Error< Index: include/clang/Driver/Options.td =================================================================== --- include/clang/Driver/Options.td +++ include/clang/Driver/Options.td @@ -410,6 +410,9 @@ HelpText<"CUDA GPU architecture (e.g. sm_35). May be specified more than once.">; def cuda_noopt_device_debug : Flag<["--"], "cuda-noopt-device-debug">, HelpText<"Enable device-side debug info generation. Disables ptxas optimizations.">; +def no_cuda_version_check : Flag<["--"], "no-cuda-version-check">, + HelpText<"Don't error out if the detected version of the CUDA install is " + "too low for the requested CUDA gpu architecture.">; def no_cuda_noopt_device_debug : Flag<["--"], "no-cuda-noopt-device-debug">; def cuda_path_EQ : Joined<["--"], "cuda-path=">, Group, HelpText<"CUDA installation path">; Index: lib/Driver/ToolChains.h =================================================================== --- lib/Driver/ToolChains.h +++ lib/Driver/ToolChains.h @@ -11,12 +11,14 @@ #define LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_H #include "Tools.h" +#include "clang/Basic/Cuda.h" #include "clang/Basic/VersionTuple.h" #include "clang/Driver/Action.h" #include "clang/Driver/Multilib.h" #include "clang/Driver/ToolChain.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/Support/Compiler.h" #include #include @@ -162,6 +164,7 @@ private: const Driver &D; bool IsValid = false; + CudaVersion Version = CudaVersion::UNKNOWN; std::string InstallPath; std::string BinPath; std::string LibPath; @@ -169,15 +172,27 @@ std::string IncludePath; llvm::StringMap LibDeviceMap; + // CUDA architectures for which we have raised an error in + // CheckCudaVersionSupportsArch. + mutable llvm::SmallSet ArchsWithVersionTooLowErrors; + public: CudaInstallationDetector(const Driver &D) : D(D) {} void init(const llvm::Triple &TargetTriple, const llvm::opt::ArgList &Args); + /// \brief Emit an error if Version does not support the given Arch. + /// + /// If either Version or Arch is unknown, does not emit an error. Emits at + /// most one error per Arch. + void CheckCudaVersionSupportsArch(CudaArch Arch) const; + /// \brief Check whether we detected a valid Cuda install. bool isValid() const { return IsValid; } /// \brief Print information about the detected CUDA installation. void print(raw_ostream &OS) const; + /// \brief Get the deteced Cuda install's version. + CudaVersion version() const { return Version; } /// \brief Get the detected Cuda installation path. StringRef getInstallPath() const { return InstallPath; } /// \brief Get the detected path to Cuda's bin directory. @@ -852,6 +867,16 @@ // ptxas. bool useIntegratedAs() const override { return false; } + void AddCudaIncludeArgs(const llvm::opt::ArgList &DriverArgs, + llvm::opt::ArgStringList &CC1Args) const override; + + const Generic_GCC::CudaInstallationDetector &cudaInstallation() const { + return CudaInstallation; + } + Generic_GCC::CudaInstallationDetector &cudaInstallation() { + return CudaInstallation; + } + protected: Tool *buildAssembler() const override; // ptxas Tool *buildLinker() const override; // fatbinary (ok, not really a linker) Index: lib/Driver/ToolChains.cpp =================================================================== --- lib/Driver/ToolChains.cpp +++ lib/Driver/ToolChains.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "ToolChains.h" +#include "clang/Basic/Cuda.h" #include "clang/Basic/ObjCRuntime.h" #include "clang/Basic/Version.h" #include "clang/Basic/VirtualFileSystem.h" @@ -1703,9 +1704,33 @@ BiarchTripleAliases.push_back(BiarchTriple.str()); } +// Parses the contents of version.txt in an CUDA installation. It should +// contain one line of the from e.g. "CUDA Version 7.5.2". +static CudaVersion ParseCudaVersionFile(llvm::StringRef V) { + if (!V.startswith("CUDA Version ")) + return CudaVersion::UNKNOWN; + V = V.substr(strlen("CUDA Version ")); + int Major = -1, Minor = -1; + auto First = V.split('.'); + auto Second = First.second.split('.'); + if (!First.first.getAsInteger(10, Major) || + !Second.first.getAsInteger(10, Minor)) + return CudaVersion::UNKNOWN; + + if (Major == 7 && Minor == 0) { + // This doesn't appear to ever happen -- version.txt doesn't exist in the + // CUDA 7 installs I've seen. But no harm in checking. + return CudaVersion::CUDA_70; + } + if (Major == 7 && Minor == 5) + return CudaVersion::CUDA_75; + if (Major == 8 && Minor == 0) + return CudaVersion::CUDA_80; + return CudaVersion::UNKNOWN; +} + // \brief -- try common CUDA installation paths looking for files we need for // CUDA compilation. - void Generic_GCC::CudaInstallationDetector::init( const llvm::Triple &TargetTriple, const llvm::opt::ArgList &Args) { SmallVector CudaPathCandidates; @@ -1768,14 +1793,40 @@ } } + llvm::ErrorOr> VersionFile = + FS.getBufferForFile(InstallPath + "/version.txt"); + if (!VersionFile) { + // CUDA 7.0 doesn't have a version.txt, so guess that's our version if + // version.txt isn't present. + Version = CudaVersion::CUDA_70; + } else { + Version = ParseCudaVersionFile((*VersionFile)->getBuffer()); + } + IsValid = true; break; } } +void Generic_GCC::CudaInstallationDetector::CheckCudaVersionSupportsArch( + CudaArch Arch) const { + if (Arch == CudaArch::UNKNOWN || Version == CudaVersion::UNKNOWN || + ArchsWithVersionTooLowErrors.count(Arch) > 0) + return; + + auto RequiredVersion = MinVersionForCudaArch(Arch); + if (Version < RequiredVersion) { + ArchsWithVersionTooLowErrors.insert(Arch); + D.Diag(diag::err_drv_cuda_version_too_low) + << InstallPath << CudaArchToString(Arch) << CudaVersionToString(Version) + << CudaVersionToString(RequiredVersion); + } +} + void Generic_GCC::CudaInstallationDetector::print(raw_ostream &OS) const { if (isValid()) - OS << "Found CUDA installation: " << InstallPath << "\n"; + OS << "Found CUDA installation: " << InstallPath << ", version " + << CudaVersionToString(Version) << "\n"; } namespace { @@ -4670,6 +4721,18 @@ } } +void CudaToolChain::AddCudaIncludeArgs(const ArgList &DriverArgs, + ArgStringList &CC1Args) const { + // Check our CUDA version if we're going to include the CUDA headers. + if (!DriverArgs.hasArg(options::OPT_nocudainc) && + !DriverArgs.hasArg(options::OPT_nocuda_version_check)) { + StringRef Arch = DriverArgs.getLastArgValue(options::OPT_march_EQ); + assert(!Arch.empty() && "Must have an explicit GPU arch."); + CudaInstallation.CheckCudaVersionSupportsArch(StringToCudaArch(Arch)); + } + Linux::AddCudaIncludeArgs(DriverArgs, CC1Args); +} + llvm::opt::DerivedArgList * CudaToolChain::TranslateArgs(const llvm::opt::DerivedArgList &Args, const char *BoundArch) const { Index: lib/Driver/Tools.cpp =================================================================== --- lib/Driver/Tools.cpp +++ lib/Driver/Tools.cpp @@ -11146,6 +11146,12 @@ assert(gpu_archs.size() == 1 && "Exactly one GPU Arch required for ptxas."); const std::string& gpu_arch = gpu_archs[0]; + // Check that our installation's ptxas supports gpu_arch. + if (!Args.hasArg(options::OPT_nocuda_version_check)) { + TC.cudaInstallation().CheckCudaVersionSupportsArch( + StringToCudaArch(gpu_arch)); + } + ArgStringList CmdArgs; CmdArgs.push_back(TC.getTriple().isArch64Bit() ? "-m64" : "-m32"); if (Args.hasFlag(options::OPT_cuda_noopt_device_debug, Index: test/Driver/Inputs/CUDA_80/usr/local/cuda/version.txt =================================================================== --- /dev/null +++ test/Driver/Inputs/CUDA_80/usr/local/cuda/version.txt @@ -0,0 +1 @@ +CUDA Version 8.0.42 Index: test/Driver/cuda-version-check.cu =================================================================== --- /dev/null +++ test/Driver/cuda-version-check.cu @@ -0,0 +1,51 @@ +// REQUIRES: clang-driver +// REQUIRES: x86-registered-target +// REQUIRES: nvptx-registered-target + +// RUN: %clang -v -### --cuda-gpu-arch=sm_20 --sysroot=%S/Inputs/CUDA 2>&1 %s | \ +// RUN: FileCheck %s --check-prefix=OK +// RUN: %clang -v -### --cuda-gpu-arch=sm_20 --sysroot=%S/Inputs/CUDA_80 2>&1 %s | \ +// RUN: FileCheck %s --check-prefix=OK +// RUN: %clang -v -### --cuda-gpu-arch=sm_60 --sysroot=%S/Inputs/CUDA_80 2>&1 %s | \ +// RUN: FileCheck %s --check-prefix=OK + +// The installation at Inputs/CUDA is CUDA 7.0, which doesn't support sm_60. +// RUN: %clang -v -### --cuda-gpu-arch=sm_60 --sysroot=%S/Inputs/CUDA 2>&1 %s | \ +// RUN: FileCheck %s --check-prefix=ERR_SM60 + +// This should only complain about sm_60, not sm_35. +// RUN: %clang -v -### --cuda-gpu-arch=sm_60 --cuda-gpu-arch=sm_35 \ +// RUN: --sysroot=%S/Inputs/CUDA 2>&1 %s | \ +// RUN: FileCheck %s --check-prefix=ERR_SM60 --check-prefix=OK_SM35 + +// We should get two errors here, one for sm_60 and one for sm_61. +// RUN: %clang -v -### --cuda-gpu-arch=sm_60 --cuda-gpu-arch=sm_61 \ +// RUN: --sysroot=%S/Inputs/CUDA 2>&1 %s | \ +// RUN: FileCheck %s --check-prefix=ERR_SM60 --check-prefix=ERR_SM61 + +// We should still get an error if we pass -nocudainc, because this compilation +// would invoke ptxas, and we do a version check on that, too. +// RUN: %clang -v -### --cuda-gpu-arch=sm_60 -nocudainc --sysroot=%S/Inputs/CUDA 2>&1 %s | \ +// RUN: FileCheck %s --check-prefix=ERR_SM60 + +// If with -nocudainc and -E, we don't touch the CUDA install, so we +// shouldn't get an error. +// RUN: %clang -v -### -E --cuda-device-only --cuda-gpu-arch=sm_60 -nocudainc \ +// RUN: --sysroot=%S/Inputs/CUDA 2>&1 %s | \ +// RUN: FileCheck %s --check-prefix=OK + +// -nocuda-version-check should suppress all of these errors. +// RUN: %clang -v -### --cuda-gpu-arch=sm_60 --sysroot=%S/Inputs/CUDA 2>&1 \ +// RUN: -nocuda-version-check %s | \ +// RUN: FileCheck %s --check-prefix=OK + +// OK-NOT: error: GPU arch + +// OK_SM35-NOT: error: GPU arch sm_35 + +// We should only get one error per architecture. +// ERR_SM60: error: GPU arch sm_60 {{.*}} +// ERR_SM60-NOT: error: GPU arch sm_60 + +// ERR_SM61: error: GPU arch sm_61 {{.*}} +// ERR_SM61-NOT: error: GPU arch sm_61