[llvm] [Option] Add BoolOrDefault for OptionalBoolField and DefaultOnOffField (PR #228835)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Sat Oct 3 23:56:31 PDT 2026
https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/228835
>From 252fc57718dca74c622137bba3a06ac78899b29b Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 3 Oct 2026 23:03:57 -0700
Subject: [PATCH] [Option] Add BoolOrDefault for OptionalBoolField and
DefaultOnOffField
The members of OptionalBoolField and DefaultOnOffField are
std::optional<bool>, on which `if (X)` tests whether the option is
given, not its value. Make them a new 1-byte `enum class BoolOrDefault :
uint8_t { Default, False, True }`, which does not convert to bool, and
read them with `valueOr(X, Default)`. A FlagOrEq without a value is
parsed as =true, so that it can set either member type.
Aided by Opus 5.5
---
llvm/docs/CommandLine.md | 4 +--
llvm/include/llvm/ADT/BoolOrDefault.h | 27 +++++++++++++++++++
llvm/include/llvm/Option/LibraryOptions.h | 9 +++++++
llvm/include/llvm/Option/LibraryOptions.td | 17 +++++++-----
llvm/lib/MC/MCAsmInfo.cpp | 8 +++---
llvm/lib/MC/MCAsmInfoXCOFF.cpp | 2 +-
llvm/unittests/Option/LibraryOptionsTest.cpp | 23 ++++++++++------
.../TableGen/Basic/OptionParserEmitter.cpp | 13 +++++----
8 files changed, 76 insertions(+), 27 deletions(-)
create mode 100644 llvm/include/llvm/ADT/BoolOrDefault.h
diff --git a/llvm/docs/CommandLine.md b/llvm/docs/CommandLine.md
index a6d642943945326..bdb25833caf7352 100644
--- a/llvm/docs/CommandLine.md
+++ b/llvm/docs/CommandLine.md
@@ -1586,9 +1586,9 @@ A member is named after its option, `enable_foo` for `-enable-foo`; a named `def
The `BoolField` is set by `-enable-foo` or `-enable-foo=true|false|1|0`.
A `ValueField`, of an integer type, `float`, `double`, or `StringRef`, is set by `-foo-threshold=8` or `-foo-threshold 8`.
-An `OptionalBoolField` is a `std::optional<bool>` that stays `std::nullopt` unless the option is given, replacing `cl::boolOrDefault`.
+An `OptionalBoolField` is a `BoolOrDefault` that stays `Default` unless the option is given, replacing `cl::boolOrDefault`; read it with `valueOr(X, Default)`.
An `EnumField` maps each of its comma-separated values to an enumerator, replacing `cl::values`: `defm : EnumField<"foo-mode", "FooMode", "FooMode::Fast", "fast,safe", ["FooMode::Fast", "FooMode::Safe"], "The mode">;` accepts `-foo-mode=fast` and `-foo-mode=safe`.
-A `DefaultOnOffField` is a `std::optional<bool>` set by `=Default`, `=Enable`, or `=Disable`.
+A `DefaultOnOffField` is a `BoolOrDefault` set by `=Default`, `=Enable`, or `=Disable`.
Both accept `--` for `-`.
Only `-help-hidden` lists the options, like `cl::Hidden`.
diff --git a/llvm/include/llvm/ADT/BoolOrDefault.h b/llvm/include/llvm/ADT/BoolOrDefault.h
new file mode 100644
index 000000000000000..619ee9f6cfe0eea
--- /dev/null
+++ b/llvm/include/llvm/ADT/BoolOrDefault.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ADT_BOOLORDEFAULT_H
+#define LLVM_ADT_BOOLORDEFAULT_H
+
+#include <cstdint>
+
+namespace llvm {
+
+// A bool, or Default when the caller's default applies. Unlike
+// std::optional<bool>, it does not convert to bool.
+enum class BoolOrDefault : uint8_t { Default, False, True };
+
+// Like std::optional<bool>::value_or.
+constexpr bool valueOr(BoolOrDefault X, bool Default) {
+ return X == BoolOrDefault::Default ? Default : X == BoolOrDefault::True;
+}
+
+} // namespace llvm
+
+#endif // LLVM_ADT_BOOLORDEFAULT_H
diff --git a/llvm/include/llvm/Option/LibraryOptions.h b/llvm/include/llvm/Option/LibraryOptions.h
index 06209d5d56a402e..5164267b216110f 100644
--- a/llvm/include/llvm/Option/LibraryOptions.h
+++ b/llvm/include/llvm/Option/LibraryOptions.h
@@ -15,6 +15,7 @@
#ifndef LLVM_OPTION_LIBRARYOPTIONS_H
#define LLVM_OPTION_LIBRARYOPTIONS_H
+#include "llvm/ADT/BoolOrDefault.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/OptTable.h"
@@ -38,6 +39,14 @@ inline bool parseArgValue(StringRef S, bool &V) {
return true;
}
+inline bool parseArgValue(StringRef S, BoolOrDefault &V) {
+ bool B;
+ if (!parseArgValue(S, B))
+ return false;
+ V = B ? BoolOrDefault::True : BoolOrDefault::False;
+ return true;
+}
+
inline bool parseArgValue(StringRef S, StringRef &V) {
V = S;
return true;
diff --git a/llvm/include/llvm/Option/LibraryOptions.td b/llvm/include/llvm/Option/LibraryOptions.td
index 588f2127e21e1ff..6a38d87fc13f0c6 100644
--- a/llvm/include/llvm/Option/LibraryOptions.td
+++ b/llvm/include/llvm/Option/LibraryOptions.td
@@ -35,11 +35,12 @@ multiclass BoolField<string name, code default, string help> {
HelpText<help>;
}
-// A std::optional<bool> member, std::nullopt unless -<name> or
-// -<name>=<true|false|1|0> is given.
+// A BoolOrDefault member, Default unless -<name> or -<name>=<true|false|1|0>
+// is given.
multiclass OptionalBoolField<string name, string help> {
def NAME : FlagOrEq<["-", "--"], name>,
- Field<NAME, "std::optional<bool>", "std::nullopt">, HelpText<help>;
+ Field<NAME, "llvm::BoolOrDefault", "llvm::BoolOrDefault::Default">,
+ HelpText<help>;
}
// A member of the enumeration <type> set by -<name>=<value> or -<name> <value>,
@@ -52,11 +53,13 @@ multiclass EnumField<string name, string type, code default, string values,
MetaVarName<"<"#!subst(",", "|", values)#">">, HelpText<help>;
}
-// A std::optional<bool> member, std::nullopt unless -<name>=Enable or
-// -<name>=Disable is given.
+// A BoolOrDefault member, Default unless -<name>=Enable or -<name>=Disable is
+// given.
multiclass DefaultOnOffField<string name, string help> {
- defm NAME : EnumField<name, "std::optional<bool>", "std::nullopt",
- "Default,Enable,Disable", ["std::nullopt", "true", "false"], help>;
+ defm NAME : EnumField<name, "llvm::BoolOrDefault",
+ "llvm::BoolOrDefault::Default", "Default,Enable,Disable",
+ ["llvm::BoolOrDefault::Default", "llvm::BoolOrDefault::True",
+ "llvm::BoolOrDefault::False"], help>;
}
// A member of the C++ type <type> set by -<name>=<value> or -<name> <value>.
diff --git a/llvm/lib/MC/MCAsmInfo.cpp b/llvm/lib/MC/MCAsmInfo.cpp
index 3b9f96739eda179..24b593c380a3b76 100644
--- a/llvm/lib/MC/MCAsmInfo.cpp
+++ b/llvm/lib/MC/MCAsmInfo.cpp
@@ -26,10 +26,10 @@ using namespace llvm;
MCAsmInfo::MCAsmInfo(const MCTargetOptions &Options) : TargetOptions(Options) {
const MCCLOptions &CLOpts = MCCLOptions::Global;
- if (CLOpts.dwarf_extended_loc)
- SupportsExtendedDwarfLocDirective = *CLOpts.dwarf_extended_loc;
- if (CLOpts.use_leb128_directives)
- HasLEB128Directives = *CLOpts.use_leb128_directives;
+ SupportsExtendedDwarfLocDirective =
+ valueOr(CLOpts.dwarf_extended_loc, SupportsExtendedDwarfLocDirective);
+ HasLEB128Directives =
+ valueOr(CLOpts.use_leb128_directives, HasLEB128Directives);
if (Options.BinutilsVersion.first > 0)
BinutilsVersion = Options.BinutilsVersion;
}
diff --git a/llvm/lib/MC/MCAsmInfoXCOFF.cpp b/llvm/lib/MC/MCAsmInfoXCOFF.cpp
index 8bf80212aa0295d..b1922fe104a2c19 100644
--- a/llvm/lib/MC/MCAsmInfoXCOFF.cpp
+++ b/llvm/lib/MC/MCAsmInfoXCOFF.cpp
@@ -23,7 +23,7 @@ MCAsmInfoXCOFF::MCAsmInfoXCOFF(const MCTargetOptions &Options)
InternalSymbolPrefix = "L..";
SupportsQuotedNames = false;
- if (!MCCLOptions::Global.use_leb128_directives)
+ if (MCCLOptions::Global.use_leb128_directives == BoolOrDefault::Default)
HasLEB128Directives = false;
ZeroDirective = "\t.space\t";
AsciiDirective = nullptr; // not supported
diff --git a/llvm/unittests/Option/LibraryOptionsTest.cpp b/llvm/unittests/Option/LibraryOptionsTest.cpp
index e900840db6d1b2b..073a1c96adc7f43 100644
--- a/llvm/unittests/Option/LibraryOptionsTest.cpp
+++ b/llvm/unittests/Option/LibraryOptionsTest.cpp
@@ -35,9 +35,9 @@ TEST(LibraryOptionsTest, Apply) {
EXPECT_EQ(O.count, 3u);
EXPECT_EQ(O.limit, std::nullopt);
EXPECT_EQ(O.mode, test::Mode::A);
- EXPECT_EQ(O.override, std::nullopt);
+ EXPECT_EQ(O.override, BoolOrDefault::Default);
EXPECT_EQ(O.ratio, 0.5);
- EXPECT_EQ(O.tristate, std::nullopt);
+ EXPECT_EQ(O.tristate, BoolOrDefault::Default);
EXPECT_EQ(O.Path, "p");
auto Apply = [&](std::initializer_list<const char *> Argv) {
@@ -57,9 +57,9 @@ TEST(LibraryOptionsTest, Apply) {
EXPECT_EQ(O.count, 7u);
EXPECT_EQ(O.limit, 0u);
EXPECT_EQ(O.mode, test::Mode::B);
- EXPECT_EQ(O.override, true);
+ EXPECT_EQ(O.override, BoolOrDefault::True);
EXPECT_EQ(O.ratio, 0.25);
- EXPECT_EQ(O.tristate, false);
+ EXPECT_EQ(O.tristate, BoolOrDefault::False);
EXPECT_EQ(O.Path, "a=b");
EXPECT_THAT(Apply({"-lib-enable=false"}), testing::Each(true));
EXPECT_FALSE(O.enable);
@@ -67,10 +67,10 @@ TEST(LibraryOptionsTest, Apply) {
Apply({"-lib-enable=1", "-lib-override=false", "-lib-tristate=Enable"}),
testing::Each(true));
EXPECT_TRUE(O.enable);
- EXPECT_EQ(O.override, false);
- EXPECT_EQ(O.tristate, true);
+ EXPECT_EQ(O.override, BoolOrDefault::False);
+ EXPECT_EQ(O.tristate, BoolOrDefault::True);
EXPECT_THAT(Apply({"-lib-tristate=Default"}), testing::Each(true));
- EXPECT_EQ(O.tristate, std::nullopt);
+ EXPECT_EQ(O.tristate, BoolOrDefault::Default);
// A rejected value leaves the member unchanged.
EXPECT_THAT(Apply({"-lib-enable=2", "-lib-count=-1", "-lib-limit=x",
@@ -80,10 +80,17 @@ TEST(LibraryOptionsTest, Apply) {
EXPECT_EQ(O.count, 7u);
EXPECT_EQ(O.limit, 0u);
EXPECT_EQ(O.mode, test::Mode::B);
- EXPECT_EQ(O.override, false);
+ EXPECT_EQ(O.override, BoolOrDefault::False);
EXPECT_EQ(O.ratio, 0.25);
}
+TEST(LibraryOptionsTest, BoolOrDefault) {
+ EXPECT_TRUE(valueOr(BoolOrDefault::Default, true));
+ EXPECT_FALSE(valueOr(BoolOrDefault::Default, false));
+ EXPECT_TRUE(valueOr(BoolOrDefault::True, false));
+ EXPECT_FALSE(valueOr(BoolOrDefault::False, true));
+}
+
// What cl:: sees of the struct, without cl::.
TEST(LibraryOptionsTest, Parser) {
opt::LibraryOptionsParser P(
diff --git a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
index b07de9492133159..33b4be58580fb26 100644
--- a/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/OptionParserEmitter.cpp
@@ -283,7 +283,7 @@ static void emitOptionsStruct(const Record &Struct,
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>")
+ if (Kind == "FlagOrEq" && Type != "bool" && Type != "llvm::BoolOrDefault")
PrintFatalError(R->getLoc(), "a FlagOrEq sets a bool member");
Fields.push_back(R);
}
@@ -307,6 +307,7 @@ static void emitOptionsStruct(const Record &Struct,
StringRef Name = Struct.getName();
OS << "\n#ifdef OPTIONS_STRUCT_DECL\n#undef OPTIONS_STRUCT_DECL\n";
+ OS << "#include \"llvm/ADT/BoolOrDefault.h\"\n";
OS << "#include \"llvm/ADT/StringRef.h\"\n\n";
StringRef Namespace = Struct.getValueAsString("Namespace");
OS << "namespace llvm {\nnamespace opt {\nclass Arg;\n"
@@ -358,10 +359,12 @@ static void emitOptionsStruct(const Record &Struct,
OS << " return false;\n }\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
+ // 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
<< ");\n";
}
OS << " }\n llvm_unreachable(\"option without a member\");\n}\n";
More information about the llvm-commits
mailing list