[llvm] [RISCV] Introduce a new syntax for processor-specific tuning feature strings (Take 2) (PR #175063)

Min-Yih Hsu via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 8 13:30:20 PST 2026


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

>From 6727fc4f2a775477887b6e2d442361d98d31e74b Mon Sep 17 00:00:00 2001
From: Min-Yih Hsu <min.hsu at sifive.com>
Date: Wed, 7 Jan 2026 15:35:01 -0800
Subject: [PATCH 1/2] [RISCV] Introduce a new syntax for specifying processor
 tuning feature strings

Co-Authored-By: Sam Elliott <aelliott at qti.qualcomm.com>
---
 llvm/docs/RISCVUsage.rst                      |  35 +++
 .../llvm/TargetParser/RISCVTargetParser.h     |  26 +++
 llvm/lib/Target/RISCV/RISCVFeatures.td        | 145 ++++++++-----
 llvm/lib/Target/RISCV/RISCVProcessors.td      |  17 +-
 llvm/lib/TargetParser/RISCVTargetParser.cpp   | 205 ++++++++++++++++++
 llvm/test/TableGen/riscv-target-def.td        |  33 ++-
 .../TargetParser/RISCVTargetParserTest.cpp    | 131 +++++++++++
 .../TableGen/Basic/RISCVTargetDefEmitter.cpp  | 120 +++++++++-
 8 files changed, 656 insertions(+), 56 deletions(-)

diff --git a/llvm/docs/RISCVUsage.rst b/llvm/docs/RISCVUsage.rst
index df5a595e8ec93..a94a21c57cd5a 100644
--- a/llvm/docs/RISCVUsage.rst
+++ b/llvm/docs/RISCVUsage.rst
@@ -610,3 +610,38 @@ Clang's ``-msmall-data-limit=`` option controls what the threshold size is (in b
 The small data limit threshold is also used to separate small constants into sections with names starting with ``.srodata``. LLD does not place these with the ``.sdata`` and ``.sbss`` sections as ``.srodata`` sections are read only and the other two are writable. Instead the ``.srodata`` sections are placed adjacent to ``.rodata``.
 
 Data suggests that these options can produce significant improvements across a range of benchmarks.
+
+Processor-Specific Tuning Feature String
+========================================
+Due to RISC-V's highly configurable nature, it is often desirable to share a single scheduling model across multiple similar RISC-V processors that only differ in a small number of (uArch) tuning features. An example of such tuning feature could be whether the latency of vector operations depend on VL or not. This could be extended to tuning features that are not directly connected to scheduling model but other parts of the RISC-V backend, like the cost of ``vrgather.vv`` instruction.
+
+To that end, RISC-V LLVM supports a tuning feature string format that helps users to build a performance model by "configuring" an existing tune CPU, along with its scheduling model. For example, this string
+
+::
+    "sifive-x280:single-element-vec-fp64"
+
+takes ``sifive-x280`` as the "base" tune CPU and configured it with ``single-element-vec-fp64``. This gives us a performance model that looks exactly like that of ``sifive-x280``, except some of the 64-bit vector floating point instructions now produce only a single element per cycle due to ``single-element-vec-fp64``. This string could eventually be used in places like ``-mtune`` at the frontend.
+
+More formally speaking, each tuning feature string has the following format:
+
+::
+    tune-cpu      ::= 'tuning CPU name in lower case'
+    directive     ::= "[a-zA-Z0-9\_-]+"
+    tune-features ::= directive ["," directive]*
+
+A *directive* can and can only _enable_ or _disable_ a certain tuning feature from the tuning CPU. A **positive directive**, like the ``single-element-vec-fp64`` we just saw, enables an additional tuning feature in the associated tuning model. A **negative directive**, on the other hand, removes a certain tuning feature. For example, ``sifive-x390`` already has the ``single-element-vec-fp64`` feature, and we can use
+
+::
+    "sifive-x390:no-single-element-vec-fp64"
+
+to create a new performance model that looks nearly the same as ``sifive-x390`` except ``single-element-vec-fp64`` being cut out. In this case, ``no-single-element-vec-fp64`` is a negative directive.
+
+There are some rules for the list of directives, though:
+
+1. The same directive cannot appear more than once.
+
+2. The positive and negative directives that belong to the same feature cannot appear at the same time.
+
+3. If a feature implies other features -- for example, ``short-forward-branch-imul`` implies ``short-forward-branch-ialu`` -- then the _implied_ features are subject to the previous two rules, too. For example, we cannot write _"short-forward-branch-imul,no-short-forward-branch-ialu"_, because the feature implied by ``short-forward-branch-imul`` violates rule 2.
+
+In addition to the rules listed above, right now, this string only accepts directives that are explicitly supported by the tune CPU. For example, _"sifive-x280:prefer-w-inst"_ is not a valide string as ``prefer-w-inst`` is not supported by ``sifive-x280`` at this moment. Vendors of these processors are expected to maintain the compatibility of their supported directives across different versions. There have been lots of discussions on having "generic" features that are universally supported by all RISC-V CPUs, yet many concerns -- including the difficulty to maintain compatibility across _all_ CPU targets and versions -- make us decide to table this issue until we find a reliable process to select such features.
diff --git a/llvm/include/llvm/TargetParser/RISCVTargetParser.h b/llvm/include/llvm/TargetParser/RISCVTargetParser.h
index 0b4c45e445bb6..b0ed9efcb9d82 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"
 
@@ -47,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;
@@ -54,6 +71,15 @@ 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 void
+getCPUConfigurableTuneFeatures(StringRef CPU,
+                               SmallVectorImpl<StringRef> &Directives);
+/// Parse the tune feature string with the respective processor. If \p ProcName
+/// is empty, directives are not filtered by processor.
+LLVM_ABI Error
+parseTuneFeatureString(StringRef ProcName, 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 66e1b18f37392..33f6fb7eab8f1 100644
--- a/llvm/lib/Target/RISCV/RISCVFeatures.td
+++ b/llvm/lib/Target/RISCV/RISCVFeatures.td
@@ -1734,6 +1734,21 @@ def HasVendorXSMTVDot
 // LLVM specific features and extensions
 //===----------------------------------------------------------------------===//
 
+class RISCVTuneFeature<string name, string pos_directive, string neg_directive,
+                       string field_name, string value, string description,
+                       list<RISCVTuneFeature> implied_features = []>
+    : SubtargetFeature<name, field_name, value, description> {
+  let Implies = implied_features;
+
+  string PositiveDirectiveName = pos_directive;
+  string NegativeDirectiveName = neg_directive;
+}
+class RISCVSimpleTuneFeature<string name, string field_name, string value,
+                             string description,
+                             list<RISCVTuneFeature> implied_features = []>
+    : RISCVTuneFeature<name, name, "no-"#name, field_name, value, description,
+                       implied_features>;
+
 // Feature32Bit exists to mark CPUs that support RV32 to distinguish them from
 // tuning CPU names.
 def Feature32Bit
@@ -1824,28 +1839,50 @@ def TuneNLogNVRGather
    : SubtargetFeature<"log-vrgather", "RISCVVRGatherCostModel", "NLog2N",
                       "Has vrgather.vv with LMUL*log2(LMUL) latency">;
 
-def TunePostRAScheduler : SubtargetFeature<"use-postra-scheduler",
-    "UsePostRAScheduler", "true", "Schedule again after register allocation">;
-
-def TuneDisableMISchedLoadClustering : SubtargetFeature<"disable-misched-load-clustering",
-    "EnableMISchedLoadClustering", "false", "Disable load clustering in the machine scheduler">;
-
-def TuneDisableMISchedStoreClustering : SubtargetFeature<"disable-misched-store-clustering",
-    "EnableMISchedStoreClustering", "false", "Disable store clustering in the machine scheduler">;
-
-def TuneDisablePostMISchedLoadClustering : SubtargetFeature<"disable-postmisched-load-clustering",
-    "EnablePostMISchedLoadClustering", "false", "Disable PostRA load clustering in the machine scheduler">;
-
-def TuneDisablePostMISchedStoreClustering : SubtargetFeature<"disable-postmisched-store-clustering",
-    "EnablePostMISchedStoreClustering", "false", "Disable PostRA store clustering in the machine scheduler">;
+def TunePostRAScheduler
+    : RISCVSimpleTuneFeature<"use-postra-scheduler", "UsePostRAScheduler",
+                             "true",
+                             "Schedule again after register allocation">;
+
+def TuneDisableMISchedLoadClustering
+    : RISCVTuneFeature<
+          "disable-misched-load-clustering", "disable-misched-load-clustering",
+          "enable-misched-load-clustering", "EnableMISchedLoadClustering",
+          "false", "Disable load clustering in the machine scheduler">;
+
+def TuneDisableMISchedStoreClustering
+    : RISCVTuneFeature<"disable-misched-store-clustering",
+                       "disable-misched-store-clustering",
+                       "enable-misched-store-clustering",
+                       "EnableMISchedStoreClustering", "false",
+                       "Disable store clustering in the machine scheduler">;
+
+def TuneDisablePostMISchedLoadClustering
+    : RISCVTuneFeature<
+          "disable-postmisched-load-clustering",
+          "disable-postmisched-load-clustering",
+          "enable-postmisched-load-clustering",
+          "EnablePostMISchedLoadClustering", "false",
+          "Disable PostRA load clustering in the machine scheduler">;
+
+def TuneDisablePostMISchedStoreClustering
+    : RISCVTuneFeature<
+          "disable-postmisched-store-clustering",
+          "disable-postmisched-store-clustering",
+          "enable-postmisched-store-clustering",
+          "EnablePostMISchedStoreClustering", "false",
+          "Disable PostRA store clustering in the machine scheduler">;
 
 def TuneDisableLatencySchedHeuristic
-    : SubtargetFeature<"disable-latency-sched-heuristic", "DisableLatencySchedHeuristic", "true",
-                       "Disable latency scheduling heuristic">;
+    : RISCVTuneFeature<
+          "disable-latency-sched-heuristic", "disable-latency-sched-heuristic",
+          "enable-latency-sched-heuristic", "DisableLatencySchedHeuristic",
+          "true", "Disable latency scheduling heuristic">;
 
 def TunePredictableSelectIsExpensive
-    : SubtargetFeature<"predictable-select-expensive", "PredictableSelectIsExpensive", "true",
-                       "Prefer likely predicted branches over selects">;
+    : RISCVSimpleTuneFeature<"predictable-select-expensive",
+                             "PredictableSelectIsExpensive", "true",
+                             "Prefer likely predicted branches over selects">;
 
 def TuneOptimizedZeroStrideLoad
    : SubtargetFeature<"optimized-zero-stride-load", "HasOptimizedZeroStrideLoad",
@@ -1860,9 +1897,10 @@ foreach nf = {2-8} in
                        "implemented as a wide memory op and shuffle">;
 
 def TuneVLDependentLatency
-    : SubtargetFeature<"vl-dependent-latency", "HasVLDependentLatency", "true",
-                       "Latency of vector instructions is dependent on the "
-                       "dynamic value of vl">;
+    : RISCVSimpleTuneFeature<
+          "vl-dependent-latency", "HasVLDependentLatency", "true",
+          "Latency of vector instructions is dependent on the "
+          "dynamic value of vl">;
 
 def Experimental
    : SubtargetFeature<"experimental", "HasExperimental",
@@ -1876,7 +1914,8 @@ def TuneDLenFactor2
                       "Vector unit DLEN(data path width) is half of VLEN">;
 
 def TuneNoDefaultUnroll
-    : SubtargetFeature<"no-default-unroll", "EnableDefaultUnroll", "false",
+    : RISCVTuneFeature<"no-default-unroll", "no-default-unroll",
+                       "enable-default-unroll", "EnableDefaultUnroll", "false",
                        "Disable default unroll preference.">;
 
 // Many Microarchitectures are able to fuse a branch over a single instruction
@@ -1901,59 +1940,67 @@ def TuneNoDefaultUnroll
 // the vendor-specific instructions should only be enabled for vendor
 // cores.
 def TuneShortForwardBranchIALU
-    : SubtargetFeature<"short-forward-branch-ialu", "HasShortForwardBranchIALU",
-                       "true", "Enable short forward branch optimization for RVI base instructions">;
+    : RISCVSimpleTuneFeature<
+          "short-forward-branch-ialu", "HasShortForwardBranchIALU", "true",
+          "Enable short forward branch optimization for RVI base instructions">;
 def HasShortForwardBranchIALU : Predicate<"Subtarget->hasShortForwardBranchIALU()">;
 def NoShortForwardBranch : Predicate<"!Subtarget->hasShortForwardBranchIALU()">;
 
 def TuneShortForwardBranchIMinMax
-    : SubtargetFeature<"short-forward-branch-iminmax", "HasShortForwardBranchIMinMax",
-                       "true", "Enable short forward branch optimization for MIN,MAX instructions in Zbb",
-                       [TuneShortForwardBranchIALU]>;
+    : RISCVSimpleTuneFeature<"short-forward-branch-iminmax",
+                             "HasShortForwardBranchIMinMax", "true",
+                             "Enable short forward branch optimization for "
+                             "MIN,MAX instructions in Zbb",
+                             [TuneShortForwardBranchIALU]>;
 def HasShortForwardBranchIMinMax : Predicate<"Subtarget->hasShortForwardBranchIMinMax()">;
 
 def TuneShortForwardBranchIMul
-    : SubtargetFeature<"short-forward-branch-imul", "HasShortForwardBranchIMul",
-                       "true", "Enable short forward branch optimization for MUL instruction",
-                       [TuneShortForwardBranchIALU]>;
+    : RISCVSimpleTuneFeature<
+          "short-forward-branch-imul", "HasShortForwardBranchIMul", "true",
+          "Enable short forward branch optimization for MUL instruction",
+          [TuneShortForwardBranchIALU]>;
 def HasShortForwardBranchIMul : Predicate<"Subtarget->hasShortForwardBranchIMul()">;
 
 def TuneShortForwardBranchILoad
-    : SubtargetFeature<"short-forward-branch-iload", "HasShortForwardBranchILoad",
-                       "true", "Enable short forward branch optimization for load instructions",
-                       [TuneShortForwardBranchIALU]>;
+    : RISCVSimpleTuneFeature<
+          "short-forward-branch-iload", "HasShortForwardBranchILoad", "true",
+          "Enable short forward branch optimization for load instructions",
+          [TuneShortForwardBranchIALU]>;
 def HasShortForwardBranchILoad : Predicate<"Subtarget->hasShortForwardBranchILoad()">;
 
 // 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",
-                       "false", "Disable sink splat operands to enable .vx, .vf,"
-                       ".wx, and .wf instructions">;
+    : RISCVTuneFeature<"no-sink-splat-operands", "no-sink-splat-operands",
+                       "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",
-                       "Prefer instructions with W suffix">;
+    : RISCVSimpleTuneFeature<"prefer-w-inst", "PreferWInst", "true",
+                             "Prefer instructions with W suffix">;
 
 def TuneConditionalCompressedMoveFusion
-    : SubtargetFeature<"conditional-cmv-fusion", "HasConditionalCompressedMoveFusion",
-                       "true", "Enable branch+c.mv fusion">;
+    : RISCVSimpleTuneFeature<"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",
-                       "Certain vector FP64 operations produce a single result "
-                       "element per cycle">;
+    : RISCVSimpleTuneFeature<
+          "single-element-vec-fp64", "HasSingleElementVectorFP64", "true",
+          "Certain vector FP64 operations produce a single result "
+          "element per cycle">;
 
-def TuneVXRMPipelineFlush : SubtargetFeature<"vxrm-pipeline-flush", "HasVXRMPipelineFlush",
-                                             "true", "VXRM writes causes pipeline flush">;
+def TuneVXRMPipelineFlush
+    : RISCVSimpleTuneFeature<"vxrm-pipeline-flush", "HasVXRMPipelineFlush",
+                             "true", "VXRM writes causes pipeline flush">;
 
 def TunePreferVsetvliOverReadVLENB
-    : SubtargetFeature<"prefer-vsetvli-over-read-vlenb",
-                       "PreferVsetvliOverReadVLENB",
-                       "true",
-                       "Prefer vsetvli over read vlenb CSR to calculate VLEN">;
+    : RISCVSimpleTuneFeature<
+          "prefer-vsetvli-over-read-vlenb", "PreferVsetvliOverReadVLENB",
+          "true", "Prefer vsetvli over read vlenb CSR to calculate VLEN">;
 
 //===----------------------------------------------------------------------===//
 // CPU Families (alphabetized by vendor).
diff --git a/llvm/lib/Target/RISCV/RISCVProcessors.td b/llvm/lib/Target/RISCV/RISCVProcessors.td
index 5becfd2ad502b..f378f3c2888c7 100644
--- a/llvm/lib/Target/RISCV/RISCVProcessors.td
+++ b/llvm/lib/Target/RISCV/RISCVProcessors.td
@@ -78,13 +78,16 @@ class RISCVProcessorModel<string n,
   int MVendorID = 0;
   int MArchID = 0;
   int MImpID = 0;
+
+  list<RISCVTuneFeature> ConfigurableTuneFeatures = [];
 }
 
-class RISCVTuneProcessorModel<string n,
-                              SchedMachineModel m,
+class RISCVTuneProcessorModel<string n, SchedMachineModel m,
                               list<SubtargetFeature> tunef = [],
                               list<SubtargetFeature> f = []>
-    : ProcessorModel<n, m, f,tunef>;
+    : ProcessorModel<n, m, f, tunef> {
+  list<RISCVTuneFeature> ConfigurableTuneFeatures = [];
+}
 
 defvar GenericTuneFeatures = [TuneOptimizedNF2SegmentLoadStore];
 
@@ -293,7 +296,9 @@ def SIFIVE_X280 : RISCVProcessorModel<"sifive-x280", SiFive7Model,
                                        FeatureStdExtZvfh,
                                        FeatureStdExtZba,
                                        FeatureStdExtZbb],
