diff --git a/clang/include/clang/Basic/TargetID.h b/clang/include/clang/Basic/TargetID.h index 902151d76556d..8871b76859fd7 100644 --- a/clang/include/clang/Basic/TargetID.h +++ b/clang/include/clang/Basic/TargetID.h @@ -9,53 +9,29 @@ #ifndef LLVM_CLANG_BASIC_TARGETID_H #define LLVM_CLANG_BASIC_TARGETID_H -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/TargetParser/Triple.h" #include -#include +#include +#include namespace clang { -/// Get all feature strings that can be used in target ID for \p Processor. -/// Target ID is a processor name with optional feature strings -/// postfixed by a plus or minus sign delimited by colons, e.g. -/// gfx908:xnack+:sramecc-. Each processor have a limited -/// number of predefined features when showing up in a target ID. -llvm::SmallVector -getAllPossibleTargetIDFeatures(const llvm::Triple &T, - llvm::StringRef Processor); - /// Get processor name from target ID. /// Returns canonical processor name or empty if the processor name is invalid. llvm::StringRef getProcessorFromTargetID(const llvm::Triple &T, llvm::StringRef OffloadArch); -/// Parse a target ID to get processor and feature map. -/// Returns canonicalized processor name or std::nullopt if the target ID is -/// invalid. Returns target ID features in \p FeatureMap if it is not null -/// pointer. This function assumes \p OffloadArch is a valid target ID. -/// If the target ID contains feature+, map it to true. -/// If the target ID contains feature-, map it to false. -/// If the target ID does not contain a feature (default), do not map it. -std::optional parseTargetID(const llvm::Triple &T, - llvm::StringRef OffloadArch, - llvm::StringMap *FeatureMap); - -/// Returns canonical target ID, assuming \p Processor is canonical and all -/// entries in \p Features are valid. -std::string getCanonicalTargetID(llvm::StringRef Processor, - const llvm::StringMap &Features); +/// A device triple paired with a target ID (processor and feature modifiers) +/// for that triple, e.g. {amdgcn-amd-amdhsa, "gfx906:xnack+"}. +using TargetIDEntry = std::pair; /// Get the conflicted pair of target IDs for a compilation or a bundled code -/// object, assuming \p TargetIDs are canonicalized. If there is no conflicts, -/// returns std::nullopt. +/// object. Two entries conflict when they resolve to the same processor but +/// disagree on whether a feature (xnack/sramecc) is explicitly specified. If +/// there is no conflict, returns std::nullopt. std::optional> -getConflictTargetIDCombination(const std::set &TargetIDs); - -/// Check whether the provided target ID is compatible with the requested -/// target ID. -bool isCompatibleTargetID(llvm::StringRef Provided, llvm::StringRef Requested); +getConflictTargetIDCombination(llvm::ArrayRef Entries); /// Sanitize a target ID string for use in a file name. /// Replaces invalid characters (like ':') with safe characters (like '@'). diff --git a/clang/lib/Basic/TargetID.cpp b/clang/lib/Basic/TargetID.cpp index 67f429607ef27..d2e1228897ceb 100644 --- a/clang/lib/Basic/TargetID.cpp +++ b/clang/lib/Basic/TargetID.cpp @@ -7,186 +7,50 @@ //===----------------------------------------------------------------------===// #include "clang/Basic/TargetID.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SmallSet.h" -#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/Support/Path.h" #include "llvm/TargetParser/AMDGPUTargetParser.h" -#include "llvm/TargetParser/Triple.h" -#include -#include -#include namespace clang { -static llvm::SmallVector -getAllPossibleAMDGPUTargetIDFeatures(const llvm::Triple &T, - llvm::StringRef Proc) { - // Entries in returned vector should be in alphabetical order. - llvm::SmallVector Ret; - auto ProcKind = T.isAMDGCN() ? llvm::AMDGPU::parseArchAMDGCN(Proc) - : llvm::AMDGPU::parseArchR600(Proc); - if (ProcKind == llvm::AMDGPU::GK_NONE) - return Ret; - auto Features = T.isAMDGCN() ? llvm::AMDGPU::getArchAttrAMDGCN(ProcKind) - : llvm::AMDGPU::getArchAttrR600(ProcKind); - if (Features & llvm::AMDGPU::FEATURE_SRAMECC) - Ret.push_back("sramecc"); - // Only allow xnack in target ID if the processor supports on/off modes. - if (Features & llvm::AMDGPU::FEATURE_XNACK_ON_OFF_MODES) - Ret.push_back("xnack"); - return Ret; -} - -llvm::SmallVector -getAllPossibleTargetIDFeatures(const llvm::Triple &T, - llvm::StringRef Processor) { - llvm::SmallVector Ret; - if (T.isAMDGPU()) - return getAllPossibleAMDGPUTargetIDFeatures(T, Processor); - return Ret; -} - -/// Returns canonical processor name or empty string if \p Processor is invalid. -static llvm::StringRef getCanonicalProcessorName(const llvm::Triple &T, - llvm::StringRef Processor) { - if (T.isAMDGPU()) - return llvm::AMDGPU::getCanonicalArchName(T, Processor); - return Processor; -} - llvm::StringRef getProcessorFromTargetID(const llvm::Triple &T, - llvm::StringRef TargetID) { - auto Split = TargetID.split(':'); - return getCanonicalProcessorName(T, Split.first); -} - -// Parse a target ID with format checking only. Do not check whether processor -// name or features are valid for the processor. -// -// A target ID is a processor name followed by a list of target features -// delimited by colon. Each target feature is a string post-fixed by a plus -// or minus sign, e.g. gfx908:sramecc+:xnack-. -static std::optional -parseTargetIDWithFormatCheckingOnly(llvm::StringRef TargetID, - llvm::StringMap *FeatureMap) { - llvm::StringRef Processor; - - if (TargetID.empty()) - return llvm::StringRef(); - - auto Split = TargetID.split(':'); - Processor = Split.first; - if (Processor.empty()) - return std::nullopt; - - auto Features = Split.second; - if (Features.empty()) - return Processor; - - llvm::StringMap LocalFeatureMap; - if (!FeatureMap) - FeatureMap = &LocalFeatureMap; - - while (!Features.empty()) { - auto Splits = Features.split(':'); - if (Splits.first.empty()) - return std::nullopt; - auto Sign = Splits.first.back(); - auto Feature = Splits.first.drop_back(); - if (Sign != '+' && Sign != '-') - return std::nullopt; - bool IsOn = Sign == '+'; - // Each feature can only show up at most once in target ID. - if (!FeatureMap->try_emplace(Feature, IsOn).second) - return std::nullopt; - Features = Splits.second; - } - return Processor; -} - -std::optional -parseTargetID(const llvm::Triple &T, llvm::StringRef TargetID, - llvm::StringMap *FeatureMap) { - auto OptionalProcessor = - parseTargetIDWithFormatCheckingOnly(TargetID, FeatureMap); - - if (!OptionalProcessor) - return std::nullopt; - - llvm::StringRef Processor = getCanonicalProcessorName(T, *OptionalProcessor); - if (Processor.empty()) - return std::nullopt; - - llvm::SmallSet AllFeatures( - llvm::from_range, getAllPossibleTargetIDFeatures(T, Processor)); - - for (auto &&F : *FeatureMap) - if (!AllFeatures.count(F.first())) - return std::nullopt; - - return Processor; -} - -// A canonical target ID is a target ID containing a canonical processor name -// and features in alphabetical order. -std::string getCanonicalTargetID(llvm::StringRef Processor, - const llvm::StringMap &Features) { - std::string TargetID = Processor.str(); - std::map OrderedMap; - for (const auto &F : Features) - OrderedMap[F.first()] = F.second; - for (const auto &F : OrderedMap) - TargetID = TargetID + ':' + F.first.str() + (F.second ? "+" : "-"); - return TargetID; + llvm::StringRef OffloadArch) { + auto Split = OffloadArch.split(':'); + if (T.isAMDGPU()) + return llvm::AMDGPU::getCanonicalArchName(T, Split.first); + return Split.first; } // For a specific processor, a feature either shows up in all target IDs, or -// does not show up in any target IDs. Otherwise the target ID combination -// is invalid. +// does not show up in any target IDs. Otherwise the target ID combination is +// invalid. std::optional> -getConflictTargetIDCombination(const std::set &TargetIDs) { +getConflictTargetIDCombination(llvm::ArrayRef Entries) { struct Info { llvm::StringRef TargetID; - llvm::StringMap Features; - Info(llvm::StringRef TargetID, const llvm::StringMap &Features) - : TargetID(TargetID), Features(Features) {} + bool HasXnack; + bool HasSramEcc; }; - llvm::StringMap FeatureMap; - for (auto &&ID : TargetIDs) { - llvm::StringMap Features; - llvm::StringRef Proc = *parseTargetIDWithFormatCheckingOnly(ID, &Features); - auto [Loc, Inserted] = FeatureMap.try_emplace(Proc, ID, Features); - if (!Inserted) { - auto &ExistingFeatures = Loc->second.Features; - if (llvm::any_of(Features, [&](auto &F) { - return ExistingFeatures.count(F.first()) == 0; - })) - return std::make_pair(Loc->second.TargetID, ID); - } - } - return std::nullopt; -} -bool isCompatibleTargetID(llvm::StringRef Provided, llvm::StringRef Requested) { - llvm::StringMap ProvidedFeatures, RequestedFeatures; - llvm::StringRef ProvidedProc = - *parseTargetIDWithFormatCheckingOnly(Provided, &ProvidedFeatures); - llvm::StringRef RequestedProc = - *parseTargetIDWithFormatCheckingOnly(Requested, &RequestedFeatures); - if (ProvidedProc != RequestedProc) - return false; - for (const auto &F : ProvidedFeatures) { - auto Loc = RequestedFeatures.find(F.first()); - // The default (unspecified) value of a feature is 'All', which can match - // either 'On' or 'Off'. - if (Loc == RequestedFeatures.end()) - return false; - // If a feature is specified, it must have exact match. - if (Loc->second != F.second) - return false; + llvm::SmallDenseMap Seen; + for (const auto &[T, ID] : Entries) { + std::optional Parsed = + llvm::AMDGPU::TargetID::parse(T, ID); + if (!Parsed) + continue; + + // A feature is present in a target ID only when an explicit '+'/'-' + // modifier is given, not when it is left unspecified. + Info Cur{ID, Parsed->isXnackOnOrOff(), Parsed->isSramEccOnOrOff()}; + auto [Loc, Inserted] = Seen.try_emplace(Parsed->getGPUKind(), Cur); + if (Inserted) + continue; + + const Info &Prev = Loc->second; + if (Cur.HasXnack != Prev.HasXnack || Cur.HasSramEcc != Prev.HasSramEcc) + return std::make_pair(Prev.TargetID, ID); } - return true; + return std::nullopt; } std::string sanitizeTargetIDInFileName(llvm::StringRef TargetID) { diff --git a/clang/lib/Basic/Targets/AMDGPU.cpp b/clang/lib/Basic/Targets/AMDGPU.cpp index 3fd9643373383..49bc99a34a5d5 100644 --- a/clang/lib/Basic/Targets/AMDGPU.cpp +++ b/clang/lib/Basic/Targets/AMDGPU.cpp @@ -295,22 +295,25 @@ void AMDGPUTargetInfo::getTargetDefines(const LangOptions &Opts, Twine("__")); Builder.defineMacro("__amdgcn_processor__", Twine("\"") + Twine(CanonName) + Twine("\"")); - Builder.defineMacro( - "__amdgcn_target_id__", - Twine("\"") + - Twine(getCanonicalTargetID(getArchNameAMDGCN(GPUKind), - OffloadArchFeatures)) + - Twine("\"")); - for (auto F : getAllPossibleTargetIDFeatures(getTriple(), CanonName)) { - auto Loc = OffloadArchFeatures.find(F); - if (Loc != OffloadArchFeatures.end()) { - std::string NewF = F.str(); + llvm::AMDGPU::TargetID TargetID(GPUKind, getTriple(), XnackSetting, + SramEccSetting); + Builder.defineMacro("__amdgcn_target_id__", + Twine("\"") + + Twine(TargetID.getCanonicalTargetIDString()) + + Twine("\"")); + auto DefineFeatureMacro = [&](StringRef Feature, + llvm::AMDGPU::TargetIDSetting Setting) { + if (Setting == llvm::AMDGPU::TargetIDSetting::On || + Setting == llvm::AMDGPU::TargetIDSetting::Off) { + std::string NewF = Feature.str(); llvm::replace(NewF, '-', '_'); - Builder.defineMacro(Twine("__amdgcn_feature_") + Twine(NewF) + - Twine("__"), - Loc->second ? "1" : "0"); + Builder.defineMacro( + Twine("__amdgcn_feature_") + Twine(NewF) + Twine("__"), + Setting == llvm::AMDGPU::TargetIDSetting::On ? "1" : "0"); } - } + }; + DefineFeatureMacro("xnack", XnackSetting); + DefineFeatureMacro("sramecc", SramEccSetting); } if (Opts.AtomicIgnoreDenormalMode) diff --git a/clang/lib/Basic/Targets/AMDGPU.h b/clang/lib/Basic/Targets/AMDGPU.h index 89ba561ef302d..b2117542c2edd 100644 --- a/clang/lib/Basic/Targets/AMDGPU.h +++ b/clang/lib/Basic/Targets/AMDGPU.h @@ -42,13 +42,13 @@ class LLVM_LIBRARY_VISIBILITY AMDGPUTargetInfo final : public TargetInfo { /// Whether having image instructions. bool HasImage = false; - /// Target ID is device name followed by optional feature name postfixed - /// by plus or minus sign delimitted by colon, e.g. gfx908:xnack+:sramecc-. - /// If the target ID contains feature+, map it to true. - /// If the target ID contains feature-, map it to false. - /// If the target ID does not contain a feature (default), do not map it. - llvm::StringMap OffloadArchFeatures; - std::string TargetID; + /// Explicit xnack/sramecc target-id feature settings from the command line, + /// e.g. gfx908:xnack+:sramecc-. "Unsupported" means the feature was not + /// specified (or is not a valid target-id modifier for the processor). + llvm::AMDGPU::TargetIDSetting XnackSetting = + llvm::AMDGPU::TargetIDSetting::Unsupported; + llvm::AMDGPU::TargetIDSetting SramEccSetting = + llvm::AMDGPU::TargetIDSetting::Unsupported; bool hasFP64() const { return getTriple().isAMDGCN() || @@ -462,8 +462,7 @@ class LLVM_LIBRARY_VISIBILITY AMDGPUTargetInfo final : public TargetInfo { bool handleTargetFeatures(std::vector &Features, DiagnosticsEngine &Diags) override { HasFullBFloat16 = true; - auto TargetIDFeatures = - getAllPossibleTargetIDFeatures(getTriple(), getArchNameAMDGCN(GPUKind)); + unsigned ArchAttr = llvm::AMDGPU::getArchAttrAMDGCN(GPUKind); for (const auto &F : Features) { assert(F.front() == '+' || F.front() == '-'); if (F == "+wavefrontsize64") @@ -474,12 +473,17 @@ class LLVM_LIBRARY_VISIBILITY AMDGPUTargetInfo final : public TargetInfo { CUMode = false; else if (F == "+image-insts") HasImage = true; - bool IsOn = F.front() == '+'; + llvm::AMDGPU::TargetIDSetting Setting = + F.front() == '+' ? llvm::AMDGPU::TargetIDSetting::On + : llvm::AMDGPU::TargetIDSetting::Off; StringRef Name = StringRef(F).drop_front(); - if (!llvm::is_contained(TargetIDFeatures, Name)) - continue; - assert(!OffloadArchFeatures.contains(Name)); - OffloadArchFeatures[Name] = IsOn; + // xnack is a valid target-id modifier only when the processor supports + // on/off modes; sramecc when the processor supports sramecc. + if (Name == "xnack" && + (ArchAttr & llvm::AMDGPU::FEATURE_XNACK_ON_OFF_MODES)) + XnackSetting = Setting; + else if (Name == "sramecc" && (ArchAttr & llvm::AMDGPU::FEATURE_SRAMECC)) + SramEccSetting = Setting; } return true; } diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index e606cdc4c1cf8..b0ebfc9e99c53 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -108,6 +108,7 @@ #include "llvm/Support/TarWriter.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/AMDGPUTargetParser.h" #include "llvm/TargetParser/Host.h" #include "llvm/TargetParser/RISCVISAInfo.h" #include // ::getenv @@ -4851,14 +4852,17 @@ static StringRef getCanonicalArchString(Compilation &C, if (IsNVIDIAOffloadArch(Arch)) return Args.MakeArgStringRef(OffloadArchToString(Arch)); - if (IsAMDOffloadArch(Arch)) { - llvm::StringMap Features; - std::optional Arch = parseTargetID(Triple, ArchStr, &Features); - if (!Arch) { + // AMDGCN target IDs carry a processor and xnack/sramecc modifiers to + // canonicalize. Other AMD offload arches (e.g. the amdgcnspirv pseudo-arch on + // a SPIR-V triple) have no target-id features and pass through unchanged. + if (IsAMDOffloadArch(Arch) && Triple.isAMDGCN()) { + std::optional ID = + llvm::AMDGPU::TargetID::parse(Triple, ArchStr); + if (!ID) { C.getDriver().Diag(clang::diag::err_drv_bad_target_id) << ArchStr; return StringRef(); } - return Args.MakeArgStringRef(getCanonicalTargetID(*Arch, Features)); + return Args.MakeArgStringRef(ID->getCanonicalTargetIDString()); } // If the input isn't CUDA or HIP just return the architecture. @@ -4869,13 +4873,18 @@ static StringRef getCanonicalArchString(Compilation &C, /// incompatible pair if a conflict occurs. static std::optional> getConflictOffloadArchCombination(const llvm::DenseSet &Archs, - llvm::Triple Triple) { + const llvm::Triple &Triple) { if (!Triple.isAMDGPU()) return std::nullopt; - std::set ArchSet; - llvm::copy(Archs, std::inserter(ArchSet, ArchSet.begin())); - return getConflictTargetIDCombination(ArchSet); + // Sort for a deterministic conflicting pair in the diagnostic. + llvm::SmallVector ArchList(Archs.begin(), Archs.end()); + llvm::sort(ArchList); + + llvm::SmallVector Entries; + for (StringRef Arch : ArchList) + Entries.emplace_back(Triple, Arch); + return getConflictTargetIDCombination(Entries); } llvm::SmallVector diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp index 8e4d44071ef55..41c09a6dec5ed 100644 --- a/clang/lib/Driver/OffloadBundler.cpp +++ b/clang/lib/Driver/OffloadBundler.cpp @@ -48,6 +48,7 @@ #include "llvm/Support/Timer.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/AMDGPUTargetParser.h" #include "llvm/TargetParser/Host.h" #include "llvm/TargetParser/Triple.h" #include @@ -1115,15 +1116,15 @@ bool isCodeObjectCompatible(const OffloadTargetInfo &CodeObjectInfo, } // Incompatible if Processors mismatch. - llvm::StringMap CodeObjectFeatureMap, TargetFeatureMap; - std::optional CodeObjectProc = clang::parseTargetID( - CodeObjectInfo.Triple, CodeObjectInfo.TargetID, &CodeObjectFeatureMap); - std::optional TargetProc = clang::parseTargetID( - TargetInfo.Triple, TargetInfo.TargetID, &TargetFeatureMap); - - // Both TargetProc and CodeObjectProc can't be empty here. - if (!TargetProc || !CodeObjectProc || - CodeObjectProc.value() != TargetProc.value()) { + std::optional CodeObjectID = + llvm::AMDGPU::TargetID::parse(CodeObjectInfo.Triple, + CodeObjectInfo.TargetID); + std::optional TargetID = + llvm::AMDGPU::TargetID::parse(TargetInfo.Triple, TargetInfo.TargetID); + + // Both target IDs must be valid and name the same processor. + if (!CodeObjectID || !TargetID || + CodeObjectID->getGPUKind() != TargetID->getGPUKind()) { DEBUG_WITH_TYPE("CodeObjectCompatibility", dbgs() << "Incompatible: Processor mismatch \t[CodeObject: " << CodeObjectInfo.str() @@ -1131,44 +1132,30 @@ bool isCodeObjectCompatible(const OffloadTargetInfo &CodeObjectInfo, return false; } - // Incompatible if CodeObject has more features than Target, irrespective of - // type or sign of features. - if (CodeObjectFeatureMap.getNumItems() > TargetFeatureMap.getNumItems()) { + // A feature (xnack/sramecc) is compatible if the code object leaves it + // unspecified ("Any"), or specifies it with the same value the target does. + // A feature the code object specifies but the target leaves unspecified is + // incompatible, as is a differing explicit value. + auto FeatureCompatible = [&](llvm::AMDGPU::TargetIDSetting CodeObject, + llvm::AMDGPU::TargetIDSetting Target) { + bool CodeObjectExplicit = CodeObject == llvm::AMDGPU::TargetIDSetting::On || + CodeObject == llvm::AMDGPU::TargetIDSetting::Off; + if (!CodeObjectExplicit) + return true; + return CodeObject == Target; + }; + + if (!FeatureCompatible(CodeObjectID->getXnackSetting(), + TargetID->getXnackSetting()) || + !FeatureCompatible(CodeObjectID->getSramEccSetting(), + TargetID->getSramEccSetting())) { DEBUG_WITH_TYPE("CodeObjectCompatibility", - dbgs() << "Incompatible: CodeObject has more features " - "than target \t[CodeObject: " + dbgs() << "Incompatible: Feature mismatch \t[CodeObject: " << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str() << "]\n"); return false; } - // Compatible if each target feature specified by target is compatible with - // target feature of code object. The target feature is compatible if the - // code object does not specify it (meaning Any), or if it specifies it - // with the same value (meaning On or Off). - for (const auto &CodeObjectFeature : CodeObjectFeatureMap) { - auto TargetFeature = TargetFeatureMap.find(CodeObjectFeature.getKey()); - if (TargetFeature == TargetFeatureMap.end()) { - DEBUG_WITH_TYPE( - "CodeObjectCompatibility", - dbgs() - << "Incompatible: Value of CodeObject's non-ANY feature is " - "not matching with Target feature's ANY value \t[CodeObject: " - << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str() - << "]\n"); - return false; - } else if (TargetFeature->getValue() != CodeObjectFeature.getValue()) { - DEBUG_WITH_TYPE( - "CodeObjectCompatibility", - dbgs() << "Incompatible: Value of CodeObject's non-ANY feature is " - "not matching with Target feature's non-ANY value " - "\t[CodeObject: " - << CodeObjectInfo.str() - << "]\t:\t[Target: " << TargetInfo.str() << "]\n"); - return false; - } - } - // CodeObject is compatible if all features of Target are: // - either, present in the Code Object's features map with the same sign, // - or, the feature is missing from CodeObjects's features map i.e. it is @@ -1537,8 +1524,17 @@ CheckHeterogeneousArchive(StringRef ArchiveName, if (CodeObjectFileError) return CodeObjectFileError; - auto &&ConflictingArchs = clang::getConflictTargetIDCombination(BundleIds); - if (ConflictingArchs) { + // A single bundle may contain several triples. Pair each target ID with its + // own triple; the conflict check groups by resolved processor, which is + // spelling-independent. + llvm::SmallVector Entries; + for (StringRef BundleId : BundleIds) { + OffloadTargetInfo Info(BundleId, BundlerConfig); + Entries.emplace_back(Info.Triple, Info.TargetID); + } + + if (auto &&ConflictingArchs = + clang::getConflictTargetIDCombination(Entries)) { std::string ErrMsg = Twine("conflicting TargetIDs [" + ConflictingArchs.value().first + ", " + ConflictingArchs.value().second + "] found in " + diff --git a/clang/lib/Driver/ToolChains/AMDGPU.cpp b/clang/lib/Driver/ToolChains/AMDGPU.cpp index 5893f6f6b2915..9ab3dafd5c8b7 100644 --- a/clang/lib/Driver/ToolChains/AMDGPU.cpp +++ b/clang/lib/Driver/ToolChains/AMDGPU.cpp @@ -754,26 +754,24 @@ AMDGPUToolChain::TranslateArgs(const DerivedArgList &Args, BoundArch BA, } if (!getTriple().isSPIRV()) { - AMDGPUToolChain::ParsedTargetIDType PTID = checkTargetID(*DAL); - - // Synthesize feature flags for target ID modifiers (xnack, sramecc). - if (PTID.OptionalFeatureMap) { - const llvm::StringMap &FeatureMap = *PTID.OptionalFeatureMap; - - auto XnackIt = FeatureMap.find("xnack"); - if (XnackIt != FeatureMap.end()) { - DAL->AddFlagArg(nullptr, Opts.getOption(XnackIt->second + std::optional PTID = checkTargetID(*DAL); + + // Synthesize feature flags for explicit target ID modifiers (xnack, + // sramecc). + if (PTID) { + using llvm::AMDGPU::TargetIDSetting; + if (PTID->isXnackOnOrOff()) + DAL->AddFlagArg(nullptr, Opts.getOption(PTID->getXnackSetting() == + TargetIDSetting::On ? options::OPT_mxnack : options::OPT_mno_xnack)); - } - auto SrameccIt = FeatureMap.find("sramecc"); - if (SrameccIt != FeatureMap.end()) { - DAL->AddFlagArg(nullptr, - Opts.getOption(SrameccIt->second - ? options::OPT_msramecc - : options::OPT_mno_sramecc)); - } + if (PTID->isSramEccOnOrOff()) + DAL->AddFlagArg( + nullptr, + Opts.getOption(PTID->getSramEccSetting() == TargetIDSetting::On + ? options::OPT_msramecc + : options::OPT_mno_sramecc)); } } @@ -987,28 +985,33 @@ AMDGPUToolChain::getGPUArch(const llvm::opt::ArgList &DriverArgs) const { getTriple(), DriverArgs.getLastArgValue(options::OPT_mcpu_EQ)); } -AMDGPUToolChain::ParsedTargetIDType +StringRef +AMDGPUToolChain::getTargetIDArg(const llvm::opt::ArgList &DriverArgs) const { + // Target IDs are only meaningful for AMDGCN targets. + if (!getTriple().isAMDGCN()) + return StringRef(); + return DriverArgs.getLastArgValue(options::OPT_mcpu_EQ); +} + +std::optional AMDGPUToolChain::getParsedTargetID(const llvm::opt::ArgList &DriverArgs) const { - StringRef TargetID = DriverArgs.getLastArgValue(options::OPT_mcpu_EQ); + StringRef TargetID = getTargetIDArg(DriverArgs); if (TargetID.empty()) - return {}; - - llvm::StringMap FeatureMap; - auto OptionalGpuArch = parseTargetID(getTriple(), TargetID, &FeatureMap); - if (!OptionalGpuArch) - return {TargetID.str(), std::nullopt, std::nullopt}; + return std::nullopt; - return {TargetID.str(), OptionalGpuArch->str(), FeatureMap}; + return llvm::AMDGPU::TargetID::parse(getTriple(), TargetID); } -AMDGPUToolChain::ParsedTargetIDType +std::optional AMDGPUToolChain::checkTargetID(const llvm::opt::ArgList &DriverArgs) const { - auto PTID = getParsedTargetID(DriverArgs); - if (PTID.OptionalTargetID && !PTID.OptionalGPUArch) { - getDriver().Diag(clang::diag::err_drv_bad_target_id) - << *PTID.OptionalTargetID; + std::optional ID = getParsedTargetID(DriverArgs); + // Diagnose a non-empty but invalid target ID. + if (!ID) { + StringRef TargetID = getTargetIDArg(DriverArgs); + if (!TargetID.empty()) + getDriver().Diag(clang::diag::err_drv_bad_target_id) << TargetID; } - return PTID; + return ID; } Expected> @@ -1288,26 +1291,21 @@ LTOKind AMDGPUToolChain::getLTOMode(const ArgList &Args, } static bool isXnackAvailable(const llvm::Triple &TT, llvm::StringRef TargetID) { - // Arch-specific check - only report as supported if arch has xnack+ - llvm::StringRef Processor = getProcessorFromTargetID(TT, TargetID); - auto ProcKind = TT.isAMDGCN() ? llvm::AMDGPU::parseArchAMDGCN(Processor) - : llvm::AMDGPU::parseArchR600(Processor); - auto Features = TT.isAMDGCN() ? llvm::AMDGPU::getArchAttrAMDGCN(ProcKind) - : llvm::AMDGPU::getArchAttrR600(ProcKind); - - // If processor has xnack but doesn't support on/off modes, xnack is always on - bool XnackAlwaysOn = (Features & llvm::AMDGPU::FEATURE_XNACK) && - !(Features & llvm::AMDGPU::FEATURE_XNACK_ON_OFF_MODES); - if (XnackAlwaysOn) + std::optional ID = + llvm::AMDGPU::TargetID::parse(TT, TargetID); + if (!ID) + return false; + + unsigned Features = llvm::AMDGPU::getArchAttrAMDGCN(ID->getGPUKind()); + + // If the processor has xnack but doesn't support on/off modes, xnack is + // always on. + if ((Features & llvm::AMDGPU::FEATURE_XNACK) && + !(Features & llvm::AMDGPU::FEATURE_XNACK_ON_OFF_MODES)) return true; - // Otherwise, check if xnack+ is explicitly enabled in the target ID - llvm::StringMap FeatureMap; - auto OptionalGpuArch = parseTargetID(TT, TargetID, &FeatureMap); - if (!OptionalGpuArch) - return false; - auto Loc = FeatureMap.find("xnack"); - return (Loc != FeatureMap.end() && Loc->second); + // Otherwise, it is available only if the target ID explicitly enables it. + return ID->getXnackSetting() == llvm::AMDGPU::TargetIDSetting::On; } SanitizerMask AMDGPUToolChain::getSupportedSanitizers( diff --git a/clang/lib/Driver/ToolChains/AMDGPU.h b/clang/lib/Driver/ToolChains/AMDGPU.h index 229c077a3943f..cb0d4e7096b8e 100644 --- a/clang/lib/Driver/ToolChains/AMDGPU.h +++ b/clang/lib/Driver/ToolChains/AMDGPU.h @@ -160,23 +160,20 @@ class LLVM_LIBRARY_VISIBILITY AMDGPUToolChain : public Generic_ELF { Action::OffloadKind DeviceOffloadingKind) const; protected: - /// The struct type returned by getParsedTargetID. - struct ParsedTargetIDType { - std::optional OptionalTargetID; - std::optional OptionalGPUArch; - std::optional> OptionalFeatureMap; - }; - - /// Check and diagnose invalid target ID specified by -mcpu. - /// Returns the parsed target ID. - virtual ParsedTargetIDType + /// Check and diagnose an invalid target ID specified by -mcpu. Returns the + /// parsed target ID, or std::nullopt if -mcpu is absent or invalid + virtual std::optional checkTargetID(const llvm::opt::ArgList &DriverArgs) const; - /// Get target ID, GPU arch, and target ID features if the target ID is - /// specified and valid. - ParsedTargetIDType + /// Parse the target ID specified by -mcpu. Returns the parsed target ID, or + /// std::nullopt if -mcpu is absent or invalid. + std::optional getParsedTargetID(const llvm::opt::ArgList &DriverArgs) const; + /// Get the raw target ID string from -mcpu, or an empty string if -mcpu is + /// absent or the target is not AMDGCN. + StringRef getTargetIDArg(const llvm::opt::ArgList &DriverArgs) const; + /// Get GPU arch from -mcpu without checking. StringRef getGPUArch(const llvm::opt::ArgList &DriverArgs) const; diff --git a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp b/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp index 40d77abe2ef7c..72ead7c0b34db 100644 --- a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp +++ b/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp @@ -349,8 +349,8 @@ int main(int argc, const char **argv) { unsigned HostTargetNum = 0u; bool HIPOnly = true; llvm::DenseSet ParsedTargets; - // Map {offload-kind}-{triple} to target IDs. - std::map> TargetIDs; + // Map {offload-kind}-{triple} to its device triple and target IDs. + std::map>> TargetIDs; // Standardize target names to include env field std::vector StandardizedTargetNames; for (StringRef Target : TargetNames) { @@ -385,8 +385,10 @@ int main(int argc, const char **argv) { return reportError(createStringError(errc::invalid_argument, Msg.str())); } - TargetIDs[OffloadInfo.OffloadKind.str() + "-" + OffloadInfo.Triple.str()] - .insert(OffloadInfo.TargetID); + auto &Entry = TargetIDs[OffloadInfo.OffloadKind.str() + "-" + + OffloadInfo.Triple.str()]; + Entry.first = OffloadInfo.Triple; + Entry.second.insert(OffloadInfo.TargetID); if (KindIsValid && OffloadInfo.hasHostKind()) { ++HostTargetNum; // Save the index of the input that refers to the host. @@ -402,14 +404,17 @@ int main(int argc, const char **argv) { BundlerConfig.TargetNames.assign(StandardizedTargetNames.begin(), StandardizedTargetNames.end()); - for (const auto &TargetID : TargetIDs) { - if (auto ConflictingTID = - clang::getConflictTargetIDCombination(TargetID.second)) { + for (const auto &[Key, TripleAndIDs] : TargetIDs) { + const auto &[Triple, IDs] = TripleAndIDs; + llvm::SmallVector Entries; + for (StringRef ID : IDs) + Entries.emplace_back(Triple, ID); + if (auto ConflictingTID = clang::getConflictTargetIDCombination(Entries)) { SmallVector Buf; raw_svector_ostream Msg(Buf); Msg << "Cannot bundle inputs with conflicting targets: '" - << TargetID.first + "-" + ConflictingTID->first << "' and '" - << TargetID.first + "-" + ConflictingTID->second << "'"; + << Key + "-" + ConflictingTID->first << "' and '" + << Key + "-" + ConflictingTID->second << "'"; return reportError(createStringError(errc::invalid_argument, Msg.str())); } } diff --git a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h index db8969b8f663e..d1e68fbbdc215 100644 --- a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h +++ b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h @@ -227,8 +227,6 @@ class LLVM_ABI TargetID { SramEccSetting = NewSramEccSetting; } - void setTargetIDFromTargetIDStream(StringRef TargetID); - GPUKind getGPUKind() const { return Arch; } StringRef getTargetTripleString() const { return TargetTripleString; } @@ -236,6 +234,15 @@ class LLVM_ABI TargetID { /// \returns True if this is an AMDHSA target. bool isAMDHSA() const { return IsAMDHSA; } + /// Parse and validate a TargetID for triple \p TT from the processor+features + /// string \p ProcAndFeatures (e.g. "gfx90a", "gfx90a:xnack+:sramecc-", ""). + /// Returns std::nullopt if the triple is not AMDGCN, the processor is + /// unrecognized, or a feature modifier is invalid for the processor. + static std::optional parse(const Triple &TT, + StringRef ProcAndFeatures); + + /// Parse and validate a TargetID from a full + /// "-:" directive string. static std::optional parseTargetIDString(StringRef TargetIDDirective); @@ -253,6 +260,16 @@ class LLVM_ABI TargetID { std::string toString() const; + /// Print the canonical processor name followed by any explicit xnack and + /// sramecc feature modifiers (e.g. "gfx908:sramecc-:xnack+"), without the + /// triple prefix. + void printCanonicalTargetIDString(raw_ostream &OS) const; + + /// \returns the canonical processor name followed by any explicit xnack and + /// sramecc feature modifiers order (e.g. "gfx908:sramecc-:xnack+"), without + /// the triple prefix. + std::string getCanonicalTargetIDString() const; + bool operator==(const TargetID &Other) const; bool operator!=(const TargetID &Other) const { return !(*this == Other); } }; diff --git a/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp b/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp index 7ead3d6f2b263..699b4f7896026 100644 --- a/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp +++ b/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp @@ -6418,9 +6418,13 @@ bool AMDGPUAsmParser::ParseDirectiveAMDHSAKernel() { return Error(IDRange.Start, "directive requires gfx8+", IDRange); if (!isUInt<1>(Val)) return OutOfRangeError(ValRange); - if (Val != getTargetStreamer().getTargetID()->isXnackOnOrAny()) - return getParser().Error(IDRange.Start, ".amdhsa_reserve_xnack_mask does not match target id", - IDRange); + bool XnackOn = getTargetStreamer().getTargetID()->isXnackOnOrAny() || + getSTI().hasFeature(AMDGPU::FeatureXNACK); + if (Val != XnackOn) { + return getParser().Error( + IDRange.Start, + ".amdhsa_reserve_xnack_mask does not match target id", IDRange); + } } else if (ID == ".amdhsa_float_round_mode_32") { PARSE_BITS_ENTRY(KD.compute_pgm_rsrc1, COMPUTE_PGM_RSRC1_FLOAT_ROUND_MODE_32, ExprVal, diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.h b/llvm/lib/Target/AMDGPU/GCNSubtarget.h index 170ec7f85aa23..710d25e535b80 100644 --- a/llvm/lib/Target/AMDGPU/GCNSubtarget.h +++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.h @@ -343,7 +343,9 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo, return HasUnalignedScratchAccess && HasUnalignedAccessMode; } - bool isXNACKEnabled() const { return TargetID.isXnackOnOrAny(); } + bool isXNACKEnabled() const { + return enableXNACK() || TargetID.isXnackOnOrAny(); + } bool hasRelaxedBufferOOBMode() const { return BufferOOBRelaxed; } bool hasRelaxedTBufferOOBMode() const { return TBufferOOBRelaxed; } diff --git a/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp b/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp index d70ad37cca867..466464142ef34 100644 --- a/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp +++ b/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp @@ -554,9 +554,11 @@ void AMDGPUTargetAsmStreamer::EmitAmdhsaKernelDescriptor( break; case AMDGPU::AMDHSA_COV4: case AMDGPU::AMDHSA_COV5: - if (getTargetID()->isXnackSupported()) - OS << "\t\t.amdhsa_reserve_xnack_mask " << getTargetID()->isXnackOnOrAny() - << '\n'; + if (STI.hasFeature(AMDGPU::FeatureSupportsXNACK)) { + bool XnackOn = getTargetID()->isXnackOnOrAny() || + STI.hasFeature(AMDGPU::FeatureXNACK); + OS << "\t\t.amdhsa_reserve_xnack_mask " << XnackOn << '\n'; + } break; } diff --git a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp index e5d9c4237c2b4..26bcc9798b01e 100644 --- a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp +++ b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp @@ -1081,7 +1081,7 @@ VOPD::InstInfo getVOPDInstInfo(unsigned VOPDOpcode, TargetID createAMDGPUTargetID(const MCSubtargetInfo &STI, StringRef FeatureString) { TargetID TargetID(parseArchAMDGCN(STI.getCPU()), STI.getTargetTriple(), - STI.getFeatureBits().test(FeatureSupportsXNACK) + STI.getFeatureBits().test(FeatureXNACKOnOffModes) ? TargetIDSetting::Any : TargetIDSetting::Unsupported, STI.getFeatureBits().test(FeatureSupportsSRAMECC) @@ -1107,7 +1107,8 @@ TargetID createAMDGPUTargetID(const MCSubtargetInfo &STI, } // Only allow changing xnack setting if the target supports on/off modes. - // Targets without on/off mode support keep their initial setting (Any). + // Targets without on/off mode support keep their initial setting + // (Unsupported). bool XnackSupported = STI.getFeatureBits().test(FeatureXNACKOnOffModes); bool SramEccSupported = TargetID.isSramEccSupported(); diff --git a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp index 16acaaeb7b71c..170125afa76e2 100644 --- a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp +++ b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "llvm/TargetParser/AMDGPUTargetParser.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Twine.h" @@ -952,14 +953,15 @@ TargetID::TargetID(GPUKind Arch, const Triple &TT, TargetIDSetting XnackSetting, XnackSetting(XnackSetting), SramEccSetting(SramEccSetting), IsAMDHSA(TT.getOS() == Triple::AMDHSA) {} -static TargetIDSetting -getTargetIDSettingFromFeatureString(StringRef FeatureString) { - if (FeatureString.ends_with("-")) - return TargetIDSetting::Off; - if (FeatureString.ends_with("+")) +// Parse a feature modifier sign ("+"/"-"). Returns "Unsupported" if \p Sign is +// neither (i.e. the modifier is malformed). +static TargetIDSetting getTargetIDSettingFromFeatureString(StringRef Sign) { + if (Sign == "+") return TargetIDSetting::On; + if (Sign == "-") + return TargetIDSetting::Off; - llvm_unreachable("Malformed feature string"); + return TargetIDSetting::Unsupported; } // Derive the architecture from the processor name in \p TargetIDStr. "generic" @@ -971,66 +973,118 @@ static GPUKind getGPUKindFromTargetID(const Triple &TT, StringRef TargetIDStr) { : parseArchAMDGCN(CPUName); } +// Compute the xnack/sramecc settings for processor \p Arch from the +// processor+features string \p TargetIDStr +// (e.g. "gfx90a:xnack+:sramecc-"). Returns false if a modifier names an unknown +// or repeated feature, names one the processor does not support, or has a +// malformed sign. +static bool computeTargetIDFeatures(GPUKind Arch, StringRef TargetIDStr, + TargetIDSetting &XnackSetting, + TargetIDSetting &SramEccSetting) { + unsigned ArchAttr = getArchAttrAMDGCN(Arch); + XnackSetting = (ArchAttr & FEATURE_XNACK_ON_OFF_MODES) + ? TargetIDSetting::Any + : TargetIDSetting::Unsupported; + SramEccSetting = (ArchAttr & FEATURE_SRAMECC) ? TargetIDSetting::Any + : TargetIDSetting::Unsupported; + + // The first component is the processor; the rest are feature modifiers of the + // form "<+|->". + SmallVector Split; + TargetIDStr.split(Split, ':'); + bool SeenXnack = false; + bool SeenSramEcc = false; + bool Valid = true; + for (unsigned I = 1, E = Split.size(); I != E; ++I) { + StringRef FeatureString = Split[I]; + if (FeatureString.consume_front("xnack")) { + TargetIDSetting Sign = getTargetIDSettingFromFeatureString(FeatureString); + if (SeenXnack || XnackSetting == TargetIDSetting::Unsupported || + Sign == TargetIDSetting::Unsupported) + Valid = false; + else + XnackSetting = Sign; + SeenXnack = true; + } else if (FeatureString.consume_front("sramecc")) { + TargetIDSetting Sign = getTargetIDSettingFromFeatureString(FeatureString); + if (SeenSramEcc || SramEccSetting == TargetIDSetting::Unsupported || + Sign == TargetIDSetting::Unsupported) + Valid = false; + else + SramEccSetting = Sign; + SeenSramEcc = true; + } else { + // Unknown feature name. + Valid = false; + } + } + return Valid; +} + TargetID::TargetID(const Triple &TT, StringRef TargetIDStr) : TargetID(getGPUKindFromTargetID(TT, TargetIDStr), TT, TargetIDSetting::Unsupported, TargetIDSetting::Unsupported) { - // Default xnack/sramecc to the "Any" wildcard when the architecture supports - // them, then apply any explicit feature overrides from the target-id string. - unsigned ArchAttr = getArchAttrAMDGCN(Arch); - if (ArchAttr & FEATURE_XNACK) - XnackSetting = TargetIDSetting::Any; - if (ArchAttr & FEATURE_SRAMECC) - SramEccSetting = TargetIDSetting::Any; - setTargetIDFromTargetIDStream(TargetIDStr); + // Derive the feature settings from the string. Validity is not checked here; + // parseTargetIDString validates untrusted input. + computeTargetIDFeatures(Arch, TargetIDStr, XnackSetting, SramEccSetting); } -void TargetID::setTargetIDFromTargetIDStream(StringRef TargetID) { - SmallVector TargetIDSplit; - TargetID.split(TargetIDSplit, ':'); +std::optional TargetID::parse(const Triple &TT, + StringRef ProcAndFeatures) { + if (!TT.isAMDGCN()) + return std::nullopt; + + // A named processor (i.e. not the empty/generic wildcard, which is resolved + // from the triple's subarch) must be a recognized GPU. + StringRef CPUName = ProcAndFeatures.split(':').first; + if (!CPUName.empty() && CPUName != "generic" && + parseArchAMDGCN(CPUName) == GK_NONE) + return std::nullopt; - for (const auto &FeatureString : TargetIDSplit) { - if (FeatureString.starts_with("xnack")) - XnackSetting = getTargetIDSettingFromFeatureString(FeatureString); - if (FeatureString.starts_with("sramecc")) - SramEccSetting = getTargetIDSettingFromFeatureString(FeatureString); - } + // Parse the processor and its feature modifiers, then construct directly from + // the resulting fields. + GPUKind Arch = getGPUKindFromTargetID(TT, ProcAndFeatures); + TargetIDSetting XnackSetting, SramEccSetting; + if (!computeTargetIDFeatures(Arch, ProcAndFeatures, XnackSetting, + SramEccSetting)) + return std::nullopt; + + return TargetID(Arch, TT, XnackSetting, SramEccSetting); } std::optional TargetID::parseTargetIDString(StringRef TargetIDDirective) { - // Split on '-' to get arch-vendor-os-environment-processor:features - // There is a single dash separator after the 4-component triple + // Split on '-' to get arch-vendor-os-environment-processor:features. There is + // a single dash separator after the 4-component triple, so the + // processor+features field must be present (even if empty). SmallVector Parts; TargetIDDirective.split(Parts, '-', /*MaxSplit=*/4); - if (Parts.size() < 4) + if (Parts.size() < 5) return std::nullopt; - Triple TT(Parts[0], Parts[1], Parts[2], Parts[3]); - if (!TT.isAMDGCN()) - return std::nullopt; + return parse(Triple(Parts[0], Parts[1], Parts[2], Parts[3]), Parts[4]); +} - // The processor+features field must be present, even if empty (the ISA can - // be encoded in the triple's subarch, e.g. - // "amdgpu12.50-amd-amdhsa-unknown-"). - return TargetID(TT, Parts[4]); +// Append the explicit (On/Off) sramecc/xnack feature modifiers in canonical +// order, e.g. ":sramecc-:xnack+". +static void printFeatureModifiers(raw_ostream &OS, TargetIDSetting SramEcc, + TargetIDSetting Xnack) { + if (SramEcc == TargetIDSetting::Off) + OS << ":sramecc-"; + else if (SramEcc == TargetIDSetting::On) + OS << ":sramecc+"; + + if (Xnack == TargetIDSetting::Off) + OS << ":xnack-"; + else if (Xnack == TargetIDSetting::On) + OS << ":xnack+"; } void TargetID::print(raw_ostream &StreamRep) const { StreamRep << TargetTripleString << '-' << getArchNameAMDGCN(Arch); - if (IsAMDHSA) { - // sramecc. - if (getSramEccSetting() == TargetIDSetting::Off) - StreamRep << ":sramecc-"; - else if (getSramEccSetting() == TargetIDSetting::On) - StreamRep << ":sramecc+"; - - // xnack. - if (getXnackSetting() == TargetIDSetting::Off) - StreamRep << ":xnack-"; - else if (getXnackSetting() == TargetIDSetting::On) - StreamRep << ":xnack+"; - } + if (IsAMDHSA) + printFeatureModifiers(StreamRep, getSramEccSetting(), getXnackSetting()); } std::string TargetID::toString() const { @@ -1040,6 +1094,18 @@ std::string TargetID::toString() const { return Str; } +void TargetID::printCanonicalTargetIDString(raw_ostream &OS) const { + OS << getArchNameAMDGCN(Arch); + printFeatureModifiers(OS, getSramEccSetting(), getXnackSetting()); +} + +std::string TargetID::getCanonicalTargetIDString() const { + std::string Str; + raw_string_ostream OS(Str); + printCanonicalTargetIDString(OS); + return Str; +} + bool TargetID::operator==(const TargetID &Other) const { return Arch == Other.Arch && XnackSetting == Other.XnackSetting && SramEccSetting == Other.SramEccSetting && IsAMDHSA == Other.IsAMDHSA && diff --git a/llvm/unittests/TargetParser/TargetParserTest.cpp b/llvm/unittests/TargetParser/TargetParserTest.cpp index 12ca39d2af4bd..2b7fe26fe8bf3 100644 --- a/llvm/unittests/TargetParser/TargetParserTest.cpp +++ b/llvm/unittests/TargetParser/TargetParserTest.cpp @@ -2828,6 +2828,115 @@ TEST(TargetParserTest, testAMDGPUgetIsaVersionFromSubArch) { (AMDGPU::IsaVersion{0, 0, 0})); } +TEST(TargetParserTest, testAMDGPUParseTargetIDString) { + using AMDGPU::TargetID; + using AMDGPU::TargetIDSetting; + + // A well-formed target id parses, canonicalizing the processor and features. + { + std::optional TID = + TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx90a"); + ASSERT_TRUE(TID.has_value()); + EXPECT_EQ(TID->getGPUKind(), AMDGPU::GK_GFX90A); + EXPECT_EQ(TID->getXnackSetting(), TargetIDSetting::Any); + EXPECT_EQ(TID->getSramEccSetting(), TargetIDSetting::Any); + } + + // Explicit feature modifiers are applied. + { + std::optional TID = TargetID::parseTargetIDString( + "amdgcn-amd-amdhsa-unknown-gfx90a:xnack+:sramecc-"); + ASSERT_TRUE(TID.has_value()); + EXPECT_EQ(TID->getXnackSetting(), TargetIDSetting::On); + EXPECT_EQ(TID->getSramEccSetting(), TargetIDSetting::Off); + } + + // The processor+features field may be empty; the ISA is taken from the + // triple subarch. + EXPECT_TRUE(TargetID::parseTargetIDString("amdgpu9.0a-amd-amdhsa-unknown-") + .has_value()); + + // Structurally malformed strings (missing the processor+features field or a + // non-AMDGCN triple) are rejected. + EXPECT_FALSE(TargetID::parseTargetIDString("not-a-valid-target-id")); + EXPECT_FALSE( + TargetID::parseTargetIDString("x86_64-unknown-linux-gnu-gfx90a")); + + // An unrecognized processor is rejected. + EXPECT_FALSE( + TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfxbogus")); + + // A feature the processor does not support is rejected: gfx600 has neither + // xnack nor sramecc. + EXPECT_FALSE( + TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx600:xnack+")); + EXPECT_FALSE(TargetID::parseTargetIDString( + "amdgcn-amd-amdhsa-unknown-gfx900:sramecc+")); + + // xnack is only a valid modifier when the processor supports on/off modes. + // gfx1250 has xnack permanently enabled (FEATURE_XNACK without + // FEATURE_XNACK_ON_OFF_MODES), so an xnack modifier is rejected. + EXPECT_FALSE(TargetID::parseTargetIDString( + "amdgcn-amd-amdhsa-unknown-gfx1250:xnack+")); + EXPECT_FALSE(TargetID::parseTargetIDString( + "amdgcn-amd-amdhsa-unknown-gfx1250:xnack-")); + + // A feature modifier with no "+"/"-" sign is rejected. + EXPECT_FALSE( + TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx908:xnack")); + + // An unknown feature name is rejected, even alongside a valid one. + EXPECT_FALSE(TargetID::parseTargetIDString( + "amdgcn-amd-amdhsa-unknown-gfx908:unknown+")); + EXPECT_FALSE(TargetID::parseTargetIDString( + "amdgcn-amd-amdhsa-unknown-gfx908:sramecc+:unknown+")); + + // A repeated feature is rejected. + EXPECT_FALSE(TargetID::parseTargetIDString( + "amdgcn-amd-amdhsa-unknown-gfx908:xnack+:xnack+")); + + // Empty processor and/or feature components must be handled without crashing. + EXPECT_FALSE(TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-:")); + EXPECT_FALSE(TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-::")); + EXPECT_FALSE( + TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx900:")); + EXPECT_FALSE( + TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx900::")); + EXPECT_FALSE(TargetID::parseTargetIDString( + "amdgcn-amd-amdhsa-unknown-gfx900:xnack+:")); + + // Constructing directly from a triple and processor+features string must also + // be crash-safe on empty components. + Triple AMDHSA("amdgcn-amd-amdhsa"); + (void)TargetID(AMDHSA, ":"); + (void)TargetID(AMDHSA, "::"); + (void)TargetID(AMDHSA, "gfx900:"); + + // TargetID::parse validates a separate triple + processor/features string. + { + std::optional TID = TargetID::parse(AMDHSA, "gfx90a:xnack+"); + ASSERT_TRUE(TID.has_value()); + EXPECT_EQ(TID->getGPUKind(), AMDGPU::GK_GFX90A); + EXPECT_EQ(TID->getXnackSetting(), AMDGPU::TargetIDSetting::On); + } + + EXPECT_EQ(TargetID::parse(AMDHSA, "gfx908:xnack+:sramecc-") + ->getCanonicalTargetIDString(), + "gfx908:sramecc-:xnack+"); + EXPECT_EQ(TargetID::parse(AMDHSA, "gfx908")->getCanonicalTargetIDString(), + "gfx908"); + EXPECT_EQ(TargetID::parse(Triple("amdgcn-amd-amdpal"), "gfx908:xnack-") + ->getCanonicalTargetIDString(), + "gfx908:xnack-"); + EXPECT_TRUE(TargetID::parse(AMDHSA, "").has_value()); + EXPECT_FALSE(TargetID::parse(AMDHSA, "gfxbogus").has_value()); + EXPECT_FALSE(TargetID::parse(AMDHSA, "gfx600:xnack+").has_value()); + EXPECT_FALSE(TargetID::parse(AMDHSA, "gfx900:").has_value()); + // A non-AMDGCN triple has no target-id features. + EXPECT_FALSE( + TargetID::parse(Triple("r600-unknown-unknown"), "cypress").has_value()); +} + TEST(TargetParserTest, testAMDGPUTargetIDProvidesFor) { using AMDGPU::TargetID; Triple AMDHSA("amdgcn-amd-amdhsa");