blob: 66c793a10daccfb7a538786ac1a9621cf23cff92 [file]
//===-- AMDGPUTargetParser - Parser for AMDGPU features ---------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file implements a target parser to recognise AMDGPU hardware features.
//
//===----------------------------------------------------------------------===//
#include "llvm/TargetParser/AMDGPUTargetParser.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringTable.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Triple.h"
#include <array>
#include <cassert>
using namespace llvm;
using namespace AMDGPU;
namespace {
constexpr unsigned NumAMDGPUSubArches =
Triple::LastAMDGPUSubArch - Triple::FirstAMDGPUSubArch + 1;
// A legacy GPU name (e.g. "tahiti") mapped to the GPUKind it aliases.
struct GPUNameAlias {
StringTable::Offset AltName;
GPUKind Kind;
};
// Per-GPU data for the AMDGCN GPUKinds, from the generated table below.
struct GPUInfo {
StringTable::Offset Name;
Triple::SubArchType SubArch;
unsigned ArchFeatures;
AMDGPUFeatureBitset Features;
IsaVersion Version;
StringTable::Offset FamilyName;
StringTable::Offset BaseName; // The canonical device name for a variant.
uint8_t MaxWavesPerEU;
uint32_t MaxHWAddressableLocalMemorySize;
};
// Per-GPU data for the R600 GPUKinds.
struct R600Info {
StringTable::Offset Name;
R600FeatureKind ArchFeatures;
};
#define GET_AMDGPU_NAME_TABLE
#define GET_AMDGPU_GPU_TABLE
#define GET_AMDGPU_GPU_ALIAS_TABLE
#define GET_AMDGPU_MAJOR_SUBARCH
#define GET_AMDGPU_SUBARCH_NAME
#define GET_AMDGPU_FEATURE_NAME_TABLE
#include "llvm/TargetParser/AMDGPUTargetParserDef.inc"
#define GET_R600_NAME_TABLE
#define GET_R600_GPU_TABLE
#define GET_R600_GPU_ALIAS_TABLE
#include "llvm/TargetParser/R600TargetParserDef.inc"
// The string tables holding GPU-name-derived strings as offsets. R600 and
// AMDGPU come from separate generated headers, each with its own pool.
constexpr StringTable AMDGPUNameStrTab = AMDGPUNameTable;
constexpr StringTable R600NameStrTab = R600NameTable;
// Look up the GPUInfo row for an AMDGCN GPUKind, or nullptr for GK_NONE / a
// non-AMDGCN (R600) kind.
const GPUInfo *getAMDGPUInfo(GPUKind AK) {
if (AK < AMDGPUFirstGPUKind)
return nullptr;
unsigned Idx = AK - AMDGPUFirstGPUKind;
if (Idx >= std::size(AMDGPUGPUTable))
return nullptr;
return &AMDGPUGPUTable[Idx];
}
// Look up the R600Info row for an R600 GPUKind, or nullptr for a non-R600 kind.
const R600Info *getR600Info(GPUKind AK) {
if (AK < R600FirstGPUKind)
return nullptr;
unsigned Idx = AK - R600FirstGPUKind;
if (Idx >= std::size(R600GPUTable))
return nullptr;
return &R600GPUTable[Idx];
}
// Scan a name -> GPUKind table (canonical names, then aliases) for \p CPU.
template <typename InfoT, size_t N, size_t M>
GPUKind parseArchImpl(StringRef CPU, const InfoT (&Table)[N], GPUKind FirstKind,
const StringTable &StrTab,
const GPUNameAlias (&Aliases)[M]) {
for (unsigned I = 0; I != N; ++I) {
if (CPU == StrTab[Table[I].Name])
return static_cast<GPUKind>(FirstKind + I);
}
for (const GPUNameAlias &A : Aliases) {
if (CPU == StrTab[A.AltName])
return A.Kind;
}
return GK_NONE;
}
// Reverse map: SubArch -> GPUKind, indexed by (SubArch - FirstAMDGPUSubArch).
// Subarches with no GPU (incl. the NoSubArch pseudo targets) map to GK_NONE.
constexpr std::array<GPUKind, NumAMDGPUSubArches> AMDGPUSubArchToGPUKind = [] {
std::array<GPUKind, NumAMDGPUSubArches> Map{};
for (unsigned I = 0; I < std::size(AMDGPUGPUTable); ++I) {
Triple::SubArchType SubArch = AMDGPUGPUTable[I].SubArch;
if (SubArch != Triple::NoSubArch) {
Map[SubArch - Triple::FirstAMDGPUSubArch] =
static_cast<GPUKind>(AMDGPUFirstGPUKind + I);
}
}
return Map;
}();
/// SubArch -> major-family, indexed by (SubArch - FirstAMDGPUSubArch).
constexpr std::array<Triple::SubArchType, NumAMDGPUSubArches>
AMDGPUMajorFamilies = [] {
std::array<Triple::SubArchType, NumAMDGPUSubArches> Map{};
for (unsigned I = 0; I < NumAMDGPUSubArches; ++I) {
Map[I] =
static_cast<Triple::SubArchType>(Triple::FirstAMDGPUSubArch + I);
}
for (const AMDGPUMajorSubArchEntry &Entry : AMDGPUMajorSubArch)
Map[Entry.SubArch - Triple::FirstAMDGPUSubArch] = Entry.Major;
return Map;
}();
// SubArch -> name-offset, indexed by (SubArch - FirstAMDGPUSubArch). Unmapped
// subarches keep offset 0 (the empty string).
constexpr std::array<StringTable::Offset, NumAMDGPUSubArches>
AMDGPUSubArchNameOffsets = [] {
std::array<StringTable::Offset, NumAMDGPUSubArches> Map{};
for (const AMDGPUSubArchNameEntry &Entry : AMDGPUSubArchNames)
Map[Entry.SubArch - Triple::FirstAMDGPUSubArch] = Entry.NameOffset;
return Map;
}();
// SubArch -> triple-name-offset (e.g. "amdgpu9.00"), like
// AMDGPUSubArchNameOffsets.
constexpr std::array<StringTable::Offset, NumAMDGPUSubArches>
AMDGPUSubArchTripleNameOffsets = [] {
std::array<StringTable::Offset, NumAMDGPUSubArches> Map{};
for (const AMDGPUSubArchNameEntry &Entry : AMDGPUSubArchNames)
Map[Entry.SubArch - Triple::FirstAMDGPUSubArch] =
Entry.TripleNameOffset;
return Map;
}();
} // namespace
StringRef llvm::AMDGPU::getArchFamilyNameAMDGCN(GPUKind AK) {
const GPUInfo *Info = getAMDGPUInfo(AK);
return Info ? AMDGPUNameStrTab[Info->FamilyName] : "";
}
Triple::SubArchType llvm::AMDGPU::getSubArch(GPUKind AK) {
const GPUInfo *Info = getAMDGPUInfo(AK);
return Info ? Info->SubArch : Triple::SubArchType::NoSubArch;
}
StringRef llvm::AMDGPU::getBaseArchNameAMDGCN(GPUKind AK) {
const GPUInfo *Info = getAMDGPUInfo(AK);
return Info ? AMDGPUNameStrTab[Info->BaseName] : "";
}
AMDGPU::GPUKind
llvm::AMDGPU::getGPUKindFromSubArch(Triple::SubArchType SubArch) {
if (SubArch < Triple::FirstAMDGPUSubArch ||
SubArch > Triple::LastAMDGPUSubArch)
return GK_NONE;
return AMDGPUSubArchToGPUKind[SubArch - Triple::FirstAMDGPUSubArch];
}
Triple::SubArchType AMDGPU::getMajorSubArch(Triple::SubArchType X) {
if (X < Triple::FirstAMDGPUSubArch || X > Triple::LastAMDGPUSubArch)
return Triple::NoSubArch;
return AMDGPUMajorFamilies[X - Triple::FirstAMDGPUSubArch];
}
bool AMDGPU::isSubArchCompatible(Triple::SubArchType A, Triple::SubArchType B) {
if (A == B || A == Triple::NoSubArch || B == Triple::NoSubArch)
return true;
Triple::SubArchType MajorA = AMDGPU::getMajorSubArch(A);
Triple::SubArchType MajorB = AMDGPU::getMajorSubArch(B);
// One side is the major-family subarch covering the other's family.
if (A == MajorA)
return MajorA == MajorB;
if (B == MajorB)
return MajorA == MajorB;
return false;
}
bool AMDGPU::isCPUValidForSubArch(Triple::SubArchType SubArch, GPUKind AK) {
// An unrecognized GPU is never valid.
if (AK == GK_NONE)
return false;
// A legacy triple without a subarch accepts any known GPU.
if (SubArch == Triple::NoSubArch)
return true;
// Reject the dummy "generic" targets
Triple::SubArchType GPUSubArch = getSubArch(AK);
if (GPUSubArch == Triple::NoSubArch)
return false;
return isSubArchCompatible(GPUSubArch, SubArch);
}
bool AMDGPU::isCPUValidForSubArch(Triple::SubArchType SubArch, StringRef CPU) {
return isCPUValidForSubArch(SubArch, parseArchAMDGCN(CPU));
}
bool AMDGPU::isPseudoTarget(GPUKind AK) {
const GPUInfo *Info = getAMDGPUInfo(AK);
return Info && Info->SubArch == Triple::NoSubArch;
}
bool AMDGPU::isPseudoTarget(StringRef CPU) {
return isPseudoTarget(parseArchAMDGCN(CPU));
}
bool AMDGPU::isSubArchCompatible(const Triple &A, const Triple &B) {
// Tolerate subarch mismatch if one entry is none. This is a hack for bitcode
// libraries.
// There's a missing enum entry for an unknown subarch. Make sure the
// subarch is really empty.
if (A.getSubArch() == Triple::NoSubArch)
return A.getArchName().size() == 6;
if (B.getSubArch() == Triple::NoSubArch)
return B.getArchName().size() == 6;
return isSubArchCompatible(A.getSubArch(), B.getSubArch());
}
std::string AMDGPU::mergeSubArch(const Triple &A, const Triple &B) {
if (A.getSubArch() == Triple::NoSubArch)
return B.str();
if (B.getSubArch() == Triple::NoSubArch)
return A.str();
Triple::SubArchType MajorA = AMDGPU::getMajorSubArch(A.getSubArch());
Triple::SubArchType MajorB = AMDGPU::getMajorSubArch(B.getSubArch());
// With a compatible major arch, return the specific subarch.
if (A.getSubArch() == MajorA) {
if (MajorA == MajorB)
return B.str();
}
if (B.getSubArch() == MajorB) {
if (MajorA == MajorB)
return A.str();
}
// Invalid case.
return B.str();
}
StringRef llvm::AMDGPU::getArchNameAMDGCN(GPUKind AK) {
const GPUInfo *Info = getAMDGPUInfo(AK);
return Info ? AMDGPUNameStrTab[Info->Name] : "";
}
StringRef llvm::AMDGPU::getArchNameFromSubArch(Triple::SubArchType SubArch) {
if (SubArch < Triple::FirstAMDGPUSubArch ||
SubArch > Triple::LastAMDGPUSubArch)
return "";
return AMDGPUNameStrTab[AMDGPUSubArchNameOffsets[SubArch -
Triple::FirstAMDGPUSubArch]];
}
StringRef llvm::AMDGPU::getSubArchName(Triple::SubArchType SubArch) {
if (SubArch == Triple::NoSubArch)
return AMDGPUNameStrTab[AMDGPUNoSubArchNameOffset];
assert(SubArch >= Triple::FirstAMDGPUSubArch &&
SubArch <= Triple::LastAMDGPUSubArch &&
"expected an AMDGPU subarch or NoSubArch");
return AMDGPUNameStrTab
[AMDGPUSubArchTripleNameOffsets[SubArch - Triple::FirstAMDGPUSubArch]];
}
StringRef llvm::AMDGPU::getArchNameR600(GPUKind AK) {
const R600Info *Info = getR600Info(AK);
return Info ? R600NameStrTab[Info->Name] : "";
}
AMDGPU::GPUKind llvm::AMDGPU::parseArchAMDGCN(StringRef CPU) {
return parseArchImpl(CPU, AMDGPUGPUTable, AMDGPUFirstGPUKind,
AMDGPUNameStrTab, AMDGPUGPUAliases);
}
AMDGPU::GPUKind llvm::AMDGPU::parseArchR600(StringRef CPU) {
return parseArchImpl(CPU, R600GPUTable, R600FirstGPUKind, R600NameStrTab,
R600GPUAliases);
}
unsigned AMDGPU::getArchAttrAMDGCN(GPUKind AK) {
const GPUInfo *Info = getAMDGPUInfo(AK);
return Info ? Info->ArchFeatures : FEATURE_NONE;
}
unsigned AMDGPU::getArchAttrAMDGCN(Triple::SubArchType SubArch) {
const GPUInfo *Info = getAMDGPUInfo(getGPUKindFromSubArch(SubArch));
return Info ? Info->ArchFeatures : FEATURE_NONE;
}
R600FeatureKind AMDGPU::getArchAttrR600(GPUKind AK) {
const R600Info *Info = getR600Info(AK);
return Info ? Info->ArchFeatures : R600_FEATURE_NONE;
}
const AMDGPUFeatureBitset &AMDGPU::getFeatureBitset(GPUKind AK) {
static constexpr AMDGPUFeatureBitset Empty{};
const GPUInfo *Info = getAMDGPUInfo(AK);
return Info ? Info->Features : Empty;
}
void AMDGPU::getFeatureNames(const AMDGPUFeatureBitset &Features,
SmallVectorImpl<StringRef> &Names) {
for (unsigned I = 0; I != NUM_FEATURES; ++I) {
if (Features.test(I))
Names.push_back(AMDGPUNameStrTab[AMDGPUFeatureNames[I]]);
}
}
void AMDGPU::fillValidArchListAMDGCN(SmallVectorImpl<StringRef> &Values,
Triple::SubArchType SubArch) {
// XXX: Should this only report unique canonical names?
// An alias shares its GPU's GPUKind, so it is filtered alongside it.
for (unsigned I = 0; I != std::size(AMDGPUGPUTable); ++I) {
GPUKind Kind = static_cast<GPUKind>(AMDGPUFirstGPUKind + I);
if (AMDGPUGPUTable[I].SubArch != Triple::NoSubArch &&
isCPUValidForSubArch(SubArch, Kind))
Values.push_back(AMDGPUNameStrTab[AMDGPUGPUTable[I].Name]);
}
for (const GPUNameAlias &A : AMDGPUGPUAliases) {
if (isCPUValidForSubArch(SubArch, A.Kind))
Values.push_back(AMDGPUNameStrTab[A.AltName]);
}
}
void AMDGPU::fillValidArchListR600(SmallVectorImpl<StringRef> &Values) {
for (const R600Info &Info : R600GPUTable)
Values.push_back(R600NameStrTab[Info.Name]);
for (const GPUNameAlias &A : R600GPUAliases)
Values.push_back(R600NameStrTab[A.AltName]);
}
AMDGPU::IsaVersion AMDGPU::getIsaVersion(StringRef GPU) {
const GPUInfo *Info = getAMDGPUInfo(parseArchAMDGCN(GPU));
return Info ? Info->Version : IsaVersion{0, 0, 0};
}
AMDGPU::IsaVersion AMDGPU::getIsaVersion(Triple::SubArchType SubArch) {
const GPUInfo *Info = getAMDGPUInfo(getGPUKindFromSubArch(SubArch));
return Info ? Info->Version : IsaVersion{0, 0, 0};
}
unsigned AMDGPU::getTotalNumSGPRs(GPUKind AK) {
IsaVersion Version = getIsaVersion(getSubArch(AK));
if (Version.Major >= 8)
return 800;
return 512;
}
unsigned AMDGPU::getTotalNumSGPRs(Triple::SubArchType SubArch) {
IsaVersion Version = getIsaVersion(SubArch);
if (Version.Major >= 8)
return 800;
return 512;
}
unsigned AMDGPU::getAddressableNumSGPRs(GPUKind AK) {
if (getFeatureBitset(AK).test(FEAT_SGPR_INIT_BUG))
return FIXED_NUM_SGPRS_FOR_INIT_BUG;
IsaVersion Version = getIsaVersion(getSubArch(AK));
if (Version.Major >= 10)
return 106;
if (Version.Major >= 8)
return 102;
return 104;
}
unsigned AMDGPU::getAddressableNumSGPRs(Triple::SubArchType SubArch) {
if (getFeatureBitset(getGPUKindFromSubArch(SubArch)).test(FEAT_SGPR_INIT_BUG))
return FIXED_NUM_SGPRS_FOR_INIT_BUG;
IsaVersion Version = getIsaVersion(SubArch);
if (Version.Major >= 10)
return 106;
if (Version.Major >= 8)
return 102;
return 104;
}
unsigned AMDGPU::getSGPRAllocGranule(GPUKind AK) {
IsaVersion Version = getIsaVersion(getSubArch(AK));
if (Version.Major >= 10)
return getAddressableNumSGPRs(AK);
if (Version.Major >= 8)
return 16;
return 8;
}
unsigned AMDGPU::getSGPRAllocGranule(Triple::SubArchType SubArch) {
IsaVersion Version = getIsaVersion(SubArch);
if (Version.Major >= 10)
return getAddressableNumSGPRs(SubArch);
if (Version.Major >= 8)
return 16;
return 8;
}
unsigned AMDGPU::getMaxHWAddressableLocalMemorySize(GPUKind AK) {
const GPUInfo *Info = getAMDGPUInfo(AK);
return Info ? Info->MaxHWAddressableLocalMemorySize : 32768;
}
unsigned
AMDGPU::getMaxHWAddressableLocalMemorySize(Triple::SubArchType SubArch) {
return getMaxHWAddressableLocalMemorySize(getGPUKindFromSubArch(SubArch));
}
unsigned AMDGPU::getMaxWavesPerEU(GPUKind AK) {
const GPUInfo *Info = getAMDGPUInfo(AK);
return Info ? Info->MaxWavesPerEU : 10;
}
unsigned AMDGPU::getMaxWavesPerEU(Triple::SubArchType SubArch) {
return getMaxWavesPerEU(getGPUKindFromSubArch(SubArch));
}
StringRef AMDGPU::getCanonicalArchName(const Triple &T, StringRef Arch) {
assert(T.isAMDGPU());
auto ProcKind = T.isAMDGCN() ? parseArchAMDGCN(Arch) : parseArchR600(Arch);
if (ProcKind == GK_NONE)
return StringRef();
return T.isAMDGCN() ? getArchNameAMDGCN(ProcKind) : getArchNameR600(ProcKind);
}
// Capability features clang queries via the feature bitset but must not
// serialize into the target-feature string.
//
// FIXME: This is hacky, we shouldn't have mismatches between the bitset and
// feature string map.
static const AMDGPUFeatureBitset FrontendOnlyFeatures = {
FEAT_FAST_FMAF, FEAT_FAST_DENORMAL_F32, FEAT_SUPPORTS_WAVE32,
FEAT_SUPPORTS_WGP, FEAT_XNACK_SUPPORT, FEAT_SRAMECC_SUPPORT,
FEAT_XNACK_ON_OFF_MODES};
// Add a GPU's features (minus the frontend-only ones) to \p Features. With \p
// Overwrite false, existing entries are kept so user -mattr overrides win.
static void addGPUFeatures(const GPUInfo &Info, bool Overwrite,
StringMap<bool> &Features) {
SmallVector<StringRef, NUM_FEATURES> Names;
getFeatureNames(Info.Features & ~FrontendOnlyFeatures, Names);
for (StringRef Name : Names) {
if (Overwrite)
Features[Name] = true;
else
Features.insert({Name, true});
}
}
/// Add a GPU's default features to \p Features (preserving user overrides) and
/// validate any requested wavesize.
static std::pair<FeatureError, StringRef>
fillAMDGCNFeatureMap(StringRef GPU, const Triple &T,
StringMap<bool> &Features) {
// With no explicit GPU, the triple's subarch identifies the target.
GPUKind Kind = GPU.empty() && T.getSubArch() != Triple::NoSubArch
? getGPUKindFromSubArch(T.getSubArch())
: parseArchAMDGCN(GPU);
const GPUInfo *Info = getAMDGPUInfo(Kind);
// A bare subarch triple (no -target-cpu) still pins down the target, so it is
// not a null GPU. The target's native wavesize (if single-mode) is in the
// feature bitset; a dual-mode GPU has neither wave bit set.
const bool IsNullGPU = T.getSubArch() == Triple::NoSubArch && GPU.empty();
const bool TargetHasWave32 =
Info && Info->Features.test(FEAT_WAVEFRONTSIZE32);
const bool TargetHasWave64 =
Info && Info->Features.test(FEAT_WAVEFRONTSIZE64);
auto Wave32Itr = Features.find("wavefrontsize32");
auto Wave64Itr = Features.find("wavefrontsize64");
const bool EnableWave32 =
Wave32Itr != Features.end() && Wave32Itr->getValue();
const bool EnableWave64 =
Wave64Itr != Features.end() && Wave64Itr->getValue();
const bool DisableWave32 =
Wave32Itr != Features.end() && !Wave32Itr->getValue();
const bool DisableWave64 =
Wave64Itr != Features.end() && !Wave64Itr->getValue();
if (EnableWave32 && EnableWave64)
return {AMDGPU::INVALID_FEATURE_COMBINATION,
"'+wavefrontsize32' and '+wavefrontsize64' are mutually exclusive"};
if (DisableWave32 && DisableWave64)
return {AMDGPU::INVALID_FEATURE_COMBINATION,
"'-wavefrontsize32' and '-wavefrontsize64' are mutually exclusive"};
if (!IsNullGPU) {
if (TargetHasWave64) {
if (EnableWave32)
return {AMDGPU::UNSUPPORTED_TARGET_FEATURE, "+wavefrontsize32"};
if (DisableWave64)
return {AMDGPU::UNSUPPORTED_TARGET_FEATURE, "-wavefrontsize64"};
}
if (TargetHasWave32) {
if (EnableWave64)
return {AMDGPU::UNSUPPORTED_TARGET_FEATURE, "+wavefrontsize64"};
if (DisableWave32)
return {AMDGPU::UNSUPPORTED_TARGET_FEATURE, "-wavefrontsize32"};
}
}
// Don't assume any wavesize with an unknown subtarget.
// Default to wave32 if target supports both.
if (!IsNullGPU && !EnableWave32 && !EnableWave64 && !TargetHasWave32 &&
!TargetHasWave64)
Features.insert({"wavefrontsize32", true});
// Merge the target defaults, keeping any user -mattr overrides.
if (Info)
addGPUFeatures(*Info, /*Overwrite=*/false, Features);
return {NO_ERROR, StringRef()};
}
/// Fills Features map with default values for given target GPU.
/// \p Features contains overriding target features and this function returns
/// default target features with entries overridden by \p Features.
std::pair<FeatureError, StringRef>
AMDGPU::fillAMDGPUFeatureMap(StringRef GPU, const Triple &T,
StringMap<bool> &Features) {
// XXX - What does the member GPU mean if device name string passed here?
if (T.isSPIRV() && T.getOS() == Triple::OSType::AMDHSA) {
// AMDGCN SPIRV must support the union of all AMDGCN features.
SmallVector<StringRef> GPUs;
fillValidArchListAMDGCN(GPUs);
for (StringRef G : GPUs)
if (const GPUInfo *Info = getAMDGPUInfo(parseArchAMDGCN(G)))
addGPUFeatures(*Info, /*Overwrite=*/true, Features);
Features["wavefrontsize32"] = true;
Features["wavefrontsize64"] = true;
} else if (T.isAMDGCN()) {
return fillAMDGCNFeatureMap(GPU, T, Features);
} else {
if (GPU.empty())
GPU = "r600";
switch (llvm::AMDGPU::parseArchR600(GPU)) {
case GK_CAYMAN:
case GK_CYPRESS:
case GK_RV770:
case GK_RV670:
// TODO: Add fp64 when implemented.
break;
case GK_TURKS:
case GK_CAICOS:
case GK_BARTS:
case GK_SUMO:
case GK_REDWOOD:
case GK_JUNIPER:
case GK_CEDAR:
case GK_RV730:
case GK_RV710:
case GK_RS880:
case GK_R630:
case GK_R600:
break;
default:
llvm_unreachable("Unhandled GPU!");
}
}
return {NO_ERROR, StringRef()};
}
TargetID::TargetID(GPUKind Arch, const Triple &TT, TargetIDSetting XnackSetting,
TargetIDSetting SramEccSetting)
: Arch(Arch),
TargetTripleString(TT.normalize(Triple::CanonicalForm::FOUR_IDENT)),
XnackSetting(XnackSetting), SramEccSetting(SramEccSetting),
IsAMDHSA(TT.getOS() == Triple::AMDHSA) {}
// 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;
return TargetIDSetting::Unsupported;
}
// Derive the architecture from the processor name in \p TargetIDStr. "generic"
// and the empty processor name act as a wildcard.
static GPUKind getGPUKindFromTargetID(const Triple &TT, StringRef TargetIDStr) {
StringRef CPUName = TargetIDStr.split(':').first;
return (CPUName.empty() || CPUName == "generic")
? getGPUKindFromSubArch(TT.getSubArch())
: 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) {
const AMDGPUFeatureBitset &Features = getFeatureBitset(Arch);
XnackSetting = Features.test(FEAT_XNACK_ON_OFF_MODES)
? TargetIDSetting::Any
: TargetIDSetting::Unsupported;
SramEccSetting = Features.test(FEAT_SRAMECC_SUPPORT)
? TargetIDSetting::Any
: TargetIDSetting::Unsupported;
// The first component is the processor; the rest are feature modifiers of the
// form "<feature><+|->".
SmallVector<StringRef, 3> 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) {
// Derive the feature settings from the string. Validity is not checked here;
// parseTargetIDString validates untrusted input.
computeTargetIDFeatures(Arch, TargetIDStr, XnackSetting, SramEccSetting);
}
std::optional<TargetID> TargetID::parse(const Triple &TT,
StringRef ProcAndFeatures) {
if (!TT.isAMDGCN())
return std::nullopt;
// Filter out unrecognized subarch suffixes.
if (TT.getSubArch() == Triple::NoSubArch && TT.getArchName() != "amdgcn")
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 that is consistent with
// the triple's subarch.
StringRef CPUName = ProcAndFeatures.split(':').first;
if (!CPUName.empty() && CPUName != "generic" &&
!isCPUValidForSubArch(TT.getSubArch(), CPUName))
return std::nullopt;
// 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>
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, so the
// processor+features field must be present (even if empty).
SmallVector<StringRef, 5> Parts;
TargetIDDirective.split(Parts, '-', /*MaxSplit=*/4);
if (Parts.size() < 5)
return std::nullopt;
return parse(Triple(Parts[0], Parts[1], Parts[2], Parts[3]), 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)
printFeatureModifiers(StreamRep, getSramEccSetting(), getXnackSetting());
}
std::string TargetID::toString() const {
std::string Str;
raw_string_ostream OS(Str);
OS << *this;
return Str;
}
void TargetID::printCanonicalTargetIDString(raw_ostream &OS) const {
OS << getArchNameAMDGCN(Arch);
printFeatureModifiers(OS, getSramEccSetting(), getXnackSetting());
}
std::string TargetID::getCanonicalFeatureString() 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 &&
TargetTripleString == Other.TargetTripleString;
}
static bool featureProvidesFor(TargetIDSetting Provided,
TargetIDSetting Requested) {
return Provided == TargetIDSetting::Any ||
Provided == TargetIDSetting::Unsupported || Provided == Requested;
}
bool TargetID::isEquivalent(const TargetID &Other) const {
// The processor and feature settings must match exactly
if (Arch != Other.Arch || XnackSetting != Other.XnackSetting ||
SramEccSetting != Other.SramEccSetting)
return false;
return Triple(getTargetTripleString())
.isCompatibleWith(Triple(Other.getTargetTripleString()));
}
bool TargetID::providesFor(const TargetID &Other) const {
// A major-family/generic processor (e.g. amdgpu9) provides for a specific
// member of its family (e.g. gfx900), but not the reverse. Otherwise the
// processors must match.
if (Arch != Other.Arch && Arch != GK_NONE && Other.Arch != GK_NONE) {
Triple::SubArchType ThisSubArch = getSubArch(Arch);
if (ThisSubArch != getMajorSubArch(ThisSubArch) ||
ThisSubArch != getMajorSubArch(getSubArch(Other.Arch)))
return false;
}
if (!featureProvidesFor(XnackSetting, Other.XnackSetting) ||
!featureProvidesFor(SramEccSetting, Other.SramEccSetting))
return false;
return Triple(getTargetTripleString())
.isCompatibleWith(Triple(Other.getTargetTripleString()));
}