-                                      SiFiveIntelligenceTuneFeatures>;
+                                      SiFiveIntelligenceTuneFeatures> {
+  let ConfigurableTuneFeatures = [TuneHasSingleElementVecFP64];
+}
 
 def SIFIVE_X390 : RISCVProcessorModel<"sifive-x390",
                                       SiFiveX390Model,
@@ -339,7 +344,9 @@ def SIFIVE_X390 : RISCVProcessorModel<"sifive-x390",
                                        FeatureVendorXSiFivecdiscarddlone,
                                        FeatureVendorXSiFivecflushdlone],
                                       !listconcat(SiFiveIntelligenceTuneFeatures,
-                                                  [TuneHasSingleElementVecFP64])>;
+                                                  [TuneHasSingleElementVecFP64])> {
+  let ConfigurableTuneFeatures = [TuneHasSingleElementVecFP64];
+}
 
 defvar SiFiveP400TuneFeatures = [TuneNoDefaultUnroll,
                                  TuneConditionalCompressedMoveFusion,
diff --git a/llvm/lib/TargetParser/RISCVTargetParser.cpp b/llvm/lib/TargetParser/RISCVTargetParser.cpp
index d0bd39f15afc4..eef426d7c9aee 100644
--- a/llvm/lib/TargetParser/RISCVTargetParser.cpp
+++ b/llvm/lib/TargetParser/RISCVTargetParser.cpp
@@ -12,13 +12,20 @@
 //===----------------------------------------------------------------------===//
 
 #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)                  \
@@ -145,6 +152,204 @@ void getFeaturesForCPU(StringRef CPU,
       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 {
diff --git a/llvm/test/TableGen/riscv-target-def.td b/llvm/test/TableGen/riscv-target-def.td
index 79178731f12a7..7e97a7926a2d4 100644
--- a/llvm/test/TableGen/riscv-target-def.td
+++ b/llvm/test/TableGen/riscv-target-def.td
@@ -1,4 +1,5 @@
 // RUN: llvm-tblgen -gen-riscv-target-def -I %p/../../include %s | FileCheck %s
+// RUN: not llvm-tblgen -gen-riscv-target-def -DDUPLICATE -I %p/../../include %s 2>&1 | FileCheck %s --check-prefix=DUPLICATE
 
 include "llvm/Target/Target.td"
 
@@ -26,6 +27,21 @@ class RISCVExperimentalExtension<string name, int major, int minor, string desc,
   let Experimental = true;
 }
 
+class RISCVTuneFeature<string name, string pos_directive, string neg_directive,
+                       string field_name, string value, string description,
+                       list<RISCVTuneFeature> implied_features = []>
+    : SubtargetFeature<name, field_name, value, description> {
+  let Implies = implied_features;
+
+  string PositiveDirectiveName = pos_directive;
+  string NegativeDirectiveName = neg_directive;
+}
+class RISCVSimpleTuneFeature<string name, string field_name, string value,
+                             string description,
+                             list<RISCVTuneFeature> implied_features = []>
+    : RISCVTuneFeature<name, name, "no-"#name, field_name, value, description,
+                       implied_features>;
+
 def FeatureStdExtI
     : RISCVExtension<"i", 2, 1,
                      "'I' (Base Integer Instruction Set)">,
@@ -84,13 +100,17 @@ class RISCVProcessorModel<string n,
   int MVendorID = 0;
   int MArchID = 0;
   int MImpID = 0;
+
+  list<RISCVTuneFeature> ConfigurableTuneFeatures = [];
 }
 
 class RISCVTuneProcessorModel<string n,
                               SchedMachineModel m,
                               list<SubtargetFeature> tunef = [],
                               list<SubtargetFeature> f = []>
-    : ProcessorModel<n, m, f,tunef>;
+    : ProcessorModel<n, m, f, tunef> {
+  list<RISCVTuneFeature> ConfigurableTuneFeatures = [];
+}
 
 def GENERIC_RV32 : RISCVProcessorModel<"generic-rv32",
                                        NoSchedModel,
@@ -188,3 +208,14 @@ def ROCKET : RISCVTuneProcessorModel<"rocket",
 // CHECK-NEXT:     {"i", 0, 8ULL},
 // CHECK-NEXT: };
 // CHECK-NEXT: #endif
+
+#ifdef DUPLICATE
+def TuneFeatureBar : RISCVTuneFeature<"bar", "abc", "def", "HasBar", "true", "TBA">;
+
+def TuneFeatureFoo : RISCVTuneFeature<"foo", "xyz", "abc", "HasFoo", "true", "TBA">;
+
+// DUPLICATE: error: RISC-V tune feature negative directive 'abc' was already defined
+// DUPLICATE-NEXT: def TuneFeatureFoo
+// DUPLICATE: note: Previously defined here
+// DUPLICATE-NEXT: def TuneFeatureBar
+#endif
diff --git a/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp b/llvm/unittests/TargetParser/RISCVTargetParserTest.cpp
index f778568dd373a..5710aff6f5bb6 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;
@@ -36,4 +38,133 @@ TEST(RISCVVType, CheckSameRatioLMUL) {
             RISCVVType::getSameRatioLMUL(
                 RISCVVType::getSEWLMULRatio(8, RISCVVType::LMUL_F4), 16));
 }
