[llvm] [Option] Add Condition for build-conditional library options (PR #230898)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sun Oct 11 10:10:27 PDT 2026


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/230898

>From 1d9af291a147321b5ff348770757a62ea13c08a5 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 10 Oct 2026 22:15:58 -0700
Subject: [PATCH 1/5] [Option] Add Condition for build-conditional library
 options

A field under `let Condition = "!defined(NDEBUG)"` keeps the member
variable with the specified default value, but the option parser code is
guided under `!defined(NDEBUG)`, so the command line rejects it as an
unknown argument in non-assertion builds.

Port -loop-fusion-verbose-debug and -stress-ivchain, the NDEBUG-only
Scalar options. LSR's release-build StressIVChain constant goes away.

Aided by Opus 5.5
---
 llvm/include/llvm/Option/LibraryOptions.td    |  3 ++
 llvm/lib/Option/LibraryOptions.cpp            |  3 ++
 llvm/lib/Transforms/Scalar/LoopFuse.cpp       | 42 ++++++++-----------
 .../Transforms/Scalar/LoopStrengthReduce.cpp  | 35 ++++++----------
 llvm/lib/Transforms/Scalar/ScalarOptions.td   |  7 ++++
 llvm/unittests/Option/LibraryOptionsTest.cpp  | 23 ++++++----
 llvm/unittests/Option/LibraryOpts.td          |  4 ++
 .../TableGen/Basic/OptionParserEmitter.cpp    |  9 +++-
 8 files changed, 68 insertions(+), 58 deletions(-)

diff --git a/llvm/include/llvm/Option/LibraryOptions.td b/llvm/include/llvm/Option/LibraryOptions.td
index 60dcb23166928..0b8b97479048f 100644
--- a/llvm/include/llvm/Option/LibraryOptions.td
+++ b/llvm/include/llvm/Option/LibraryOptions.td
@@ -23,10 +23,13 @@ class OptionsStruct<string namespace = "llvm", string prefix = ""> {
 }
 
 // The member an option sets, and the member's type and default value.
+// `let Condition = "!defined(NDEBUG)" in` makes the option unknown where the
+// preprocessor condition is false; the member then keeps its default.
 class Field<string name, string type, code default> {
   string FieldName = name;
   string FieldType = type;
   code FieldDefault = default;
+  code Condition = ?;
 }
 
 // The value a FlagOrEq row assigns when given without one.
diff --git a/llvm/lib/Option/LibraryOptions.cpp b/llvm/lib/Option/LibraryOptions.cpp
index cfcb82273eb6d..55a317dc57f49 100644
--- a/llvm/lib/Option/LibraryOptions.cpp
+++ b/llvm/lib/Option/LibraryOptions.cpp
@@ -24,6 +24,9 @@ void LibraryOptionsParser::forEachOption(
     unsigned Kind = T.getOptionKind(ID);
     if (Kind != Option::FlagOrEqClass && Kind != Option::SeparateOrEqClass)
       continue;
+    // Skip options a false Condition makes unknown.
+    if (!T.getOption(ID).hasVisibilityFlag(DefaultVis))
+      continue;
     StringRef V = T.getOptionMetaVar(ID);
     std::string MetaVar;
     if (Kind == Option::SeparateOrEqClass)
diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index 894f14581bd87..d686f1ada5977 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -58,7 +58,6 @@
 #include "llvm/Analysis/TargetTransformInfo.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/Verifier.h"
-#include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
@@ -100,13 +99,6 @@ STATISTIC(NumHoistedInsts, "Number of hoisted preheader instructions.");
 STATISTIC(NumSunkInsts, "Number of sunk preheader instructions.");
 STATISTIC(NumDA, "DA checks passed");
 
-#ifndef NDEBUG
-static cl::opt<bool>
-    VerboseFusionDebugging("loop-fusion-verbose-debug",
-                           cl::desc("Enable verbose debugging for Loop Fusion"),
-                           cl::Hidden, cl::init(false));
-#endif
-
 namespace {
 /// This class is used to represent a candidate for loop fusion. When it is
 /// constructed, it checks the conditions for loop fusion to ensure that it
@@ -544,6 +536,8 @@ struct LoopDepthTree {
 
 struct LoopFuser {
 private:
+  const ScalarOptions &Opts;
+
   // Sets of control flow equivalent fusion candidates for a given nest level.
   FusionCandidateCollection FusionCandidates;
 
@@ -560,19 +554,19 @@ struct LoopFuser {
   const TargetTransformInfo &TTI;
 
 public:
-  LoopFuser(LoopInfo &LI, DominatorTree &DT, DependenceInfo &DI,
-            ScalarEvolution &SE, PostDominatorTree &PDT,
+  LoopFuser(const ScalarOptions &Opts, LoopInfo &LI, DominatorTree &DT,
+            DependenceInfo &DI, ScalarEvolution &SE, PostDominatorTree &PDT,
             OptimizationRemarkEmitter &ORE, AssumptionCache &AC,
             const TargetTransformInfo &TTI)
-      : LDT(LI), DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy), LI(LI),
-        DT(DT), DI(DI), SE(SE), PDT(PDT), ORE(ORE), AC(AC), TTI(TTI) {}
+      : Opts(Opts), LDT(LI), DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy),
+        LI(LI), DT(DT), DI(DI), SE(SE), PDT(PDT), ORE(ORE), AC(AC), TTI(TTI) {}
 
   /// This is the main entry point for loop fusion. It will traverse the
   /// specified function and collect candidate loops to fuse, starting at the
   /// outermost nesting level and working inwards.
   bool fuseLoops(Function &F) {
 #ifndef NDEBUG
-    if (VerboseFusionDebugging) {
+    if (Opts.loop_fusion_verbose_debug) {
       LI.print(dbgs());
     }
 #endif
@@ -593,7 +587,7 @@ struct LoopFuser {
         if (LV.size() == 1)
           continue;
 #ifndef NDEBUG
-        if (VerboseFusionDebugging) {
+        if (Opts.loop_fusion_verbose_debug) {
           LLVM_DEBUG({
             dbgs() << "  Visit loop set (#" << LV.size() << "):\n";
             printLoopVector(LV);
@@ -652,7 +646,7 @@ struct LoopFuser {
           FoundAdjacent = true;
           NumFusionCandidates++;
 #ifndef NDEBUG
-          if (VerboseFusionDebugging)
+          if (Opts.loop_fusion_verbose_debug)
             LLVM_DEBUG(dbgs() << "Adding " << CurrCand
                               << " to existing candidate list\n");
 #endif
@@ -662,7 +656,7 @@ struct LoopFuser {
       if (!FoundAdjacent) {
         // No list was found. Create a new list and add to FusionCandidates
 #ifndef NDEBUG
-        if (VerboseFusionDebugging)
+        if (Opts.loop_fusion_verbose_debug)
           LLVM_DEBUG(dbgs() << "Adding " << CurrCand << " to new list\n");
 #endif
         FusionCandidateList NewCandList;
@@ -843,11 +837,9 @@ struct LoopFuser {
         // the first loop has a larger trip count. In this case it is possible
         // that the first loop is peeled to expose the fusion opportunity.
         // Peeling the second loop is not currently supported.
-        bool WillPeel =
-            FC0.AbleToPeel && TCDifference && *TCDifference > 0 &&
-            *TCDifference <=
-                static_cast<int64_t>(
-                    ScalarOptions::Global.loop_fusion_peel_max_count);
+        bool WillPeel = FC0.AbleToPeel && TCDifference && *TCDifference > 0 &&
+                        *TCDifference <= static_cast<int64_t>(
+                                             Opts.loop_fusion_peel_max_count);
 
         if (!WillPeel && (!TCDifference || *TCDifference != 0)) {
           LLVM_DEBUG(dbgs() << "Fusion candidates do not have identical trip "
@@ -1187,7 +1179,7 @@ struct LoopFuser {
                               const FusionCandidate &FC1, Instruction &I0,
                               Instruction &I1) {
 #ifndef NDEBUG
-    if (VerboseFusionDebugging) {
+    if (Opts.loop_fusion_verbose_debug) {
       LLVM_DEBUG(dbgs() << "Check dep: " << I0 << " vs " << I1 << "\n");
     }
 #endif
@@ -1203,7 +1195,7 @@ struct LoopFuser {
         return true;
     }
 #ifndef NDEBUG
-    if (VerboseFusionDebugging) {
+    if (Opts.loop_fusion_verbose_debug) {
       LLVM_DEBUG(dbgs() << "DA res: "; DepResult->dump(dbgs());
                  dbgs() << " [#l: " << DepResult->getLevels() << "][Ordered: "
                         << (DepResult->isOrdered() ? "true" : "false")
@@ -1366,7 +1358,7 @@ struct LoopFuser {
     NumHoistedInsts += HoistInsts.size();
     NumSunkInsts += SinkInsts.size();
 
-    LLVM_DEBUG(if (VerboseFusionDebugging) {
+    LLVM_DEBUG(if (Opts.loop_fusion_verbose_debug) {
       if (!HoistInsts.empty())
         dbgs() << "Hoisting: \n";
       for (Instruction *I : HoistInsts)
@@ -1934,7 +1926,7 @@ PreservedAnalyses LoopFusePass::run(Function &F, FunctionAnalysisManager &AM) {
   if (Changed)
     PDT.recalculate(F);
 
-  LoopFuser LF(LI, DT, DI, SE, PDT, ORE, AC, TTI);
+  LoopFuser LF(ScalarOptions::Global, LI, DT, DI, SE, PDT, ORE, AC, TTI);
   Changed |= LF.fuseLoops(F);
   if (!Changed)
     return PreservedAnalyses::all();
diff --git a/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp b/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp
index 3f595f95ef064..7252e64a66b18 100644
--- a/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp
@@ -104,7 +104,6 @@
 #include "llvm/InitializePasses.h"
 #include "llvm/Pass.h"
 #include "llvm/Support/Casting.h"
-#include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/ErrorHandling.h"
@@ -144,15 +143,6 @@ static const unsigned MaxIVUsers = 200;
 /// the salvaging is not too expensive for the compiler.
 static const unsigned MaxSCEVSalvageExpressionSize = 64;
 
-#ifndef NDEBUG
-// Stress test IV chain generation.
-static cl::opt<bool> StressIVChain(
-  "stress-ivchain", cl::Hidden, cl::init(false),
-  cl::desc("Stress test LSR IV chains"));
-#else
-static bool StressIVChain = false;
-#endif
-
 namespace {
 
 struct MemAccessTy {
@@ -2116,9 +2106,8 @@ struct IVChain {
   Instruction *tailUserInst() const { return Incs.back().UserInst; }
 
   // Returns true if IncExpr can be profitably added to this chain.
-  bool isProfitableIncrement(const SCEV *OperExpr,
-                             const SCEV *IncExpr,
-                             ScalarEvolution&);
+  bool isProfitableIncrement(const ScalarOptions &Opts, const SCEV *OperExpr,
+                             const SCEV *IncExpr, ScalarEvolution &);
 };
 
 /// Helper for CollectChains to track multiple IV increment uses.  Distinguish
@@ -3052,11 +3041,11 @@ static const SCEV *getExprBase(const SCEV *S) {
 /// increment will be an offset relative to the same base. We allow such offsets
 /// to potentially be used as chain increment as long as it's not obviously
 /// expensive to expand using real instructions.
-bool IVChain::isProfitableIncrement(const SCEV *OperExpr,
-                                    const SCEV *IncExpr,
+bool IVChain::isProfitableIncrement(const ScalarOptions &Opts,
+                                    const SCEV *OperExpr, const SCEV *IncExpr,
                                     ScalarEvolution &SE) {
   // Aggressively form chains when -stress-ivchain.
-  if (StressIVChain)
+  if (Opts.stress_ivchain)
     return true;
 
   // Do not replace a constant offset from IV head with a nonconstant IV
@@ -3081,11 +3070,11 @@ bool IVChain::isProfitableIncrement(const SCEV *OperExpr,
 /// increments can be computed in fewer registers when chained.
 ///
 /// TODO: Consider IVInc free if it's already used in another chains.
-static bool isProfitableChain(IVChain &Chain,
+static bool isProfitableChain(const ScalarOptions &Opts, IVChain &Chain,
                               SmallPtrSetImpl<Instruction *> &Users,
                               ScalarEvolution &SE,
                               const TargetTransformInfo &TTI) {
-  if (StressIVChain)
+  if (Opts.stress_ivchain)
     return true;
 
   if (!Chain.hasIncs())
@@ -3179,7 +3168,7 @@ void LSRInstance::ChainInstruction(Instruction *UserInst, Instruction *IVOper,
     // are expressions that operate on the same unscaled SCEVUnknown. This
     // "base" will be canceled by the subsequent getMinusSCEV call. Checking
     // first avoids creating extra SCEV expressions.
-    if (!StressIVChain && Chain.ExprBase != OperExprBase)
+    if (!Opts.stress_ivchain && Chain.ExprBase != OperExprBase)
       continue;
 
     Value *PrevIV = getWideOperand(Chain.Incs.back().IVOperand);
@@ -3196,7 +3185,7 @@ void LSRInstance::ChainInstruction(Instruction *UserInst, Instruction *IVOper,
     if (isa<SCEVCouldNotCompute>(IncExpr) || !SE.isLoopInvariant(IncExpr, L))
       continue;
 
-    if (Chain.isProfitableIncrement(OperExpr, IncExpr, SE)) {
+    if (Chain.isProfitableIncrement(Opts, OperExpr, IncExpr, SE)) {
       LastIncExpr = IncExpr;
       break;
     }
@@ -3206,7 +3195,7 @@ void LSRInstance::ChainInstruction(Instruction *UserInst, Instruction *IVOper,
   if (ChainIdx == NChains) {
     if (isa<PHINode>(UserInst))
       return;
-    if (NChains >= MaxChains && !StressIVChain) {
+    if (NChains >= MaxChains && !Opts.stress_ivchain) {
       LLVM_DEBUG(dbgs() << "IV Chain Limit\n");
       return;
     }
@@ -3352,7 +3341,7 @@ void LSRInstance::CollectChains() {
   unsigned ChainIdx = 0;
   for (unsigned UsersIdx = 0, NChains = IVChainVec.size();
        UsersIdx < NChains; ++UsersIdx) {
-    if (!isProfitableChain(IVChainVec[UsersIdx],
+    if (!isProfitableChain(Opts, IVChainVec[UsersIdx],
                            ChainUsersVec[UsersIdx].FarUsers, SE, TTI))
       continue;
     // Preserve the chain at UsesIdx.
@@ -6330,7 +6319,7 @@ LSRInstance::LSRInstance(const ScalarOptions &Opts, Loop *L, IVUsers &IU,
   // registers.
   // FIXME: add profitable chain optimization for other kinds major cost, for
   // example number of instructions.
-  if (TTI.isNumRegsMajorCostOfLSR() || StressIVChain)
+  if (TTI.isNumRegsMajorCostOfLSR() || Opts.stress_ivchain)
     CollectChains();
   CollectInterestingTypesAndFactors();
   CollectFixupsAndInitialFormulae();
diff --git a/llvm/lib/Transforms/Scalar/ScalarOptions.td b/llvm/lib/Transforms/Scalar/ScalarOptions.td
index 1d6275cac7afc..68db4f2c24911 100644
--- a/llvm/lib/Transforms/Scalar/ScalarOptions.td
+++ b/llvm/lib/Transforms/Scalar/ScalarOptions.td
@@ -259,6 +259,9 @@ defm : BoolField<"loop-flatten-widen-iv", "true",
 // LoopFuse.cpp
 defm : ValueField<"loop-fusion-peel-max-count", "uint32_t", "0",
   "Max number of iterations to be peeled from a loop, such that fusion can take place">;
+let Condition = "!defined(NDEBUG)" in
+defm : BoolField<"loop-fusion-verbose-debug", "false",
+  "Enable verbose debugging for Loop Fusion">;
 
 // LoopIdiomRecognize.cpp
 defm : EnumField<"loop-idiom-crc-strategy", "CRCStrategyKind", "CRCStrategyKind::Auto",
@@ -360,6 +363,10 @@ defm : OptionalEnumField<"lsr-preferred-addressing-mode", "TargetTransformInfo::
   "A flag that overrides the target's preferred addressing mode">;
 defm : ValueField<"lsr-setupcost-depth-limit", "unsigned", "7",
   "The limit on recursion depth for LSRs setup cost">;
+// Stress test IV chain generation.
+let Condition = "!defined(NDEBUG)" in
+defm : BoolField<"stress-ivchain", "false",
+  "Stress test LSR IV chains">;
 
 // LoopUnrollAndJamPass.cpp
 defm : OptionalBoolField<"allow-unroll-and-jam",
diff --git a/llvm/unittests/Option/LibraryOptionsTest.cpp b/llvm/unittests/Option/LibraryOptionsTest.cpp
index 0c94a172dfc88..dff1d179845b7 100644
--- a/llvm/unittests/Option/LibraryOptionsTest.cpp
+++ b/llvm/unittests/Option/LibraryOptionsTest.cpp
@@ -126,15 +126,16 @@ TEST(LibraryOptionsTest, Parser) {
   P.forEachOption([&](StringRef Spelling, StringRef MetaVar, StringRef Help) {
     Rows.push_back((Spelling + "|" + MetaVar + "|" + Help).str());
   });
-  EXPECT_THAT(
-      Rows,
-      testing::ElementsAre(
-          "lib-count|=<value>|An unsigned", "lib-enable||A bool",
-          "lib-limit|=<value>|An optional", "lib-list|=<value>|A list",
-          "lib-modes|=<a|b>|An enum list", "lib-mode|=<a|b>|An enum",
-          "lib-override||An optional bool", "lib-path|=<value>|A string",
-          "lib-print|[=<a|b>]|A flag or enum", "lib-ratio|=<value>|A double",
-          "lib-tristate|=<Default|Enable|Disable>|A tri-state"));
+  EXPECT_THAT(Rows,
+              testing::ElementsAre(
+                  "lib-count|=<value>|An unsigned", "lib-enable||A bool",
+                  "lib-in-build||Available", "lib-limit|=<value>|An optional",
+                  "lib-list|=<value>|A list", "lib-modes|=<a|b>|An enum list",
+                  "lib-mode|=<a|b>|An enum", "lib-override||An optional bool",
+                  "lib-path|=<value>|A string",
+                  "lib-print|[=<a|b>]|A flag or enum",
+                  "lib-ratio|=<value>|A double",
+                  "lib-tristate|=<Default|Enable|Disable>|A tri-state"));
 
   BumpPtrAllocator Alloc;
   auto Parse = [&](std::initializer_list<const char *> Argv) {
@@ -150,6 +151,10 @@ TEST(LibraryOptionsTest, Parser) {
             "1 option '-lib-count' requires an argument");
   EXPECT_EQ(Parse({"-lib-other"}), "1 unknown argument '-lib-other'");
   EXPECT_EQ(Parse({"-lib-counts=1"}), "1 unknown argument '-lib-counts=1'");
+  EXPECT_EQ(Parse({"-lib-in-build"}), "1 ");
+  EXPECT_TRUE(TestLibraryOptions::Global.in_build);
+  EXPECT_EQ(Parse({"-lib-not-in-build"}),
+            "1 unknown argument '-lib-not-in-build'");
   EXPECT_EQ(Parse({"-lib-enable", "0"}), "1 ");
   EXPECT_EQ(Parse({"-lib-print", "a"}), "1 ");
   EXPECT_EQ(Parse({"-lib-count", "x"}),
diff --git a/llvm/unittests/Option/LibraryOpts.td b/llvm/unittests/Option/LibraryOpts.td
index e39ca46a684e4..2537e3d3a64b7 100644
--- a/llvm/unittests/Option/LibraryOpts.td
+++ b/llvm/unittests/Option/LibraryOpts.td
@@ -11,6 +11,10 @@ include "llvm/Option/LibraryOptions.td"
 def TestLibraryOptions : OptionsStruct<"test", prefix = "lib-">;
 
 defm : BoolField<"lib-enable", "false", "A bool">;
+let Condition = "1" in
+defm : BoolField<"lib-in-build", "false", "Available">;
+let Condition = "0" in
+defm : BoolField<"lib-not-in-build", "false", "Unavailable">;
 defm : ValueField<"lib-count", "unsigned", "3", "An unsigned">;
 defm : OptionalValueField<"lib-limit", "unsigned", "An optional">;
 defm : ListField<"lib-list", "unsigned", "A list">;
diff --git a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
index 09baae4d80c6e..be7dce46b3e75 100644
--- a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
@@ -647,7 +647,14 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
     OS << ", ";
     writeStrTableOffset(OS, Table, getHelpText(R));
     OS << ", " << GetRefID(R, "Group") << ", " << GetRefID(R, "Alias");
-    OS << ", " << ExtraOffset.lookup(&R);
+    // Where a Condition is false, row 0's zero visibility makes the parser
+    // treat the option as unknown.
+    StringRef Condition = getOptionalString(R, "Condition");
+    if (Condition.empty())
+      OS << ", " << ExtraOffset.lookup(&R);
+    else
+      OS << ",\n#if " << Condition << "\n      " << ExtraOffset.lookup(&R)
+         << "\n#else\n      0\n#endif\n     ";
     std::vector<StringRef> RPrefixes = R.getValueAsListOfStrings("Prefixes");
     OS << ", " << Prefixes[PrefixKeyT(RPrefixes.begin(), RPrefixes.end())];
     OS << ", llvm::opt::Option::"

>From 08ca432593e4bfddf55426d6313f2d5e23ecaf2e Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 10 Oct 2026 22:43:18 -0700
Subject: [PATCH 2/5] enhance unittest

---
 llvm/unittests/Option/LibraryOpts.td | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/unittests/Option/LibraryOpts.td b/llvm/unittests/Option/LibraryOpts.td
index 2537e3d3a64b7..72fc1e7edf179 100644
--- a/llvm/unittests/Option/LibraryOpts.td
+++ b/llvm/unittests/Option/LibraryOpts.td
@@ -11,9 +11,9 @@ include "llvm/Option/LibraryOptions.td"
 def TestLibraryOptions : OptionsStruct<"test", prefix = "lib-">;
 
 defm : BoolField<"lib-enable", "false", "A bool">;
-let Condition = "1" in
+let Condition = "__cplusplus" in
 defm : BoolField<"lib-in-build", "false", "Available">;
-let Condition = "0" in
+let Condition = "__cplusplus < 201103L" in
 defm : BoolField<"lib-not-in-build", "false", "Unavailable">;
 defm : ValueField<"lib-count", "unsigned", "3", "An unsigned">;
 defm : OptionalValueField<"lib-limit", "unsigned", "An optional">;

>From 6daa87ce61f3ed4e2fa4d6e21a52a50946e04e95 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 11 Oct 2026 09:40:40 -0700
Subject: [PATCH 3/5] remove member when the condition is false

---
 llvm/include/llvm/Option/LibraryOptions.td    |  4 +-
 .../Transforms/Scalar/LoopStrengthReduce.cpp  | 39 +++++++++++--------
 .../TableGen/Basic/OptionParserEmitter.cpp    | 39 ++++++++++++-------
 3 files changed, 49 insertions(+), 33 deletions(-)

diff --git a/llvm/include/llvm/Option/LibraryOptions.td b/llvm/include/llvm/Option/LibraryOptions.td
index 0b8b97479048f..9ebffa8bb7d15 100644
--- a/llvm/include/llvm/Option/LibraryOptions.td
+++ b/llvm/include/llvm/Option/LibraryOptions.td
@@ -23,8 +23,8 @@ class OptionsStruct<string namespace = "llvm", string prefix = ""> {
 }
 
 // The member an option sets, and the member's type and default value.
-// `let Condition = "!defined(NDEBUG)" in` makes the option unknown where the
-// preprocessor condition is false; the member then keeps its default.
+// `let Condition = "!defined(NDEBUG)" in` makes the option unknown, and removes
+// the member, where the preprocessor condition is false.
 class Field<string name, string type, code default> {
   string FieldName = name;
   string FieldType = type;
diff --git a/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp b/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp
index 7252e64a66b18..6073a177babaf 100644
--- a/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp
@@ -143,6 +143,14 @@ static const unsigned MaxIVUsers = 200;
 /// the salvaging is not too expensive for the compiler.
 static const unsigned MaxSCEVSalvageExpressionSize = 64;
 
+static bool stressIVChain(const ScalarOptions &Opts) {
+#ifndef NDEBUG
+  return Opts.stress_ivchain;
+#else
+  return false;
+#endif
+}
+
 namespace {
 
 struct MemAccessTy {
@@ -2106,8 +2114,9 @@ struct IVChain {
   Instruction *tailUserInst() const { return Incs.back().UserInst; }
 
   // Returns true if IncExpr can be profitably added to this chain.
-  bool isProfitableIncrement(const ScalarOptions &Opts, const SCEV *OperExpr,
-                             const SCEV *IncExpr, ScalarEvolution &);
+  bool isProfitableIncrement(const SCEV *OperExpr,
+                             const SCEV *IncExpr,
+                             ScalarEvolution&);
 };
 
 /// Helper for CollectChains to track multiple IV increment uses.  Distinguish
@@ -3041,13 +3050,9 @@ static const SCEV *getExprBase(const SCEV *S) {
 /// increment will be an offset relative to the same base. We allow such offsets
 /// to potentially be used as chain increment as long as it's not obviously
 /// expensive to expand using real instructions.
-bool IVChain::isProfitableIncrement(const ScalarOptions &Opts,
-                                    const SCEV *OperExpr, const SCEV *IncExpr,
+bool IVChain::isProfitableIncrement(const SCEV *OperExpr,
+                                    const SCEV *IncExpr,
                                     ScalarEvolution &SE) {
-  // Aggressively form chains when -stress-ivchain.
-  if (Opts.stress_ivchain)
-    return true;
-
   // Do not replace a constant offset from IV head with a nonconstant IV
   // increment.
   if (!isa<SCEVConstant>(IncExpr)) {
@@ -3070,13 +3075,10 @@ bool IVChain::isProfitableIncrement(const ScalarOptions &Opts,
 /// increments can be computed in fewer registers when chained.
 ///
 /// TODO: Consider IVInc free if it's already used in another chains.
-static bool isProfitableChain(const ScalarOptions &Opts, IVChain &Chain,
+static bool isProfitableChain(IVChain &Chain,
                               SmallPtrSetImpl<Instruction *> &Users,
                               ScalarEvolution &SE,
                               const TargetTransformInfo &TTI) {
-  if (Opts.stress_ivchain)
-    return true;
-
   if (!Chain.hasIncs())
     return false;
 
@@ -3168,7 +3170,7 @@ void LSRInstance::ChainInstruction(Instruction *UserInst, Instruction *IVOper,
     // are expressions that operate on the same unscaled SCEVUnknown. This
     // "base" will be canceled by the subsequent getMinusSCEV call. Checking
     // first avoids creating extra SCEV expressions.
-    if (!Opts.stress_ivchain && Chain.ExprBase != OperExprBase)
+    if (!stressIVChain(Opts) && Chain.ExprBase != OperExprBase)
       continue;
 
     Value *PrevIV = getWideOperand(Chain.Incs.back().IVOperand);
@@ -3185,7 +3187,9 @@ void LSRInstance::ChainInstruction(Instruction *UserInst, Instruction *IVOper,
     if (isa<SCEVCouldNotCompute>(IncExpr) || !SE.isLoopInvariant(IncExpr, L))
       continue;
 
-    if (Chain.isProfitableIncrement(Opts, OperExpr, IncExpr, SE)) {
+    // Aggressively form chains when -stress-ivchain.
+    if (stressIVChain(Opts) ||
+        Chain.isProfitableIncrement(OperExpr, IncExpr, SE)) {
       LastIncExpr = IncExpr;
       break;
     }
@@ -3195,7 +3199,7 @@ void LSRInstance::ChainInstruction(Instruction *UserInst, Instruction *IVOper,
   if (ChainIdx == NChains) {
     if (isa<PHINode>(UserInst))
       return;
-    if (NChains >= MaxChains && !Opts.stress_ivchain) {
+    if (NChains >= MaxChains && !stressIVChain(Opts)) {
       LLVM_DEBUG(dbgs() << "IV Chain Limit\n");
       return;
     }
@@ -3341,7 +3345,8 @@ void LSRInstance::CollectChains() {
   unsigned ChainIdx = 0;
   for (unsigned UsersIdx = 0, NChains = IVChainVec.size();
        UsersIdx < NChains; ++UsersIdx) {
-    if (!isProfitableChain(Opts, IVChainVec[UsersIdx],
+    if (!stressIVChain(Opts) &&
+        !isProfitableChain(IVChainVec[UsersIdx],
                            ChainUsersVec[UsersIdx].FarUsers, SE, TTI))
       continue;
     // Preserve the chain at UsesIdx.
@@ -6319,7 +6324,7 @@ LSRInstance::LSRInstance(const ScalarOptions &Opts, Loop *L, IVUsers &IU,
   // registers.
   // FIXME: add profitable chain optimization for other kinds major cost, for
   // example number of instructions.
-  if (TTI.isNumRegsMajorCostOfLSR() || Opts.stress_ivchain)
+  if (TTI.isNumRegsMajorCostOfLSR() || stressIVChain(Opts))
     CollectChains();
   CollectInterestingTypesAndFactors();
   CollectFixupsAndInitialFormulae();
diff --git a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
index be7dce46b3e75..23b6d4eacf581 100644
--- a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
@@ -267,7 +267,7 @@ static void emitOptionsStruct(const Record &Struct,
                               ArrayRef<const Record *> Opts, raw_ostream &OS) {
   struct Member {
     std::string Name;
-    StringRef Type, Default, Spelling;
+    StringRef Type, Default, Spelling, Condition;
   };
   std::vector<const Record *> Fields;
   for (const Record *R : Opts) {
@@ -298,7 +298,8 @@ static void emitOptionsStruct(const Record &Struct,
   StringMap<StringRef> MemberSpelling;
   for (const Record *R : ByID) {
     Member M{getMemberName(*R, Prefix), R->getValueAsString("FieldType"),
-             R->getValueAsString("FieldDefault"), R->getValueAsString("Name")};
+             R->getValueAsString("FieldDefault"), R->getValueAsString("Name"),
+             getOptionalString(*R, "Condition")};
     auto [It, Inserted] = MemberSpelling.try_emplace(M.Name, M.Spelling);
     if (!Inserted)
       PrintFatalError(R->getLoc(),
@@ -319,8 +320,13 @@ static void emitOptionsStruct(const Record &Struct,
   OS << "struct " << Name << " {\n";
   if (Namespace != "llvm")
     OS << "  using StringRef = llvm::StringRef;\n";
-  for (const Member &M : Members)
+  for (const Member &M : Members) {
+    if (!M.Condition.empty())
+      OS << "#if " << M.Condition << '\n';
     OS << "  " << M.Type << " " << M.Name << "{" << M.Default << "};\n";
+    if (!M.Condition.empty())
+      OS << "#endif\n";
+  }
   OS << "\n  /// The instance cl::ParseCommandLineOptions sets.\n";
   OS << "  static " << Name << " Global;\n\n";
   OS << "  static const llvm::opt::OptTable &optTable();\n";
@@ -347,6 +353,9 @@ static void emitOptionsStruct(const Record &Struct,
      << "::apply(const llvm::opt::Arg &A, llvm::BumpPtrAllocator &Alloc) {\n";
   OS << "  switch (A.getOption().getID()) {\n";
   for (const Record *R : Fields) {
+    StringRef Condition = getOptionalString(*R, "Condition");
+    if (!Condition.empty())
+      OS << "#if " << Condition << '\n';
     OS << "  case OPT_" << getStructOptionID(*R) << ":\n";
     std::string Member = getMemberName(*R, Prefix);
     if (R->getValue("BareValue"))
@@ -368,15 +377,17 @@ static void emitOptionsStruct(const Record &Struct,
            << "\") {\n        X = " << Enumerator
            << ";\n        return true;\n      }\n";
       OS << "      return false;\n    });\n";
-      continue;
+    } else {
+      // A FlagOrEq without a value means =true.
+      StringRef Value =
+          R->getValueAsDef("Kind")->getValueAsString("Name") == "FlagOrEq"
+              ? "A.getNumValues() ? A.getValue() : \"true\""
+              : "A.getValue()";
+      OS << "    return llvm::opt::parseArgValue(" << Value << ", " << Member
+         << ", Alloc);\n";
     }
-    // A FlagOrEq without a value means =true.
-    StringRef Value =
-        R->getValueAsDef("Kind")->getValueAsString("Name") == "FlagOrEq"
-            ? "A.getNumValues() ? A.getValue() : \"true\""
-            : "A.getValue()";
-    OS << "    return llvm::opt::parseArgValue(" << Value << ", " << Member
-       << ", Alloc);\n";
+    if (!Condition.empty())
+      OS << "#endif\n";
   }
   OS << "  }\n  llvm_unreachable(\"option without a member\");\n}\n";
   OS << "#endif // OPTIONS_STRUCT_DEFS\n";
@@ -649,12 +660,12 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
     OS << ", " << GetRefID(R, "Group") << ", " << GetRefID(R, "Alias");
     // Where a Condition is false, row 0's zero visibility makes the parser
     // treat the option as unknown.
+    unsigned Extra = ExtraOffset.lookup(&R);
     StringRef Condition = getOptionalString(R, "Condition");
     if (Condition.empty())
-      OS << ", " << ExtraOffset.lookup(&R);
+      OS << ", " << Extra;
     else
-      OS << ",\n#if " << Condition << "\n      " << ExtraOffset.lookup(&R)
-         << "\n#else\n      0\n#endif\n     ";
+      OS << ",\n#if " << Condition << '\n' << Extra << "\n#else\n0\n#endif\n";
     std::vector<StringRef> RPrefixes = R.getValueAsListOfStrings("Prefixes");
     OS << ", " << Prefixes[PrefixKeyT(RPrefixes.begin(), RPrefixes.end())];
     OS << ", llvm::opt::Option::"

>From f604569068137daa2bba3c41c5497fd550b75d3e Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 11 Oct 2026 10:01:10 -0700
Subject: [PATCH 4/5] remove member when the condition is false

---
 llvm/lib/Option/LibraryOptions.cpp            |   3 -
 .../TableGen/Basic/OptionParserEmitter.cpp    | 104 +++++++++++-------
 2 files changed, 65 insertions(+), 42 deletions(-)

diff --git a/llvm/lib/Option/LibraryOptions.cpp b/llvm/lib/Option/LibraryOptions.cpp
index 55a317dc57f49..cfcb82273eb6d 100644
--- a/llvm/lib/Option/LibraryOptions.cpp
+++ b/llvm/lib/Option/LibraryOptions.cpp
@@ -24,9 +24,6 @@ void LibraryOptionsParser::forEachOption(
     unsigned Kind = T.getOptionKind(ID);
     if (Kind != Option::FlagOrEqClass && Kind != Option::SeparateOrEqClass)
       continue;
-    // Skip options a false Condition makes unknown.
-    if (!T.getOption(ID).hasVisibilityFlag(DefaultVis))
-      continue;
     StringRef V = T.getOptionMetaVar(ID);
     std::string MetaVar;
     if (Kind == Option::SeparateOrEqClass)
diff --git a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
index 23b6d4eacf581..5050199799ca2 100644
--- a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
@@ -259,6 +259,39 @@ static std::string getStructOptionID(const Record &R) {
   return getSpellingIdentifier(R);
 }
 
+namespace {
+// Wraps what is emitted during its lifetime in `#if Condition` where R has one.
+class ConditionScope {
+  raw_ostream &OS;
+  StringRef Condition;
+
+public:
+  ConditionScope(raw_ostream &OS, const Record &R)
+      : OS(OS), Condition(getOptionalString(R, "Condition")) {
+    if (!Condition.empty())
+      OS << "#if " << Condition << '\n';
+  }
+  ~ConditionScope() {
+    if (!Condition.empty())
+      OS << "#endif\n";
+  }
+};
+} // namespace
+
+// Emits the OPT_ IDs of an OptionsStruct, which its table rows name.
+static void emitStructOptionIDs(ArrayRef<const Record *> Groups,
+                                ArrayRef<const Record *> Opts,
+                                raw_ostream &OS) {
+  OS << "namespace {\nenum ID : unsigned {\n  OPT_INVALID = 0,\n";
+  for (const Record *R : Groups)
+    OS << "  OPT_" << getOptionName(*R) << ",\n";
+  for (const Record *R : Opts) {
+    ConditionScope CS(OS, *R);
+    OS << "  OPT_" << getStructOptionID(*R) << ",\n";
+  }
+  OS << "};\n} // namespace\n\n";
+}
+
 // Emits the struct an OptionsStruct def declares: its declaration under
 // OPTIONS_STRUCT_DECL, and under OPTIONS_STRUCT_DEFS the global instance, the
 // option table, and apply(), which sets the member an argument names.
@@ -267,7 +300,7 @@ static void emitOptionsStruct(const Record &Struct,
                               ArrayRef<const Record *> Opts, raw_ostream &OS) {
   struct Member {
     std::string Name;
-    StringRef Type, Default, Spelling, Condition;
+    StringRef Type, Default, Spelling;
   };
   std::vector<const Record *> Fields;
   for (const Record *R : Opts) {
@@ -298,8 +331,7 @@ static void emitOptionsStruct(const Record &Struct,
   StringMap<StringRef> MemberSpelling;
   for (const Record *R : ByID) {
     Member M{getMemberName(*R, Prefix), R->getValueAsString("FieldType"),
-             R->getValueAsString("FieldDefault"), R->getValueAsString("Name"),
-             getOptionalString(*R, "Condition")};
+             R->getValueAsString("FieldDefault"), R->getValueAsString("Name")};
     auto [It, Inserted] = MemberSpelling.try_emplace(M.Name, M.Spelling);
     if (!Inserted)
       PrintFatalError(R->getLoc(),
@@ -320,12 +352,9 @@ static void emitOptionsStruct(const Record &Struct,
   OS << "struct " << Name << " {\n";
   if (Namespace != "llvm")
     OS << "  using StringRef = llvm::StringRef;\n";
-  for (const Member &M : Members) {
-    if (!M.Condition.empty())
-      OS << "#if " << M.Condition << '\n';
+  for (auto [R, M] : llvm::zip_equal(ByID, Members)) {
+    ConditionScope CS(OS, *R);
     OS << "  " << M.Type << " " << M.Name << "{" << M.Default << "};\n";
-    if (!M.Condition.empty())
-      OS << "#endif\n";
   }
   OS << "\n  /// The instance cl::ParseCommandLineOptions sets.\n";
   OS << "  static " << Name << " Global;\n\n";
@@ -340,12 +369,6 @@ static void emitOptionsStruct(const Record &Struct,
   std::string Qualified = (Namespace + "::" + Name).str();
   OS << "\n#ifdef OPTIONS_STRUCT_DEFS\n#undef OPTIONS_STRUCT_DEFS\n";
   OS << Qualified << " " << Qualified << "::Global;\n\n";
-  OS << "namespace {\nenum ID : unsigned {\n  OPT_INVALID = 0,\n";
-  for (const Record *R : Groups)
-    OS << "  OPT_" << getOptionName(*R) << ",\n";
-  for (const Record *R : Opts)
-    OS << "  OPT_" << getStructOptionID(*R) << ",\n";
-  OS << "};\n} // namespace\n\n";
   OS << "const llvm::opt::OptTable &" << Qualified << "::optTable() {\n";
   OS << "  static const llvm::opt::LibraryOptTable T(optionTables());\n";
   OS << "  return T;\n}\n\n";
@@ -353,9 +376,7 @@ static void emitOptionsStruct(const Record &Struct,
      << "::apply(const llvm::opt::Arg &A, llvm::BumpPtrAllocator &Alloc) {\n";
   OS << "  switch (A.getOption().getID()) {\n";
   for (const Record *R : Fields) {
-    StringRef Condition = getOptionalString(*R, "Condition");
-    if (!Condition.empty())
-      OS << "#if " << Condition << '\n';
+    ConditionScope CS(OS, *R);
     OS << "  case OPT_" << getStructOptionID(*R) << ":\n";
     std::string Member = getMemberName(*R, Prefix);
     if (R->getValue("BareValue"))
@@ -377,17 +398,15 @@ static void emitOptionsStruct(const Record &Struct,
            << "\") {\n        X = " << Enumerator
            << ";\n        return true;\n      }\n";
       OS << "      return false;\n    });\n";
-    } else {
-      // A FlagOrEq without a value means =true.
-      StringRef Value =
-          R->getValueAsDef("Kind")->getValueAsString("Name") == "FlagOrEq"
-              ? "A.getNumValues() ? A.getValue() : \"true\""
-              : "A.getValue()";
-      OS << "    return llvm::opt::parseArgValue(" << Value << ", " << Member
-         << ", Alloc);\n";
+      continue;
     }
-    if (!Condition.empty())
-      OS << "#endif\n";
+    // A FlagOrEq without a value means =true.
+    StringRef Value =
+        R->getValueAsDef("Kind")->getValueAsString("Name") == "FlagOrEq"
+            ? "A.getNumValues() ? A.getValue() : \"true\""
+            : "A.getValue()";
+    OS << "    return llvm::opt::parseArgValue(" << Value << ", " << Member
+       << ", Alloc);\n";
   }
   OS << "  }\n  llvm_unreachable(\"option without a member\");\n}\n";
   OS << "#endif // OPTIONS_STRUCT_DEFS\n";
@@ -488,10 +507,16 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
     OptionID.try_emplace(&R, OptionID.size() + 1);
   for (const Record &R : llvm::make_pointee_range(Opts))
     OptionID.try_emplace(&R, OptionID.size() + 1);
-  auto GetRefID = [&](const Record &R, StringRef Field) {
-    if (const DefInit *DI = dyn_cast<DefInit>(R.getValueInit(Field)))
-      return OptionID.lookup(DI->getDef());
-    return 0u;
+  // An OptionsStruct's rows name the IDs, since a false Condition omits a row
+  // and shifts the IDs after it.
+  auto GetRefID = [&](const Record &R, StringRef Field) -> std::string {
+    const auto *DI = dyn_cast<DefInit>(R.getValueInit(Field));
+    if (!DI)
+      return "0";
+    const Record *Ref = DI->getDef();
+    if (Structs.empty())
+      return utostr(OptionID.lookup(Ref));
+    return "OPT_" + getStructOptionID(*Ref);
   };
 
   OS << "/////////\n";
@@ -500,6 +525,8 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
   OS << (Structs.empty()
              ? "#ifdef OPTTABLE_CODE\n"
              : "#if defined(OPTTABLE_CODE) || defined(OPTIONS_STRUCT_DEFS)\n");
+  if (!Structs.empty())
+    emitStructOptionIDs(Groups, Opts, OS);
   // A function rather than an object: the object needs dynamic relocations.
   OS << "static llvm::opt::OptTable::Tables optionTables() {\n";
   // An OptionsStruct's .cpp has no using-directive for llvm::opt.
@@ -652,20 +679,18 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
        << ", 0, 0, 0, llvm::opt::Option::GroupClass},\n";
   }
   for (const Record &R : llvm::make_pointee_range(Opts)) {
+    ConditionScope CS(OS, R);
     OS << "    {";
     writeStrTableOffset(OS, Table, getOptionPrefixedName(R),
                         /*EmitComment=*/true);
     OS << ", ";
     writeStrTableOffset(OS, Table, getHelpText(R));
+    if (const auto *DI = dyn_cast<DefInit>(R.getValueInit("Alias"));
+        DI && !getOptionalString(*DI->getDef(), "Condition").empty())
+      PrintFatalError(R.getLoc(), "an alias cannot refer to an option with a "
+                                  "Condition");
     OS << ", " << GetRefID(R, "Group") << ", " << GetRefID(R, "Alias");
-    // Where a Condition is false, row 0's zero visibility makes the parser
-    // treat the option as unknown.
-    unsigned Extra = ExtraOffset.lookup(&R);
-    StringRef Condition = getOptionalString(R, "Condition");
-    if (Condition.empty())
-      OS << ", " << Extra;
-    else
-      OS << ",\n#if " << Condition << '\n' << Extra << "\n#else\n0\n#endif\n";
+    OS << ", " << ExtraOffset.lookup(&R);
     std::vector<StringRef> RPrefixes = R.getValueAsListOfStrings("Prefixes");
     OS << ", " << Prefixes[PrefixKeyT(RPrefixes.begin(), RPrefixes.end())];
     OS << ", llvm::opt::Option::"
@@ -832,6 +857,7 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
 
   std::vector<const Record *> OptsWithMarshalling;
   for (const Record &R : llvm::make_pointee_range(Opts)) {
+    ConditionScope CS(OS, R);
     // Start a single option entry.
     OS << "OPTION(";
     WriteOptRecordFields(OS, R);

>From e679714a391a0ec853283463296850672ca02ebe Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 11 Oct 2026 10:10:14 -0700
Subject: [PATCH 5/5] enable `let Condition = AssertsOnly in`

---
 llvm/include/llvm/Option/LibraryOptions.td        | 13 ++++++++++---
 llvm/lib/Transforms/Scalar/ScalarOptions.td       |  4 ++--
 llvm/unittests/Option/LibraryOpts.td              |  6 ++++--
 llvm/utils/TableGen/Basic/OptionParserEmitter.cpp | 11 +++++++++--
 4 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/llvm/include/llvm/Option/LibraryOptions.td b/llvm/include/llvm/Option/LibraryOptions.td
index 9ebffa8bb7d15..8aca537f309b8 100644
--- a/llvm/include/llvm/Option/LibraryOptions.td
+++ b/llvm/include/llvm/Option/LibraryOptions.td
@@ -22,14 +22,21 @@ class OptionsStruct<string namespace = "llvm", string prefix = ""> {
   string MemberPrefix = prefix;
 }
 
+// The builds an option exists in, as a preprocessor expression.
+class BuildCondition<code expr> {
+  code Expr = expr;
+}
+def AssertsOnly : BuildCondition<"!defined(NDEBUG)">;
+def DumpOnly : BuildCondition<"!defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)">;
+
 // The member an option sets, and the member's type and default value.
-// `let Condition = "!defined(NDEBUG)" in` makes the option unknown, and removes
-// the member, where the preprocessor condition is false.
+// `let Condition = AssertsOnly in` makes the option unknown, and removes the
+// member, in other builds.
 class Field<string name, string type, code default> {
   string FieldName = name;
   string FieldType = type;
   code FieldDefault = default;
-  code Condition = ?;
+  BuildCondition Condition = ?;
 }
 
 // The value a FlagOrEq row assigns when given without one.
diff --git a/llvm/lib/Transforms/Scalar/ScalarOptions.td b/llvm/lib/Transforms/Scalar/ScalarOptions.td
index 68db4f2c24911..4aa027c1a512f 100644
--- a/llvm/lib/Transforms/Scalar/ScalarOptions.td
+++ b/llvm/lib/Transforms/Scalar/ScalarOptions.td
@@ -259,7 +259,7 @@ defm : BoolField<"loop-flatten-widen-iv", "true",
 // LoopFuse.cpp
 defm : ValueField<"loop-fusion-peel-max-count", "uint32_t", "0",
   "Max number of iterations to be peeled from a loop, such that fusion can take place">;
-let Condition = "!defined(NDEBUG)" in
+let Condition = AssertsOnly in
 defm : BoolField<"loop-fusion-verbose-debug", "false",
   "Enable verbose debugging for Loop Fusion">;
 
@@ -364,7 +364,7 @@ defm : OptionalEnumField<"lsr-preferred-addressing-mode", "TargetTransformInfo::
 defm : ValueField<"lsr-setupcost-depth-limit", "unsigned", "7",
   "The limit on recursion depth for LSRs setup cost">;
 // Stress test IV chain generation.
-let Condition = "!defined(NDEBUG)" in
+let Condition = AssertsOnly in
 defm : BoolField<"stress-ivchain", "false",
   "Stress test LSR IV chains">;
 
diff --git a/llvm/unittests/Option/LibraryOpts.td b/llvm/unittests/Option/LibraryOpts.td
index 72fc1e7edf179..6e4d48bc8d37a 100644
--- a/llvm/unittests/Option/LibraryOpts.td
+++ b/llvm/unittests/Option/LibraryOpts.td
@@ -11,9 +11,11 @@ include "llvm/Option/LibraryOptions.td"
 def TestLibraryOptions : OptionsStruct<"test", prefix = "lib-">;
 
 defm : BoolField<"lib-enable", "false", "A bool">;
-let Condition = "__cplusplus" in
+def InBuild : BuildCondition<"__cplusplus">;
+def NotInBuild : BuildCondition<"__cplusplus < 201103L">;
+let Condition = InBuild in
 defm : BoolField<"lib-in-build", "false", "Available">;
-let Condition = "__cplusplus < 201103L" in
+let Condition = NotInBuild in
 defm : BoolField<"lib-not-in-build", "false", "Unavailable">;
 defm : ValueField<"lib-count", "unsigned", "3", "An unsigned">;
 defm : OptionalValueField<"lib-limit", "unsigned", "An optional">;
diff --git a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
index 5050199799ca2..c4b23c046e718 100644
--- a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
@@ -259,6 +259,13 @@ static std::string getStructOptionID(const Record &R) {
   return getSpellingIdentifier(R);
 }
 
+// The preprocessor expression of R's Condition, or "" if it has none.
+static StringRef getCondition(const Record &R) {
+  const RecordVal *V = R.getValue("Condition");
+  const auto *DI = V ? dyn_cast<DefInit>(V->getValue()) : nullptr;
+  return DI ? DI->getDef()->getValueAsString("Expr") : "";
+}
+
 namespace {
 // Wraps what is emitted during its lifetime in `#if Condition` where R has one.
 class ConditionScope {
@@ -267,7 +274,7 @@ class ConditionScope {
 
 public:
   ConditionScope(raw_ostream &OS, const Record &R)
-      : OS(OS), Condition(getOptionalString(R, "Condition")) {
+      : OS(OS), Condition(getCondition(R)) {
     if (!Condition.empty())
       OS << "#if " << Condition << '\n';
   }
@@ -686,7 +693,7 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
     OS << ", ";
     writeStrTableOffset(OS, Table, getHelpText(R));
     if (const auto *DI = dyn_cast<DefInit>(R.getValueInit("Alias"));
-        DI && !getOptionalString(*DI->getDef(), "Condition").empty())
+        DI && !getCondition(*DI->getDef()).empty())
       PrintFatalError(R.getLoc(), "an alias cannot refer to an option with a "
                                   "Condition");
     OS << ", " << GetRefID(R, "Group") << ", " << GetRefID(R, "Alias");



More information about the llvm-commits mailing list