[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