+
+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(), 19U);
+  for (auto F :
+       {"conditional-cmv-fusion", "disable-latency-sched-heuristic",
+        "disable-misched-load-clustering", "disable-misched-store-clustering",
+        "disable-postmisched-load-clustering",
+        "disable-postmisched-store-clustering", "single-element-vec-fp64",
+        "no-default-unroll", "no-sink-splat-operands", "use-postra-scheduler",
+        "predictable-select-expensive", "prefer-vsetvli-over-read-vlenb",
+        "prefer-w-inst", "short-forward-branch-ialu",
+        "short-forward-branch-iminmax", "short-forward-branch-imul",
+        "short-forward-branch-iload", "vl-dependent-latency",
+        "vxrm-pipeline-flush"})
+    EXPECT_TRUE(is_contained(AllTuneFeatures, F));
+}
+
+TEST(RISCVTuneFeature, LegalTuneFeatureStrings) {
+  SmallVector<std::string> Result;
+  EXPECT_FALSE(errorToBool(RISCV::parseTuneFeatureString(
+      /*ProcName=*/"",
+      "prefer-w-inst,no-short-forward-branch-ialu,vl-dependent-latency",
+      Result)));
+  EXPECT_TRUE(is_contained(Result, "+prefer-w-inst"));
+  EXPECT_TRUE(is_contained(Result, "+vl-dependent-latency"));
+  EXPECT_TRUE(is_contained(Result, "-short-forward-branch-ialu"));
+  EXPECT_TRUE(is_contained(Result, "-short-forward-branch-iminmax"));
+  EXPECT_TRUE(is_contained(Result, "-short-forward-branch-imul"));
+  EXPECT_TRUE(is_contained(Result, "-short-forward-branch-iload"));
+
+  Result.clear();
+  // Test inverse implied features.
+  EXPECT_FALSE(errorToBool(RISCV::parseTuneFeatureString(
+      /*ProcName=*/"",
+      "no-short-forward-branch-imul,short-forward-branch-iminmax", Result)));
+  EXPECT_TRUE(is_contained(Result, "+short-forward-branch-iminmax"));
+  EXPECT_TRUE(is_contained(Result, "+short-forward-branch-ialu"));
+  EXPECT_TRUE(is_contained(Result, "-short-forward-branch-imul"));
+
+  Result.clear();
+  // Test custom directive names.
+  EXPECT_FALSE(errorToBool(
+      RISCV::parseTuneFeatureString(/*ProcName=*/"",
+                                    "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) {
+  SmallVector<std::string> Result;
+  auto Err = RISCV::parseTuneFeatureString(/*ProcName=*/"",
+                                           "32bit,prefer-w-inst", 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, "+prefer-w-inst"));
+}
+
+TEST(RISCVTuneFeature, DuplicatedFeatures) {
+  SmallVector<std::string> Result;
+  EXPECT_EQ(toString(RISCV::parseTuneFeatureString(
+                /*ProcName=*/"", "prefer-w-inst,prefer-w-inst", Result)),
+            "cannot specify more than one instance of 'prefer-w-inst'");
+
+  EXPECT_EQ(toString(RISCV::parseTuneFeatureString(
+                /*ProcName=*/"",
+                "prefer-w-inst,no-prefer-w-inst,short-forward-branch-imul,no-"
+                "short-forward-branch-imul",
+                Result)),
+            "Feature(s) 'prefer-w-inst', 'short-forward-branch-imul' 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(
+          /*ProcName=*/"",
+          "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(
+          /*ProcName=*/"",
+          "short-forward-branch-imul,no-short-forward-branch-ialu", Result)),
+      "Feature(s) 'short-forward-branch-imul', 'short-forward-branch-ialu' "
+      "were implied by both positive and negative directives");
+}
+
+TEST(RISCVTuneFeature, ProcConfigurableFeatures) {
+  SmallVector<std::string> Result;
+  EXPECT_FALSE(errorToBool(RISCV::parseTuneFeatureString(
+      "sifive-x280", "single-element-vec-fp64", Result)));
+  EXPECT_TRUE(is_contained(Result, "+single-element-vec-fp64"));
+
+  Result.clear();
+  EXPECT_EQ(
+      toString(RISCV::parseTuneFeatureString(
+          "sifive-x280", "single-element-vec-fp64,prefer-w-inst", Result)),
+      "Directive 'prefer-w-inst' is not allowed to be used with processor "
+      "'sifive-x280'");
+}
+
+TEST(RISCVTuneFeature, AllProcConfigurableFeatures) {
+  SmallVector<StringRef> Result;
+  RISCV::getCPUConfigurableTuneFeatures("sifive-x280", Result);
+  EXPECT_TRUE(is_contained(Result, "single-element-vec-fp64"));
+  EXPECT_TRUE(is_contained(Result, "no-single-element-vec-fp64"));
+  EXPECT_EQ(Result.size(), 2U);
+
+  Result.clear();
+  RISCV::getCPUConfigurableTuneFeatures("sifive-x390", Result);
+  EXPECT_TRUE(is_contained(Result, "single-element-vec-fp64"));
+  EXPECT_TRUE(is_contained(Result, "no-single-element-vec-fp64"));
+  EXPECT_EQ(Result.size(), 2U);
+
+  Result.clear();
+  RISCV::getCPUConfigurableTuneFeatures("rocket", Result);
+  EXPECT_TRUE(Result.empty());
+}
 } // namespace
