| //===-- 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; | |
| } | |
| namespace IME { | |
| static void assertValidXLenForVType(unsigned XLen) { | |
| assert((XLen == 32 || XLen == 64) && "Invalid XLEN"); | |
| } | |
| static unsigned getLambdaShift(unsigned XLen) { | |
| assertValidXLenForVType(XLen); | |
| return XLen - 4; | |
| } | |
| static unsigned getAltFmtAShift(unsigned XLen) { | |
| assertValidXLenForVType(XLen); | |
| return XLen - 5; | |
| } | |
| static unsigned getAltFmtBShift(unsigned XLen) { | |
| assertValidXLenForVType(XLen); | |
| return XLen - 6; | |
| } | |
| static unsigned getBSShift(unsigned XLen) { | |
| assertValidXLenForVType(XLen); | |
| return XLen - 7; | |
| } | |
| unsigned encodeLambda(unsigned Lambda) { | |
| assert(isValidLambda(Lambda) && "Invalid IME lambda"); | |
| if (Lambda == 0) | |
| return 0; | |
| return Log2_32(Lambda) + 1; | |
| } | |
| std::optional<unsigned> decodeLambda(unsigned Encoding) { | |
| assert(Encoding < 8 && "Invalid IME lambda encoding"); | |
| if (Encoding == 0) | |
| return std::nullopt; | |
| return 1U << (Encoding - 1); | |
| } | |
| uint64_t getVTypeFieldsMask(unsigned XLen) { | |
| assertValidXLenForVType(XLen); | |
| return (0x7ULL << getLambdaShift(XLen)) | (1ULL << getAltFmtAShift(XLen)) | | |
| (1ULL << getAltFmtBShift(XLen)) | (1ULL << getBSShift(XLen)); | |
| } | |
| uint64_t encodeVTypeFields(unsigned XLen, unsigned Lambda, bool AltFmtA, | |
| bool AltFmtB, bool BlockSize16) { | |
| assertValidXLenForVType(XLen); | |
| uint64_t VType = uint64_t(encodeLambda(Lambda)) << getLambdaShift(XLen); | |
| if (AltFmtA) | |
| VType |= 1ULL << getAltFmtAShift(XLen); | |
| if (AltFmtB) | |
| VType |= 1ULL << getAltFmtBShift(XLen); | |
| if (BlockSize16) | |
| VType |= 1ULL << getBSShift(XLen); | |
| return VType; | |
| } | |
| uint64_t addVTypeFields(uint64_t VType, unsigned XLen, unsigned Lambda, | |
| bool AltFmtA, bool AltFmtB, bool BlockSize16) { | |
| return (VType & ~getVTypeFieldsMask(XLen)) | | |
| encodeVTypeFields(XLen, Lambda, AltFmtA, AltFmtB, BlockSize16); | |
| } | |
| unsigned getLambdaEncoding(uint64_t VType, unsigned XLen) { | |
| assertValidXLenForVType(XLen); | |
| return (VType >> getLambdaShift(XLen)) & 0x7; | |
| } | |
| std::optional<unsigned> getLambda(uint64_t VType, unsigned XLen) { | |
| return decodeLambda(getLambdaEncoding(VType, XLen)); | |
| } | |
| bool isAltFmtA(uint64_t VType, unsigned XLen) { | |
| assertValidXLenForVType(XLen); | |
| return VType & (1ULL << getAltFmtAShift(XLen)); | |
| } | |
| bool isAltFmtB(uint64_t VType, unsigned XLen) { | |
| assertValidXLenForVType(XLen); | |
| return VType & (1ULL << getAltFmtBShift(XLen)); | |
| } | |
| bool isBlockSize16(uint64_t VType, unsigned XLen) { | |
| assertValidXLenForVType(XLen); | |
| return VType & (1ULL << getBSShift(XLen)); | |
| } | |
| } // namespace IME | |
| 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 |