| 1 | //===-- RISCVTargetParser.cpp - Parser for target features ------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file implements a target parser to recognise hardware features |
| 10 | // for RISC-V CPUs. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "llvm/TargetParser/RISCVTargetParser.h" |
| 15 | #include "llvm/ADT/SetOperations.h" |
| 16 | #include "llvm/ADT/SmallSet.h" |
| 17 | #include "llvm/ADT/SmallVector.h" |
| 18 | #include "llvm/ADT/StringExtras.h" |
| 19 | #include "llvm/ADT/StringSwitch.h" |
| 20 | #include "llvm/ADT/StringTable.h" |
| 21 | #include "llvm/TargetParser/RISCVISAInfo.h" |
| 22 | |
| 23 | namespace llvm { |
| 24 | namespace RISCV { |
| 25 | |
| 26 | char ParserError::ID = 0; |
| 27 | char ParserWarning::ID = 0; |
| 28 | |
| 29 | enum CPUKind : unsigned { |
| 30 | #define PROC(ENUM, NAME, DEFAULT_MARCH, FAST_SCALAR_UNALIGN, \ |
| 31 | FAST_VECTOR_UNALIGN, MVENDORID, MARCHID, MIMPID) \ |
| 32 | CK_##ENUM, |
| 33 | #define TUNE_PROC(ENUM, NAME) CK_##ENUM, |
| 34 | #include "llvm/TargetParser/RISCVTargetParserDef.inc" |
| 35 | }; |
| 36 | |
| 37 | constexpr CPUInfo RISCVCPUInfo[] = { |
| 38 | #define PROC(ENUM, NAME, DEFAULT_MARCH, FAST_SCALAR_UNALIGN, \ |
| 39 | FAST_VECTOR_UNALIGN, MVENDORID, MARCHID, MIMPID) \ |
| 40 | { \ |
| 41 | NAME, \ |
| 42 | DEFAULT_MARCH, \ |
| 43 | FAST_SCALAR_UNALIGN, \ |
| 44 | FAST_VECTOR_UNALIGN, \ |
| 45 | {MVENDORID, MARCHID, MIMPID}, \ |
| 46 | }, |
| 47 | #include "llvm/TargetParser/RISCVTargetParserDef.inc" |
| 48 | }; |
| 49 | |
| 50 | static const CPUInfo *getCPUInfoByName(StringRef CPU) { |
| 51 | for (auto &C : RISCVCPUInfo) |
| 52 | if (C.Name == CPU) |
| 53 | return &C; |
| 54 | return nullptr; |
| 55 | } |
| 56 | |
| 57 | bool hasFastScalarUnalignedAccess(StringRef CPU) { |
| 58 | const CPUInfo *Info = getCPUInfoByName(CPU); |
| 59 | return Info && Info->FastScalarUnalignedAccess; |
| 60 | } |
| 61 | |
| 62 | bool hasFastVectorUnalignedAccess(StringRef CPU) { |
| 63 | const CPUInfo *Info = getCPUInfoByName(CPU); |
| 64 | return Info && Info->FastVectorUnalignedAccess; |
| 65 | } |
| 66 | |
| 67 | bool hasValidCPUModel(StringRef CPU) { return getCPUModel(CPU).isValid(); } |
| 68 | |
| 69 | CPUModel getCPUModel(StringRef CPU) { |
| 70 | const CPUInfo *Info = getCPUInfoByName(CPU); |
| 71 | if (!Info) |
| 72 | return {.MVendorID: 0, .MArchID: 0, .MImpID: 0}; |
| 73 | return Info->Model; |
| 74 | } |
| 75 | |
| 76 | StringRef getCPUNameFromCPUModel(const CPUModel &Model) { |
| 77 | if (!Model.isValid()) |
| 78 | return "" ; |
| 79 | |
| 80 | for (auto &C : RISCVCPUInfo) |
| 81 | if (C.Model == Model) |
| 82 | return C.Name; |
| 83 | return "" ; |
| 84 | } |
| 85 | |
| 86 | bool parseCPU(StringRef CPU, bool IsRV64) { |
| 87 | const CPUInfo *Info = getCPUInfoByName(CPU); |
| 88 | |
| 89 | if (!Info) |
| 90 | return false; |
| 91 | return Info->is64Bit() == IsRV64; |
| 92 | } |
| 93 | |
| 94 | bool parseTuneCPU(StringRef TuneCPU, bool IsRV64) { |
| 95 | std::optional<CPUKind> Kind = |
| 96 | llvm::StringSwitch<std::optional<CPUKind>>(TuneCPU) |
| 97 | #define TUNE_PROC(ENUM, NAME) .Case(NAME, CK_##ENUM) |
| 98 | #include "llvm/TargetParser/RISCVTargetParserDef.inc" |
| 99 | .Default(Value: std::nullopt); |
| 100 | |
| 101 | if (Kind.has_value()) |
| 102 | return true; |
| 103 | |
| 104 | // Fallback to parsing as a CPU. |
| 105 | return parseCPU(CPU: TuneCPU, IsRV64); |
| 106 | } |
| 107 | |
| 108 | StringRef getMArchFromMcpu(StringRef CPU) { |
| 109 | const CPUInfo *Info = getCPUInfoByName(CPU); |
| 110 | if (!Info) |
| 111 | return "" ; |
| 112 | return Info->DefaultMarch; |
| 113 | } |
| 114 | |
| 115 | void fillValidCPUArchList(SmallVectorImpl<StringRef> &Values, bool IsRV64) { |
| 116 | for (const auto &C : RISCVCPUInfo) { |
| 117 | if (IsRV64 == C.is64Bit()) |
| 118 | Values.emplace_back(Args: C.Name); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | void fillValidTuneCPUArchList(SmallVectorImpl<StringRef> &Values, bool IsRV64) { |
| 123 | for (const auto &C : RISCVCPUInfo) { |
| 124 | if (IsRV64 == C.is64Bit()) |
| 125 | Values.emplace_back(Args: C.Name); |
| 126 | } |
| 127 | #define TUNE_PROC(ENUM, NAME) Values.emplace_back(StringRef(NAME)); |
| 128 | #include "llvm/TargetParser/RISCVTargetParserDef.inc" |
| 129 | } |
| 130 | |
| 131 | // This function is currently used by IREE, so it's not dead code. |
| 132 | void getFeaturesForCPU(StringRef CPU, |
| 133 | SmallVectorImpl<std::string> &EnabledFeatures, |
| 134 | bool NeedPlus) { |
| 135 | StringRef MarchFromCPU = llvm::RISCV::getMArchFromMcpu(CPU); |
| 136 | if (MarchFromCPU == "" ) |
| 137 | return; |
| 138 | |
| 139 | EnabledFeatures.clear(); |
| 140 | auto RII = RISCVISAInfo::parseArchString( |
| 141 | Arch: MarchFromCPU, /* EnableExperimentalExtension */ true); |
| 142 | |
| 143 | if (llvm::errorToBool(Err: RII.takeError())) |
| 144 | return; |
| 145 | |
| 146 | std::vector<std::string> FeatStrings = |
| 147 | (*RII)->toFeatures(/* AddAllExtensions */ false); |
| 148 | for (const auto &F : FeatStrings) |
| 149 | if (NeedPlus) |
| 150 | EnabledFeatures.push_back(Elt: F); |
| 151 | else |
| 152 | EnabledFeatures.push_back(Elt: F.substr(pos: 1)); |
| 153 | } |
| 154 | |
| 155 | namespace { |
| 156 | class RISCVTuneFeatureLookupTable { |
| 157 | struct RISCVTuneFeature { |
| 158 | unsigned PosIdx; |
| 159 | unsigned NegIdx; |
| 160 | unsigned FeatureIdx; |
| 161 | }; |
| 162 | |
| 163 | struct RISCVImpliedTuneFeature { |
| 164 | unsigned FeatureIdx; |
| 165 | unsigned ImpliedFeatureIdx; |
| 166 | }; |
| 167 | |
| 168 | struct RISCVConfigurableTuneFeatures { |
| 169 | StringRef Processor; |
| 170 | unsigned DirectiveIdx; |
| 171 | |
| 172 | bool operator<(const RISCVConfigurableTuneFeatures &RHS) const { |
| 173 | return Processor < RHS.Processor; |
| 174 | } |
| 175 | }; |
| 176 | |
| 177 | #define GET_TUNE_FEATURES |
| 178 | #define GET_CONFIGURABLE_TUNE_FEATURES |
| 179 | #include "llvm/TargetParser/RISCVTargetParserDef.inc" |
| 180 | |
| 181 | // Positive directive name -> Feature name |
| 182 | StringMap<StringRef> PositiveMap; |
| 183 | // Negative directive name -> Feature name |
| 184 | StringMap<StringRef> NegativeMap; |
| 185 | |
| 186 | StringMap<SmallVector<StringRef>> ImpliedFeatureMap; |
| 187 | StringMap<SmallVector<StringRef>> InvImpliedFeatureMap; |
| 188 | |
| 189 | public: |
| 190 | using SmallStringSet = SmallSet<StringRef, 4>; |
| 191 | |
| 192 | static void getAllTuneFeatures(SmallVectorImpl<StringRef> &Features) { |
| 193 | for (const auto &TuneFeature : TuneFeatures) |
| 194 | Features.push_back(Elt: TuneFeatureStrings[TuneFeature.FeatureIdx]); |
| 195 | } |
| 196 | |
| 197 | static void getConfigurableFeatures(StringRef ProcName, |
| 198 | SmallStringSet &Directives) { |
| 199 | // Entries for the same processor are always put together. |
| 200 | auto [ItFirst, ItEnd] = |
| 201 | std::equal_range(first: std::begin(arr: ConfigurableTuneFeatures), |
| 202 | last: std::end(arr: ConfigurableTuneFeatures), |
| 203 | val: RISCVConfigurableTuneFeatures{.Processor: ProcName, .DirectiveIdx: 0}); |
| 204 | for (; ItFirst != ItEnd; ++ItFirst) |
| 205 | Directives.insert(V: TuneFeatureStrings[ItFirst->DirectiveIdx]); |
| 206 | } |
| 207 | |
| 208 | RISCVTuneFeatureLookupTable() { |
| 209 | for (const auto &TuneFeature : TuneFeatures) { |
| 210 | StringRef PosDirective = TuneFeatureStrings[TuneFeature.PosIdx]; |
| 211 | StringRef NegDirective = TuneFeatureStrings[TuneFeature.NegIdx]; |
| 212 | StringRef FeatureName = TuneFeatureStrings[TuneFeature.FeatureIdx]; |
| 213 | PositiveMap[PosDirective] = FeatureName; |
| 214 | NegativeMap[NegDirective] = FeatureName; |
| 215 | } |
| 216 | |
| 217 | for (const auto &Imp : ImpliedTuneFeatures) { |
| 218 | StringRef Feature = TuneFeatureStrings[Imp.FeatureIdx]; |
| 219 | StringRef ImpliedFeature = TuneFeatureStrings[Imp.ImpliedFeatureIdx]; |
| 220 | ImpliedFeatureMap[Feature].push_back(Elt: ImpliedFeature); |
| 221 | InvImpliedFeatureMap[ImpliedFeature].push_back(Elt: Feature); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | /// Returns {Feature name, Is positive or not}, or empty feature name |
| 226 | /// if not found. |
| 227 | std::pair<StringRef, bool> getFeature(StringRef DirectiveName) const { |
| 228 | auto It = PositiveMap.find(Key: DirectiveName); |
| 229 | if (It != PositiveMap.end()) |
| 230 | return {It->getValue(), /*IsPositive=*/true}; |
| 231 | |
| 232 | return {NegativeMap.lookup(Key: DirectiveName), /*IsPositive=*/false}; |
| 233 | } |
| 234 | |
| 235 | /// Returns the implied features, or empty ArrayRef if not found. Note: |
| 236 | /// ImpliedFeatureMap / InvImpliedFeatureMap are the owners of these implied |
| 237 | /// feature lists, so we can just return the ArrayRef. |
| 238 | ArrayRef<StringRef> featureImplies(StringRef FeatureName, |
| 239 | bool Inverse = false) const { |
| 240 | const auto &Map = Inverse ? InvImpliedFeatureMap : ImpliedFeatureMap; |
| 241 | auto It = Map.find(Key: FeatureName); |
| 242 | if (It == Map.end()) |
| 243 | return {}; |
| 244 | return It->second; |
| 245 | } |
| 246 | }; |
| 247 | } // namespace |
| 248 | |
| 249 | void getAllTuneFeatures(SmallVectorImpl<StringRef> &Features) { |
| 250 | RISCVTuneFeatureLookupTable::getAllTuneFeatures(Features); |
| 251 | } |
| 252 | |
| 253 | Error parseTuneFeatureString(StringRef ProcName, StringRef TFString, |
| 254 | SmallVectorImpl<std::string> &ResFeatures) { |
| 255 | RISCVTuneFeatureLookupTable TFLookup; |
| 256 | using SmallStringSet = RISCVTuneFeatureLookupTable::SmallStringSet; |
| 257 | |
| 258 | // Do not create ParserWarning right away. Instead, we store the warning |
| 259 | // message until the last moment. |
| 260 | std::string WarningMsg; |
| 261 | |
| 262 | TFString = TFString.trim(); |
| 263 | if (TFString.empty()) |
| 264 | return Error::success(); |
| 265 | |
| 266 | // Note: StringSet is not really ergonomic to use in this case here. |
| 267 | SmallStringSet PositiveFeatures; |
| 268 | SmallStringSet NegativeFeatures; |
| 269 | SmallStringSet PerProcDirectives; |
| 270 | RISCVTuneFeatureLookupTable::getConfigurableFeatures(ProcName, |
| 271 | Directives&: PerProcDirectives); |
| 272 | if (PerProcDirectives.empty() && !ProcName.empty()) |
| 273 | return make_error<ParserError>(Args: "Processor '" + Twine(ProcName) + |
| 274 | "' has no " |
| 275 | "configurable tuning features" ); |
| 276 | |
| 277 | // Phase 1: Collect explicit features. |
| 278 | StringRef DirectiveStr; |
| 279 | do { |
| 280 | std::tie(args&: DirectiveStr, args&: TFString) = TFString.split(Separator: "," ); |
| 281 | auto [FeatureName, IsPositive] = TFLookup.getFeature(DirectiveName: DirectiveStr); |
| 282 | if (FeatureName.empty()) { |
| 283 | raw_string_ostream SS(WarningMsg); |
| 284 | SS << "unrecognized tune feature directive '" << DirectiveStr << "'" ; |
| 285 | continue; |
| 286 | } |
| 287 | |
| 288 | auto &Features = IsPositive ? PositiveFeatures : NegativeFeatures; |
| 289 | if (!Features.insert(V: FeatureName).second) |
| 290 | return make_error<ParserError>( |
| 291 | Args: "cannot specify more than one instance of '" + Twine(DirectiveStr) + |
| 292 | "'" ); |
| 293 | |
| 294 | if (!PerProcDirectives.count(V: DirectiveStr) && !ProcName.empty()) |
| 295 | return make_error<ParserError>(Args: "Directive '" + Twine(DirectiveStr) + |
| 296 | "' is not " |
| 297 | "allowed to be used with processor '" + |
| 298 | Twine(ProcName) + "'" ); |
| 299 | } while (!TFString.empty()); |
| 300 | |
| 301 | auto Intersection = |
| 302 | llvm::set_intersection(S1: PositiveFeatures, S2: NegativeFeatures); |
| 303 | if (!Intersection.empty()) { |
| 304 | std::string IntersectedStr = join(R&: Intersection, Separator: "', '" ); |
| 305 | return make_error<ParserError>(Args: "Feature(s) '" + Twine(IntersectedStr) + |
| 306 | "' cannot appear in both " |
| 307 | "positive and negative directives" ); |
| 308 | } |
| 309 | |
| 310 | // Phase 2: Derive implied features. |
| 311 | SmallStringSet DerivedPosFeatures; |
| 312 | SmallStringSet DerivedNegFeatures; |
| 313 | for (StringRef PF : PositiveFeatures) { |
| 314 | if (auto FeatureList = TFLookup.featureImplies(FeatureName: PF); !FeatureList.empty()) |
| 315 | DerivedPosFeatures.insert_range(R&: FeatureList); |
| 316 | } |
| 317 | for (StringRef NF : NegativeFeatures) { |
| 318 | if (auto FeatureList = TFLookup.featureImplies(FeatureName: NF, /*Inverse=*/true); |
| 319 | !FeatureList.empty()) |
| 320 | DerivedNegFeatures.insert_range(R&: FeatureList); |
| 321 | } |
| 322 | PositiveFeatures.insert_range(R&: DerivedPosFeatures); |
| 323 | NegativeFeatures.insert_range(R&: DerivedNegFeatures); |
| 324 | |
| 325 | Intersection = llvm::set_intersection(S1: PositiveFeatures, S2: NegativeFeatures); |
| 326 | if (!Intersection.empty()) { |
| 327 | std::string IntersectedStr = join(R&: Intersection, Separator: "', '" ); |
| 328 | return make_error<ParserError>(Args: "Feature(s) '" + Twine(IntersectedStr) + |
| 329 | "' were implied by both " |
| 330 | "positive and negative directives" ); |
| 331 | } |
| 332 | |
| 333 | // Export the result. |
| 334 | const std::string PosPrefix("+" ); |
| 335 | const std::string NegPrefix("-" ); |
| 336 | for (StringRef PF : PositiveFeatures) |
| 337 | ResFeatures.emplace_back(Args: PosPrefix + PF.str()); |
| 338 | for (StringRef NF : NegativeFeatures) |
| 339 | ResFeatures.emplace_back(Args: NegPrefix + NF.str()); |
| 340 | |
| 341 | if (WarningMsg.empty()) |
| 342 | return Error::success(); |
| 343 | |
| 344 | return make_error<ParserWarning>(Args&: WarningMsg); |
| 345 | } |
| 346 | |
| 347 | void getCPUConfigurableTuneFeatures(StringRef CPU, |
| 348 | SmallVectorImpl<StringRef> &Directives) { |
| 349 | RISCVTuneFeatureLookupTable::SmallStringSet DirectiveSet; |
| 350 | RISCVTuneFeatureLookupTable::getConfigurableFeatures(ProcName: CPU, Directives&: DirectiveSet); |
| 351 | Directives.assign(in_start: DirectiveSet.begin(), in_end: DirectiveSet.end()); |
| 352 | } |
| 353 | } // namespace RISCV |
| 354 | |
| 355 | namespace RISCVVType { |
| 356 | // Encode VTYPE into the binary format used by the the VSETVLI instruction which |
| 357 | // is used by our MC layer representation. |
| 358 | // |
| 359 | // Bits | Name | Description |
| 360 | // -----+------------+------------------------------------------------ |
| 361 | // 8 | altfmt | Alternative format for bf16/ofp8 |
| 362 | // 7 | vma | Vector mask agnostic |
| 363 | // 6 | vta | Vector tail agnostic |
| 364 | // 5:3 | vsew[2:0] | Standard element width (SEW) setting |
| 365 | // 2:0 | vlmul[2:0] | Vector register group multiplier (LMUL) setting |
| 366 | unsigned encodeVTYPE(VLMUL VLMul, unsigned SEW, bool TailAgnostic, |
| 367 | bool MaskAgnostic, bool AltFmt) { |
| 368 | assert(isValidSEW(SEW) && "Invalid SEW" ); |
| 369 | unsigned VLMulBits = static_cast<unsigned>(VLMul); |
| 370 | unsigned VSEWBits = encodeSEW(SEW); |
| 371 | unsigned VTypeI = (VSEWBits << 3) | (VLMulBits & 0x7); |
| 372 | if (TailAgnostic) |
| 373 | VTypeI |= 0x40; |
| 374 | if (MaskAgnostic) |
| 375 | VTypeI |= 0x80; |
| 376 | if (AltFmt) |
| 377 | VTypeI |= 0x100; |
| 378 | |
| 379 | return VTypeI; |
| 380 | } |
| 381 | |
| 382 | unsigned encodeXSfmmVType(unsigned SEW, unsigned Widen, bool AltFmt) { |
| 383 | assert(isValidSEW(SEW) && "Invalid SEW" ); |
| 384 | assert((Widen == 1 || Widen == 2 || Widen == 4) && "Invalid Widen" ); |
| 385 | unsigned VSEWBits = encodeSEW(SEW); |
| 386 | unsigned TWiden = Log2_32(Value: Widen) + 1; |
| 387 | unsigned VTypeI = (VSEWBits << 3) | AltFmt << 8 | TWiden << 9; |
| 388 | return VTypeI; |
| 389 | } |
| 390 | |
| 391 | std::pair<unsigned, bool> decodeVLMUL(VLMUL VLMul) { |
| 392 | switch (VLMul) { |
| 393 | default: |
| 394 | llvm_unreachable("Unexpected LMUL value!" ); |
| 395 | case LMUL_1: |
| 396 | case LMUL_2: |
| 397 | case LMUL_4: |
| 398 | case LMUL_8: |
| 399 | return std::make_pair(x: 1 << static_cast<unsigned>(VLMul), y: false); |
| 400 | case LMUL_F2: |
| 401 | case LMUL_F4: |
| 402 | case LMUL_F8: |
| 403 | return std::make_pair(x: 1 << (8 - static_cast<unsigned>(VLMul)), y: true); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | void printVType(unsigned VType, raw_ostream &OS) { |
| 408 | unsigned Sew = getSEW(VType); |
| 409 | OS << "e" << Sew; |
| 410 | |
| 411 | bool AltFmt = RISCVVType::isAltFmt(VType); |
| 412 | if (AltFmt) |
| 413 | OS << "alt" ; |
| 414 | |
| 415 | unsigned LMul; |
| 416 | bool Fractional; |
| 417 | std::tie(args&: LMul, args&: Fractional) = decodeVLMUL(VLMul: getVLMUL(VType)); |
| 418 | |
| 419 | if (Fractional) |
| 420 | OS << ", mf" ; |
| 421 | else |
| 422 | OS << ", m" ; |
| 423 | OS << LMul; |
| 424 | |
| 425 | if (isTailAgnostic(VType)) |
| 426 | OS << ", ta" ; |
| 427 | else |
| 428 | OS << ", tu" ; |
| 429 | |
| 430 | if (isMaskAgnostic(VType)) |
| 431 | OS << ", ma" ; |
| 432 | else |
| 433 | OS << ", mu" ; |
| 434 | } |
| 435 | |
| 436 | void printXSfmmVType(unsigned VType, raw_ostream &OS) { |
| 437 | OS << "e" << getSEW(VType) << ", w" << getXSfmmWiden(VType); |
| 438 | } |
| 439 | |
| 440 | unsigned getSEWLMULRatio(unsigned SEW, VLMUL VLMul) { |
| 441 | unsigned LMul; |
| 442 | bool Fractional; |
| 443 | std::tie(args&: LMul, args&: Fractional) = decodeVLMUL(VLMul); |
| 444 | |
| 445 | // Convert LMul to a fixed point value with 3 fractional bits. |
| 446 | LMul = Fractional ? (8 / LMul) : (LMul * 8); |
| 447 | |
| 448 | assert(SEW >= 8 && "Unexpected SEW value" ); |
| 449 | return (SEW * 8) / LMul; |
| 450 | } |
| 451 | |
| 452 | std::optional<VLMUL> getSameRatioLMUL(unsigned Ratio, unsigned EEW) { |
| 453 | unsigned EMULFixedPoint = (EEW * 8) / Ratio; |
| 454 | bool Fractional = EMULFixedPoint < 8; |
| 455 | unsigned EMUL = Fractional ? 8 / EMULFixedPoint : EMULFixedPoint / 8; |
| 456 | if (!isValidLMUL(LMUL: EMUL, Fractional)) |
| 457 | return std::nullopt; |
| 458 | return RISCVVType::encodeLMUL(LMUL: EMUL, Fractional); |
| 459 | } |
| 460 | |
| 461 | } // namespace RISCVVType |
| 462 | |
| 463 | } // namespace llvm |
| 464 | |