diff --git a/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp b/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp
index f7959376adc4a..24f122b4cb178 100644
--- a/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/RISCVTargetDefEmitter.cpp
@@ -13,8 +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;
@@ -259,7 +262,118 @@ 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,
+                                  StringToOffsetTable &StrTable,
+                                  raw_ostream &OS) {
+  std::vector<const Record *> TuneFeatureRecords =
+      RK.getAllDerivedDefinitionsIfDefined("RISCVTuneFeature");
+
+  // {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;
+
+  for (const auto *R : TuneFeatureRecords) {
+    // Preemptively insert feature name into the string table because we know
+    // it will be used later.
+    StringRef FeatureName = R->getValueAsString("Name");
+    StrTable.GetOrAddStringOffset(FeatureName);
+
+    StringRef PosName = R->getValueAsString("PositiveDirectiveName");
+    StringRef NegName = R->getValueAsString("NegativeDirectiveName");
+    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");
+    }
+
+    TuneFeatureDirectives.emplace_back(PosIdx, NegIdx, R);
+  }
+
+  for (const auto *R : TuneFeatureRecords) {
+    std::vector<const Record *> Implies = R->getValueAsListOfDefs("Implies");
+    for (const auto *ImpliedRecord : Implies) {
+      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";
+
+  StrTable.EmitStringTableDef(OS, "TuneFeatureStrings");
+  OS << "\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 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_TUNE_FEATURES\n\n";
+}
+
+static void
+emitRISCVConfigurableTuneFeatures(const RecordKeeper &RK,
+                                  const StringToOffsetTable &StrTable,
+                                  raw_ostream &OS) {
+  std::vector<const Record *> AllProcModels =
+      RK.getAllDerivedDefinitionsIfDefined("ProcessorModel");
+
+  OS << "#ifdef GET_CONFIGURABLE_TUNE_FEATURES\n";
+  OS << "#undef GET_CONFIGURABLE_TUNE_FEATURES\n\n";
+
+  OS << "static constexpr RISCVConfigurableTuneFeatures "
+        "ConfigurableTuneFeatures[] = {\n";
+
+  for (const Record *Proc : AllProcModels) {
+    StringRef ProcName = Proc->getValueAsString("Name");
+    std::vector<const Record *> TuneFeatures =
+        Proc->getValueAsListOfDefs("ConfigurableTuneFeatures");
+    for (const Record *TF : TuneFeatures) {
+      unsigned PosDirectiveIdx = *StrTable.GetStringOffset(
+          TF->getValueAsString("PositiveDirectiveName"));
+      unsigned NegDirectiveIdx = *StrTable.GetStringOffset(
+          TF->getValueAsString("NegativeDirectiveName"));
+      OS.indent(4) << formatv("{{ {{ \"{0}\" }, {1} },\n", ProcName,
+                              PosDirectiveIdx);
+      OS.indent(4) << formatv("{{ {{ \"{0}\" }, {1} },\n", ProcName,
+                              NegDirectiveIdx);
+    }
+  }
+
+  OS << "};\n\n";
+  OS << "#endif // GET_CONFIGURABLE_TUNE_FEATURES\n";
 }
 
 static void emitRiscvTargetDef(const RecordKeeper &RK, raw_ostream &OS) {
@@ -267,6 +381,10 @@ static void emitRiscvTargetDef(const RecordKeeper &RK, raw_ostream &OS) {
   emitRISCVProfiles(RK, OS);
   emitRISCVProcs(RK, OS);
   emitRISCVExtensionBitmask(RK, OS);
+
+  StringToOffsetTable TuneFeatureStrTable;
+  emitRISCVTuneFeatures(RK, TuneFeatureStrTable, OS);
+  emitRISCVConfigurableTuneFeatures(RK, TuneFeatureStrTable, OS);
 }
 
 static TableGen::Emitter::Opt X("gen-riscv-target-def", emitRiscvTargetDef,

>From c3c19f8535faf46b8629e08db25d481ae444db96 Mon Sep 17 00:00:00 2001
From: Min-Yih Hsu <min.hsu at sifive.com>
Date: Thu, 8 Jan 2026 13:30:04 -0800
Subject: [PATCH 2/2] fixup! [RISCV] Introduce a new syntax for specifying
 processor tuning feature strings

---
 llvm/lib/Target/RISCV/RISCVFeatures.td | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/RISCV/RISCVFeatures.td b/llvm/lib/Target/RISCV/RISCVFeatures.td
index 33f6fb7eab8f1..366ce4116d663 100644
--- a/llvm/lib/Target/RISCV/RISCVFeatures.td
+++ b/llvm/lib/Target/RISCV/RISCVFeatures.td
@@ -1973,7 +1973,7 @@ def HasShortForwardBranchILoad : Predicate<"Subtarget->hasShortForwardBranchILoa
 def TuneNoSinkSplatOperands
     : RISCVTuneFeature<"no-sink-splat-operands", "no-sink-splat-operands",
                        "sink-splat-operands", "SinkSplatOperands", "false",
-                       "Disable sink splat operands to enable .vx, .vf, .wx, "
+                       "Disable sink splat operands to enable .vx, .vf,.wx, "
                        "and .wf instructions">;
 
 def TunePreferWInst



More information about the llvm-commits mailing list