[clang-tools-extra] [llvm] [Option] Add FlagOrEq and SeparateOrEq option kinds (PR #228319)

Fangrui Song via cfe-commits cfe-commits at lists.llvm.org
Sat Oct 3 23:04:48 PDT 2026


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

>From de92cab8c3c17019af817c7a0de9b60a0af15617 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Tue, 29 Sep 2026 23:05:34 -0700
Subject: [PATCH] [Option] Add FlagOrEq and SeparateOrEq option kinds

FlagOrEq accepts -x and -x=v; SeparateOrEq accepts -x=v and -x v. The
name is stored without '=', and RenderEqStyle renders -x=v. BoolField
and ValueField use them, so a library option takes one table row instead
of a Joined twin plus a Flag or a Separate alias.

CGDataOptions.cpp.o shrinks from 4,977 to 4,370 bytes and
InstCombineCLOptions.cpp.o from 4,180 to 3,553.

Aided by Opus 5.5
---
 clang-tools-extra/clangd/CompileCommands.cpp |  3 ++
 llvm/include/llvm/Option/LibraryOptions.td   | 12 ++---
 llvm/include/llvm/Option/OptParser.td        | 10 ++++
 llvm/include/llvm/Option/Option.h            |  8 ++-
 llvm/lib/Option/Arg.cpp                      | 16 ++++--
 llvm/lib/Option/LibraryOptions.cpp           | 11 +++--
 llvm/lib/Option/OptTable.cpp                 | 13 +++++
 llvm/lib/Option/Option.cpp                   | 19 +++++++
 llvm/unittests/Option/LibraryOptionsTest.cpp | 14 ++++--
 llvm/utils/TableGen/OptionParserEmitter.cpp  | 52 ++++++++------------
 10 files changed, 103 insertions(+), 55 deletions(-)

diff --git a/clang-tools-extra/clangd/CompileCommands.cpp b/clang-tools-extra/clangd/CompileCommands.cpp
index 2ba9446aa57dc..eb9e1df4b1c4a 100644
--- a/clang-tools-extra/clangd/CompileCommands.cpp
+++ b/clang-tools-extra/clangd/CompileCommands.cpp
@@ -416,7 +416,10 @@ std::pair<unsigned, unsigned> getArgCount(const llvm::opt::Option &Opt) {
   case Option::MultiArgClass:
     return {1 + Opt.getNumArgs(), 0};
   case Option::JoinedOrSeparateClass:
+  case Option::SeparateOrEqClass:
     return {2, 1};
+  case Option::FlagOrEqClass:
+    return {1, 1};
   case Option::RemainingArgsClass:
     return {Rest, 0};
   case Option::RemainingArgsJoinedClass:
diff --git a/llvm/include/llvm/Option/LibraryOptions.td b/llvm/include/llvm/Option/LibraryOptions.td
index 52af2a49c13d1..af94b1ebdd7c4 100644
--- a/llvm/include/llvm/Option/LibraryOptions.td
+++ b/llvm/include/llvm/Option/LibraryOptions.td
@@ -22,25 +22,21 @@ class OptionsStruct<string namespace = "llvm", string prefix = ""> {
   string MemberPrefix = prefix;
 }
 
-// The member a row sets, the member's type and default value, and the value a
-// Flag assigns. Other kinds parse their argument.
+// The member an option sets, and the member's type and default value.
 class Field<string name, string type, code default> {
   string FieldName = name;
   string FieldType = type;
   code FieldDefault = default;
 }
-class FieldValue<code value> { code FieldValue = value; }
 
 // A bool member set by -<name> or -<name>=<true|false|1|0>.
 multiclass BoolField<string name, code default, string help> {
-  def NAME : Flag<["-", "--"], name>, Field<NAME, "bool", default>,
-    FieldValue<"true">, HelpText<help>;
-  def NAME#_EQ : Joined<["-", "--"], name#"=">, Field<NAME, "bool", default>;
+  def NAME : FlagOrEq<["-", "--"], name>, Field<NAME, "bool", default>,
+    HelpText<help>;
 }
 
 // A member of the C++ type <type> set by -<name>=<value> or -<name> <value>.
 multiclass ValueField<string name, string type, code default, string help> {
-  def NAME#_EQ : Joined<["-", "--"], name#"=">, Field<NAME, type, default>,
+  def NAME : SeparateOrEq<["-", "--"], name>, Field<NAME, type, default>,
     HelpText<help>;
-  def NAME : Separate<["-", "--"], name>, Alias<!cast<Option>(NAME#"_EQ")>;
 }
diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td
index 7adb2cf6fa5de..122a90ea14795 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -32,10 +32,14 @@ def KIND_INPUT : OptionKind<"Input", 1, true>;
 def KIND_UNKNOWN : OptionKind<"Unknown", 2, true>;
 // A flag with no values.
 def KIND_FLAG : OptionKind<"Flag">;
+// A flag, or an option whose value follows '=' (--x or --x=v).
+def KIND_FLAG_OR_EQ : OptionKind<"FlagOrEq">;
 // An option which prefixes its (single) value.
 def KIND_JOINED : OptionKind<"Joined", 1>;
 // An option which is followed by its value.
 def KIND_SEPARATE : OptionKind<"Separate">;
+// An option whose value follows '=' or is the next argument (--x=v or --x v).
+def KIND_SEPARATE_OR_EQ : OptionKind<"SeparateOrEq">;
 // An option followed by its values, which are separated by commas.
 def KIND_COMMAJOINED : OptionKind<"CommaJoined">;
 // An option which is which takes multiple (separate) arguments.
@@ -163,6 +167,12 @@ class JoinedOrSeparate<list<string> prefixes, string name,
 class JoinedAndSeparate<list<string> prefixes, string name,
                         list<SubCommand> subcommands = []>
     : Option<prefixes, name, KIND_JOINED_AND_SEPARATE, subcommands>;
+class FlagOrEq<list<string> prefixes, string name,
+               list<SubCommand> subcommands = []>
+    : Option<prefixes, name, KIND_FLAG_OR_EQ, subcommands>;
+class SeparateOrEq<list<string> prefixes, string name,
+                   list<SubCommand> subcommands = []>
+    : Option<prefixes, name, KIND_SEPARATE_OR_EQ, subcommands>;
 
 // Mix-ins for adding optional attributes.
 
diff --git a/llvm/include/llvm/Option/Option.h b/llvm/include/llvm/Option/Option.h
index c1b24efbe5d50..4981a382a4968 100644
--- a/llvm/include/llvm/Option/Option.h
+++ b/llvm/include/llvm/Option/Option.h
@@ -59,19 +59,22 @@ class Option {
     InputClass,
     UnknownClass,
     FlagClass,
+    FlagOrEqClass,
     JoinedClass,
     ValuesClass,
     SeparateClass,
+    SeparateOrEqClass,
     RemainingArgsClass,
     RemainingArgsJoinedClass,
     CommaJoinedClass,
     MultiArgClass,
     JoinedOrSeparateClass,
-    JoinedAndSeparateClass
+    JoinedAndSeparateClass,
   };
 
   enum RenderStyleKind {
     RenderCommaJoinedStyle,
+    RenderEqStyle,
     RenderJoinedStyle,
     RenderSeparateStyle,
     RenderValuesStyle
@@ -153,6 +156,9 @@ class Option {
     case JoinedClass:
     case JoinedAndSeparateClass:
       return RenderJoinedStyle;
+    case FlagOrEqClass:
+    case SeparateOrEqClass:
+      return RenderEqStyle;
     case CommaJoinedClass:
       return RenderCommaJoinedStyle;
     case FlagClass:
diff --git a/llvm/lib/Option/Arg.cpp b/llvm/lib/Option/Arg.cpp
index 3aab7c0768e14..bae86d15d0404 100644
--- a/llvm/lib/Option/Arg.cpp
+++ b/llvm/lib/Option/Arg.cpp
@@ -106,9 +106,15 @@ void Arg::render(const ArgList &Args, ArgStringList &Output) const {
     Output.append(Values.begin() + 1, Values.end());
     break;
 
-  case Option::RenderSeparateStyle:
-    Output.push_back(Args.MakeArgString(getSpelling()));
-    Output.append(Values.begin(), Values.end());
-    break;
-  }
+ case Option::RenderEqStyle:
+   Output.push_back(Values.empty()
+                        ? Args.MakeArgString(getSpelling())
+                        : Args.MakeArgString(getSpelling() + "=" + getValue()));
+   break;
+
+ case Option::RenderSeparateStyle:
+   Output.push_back(Args.MakeArgString(getSpelling()));
+   Output.append(Values.begin(), Values.end());
+   break;
+ }
 }
diff --git a/llvm/lib/Option/LibraryOptions.cpp b/llvm/lib/Option/LibraryOptions.cpp
index 2b45d1ae7a2a1..3f54c850ad726 100644
--- a/llvm/lib/Option/LibraryOptions.cpp
+++ b/llvm/lib/Option/LibraryOptions.cpp
@@ -22,12 +22,13 @@ void LibraryOptionsParser::forEachOption(
   const OptTable &T = Table();
   for (unsigned ID = 1, E = T.getNumOptions(); ID <= E; ++ID) {
     unsigned Kind = T.getOptionKind(ID);
-    if (Kind != Option::FlagClass && Kind != Option::JoinedClass &&
-        Kind != Option::SeparateClass)
+    if (Kind != Option::FlagOrEqClass && Kind != Option::SeparateOrEqClass)
       continue;
-    StringRef MetaVar = T.getOptionMetaVar(ID);
-    if (MetaVar.empty() && Kind == Option::JoinedClass)
-      MetaVar = "<value>";
+    std::string MetaVar;
+    if (Kind == Option::SeparateOrEqClass) {
+      StringRef V = T.getOptionMetaVar(ID);
+      MetaVar = ("=" + (V.empty() ? StringRef("<value>") : V)).str();
+    }
     Fn(T.getOptionName(ID), MetaVar, T.getOptionHelpText(ID));
   }
 }
diff --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp
index dc17d4a10d45b..7fc9460af418a 100644
--- a/llvm/lib/Option/OptTable.cpp
+++ b/llvm/lib/Option/OptTable.cpp
@@ -299,6 +299,10 @@ unsigned OptTable::internalFindNearest(
       std::tie(NormalizedName, RHS) = Option.split(Last);
       if (Option.find(Last) == NormalizedName.size())
         NormalizedName += Last;
+    } else if (CandidateInfo.Kind == opt::Option::FlagOrEqClass ||
+               CandidateInfo.Kind == opt::Option::SeparateOrEqClass) {
+      NormalizedName = Option.split('=').first;
+      RHS = Option.drop_front(NormalizedName.size());
     } else
       NormalizedName = Option;
 
@@ -629,6 +633,15 @@ static std::string getOptionHelpName(const OptTable &Opts, OptSpecifier Id) {
     break;
 
   case Option::FlagClass:
+  case Option::FlagOrEqClass:
+    break;
+
+  case Option::SeparateOrEqClass:
+    Name += '=';
+    if (StringRef MetaVarName = Opts.getOptionMetaVar(Id); !MetaVarName.empty())
+      Name += MetaVarName;
+    else
+      Name += "<value>";
     break;
 
   case Option::ValuesClass:
diff --git a/llvm/lib/Option/Option.cpp b/llvm/lib/Option/Option.cpp
index 8e3638485b667..9a0d82d3a30d7 100644
--- a/llvm/lib/Option/Option.cpp
+++ b/llvm/lib/Option/Option.cpp
@@ -46,9 +46,11 @@ void Option::print(raw_ostream &O, bool AddNewLine) const {
     P(InputClass);
     P(UnknownClass);
     P(FlagClass);
+    P(FlagOrEqClass);
     P(JoinedClass);
     P(ValuesClass);
     P(SeparateClass);
+    P(SeparateOrEqClass);
     P(CommaJoinedClass);
     P(MultiArgClass);
     P(JoinedOrSeparateClass);
@@ -216,6 +218,23 @@ std::unique_ptr<Arg> Option::acceptInternal(const ArgList &Args,
       A->getValues().push_back(Args.getArgString(Index++));
     return A;
   }
+  case FlagOrEqClass:
+  case SeparateOrEqClass: {
+    const char *Rest = Args.getArgString(Index) + SpellingSize;
+    if (*Rest == '=')
+      return std::make_unique<Arg>(*this, CurArg, Index++, Rest + 1);
+    if (*Rest)
+      return nullptr;
+    if (getKind() == FlagOrEqClass)
+      return std::make_unique<Arg>(*this, CurArg, Index++);
+
+    Index += 2;
+    if (Index > Args.getNumInputArgStrings() ||
+        Args.getArgString(Index - 1) == nullptr)
+      return nullptr;
+    return std::make_unique<Arg>(*this, CurArg, Index - 2,
+                                 Args.getArgString(Index - 1));
+  }
   case RemainingArgsJoinedClass: {
     auto A = std::make_unique<Arg>(*this, CurArg, Index);
     if (SpellingSize != ArgStringSize) {
diff --git a/llvm/unittests/Option/LibraryOptionsTest.cpp b/llvm/unittests/Option/LibraryOptionsTest.cpp
index f13147b9feab5..0b4b83f030ac3 100644
--- a/llvm/unittests/Option/LibraryOptionsTest.cpp
+++ b/llvm/unittests/Option/LibraryOptionsTest.cpp
@@ -70,11 +70,10 @@ 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-count||",
-                        "lib-enable=|<value>|", "lib-enable||A bool",
-                        "lib-path=|<value>|A string", "lib-path||",
-                        "lib-ratio=|<value>|A double", "lib-ratio||"));
+  EXPECT_THAT(Rows, testing::ElementsAre("lib-count|=<value>|An unsigned",
+                                         "lib-enable||A bool",
+                                         "lib-path|=<value>|A string",
+                                         "lib-ratio|=<value>|A double"));
 
   auto Parse = [&](std::initializer_list<const char *> Argv) {
     unsigned Consumed = 0;
@@ -88,6 +87,11 @@ TEST(LibraryOptionsTest, Parser) {
   EXPECT_EQ(Parse({"-lib-count"}),
             "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-enable", "0"}), "1 ");
+  EXPECT_EQ(Parse({"-lib-count", "x"}),
+            "2 invalid value 'x' in '-lib-count=x'");
+  EXPECT_EQ(Parse({"-lib-enable=x"}), "1 invalid value 'x' in '-lib-enable=x'");
   P.reset();
   EXPECT_EQ(TestLibraryOptions::Global.count, 3u);
 }
diff --git a/llvm/utils/TableGen/OptionParserEmitter.cpp b/llvm/utils/TableGen/OptionParserEmitter.cpp
index 06f883e0ada71..a20bfe565c223 100644
--- a/llvm/utils/TableGen/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/OptionParserEmitter.cpp
@@ -230,10 +230,10 @@ static MarshallingInfo createMarshallingInfo(const Record &R) {
   return Ret;
 }
 
-// -foo-bar and -foo-bar= become foo_bar, or bar if Prefix is "foo-".
+// -foo-bar becomes foo_bar, or bar if Prefix is "foo-".
 static std::string getSpellingIdentifier(const Record &R,
                                          StringRef Prefix = "") {
-  StringRef Spelling = R.getValueAsString("Name").rtrim('=');
+  StringRef Spelling = R.getValueAsString("Name");
   Spelling.consume_front(Prefix);
   std::string ID = Spelling.str();
   llvm::replace(ID, '-', '_');
@@ -253,14 +253,11 @@ static std::string getMemberName(const Record &R, StringRef Prefix) {
 }
 
 // The OPT_ name of an option of an OptionsStruct. `defm :` rows are named
-// after the spelling: -foo-bar= is OPT_foo_bar_EQ.
+// after the spelling: -foo-bar is OPT_foo_bar.
 static std::string getStructOptionID(const Record &R) {
   if (!R.getName().starts_with("anonymous_"))
     return getOptionName(R);
-  std::string ID = getSpellingIdentifier(R);
-  if (R.getValueAsString("Name").ends_with('='))
-    ID += "_EQ";
-  return ID;
+  return getSpellingIdentifier(R);
 }
 
 // Emits the struct an OptionsStruct def declares: its declaration under
@@ -283,11 +280,11 @@ static void emitOptionsStruct(const Record &Struct,
                                      "declared with BoolField or ValueField");
       continue;
     }
-    bool HasValue = R->getValue("FieldValue");
-    if ((Kind == "Flag") != HasValue ||
-        (Kind != "Flag" && Kind != "Joined" && Kind != "Separate"))
-      PrintFatalError(R->getLoc(), "a member is set by a Flag with a "
-                                   "FieldValue, or by a Joined or Separate");
+    if (Kind != "FlagOrEq" && Kind != "SeparateOrEq")
+      PrintFatalError(R->getLoc(),
+                      "a member is set by a FlagOrEq or SeparateOrEq");
+    if (Kind == "FlagOrEq" && R->getValueAsString("FieldType") != "bool")
+      PrintFatalError(R->getLoc(), "a FlagOrEq sets a bool member");
     Fields.push_back(R);
   }
   // Members in declaration order.
@@ -297,21 +294,15 @@ static void emitOptionsStruct(const Record &Struct,
   });
   StringRef Prefix = Struct.getValueAsString("MemberPrefix");
   std::vector<Member> Members;
-  StringMap<unsigned> MemberIndex;
+  StringMap<StringRef> MemberSpelling;
   for (const Record *R : ByID) {
     Member M{getMemberName(*R, Prefix), R->getValueAsString("FieldType"),
-             R->getValueAsString("FieldDefault"),
-             R->getValueAsString("Name").rtrim('=')};
-    auto [It, Inserted] = MemberIndex.try_emplace(M.Name, Members.size());
-    if (Inserted) {
-      Members.push_back(M);
-      continue;
-    }
-    // The rows of one BoolField or ValueField share the member.
-    Member &Prev = Members[It->second];
-    if (Prev.Spelling != M.Spelling)
-      PrintFatalError(R->getLoc(), "member '" + M.Name + "' is also set by -" +
-                                       Prev.Spelling);
+             R->getValueAsString("FieldDefault"), R->getValueAsString("Name")};
+    auto [It, Inserted] = MemberSpelling.try_emplace(M.Name, M.Spelling);
+    if (!Inserted)
+      PrintFatalError(R->getLoc(),
+                      "member '" + M.Name + "' is also set by -" + It->second);
+    Members.push_back(M);
   }
 
   StringRef Name = Struct.getName();
@@ -352,12 +343,11 @@ static void emitOptionsStruct(const Record &Struct,
   for (const Record *R : Fields) {
     OS << "  case OPT_" << getStructOptionID(*R) << ":\n";
     std::string Member = getMemberName(*R, Prefix);
-    if (!R->getValue("FieldValue"))
-      OS << "    return llvm::opt::parseArgValue(A.getValue(), " << Member
-         << ");\n";
-    else
-      OS << "    " << Member << " = " << R->getValueAsString("FieldValue")
-         << ";\n    return true;\n";
+    if (R->getValueAsDef("Kind")->getValueAsString("Name") == "FlagOrEq")
+      OS << "    if (!A.getNumValues()) {\n      " << Member
+         << " = true;\n      return true;\n    }\n";
+    OS << "    return llvm::opt::parseArgValue(A.getValue(), " << Member
+       << ");\n";
   }
   OS << "  }\n  llvm_unreachable(\"option without a member\");\n}\n";
   OS << "#endif // OPTIONS_STRUCT_DEFS\n";



More information about the cfe-commits mailing list