This patch proposes new a tuning feature string format that helps users
to build a performance model by "configuring" an existing tune CPU,
along with its scheduling model. For example, this string
```
"sifive-x280:single-element-vec-fp64"
```
takes ``sifive-x280`` as the "base" tune CPU and configured it with
``single-element-vec-fp64``. This gives us a performance model that
looks exactly like that of ``sifive-x280``, except some of the 64-bit
vector floating point instructions now produce only a single element per
cycle due to ``single-element-vec-fp64``.
This string could eventually be used in places like ``-mtune`` at the
frontend. Right now, this patch only implements the parser part, which
is put under the TargetParser library.
The grammar for this string is:
```
tune-cpu ::= 'tuning CPU name in lower case'
directive ::= "[a-zA-Z0-9_-]+"
tune-features ::= directive ["," directive]*
```
A *directive* can and can only _enable_ or _disable_ a certain tuning
feature from the tuning CPU. A **positive directive**, like the
``single-element-vec-fp64`` we just saw, enables an additional tuning
feature in the associated tuning model.
A **negative directive**, on the other hand, removes a certain tuning
feature. For example, ``sifive-x390`` already has the
``single-element-vec-fp64`` feature, and we can use
"sifive-x390:no-single-element-vec-fp64" to create a new performance
model that looks nearly the same as ``sifive-x390`` except
``single-element-vec-fp64`` being cut out. In this case,
``no-single-element-vec-fp64`` is a negative directive.
There are additional restrictions on what we can put in the list of
directives, please refer to the documentations for more details.
Right now, this string only accepts directives that are explicitly
supported by the tune CPU. For example, "sifive-x280:prefer-w-inst" is
not a valide string as ``prefer-w-inst`` is not supported by
``sifive-x280`` at this moment. Vendors of these processors are expected
to maintain the compatibility of their supported directives across
different versions.
---------
Co-authored-by: Sam Elliott <aelliott@qti.qualcomm.com>
464 lines
15 KiB
C++
464 lines
15 KiB
C++
//===-- RISCVTargetParser.cpp - Parser for target 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 hardware features
|
|
// for RISC-V CPUs.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/TargetParser/RISCVTargetParser.h"
|
|
#include "llvm/ADT/SetOperations.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/ADT/StringSwitch.h"
|
|
#include "llvm/ADT/StringTable.h"
|
|
#include "llvm/TargetParser/RISCVISAInfo.h"
|
|
|
|
namespace llvm {
|
|
namespace RISCV {
|
|
|
|
char ParserError::ID = 0;
|
|
char ParserWarning::ID = 0;
|
|
|
|
enum CPUKind : unsigned {
|
|
#define PROC(ENUM, NAME, DEFAULT_MARCH, FAST_SCALAR_UNALIGN, \
|
|
FAST_VECTOR_UNALIGN, MVENDORID, MARCHID, MIMPID) \
|
|
CK_##ENUM,
|
|
#define TUNE_PROC(ENUM, NAME) CK_##ENUM,
|
|
#include "llvm/TargetParser/RISCVTargetParserDef.inc"
|
|
};
|
|
|
|
constexpr CPUInfo RISCVCPUInfo[] = {
|
|
#define PROC(ENUM, NAME, DEFAULT_MARCH, FAST_SCALAR_UNALIGN, \
|
|
FAST_VECTOR_UNALIGN, MVENDORID, MARCHID, MIMPID) \
|
|
{ \
|
|
NAME, \
|
|
DEFAULT_MARCH, \
|
|
FAST_SCALAR_UNALIGN, \
|
|
FAST_VECTOR_UNALIGN, \
|
|
{MVENDORID, MARCHID, MIMPID}, \
|
|
},
|
|
#include "llvm/TargetParser/RISCVTargetParserDef.inc"
|
|
};
|
|
|
|
static const CPUInfo *getCPUInfoByName(StringRef CPU) {
|
|
for (auto &C : RISCVCPUInfo)
|
|
if (C.Name == CPU)
|
|
return &C;
|
|
return nullptr;
|
|
}
|
|
|
|
bool hasFastScalarUnalignedAccess(StringRef CPU) {
|
|
const CPUInfo *Info = getCPUInfoByName(CPU);
|
|
return Info && Info->FastScalarUnalignedAccess;
|
|
}
|
|
|
|
bool hasFastVectorUnalignedAccess(StringRef CPU) {
|
|
const CPUInfo *Info = getCPUInfoByName(CPU);
|
|
return Info && Info->FastVectorUnalignedAccess;
|
|
}
|
|
|
|
bool hasValidCPUModel(StringRef CPU) { return getCPUModel(CPU).isValid(); }
|
|
|
|
CPUModel getCPUModel(StringRef CPU) {
|
|
const CPUInfo *Info = getCPUInfoByName(CPU);
|
|
if (!Info)
|
|
return {0, 0, 0};
|
|
return Info->Model;
|
|
}
|
|
|
|
StringRef getCPUNameFromCPUModel(const CPUModel &Model) {
|
|
if (!Model.isValid())
|
|
return "";
|
|
|
|
for (auto &C : RISCVCPUInfo)
|
|
if (C.Model == Model)
|
|
return C.Name;
|
|
return "";
|
|
}
|
|
|
|
bool parseCPU(StringRef CPU, bool IsRV64) {
|
|
const CPUInfo *Info = getCPUInfoByName(CPU);
|
|
|
|
if (!Info)
|
|
return false;
|
|
return Info->is64Bit() == IsRV64;
|
|
}
|
|
|
|
bool parseTuneCPU(StringRef TuneCPU, bool IsRV64) {
|
|
std::optional<CPUKind> Kind =
|
|
llvm::StringSwitch<std::optional<CPUKind>>(TuneCPU)
|
|
#define TUNE_PROC(ENUM, NAME) .Case(NAME, CK_##ENUM)
|
|
#include "llvm/TargetParser/RISCVTargetParserDef.inc"
|
|
.Default(std::nullopt);
|
|
|
|
if (Kind.has_value())
|
|
return true;
|
|
|
|
// Fallback to parsing as a CPU.
|
|
return parseCPU(TuneCPU, IsRV64);
|
|
}
|
|
|
|
StringRef getMArchFromMcpu(StringRef CPU) {
|
|
const CPUInfo *Info = getCPUInfoByName(CPU);
|
|
if (!Info)
|
|
return "";
|
|
return Info->DefaultMarch;
|
|
}
|
|
|
|
void fillValidCPUArchList(SmallVectorImpl<StringRef> &Values, bool IsRV64) {
|
|
for (const auto &C : RISCVCPUInfo) {
|
|
if (IsRV64 == C.is64Bit())
|
|
Values.emplace_back(C.Name);
|
|
}
|
|
}
|
|
|
|
void fillValidTuneCPUArchList(SmallVectorImpl<StringRef> &Values, bool IsRV64) {
|
|
for (const auto &C : RISCVCPUInfo) {
|
|
if (IsRV64 == C.is64Bit())
|
|
Values.emplace_back(C.Name);
|
|
}
|
|
#define TUNE_PROC(ENUM, NAME) Values.emplace_back(StringRef(NAME));
|
|
#include "llvm/TargetParser/RISCVTargetParserDef.inc"
|
|
}
|
|
|
|
// This function is currently used by IREE, so it's not dead code.
|
|
void getFeaturesForCPU(StringRef CPU,
|
|
SmallVectorImpl<std::string> &EnabledFeatures,
|
|
bool NeedPlus) {
|
|
StringRef MarchFromCPU = llvm::RISCV::getMArchFromMcpu(CPU);
|
|
if (MarchFromCPU == "")
|
|
return;
|
|
|
|
EnabledFeatures.clear();
|
|
auto RII = RISCVISAInfo::parseArchString(
|
|
MarchFromCPU, /* EnableExperimentalExtension */ true);
|
|
|
|
if (llvm::errorToBool(RII.takeError()))
|
|
return;
|
|
|
|
std::vector<std::string> FeatStrings =
|
|
(*RII)->toFeatures(/* AddAllExtensions */ false);
|
|
for (const auto &F : FeatStrings)
|
|
if (NeedPlus)
|
|
EnabledFeatures.push_back(F);
|
|
else
|
|
EnabledFeatures.push_back(F.substr(1));
|
|
}
|
|
|
|
namespace {
|
|
class RISCVTuneFeatureLookupTable {
|
|
struct RISCVTuneFeature {
|
|
unsigned PosIdx;
|
|
unsigned NegIdx;
|
|
unsigned FeatureIdx;
|
|
};
|
|
|
|
struct RISCVImpliedTuneFeature {
|
|
unsigned FeatureIdx;
|
|
unsigned ImpliedFeatureIdx;
|
|
};
|
|
|
|
struct RISCVConfigurableTuneFeatures {
|
|
StringRef Processor;
|
|
unsigned DirectiveIdx;
|
|
|
|
bool operator<(const RISCVConfigurableTuneFeatures &RHS) const {
|
|
return Processor < RHS.Processor;
|
|
}
|
|
};
|
|
|
|
#define GET_TUNE_FEATURES
|
|
#define GET_CONFIGURABLE_TUNE_FEATURES
|
|
#include "llvm/TargetParser/RISCVTargetParserDef.inc"
|
|
|
|
// Positive directive name -> Feature name
|
|
StringMap<StringRef> PositiveMap;
|
|
// Negative directive name -> Feature name
|
|
StringMap<StringRef> NegativeMap;
|
|
|
|
StringMap<SmallVector<StringRef>> ImpliedFeatureMap;
|
|
StringMap<SmallVector<StringRef>> InvImpliedFeatureMap;
|
|
|
|
public:
|
|
using SmallStringSet = SmallSet<StringRef, 4>;
|
|
|
|
static void getAllTuneFeatures(SmallVectorImpl<StringRef> &Features) {
|
|
for (const auto &TuneFeature : TuneFeatures)
|
|
Features.push_back(TuneFeatureStrings[TuneFeature.FeatureIdx]);
|
|
}
|
|
|
|
static void getConfigurableFeatures(StringRef ProcName,
|
|
SmallStringSet &Directives) {
|
|
// Entries for the same processor are always put together.
|
|
auto [ItFirst, ItEnd] =
|
|
std::equal_range(std::begin(ConfigurableTuneFeatures),
|
|
std::end(ConfigurableTuneFeatures),
|
|
RISCVConfigurableTuneFeatures{ProcName, 0});
|
|
for (; ItFirst != ItEnd; ++ItFirst)
|
|
Directives.insert(TuneFeatureStrings[ItFirst->DirectiveIdx]);
|
|
}
|
|
|
|
RISCVTuneFeatureLookupTable() {
|
|
for (const auto &TuneFeature : TuneFeatures) {
|
|
StringRef PosDirective = TuneFeatureStrings[TuneFeature.PosIdx];
|
|
StringRef NegDirective = TuneFeatureStrings[TuneFeature.NegIdx];
|
|
StringRef FeatureName = TuneFeatureStrings[TuneFeature.FeatureIdx];
|
|
PositiveMap[PosDirective] = FeatureName;
|
|
NegativeMap[NegDirective] = FeatureName;
|
|
}
|
|
|
|
for (const auto &Imp : ImpliedTuneFeatures) {
|
|
StringRef Feature = TuneFeatureStrings[Imp.FeatureIdx];
|
|
StringRef ImpliedFeature = TuneFeatureStrings[Imp.ImpliedFeatureIdx];
|
|
ImpliedFeatureMap[Feature].push_back(ImpliedFeature);
|
|
InvImpliedFeatureMap[ImpliedFeature].push_back(Feature);
|
|
}
|
|
}
|
|
|
|
/// Returns {Feature name, Is positive or not}, or empty feature name
|
|
/// if not found.
|
|
std::pair<StringRef, bool> getFeature(StringRef DirectiveName) const {
|
|
auto It = PositiveMap.find(DirectiveName);
|
|
if (It != PositiveMap.end())
|
|
return {It->getValue(), /*IsPositive=*/true};
|
|
|
|
return {NegativeMap.lookup(DirectiveName), /*IsPositive=*/false};
|
|
}
|
|
|
|
/// Returns the implied features, or empty ArrayRef if not found. Note:
|
|
/// ImpliedFeatureMap / InvImpliedFeatureMap are the owners of these implied
|
|
/// feature lists, so we can just return the ArrayRef.
|
|
ArrayRef<StringRef> featureImplies(StringRef FeatureName,
|
|
bool Inverse = false) const {
|
|
const auto &Map = Inverse ? InvImpliedFeatureMap : ImpliedFeatureMap;
|
|
auto It = Map.find(FeatureName);
|
|
if (It == Map.end())
|
|
return {};
|
|
return It->second;
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
void getAllTuneFeatures(SmallVectorImpl<StringRef> &Features) {
|
|
RISCVTuneFeatureLookupTable::getAllTuneFeatures(Features);
|
|
}
|
|
|
|
Error parseTuneFeatureString(StringRef ProcName, StringRef TFString,
|
|
SmallVectorImpl<std::string> &ResFeatures) {
|
|
RISCVTuneFeatureLookupTable TFLookup;
|
|
using SmallStringSet = RISCVTuneFeatureLookupTable::SmallStringSet;
|
|
|
|
// Do not create ParserWarning right away. Instead, we store the warning
|
|
// message until the last moment.
|
|
std::string WarningMsg;
|
|
|
|
TFString = TFString.trim();
|
|
if (TFString.empty())
|
|
return Error::success();
|
|
|
|
// Note: StringSet is not really ergonomic to use in this case here.
|
|
SmallStringSet PositiveFeatures;
|
|
SmallStringSet NegativeFeatures;
|
|
SmallStringSet PerProcDirectives;
|
|
RISCVTuneFeatureLookupTable::getConfigurableFeatures(ProcName,
|
|
PerProcDirectives);
|
|
if (PerProcDirectives.empty() && !ProcName.empty())
|
|
return make_error<ParserError>("Processor '" + Twine(ProcName) +
|
|
"' has no "
|
|
"configurable tuning features");
|
|
|
|
// Phase 1: Collect explicit features.
|
|
StringRef DirectiveStr;
|
|
do {
|
|
std::tie(DirectiveStr, TFString) = TFString.split(",");
|
|
auto [FeatureName, IsPositive] = TFLookup.getFeature(DirectiveStr);
|
|
if (FeatureName.empty()) {
|
|
raw_string_ostream SS(WarningMsg);
|
|
SS << "unrecognized tune feature directive '" << DirectiveStr << "'";
|
|
continue;
|
|
}
|
|
|
|
auto &Features = IsPositive ? PositiveFeatures : NegativeFeatures;
|
|
if (!Features.insert(FeatureName).second)
|
|
return make_error<ParserError>(
|
|
"cannot specify more than one instance of '" + Twine(DirectiveStr) +
|
|
"'");
|
|
|
|
if (!PerProcDirectives.count(DirectiveStr) && !ProcName.empty())
|
|
return make_error<ParserError>("Directive '" + Twine(DirectiveStr) +
|
|
"' is not "
|
|
"allowed to be used with processor '" +
|
|
Twine(ProcName) + "'");
|
|
} while (!TFString.empty());
|
|
|
|
auto Intersection =
|
|
llvm::set_intersection(PositiveFeatures, NegativeFeatures);
|
|
if (!Intersection.empty()) {
|
|
std::string IntersectedStr = join(Intersection, "', '");
|
|
return make_error<ParserError>("Feature(s) '" + Twine(IntersectedStr) +
|
|
"' cannot appear in both "
|
|
"positive and negative directives");
|
|
}
|
|
|
|
// Phase 2: Derive implied features.
|
|
SmallStringSet DerivedPosFeatures;
|
|
SmallStringSet DerivedNegFeatures;
|
|
for (StringRef PF : PositiveFeatures) {
|
|
if (auto FeatureList = TFLookup.featureImplies(PF); !FeatureList.empty())
|
|
DerivedPosFeatures.insert_range(FeatureList);
|
|
}
|
|
for (StringRef NF : NegativeFeatures) {
|
|
if (auto FeatureList = TFLookup.featureImplies(NF, /*Inverse=*/true);
|
|
!FeatureList.empty())
|
|
DerivedNegFeatures.insert_range(FeatureList);
|
|
}
|
|
PositiveFeatures.insert_range(DerivedPosFeatures);
|
|
NegativeFeatures.insert_range(DerivedNegFeatures);
|
|
|
|
Intersection = llvm::set_intersection(PositiveFeatures, NegativeFeatures);
|
|
if (!Intersection.empty()) {
|
|
std::string IntersectedStr = join(Intersection, "', '");
|
|
return make_error<ParserError>("Feature(s) '" + Twine(IntersectedStr) +
|
|
"' were implied by both "
|
|
"positive and negative directives");
|
|
}
|
|
|
|
// Export the result.
|
|
const std::string PosPrefix("+");
|
|
const std::string NegPrefix("-");
|
|
for (StringRef PF : PositiveFeatures)
|
|
ResFeatures.emplace_back(PosPrefix + PF.str());
|
|
for (StringRef NF : NegativeFeatures)
|
|
ResFeatures.emplace_back(NegPrefix + NF.str());
|
|
|
|
if (WarningMsg.empty())
|
|
return Error::success();
|
|
|
|
return make_error<ParserWarning>(WarningMsg);
|
|
}
|
|
|
|
void getCPUConfigurableTuneFeatures(StringRef CPU,
|
|
SmallVectorImpl<StringRef> &Directives) {
|
|
RISCVTuneFeatureLookupTable::SmallStringSet DirectiveSet;
|
|
RISCVTuneFeatureLookupTable::getConfigurableFeatures(CPU, DirectiveSet);
|
|
Directives.assign(DirectiveSet.begin(), DirectiveSet.end());
|
|
}
|
|
} // namespace RISCV
|
|
|
|
namespace RISCVVType {
|
|
// Encode VTYPE into the binary format used by the the VSETVLI instruction which
|
|
// is used by our MC layer representation.
|
|
//
|
|
// Bits | Name | Description
|
|
// -----+------------+------------------------------------------------
|
|
// 8 | altfmt | Alternative format for bf16/ofp8
|
|
// 7 | vma | Vector mask agnostic
|
|
// 6 | vta | Vector tail agnostic
|
|
// 5:3 | vsew[2:0] | Standard element width (SEW) setting
|
|
// 2:0 | vlmul[2:0] | Vector register group multiplier (LMUL) setting
|
|
unsigned encodeVTYPE(VLMUL VLMul, unsigned SEW, bool TailAgnostic,
|
|
bool MaskAgnostic, bool AltFmt) {
|
|
assert(isValidSEW(SEW) && "Invalid SEW");
|
|
unsigned VLMulBits = static_cast<unsigned>(VLMul);
|
|
unsigned VSEWBits = encodeSEW(SEW);
|
|
unsigned VTypeI = (VSEWBits << 3) | (VLMulBits & 0x7);
|
|
if (TailAgnostic)
|
|
VTypeI |= 0x40;
|
|
if (MaskAgnostic)
|
|
VTypeI |= 0x80;
|
|
if (AltFmt)
|
|
VTypeI |= 0x100;
|
|
|
|
return VTypeI;
|
|
}
|
|
|
|
unsigned encodeXSfmmVType(unsigned SEW, unsigned Widen, bool AltFmt) {
|
|
assert(isValidSEW(SEW) && "Invalid SEW");
|
|
assert((Widen == 1 || Widen == 2 || Widen == 4) && "Invalid Widen");
|
|
unsigned VSEWBits = encodeSEW(SEW);
|
|
unsigned TWiden = Log2_32(Widen) + 1;
|
|
unsigned VTypeI = (VSEWBits << 3) | AltFmt << 8 | TWiden << 9;
|
|
return VTypeI;
|
|
}
|
|
|
|
std::pair<unsigned, bool> decodeVLMUL(VLMUL VLMul) {
|
|
switch (VLMul) {
|
|
default:
|
|
llvm_unreachable("Unexpected LMUL value!");
|
|
case LMUL_1:
|
|
case LMUL_2:
|
|
case LMUL_4:
|
|
case LMUL_8:
|
|
return std::make_pair(1 << static_cast<unsigned>(VLMul), false);
|
|
case LMUL_F2:
|
|
case LMUL_F4:
|
|
case LMUL_F8:
|
|
return std::make_pair(1 << (8 - static_cast<unsigned>(VLMul)), true);
|
|
}
|
|
}
|
|
|
|
void printVType(unsigned VType, raw_ostream &OS) {
|
|
unsigned Sew = getSEW(VType);
|
|
OS << "e" << Sew;
|
|
|
|
bool AltFmt = RISCVVType::isAltFmt(VType);
|
|
if (AltFmt)
|
|
OS << "alt";
|
|
|
|
unsigned LMul;
|
|
bool Fractional;
|
|
std::tie(LMul, Fractional) = decodeVLMUL(getVLMUL(VType));
|
|
|
|
if (Fractional)
|
|
OS << ", mf";
|
|
else
|
|
OS << ", m";
|
|
OS << LMul;
|
|
|
|
if (isTailAgnostic(VType))
|
|
OS << ", ta";
|
|
else
|
|
OS << ", tu";
|
|
|
|
if (isMaskAgnostic(VType))
|
|
OS << ", ma";
|
|
else
|
|
OS << ", mu";
|
|
}
|
|
|
|
void printXSfmmVType(unsigned VType, raw_ostream &OS) {
|
|
OS << "e" << getSEW(VType) << ", w" << getXSfmmWiden(VType);
|
|
}
|
|
|
|
unsigned getSEWLMULRatio(unsigned SEW, VLMUL VLMul) {
|
|
unsigned LMul;
|
|
bool Fractional;
|
|
std::tie(LMul, Fractional) = decodeVLMUL(VLMul);
|
|
|
|
// Convert LMul to a fixed point value with 3 fractional bits.
|
|
LMul = Fractional ? (8 / LMul) : (LMul * 8);
|
|
|
|
assert(SEW >= 8 && "Unexpected SEW value");
|
|
return (SEW * 8) / LMul;
|
|
}
|
|
|
|
std::optional<VLMUL> getSameRatioLMUL(unsigned Ratio, unsigned EEW) {
|
|
unsigned EMULFixedPoint = (EEW * 8) / Ratio;
|
|
bool Fractional = EMULFixedPoint < 8;
|
|
unsigned EMUL = Fractional ? 8 / EMULFixedPoint : EMULFixedPoint / 8;
|
|
if (!isValidLMUL(EMUL, Fractional))
|
|
return std::nullopt;
|
|
return RISCVVType::encodeLMUL(EMUL, Fractional);
|
|
}
|
|
|
|
} // namespace RISCVVType
|
|
|
|
} // namespace llvm
|