[llvm] [RISCV] Introduce a new tune feature string syntax and its parser (PR #168160)

Min-Yih Hsu via llvm-commits llvm-commits at lists.llvm.org
Wed Nov 19 12:04:41 PST 2025


https://github.com/mshockwave updated https://github.com/llvm/llvm-project/pull/168160

>From ef9637b5e74358d7e1a3f3a57624d3d184306900 Mon Sep 17 00:00:00 2001
From: Min-Yih Hsu <min.hsu at sifive.com>
Date: Fri, 14 Nov 2025 14:22:07 -0800
Subject: [PATCH 1/4] [RISCV] Introduce a new syntax for specifying tuning
 feature strings

Co-Authored-By: Sam Elliott <aelliott at qti.qualcomm.com>
---
 .../llvm/TargetParser/RISCVTargetParser.h     |  4 +
 llvm/lib/Target/RISCV/RISCVFeatures.td        | 48 ++++-----
 llvm/lib/TargetParser/RISCVTargetParser.cpp   | 99 +++++++++++++++++++
 .../TargetParser/RISCVTargetParserTest.cpp    | 88 +++++++++++++++++
 .../TableGen/Basic/RISCVTargetDefEmitter.cpp  | 49 ++++++++-
 5 files changed, 265 insertions(+), 23 deletions(-)

diff --git a/llvm/include/llvm/TargetParser/RISCVTargetParser.h b/llvm/include/llvm/TargetParser/RISCVTargetParser.h
index 2ac58a539d5ee..2c6a59a59a993 100644
--- a/llvm/include/llvm/TargetParser/RISCVTargetParser.h
+++ b/llvm/include/llvm/TargetParser/RISCVTargetParser.h
@@ -16,6 +16,7 @@
 
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/raw_ostream.h"
 
@@ -54,6 +55,9 @@ static constexpr unsigned RVVBytesPerBlock = RVVBitsPerBlock / 8;
 LLVM_ABI void getFeaturesForCPU(StringRef CPU,
                                 SmallVectorImpl<std::string> &EnabledFeatures,
                                 bool NeedPlus = false);
+LLVM_ABI void getAllTuneFeatures(SmallVectorImpl<StringRef> &TuneFeatures);
+LLVM_ABI Error parseTuneFeatureString(
+    StringRef TFString, SmallVectorImpl<std::string> &TuneFeatures);
 LLVM_ABI bool parseCPU(StringRef CPU, bool IsRV64);
 LLVM_ABI bool parseTuneCPU(StringRef CPU, bool IsRV64);
 LLVM_ABI StringRef getMArchFromMcpu(StringRef CPU);
diff --git a/llvm/lib/Target/RISCV/RISCVFeatures.td b/llvm/lib/Target/RISCV/RISCVFeatures.td
index 0b964c4808d8a..ef5203445b55c 100644
--- a/llvm/lib/Target/RISCV/RISCVFeatures.td
+++ b/llvm/lib/Target/RISCV/RISCVFeatures.td
@@ -1740,6 +1740,10 @@ def HasVendorXSMTVDot
 // LLVM specific features and extensions
 //===----------------------------------------------------------------------===//
 
+class RISCVTuneFeature<string name, string field_name, string value,
+                       string description, list<SubtargetFeature> implied = []>
+    : SubtargetFeature<name, field_name, value, description, implied>;
+
 // Feature32Bit exists to mark CPUs that support RV32 to distinguish them from
 // tuning CPU names.
 def Feature32Bit
@@ -1788,46 +1792,46 @@ def FeatureUnalignedVectorMem
                       "loads and stores">;
 
 def TuneNLogNVRGather
-   : SubtargetFeature<"log-vrgather", "RISCVVRGatherCostModel", "NLog2N",
+   : RISCVTuneFeature<"log-vrgather", "RISCVVRGatherCostModel", "NLog2N",
                       "Has vrgather.vv with LMUL*log2(LMUL) latency">;
 
-def TunePostRAScheduler : SubtargetFeature<"use-postra-scheduler",
+def TunePostRAScheduler : RISCVTuneFeature<"use-postra-scheduler",
     "UsePostRAScheduler", "true", "Schedule again after register allocation">;
 
-def TuneDisableMISchedLoadClustering : SubtargetFeature<"disable-misched-load-clustering",
+def TuneDisableMISchedLoadClustering : RISCVTuneFeature<"disable-misched-load-clustering",
     "EnableMISchedLoadClustering", "false", "Disable load clustering in the machine scheduler">;
 
-def TuneDisableMISchedStoreClustering : SubtargetFeature<"disable-misched-store-clustering",
+def TuneDisableMISchedStoreClustering : RISCVTuneFeature<"disable-misched-store-clustering",
     "EnableMISchedStoreClustering", "false", "Disable store clustering in the machine scheduler">;
 
-def TuneDisablePostMISchedLoadClustering : SubtargetFeature<"disable-postmisched-load-clustering",
+def TuneDisablePostMISchedLoadClustering : RISCVTuneFeature<"disable-postmisched-load-clustering",
     "EnablePostMISchedLoadClustering", "false", "Disable PostRA load clustering in the machine scheduler">;
 
-def TuneDisablePostMISchedStoreClustering : SubtargetFeature<"disable-postmisched-store-clustering",
+def TuneDisablePostMISchedStoreClustering : RISCVTuneFeature<"disable-postmisched-store-clustering",
     "EnablePostMISchedStoreClustering", "false", "Disable PostRA store clustering in the machine scheduler">;
 
 def TuneDisableLatencySchedHeuristic
-    : SubtargetFeature<"disable-latency-sched-heuristic", "DisableLatencySchedHeuristic", "true",
+    : RISCVTuneFeature<"disable-latency-sched-heuristic", "DisableLatencySchedHeuristic", "true",
                        "Disable latency scheduling heuristic">;
 
 def TunePredictableSelectIsExpensive
-    : SubtargetFeature<"predictable-select-expensive", "PredictableSelectIsExpensive", "true",
+    : RISCVTuneFeature<"predictable-select-expensive", "PredictableSelectIsExpensive", "true",
                        "Prefer likely predicted branches over selects">;
 
 def TuneOptimizedZeroStrideLoad
-   : SubtargetFeature<"optimized-zero-stride-load", "HasOptimizedZeroStrideLoad",
+   : RISCVTuneFeature<"optimized-zero-stride-load", "HasOptimizedZeroStrideLoad",
                       "true", "Optimized (perform fewer memory operations)"
                       "zero-stride vector load">;
 
 foreach nf = {2-8} in
   def TuneOptimizedNF#nf#SegmentLoadStore :
-      SubtargetFeature<"optimized-nf"#nf#"-segment-load-store",
+      RISCVTuneFeature<"optimized-nf"#nf#"-segment-load-store",
                        "HasOptimizedNF"#nf#"SegmentLoadStore",
                        "true", "vlseg"#nf#"eN.v and vsseg"#nf#"eN.v are "
                        "implemented as a wide memory op and shuffle">;
 
 def TuneVLDependentLatency
-    : SubtargetFeature<"vl-dependent-latency", "HasVLDependentLatency", "true",
+    : RISCVTuneFeature<"vl-dependent-latency", "HasVLDependentLatency", "true",
                        "Latency of vector instructions is dependent on the "
                        "dynamic value of vl">;
 
@@ -1839,50 +1843,50 @@ def Experimental
 // and instead split over multiple cycles. DLEN refers to the datapath width
 // that can be done in parallel.
 def TuneDLenFactor2
-   : SubtargetFeature<"dlen-factor-2", "DLenFactor2", "true",
+   : RISCVTuneFeature<"dlen-factor-2", "DLenFactor2", "true",
                       "Vector unit DLEN(data path width) is half of VLEN">;
 
 def TuneNoDefaultUnroll
-    : SubtargetFeature<"no-default-unroll", "EnableDefaultUnroll", "false",
+    : RISCVTuneFeature<"no-default-unroll", "EnableDefaultUnroll", "false",
                        "Disable default unroll preference.">;
 
 // SiFive 7 is able to fuse integer ALU operations with a preceding branch
 // instruction.
 def TuneShortForwardBranchOpt
-    : SubtargetFeature<"short-forward-branch-opt", "HasShortForwardBranchOpt",
+    : RISCVTuneFeature<"short-forward-branch-opt", "HasShortForwardBranchOpt",
                        "true", "Enable short forward branch optimization">;
 def HasShortForwardBranchOpt : Predicate<"Subtarget->hasShortForwardBranchOpt()">;
 def NoShortForwardBranchOpt : Predicate<"!Subtarget->hasShortForwardBranchOpt()">;
 
 def TuneShortForwardBranchIMinMax
-    : SubtargetFeature<"short-forward-branch-i-minmax", "HasShortForwardBranchIMinMax",
+    : RISCVTuneFeature<"short-forward-branch-i-minmax", "HasShortForwardBranchIMinMax",
                        "true", "Enable short forward branch optimization for min,max instructions in Zbb",
                        [TuneShortForwardBranchOpt]>;
 
 def TuneShortForwardBranchIMul
-    : SubtargetFeature<"short-forward-branch-i-mul", "HasShortForwardBranchIMul",
+    : RISCVTuneFeature<"short-forward-branch-i-mul", "HasShortForwardBranchIMul",
                        "true", "Enable short forward branch optimization for mul instruction",
                        [TuneShortForwardBranchOpt]>;
 
 // Some subtargets require a S2V transfer buffer to move scalars into vectors.
 // FIXME: Forming .vx/.vf/.wx/.wf can reduce register pressure.
 def TuneNoSinkSplatOperands
-    : SubtargetFeature<"no-sink-splat-operands", "SinkSplatOperands",
+    : RISCVTuneFeature<"no-sink-splat-operands", "SinkSplatOperands",
                        "false", "Disable sink splat operands to enable .vx, .vf,"
                        ".wx, and .wf instructions">;
 
 def TunePreferWInst
-    : SubtargetFeature<"prefer-w-inst", "PreferWInst", "true",
+    : RISCVTuneFeature<"prefer-w-inst", "PreferWInst", "true",
                        "Prefer instructions with W suffix">;
 
 def TuneConditionalCompressedMoveFusion
-    : SubtargetFeature<"conditional-cmv-fusion", "HasConditionalCompressedMoveFusion",
+    : RISCVTuneFeature<"conditional-cmv-fusion", "HasConditionalCompressedMoveFusion",
                        "true", "Enable branch+c.mv fusion">;
 def HasConditionalMoveFusion : Predicate<"Subtarget->hasConditionalMoveFusion()">;
 def NoConditionalMoveFusion  : Predicate<"!Subtarget->hasConditionalMoveFusion()">;
 
 def TuneHasSingleElementVecFP64
-    : SubtargetFeature<"single-element-vec-fp64", "HasSingleElementVectorFP64", "true",
+    : RISCVTuneFeature<"single-element-vec-fp64", "HasSingleElementVectorFP64", "true",
                        "Certain vector FP64 operations produce a single result "
                        "element per cycle">;
 
@@ -1899,11 +1903,11 @@ def TuneVentanaVeyron : SubtargetFeature<"ventana-veyron", "RISCVProcFamily", "V
 def TuneAndes45 : SubtargetFeature<"andes45", "RISCVProcFamily", "Andes45",
                                    "Andes 45-Series processors">;
 
-def TuneVXRMPipelineFlush : SubtargetFeature<"vxrm-pipeline-flush", "HasVXRMPipelineFlush",
+def TuneVXRMPipelineFlush : RISCVTuneFeature<"vxrm-pipeline-flush", "HasVXRMPipelineFlush",
                                              "true", "VXRM writes causes pipeline flush">;
 
 def TunePreferVsetvliOverReadVLENB
-    : SubtargetFeature<"prefer-vsetvli-over-read-vlenb",
+    : RISCVTuneFeature<"prefer-vsetvli-over-read-vlenb",
                        "PreferVsetvliOverReadVLENB",
                        "true",
                        "Prefer vsetvli over read vlenb CSR to calculate VLEN">;
diff --git a/llvm/lib/TargetParser/RISCVTargetParser.cpp b/llvm/lib/TargetParser/RISCVTargetParser.cpp
index 5ea63a973ea37..aa2039a00a550 100644
--- a/llvm/lib/TargetParser/RISCVTargetParser.cpp
+++ b/llvm/lib/TargetParser/RISCVTargetParser.cpp
@@ -12,7 +12,11 @@
 //===----------------------------------------------------------------------===//
 
 #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/StringSet.h"
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/TargetParser/RISCVISAInfo.h"
 
@@ -145,6 +149,101 @@ void getFeaturesForCPU(StringRef CPU,
       EnabledFeatures.push_back(F.substr(1));
 }
 
+using RISCVImpliedTuneFeature = std::pair<const char *, const char *>;
+
+#define GET_TUNE_FEATURES
+#define GET_IMPLIED_TUNE_FEATURES
+#include "llvm/TargetParser/RISCVTargetParserDef.inc"
+
+void getAllTuneFeatures(SmallVectorImpl<StringRef> &Features) {
+  Features.assign(std::begin(TuneFeatures), std::end(TuneFeatures));
+}
+
+Error parseTuneFeatureString(StringRef TFString,
+                             SmallVectorImpl<std::string> &ResFeatures) {
+  const StringSet<> AllTuneFeatureSet(llvm::from_range, TuneFeatures);
+  using SmallStringSet = SmallSet<StringRef, 4>;
+
+  TFString = TFString.trim();
+  // Note: StringSet is not really ergnomic to use in this case here.
+  SmallStringSet PositiveFeatures;
+  SmallStringSet NegativeFeatures;
+  // Phase 1: Collect explicit features.
+  StringRef FeatureStr;
+  do {
+    std::tie(FeatureStr, TFString) = TFString.split(",");
+    if (AllTuneFeatureSet.count(FeatureStr)) {
+      if (!PositiveFeatures.insert(FeatureStr).second)
+        return createStringError(inconvertibleErrorCode(),
+                                 "cannot specify more than one instance of '" +
+                                     Twine(FeatureStr) + "'");
+    } else if (FeatureStr.starts_with("no-")) {
+      // Check if this is a negative feature, like `no-foo` for `foo`.
+      StringRef ActualFeature = FeatureStr.drop_front(3);
+      if (AllTuneFeatureSet.count(ActualFeature)) {
+        if (!NegativeFeatures.insert(ActualFeature).second)
+          return createStringError(
+              inconvertibleErrorCode(),
+              "cannot specify more than one instance of '" + Twine(FeatureStr) +
+                  "'");
+      }
+    } else {
+      return createStringError(inconvertibleErrorCode(),
+                               "unrecognized tune feature directive '" +
+                                   Twine(FeatureStr) + "'");
+    }
+  } while (!TFString.empty());
+
+  auto Intersection =
+      llvm::set_intersection(PositiveFeatures, NegativeFeatures);
+  if (!Intersection.empty()) {
+    std::string IntersectedStr = join(Intersection, "', '");
+    return createStringError(inconvertibleErrorCode(),
+                             "Feature(s) '" + Twine(IntersectedStr) +
+                                 "' cannot appear in both "
+                                 "positive and negative directives");
+  }
+
+  // Phase 2: Derive implied features.
+  StringMap<SmallVector<StringRef, 2>> ImpliedFeatureMap;
+  StringMap<SmallVector<StringRef, 2>> InverseImpliedFeatureMap;
+  for (auto [Feature, ImpliedFeature] : ImpliedTuneFeatures) {
+    ImpliedFeatureMap[Feature].push_back(ImpliedFeature);
+    InverseImpliedFeatureMap[ImpliedFeature].push_back(Feature);
+  }
+
+  for (StringRef PF : PositiveFeatures) {
+    auto ItFeatures = ImpliedFeatureMap.find(PF);
+    if (ItFeatures != ImpliedFeatureMap.end())
+      PositiveFeatures.insert(ItFeatures->second.begin(),
+                              ItFeatures->second.end());
+  }
+  for (StringRef NF : NegativeFeatures) {
+    auto ItFeatures = InverseImpliedFeatureMap.find(NF);
+    if (ItFeatures != InverseImpliedFeatureMap.end())
+      NegativeFeatures.insert(ItFeatures->second.begin(),
+                              ItFeatures->second.end());
+  }
+
+  Intersection = llvm::set_intersection(PositiveFeatures, NegativeFeatures);
+  if (!Intersection.empty()) {
+    std::string IntersectedStr = join(Intersection, "', '");
+    return createStringError(inconvertibleErrorCode(),
+                             "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());
+
+  return Error::success();
+}
 } // namespace RISCV
 
 namespace RISCVVType {
diff --git a/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp b/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
index 63ac8f993ecdc..e85b08a5df5ff 100644
--- a/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
+++ b/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
@@ -7,6 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/TargetParser/RISCVTargetParser.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
 #include "gtest/gtest.h"
 
 using namespace llvm;
@@ -30,4 +32,90 @@ TEST(RISCVVType, CheckSameRatioLMUL) {
   EXPECT_EQ(RISCVVType::LMUL_F2,
             RISCVVType::getSameRatioLMUL(8, RISCVVType::LMUL_F4, 16));
 }
+
+TEST(RISCVTuneFeature, AllTuneFeatures) {
+  SmallVector<StringRef> AllTuneFeatures;
+  RISCV::getAllTuneFeatures(AllTuneFeatures);
+  EXPECT_EQ(AllTuneFeatures.size(), 28U);
+  for (auto F : {"conditional-cmv-fusion",
+                 "dlen-factor-2",
+                 "disable-latency-sched-heuristic",
+                 "disable-misched-load-clustering",
+                 "disable-misched-store-clustering",
+                 "disable-postmisched-load-clustering",
+                 "disable-postmisched-store-clustering",
+                 "single-element-vec-fp64",
+                 "log-vrgather",
+                 "no-default-unroll",
+                 "no-sink-splat-operands",
+                 "optimized-nf2-segment-load-store",
+                 "optimized-nf3-segment-load-store",
+                 "optimized-nf4-segment-load-store",
+                 "optimized-nf5-segment-load-store",
+                 "optimized-nf6-segment-load-store",
+                 "optimized-nf7-segment-load-store",
+                 "optimized-nf8-segment-load-store",
+                 "optimized-zero-stride-load",
+                 "use-postra-scheduler",
+                 "predictable-select-expensive",
+                 "prefer-vsetvli-over-read-vlenb",
+                 "prefer-w-inst",
+                 "short-forward-branch-i-minmax",
+                 "short-forward-branch-i-mul",
+                 "short-forward-branch-opt",
+                 "vl-dependent-latency",
+                 "vxrm-pipeline-flush"})
+    EXPECT_TRUE(is_contained(AllTuneFeatures, F));
+}
+
+TEST(RISCVTuneFeature, LegalTuneFeatureStrings) {
+  SmallVector<std::string> Result;
+  EXPECT_FALSE(errorToBool(RISCV::parseTuneFeatureString(
+      "log-vrgather,no-short-forward-branch-opt,vl-dependent-latency",
+      Result)));
+  EXPECT_TRUE(is_contained(Result, "+log-vrgather"));
+  EXPECT_TRUE(is_contained(Result, "+vl-dependent-latency"));
+  EXPECT_TRUE(is_contained(Result, "-short-forward-branch-opt"));
+  EXPECT_TRUE(is_contained(Result, "-short-forward-branch-i-minmax"));
+  EXPECT_TRUE(is_contained(Result, "-short-forward-branch-i-mul"));
+
+  Result.clear();
+  EXPECT_FALSE(errorToBool(RISCV::parseTuneFeatureString(
+      "no-short-forward-branch-i-mul,short-forward-branch-i-minmax", Result)));
+  EXPECT_TRUE(is_contained(Result, "+short-forward-branch-i-minmax"));
+  EXPECT_TRUE(is_contained(Result, "+short-forward-branch-opt"));
+  EXPECT_TRUE(is_contained(Result, "-short-forward-branch-i-mul"));
+
+  Result.clear();
+  EXPECT_FALSE(errorToBool(RISCV::parseTuneFeatureString(
+      "no-no-default-unroll,no-sink-splat-operands", Result)));
+  EXPECT_TRUE(is_contained(Result, "+no-sink-splat-operands"));
+  EXPECT_TRUE(is_contained(Result, "-no-default-unroll"));
+}
+
+TEST(RISCVTuneFeature, UnrecognizedTuneFeature) {
+  SmallVector<std::string> Result;
+  EXPECT_EQ(toString(RISCV::parseTuneFeatureString("32bit", Result)),
+            "unrecognized tune feature directive '32bit'");
+}
+
+TEST(RISCVTuneFeature, DuplicatedFeatures) {
+  SmallVector<std::string> Result;
+  EXPECT_EQ(toString(RISCV::parseTuneFeatureString("log-vrgather,log-vrgather",
+                                                   Result)),
+            "cannot specify more than one instance of 'log-vrgather'");
+
+  EXPECT_EQ(toString(RISCV::parseTuneFeatureString(
+                "log-vrgather,no-log-vrgather,short-forward-branch-i-mul,no-"
+                "short-forward-branch-i-mul",
+                Result)),
+            "Feature(s) 'log-vrgather', 'short-forward-branch-i-mul' cannot "
+            "appear in both positive and negative directives");
+
+  EXPECT_EQ(
+      toString(RISCV::parseTuneFeatureString(
+          "short-forward-branch-i-mul,no-short-forward-branch-opt", Result)),
+      "Feature(s) 'short-forward-branch-i-mul', 'short-forward-branch-opt' "
+      "were implied by both positive and negative directives");
+}
 } // namespace
diff --git a/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp b/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp
index f7959376adc4a..7420fa439ecd4 100644
--- a/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp
@@ -14,6 +14,7 @@
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/RISCVISAUtils.h"
+#include "llvm/TableGen/Error.h"
 #include "llvm/TableGen/Record.h"
 #include "llvm/TableGen/TableGenBackend.h"
 
@@ -259,7 +260,52 @@ static void emitRISCVExtensionBitmask(const RecordKeeper &RK, raw_ostream &OS) {
                  << "},\n";
   }
   OS << "};\n";
-  OS << "#endif\n";
+  OS << "#endif\n\n";
+}
+
+static void emitRISCVTuneFeatures(const RecordKeeper &RK, raw_ostream &OS) {
+  std::vector<const Record *> TuneFeatureRecords =
+      RK.getAllDerivedDefinitionsIfDefined("RISCVTuneFeature");
+
+  SmallVector<StringRef> TuneFeatures;
+  // A list of {Feature -> Implied Feature}
+  SmallVector<std::pair<StringRef, StringRef>> ImpliedFeatures;
+  for (const auto *R : TuneFeatureRecords) {
+    StringRef FeatureName = R->getValueAsString("Name");
+    TuneFeatures.push_back(FeatureName);
+    std::vector<const Record *> Implies = R->getValueAsListOfDefs("Implies");
+    for (const auto *ImpliedRecord : Implies) {
+      if (!ImpliedRecord->isSubClassOf("RISCVTuneFeature") ||
+          ImpliedRecord == R) {
+        PrintError(ImpliedRecord,
+                   "RISCVTuneFeature can only imply other RISCVTuneFeatures");
+        PrintFatalNote(R, "implied by this RISCVTuneFeature");
+      }
+      ImpliedFeatures.emplace_back(FeatureName,
+                                   ImpliedRecord->getValueAsString("Name"));
+    }
+  }
+
+  OS << "#ifdef GET_TUNE_FEATURES\n";
+  OS << "#undef GET_TUNE_FEATURES\n\n";
+
+  OS << "static const char *TuneFeatures[] = {\n";
+  for (StringRef Feature : TuneFeatures)
+    OS.indent(4) << "\"" << Feature << "\",\n";
+  OS << "};\n\n";
+
+  OS << "#endif // GET_TUNE_FEATURES\n\n";
+
+  OS << "#ifdef GET_IMPLIED_TUNE_FEATURES\n";
+  OS << "#undef GET_IMPLIED_TUNE_FEATURES\n\n";
+
+  OS << "static const RISCVImpliedTuneFeature ImpliedTuneFeatures[] = {\n";
+  for (auto [Feature, ImpliedFeature] : ImpliedFeatures)
+    OS.indent(4) << "{" << "\"" << Feature << "\", \"" << ImpliedFeature
+                 << "\"},\n";
+  OS << "};\n\n";
+
+  OS << "#endif // GET_IMPLIED_TUNE_FEATURES\n";
 }
 
 static void emitRiscvTargetDef(const RecordKeeper &RK, raw_ostream &OS) {
@@ -267,6 +313,7 @@ static void emitRiscvTargetDef(const RecordKeeper &RK, raw_ostream &OS) {
   emitRISCVProfiles(RK, OS);
   emitRISCVProcs(RK, OS);
   emitRISCVExtensionBitmask(RK, OS);
+  emitRISCVTuneFeatures(RK, OS);
 }
 
 static TableGen::Emitter::Opt X("gen-riscv-target-def", emitRiscvTargetDef,

>From 0af6194f69d01bbda51c1df5f2a4f2e99c80d7ad Mon Sep 17 00:00:00 2001
From: Min-Yih Hsu <min.hsu at sifive.com>
Date: Tue, 18 Nov 2025 14:06:48 -0800
Subject: [PATCH 2/4] fixup! Raise unrecognized (explicit) feature as warning

---
 .../llvm/TargetParser/RISCVTargetParser.h     | 16 +++++++++
 llvm/lib/TargetParser/RISCVTargetParser.cpp   | 34 +++++++++----------
 .../TargetParser/RISCVTargetParserTest.cpp    |  5 ++-
 3 files changed, 37 insertions(+), 18 deletions(-)

diff --git a/llvm/include/llvm/TargetParser/RISCVTargetParser.h b/llvm/include/llvm/TargetParser/RISCVTargetParser.h
index 2c6a59a59a993..5cb339ff58a75 100644
--- a/llvm/include/llvm/TargetParser/RISCVTargetParser.h
+++ b/llvm/include/llvm/TargetParser/RISCVTargetParser.h
@@ -48,6 +48,22 @@ struct CPUInfo {
   bool is64Bit() const { return DefaultMarch.starts_with("rv64"); }
 };
 
+/// Fatal errors encountered during parsing.
+struct ParserError : public ErrorInfo<ParserError, StringError> {
+  using ErrorInfo<ParserError, StringError>::ErrorInfo;
+  explicit ParserError(const Twine &S)
+      : ErrorInfo(S, inconvertibleErrorCode()) {}
+  static char ID;
+};
+
+/// Warnings encountered during parsing.
+struct ParserWarning : public ErrorInfo<ParserWarning, StringError> {
+  using ErrorInfo<ParserWarning, StringError>::ErrorInfo;
+  explicit ParserWarning(const Twine &S)
+      : ErrorInfo(S, inconvertibleErrorCode()) {}
+  static char ID;
+};
+
 // We use 64 bits as the known part in the scalable vector types.
 static constexpr unsigned RVVBitsPerBlock = 64;
 static constexpr unsigned RVVBytesPerBlock = RVVBitsPerBlock / 8;
diff --git a/llvm/lib/TargetParser/RISCVTargetParser.cpp b/llvm/lib/TargetParser/RISCVTargetParser.cpp
index aa2039a00a550..4914ba798f56d 100644
--- a/llvm/lib/TargetParser/RISCVTargetParser.cpp
+++ b/llvm/lib/TargetParser/RISCVTargetParser.cpp
@@ -23,6 +23,9 @@
 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)                  \
@@ -174,23 +177,22 @@ Error parseTuneFeatureString(StringRef TFString,
     std::tie(FeatureStr, TFString) = TFString.split(",");
     if (AllTuneFeatureSet.count(FeatureStr)) {
       if (!PositiveFeatures.insert(FeatureStr).second)
-        return createStringError(inconvertibleErrorCode(),
-                                 "cannot specify more than one instance of '" +
-                                     Twine(FeatureStr) + "'");
+        return make_error<ParserError>(
+            "cannot specify more than one instance of '" + Twine(FeatureStr) +
+            "'");
     } else if (FeatureStr.starts_with("no-")) {
       // Check if this is a negative feature, like `no-foo` for `foo`.
       StringRef ActualFeature = FeatureStr.drop_front(3);
       if (AllTuneFeatureSet.count(ActualFeature)) {
         if (!NegativeFeatures.insert(ActualFeature).second)
-          return createStringError(
-              inconvertibleErrorCode(),
+          return make_error<ParserError>(
               "cannot specify more than one instance of '" + Twine(FeatureStr) +
-                  "'");
+              "'");
       }
     } else {
-      return createStringError(inconvertibleErrorCode(),
-                               "unrecognized tune feature directive '" +
-                                   Twine(FeatureStr) + "'");
+      // Raise it as a warning for better compatibilities.
+      return make_error<ParserWarning>("unrecognized tune feature directive '" +
+                                       Twine(FeatureStr) + "'");
     }
   } while (!TFString.empty());
 
@@ -198,10 +200,9 @@ Error parseTuneFeatureString(StringRef TFString,
       llvm::set_intersection(PositiveFeatures, NegativeFeatures);
   if (!Intersection.empty()) {
     std::string IntersectedStr = join(Intersection, "', '");
-    return createStringError(inconvertibleErrorCode(),
-                             "Feature(s) '" + Twine(IntersectedStr) +
-                                 "' cannot appear in both "
-                                 "positive and negative directives");
+    return make_error<ParserError>("Feature(s) '" + Twine(IntersectedStr) +
+                                   "' cannot appear in both "
+                                   "positive and negative directives");
   }
 
   // Phase 2: Derive implied features.
@@ -228,10 +229,9 @@ Error parseTuneFeatureString(StringRef TFString,
   Intersection = llvm::set_intersection(PositiveFeatures, NegativeFeatures);
   if (!Intersection.empty()) {
     std::string IntersectedStr = join(Intersection, "', '");
-    return createStringError(inconvertibleErrorCode(),
-                             "Feature(s) '" + Twine(IntersectedStr) +
-                                 "' were implied by both "
-                                 "positive and negative directives");
+    return make_error<ParserError>("Feature(s) '" + Twine(IntersectedStr) +
+                                   "' were implied by both "
+                                   "positive and negative directives");
   }
 
   // Export the result.
diff --git a/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp b/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
index e85b08a5df5ff..12383bcc3e61f 100644
--- a/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
+++ b/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
@@ -95,7 +95,10 @@ TEST(RISCVTuneFeature, LegalTuneFeatureStrings) {
 
 TEST(RISCVTuneFeature, UnrecognizedTuneFeature) {
   SmallVector<std::string> Result;
-  EXPECT_EQ(toString(RISCV::parseTuneFeatureString("32bit", Result)),
+  auto Err = RISCV::parseTuneFeatureString("32bit", Result);
+  // This should be an warning.
+  EXPECT_TRUE(Err.isA<RISCV::ParserWarning>());
+  EXPECT_EQ(toString(std::move(Err)),
             "unrecognized tune feature directive '32bit'");
 }
 

>From b13750236932112aaab7b14df2be6180d175aa04 Mon Sep 17 00:00:00 2001
From: Min-Yih Hsu <min.hsu at sifive.com>
Date: Tue, 18 Nov 2025 16:41:34 -0800
Subject: [PATCH 3/4] fixup! Ignore unrecognized feature and move on

---
 llvm/lib/TargetParser/RISCVTargetParser.cpp        | 14 ++++++++++----
 .../TargetParser/RISCVTargetParserTest.cpp         |  5 +++--
 2 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/TargetParser/RISCVTargetParser.cpp b/llvm/lib/TargetParser/RISCVTargetParser.cpp
index 4914ba798f56d..b2d4872d795db 100644
--- a/llvm/lib/TargetParser/RISCVTargetParser.cpp
+++ b/llvm/lib/TargetParser/RISCVTargetParser.cpp
@@ -167,6 +167,10 @@ Error parseTuneFeatureString(StringRef TFString,
   const StringSet<> AllTuneFeatureSet(llvm::from_range, TuneFeatures);
   using SmallStringSet = SmallSet<StringRef, 4>;
 
+  // Do not create ParserWarning right away. Instead, we store the warning
+  // message until the last moment.
+  std::string WarningMsg;
+
   TFString = TFString.trim();
   // Note: StringSet is not really ergnomic to use in this case here.
   SmallStringSet PositiveFeatures;
@@ -190,9 +194,8 @@ Error parseTuneFeatureString(StringRef TFString,
               "'");
       }
     } else {
-      // Raise it as a warning for better compatibilities.
-      return make_error<ParserWarning>("unrecognized tune feature directive '" +
-                                       Twine(FeatureStr) + "'");
+      raw_string_ostream SS(WarningMsg);
+      SS << "unrecognized tune feature directive '" << FeatureStr << "'";
     }
   } while (!TFString.empty());
 
@@ -242,7 +245,10 @@ Error parseTuneFeatureString(StringRef TFString,
   for (StringRef NF : NegativeFeatures)
     ResFeatures.emplace_back(NegPrefix + NF.str());
 
-  return Error::success();
+  if (WarningMsg.empty())
+    return Error::success();
+  else
+    return make_error<ParserWarning>(WarningMsg);
 }
 } // namespace RISCV
 
diff --git a/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp b/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
index 12383bcc3e61f..6288601529da2 100644
--- a/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
+++ b/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
@@ -93,13 +93,14 @@ TEST(RISCVTuneFeature, LegalTuneFeatureStrings) {
   EXPECT_TRUE(is_contained(Result, "-no-default-unroll"));
 }
 
-TEST(RISCVTuneFeature, UnrecognizedTuneFeature) {
+TEST(RISCVTuneFeature, IgnoreUnrecognizedTuneFeature) {
   SmallVector<std::string> Result;
-  auto Err = RISCV::parseTuneFeatureString("32bit", Result);
+  auto Err = RISCV::parseTuneFeatureString("32bit,log-vrgather", Result);
   // This should be an warning.
   EXPECT_TRUE(Err.isA<RISCV::ParserWarning>());
   EXPECT_EQ(toString(std::move(Err)),
             "unrecognized tune feature directive '32bit'");
+  EXPECT_TRUE(is_contained(Result, "+log-vrgather"));
 }
 
 TEST(RISCVTuneFeature, DuplicatedFeatures) {

>From be277d08d655b65380b8d67b020b45470fb57b14 Mon Sep 17 00:00:00 2001
From: Min-Yih Hsu <min.hsu at sifive.com>
Date: Wed, 19 Nov 2025 12:03:45 -0800
Subject: [PATCH 4/4] fixup! Allow users to use custom directive names

---
 llvm/lib/Target/RISCV/RISCVFeatures.td        | 119 +++++++++-------
 llvm/lib/TargetParser/RISCVTargetParser.cpp   | 127 ++++++++++++------
 .../TargetParser/RISCVTargetParserTest.cpp    |  20 ++-
 .../TableGen/Basic/RISCVTargetDefEmitter.cpp  | 103 ++++++++++----
 4 files changed, 259 insertions(+), 110 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVFeatures.td b/llvm/lib/Target/RISCV/RISCVFeatures.td
index ef5203445b55c..65594a672c0b4 100644
--- a/llvm/lib/Target/RISCV/RISCVFeatures.td
+++ b/llvm/lib/Target/RISCV/RISCVFeatures.td
@@ -1740,9 +1740,12 @@ def HasVendorXSMTVDot
 // LLVM specific features and extensions
 //===----------------------------------------------------------------------===//
 
-class RISCVTuneFeature<string name, string field_name, string value,
-                       string description, list<SubtargetFeature> implied = []>
-    : SubtargetFeature<name, field_name, value, description, implied>;
+class RISCVTuneFeatureBase;
+class RISCVSimpleTuneFeature : RISCVTuneFeatureBase;
+class RISCVTuneFeature<string pos_directive, string neg_directive> : RISCVTuneFeatureBase {
+  string PositiveDirectiveName = pos_directive;
+  string NegativeDirectiveName = neg_directive;
+}
 
 // Feature32Bit exists to mark CPUs that support RV32 to distinguish them from
 // tuning CPU names.
@@ -1792,48 +1795,59 @@ def FeatureUnalignedVectorMem
                       "loads and stores">;
 
 def TuneNLogNVRGather
-   : RISCVTuneFeature<"log-vrgather", "RISCVVRGatherCostModel", "NLog2N",
-                      "Has vrgather.vv with LMUL*log2(LMUL) latency">;
+   : SubtargetFeature<"log-vrgather", "RISCVVRGatherCostModel", "NLog2N",
+                      "Has vrgather.vv with LMUL*log2(LMUL) latency">,
+     RISCVSimpleTuneFeature;
 
-def TunePostRAScheduler : RISCVTuneFeature<"use-postra-scheduler",
-    "UsePostRAScheduler", "true", "Schedule again after register allocation">;
+def TunePostRAScheduler : SubtargetFeature<"use-postra-scheduler",
+    "UsePostRAScheduler", "true", "Schedule again after register allocation">,
+    RISCVSimpleTuneFeature;
 
-def TuneDisableMISchedLoadClustering : RISCVTuneFeature<"disable-misched-load-clustering",
-    "EnableMISchedLoadClustering", "false", "Disable load clustering in the machine scheduler">;
+def TuneDisableMISchedLoadClustering : SubtargetFeature<"disable-misched-load-clustering",
+    "EnableMISchedLoadClustering", "false", "Disable load clustering in the machine scheduler">,
+    RISCVTuneFeature<"disable-misched-load-clustering", "enable-misched-load-clustering">;
 
-def TuneDisableMISchedStoreClustering : RISCVTuneFeature<"disable-misched-store-clustering",
-    "EnableMISchedStoreClustering", "false", "Disable store clustering in the machine scheduler">;
+def TuneDisableMISchedStoreClustering : SubtargetFeature<"disable-misched-store-clustering",
+    "EnableMISchedStoreClustering", "false", "Disable store clustering in the machine scheduler">,
+    RISCVTuneFeature<"disable-misched-store-clustering", "enable-misched-store-clustering">;
 
-def TuneDisablePostMISchedLoadClustering : RISCVTuneFeature<"disable-postmisched-load-clustering",
-    "EnablePostMISchedLoadClustering", "false", "Disable PostRA load clustering in the machine scheduler">;
+def TuneDisablePostMISchedLoadClustering : SubtargetFeature<"disable-postmisched-load-clustering",
+    "EnablePostMISchedLoadClustering", "false", "Disable PostRA load clustering in the machine scheduler">,
+    RISCVTuneFeature<"disable-postmisched-load-clustering", "enable-postmisched-load-clustering">;
 
-def TuneDisablePostMISchedStoreClustering : RISCVTuneFeature<"disable-postmisched-store-clustering",
-    "EnablePostMISchedStoreClustering", "false", "Disable PostRA store clustering in the machine scheduler">;
+def TuneDisablePostMISchedStoreClustering : SubtargetFeature<"disable-postmisched-store-clustering",
+    "EnablePostMISchedStoreClustering", "false", "Disable PostRA store clustering in the machine scheduler">,
+    RISCVTuneFeature<"disable-postmisched-store-clustering", "enable-postmisched-store-clustering">;
 
 def TuneDisableLatencySchedHeuristic
-    : RISCVTuneFeature<"disable-latency-sched-heuristic", "DisableLatencySchedHeuristic", "true",
-                       "Disable latency scheduling heuristic">;
+    : SubtargetFeature<"disable-latency-sched-heuristic", "DisableLatencySchedHeuristic", "true",
+                       "Disable latency scheduling heuristic">,
+      RISCVTuneFeature<"disable-latency-sched-heuristic", "enable-latency-sched-heuristic">;
 
 def TunePredictableSelectIsExpensive
-    : RISCVTuneFeature<"predictable-select-expensive", "PredictableSelectIsExpensive", "true",
-                       "Prefer likely predicted branches over selects">;
+    : SubtargetFeature<"predictable-select-expensive", "PredictableSelectIsExpensive", "true",
+                       "Prefer likely predicted branches over selects">,
+      RISCVSimpleTuneFeature;
 
 def TuneOptimizedZeroStrideLoad
-   : RISCVTuneFeature<"optimized-zero-stride-load", "HasOptimizedZeroStrideLoad",
+   : SubtargetFeature<"optimized-zero-stride-load", "HasOptimizedZeroStrideLoad",
                       "true", "Optimized (perform fewer memory operations)"
-                      "zero-stride vector load">;
+                      "zero-stride vector load">,
+      RISCVSimpleTuneFeature;
 
 foreach nf = {2-8} in
   def TuneOptimizedNF#nf#SegmentLoadStore :
-      RISCVTuneFeature<"optimized-nf"#nf#"-segment-load-store",
+      SubtargetFeature<"optimized-nf"#nf#"-segment-load-store",
                        "HasOptimizedNF"#nf#"SegmentLoadStore",
                        "true", "vlseg"#nf#"eN.v and vsseg"#nf#"eN.v are "
-                       "implemented as a wide memory op and shuffle">;
+                       "implemented as a wide memory op and shuffle">,
+      RISCVSimpleTuneFeature;
 
 def TuneVLDependentLatency
-    : RISCVTuneFeature<"vl-dependent-latency", "HasVLDependentLatency", "true",
+    : SubtargetFeature<"vl-dependent-latency", "HasVLDependentLatency", "true",
                        "Latency of vector instructions is dependent on the "
-                       "dynamic value of vl">;
+                       "dynamic value of vl">,
+      RISCVSimpleTuneFeature;
 
 def Experimental
    : SubtargetFeature<"experimental", "HasExperimental",
@@ -1843,52 +1857,61 @@ def Experimental
 // and instead split over multiple cycles. DLEN refers to the datapath width
 // that can be done in parallel.
 def TuneDLenFactor2
-   : RISCVTuneFeature<"dlen-factor-2", "DLenFactor2", "true",
-                      "Vector unit DLEN(data path width) is half of VLEN">;
+   : SubtargetFeature<"dlen-factor-2", "DLenFactor2", "true",
+                      "Vector unit DLEN(data path width) is half of VLEN">,
+      RISCVSimpleTuneFeature;
 
 def TuneNoDefaultUnroll
-    : RISCVTuneFeature<"no-default-unroll", "EnableDefaultUnroll", "false",
-                       "Disable default unroll preference.">;
+    : SubtargetFeature<"no-default-unroll", "EnableDefaultUnroll", "false",
+                       "Disable default unroll preference.">,
+      RISCVTuneFeature<"no-default-unroll", "enable-default-unroll">;
 
 // SiFive 7 is able to fuse integer ALU operations with a preceding branch
 // instruction.
 def TuneShortForwardBranchOpt
-    : RISCVTuneFeature<"short-forward-branch-opt", "HasShortForwardBranchOpt",
-                       "true", "Enable short forward branch optimization">;
+    : SubtargetFeature<"short-forward-branch-opt", "HasShortForwardBranchOpt",
+                       "true", "Enable short forward branch optimization">,
+      RISCVSimpleTuneFeature;
 def HasShortForwardBranchOpt : Predicate<"Subtarget->hasShortForwardBranchOpt()">;
 def NoShortForwardBranchOpt : Predicate<"!Subtarget->hasShortForwardBranchOpt()">;
 
 def TuneShortForwardBranchIMinMax
-    : RISCVTuneFeature<"short-forward-branch-i-minmax", "HasShortForwardBranchIMinMax",
+    : SubtargetFeature<"short-forward-branch-i-minmax", "HasShortForwardBranchIMinMax",
                        "true", "Enable short forward branch optimization for min,max instructions in Zbb",
-                       [TuneShortForwardBranchOpt]>;
+                       [TuneShortForwardBranchOpt]>,
+      RISCVSimpleTuneFeature;
 
 def TuneShortForwardBranchIMul
-    : RISCVTuneFeature<"short-forward-branch-i-mul", "HasShortForwardBranchIMul",
+    : SubtargetFeature<"short-forward-branch-i-mul", "HasShortForwardBranchIMul",
                        "true", "Enable short forward branch optimization for mul instruction",
-                       [TuneShortForwardBranchOpt]>;
+                       [TuneShortForwardBranchOpt]>,
+      RISCVSimpleTuneFeature;
 
 // Some subtargets require a S2V transfer buffer to move scalars into vectors.
 // FIXME: Forming .vx/.vf/.wx/.wf can reduce register pressure.
 def TuneNoSinkSplatOperands
-    : RISCVTuneFeature<"no-sink-splat-operands", "SinkSplatOperands",
+    : SubtargetFeature<"no-sink-splat-operands", "SinkSplatOperands",
                        "false", "Disable sink splat operands to enable .vx, .vf,"
-                       ".wx, and .wf instructions">;
+                       ".wx, and .wf instructions">,
+      RISCVTuneFeature<"no-sink-splat-operands", "sink-splat-operands">;
 
 def TunePreferWInst
-    : RISCVTuneFeature<"prefer-w-inst", "PreferWInst", "true",
-                       "Prefer instructions with W suffix">;
+    : SubtargetFeature<"prefer-w-inst", "PreferWInst", "true",
+                       "Prefer instructions with W suffix">,
+      RISCVSimpleTuneFeature;
 
 def TuneConditionalCompressedMoveFusion
-    : RISCVTuneFeature<"conditional-cmv-fusion", "HasConditionalCompressedMoveFusion",
-                       "true", "Enable branch+c.mv fusion">;
+    : SubtargetFeature<"conditional-cmv-fusion", "HasConditionalCompressedMoveFusion",
+                       "true", "Enable branch+c.mv fusion">,
+      RISCVSimpleTuneFeature;
 def HasConditionalMoveFusion : Predicate<"Subtarget->hasConditionalMoveFusion()">;
 def NoConditionalMoveFusion  : Predicate<"!Subtarget->hasConditionalMoveFusion()">;
 
 def TuneHasSingleElementVecFP64
-    : RISCVTuneFeature<"single-element-vec-fp64", "HasSingleElementVectorFP64", "true",
+    : SubtargetFeature<"single-element-vec-fp64", "HasSingleElementVectorFP64", "true",
                        "Certain vector FP64 operations produce a single result "
-                       "element per cycle">;
+                       "element per cycle">,
+      RISCVSimpleTuneFeature;
 
 def TuneMIPSP8700
     : SubtargetFeature<"mips-p8700", "RISCVProcFamily", "MIPSP8700",
@@ -1903,14 +1926,16 @@ def TuneVentanaVeyron : SubtargetFeature<"ventana-veyron", "RISCVProcFamily", "V
 def TuneAndes45 : SubtargetFeature<"andes45", "RISCVProcFamily", "Andes45",
                                    "Andes 45-Series processors">;
 
-def TuneVXRMPipelineFlush : RISCVTuneFeature<"vxrm-pipeline-flush", "HasVXRMPipelineFlush",
-                                             "true", "VXRM writes causes pipeline flush">;
+def TuneVXRMPipelineFlush : SubtargetFeature<"vxrm-pipeline-flush", "HasVXRMPipelineFlush",
+                                             "true", "VXRM writes causes pipeline flush">,
+                            RISCVSimpleTuneFeature;
 
 def TunePreferVsetvliOverReadVLENB
-    : RISCVTuneFeature<"prefer-vsetvli-over-read-vlenb",
+    : SubtargetFeature<"prefer-vsetvli-over-read-vlenb",
                        "PreferVsetvliOverReadVLENB",
                        "true",
-                       "Prefer vsetvli over read vlenb CSR to calculate VLEN">;
+                       "Prefer vsetvli over read vlenb CSR to calculate VLEN">,
+      RISCVSimpleTuneFeature;
 
 // Assume that lock-free native-width atomics are available, even if the target
 // and operating system combination would not usually provide them. The user
diff --git a/llvm/lib/TargetParser/RISCVTargetParser.cpp b/llvm/lib/TargetParser/RISCVTargetParser.cpp
index b2d4872d795db..f8b4c05c83268 100644
--- a/llvm/lib/TargetParser/RISCVTargetParser.cpp
+++ b/llvm/lib/TargetParser/RISCVTargetParser.cpp
@@ -16,8 +16,8 @@
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringExtras.h"
-#include "llvm/ADT/StringSet.h"
 #include "llvm/ADT/StringSwitch.h"
+#include "llvm/ADT/StringTable.h"
 #include "llvm/TargetParser/RISCVISAInfo.h"
 
 namespace llvm {
@@ -152,20 +152,83 @@ void getFeaturesForCPU(StringRef CPU,
       EnabledFeatures.push_back(F.substr(1));
 }
 
-using RISCVImpliedTuneFeature = std::pair<const char *, const char *>;
+class RISCVTuneFeatureLookupTable {
+  struct RISCVTuneFeature {
+    unsigned PosIdx;
+    unsigned NegIdx;
+    unsigned FeatureIdx;
+  };
+
+  struct RISCVImpliedTuneFeature {
+    unsigned FeatureIdx;
+    unsigned ImpliedFeatureIdx;
+  };
 
 #define GET_TUNE_FEATURES
-#define GET_IMPLIED_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:
+  static void getAllTuneFeatures(SmallVectorImpl<StringRef> &Features) {
+    for (const auto &TuneFeature : TuneFeatures)
+      Features.push_back(TuneFeatureStrings[TuneFeature.FeatureIdx]);
+  }
+
+  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 owner of these implied
+  /// feature list, 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;
+  }
+};
+
 void getAllTuneFeatures(SmallVectorImpl<StringRef> &Features) {
-  Features.assign(std::begin(TuneFeatures), std::end(TuneFeatures));
+  RISCVTuneFeatureLookupTable::getAllTuneFeatures(Features);
 }
 
 Error parseTuneFeatureString(StringRef TFString,
                              SmallVectorImpl<std::string> &ResFeatures) {
-  const StringSet<> AllTuneFeatureSet(llvm::from_range, TuneFeatures);
   using SmallStringSet = SmallSet<StringRef, 4>;
+  RISCVTuneFeatureLookupTable TFLookup;
 
   // Do not create ParserWarning right away. Instead, we store the warning
   // message until the last moment.
@@ -176,27 +239,21 @@ Error parseTuneFeatureString(StringRef TFString,
   SmallStringSet PositiveFeatures;
   SmallStringSet NegativeFeatures;
   // Phase 1: Collect explicit features.
-  StringRef FeatureStr;
+  StringRef DirectiveStr;
   do {
-    std::tie(FeatureStr, TFString) = TFString.split(",");
-    if (AllTuneFeatureSet.count(FeatureStr)) {
-      if (!PositiveFeatures.insert(FeatureStr).second)
-        return make_error<ParserError>(
-            "cannot specify more than one instance of '" + Twine(FeatureStr) +
-            "'");
-    } else if (FeatureStr.starts_with("no-")) {
-      // Check if this is a negative feature, like `no-foo` for `foo`.
-      StringRef ActualFeature = FeatureStr.drop_front(3);
-      if (AllTuneFeatureSet.count(ActualFeature)) {
-        if (!NegativeFeatures.insert(ActualFeature).second)
-          return make_error<ParserError>(
-              "cannot specify more than one instance of '" + Twine(FeatureStr) +
-              "'");
-      }
-    } else {
+    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 '" << FeatureStr << "'";
+      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) +
+          "'");
   } while (!TFString.empty());
 
   auto Intersection =
@@ -209,25 +266,19 @@ Error parseTuneFeatureString(StringRef TFString,
   }
 
   // Phase 2: Derive implied features.
-  StringMap<SmallVector<StringRef, 2>> ImpliedFeatureMap;
-  StringMap<SmallVector<StringRef, 2>> InverseImpliedFeatureMap;
-  for (auto [Feature, ImpliedFeature] : ImpliedTuneFeatures) {
-    ImpliedFeatureMap[Feature].push_back(ImpliedFeature);
-    InverseImpliedFeatureMap[ImpliedFeature].push_back(Feature);
-  }
-
+  SmallStringSet DerivedPosFeatures;
+  SmallStringSet DerivedNegFeatures;
   for (StringRef PF : PositiveFeatures) {
-    auto ItFeatures = ImpliedFeatureMap.find(PF);
-    if (ItFeatures != ImpliedFeatureMap.end())
-      PositiveFeatures.insert(ItFeatures->second.begin(),
-                              ItFeatures->second.end());
+    if (auto FeatureList = TFLookup.featureImplies(PF); !FeatureList.empty())
+      DerivedPosFeatures.insert_range(FeatureList);
   }
   for (StringRef NF : NegativeFeatures) {
-    auto ItFeatures = InverseImpliedFeatureMap.find(NF);
-    if (ItFeatures != InverseImpliedFeatureMap.end())
-      NegativeFeatures.insert(ItFeatures->second.begin(),
-                              ItFeatures->second.end());
+    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()) {
diff --git a/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp b/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
index 6288601529da2..d294fee88557f 100644
--- a/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
+++ b/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
@@ -36,6 +36,8 @@ TEST(RISCVVType, CheckSameRatioLMUL) {
 TEST(RISCVTuneFeature, AllTuneFeatures) {
   SmallVector<StringRef> AllTuneFeatures;
   RISCV::getAllTuneFeatures(AllTuneFeatures);
+  // Only allowed subtarget features that are explicitly marked by
+  // special TableGen class.
   EXPECT_EQ(AllTuneFeatures.size(), 28U);
   for (auto F : {"conditional-cmv-fusion",
                  "dlen-factor-2",
@@ -80,6 +82,7 @@ TEST(RISCVTuneFeature, LegalTuneFeatureStrings) {
   EXPECT_TRUE(is_contained(Result, "-short-forward-branch-i-mul"));
 
   Result.clear();
+  // Test inverse implied features.
   EXPECT_FALSE(errorToBool(RISCV::parseTuneFeatureString(
       "no-short-forward-branch-i-mul,short-forward-branch-i-minmax", Result)));
   EXPECT_TRUE(is_contained(Result, "+short-forward-branch-i-minmax"));
@@ -87,10 +90,14 @@ TEST(RISCVTuneFeature, LegalTuneFeatureStrings) {
   EXPECT_TRUE(is_contained(Result, "-short-forward-branch-i-mul"));
 
   Result.clear();
-  EXPECT_FALSE(errorToBool(RISCV::parseTuneFeatureString(
-      "no-no-default-unroll,no-sink-splat-operands", Result)));
+  // Test custom directive names.
+  EXPECT_FALSE(errorToBool(
+      RISCV::parseTuneFeatureString("enable-default-unroll,no-sink-splat-"
+                                    "operands,enable-latency-sched-heuristic",
+                                    Result)));
   EXPECT_TRUE(is_contained(Result, "+no-sink-splat-operands"));
   EXPECT_TRUE(is_contained(Result, "-no-default-unroll"));
+  EXPECT_TRUE(is_contained(Result, "-disable-latency-sched-heuristic"));
 }
 
 TEST(RISCVTuneFeature, IgnoreUnrecognizedTuneFeature) {
@@ -116,6 +123,15 @@ TEST(RISCVTuneFeature, DuplicatedFeatures) {
             "Feature(s) 'log-vrgather', 'short-forward-branch-i-mul' cannot "
             "appear in both positive and negative directives");
 
+  // The error message should show the feature name for those using custom
+  // directives.
+  EXPECT_EQ(
+      toString(RISCV::parseTuneFeatureString(
+          "disable-latency-sched-heuristic,enable-latency-sched-heuristic",
+          Result)),
+      "Feature(s) 'disable-latency-sched-heuristic' cannot appear in both "
+      "positive and negative directives");
+
   EXPECT_EQ(
       toString(RISCV::parseTuneFeatureString(
           "short-forward-branch-i-mul,no-short-forward-branch-opt", Result)),
diff --git a/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp b/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp
index 7420fa439ecd4..cbebd2577e81f 100644
--- a/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp
@@ -13,9 +13,11 @@
 
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/Support/Format.h"
+#include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/RISCVISAUtils.h"
 #include "llvm/TableGen/Error.h"
 #include "llvm/TableGen/Record.h"
+#include "llvm/TableGen/StringToOffsetTable.h"
 #include "llvm/TableGen/TableGenBackend.h"
 
 using namespace llvm;
@@ -265,47 +267,102 @@ static void emitRISCVExtensionBitmask(const RecordKeeper &RK, raw_ostream &OS) {
 
 static void emitRISCVTuneFeatures(const RecordKeeper &RK, raw_ostream &OS) {
   std::vector<const Record *> TuneFeatureRecords =
-      RK.getAllDerivedDefinitionsIfDefined("RISCVTuneFeature");
+      RK.getAllDerivedDefinitionsIfDefined("RISCVTuneFeatureBase");
+
+  // {Post Directive Idx, Neg Directive Idx, TuneFeature Record}
+  SmallVector<std::tuple<unsigned, unsigned, const Record *>>
+      TuneFeatureDirectives;
+  // {Directive Idx -> Original Record}
+  // This is primarily for diagnosing purposes -- when there is a duplication,
+  // we are able to pointed out the previous definition.
+  DenseMap<unsigned, const Record *> DirectiveToRecord;
+  // A list of {Feature Name, Implied Feature Name}
+  SmallVector<std::pair<StringRef, StringRef>> ImpliedFeatureList;
+  StringToOffsetTable StrTable;
+
+  auto tryInsertDirectives = [&](StringRef PosName, StringRef NegName,
+                                 const Record *R) {
+    unsigned PosIdx = StrTable.GetOrAddStringOffset(PosName);
+    if (auto [ItEntry, Inserted] = DirectiveToRecord.try_emplace(PosIdx, R);
+        !Inserted) {
+      PrintError(R, "RISC-V tune feature positive directive '" +
+                        Twine(PosName) + "' was already defined");
+      PrintFatalNote(ItEntry->second, "Previously defined here");
+    }
+    unsigned NegIdx = StrTable.GetOrAddStringOffset(NegName);
+    if (auto [ItEntry, Inserted] = DirectiveToRecord.try_emplace(NegIdx, R);
+        !Inserted) {
+      PrintError(R, "RISC-V tune feature negative directive '" +
+                        Twine(NegName) + "' was already defined");
+      PrintFatalNote(ItEntry->second, "Previously defined here");
+    }
 
-  SmallVector<StringRef> TuneFeatures;
-  // A list of {Feature -> Implied Feature}
-  SmallVector<std::pair<StringRef, StringRef>> ImpliedFeatures;
+    TuneFeatureDirectives.emplace_back(PosIdx, NegIdx, R);
+  };
+
+  const std::string SimpleNegPrefix("no-");
   for (const auto *R : TuneFeatureRecords) {
+    if (!R->isSubClassOf("SubtargetFeature"))
+      PrintFatalError(
+          R, "A RISC-V tune feature should also be a SubtargetFeature");
+    // Preemptively insert feature name into the string table because we know
+    // it will be used later.
     StringRef FeatureName = R->getValueAsString("Name");
-    TuneFeatures.push_back(FeatureName);
+    StrTable.GetOrAddStringOffset(FeatureName);
+    if (R->isSubClassOf("RISCVSimpleTuneFeature")) {
+      // The positive directve will be the feature name, and the negative
+      // directive will be "no-" + feature name.
+      std::string NegName = SimpleNegPrefix + FeatureName.str();
+      tryInsertDirectives(FeatureName, NegName, R);
+    } else if (R->isSubClassOf("RISCVTuneFeature")) {
+      StringRef PosName = R->getValueAsString("PositiveDirectiveName");
+      StringRef NegName = R->getValueAsString("NegativeDirectiveName");
+      tryInsertDirectives(PosName, NegName, R);
+    } else {
+      llvm_unreachable("unrecognized RISCVTuneFeatureBase");
+    }
+  }
+
+  for (const auto *R : TuneFeatureRecords) {
     std::vector<const Record *> Implies = R->getValueAsListOfDefs("Implies");
     for (const auto *ImpliedRecord : Implies) {
-      if (!ImpliedRecord->isSubClassOf("RISCVTuneFeature") ||
+      if (!ImpliedRecord->isSubClassOf("RISCVTuneFeatureBase") ||
           ImpliedRecord == R) {
         PrintError(ImpliedRecord,
-                   "RISCVTuneFeature can only imply other RISCVTuneFeatures");
-        PrintFatalNote(R, "implied by this RISCVTuneFeature");
+                   "A RISC-V tune feature can only imply another tune feature");
+        PrintFatalNote(R, "implied by this tune feature");
       }
-      ImpliedFeatures.emplace_back(FeatureName,
-                                   ImpliedRecord->getValueAsString("Name"));
+      StringRef CurrFeatureName = R->getValueAsString("Name");
+      StringRef ImpliedFeatureName = ImpliedRecord->getValueAsString("Name");
+
+      ImpliedFeatureList.emplace_back(CurrFeatureName, ImpliedFeatureName);
     }
   }
 
   OS << "#ifdef GET_TUNE_FEATURES\n";
   OS << "#undef GET_TUNE_FEATURES\n\n";
 
-  OS << "static const char *TuneFeatures[] = {\n";
-  for (StringRef Feature : TuneFeatures)
-    OS.indent(4) << "\"" << Feature << "\",\n";
-  OS << "};\n\n";
-
-  OS << "#endif // GET_TUNE_FEATURES\n\n";
+  StrTable.EmitStringTableDef(OS, "TuneFeatureStrings");
+  OS << "\n";
 
-  OS << "#ifdef GET_IMPLIED_TUNE_FEATURES\n";
-  OS << "#undef GET_IMPLIED_TUNE_FEATURES\n\n";
+  OS << "static constexpr RISCVTuneFeature TuneFeatures[] = {\n";
+  for (const auto &[PosIdx, NegIdx, R] : TuneFeatureDirectives) {
+    StringRef FeatureName = R->getValueAsString("Name");
+    OS.indent(4) << formatv("{{ {0}, {1}, {2} },\t// '{3}'\n", PosIdx, NegIdx,
+                            *StrTable.GetStringOffset(FeatureName),
+                            FeatureName);
+  }
+  OS << "};\n\n";
 
-  OS << "static const RISCVImpliedTuneFeature ImpliedTuneFeatures[] = {\n";
-  for (auto [Feature, ImpliedFeature] : ImpliedFeatures)
-    OS.indent(4) << "{" << "\"" << Feature << "\", \"" << ImpliedFeature
-                 << "\"},\n";
+  OS << "static constexpr RISCVImpliedTuneFeature ImpliedTuneFeatures[] = {\n";
+  for (auto [Feature, ImpliedFeature] : ImpliedFeatureList)
+    OS.indent(4) << formatv("{{ {0}, {1} }, // '{2}' -> '{3}'\n",
+                            *StrTable.GetStringOffset(Feature),
+                            *StrTable.GetStringOffset(ImpliedFeature), Feature,
+                            ImpliedFeature);
   OS << "};\n\n";
 
-  OS << "#endif // GET_IMPLIED_TUNE_FEATURES\n";
+  OS << "#endif // GET_TUNE_FEATURES\n";
 }
 
 static void emitRiscvTargetDef(const RecordKeeper &RK, raw_ostream &OS) {



More information about the llvm-commits mailing list