[Option] Add FlagOrEq and SeparateOrEq option kinds (#228319)

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
diff --git a/clang-tools-extra/clangd/CompileCommands.cpp b/clang-tools-extra/clangd/CompileCommands.cpp
index 2ba9446..eb9e1df 100644
--- a/clang-tools-extra/clangd/CompileCommands.cpp
+++ b/clang-tools-extra/clangd/CompileCommands.cpp
@@ -416,7 +416,10 @@
   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 68a92e5..588f212 100644
--- a/llvm/include/llvm/Option/LibraryOptions.td
+++ b/llvm/include/llvm/Option/LibraryOptions.td
@@ -22,30 +22,24 @@
   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 std::optional<bool> member, std::nullopt unless -<name> or
 // -<name>=<true|false|1|0> is given.
 multiclass OptionalBoolField<string name, string help> {
-  def NAME : Flag<["-", "--"], name>,
-    Field<NAME, "std::optional<bool>", "std::nullopt">, FieldValue<"true">,
-    HelpText<help>;
-  def NAME#_EQ : Joined<["-", "--"], name#"=">,
-    Field<NAME, "std::optional<bool>", "std::nullopt">;
+  def NAME : FlagOrEq<["-", "--"], name>,
+    Field<NAME, "std::optional<bool>", "std::nullopt">, HelpText<help>;
 }
 
 // A member of the enumeration <type> set by -<name>=<value> or -<name> <value>,
@@ -53,10 +47,9 @@
 // the C++ <enumerators>.
 multiclass EnumField<string name, string type, code default, string values,
                      list<code> enumerators, string help> {
-  def NAME#_EQ : Joined<["-", "--"], name#"=">, Field<NAME, type, default>,
+  def NAME : SeparateOrEq<["-", "--"], name>, Field<NAME, type, default>,
     Values<values>, NormalizedValues<enumerators>,
     MetaVarName<"<"#!subst(",", "|", values)#">">, HelpText<help>;
-  def NAME : Separate<["-", "--"], name>, Alias<!cast<Option>(NAME#"_EQ")>;
 }
 
 // A std::optional<bool> member, std::nullopt unless -<name>=Enable or
@@ -68,7 +61,6 @@
 
 // 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 7adb2cf..122a90e 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -32,10 +32,14 @@
 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 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 c1b24ef..4981a38 100644
--- a/llvm/include/llvm/Option/Option.h
+++ b/llvm/include/llvm/Option/Option.h
@@ -59,19 +59,22 @@
     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 @@
     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 3aab7c0..bae86d1 100644
--- a/llvm/lib/Option/Arg.cpp
+++ b/llvm/lib/Option/Arg.cpp
@@ -106,9 +106,15 @@
     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 2b45d1a..3f54c85 100644
--- a/llvm/lib/Option/LibraryOptions.cpp
+++ b/llvm/lib/Option/LibraryOptions.cpp
@@ -22,12 +22,13 @@
   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 dc17d4a..7fc9460a 100644
--- a/llvm/lib/Option/OptTable.cpp
+++ b/llvm/lib/Option/OptTable.cpp
@@ -299,6 +299,10 @@
       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 @@
     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 8e36384..9a0d82d 100644
--- a/llvm/lib/Option/Option.cpp
+++ b/llvm/lib/Option/Option.cpp
@@ -46,9 +46,11 @@
     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 @@
       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 1ca9d33..e900840 100644
--- a/llvm/unittests/Option/LibraryOptionsTest.cpp
+++ b/llvm/unittests/Option/LibraryOptionsTest.cpp
@@ -97,15 +97,11 @@
   });
   EXPECT_THAT(Rows,
               testing::ElementsAre(
-                  "lib-count=|<value>|An unsigned", "lib-count||",
-                  "lib-enable=|<value>|", "lib-enable||A bool",
-                  "lib-limit=|<value>|An optional", "lib-limit||",
-                  "lib-mode=|<a|b>|An enum", "lib-mode||",
-                  "lib-override=|<value>|", "lib-override||An optional bool",
-                  "lib-path=|<value>|A string", "lib-path||",
-                  "lib-ratio=|<value>|A double", "lib-ratio||",
-                  "lib-tristate=|<Default|Enable|Disable>|A tri-state",
-                  "lib-tristate||"));
+                  "lib-count|=<value>|An unsigned", "lib-enable||A bool",
+                  "lib-limit|=<value>|An optional", "lib-mode|=<a|b>|An enum",
+                  "lib-override||An optional bool",
+                  "lib-path|=<value>|A string", "lib-ratio|=<value>|A double",
+                  "lib-tristate|=<Default|Enable|Disable>|A tri-state"));
 
   auto Parse = [&](std::initializer_list<const char *> Argv) {
     unsigned Consumed = 0;
@@ -119,6 +115,11 @@
   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/Basic/OptionParserEmitter.cpp b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
index b4487c4..b07de94 100644
--- a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
@@ -229,10 +229,10 @@
   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, '-', '_');
@@ -252,14 +252,11 @@
 }
 
 // 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
@@ -282,11 +279,12 @@
                                      "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");
+    StringRef Type = R->getValueAsString("FieldType");
+    if (Kind == "FlagOrEq" && Type != "bool" && Type != "std::optional<bool>")
+      PrintFatalError(R->getLoc(), "a FlagOrEq sets a bool member");
     Fields.push_back(R);
   }
   // Members in declaration order.
@@ -296,21 +294,15 @@
   });
   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();
@@ -364,12 +356,13 @@
         OS << "      if (V == \"" << Value << "\") {\n        " << Member
            << " = " << Enumerator << ";\n        return true;\n      }\n";
       OS << "      return false;\n    }\n";
-    } else if (!R->getValue("FieldValue"))
-      OS << "    return llvm::opt::parseArgValue(A.getValue(), " << Member
-         << ");\n";
-    else
-      OS << "    " << Member << " = " << R->getValueAsString("FieldValue")
-         << ";\n    return true;\n";
+      continue;
+    }
+    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";