[llvm] [Option] Declare library command line options in TableGen (PR #226087)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 29 22:11:42 PDT 2026
https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/226087
>From f1fd167eda2fdab5a7bf158a89e8ec4916396987 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Thu, 24 Sep 2026 00:42:37 -0700
Subject: [PATCH 1/6] [CGData] Declare command line options in TableGen, one
struct per library
Implement the first step of
https://discourse.llvm.org/t/rfc-declare-library-command-line-options-in-tablegen-one-struct-per-library/91877
and migrate LLVMCGData's 13 cl::opts.
A .td with an `OptionsStruct` def declares a library's options with
`BoolField` (`-x`, `-no-x`, `-x=<bool>`) and `ValueField` (`-x=v`,
`-x v`). `-gen-opt-parser-defs` generates a struct with one member per
option, a `Global` instance, the option table, and `apply(const Arg &)`.
`cl::ParseCommandLineOptions` keeps owning argv: a static
`opt::RegisterLibraryOptions<T>` registers the struct as a
`cl::LibraryOptions`, and an argument naming none of cl::'s options is
dispatched to the library that declares it. `-help-hidden` lists library
options, `ResetAllOptionOccurrences` restores their defaults, and a name
shared with a cl::opt or another library is a fatal error.
CGDataOptions is private to lib/CGData:
* LTO calls `cgdata::thinLTOTwoRounds()` instead of declaring
`extern cl::opt<bool> CodeGenDataThinLTOTwoRounds`.
* llvm-cgdata passes `--indexed-codegen-data-lazy-loading` to
`CodeGenDataReader::create` instead of assigning the cl::opt exported
from CodeGenDataReader.h.
---
llvm/docs/CommandLine.md | 33 +++++
llvm/include/llvm/CGData/CodeGenData.h | 4 +
llvm/include/llvm/CGData/CodeGenDataReader.h | 16 +--
llvm/include/llvm/Option/LibraryOptions.h | 96 +++++++++++++
llvm/include/llvm/Option/OptParser.td | 36 +++++
llvm/include/llvm/Support/CommandLine.h | 28 ++++
llvm/lib/CGData/CGDataOptions.cpp | 15 +++
llvm/lib/CGData/CGDataOptions.h | 17 +++
llvm/lib/CGData/CGDataOptions.td | 51 +++++++
llvm/lib/CGData/CMakeLists.txt | 7 +
llvm/lib/CGData/CodeGenData.cpp | 31 ++---
llvm/lib/CGData/CodeGenDataReader.cpp | 29 ++--
llvm/lib/CGData/StableFunctionMap.cpp | 57 ++------
llvm/lib/LTO/LTO.cpp | 3 +-
llvm/lib/Option/CMakeLists.txt | 1 +
llvm/lib/Option/LibraryOptions.cpp | 52 +++++++
llvm/lib/Support/CommandLine.cpp | 82 ++++++++++-
llvm/tools/llvm-cgdata/llvm-cgdata.cpp | 8 +-
llvm/unittests/Option/CMakeLists.txt | 4 +
llvm/unittests/Option/LibraryOptionsTest.cpp | 111 +++++++++++++++
llvm/unittests/Option/LibraryOpts.td | 16 +++
llvm/unittests/Support/CommandLineTest.cpp | 127 ++++++++++++++++++
llvm/utils/TableGen/OptionParserEmitter.cpp | 125 ++++++++++++++++-
.../gn/secondary/llvm/lib/CGData/BUILD.gn | 10 ++
.../gn/secondary/llvm/lib/Option/BUILD.gn | 1 +
.../secondary/llvm/unittests/Option/BUILD.gn | 7 +
.../llvm-project-overlay/llvm/BUILD.bazel | 16 ++-
.../llvm/unittests/BUILD.bazel | 11 ++
28 files changed, 889 insertions(+), 105 deletions(-)
create mode 100644 llvm/include/llvm/Option/LibraryOptions.h
create mode 100644 llvm/lib/CGData/CGDataOptions.cpp
create mode 100644 llvm/lib/CGData/CGDataOptions.h
create mode 100644 llvm/lib/CGData/CGDataOptions.td
create mode 100644 llvm/lib/Option/LibraryOptions.cpp
create mode 100644 llvm/unittests/Option/LibraryOptionsTest.cpp
create mode 100644 llvm/unittests/Option/LibraryOpts.td
diff --git a/llvm/docs/CommandLine.md b/llvm/docs/CommandLine.md
index 3dd499e2a2f6c..d8614e394109d 100644
--- a/llvm/docs/CommandLine.md
+++ b/llvm/docs/CommandLine.md
@@ -1680,3 +1680,36 @@ TODO: complete this section
:::{todo}
TODO: fill in this section
:::
+
+## Declaring a Library's Options in TableGen
+
+A library can declare its options in a `.td` file instead of as `cl::opt`
+globals. `llvm-tblgen -gen-opt-parser-defs` generates a struct with a member
+per option, the table that parses them, and the hooks through which
+`cl::ParseCommandLineOptions` parses them and `-help-hidden` lists them.
+
+```text
+include "llvm/Option/OptParser.td"
+
+def FooOptions : OptionsStruct;
+// The spellings of FooMode, a C++ enumeration declared elsewhere.
+def FooMode : OptionEnum<"FooMode", [EnumMember<"Fast", "fast">,
+ EnumMember<"Small", "small">]>;
+
+defm Enable : BoolField<"foo-enable", "1", "Enable foo">;
+defm Threshold : ValueField<"foo-threshold", "unsigned", "8", "The threshold">;
+defm Mode : EnumField<"foo-mode", FooMode, "FooMode::Fast", "Foo's mode">;
+```
+
+The `defm` name is the member name. A `BoolField` is set by `-x`, `-no-x`, or
+`-x=true|false|1|0`; a `ValueField` of an integer type, `double`,
+or `std::string` by `-x=value` or `-x value`. Both accept `--` for `-`.
+
+The header declares the struct after including what the member defaults need,
+and one source file defines it and registers it with `cl::`.
+
+The library then lists `XXOptionsTableGen` under `DEPENDS` and `Option`
+under `LINK_COMPONENTS`. Code reads `XXOptions::Global.CodeGenDataGenerate`,
+the instance the command line sets. Keep the header in `lib/`, as private as the
+`static cl::opt` it replaces; another library that needs a value calls a
+function or takes a parameter.
diff --git a/llvm/include/llvm/CGData/CodeGenData.h b/llvm/include/llvm/CGData/CodeGenData.h
index e44497a408245..cd1d849fdc155 100644
--- a/llvm/include/llvm/CGData/CodeGenData.h
+++ b/llvm/include/llvm/CGData/CodeGenData.h
@@ -242,6 +242,10 @@ struct StreamCacheData {
}
};
+/// Returns whether ThinLTO runs two rounds of code generation
+/// (-codegen-data-thinlto-two-rounds).
+LLVM_ABI bool thinLTOTwoRounds();
+
/// Save \p TheModule before the first codegen round.
/// \p Task represents the partition number in the parallel code generation
/// process. \p AddStream is the callback used to add the serialized module to
diff --git a/llvm/include/llvm/CGData/CodeGenDataReader.h b/llvm/include/llvm/CGData/CodeGenDataReader.h
index a656147a48610..94f7af2de1701 100644
--- a/llvm/include/llvm/CGData/CodeGenDataReader.h
+++ b/llvm/include/llvm/CGData/CodeGenDataReader.h
@@ -16,7 +16,6 @@
#include "llvm/CGData/CodeGenData.h"
#include "llvm/CGData/OutlinedHashTreeRecord.h"
#include "llvm/CGData/StableFunctionMapRecord.h"
-#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/VirtualFileSystem.h"
@@ -50,14 +49,15 @@ class CodeGenDataReader {
}
/// Factory method to create an appropriately typed reader for the given
- /// codegen data file path and file system.
+ /// codegen data file path and file system. \p LazyLoading defers
+ /// deserializing the stable function map of indexed codegen data.
LLVM_ABI static Expected<std::unique_ptr<CodeGenDataReader>>
- create(const Twine &Path, vfs::FileSystem &FS);
+ create(const Twine &Path, vfs::FileSystem &FS, bool LazyLoading = false);
/// Factory method to create an appropriately typed reader for the given
/// memory buffer.
LLVM_ABI static Expected<std::unique_ptr<CodeGenDataReader>>
- create(std::unique_ptr<MemoryBuffer> Buffer);
+ create(std::unique_ptr<MemoryBuffer> Buffer, bool LazyLoading = false);
/// Extract the cgdata embedded in sections from the given object file and
/// merge them into the GlobalOutlineRecord. This is a static helper that
@@ -100,17 +100,17 @@ class CodeGenDataReader {
Error success() { return error(cgdata_error::success); }
};
-LLVM_ABI extern cl::opt<bool> IndexedCodeGenDataLazyLoading;
-
class LLVM_ABI IndexedCodeGenDataReader : public CodeGenDataReader {
/// The codegen data file contents.
std::unique_ptr<MemoryBuffer> DataBuffer;
/// The header
IndexedCGData::Header Header;
+ bool LazyLoading;
public:
- IndexedCodeGenDataReader(std::unique_ptr<MemoryBuffer> DataBuffer)
- : DataBuffer(std::move(DataBuffer)) {}
+ IndexedCodeGenDataReader(std::unique_ptr<MemoryBuffer> DataBuffer,
+ bool LazyLoading = false)
+ : DataBuffer(std::move(DataBuffer)), LazyLoading(LazyLoading) {}
IndexedCodeGenDataReader(const IndexedCodeGenDataReader &) = delete;
IndexedCodeGenDataReader &
operator=(const IndexedCodeGenDataReader &) = delete;
diff --git a/llvm/include/llvm/Option/LibraryOptions.h b/llvm/include/llvm/Option/LibraryOptions.h
new file mode 100644
index 0000000000000..2ddcb34b3323b
--- /dev/null
+++ b/llvm/include/llvm/Option/LibraryOptions.h
@@ -0,0 +1,96 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Support for the options struct that -gen-opt-parser-defs generates from an
+// OptionsStruct def. See "Declaring a Library's Options in TableGen" in
+// llvm/docs/CommandLine.md.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_OPTION_LIBRARYOPTIONS_H
+#define LLVM_OPTION_LIBRARYOPTIONS_H
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Option/Arg.h"
+#include "llvm/Option/OptTable.h"
+#include "llvm/Option/Option.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Compiler.h"
+#include <string>
+#include <type_traits>
+
+namespace llvm {
+namespace opt {
+
+// Each accepts the spellings cl::opt accepts for the type.
+inline bool parseArgValue(StringRef S, bool &V) {
+ if (S == "true" || S == "1")
+ V = true;
+ else if (S == "false" || S == "0")
+ V = false;
+ else
+ return false;
+ return true;
+}
+
+inline bool parseArgValue(StringRef S, std::string &V) {
+ V = S.str();
+ return true;
+}
+
+template <typename T>
+std::enable_if_t<std::is_arithmetic_v<T> && !std::is_same_v<T, bool>, bool>
+parseArgValue(StringRef S, T &V) {
+ if constexpr (std::is_floating_point_v<T>)
+ return to_float(S, V);
+ else
+ return to_integer(S, V);
+}
+
+/// An OptTable with a public constructor, shared by every options struct.
+class LLVM_ABI LibraryOptTable : public OptTable {
+public:
+ explicit LibraryOptTable(const Tables &T) : OptTable(T) {}
+ ~LibraryOptTable() override;
+};
+
+/// Connects an options struct to cl::ParseCommandLineOptions.
+class LLVM_ABI LibraryOptionsParser final : public cl::LibraryOptions {
+public:
+ using ApplyFn = bool (*)(const Arg &);
+ using TableFn = const OptTable &(*)();
+ LibraryOptionsParser(TableFn Table, ApplyFn Apply, void (*Reset)())
+ : Table(Table), Apply(Apply), Reset(Reset) {}
+
+ void forEachOption(
+ function_ref<void(StringRef, StringRef, StringRef)> Fn) const override;
+ Error parse(ArrayRef<const char *> Args, unsigned &Consumed) override;
+ void reset() override { Reset(); }
+
+private:
+ TableFn Table;
+ ApplyFn Apply;
+ void (*Reset)();
+};
+
+/// Registers T::Global with cl::ParseCommandLineOptions. The library owning T
+/// defines one static instance in the file that includes the struct's
+/// definitions.
+template <typename T> class RegisterLibraryOptions {
+ LibraryOptionsParser Parser{T::optTable,
+ [](const Arg &A) { return T::Global.apply(A); },
+ [] { T::Global = T(); }};
+
+public:
+ RegisterLibraryOptions() { cl::addLibraryOptions(Parser); }
+};
+
+} // namespace opt
+} // namespace llvm
+
+#endif // LLVM_OPTION_LIBRARYOPTIONS_H
diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td
index 7adb2cf6fa5de..d5fe98430b5a6 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -136,6 +136,13 @@ class Option<list<string> prefixes, string name, OptionKind kind,
code Denormalizer = "";
list<code> NormalizedValues = ?;
list<SubCommand> SubCommands = subcommands;
+ // For an option of an OptionsStruct: the member it sets, the member's type
+ // and default value, and the value a Flag assigns. Other kinds parse their
+ // argument.
+ string FieldName = ?;
+ string FieldType = ?;
+ code FieldDefault = ?;
+ code FieldValue = ?;
}
// Helpers for defining options.
@@ -271,6 +278,35 @@ class Denormalizer<code denormalizer> { code Denormalizer = denormalizer; }
class NormalizedValuesScope<code scope> { code NormalizedValuesScope = scope; }
class NormalizedValues<list<code> definitions> { list<code> NormalizedValues = definitions; }
+// Declaring a library's options as the members of a struct. A .td with an
+// OptionsStruct def generates a struct named after the def, with one member per
+// BoolField or ValueField. Every option is hidden from -help.
+
+class OptionsStruct;
+
+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>, -no-<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>, Flags<[HelpHidden]>;
+ def no_#NAME : Flag<["-", "--"], "no-"#name>, Field<NAME, "bool", default>,
+ FieldValue<"false">;
+ def NAME#_EQ : Joined<["-", "--"], name#"=">, Field<NAME, "bool", default>;
+}
+
+// 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>,
+ HelpText<help>, Flags<[HelpHidden]>;
+ def NAME : Separate<["-", "--"], name>, Alias<!cast<Option>(NAME#"_EQ")>;
+}
+
// Predefined options.
// FIXME: Have generator validate that these appear in correct position (and
diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h
index 15fbb8c56a0b0..c8917a75a291d 100644
--- a/llvm/include/llvm/Support/CommandLine.h
+++ b/llvm/include/llvm/Support/CommandLine.h
@@ -44,6 +44,7 @@ namespace llvm {
class StringSaver;
class ElementCount;
+class Error;
/// This namespace contains all of the command line option processing machinery.
/// It is intentionally a short name to make qualified usage concise.
@@ -2316,6 +2317,33 @@ LLVM_ABI void ResetCommandLineParser();
/// Parses `Arg` into the option handler `Handler`.
LLVM_ABI bool ProvidePositionalOption(Option *Handler, StringRef Arg, int i);
+/// The options of a library that declares them in TableGen rather than as
+/// cl::opt (see llvm/Option/LibraryOptions.h). ParseCommandLineOptions hands
+/// every argument naming one of them to parse().
+class LLVM_ABI LibraryOptions {
+public:
+ /// Calls \p Fn with the spelling of each option without its prefix (e.g.
+ /// "x" or "x="), its metavariable, and its help text.
+ virtual void forEachOption(
+ function_ref<void(StringRef Spelling, StringRef MetaVar, StringRef Help)>
+ Fn) const = 0;
+
+ /// Parses the option spelled by Args[0], which may take Args[1] as its
+ /// value, and sets \p Consumed to the number of arguments it spans.
+ virtual Error parse(ArrayRef<const char *> Args, unsigned &Consumed) = 0;
+
+ /// Restores the default values.
+ virtual void reset() = 0;
+
+protected:
+ // Registrations are static and never destroyed through this class.
+ ~LibraryOptions() = default;
+};
+
+/// Makes ParseCommandLineOptions recognize \p L's options. A name that is also
+/// a cl::opt or belongs to another library is a fatal error.
+LLVM_ABI void addLibraryOptions(LibraryOptions &L);
+
} // end namespace cl
} // end namespace llvm
diff --git a/llvm/lib/CGData/CGDataOptions.cpp b/llvm/lib/CGData/CGDataOptions.cpp
new file mode 100644
index 0000000000000..5154719dcb3f0
--- /dev/null
+++ b/llvm/lib/CGData/CGDataOptions.cpp
@@ -0,0 +1,15 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "CGDataOptions.h"
+#include "llvm/Option/LibraryOptions.h"
+
+#define OPTIONS_STRUCT_DEFS
+#include "CGDataOptions.inc"
+
+static llvm::opt::RegisterLibraryOptions<llvm::CGDataOptions> Registration;
diff --git a/llvm/lib/CGData/CGDataOptions.h b/llvm/lib/CGData/CGDataOptions.h
new file mode 100644
index 0000000000000..b2430ad000f3a
--- /dev/null
+++ b/llvm/lib/CGData/CGDataOptions.h
@@ -0,0 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_LIB_CGDATA_CGDATAOPTIONS_H
+#define LLVM_LIB_CGDATA_CGDATAOPTIONS_H
+
+#include <limits>
+
+#define OPTIONS_STRUCT_DECL
+#include "CGDataOptions.inc"
+
+#endif // LLVM_LIB_CGDATA_CGDATAOPTIONS_H
diff --git a/llvm/lib/CGData/CGDataOptions.td b/llvm/lib/CGData/CGDataOptions.td
new file mode 100644
index 0000000000000..b46293d43edb1
--- /dev/null
+++ b/llvm/lib/CGData/CGDataOptions.td
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+include "llvm/Option/OptParser.td"
+
+def CGDataOptions : OptionsStruct;
+
+defm CodeGenDataGenerate : BoolField<"codegen-data-generate", "false",
+ "Emit CodeGen Data into custom sections">;
+defm CodeGenDataThinLTOTwoRounds : BoolField<"codegen-data-thinlto-two-rounds",
+ "false", "Enable two-round ThinLTO code generation. The first round emits "
+ "codegen data, while the second round uses the emitted codegen data for "
+ "further optimizations.">;
+defm CodeGenDataUsePath : ValueField<"codegen-data-use-path", "std::string",
+ "", "File path to where .cgdata file is read">;
+defm GlobalMergingCallOverhead : ValueField<"global-merging-call-overhead",
+ "double", "1.0", "The overhead cost associated with each function call when "
+ "merging functions.">;
+defm GlobalMergingExtraThreshold : ValueField<"global-merging-extra-threshold",
+ "double", "0.0", "An additional cost threshold that must be exceeded for "
+ "merging to be considered beneficial.">;
+defm GlobalMergingInstOverhead : ValueField<"global-merging-inst-overhead",
+ "double", "1.2", "The overhead cost associated with each instruction when "
+ "lowering to machine instruction.">;
+defm GlobalMergingMaxParams : ValueField<"global-merging-max-params",
+ "unsigned", "std::numeric_limits<unsigned>::max()",
+ "The maximum number of parameters allowed when merging functions.">;
+defm GlobalMergingMinInstrs : ValueField<"global-merging-min-instrs",
+ "unsigned", "1",
+ "The minimum instruction count required when merging functions.">;
+defm GlobalMergingMinMerges : ValueField<"global-merging-min-merges",
+ "unsigned", "2", "Minimum number of similar functions with the same hash "
+ "required for merging.">;
+defm GlobalMergingParamOverhead : ValueField<"global-merging-param-overhead",
+ "double", "2.0", "The overhead cost associated with each parameter when "
+ "merging functions.">;
+defm GlobalMergingSkipNoParams : BoolField<"global-merging-skip-no-params",
+ "true", "Skip merging functions with no parameters.">;
+defm IndexedCodeGenDataLazyLoading : BoolField<
+ "indexed-codegen-data-lazy-loading", "false", "Lazily load indexed "
+ "CodeGenData. Enable to save memory and time for final consumption of the "
+ "indexed CodeGenData in production.">;
+defm IndexedCodeGenDataReadFunctionMapNames : BoolField<
+ "indexed-codegen-data-read-function-map-names", "true", "Read function map "
+ "names in indexed CodeGenData. Can be disabled to save memory and time for "
+ "final consumption of the indexed CodeGenData in production.">;
diff --git a/llvm/lib/CGData/CMakeLists.txt b/llvm/lib/CGData/CMakeLists.txt
index 003173139f36c..6c31d76331f6f 100644
--- a/llvm/lib/CGData/CMakeLists.txt
+++ b/llvm/lib/CGData/CMakeLists.txt
@@ -1,4 +1,9 @@
+set(LLVM_TARGET_DEFINITIONS CGDataOptions.td)
+tablegen(LLVM CGDataOptions.inc -gen-opt-parser-defs)
+add_public_tablegen_target(CGDataOptionsTableGen)
+
add_llvm_component_library(LLVMCGData
+ CGDataOptions.cpp
CodeGenData.cpp
CodeGenDataReader.cpp
CodeGenDataWriter.cpp
@@ -11,6 +16,7 @@ add_llvm_component_library(LLVMCGData
${LLVM_MAIN_INCLUDE_DIR}/llvm/CGData
DEPENDS
+ CGDataOptionsTableGen
intrinsics_gen
LINK_COMPONENTS
@@ -19,4 +25,5 @@ add_llvm_component_library(LLVMCGData
Core
Support
Object
+ Option
)
diff --git a/llvm/lib/CGData/CodeGenData.cpp b/llvm/lib/CGData/CodeGenData.cpp
index 7900dc7653c03..0b253d1504e50 100644
--- a/llvm/lib/CGData/CodeGenData.cpp
+++ b/llvm/lib/CGData/CodeGenData.cpp
@@ -11,13 +11,13 @@
//
//===----------------------------------------------------------------------===//
+#include "CGDataOptions.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/CGData/CodeGenDataReader.h"
#include "llvm/CGData/OutlinedHashTreeRecord.h"
#include "llvm/CGData/StableFunctionMapRecord.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/Caching.h"
-#include "llvm/Support/CommandLine.h"
#include "llvm/Support/WithColor.h"
#define DEBUG_TYPE "cg-data"
@@ -25,21 +25,6 @@
using namespace llvm;
using namespace cgdata;
-static cl::opt<bool>
- CodeGenDataGenerate("codegen-data-generate", cl::init(false), cl::Hidden,
- cl::desc("Emit CodeGen Data into custom sections"));
-static cl::opt<std::string>
- CodeGenDataUsePath("codegen-data-use-path", cl::init(""), cl::Hidden,
- cl::desc("File path to where .cgdata file is read"));
-
-namespace llvm {
-cl::opt<bool> CodeGenDataThinLTOTwoRounds(
- "codegen-data-thinlto-two-rounds", cl::init(false), cl::Hidden,
- cl::desc("Enable two-round ThinLTO code generation. The first round "
- "emits codegen data, while the second round uses the emitted "
- "codegen data for further optimizations."));
-} // end namespace llvm
-
static std::string getCGDataErrString(cgdata_error Err,
const std::string &ErrMsg = "") {
std::string Msg;
@@ -123,6 +108,10 @@ const char *CodeGenDataSectNamePrefix[] = {
} // namespace
+bool llvm::cgdata::thinLTOTwoRounds() {
+ return CGDataOptions::Global.CodeGenDataThinLTOTwoRounds;
+}
+
namespace llvm {
std::string getCodeGenDataSectionName(CGDataSectKind CGSK,
@@ -148,17 +137,19 @@ CodeGenData &CodeGenData::getInstance() {
std::call_once(CodeGenData::OnceFlag, []() {
Instance = std::unique_ptr<CodeGenData>(new CodeGenData());
- if (CodeGenDataGenerate || CodeGenDataThinLTOTwoRounds)
+ const CGDataOptions &Opts = CGDataOptions::Global;
+ if (Opts.CodeGenDataGenerate || Opts.CodeGenDataThinLTOTwoRounds)
Instance->EmitCGData = true;
- else if (!CodeGenDataUsePath.empty()) {
+ else if (!Opts.CodeGenDataUsePath.empty()) {
// Initialize the global CGData if the input file name is given.
// We do not error-out when failing to parse the input file.
// Instead, just emit an warning message and fall back as if no CGData
// were available.
auto FS = vfs::getRealFileSystem();
- auto ReaderOrErr = CodeGenDataReader::create(CodeGenDataUsePath, *FS);
+ auto ReaderOrErr = CodeGenDataReader::create(
+ Opts.CodeGenDataUsePath, *FS, Opts.IndexedCodeGenDataLazyLoading);
if (Error E = ReaderOrErr.takeError()) {
- warn(std::move(E), CodeGenDataUsePath);
+ warn(std::move(E), Opts.CodeGenDataUsePath);
return;
}
// Publish each CGData based on the data type in the header.
diff --git a/llvm/lib/CGData/CodeGenDataReader.cpp b/llvm/lib/CGData/CodeGenDataReader.cpp
index aeb4a4d0d4b94..64bcdc9fb5595 100644
--- a/llvm/lib/CGData/CodeGenDataReader.cpp
+++ b/llvm/lib/CGData/CodeGenDataReader.cpp
@@ -11,29 +11,17 @@
//===----------------------------------------------------------------------===//
#include "llvm/CGData/CodeGenDataReader.h"
+#include "CGDataOptions.h"
#include "llvm/CGData/OutlinedHashTreeRecord.h"
#include "llvm/Object/ObjectFile.h"
-#include "llvm/Support/CommandLine.h"
#include "llvm/Support/MemoryBuffer.h"
#define DEBUG_TYPE "cg-data-reader"
using namespace llvm;
-static cl::opt<bool> IndexedCodeGenDataReadFunctionMapNames(
- "indexed-codegen-data-read-function-map-names", cl::init(true), cl::Hidden,
- cl::desc("Read function map names in indexed CodeGenData. Can be "
- "disabled to save memory and time for final consumption of the "
- "indexed CodeGenData in production."));
-
namespace llvm {
-cl::opt<bool> IndexedCodeGenDataLazyLoading(
- "indexed-codegen-data-lazy-loading", cl::init(false), cl::Hidden,
- cl::desc(
- "Lazily load indexed CodeGenData. Enable to save memory and time "
- "for final consumption of the indexed CodeGenData in production."));
-
static Expected<std::unique_ptr<MemoryBuffer>>
setupMemoryBuffer(const Twine &Filename, vfs::FileSystem &FS) {
auto BufferOrErr = Filename.str() == "-" ? MemoryBuffer::getSTDIN()
@@ -123,8 +111,8 @@ Error IndexedCodeGenDataReader::read() {
if (Ptr >= End)
return error(cgdata_error::eof);
FunctionMapRecord.setReadStableFunctionMapNames(
- IndexedCodeGenDataReadFunctionMapNames);
- if (IndexedCodeGenDataLazyLoading)
+ CGDataOptions::Global.IndexedCodeGenDataReadFunctionMapNames);
+ if (LazyLoading)
FunctionMapRecord.lazyDeserialize(std::move(SharedDataBuffer),
Header.StableFunctionMapOffset);
else
@@ -135,23 +123,26 @@ Error IndexedCodeGenDataReader::read() {
}
Expected<std::unique_ptr<CodeGenDataReader>>
-CodeGenDataReader::create(const Twine &Path, vfs::FileSystem &FS) {
+CodeGenDataReader::create(const Twine &Path, vfs::FileSystem &FS,
+ bool LazyLoading) {
// Set up the buffer to read.
auto BufferOrError = setupMemoryBuffer(Path, FS);
if (Error E = BufferOrError.takeError())
return std::move(E);
- return CodeGenDataReader::create(std::move(BufferOrError.get()));
+ return CodeGenDataReader::create(std::move(BufferOrError.get()), LazyLoading);
}
Expected<std::unique_ptr<CodeGenDataReader>>
-CodeGenDataReader::create(std::unique_ptr<MemoryBuffer> Buffer) {
+CodeGenDataReader::create(std::unique_ptr<MemoryBuffer> Buffer,
+ bool LazyLoading) {
if (Buffer->getBufferSize() == 0)
return make_error<CGDataError>(cgdata_error::empty_cgdata);
std::unique_ptr<CodeGenDataReader> Reader;
// Create the reader.
if (IndexedCodeGenDataReader::hasFormat(*Buffer))
- Reader = std::make_unique<IndexedCodeGenDataReader>(std::move(Buffer));
+ Reader = std::make_unique<IndexedCodeGenDataReader>(std::move(Buffer),
+ LazyLoading);
else if (TextCodeGenDataReader::hasFormat(*Buffer))
Reader = std::make_unique<TextCodeGenDataReader>(std::move(Buffer));
else
diff --git a/llvm/lib/CGData/StableFunctionMap.cpp b/llvm/lib/CGData/StableFunctionMap.cpp
index d0fae3a9d32f0..2ccfe58dc913d 100644
--- a/llvm/lib/CGData/StableFunctionMap.cpp
+++ b/llvm/lib/CGData/StableFunctionMap.cpp
@@ -14,54 +14,15 @@
//===----------------------------------------------------------------------===//
#include "llvm/CGData/StableFunctionMap.h"
+#include "CGDataOptions.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/CGData/StableFunctionMapRecord.h"
-#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "stable-function-map"
using namespace llvm;
-static cl::opt<unsigned>
- GlobalMergingMinMerges("global-merging-min-merges",
- cl::desc("Minimum number of similar functions with "
- "the same hash required for merging."),
- cl::init(2), cl::Hidden);
-static cl::opt<unsigned> GlobalMergingMinInstrs(
- "global-merging-min-instrs",
- cl::desc("The minimum instruction count required when merging functions."),
- cl::init(1), cl::Hidden);
-static cl::opt<unsigned> GlobalMergingMaxParams(
- "global-merging-max-params",
- cl::desc(
- "The maximum number of parameters allowed when merging functions."),
- cl::init(std::numeric_limits<unsigned>::max()), cl::Hidden);
-static cl::opt<bool> GlobalMergingSkipNoParams(
- "global-merging-skip-no-params",
- cl::desc("Skip merging functions with no parameters."), cl::init(true),
- cl::Hidden);
-static cl::opt<double> GlobalMergingInstOverhead(
- "global-merging-inst-overhead",
- cl::desc("The overhead cost associated with each instruction when lowering "
- "to machine instruction."),
- cl::init(1.2), cl::Hidden);
-static cl::opt<double> GlobalMergingParamOverhead(
- "global-merging-param-overhead",
- cl::desc("The overhead cost associated with each parameter when merging "
- "functions."),
- cl::init(2.0), cl::Hidden);
-static cl::opt<double>
- GlobalMergingCallOverhead("global-merging-call-overhead",
- cl::desc("The overhead cost associated with each "
- "function call when merging functions."),
- cl::init(1.0), cl::Hidden);
-static cl::opt<double> GlobalMergingExtraThreshold(
- "global-merging-extra-threshold",
- cl::desc("An additional cost threshold that must be exceeded for merging "
- "to be considered beneficial."),
- cl::init(0.0), cl::Hidden);
-
unsigned StableFunctionMap::getIdOrCreateForName(StringRef Name) {
auto It = NameToId.find(Name);
if (It != NameToId.end())
@@ -201,12 +162,13 @@ removeIdenticalIndexPair(StableFunctionMap::StableFunctionEntries &SFS) {
}
static bool isProfitable(const StableFunctionMap::StableFunctionEntries &SFS) {
+ const CGDataOptions &Opts = CGDataOptions::Global;
unsigned StableFunctionCount = SFS.size();
- if (StableFunctionCount < GlobalMergingMinMerges)
+ if (StableFunctionCount < Opts.GlobalMergingMinMerges)
return false;
unsigned InstCount = SFS[0]->InstCount;
- if (InstCount < GlobalMergingMinInstrs)
+ if (InstCount < Opts.GlobalMergingMinInstrs)
return false;
double Cost = 0.0;
@@ -216,21 +178,22 @@ static bool isProfitable(const StableFunctionMap::StableFunctionEntries &SFS) {
for (auto &[IndexPair, Hash] : *SF->IndexOperandHashMap)
UniqueHashVals.insert(Hash);
unsigned ParamCount = UniqueHashVals.size();
- if (ParamCount > GlobalMergingMaxParams)
+ if (ParamCount > Opts.GlobalMergingMaxParams)
return false;
// Theoretically, if ParamCount is 0, it results in identical code folding
// (ICF), which we can skip merging here since the linker already handles
// ICF. This pass would otherwise introduce unnecessary thunks that are
// merely direct jumps. However, enabling this could be beneficial depending
// on downstream passes, so we provide an option for it.
- if (GlobalMergingSkipNoParams && ParamCount == 0)
+ if (Opts.GlobalMergingSkipNoParams && ParamCount == 0)
return false;
- Cost += ParamCount * GlobalMergingParamOverhead + GlobalMergingCallOverhead;
+ Cost += ParamCount * Opts.GlobalMergingParamOverhead +
+ Opts.GlobalMergingCallOverhead;
}
- Cost += GlobalMergingExtraThreshold;
+ Cost += Opts.GlobalMergingExtraThreshold;
double Benefit =
- InstCount * (StableFunctionCount - 1) * GlobalMergingInstOverhead;
+ InstCount * (StableFunctionCount - 1) * Opts.GlobalMergingInstOverhead;
bool Result = Benefit > Cost;
LLVM_DEBUG(dbgs() << "isProfitable: Hash = " << SFS[0]->Hash << ", "
<< "StableFunctionCount = " << StableFunctionCount
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 4594c52fb5f6e..5f6edf88ce3e4 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -108,7 +108,6 @@ static cl::opt<bool>
DumpThinCGSCCs("dump-thin-cg-sccs", cl::init(false), cl::Hidden,
cl::desc("Dump the SCCs in the ThinLTO index's callgraph"));
namespace llvm {
-extern cl::opt<bool> CodeGenDataThinLTOTwoRounds;
extern cl::opt<bool> ForceImportAll;
extern cl::opt<bool> AlwaysRenamePromotedLocals;
} // end namespace llvm
@@ -2297,7 +2296,7 @@ Error LTO::runThinLTO(AddStreamFn AddStream, FileCache Cache,
return BackendProcess->wait();
};
- if (!CodeGenDataThinLTOTwoRounds) {
+ if (!cgdata::thinLTOTwoRounds()) {
std::unique_ptr<ThinBackendProc> BackendProc =
ThinLTO.Backend(Conf, ThinLTO.CombinedIndex, ModuleToDefinedGVSummaries,
AddStream, Cache, BitcodeLibFuncs);
diff --git a/llvm/lib/Option/CMakeLists.txt b/llvm/lib/Option/CMakeLists.txt
index dc7669c521115..3106bcfa3878a 100644
--- a/llvm/lib/Option/CMakeLists.txt
+++ b/llvm/lib/Option/CMakeLists.txt
@@ -1,6 +1,7 @@
add_llvm_component_library(LLVMOption
Arg.cpp
ArgList.cpp
+ LibraryOptions.cpp
Option.cpp
OptTable.cpp
diff --git a/llvm/lib/Option/LibraryOptions.cpp b/llvm/lib/Option/LibraryOptions.cpp
new file mode 100644
index 0000000000000..62d87686e88cd
--- /dev/null
+++ b/llvm/lib/Option/LibraryOptions.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Option/LibraryOptions.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/OptTable.h"
+#include "llvm/Option/Option.h"
+#include "llvm/Support/Error.h"
+
+using namespace llvm;
+using namespace llvm::opt;
+
+LibraryOptTable::~LibraryOptTable() = default;
+
+void LibraryOptionsParser::forEachOption(
+ function_ref<void(StringRef, StringRef, StringRef)> Fn) const {
+ 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)
+ continue;
+ StringRef MetaVar = T.getOptionMetaVar(ID);
+ if (MetaVar.empty() && Kind == Option::JoinedClass)
+ MetaVar = "<value>";
+ Fn(T.getOptionName(ID), MetaVar, T.getOptionHelpText(ID));
+ }
+}
+
+Error LibraryOptionsParser::parse(ArrayRef<const char *> Args,
+ unsigned &Consumed) {
+ // An option takes at most one separate value.
+ Args = Args.take_front(2);
+ InputArgList List(Args.begin(), Args.end());
+ Consumed = 0;
+ std::unique_ptr<Arg> A = Table().ParseOneArg(List, Consumed);
+ Consumed = std::min<unsigned>(Consumed, Args.size());
+ if (!A)
+ return createStringError("option '" + Twine(Args[0]) +
+ "' requires an argument");
+ if (A->getOption().getKind() == Option::UnknownClass)
+ return createStringError("unknown argument '" + Twine(Args[0]) + "'");
+ if (!Apply(*A))
+ return createStringError("invalid value '" + Twine(A->getValue()) +
+ "' in '" + A->getAsString(List) + "'");
+ return Error::success();
+}
diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp
index 70eca48ee6635..e32ca238ee538 100644
--- a/llvm/lib/Support/CommandLine.cpp
+++ b/llvm/lib/Support/CommandLine.cpp
@@ -182,6 +182,12 @@ class CommandLineParser {
// This collects the different subcommands that have been registered.
SmallPtrSet<SubCommand *, 4> RegisteredSubCommands;
+ // Libraries whose options are declared in TableGen, and an index from their
+ // option names. Libraries[NumIndexedLibraries:] are not indexed yet.
+ SmallVector<LibraryOptions *, 0> Libraries;
+ DenseMap<StringRef, LibraryOptions *> LibraryIndex;
+ size_t NumIndexedLibraries = 0;
+
CommandLineParser() { registerSubCommand(&SubCommand::getTopLevel()); }
void ResetAllOptionOccurrences();
@@ -228,7 +234,8 @@ class CommandLineParser {
bool HadErrors = false;
if (O->hasArgStr()) {
// Add argument to the argument map!
- if (!SC->OptionsMap.insert(std::make_pair(O->ArgStr, O)).second) {
+ if (!SC->OptionsMap.insert(std::make_pair(O->ArgStr, O)).second ||
+ LibraryIndex.contains(O->ArgStr)) {
errs() << ProgramName << ": CommandLine Error: Option '" << O->ArgStr
<< "' registered more than once!\n";
HadErrors = true;
@@ -299,7 +306,7 @@ class CommandLineParser {
if (hasOptions(*S))
return true;
}
- return false;
+ return !Libraries.empty();
}
bool hasNamedSubCommands() const {
@@ -328,6 +335,34 @@ class CommandLineParser {
void printOptionValues();
+ void indexLibraryOptions() {
+ bool HadErrors = false;
+ for (; NumIndexedLibraries != Libraries.size(); ++NumIndexedLibraries) {
+ LibraryOptions *L = Libraries[NumIndexedLibraries];
+ L->forEachOption([&](StringRef Spelling, StringRef, StringRef) {
+ StringRef Name = Spelling.rtrim('=');
+ auto [It, Inserted] = LibraryIndex.try_emplace(Name, L);
+ if (Inserted ? none_of(RegisteredSubCommands,
+ [&](SubCommand *SC) {
+ return SC->OptionsMap.contains(Name);
+ })
+ : It->second == L)
+ return;
+ errs() << ProgramName << ": CommandLine Error: Option '" << Name
+ << "' registered more than once!\n";
+ HadErrors = true;
+ });
+ }
+ if (HadErrors)
+ report_fatal_error("inconsistency in registered CommandLine options");
+ }
+
+ // A library loaded while parsing (e.g. a pass plugin) may define the name.
+ LibraryOptions *lookupLibraryOption(StringRef Name) {
+ indexLibraryOptions();
+ return LibraryIndex.lookup(Name);
+ }
+
void registerCategory(OptionCategory *cat) {
assert(count_if(RegisteredOptionCategories,
[cat](const OptionCategory *Category) {
@@ -380,6 +415,9 @@ class CommandLineParser {
ResetAllOptionOccurrences();
RegisteredSubCommands.clear();
+ Libraries.clear();
+ LibraryIndex.clear();
+ NumIndexedLibraries = 0;
SubCommand::getTopLevel().reset();
SubCommand::getAll().reset();
@@ -1468,6 +1506,8 @@ void CommandLineParser::ResetAllOptionOccurrences() {
if (SC->ConsumeAfterOpt)
SC->ConsumeAfterOpt->reset();
}
+ for (LibraryOptions *L : Libraries)
+ L->reset();
}
bool CommandLineParser::ParseCommandLineOptions(
@@ -1501,6 +1541,7 @@ bool CommandLineParser::ParseCommandLineOptions(
// Copy the program name into ProgName, making sure not to overflow it.
ProgramName = std::string(sys::path::filename(StringRef(argv[0])));
+ indexLibraryOptions();
// Check out the positional arguments to collect information about them.
unsigned NumPositionalRequired = 0;
@@ -1651,6 +1692,18 @@ bool CommandLineParser::ParseCommandLineOptions(
Handler = LookupLongOption(SubCommand::getTopLevel(), ArgName, Value,
LongOptionsUseDoubleDash, HaveDoubleDash);
+ if (!Handler && (!LongOptionsUseDoubleDash || HaveDoubleDash)) {
+ if (LibraryOptions *L = lookupLibraryOption(ArgName.split('=').first)) {
+ unsigned N = 1;
+ if (Error E = L->parse(ArrayRef(argv + i, argv + argc), N)) {
+ *Errs << ProgramName << ": " << toString(std::move(E)) << '\n';
+ ErrorParsing = true;
+ }
+ i += N - 1;
+ continue;
+ }
+ }
+
// Check to see if this "option" is really a prefixed or grouped argument.
if (!Handler && !(LongOptionsUseDoubleDash && HaveDoubleDash))
Handler = HandlePrefixedOrGroupedOption(ArgName, Value, ErrorParsing,
@@ -2440,9 +2493,29 @@ class HelpPrinter {
for (const auto &Opt : Opts)
MaxArgLen = std::max(MaxArgLen, Opt.second->getOptionWidth());
+ // Library options are all hidden.
+ SmallVector<std::pair<std::string, StringRef>, 0> LibraryOpts;
+ if (ShowHidden)
+ for (LibraryOptions *L : globalParser().Libraries)
+ L->forEachOption(
+ [&](StringRef Spelling, StringRef MetaVar, StringRef Help) {
+ if (!Help.empty())
+ LibraryOpts.emplace_back((Spelling + MetaVar).str(), Help);
+ });
+ llvm::sort(LibraryOpts);
+ for (const auto &[Name, Help] : LibraryOpts)
+ MaxArgLen = std::max(MaxArgLen, argPlusPrefixesSize(Name));
+
outs() << "OPTIONS:\n";
printOptions(Opts, MaxArgLen);
+ if (!LibraryOpts.empty())
+ outs() << "\nLibrary options:\n\n";
+ for (const auto &[Name, Help] : LibraryOpts) {
+ outs() << PrintArg(Name);
+ Option::printHelpStr(Help, MaxArgLen, argPlusPrefixesSize(Name));
+ }
+
// Print any extra help the user has declared.
for (const auto &I : globalParser().MoreHelp)
outs() << I;
@@ -2867,6 +2940,11 @@ void cl::HideUnrelatedOptions(ArrayRef<const cl::OptionCategory *> Categories,
}
void cl::ResetCommandLineParser() { globalParser().reset(); }
+
+void cl::addLibraryOptions(LibraryOptions &L) {
+ globalParser().Libraries.push_back(&L);
+}
+
void cl::ResetAllOptionOccurrences() {
globalParser().ResetAllOptionOccurrences();
}
diff --git a/llvm/tools/llvm-cgdata/llvm-cgdata.cpp b/llvm/tools/llvm-cgdata/llvm-cgdata.cpp
index f06119a24e6ff..9cb7b1657aa0e 100644
--- a/llvm/tools/llvm-cgdata/llvm-cgdata.cpp
+++ b/llvm/tools/llvm-cgdata/llvm-cgdata.cpp
@@ -67,6 +67,7 @@ static std::string OutputFilename = "-";
static std::string Filename;
static bool ShowCGDataVersion;
static bool SkipTrim;
+static bool LazyLoading;
static CGDataAction Action;
static std::optional<CGDataFormat> OutputFormat;
static std::vector<std::string> InputFilenames;
@@ -107,7 +108,7 @@ static int convert_main(int argc, const char *argv[]) {
exitWithErrorCode(EC, OutputFilename);
auto FS = vfs::getRealFileSystem();
- auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS);
+ auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS, LazyLoading);
if (Error E = ReaderOrErr.takeError())
exitWithError(std::move(E), Filename);
@@ -239,7 +240,7 @@ static int show_main(int argc, const char *argv[]) {
exitWithErrorCode(EC, OutputFilename);
auto FS = vfs::getRealFileSystem();
- auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS);
+ auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS, LazyLoading);
if (Error E = ReaderOrErr.takeError())
exitWithError(std::move(E), Filename);
@@ -350,8 +351,7 @@ static void parseArgs(int argc, char **argv) {
llvm_unreachable("unrecognized action");
}
- IndexedCodeGenDataLazyLoading =
- Args.hasArg(OPT_indexed_codegen_data_lazy_loading);
+ LazyLoading = Args.hasArg(OPT_indexed_codegen_data_lazy_loading);
}
int llvm_cgdata_main(int argc, char **argvNonConst, const llvm::ToolContext &) {
diff --git a/llvm/unittests/Option/CMakeLists.txt b/llvm/unittests/Option/CMakeLists.txt
index 5fefb5e85afde..2c48a7e8ad4b3 100644
--- a/llvm/unittests/Option/CMakeLists.txt
+++ b/llvm/unittests/Option/CMakeLists.txt
@@ -3,6 +3,9 @@ set(LLVM_LINK_COMPONENTS
Support
)
+set(LLVM_TARGET_DEFINITIONS LibraryOpts.td)
+tablegen(LLVM LibraryOpts.inc -gen-opt-parser-defs)
+
set(LLVM_TARGET_DEFINITIONS Opts.td)
tablegen(LLVM Opts.inc -gen-opt-parser-defs)
@@ -12,6 +15,7 @@ tablegen(LLVM SubCommandOpts.inc -gen-opt-parser-defs)
add_public_tablegen_target(OptsTestTableGen)
add_llvm_unittest(OptionTests
+ LibraryOptionsTest.cpp
OptionParsingTest.cpp
OptionMarshallingTest.cpp
OptionSubCommandsTest.cpp
diff --git a/llvm/unittests/Option/LibraryOptionsTest.cpp b/llvm/unittests/Option/LibraryOptionsTest.cpp
new file mode 100644
index 0000000000000..6dde8550c4d8b
--- /dev/null
+++ b/llvm/unittests/Option/LibraryOptionsTest.cpp
@@ -0,0 +1,111 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#define OPTIONS_STRUCT_DECL
+#include "LibraryOpts.inc"
+
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/LibraryOptions.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#define OPTIONS_STRUCT_DEFS
+#include "LibraryOpts.inc"
+
+using namespace llvm;
+
+namespace {
+
+// The struct -gen-opt-parser-defs generates: every spelling sets its member.
+TEST(LibraryOptionsTest, Apply) {
+ TestLibraryOptions O;
+ EXPECT_FALSE(O.Enable);
+ EXPECT_EQ(O.Count, 3u);
+ EXPECT_EQ(O.Ratio, 0.5);
+ EXPECT_EQ(O.Path, "p");
+
+ auto Apply = [&](std::initializer_list<const char *> Argv) {
+ unsigned MissingIndex, MissingCount;
+ opt::InputArgList Args = TestLibraryOptions::optTable().ParseArgs(
+ Argv, MissingIndex, MissingCount);
+ std::vector<bool> Applied;
+ for (const opt::Arg *A : Args)
+ Applied.push_back(O.apply(*A));
+ return Applied;
+ };
+ EXPECT_THAT(Apply({"-lib-enable", "--lib-count=7", "-lib-ratio", "0.25",
+ "-lib-path=a=b"}),
+ testing::Each(true));
+ EXPECT_TRUE(O.Enable);
+ EXPECT_EQ(O.Count, 7u);
+ EXPECT_EQ(O.Ratio, 0.25);
+ EXPECT_EQ(O.Path, "a=b");
+ EXPECT_THAT(Apply({"-no-lib-enable"}), testing::Each(true));
+ EXPECT_FALSE(O.Enable);
+ EXPECT_THAT(Apply({"-lib-enable=1"}), testing::Each(true));
+ EXPECT_TRUE(O.Enable);
+
+ // A rejected value leaves the member unchanged.
+ EXPECT_THAT(Apply({"-lib-enable=2", "-lib-count=x", "-lib-ratio=y"}),
+ testing::Each(false));
+ EXPECT_TRUE(O.Enable);
+ EXPECT_EQ(O.Count, 7u);
+ EXPECT_EQ(O.Ratio, 0.25);
+}
+
+// What cl:: sees of the struct, without cl::.
+TEST(LibraryOptionsTest, Parser) {
+ opt::LibraryOptionsParser P(
+ TestLibraryOptions::optTable,
+ [](const opt::Arg &A) { return TestLibraryOptions::Global.apply(A); },
+ [] { TestLibraryOptions::Global = TestLibraryOptions(); });
+
+ std::vector<std::string> Rows;
+ 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||",
+ "no-lib-enable||"));
+
+ auto Parse = [&](std::initializer_list<const char *> Argv) {
+ unsigned Consumed = 0;
+ std::string Err = toString(P.parse(Argv, Consumed));
+ return std::to_string(Consumed) + " " + Err;
+ };
+ EXPECT_EQ(Parse({"-lib-count", "5", "-lib-enable"}), "2 ");
+ EXPECT_EQ(TestLibraryOptions::Global.Count, 5u);
+ EXPECT_EQ(Parse({"-lib-count=x", "-lib-enable"}),
+ "1 invalid value 'x' in '-lib-count=x'");
+ EXPECT_EQ(Parse({"-lib-count"}),
+ "1 option '-lib-count' requires an argument");
+ EXPECT_EQ(Parse({"-no-lib-enable=1"}),
+ "1 unknown argument '-no-lib-enable=1'");
+ P.reset();
+ EXPECT_EQ(TestLibraryOptions::Global.Count, 3u);
+}
+
+// A static RegisterLibraryOptions connects the struct's Global to cl::.
+TEST(LibraryOptionsTest, Register) {
+ cl::ResetCommandLineParser();
+ opt::RegisterLibraryOptions<TestLibraryOptions> Registration;
+ const TestLibraryOptions &G = TestLibraryOptions::Global;
+ const char *Args[] = {"prog", "-lib-count", "5", "-lib-enable"};
+ EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(Args), Args, "", &nulls()));
+ EXPECT_EQ(G.Count, 5u);
+ EXPECT_TRUE(G.Enable);
+ cl::ResetAllOptionOccurrences();
+ EXPECT_EQ(G.Count, 3u);
+ EXPECT_FALSE(G.Enable);
+ cl::ResetCommandLineParser();
+}
+
+} // namespace
diff --git a/llvm/unittests/Option/LibraryOpts.td b/llvm/unittests/Option/LibraryOpts.td
new file mode 100644
index 0000000000000..8efd1e3059949
--- /dev/null
+++ b/llvm/unittests/Option/LibraryOpts.td
@@ -0,0 +1,16 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+include "llvm/Option/OptParser.td"
+
+def TestLibraryOptions : OptionsStruct;
+
+defm Enable : BoolField<"lib-enable", "false", "A bool">;
+defm Count : ValueField<"lib-count", "unsigned", "3", "An unsigned">;
+defm Ratio : ValueField<"lib-ratio", "double", "0.5", "A double">;
+defm Path : ValueField<"lib-path", "std::string", "\"p\"", "A string">;
diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index 6af5ec7ccc774..ce5b2609fd2d6 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -2465,4 +2465,131 @@ TEST(CommandLineTest, HelpWithEmptyCategory) {
cl::ResetCommandLineParser();
}
+// Records the arguments it is handed. -library-int takes a separate value and
+// rejects "bad".
+struct TestLibrary final : cl::LibraryOptions {
+ std::vector<std::string> Args;
+ void forEachOption(
+ function_ref<void(StringRef, StringRef, StringRef)> Fn) const override {
+ Fn("library-flag", "", "A flag");
+ Fn("library-int", " <int>", "");
+ }
+ Error parse(ArrayRef<const char *> Argv, unsigned &Consumed) override {
+ Consumed = 1;
+ Args.push_back(Argv[0]);
+ if (StringRef(Argv[0]).ltrim('-') != "library-int")
+ return Error::success();
+ if (Argv.size() < 2)
+ return createStringError("missing value");
+ Consumed = 2;
+ Args.push_back(Argv[1]);
+ if (Args.back() == "bad")
+ return createStringError("bad value");
+ return Error::success();
+ }
+ void reset() override { Args.clear(); }
+};
+
+TEST(CommandLineTest, LibraryOptions) {
+ cl::ResetCommandLineParser();
+ TestLibrary L;
+ cl::addLibraryOptions(L);
+
+ // A library's options suffice to parse.
+ const char *Only[] = {"prog", "-library-flag"};
+ EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(Only), Only, StringRef(),
+ &llvm::nulls()));
+ EXPECT_THAT(L.Args, testing::ElementsAre("-library-flag"));
+ cl::ResetAllOptionOccurrences();
+ EXPECT_TRUE(L.Args.empty());
+
+ StackOption<int> Local("local");
+ const char *Args[] = {"prog", "--library-int", "4", "-local=3",
+ "-library-flag"};
+ EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(Args), Args, StringRef(),
+ &llvm::nulls()));
+ EXPECT_EQ(Local, 3);
+ EXPECT_THAT(L.Args,
+ testing::ElementsAre("--library-int", "4", "-library-flag"));
+ cl::ResetAllOptionOccurrences();
+
+ // A rejected value is still consumed.
+ std::string Errs;
+ raw_string_ostream OS(Errs);
+ const char *Bad[] = {"prog", "-library-int", "bad", "-local=1",
+ "-library-int"};
+ EXPECT_FALSE(
+ cl::ParseCommandLineOptions(std::size(Bad), Bad, StringRef(), &OS));
+ EXPECT_EQ(Local, 1);
+ EXPECT_EQ(Errs, "prog: bad value\nprog: missing value\n");
+ cl::ResetCommandLineParser();
+}
+
+TEST(CommandLineTest, LibraryOptionsHelp) {
+ cl::ResetCommandLineParser();
+ TestLibrary L;
+ cl::addLibraryOptions(L);
+ StackOption<int> Local("local", cl::desc("A cl::opt"));
+ const char *Args[] = {"prog"};
+ cl::ParseCommandLineOptions(std::size(Args), Args, StringRef(),
+ &llvm::nulls());
+
+ // Library options are listed only by -help-hidden, aligned with cl::'s.
+ // Options without help text are not listed.
+ std::string Hidden = interceptStdout(
+ [] { cl::PrintHelpMessage(/*Hidden=*/true, /*Categorized=*/false); });
+ auto HelpColumn = [&](StringRef Line) {
+ size_t Begin = Hidden.find(("\n" + Line).str());
+ return Begin == std::string::npos ? Begin
+ : Hidden.find(" - ", Begin) - Begin;
+ };
+ EXPECT_NE(std::string::npos,
+ Hidden.find("\nLibrary options:\n\n --library-flag "))
+ << Hidden;
+ EXPECT_NE(std::string::npos, HelpColumn(" --local="));
+ EXPECT_EQ(HelpColumn(" --local="), HelpColumn(" --library-flag "));
+ EXPECT_EQ(std::string::npos, Hidden.find("library-int"));
+ std::string Visible = interceptStdout(
+ [] { cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/false); });
+ EXPECT_EQ(std::string::npos, Visible.find("library"));
+ cl::ResetCommandLineParser();
+}
+
+// A library registered while parsing, as a plugin that an argument loads
+// does, serves the arguments after it.
+TEST(CommandLineTest, LibraryOptionsRegisteredWhileParsing) {
+ cl::ResetCommandLineParser();
+ TestLibrary L;
+ StackOption<std::string> Load("load", cl::callback([&](const std::string &) {
+ cl::addLibraryOptions(L);
+ }));
+
+ const char *Args[] = {"prog", "-load=plugin", "-library-flag"};
+ EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(Args), Args, StringRef(),
+ &llvm::nulls()));
+ EXPECT_THAT(L.Args, testing::ElementsAre("-library-flag"));
+ cl::ResetCommandLineParser();
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(CommandLineTest, LibraryOptionsConflict) {
+ cl::ResetCommandLineParser();
+ TestLibrary L;
+ cl::addLibraryOptions(L);
+ const char *Args[] = {"prog"};
+ // A cl::opt registered before the library is indexed.
+ EXPECT_DEATH(
+ {
+ StackOption<int> Clash("library-int");
+ cl::ParseCommandLineOptions(std::size(Args), Args);
+ },
+ "Option 'library-int' registered more than once");
+ // A cl::opt registered after.
+ cl::ParseCommandLineOptions(std::size(Args), Args);
+ EXPECT_DEATH(StackOption<int>("library-flag"),
+ "Option 'library-flag' registered more than once");
+ cl::ResetCommandLineParser();
+}
+#endif
+
} // anonymous namespace
diff --git a/llvm/utils/TableGen/OptionParserEmitter.cpp b/llvm/utils/TableGen/OptionParserEmitter.cpp
index 2360d55267625..8e55446b52110 100644
--- a/llvm/utils/TableGen/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/OptionParserEmitter.cpp
@@ -11,6 +11,7 @@
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Support/raw_ostream.h"
@@ -228,6 +229,108 @@ static MarshallingInfo createMarshallingInfo(const Record &R) {
return Ret;
}
+// 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.
+static void emitOptionsStruct(const Record &Struct,
+ ArrayRef<const Record *> Groups,
+ ArrayRef<const Record *> Opts, raw_ostream &OS) {
+ struct Member {
+ StringRef Name, Type, Default, HelpText;
+ };
+ std::vector<const Record *> Fields;
+ for (const Record *R : Opts) {
+ StringRef Kind = R->getValueAsDef("Kind")->getValueAsString("Name");
+ if (isa<UnsetInit>(R->getValueInit("FieldName"))) {
+ if (Kind != "Input" && Kind != "Unknown" &&
+ isa<UnsetInit>(R->getValueInit("Alias")))
+ PrintFatalError(R->getLoc(), "an option of an OptionsStruct must be "
+ "declared with BoolField or ValueField");
+ continue;
+ }
+ bool HasValue = !isa<UnsetInit>(R->getValueInit("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");
+ Fields.push_back(R);
+ }
+ // Members in declaration order.
+ std::vector<const Record *> ByID = Fields;
+ llvm::sort(ByID, [](const Record *A, const Record *B) {
+ return A->getID() < B->getID();
+ });
+ std::vector<Member> Members;
+ StringMap<unsigned> MemberIndex;
+ for (const Record *R : ByID) {
+ Member M{R->getValueAsString("FieldName"), R->getValueAsString("FieldType"),
+ R->getValueAsString("FieldDefault"),
+ getOptionalString(*R, "HelpText")};
+ auto [It, Inserted] = MemberIndex.try_emplace(M.Name, Members.size());
+ if (Inserted) {
+ Members.push_back(M);
+ continue;
+ }
+ Member &Prev = Members[It->second];
+ if (Prev.Type != M.Type || Prev.Default != M.Default)
+ PrintFatalError(R->getLoc(), "member '" + M.Name +
+ "' is declared with a different type "
+ "or default");
+ if (Prev.HelpText.empty())
+ Prev.HelpText = M.HelpText;
+ }
+
+ StringRef Name = Struct.getName();
+ OS << "\n#ifdef OPTIONS_STRUCT_DECL\n#undef OPTIONS_STRUCT_DECL\n";
+ OS << "#include <string>\n\n";
+ OS << "namespace llvm {\nnamespace opt {\nclass Arg;\n"
+ "class OptTable;\n} // namespace opt\n\n";
+ OS << "struct " << Name << " {\n";
+ for (const Member &M : Members) {
+ if (!M.HelpText.empty()) {
+ std::string Help = M.HelpText.str();
+ llvm::replace(Help, '\n', ' ');
+ OS << " /// " << Help << "\n";
+ }
+ OS << " " << M.Type << " " << M.Name << "{" << M.Default << "};\n";
+ }
+ OS << "\n /// The instance cl::ParseCommandLineOptions sets.\n";
+ OS << " static " << Name << " Global;\n\n";
+ OS << " static const opt::OptTable &optTable();\n";
+ OS << " /// Sets the member that \\p A names. Returns false if the value is "
+ "invalid.\n";
+ OS << " bool apply(const opt::Arg &A);\n";
+ OS << "};\n} // namespace llvm\n";
+ OS << "#endif // OPTIONS_STRUCT_DECL\n";
+
+ std::string Qualified = ("llvm::" + 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_" << getOptionName(*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";
+ OS << "bool " << Qualified << "::apply(const llvm::opt::Arg &A) {\n";
+ OS << " switch (A.getOption().getID()) {\n";
+ for (const Record *R : Fields) {
+ OS << " case OPT_" << getOptionName(*R) << ":\n";
+ StringRef Member = R->getValueAsString("FieldName");
+ if (isa<UnsetInit>(R->getValueInit("FieldValue")))
+ OS << " return llvm::opt::parseArgValue(A.getValue(), " << Member
+ << ");\n";
+ else
+ OS << " " << Member << " = " << R->getValueAsString("FieldValue")
+ << ";\n return true;\n";
+ }
+ OS << " }\n llvm_unreachable(\"option without a member\");\n}\n";
+ OS << "#endif // OPTIONS_STRUCT_DEFS\n";
+}
+
/// OptionParserEmitter - This tablegen backend takes an input .td file
/// describing a list of options and emits a data structure for parsing and
/// working with those options when given an input command line.
@@ -241,6 +344,11 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
std::vector<const Record *> SubCommands =
Records.getAllDerivedDefinitions("SubCommand");
+ ArrayRef<const Record *> Structs =
+ Records.getAllDerivedDefinitions("OptionsStruct");
+ if (Structs.size() > 1)
+ PrintFatalError(Structs[1]->getLoc(), "only one OptionsStruct is allowed");
+
emitSourceFileHeader("Option Parsing Definitions", OS);
// Generate prefix groups.
@@ -324,9 +432,17 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
OS << "/////////\n";
OS << "// Tables\n\n";
- OS << "#ifdef OPTTABLE_CODE\n";
+ // An OptionsStruct's definitions use the tables.
+ OS << (Structs.empty()
+ ? "#ifdef OPTTABLE_CODE\n"
+ : "#if defined(OPTTABLE_CODE) || defined(OPTIONS_STRUCT_DEFS)\n");
// 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.
+ if (!Structs.empty())
+ OS << " using llvm::opt::DefaultVis, llvm::opt::HelpHidden,\n"
+ " llvm::opt::RenderAsInput, llvm::opt::RenderJoined,\n"
+ " llvm::opt::RenderSeparate;\n";
Table.EmitStringTableDef(OS, "OptionStrTable");
OS << "\n";
@@ -484,7 +600,12 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
<< ", OptionSubCommandIDsTable};\n";
OS << "}\n";
OS << "#undef OPTTABLE_CODE\n";
- OS << "#endif // OPTTABLE_CODE\n\n";
+ OS << (Structs.empty()
+ ? "#endif // OPTTABLE_CODE\n\n"
+ : "#endif // OPTTABLE_CODE || OPTIONS_STRUCT_DEFS\n\n");
+
+ if (!Structs.empty())
+ emitOptionsStruct(*Structs[0], Groups, Opts, OS);
// Dump ValuesCode.
OS << "/////////\n";
diff --git a/llvm/utils/gn/secondary/llvm/lib/CGData/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/CGData/BUILD.gn
index 9947c68a319d8..cf2b6997540bf 100644
--- a/llvm/utils/gn/secondary/llvm/lib/CGData/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/CGData/BUILD.gn
@@ -1,10 +1,20 @@
+import("//llvm/utils/TableGen/tablegen.gni")
+
+tablegen("CGDataOptions") {
+ visibility = [ ":CGData" ]
+ args = [ "-gen-opt-parser-defs" ]
+}
+
static_library("CGData") {
output_name = "LLVMCGData"
deps = [
+ ":CGDataOptions",
"//llvm/lib/IR",
+ "//llvm/lib/Option",
"//llvm/lib/Support",
]
sources = [
+ "CGDataOptions.cpp",
"CodeGenData.cpp",
"CodeGenDataReader.cpp",
"CodeGenDataWriter.cpp",
diff --git a/llvm/utils/gn/secondary/llvm/lib/Option/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/Option/BUILD.gn
index 2d1cf272865d0..b193227d160e5 100644
--- a/llvm/utils/gn/secondary/llvm/lib/Option/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/Option/BUILD.gn
@@ -4,6 +4,7 @@ static_library("Option") {
sources = [
"Arg.cpp",
"ArgList.cpp",
+ "LibraryOptions.cpp",
"OptTable.cpp",
"Option.cpp",
]
diff --git a/llvm/utils/gn/secondary/llvm/unittests/Option/BUILD.gn b/llvm/utils/gn/secondary/llvm/unittests/Option/BUILD.gn
index 759fd6e40fa9f..4694b886c50c7 100644
--- a/llvm/utils/gn/secondary/llvm/unittests/Option/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/unittests/Option/BUILD.gn
@@ -1,6 +1,11 @@
import("//llvm/utils/TableGen/tablegen.gni")
import("//third-party/unittest/unittest.gni")
+tablegen("LibraryOpts") {
+ visibility = [ ":OptionTests" ]
+ args = [ "-gen-opt-parser-defs" ]
+}
+
tablegen("Opts") {
visibility = [ ":OptionTests" ]
args = [ "-gen-opt-parser-defs" ]
@@ -13,12 +18,14 @@ tablegen("SubCommandOpts") {
unittest("OptionTests") {
deps = [
+ ":LibraryOpts",
":Opts",
":SubCommandOpts",
"//llvm/lib/Option",
"//llvm/lib/Support",
]
sources = [
+ "LibraryOptionsTest.cpp",
"OptionMarshallingTest.cpp",
"OptionParsingTest.cpp",
"OptionSubCommandsTest.cpp",
diff --git a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
index b683f86ca3863..2680e426957a8 100644
--- a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
@@ -2361,9 +2361,21 @@ cc_library(
],
)
+gentbl_cc_library(
+ name = "CGDataOptionsTableGen",
+ strip_include_prefix = "lib/CGData",
+ tbl_outs = {"lib/CGData/CGDataOptions.inc": ["-gen-opt-parser-defs"]},
+ tblgen = ":llvm-tblgen",
+ td_file = "lib/CGData/CGDataOptions.td",
+ deps = [":OptParserTdFiles"],
+)
+
cc_library(
name = "CGData",
- srcs = glob(["lib/CGData/**/*.cpp"]),
+ srcs = glob([
+ "lib/CGData/**/*.cpp",
+ "lib/CGData/**/*.h",
+ ]),
hdrs = glob([
"include/llvm/CGData/**/*.h",
"include/llvm/CGData/**/*.inc",
@@ -2372,9 +2384,11 @@ cc_library(
deps = [
":BitReader",
":BitWriter",
+ ":CGDataOptionsTableGen",
":Core",
":Object",
":ObjectYAML",
+ ":Option",
":Support",
":TargetParser",
],
diff --git a/utils/bazel/llvm-project-overlay/llvm/unittests/BUILD.bazel b/utils/bazel/llvm-project-overlay/llvm/unittests/BUILD.bazel
index 90a6760e9fb74..4ab839839856c 100644
--- a/utils/bazel/llvm-project-overlay/llvm/unittests/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/llvm/unittests/BUILD.bazel
@@ -644,6 +644,15 @@ cc_test(
],
)
+gentbl_cc_library(
+ name = "library_opts_tests_gen",
+ strip_include_prefix = "Option",
+ tbl_outs = {"Option/LibraryOpts.inc": ["-gen-opt-parser-defs"]},
+ tblgen = "//llvm:llvm-tblgen",
+ td_file = "Option/LibraryOpts.td",
+ deps = ["//llvm:OptParserTdFiles"],
+)
+
gentbl_cc_library(
name = "option_tests_gen",
strip_include_prefix = "Option",
@@ -682,10 +691,12 @@ cc_test(
allow_empty = False,
),
deps = [
+ ":library_opts_tests_gen",
":option_tests_gen",
":sub_command_opts_tests_gen",
"//llvm:Option",
"//llvm:Support",
+ "//third-party/unittest:gmock",
"//third-party/unittest:gtest",
"//third-party/unittest:gtest_main",
],
>From 3aafe92567928b0779bb04aebfff0705dec1fecb Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 26 Sep 2026 10:45:17 -0700
Subject: [PATCH 2/6] rebase, derive snake_case record names from dashed names
---
llvm/docs/CommandLine.md | 24 +++----
llvm/include/llvm/Option/LibraryOptions.h | 4 +-
llvm/include/llvm/Option/OptParser.td | 24 ++++---
llvm/include/llvm/Support/CommandLine.h | 10 +--
llvm/lib/CGData/CGDataOptions.td | 66 ++++++++++----------
llvm/lib/CGData/CodeGenData.cpp | 13 ++--
llvm/lib/CGData/CodeGenDataReader.cpp | 2 +-
llvm/lib/CGData/StableFunctionMap.cpp | 16 ++---
llvm/lib/Option/LibraryOptions.cpp | 5 +-
llvm/lib/Support/CommandLine.cpp | 18 +++---
llvm/unittests/Option/LibraryOptionsTest.cpp | 53 ++++++++--------
llvm/unittests/Option/LibraryOpts.td | 9 +--
llvm/unittests/Support/CommandLineTest.cpp | 18 +++---
llvm/utils/TableGen/OptionParserEmitter.cpp | 65 ++++++++++++++-----
14 files changed, 188 insertions(+), 139 deletions(-)
diff --git a/llvm/docs/CommandLine.md b/llvm/docs/CommandLine.md
index 65b7e42b730b4..b4fa6ea01b317 100644
--- a/llvm/docs/CommandLine.md
+++ b/llvm/docs/CommandLine.md
@@ -1576,24 +1576,26 @@ per option, the table that parses them, and the hooks through which
include "llvm/Option/OptParser.td"
def FooOptions : OptionsStruct;
-// The spellings of FooMode, a C++ enumeration declared elsewhere.
-def FooMode : OptionEnum<"FooMode", [EnumMember<"Fast", "fast">,
- EnumMember<"Small", "small">]>;
-defm Enable : BoolField<"foo-enable", "1", "Enable foo">;
-defm Threshold : ValueField<"foo-threshold", "unsigned", "8", "The threshold">;
-defm Mode : EnumField<"foo-mode", FooMode, "FooMode::Fast", "Foo's mode">;
+defm : BoolField<"foo-enable", "1", "Enable foo">;
+defm : ValueField<"foo-threshold", "unsigned", "8", "The threshold">;
+let Hidden = 0 in
+defm : ValueField<"foo-path", "StringRef", "", "The input path">;
```
-The `defm` name is the member name. A `BoolField` is set by `-x`, `-no-x`, or
-`-x=true|false|1|0`; a `ValueField` of an integer type, `double`,
-or `StringRef` by `-x=value` or `-x value`. Both accept `--` for `-`.
+The struct is in namespace `llvm` unless the def names another, as in
+`OptionsStruct<"mlir">`. A member is named after its option, `foo_enable` for
+`-foo-enable`, unless the `defm` names it. A `BoolField` is set by `-x` or
+`-x=true|false|1|0`; a `ValueField` of an integer type, `double`, or
+`StringRef` by `-x=value` or `-x value`. Both accept `--` for `-`. Only
+`-help-hidden` lists the options, like `cl::Hidden`; those declared in
+`let Hidden = 0 in` are also listed by `-help`.
The header declares the struct after including what the member defaults need,
and one source file defines it and registers it with `cl::`.
-The library then lists `XXOptionsTableGen` under `DEPENDS` and `Option`
-under `LINK_COMPONENTS`. Code reads `XXOptions::Global.CodeGenDataGenerate`,
+The library then lists `FooOptionsTableGen` under `DEPENDS` and `Option`
+under `LINK_COMPONENTS`. Code reads `FooOptions::Global.foo_enable`,
the instance the command line sets. Keep the header in `lib/`, as private as the
`static cl::opt` it replaces; another library that needs a value calls a
function or takes a parameter.
diff --git a/llvm/include/llvm/Option/LibraryOptions.h b/llvm/include/llvm/Option/LibraryOptions.h
index 7d9bd4f823963..74b2b19bc8601 100644
--- a/llvm/include/llvm/Option/LibraryOptions.h
+++ b/llvm/include/llvm/Option/LibraryOptions.h
@@ -66,8 +66,8 @@ class LLVM_ABI LibraryOptionsParser final : public cl::LibraryOptions {
LibraryOptionsParser(TableFn Table, ApplyFn Apply, void (*Reset)())
: Table(Table), Apply(Apply), Reset(Reset) {}
- void forEachOption(
- function_ref<void(StringRef, StringRef, StringRef)> Fn) const override;
+ void forEachOption(function_ref<void(StringRef, StringRef, StringRef, bool)>
+ Fn) const override;
Error parse(ArrayRef<const char *> Args, unsigned &Consumed) override;
void reset() override { Reset(); }
diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td
index d5fe98430b5a6..42d2ffc7d07d9 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -138,11 +138,12 @@ class Option<list<string> prefixes, string name, OptionKind kind,
list<SubCommand> SubCommands = subcommands;
// For an option of an OptionsStruct: the member it sets, the member's type
// and default value, and the value a Flag assigns. Other kinds parse their
- // argument.
+ // argument. Hidden adds HelpHidden.
string FieldName = ?;
string FieldType = ?;
code FieldDefault = ?;
code FieldValue = ?;
+ bit Hidden = 1;
}
// Helpers for defining options.
@@ -279,10 +280,13 @@ class NormalizedValuesScope<code scope> { code NormalizedValuesScope = scope; }
class NormalizedValues<list<code> definitions> { list<code> NormalizedValues = definitions; }
// Declaring a library's options as the members of a struct. A .td with an
-// OptionsStruct def generates a struct named after the def, with one member per
-// BoolField or ValueField. Every option is hidden from -help.
+// OptionsStruct def generates a struct named after the def in \p namespace,
+// with one member per BoolField or ValueField.
+// `defm : BoolField<"foo-bar", ...>` declares member foo_bar, and
+// `defm Name : ...` names it. Only -help-hidden lists the options, except those
+// declared in `let Hidden = 0 in { ... }`.
-class OptionsStruct;
+class OptionsStruct<string namespace = "llvm"> { string Namespace = namespace; }
class Field<string name, string type, code default> {
string FieldName = name;
@@ -291,19 +295,21 @@ class Field<string name, string type, code default> {
}
class FieldValue<code value> { code FieldValue = value; }
-// A bool member set by -<name>, -no-<name>, or -<name>=<true|false|1|0>.
+// 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>, Flags<[HelpHidden]>;
- def no_#NAME : Flag<["-", "--"], "no-"#name>, Field<NAME, "bool", default>,
- FieldValue<"false">;
+ FieldValue<"true">, HelpText<help> {
+ let Flags = !if(Hidden, [HelpHidden], []);
+ }
def NAME#_EQ : Joined<["-", "--"], name#"=">, Field<NAME, "bool", default>;
}
// 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>,
- HelpText<help>, Flags<[HelpHidden]>;
+ HelpText<help> {
+ let Flags = !if(Hidden, [HelpHidden], []);
+ }
def NAME : Separate<["-", "--"], name>, Alias<!cast<Option>(NAME#"_EQ")>;
}
diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h
index 9ce001233d4aa..06c11be3e9b4d 100644
--- a/llvm/include/llvm/Support/CommandLine.h
+++ b/llvm/include/llvm/Support/CommandLine.h
@@ -2169,10 +2169,12 @@ LLVM_ABI bool ProvidePositionalOption(Option *Handler, StringRef Arg, int i);
class LLVM_ABI LibraryOptions {
public:
/// Calls \p Fn with the spelling of each option without its prefix (e.g.
- /// "x" or "x="), its metavariable, and its help text.
- virtual void forEachOption(
- function_ref<void(StringRef Spelling, StringRef MetaVar, StringRef Help)>
- Fn) const = 0;
+ /// "x" or "x="), its metavariable, its help text, and whether only
+ /// -help-hidden lists it.
+ virtual void
+ forEachOption(function_ref<void(StringRef Spelling, StringRef MetaVar,
+ StringRef Help, bool Hidden)>
+ Fn) const = 0;
/// Parses the option spelled by Args[0], which may take Args[1] as its
/// value, and sets \p Consumed to the number of arguments it spans. The
diff --git a/llvm/lib/CGData/CGDataOptions.td b/llvm/lib/CGData/CGDataOptions.td
index c5a9ccfb922a5..cfc8c3d86c956 100644
--- a/llvm/lib/CGData/CGDataOptions.td
+++ b/llvm/lib/CGData/CGDataOptions.td
@@ -10,42 +10,40 @@ include "llvm/Option/OptParser.td"
def CGDataOptions : OptionsStruct;
-defm CodeGenDataGenerate : BoolField<"codegen-data-generate", "false",
+defm : BoolField<"codegen-data-generate", "0",
"Emit CodeGen Data into custom sections">;
-defm CodeGenDataThinLTOTwoRounds : BoolField<"codegen-data-thinlto-two-rounds",
- "false", "Enable two-round ThinLTO code generation. The first round emits "
+defm : BoolField<"codegen-data-thinlto-two-rounds", "0",
+ "Enable two-round ThinLTO code generation. The first round emits "
"codegen data, while the second round uses the emitted codegen data for "
"further optimizations.">;
-defm CodeGenDataUsePath : ValueField<"codegen-data-use-path", "StringRef",
- "", "File path to where .cgdata file is read">;
-defm GlobalMergingCallOverhead : ValueField<"global-merging-call-overhead",
- "double", "1.0", "The overhead cost associated with each function call when "
- "merging functions.">;
-defm GlobalMergingExtraThreshold : ValueField<"global-merging-extra-threshold",
- "double", "0.0", "An additional cost threshold that must be exceeded for "
- "merging to be considered beneficial.">;
-defm GlobalMergingInstOverhead : ValueField<"global-merging-inst-overhead",
- "double", "1.2", "The overhead cost associated with each instruction when "
- "lowering to machine instruction.">;
-defm GlobalMergingMaxParams : ValueField<"global-merging-max-params",
- "unsigned", "std::numeric_limits<unsigned>::max()",
+defm : ValueField<"codegen-data-use-path", "StringRef", "",
+ "File path to where .cgdata file is read">;
+defm : ValueField<"global-merging-call-overhead", "double", "1.0",
+ "The overhead cost associated with each function call when merging "
+ "functions.">;
+defm : ValueField<"global-merging-extra-threshold", "double", "0.0",
+ "An additional cost threshold that must be exceeded for merging to be "
+ "considered beneficial.">;
+defm : ValueField<"global-merging-inst-overhead", "double", "1.2",
+ "The overhead cost associated with each instruction when lowering to "
+ "machine instruction.">;
+defm : ValueField<"global-merging-max-params", "unsigned",
+ "std::numeric_limits<unsigned>::max()",
"The maximum number of parameters allowed when merging functions.">;
-defm GlobalMergingMinInstrs : ValueField<"global-merging-min-instrs",
- "unsigned", "1",
+defm : ValueField<"global-merging-min-instrs", "unsigned", "1",
"The minimum instruction count required when merging functions.">;
-defm GlobalMergingMinMerges : ValueField<"global-merging-min-merges",
- "unsigned", "2", "Minimum number of similar functions with the same hash "
- "required for merging.">;
-defm GlobalMergingParamOverhead : ValueField<"global-merging-param-overhead",
- "double", "2.0", "The overhead cost associated with each parameter when "
- "merging functions.">;
-defm GlobalMergingSkipNoParams : BoolField<"global-merging-skip-no-params",
- "true", "Skip merging functions with no parameters.">;
-defm IndexedCodeGenDataLazyLoading : BoolField<
- "indexed-codegen-data-lazy-loading", "false", "Lazily load indexed "
- "CodeGenData. Enable to save memory and time for final consumption of the "
- "indexed CodeGenData in production.">;
-defm IndexedCodeGenDataReadFunctionMapNames : BoolField<
- "indexed-codegen-data-read-function-map-names", "true", "Read function map "
- "names in indexed CodeGenData. Can be disabled to save memory and time for "
- "final consumption of the indexed CodeGenData in production.">;
+defm : ValueField<"global-merging-min-merges", "unsigned", "2",
+ "Minimum number of similar functions with the same hash required for "
+ "merging.">;
+defm : ValueField<"global-merging-param-overhead", "double", "2.0",
+ "The overhead cost associated with each parameter when merging "
+ "functions.">;
+defm : BoolField<"global-merging-skip-no-params", "1",
+ "Skip merging functions with no parameters.">;
+defm : BoolField<"indexed-codegen-data-lazy-loading", "0",
+ "Lazily load indexed CodeGenData. Enable to save memory and time for final "
+ "consumption of the indexed CodeGenData in production.">;
+defm : BoolField<"indexed-codegen-data-read-function-map-names", "1",
+ "Read function map names in indexed CodeGenData. Can be disabled to save "
+ "memory and time for final consumption of the indexed CodeGenData in "
+ "production.">;
diff --git a/llvm/lib/CGData/CodeGenData.cpp b/llvm/lib/CGData/CodeGenData.cpp
index 0b253d1504e50..d27ac5a589fea 100644
--- a/llvm/lib/CGData/CodeGenData.cpp
+++ b/llvm/lib/CGData/CodeGenData.cpp
@@ -109,7 +109,7 @@ const char *CodeGenDataSectNamePrefix[] = {
} // namespace
bool llvm::cgdata::thinLTOTwoRounds() {
- return CGDataOptions::Global.CodeGenDataThinLTOTwoRounds;
+ return CGDataOptions::Global.codegen_data_thinlto_two_rounds;
}
namespace llvm {
@@ -138,18 +138,19 @@ CodeGenData &CodeGenData::getInstance() {
Instance = std::unique_ptr<CodeGenData>(new CodeGenData());
const CGDataOptions &Opts = CGDataOptions::Global;
- if (Opts.CodeGenDataGenerate || Opts.CodeGenDataThinLTOTwoRounds)
+ if (Opts.codegen_data_generate || Opts.codegen_data_thinlto_two_rounds)
Instance->EmitCGData = true;
- else if (!Opts.CodeGenDataUsePath.empty()) {
+ else if (!Opts.codegen_data_use_path.empty()) {
// Initialize the global CGData if the input file name is given.
// We do not error-out when failing to parse the input file.
// Instead, just emit an warning message and fall back as if no CGData
// were available.
auto FS = vfs::getRealFileSystem();
- auto ReaderOrErr = CodeGenDataReader::create(
- Opts.CodeGenDataUsePath, *FS, Opts.IndexedCodeGenDataLazyLoading);
+ auto ReaderOrErr =
+ CodeGenDataReader::create(Opts.codegen_data_use_path, *FS,
+ Opts.indexed_codegen_data_lazy_loading);
if (Error E = ReaderOrErr.takeError()) {
- warn(std::move(E), Opts.CodeGenDataUsePath);
+ warn(std::move(E), Opts.codegen_data_use_path);
return;
}
// Publish each CGData based on the data type in the header.
diff --git a/llvm/lib/CGData/CodeGenDataReader.cpp b/llvm/lib/CGData/CodeGenDataReader.cpp
index 64bcdc9fb5595..21b3221e730ff 100644
--- a/llvm/lib/CGData/CodeGenDataReader.cpp
+++ b/llvm/lib/CGData/CodeGenDataReader.cpp
@@ -111,7 +111,7 @@ Error IndexedCodeGenDataReader::read() {
if (Ptr >= End)
return error(cgdata_error::eof);
FunctionMapRecord.setReadStableFunctionMapNames(
- CGDataOptions::Global.IndexedCodeGenDataReadFunctionMapNames);
+ CGDataOptions::Global.indexed_codegen_data_read_function_map_names);
if (LazyLoading)
FunctionMapRecord.lazyDeserialize(std::move(SharedDataBuffer),
Header.StableFunctionMapOffset);
diff --git a/llvm/lib/CGData/StableFunctionMap.cpp b/llvm/lib/CGData/StableFunctionMap.cpp
index 2ccfe58dc913d..b8b8cfd1286ae 100644
--- a/llvm/lib/CGData/StableFunctionMap.cpp
+++ b/llvm/lib/CGData/StableFunctionMap.cpp
@@ -164,11 +164,11 @@ removeIdenticalIndexPair(StableFunctionMap::StableFunctionEntries &SFS) {
static bool isProfitable(const StableFunctionMap::StableFunctionEntries &SFS) {
const CGDataOptions &Opts = CGDataOptions::Global;
unsigned StableFunctionCount = SFS.size();
- if (StableFunctionCount < Opts.GlobalMergingMinMerges)
+ if (StableFunctionCount < Opts.global_merging_min_merges)
return false;
unsigned InstCount = SFS[0]->InstCount;
- if (InstCount < Opts.GlobalMergingMinInstrs)
+ if (InstCount < Opts.global_merging_min_instrs)
return false;
double Cost = 0.0;
@@ -178,22 +178,22 @@ static bool isProfitable(const StableFunctionMap::StableFunctionEntries &SFS) {
for (auto &[IndexPair, Hash] : *SF->IndexOperandHashMap)
UniqueHashVals.insert(Hash);
unsigned ParamCount = UniqueHashVals.size();
- if (ParamCount > Opts.GlobalMergingMaxParams)
+ if (ParamCount > Opts.global_merging_max_params)
return false;
// Theoretically, if ParamCount is 0, it results in identical code folding
// (ICF), which we can skip merging here since the linker already handles
// ICF. This pass would otherwise introduce unnecessary thunks that are
// merely direct jumps. However, enabling this could be beneficial depending
// on downstream passes, so we provide an option for it.
- if (Opts.GlobalMergingSkipNoParams && ParamCount == 0)
+ if (Opts.global_merging_skip_no_params && ParamCount == 0)
return false;
- Cost += ParamCount * Opts.GlobalMergingParamOverhead +
- Opts.GlobalMergingCallOverhead;
+ Cost += ParamCount * Opts.global_merging_param_overhead +
+ Opts.global_merging_call_overhead;
}
- Cost += Opts.GlobalMergingExtraThreshold;
+ Cost += Opts.global_merging_extra_threshold;
double Benefit =
- InstCount * (StableFunctionCount - 1) * Opts.GlobalMergingInstOverhead;
+ InstCount * (StableFunctionCount - 1) * Opts.global_merging_inst_overhead;
bool Result = Benefit > Cost;
LLVM_DEBUG(dbgs() << "isProfitable: Hash = " << SFS[0]->Hash << ", "
<< "StableFunctionCount = " << StableFunctionCount
diff --git a/llvm/lib/Option/LibraryOptions.cpp b/llvm/lib/Option/LibraryOptions.cpp
index 2b45d1ae7a2a1..5d050f0d8eb22 100644
--- a/llvm/lib/Option/LibraryOptions.cpp
+++ b/llvm/lib/Option/LibraryOptions.cpp
@@ -18,7 +18,7 @@ using namespace llvm::opt;
LibraryOptTable::~LibraryOptTable() = default;
void LibraryOptionsParser::forEachOption(
- function_ref<void(StringRef, StringRef, StringRef)> Fn) const {
+ function_ref<void(StringRef, StringRef, StringRef, bool)> Fn) const {
const OptTable &T = Table();
for (unsigned ID = 1, E = T.getNumOptions(); ID <= E; ++ID) {
unsigned Kind = T.getOptionKind(ID);
@@ -28,7 +28,8 @@ void LibraryOptionsParser::forEachOption(
StringRef MetaVar = T.getOptionMetaVar(ID);
if (MetaVar.empty() && Kind == Option::JoinedClass)
MetaVar = "<value>";
- Fn(T.getOptionName(ID), MetaVar, T.getOptionHelpText(ID));
+ Fn(T.getOptionName(ID), MetaVar, T.getOptionHelpText(ID),
+ T.getOption(ID).hasFlag(HelpHidden));
}
}
diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp
index 401c86787712a..74fc1ca42cd9b 100644
--- a/llvm/lib/Support/CommandLine.cpp
+++ b/llvm/lib/Support/CommandLine.cpp
@@ -332,7 +332,7 @@ class CommandLineParser {
bool HadErrors = false;
for (; NumIndexedLibraries != Libraries.size(); ++NumIndexedLibraries) {
LibraryOptions *L = Libraries[NumIndexedLibraries];
- L->forEachOption([&](StringRef Spelling, StringRef, StringRef) {
+ L->forEachOption([&](StringRef Spelling, StringRef, StringRef, bool) {
StringRef Name = Spelling.rtrim('=');
auto [It, Inserted] = LibraryIndex.try_emplace(Name, L);
if (Inserted ? none_of(RegisteredSubCommands,
@@ -1624,7 +1624,7 @@ bool CommandLineParser::ParseCommandLineOptions(int argc,
if (!Handler && ChosenSubCommand != &SubCommand::getTopLevel())
Handler = LookupOption(SubCommand::getTopLevel(), ArgName, Value);
- if (!Handler && (!LongOptionsUseDoubleDash || HaveDoubleDash)) {
+ if (!Handler) {
if (LibraryOptions *L = lookupLibraryOption(ArgName.split('=').first)) {
// An option takes at most one separate value.
StringSaver Saver(LibraryArgAlloc);
@@ -2430,15 +2430,13 @@ class HelpPrinter {
for (const auto &Opt : Opts)
MaxArgLen = std::max(MaxArgLen, Opt.second->getOptionWidth());
- // Library options are all hidden.
SmallVector<std::pair<std::string, StringRef>, 0> LibraryOpts;
- if (ShowHidden)
- for (LibraryOptions *L : globalParser().Libraries)
- L->forEachOption(
- [&](StringRef Spelling, StringRef MetaVar, StringRef Help) {
- if (!Help.empty())
- LibraryOpts.emplace_back((Spelling + MetaVar).str(), Help);
- });
+ for (LibraryOptions *L : globalParser().Libraries)
+ L->forEachOption([&](StringRef Spelling, StringRef MetaVar,
+ StringRef Help, bool Hidden) {
+ if (!Help.empty() && (ShowHidden || !Hidden))
+ LibraryOpts.emplace_back((Spelling + MetaVar).str(), Help);
+ });
llvm::sort(LibraryOpts);
for (const auto &[Name, Help] : LibraryOpts)
MaxArgLen = std::max(MaxArgLen, argPlusPrefixesSize(Name));
diff --git a/llvm/unittests/Option/LibraryOptionsTest.cpp b/llvm/unittests/Option/LibraryOptionsTest.cpp
index e47b00b0961e6..f995bc3c44a96 100644
--- a/llvm/unittests/Option/LibraryOptionsTest.cpp
+++ b/llvm/unittests/Option/LibraryOptionsTest.cpp
@@ -18,15 +18,16 @@
#include "LibraryOpts.inc"
using namespace llvm;
+using test::TestLibraryOptions;
namespace {
// The struct -gen-opt-parser-defs generates: every spelling sets its member.
TEST(LibraryOptionsTest, Apply) {
TestLibraryOptions O;
- EXPECT_FALSE(O.Enable);
- EXPECT_EQ(O.Count, 3u);
- EXPECT_EQ(O.Ratio, 0.5);
+ EXPECT_FALSE(O.lib_enable);
+ EXPECT_EQ(O.lib_count, 3u);
+ EXPECT_EQ(O.lib_ratio, 0.5);
EXPECT_EQ(O.Path, "p");
auto Apply = [&](std::initializer_list<const char *> Argv) {
@@ -41,21 +42,21 @@ TEST(LibraryOptionsTest, Apply) {
EXPECT_THAT(Apply({"-lib-enable", "--lib-count=7", "-lib-ratio", "0.25",
"-lib-path=a=b"}),
testing::Each(true));
- EXPECT_TRUE(O.Enable);
- EXPECT_EQ(O.Count, 7u);
- EXPECT_EQ(O.Ratio, 0.25);
+ EXPECT_TRUE(O.lib_enable);
+ EXPECT_EQ(O.lib_count, 7u);
+ EXPECT_EQ(O.lib_ratio, 0.25);
EXPECT_EQ(O.Path, "a=b");
- EXPECT_THAT(Apply({"-no-lib-enable"}), testing::Each(true));
- EXPECT_FALSE(O.Enable);
+ EXPECT_THAT(Apply({"-lib-enable=0"}), testing::Each(true));
+ EXPECT_FALSE(O.lib_enable);
EXPECT_THAT(Apply({"-lib-enable=1"}), testing::Each(true));
- EXPECT_TRUE(O.Enable);
+ EXPECT_TRUE(O.lib_enable);
// A rejected value leaves the member unchanged.
EXPECT_THAT(Apply({"-lib-enable=2", "-lib-count=x", "-lib-ratio=y"}),
testing::Each(false));
- EXPECT_TRUE(O.Enable);
- EXPECT_EQ(O.Count, 7u);
- EXPECT_EQ(O.Ratio, 0.25);
+ EXPECT_TRUE(O.lib_enable);
+ EXPECT_EQ(O.lib_count, 7u);
+ EXPECT_EQ(O.lib_ratio, 0.25);
}
// What cl:: sees of the struct, without cl::.
@@ -66,15 +67,16 @@ TEST(LibraryOptionsTest, Parser) {
[] { TestLibraryOptions::Global = TestLibraryOptions(); });
std::vector<std::string> Rows;
- P.forEachOption([&](StringRef Spelling, StringRef MetaVar, StringRef Help) {
- Rows.push_back((Spelling + "|" + MetaVar + "|" + Help).str());
+ P.forEachOption([&](StringRef Spelling, StringRef MetaVar, StringRef Help,
+ bool Hidden) {
+ Rows.push_back(
+ (Spelling + "|" + MetaVar + "|" + Help + (Hidden ? "|h" : "")).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||",
- "no-lib-enable||"));
+ "lib-enable=|<value>|", "lib-enable||A bool|h",
+ "lib-path=|<value>|A string|h", "lib-path||",
+ "lib-ratio=|<value>|A double|h", "lib-ratio||"));
auto Parse = [&](std::initializer_list<const char *> Argv) {
unsigned Consumed = 0;
@@ -82,15 +84,14 @@ TEST(LibraryOptionsTest, Parser) {
return std::to_string(Consumed) + " " + Err;
};
EXPECT_EQ(Parse({"-lib-count", "5"}), "2 ");
- EXPECT_EQ(TestLibraryOptions::Global.Count, 5u);
+ EXPECT_EQ(TestLibraryOptions::Global.lib_count, 5u);
EXPECT_EQ(Parse({"-lib-count=x", "-lib-enable"}),
"1 invalid value 'x' in '-lib-count=x'");
EXPECT_EQ(Parse({"-lib-count"}),
"1 option '-lib-count' requires an argument");
- EXPECT_EQ(Parse({"-no-lib-enable=1"}),
- "1 unknown argument '-no-lib-enable=1'");
+ EXPECT_EQ(Parse({"-lib-other"}), "1 unknown argument '-lib-other'");
P.reset();
- EXPECT_EQ(TestLibraryOptions::Global.Count, 3u);
+ EXPECT_EQ(TestLibraryOptions::Global.lib_count, 3u);
}
// A static RegisterLibraryOptions connects the struct's Global to cl::.
@@ -101,14 +102,14 @@ TEST(LibraryOptionsTest, Register) {
std::string Path = "-lib-path=q";
const char *Args[] = {"prog", "-lib-count", "5", "-lib-enable", Path.c_str()};
EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(Args), Args, "", &nulls()));
- EXPECT_EQ(G.Count, 5u);
- EXPECT_TRUE(G.Enable);
+ EXPECT_EQ(G.lib_count, 5u);
+ EXPECT_TRUE(G.lib_enable);
// A StringRef member does not refer to the caller's argument.
Path.assign(Path.size(), 'x');
EXPECT_EQ(G.Path, "q");
cl::ResetAllOptionOccurrences();
- EXPECT_EQ(G.Count, 3u);
- EXPECT_FALSE(G.Enable);
+ EXPECT_EQ(G.lib_count, 3u);
+ EXPECT_FALSE(G.lib_enable);
EXPECT_EQ(G.Path, "p");
cl::ResetCommandLineParser();
}
diff --git a/llvm/unittests/Option/LibraryOpts.td b/llvm/unittests/Option/LibraryOpts.td
index 03a0f5e2e8cd0..ff53a6d6d70bd 100644
--- a/llvm/unittests/Option/LibraryOpts.td
+++ b/llvm/unittests/Option/LibraryOpts.td
@@ -8,9 +8,10 @@
include "llvm/Option/OptParser.td"
-def TestLibraryOptions : OptionsStruct;
+def TestLibraryOptions : OptionsStruct<"test">;
-defm Enable : BoolField<"lib-enable", "false", "A bool">;
-defm Count : ValueField<"lib-count", "unsigned", "3", "An unsigned">;
-defm Ratio : ValueField<"lib-ratio", "double", "0.5", "A double">;
+defm : BoolField<"lib-enable", "0", "A bool">;
+let Hidden = 0 in
+defm : ValueField<"lib-count", "unsigned", "3", "An unsigned">;
+defm : ValueField<"lib-ratio", "double", "0.5", "A double">;
defm Path : ValueField<"lib-path", "StringRef", "\"p\"", "A string">;
diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index 1f7fd72d64f6d..acb0073cf62b9 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -2255,10 +2255,11 @@ TEST(CommandLineTest, HelpWithEmptyCategory) {
// rejects "bad".
struct TestLibrary final : cl::LibraryOptions {
std::vector<std::string> Args;
- void forEachOption(
- function_ref<void(StringRef, StringRef, StringRef)> Fn) const override {
- Fn("library-flag", "", "A flag");
- Fn("library-int", " <int>", "");
+ void forEachOption(function_ref<void(StringRef, StringRef, StringRef, bool)>
+ Fn) const override {
+ Fn("library-flag", "", "A flag", true);
+ Fn("library-int", " <int>", "", true);
+ Fn("library-shown", "", "A visible flag", false);
}
Error parse(ArrayRef<const char *> Argv, unsigned &Consumed) override {
Consumed = 1;
@@ -2320,8 +2321,8 @@ TEST(CommandLineTest, LibraryOptionsHelp) {
cl::ParseCommandLineOptions(std::size(Args), Args, StringRef(),
&llvm::nulls());
- // Library options are listed only by -help-hidden, aligned with cl::'s.
- // Options without help text are not listed.
+ // Library options are aligned with cl::'s. Options without help text are not
+ // listed.
std::string Hidden = interceptStdout(
[] { cl::PrintHelpMessage(/*Hidden=*/true, /*Categorized=*/false); });
auto HelpColumn = [&](StringRef Line) {
@@ -2337,7 +2338,10 @@ TEST(CommandLineTest, LibraryOptionsHelp) {
EXPECT_EQ(std::string::npos, Hidden.find("library-int"));
std::string Visible = interceptStdout(
[] { cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/false); });
- EXPECT_EQ(std::string::npos, Visible.find("library"));
+ EXPECT_NE(std::string::npos,
+ Visible.find("\nLibrary options:\n\n --library-shown "))
+ << Visible;
+ EXPECT_EQ(std::string::npos, Visible.find("library-flag"));
cl::ResetCommandLineParser();
}
diff --git a/llvm/utils/TableGen/OptionParserEmitter.cpp b/llvm/utils/TableGen/OptionParserEmitter.cpp
index acdb46c086c15..3723ee832aecc 100644
--- a/llvm/utils/TableGen/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/OptionParserEmitter.cpp
@@ -229,6 +229,35 @@ static MarshallingInfo createMarshallingInfo(const Record &R) {
return Ret;
}
+// -foo-bar and -foo-bar= become foo_bar.
+static std::string getSpellingIdentifier(const Record &R) {
+ std::string ID = R.getValueAsString("Name").rtrim('=').str();
+ llvm::replace(ID, '-', '_');
+ if (ID.empty() || isDigit(ID[0]) ||
+ !all_of(ID, [](char C) { return isAlnum(C) || C == '_'; }))
+ PrintFatalError(R.getLoc(), "the spelling is not an identifier; name the "
+ "defm");
+ return ID;
+}
+
+// `defm : BoolField<"foo-bar", ...>` declares member foo_bar, and a named defm
+// names it.
+static std::string getMemberName(const Record &R) {
+ StringRef Name = R.getValueAsString("FieldName");
+ return Name.starts_with("anonymous_") ? getSpellingIdentifier(R) : Name.str();
+}
+
+// The OPT_ name of an option of an OptionsStruct. `defm :` rows are named
+// after the spelling: -foo-bar= is OPT_foo_bar_EQ.
+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;
+}
+
// 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.
@@ -236,7 +265,8 @@ static void emitOptionsStruct(const Record &Struct,
ArrayRef<const Record *> Groups,
ArrayRef<const Record *> Opts, raw_ostream &OS) {
struct Member {
- StringRef Name, Type, Default;
+ std::string Name;
+ StringRef Type, Default, Spelling;
};
std::vector<const Record *> Fields;
for (const Record *R : Opts) {
@@ -263,45 +293,50 @@ static void emitOptionsStruct(const Record &Struct,
std::vector<Member> Members;
StringMap<unsigned> MemberIndex;
for (const Record *R : ByID) {
- Member M{R->getValueAsString("FieldName"), R->getValueAsString("FieldType"),
- R->getValueAsString("FieldDefault")};
+ Member M{getMemberName(*R), 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.Type != M.Type || Prev.Default != M.Default)
- PrintFatalError(R->getLoc(), "member '" + M.Name +
- "' is declared with a different type "
- "or default");
+ if (Prev.Spelling != M.Spelling)
+ PrintFatalError(R->getLoc(), "member '" + M.Name + "' is also set by -" +
+ Prev.Spelling);
}
StringRef Name = Struct.getName();
OS << "\n#ifdef OPTIONS_STRUCT_DECL\n#undef OPTIONS_STRUCT_DECL\n";
OS << "#include \"llvm/ADT/StringRef.h\"\n\n";
+ StringRef Namespace = Struct.getValueAsString("Namespace");
OS << "namespace llvm {\nnamespace opt {\nclass Arg;\n"
- "class OptTable;\n} // namespace opt\n\n";
+ "class OptTable;\n} // namespace opt\n} // namespace llvm\n\n";
+ OS << "namespace " << Namespace << " {\n";
OS << "struct " << Name << " {\n";
+ if (Namespace != "llvm")
+ OS << " using StringRef = llvm::StringRef;\n";
for (const Member &M : Members)
OS << " " << M.Type << " " << M.Name << "{" << M.Default << "};\n";
OS << "\n /// The instance cl::ParseCommandLineOptions sets.\n";
OS << " static " << Name << " Global;\n\n";
- OS << " static const opt::OptTable &optTable();\n";
+ OS << " static const llvm::opt::OptTable &optTable();\n";
OS << " /// Sets the member that \\p A names. Returns false if the value is "
"invalid.\n";
- OS << " bool apply(const opt::Arg &A);\n";
- OS << "};\n} // namespace llvm\n";
+ OS << " bool apply(const llvm::opt::Arg &A);\n";
+ OS << "};\n} // namespace " << Namespace << "\n";
OS << "#endif // OPTIONS_STRUCT_DECL\n";
- std::string Qualified = ("llvm::" + Name).str();
+ 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_" << getOptionName(*R) << ",\n";
+ 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";
@@ -309,8 +344,8 @@ static void emitOptionsStruct(const Record &Struct,
OS << "bool " << Qualified << "::apply(const llvm::opt::Arg &A) {\n";
OS << " switch (A.getOption().getID()) {\n";
for (const Record *R : Fields) {
- OS << " case OPT_" << getOptionName(*R) << ":\n";
- StringRef Member = R->getValueAsString("FieldName");
+ OS << " case OPT_" << getStructOptionID(*R) << ":\n";
+ std::string Member = getMemberName(*R);
if (isa<UnsetInit>(R->getValueInit("FieldValue")))
OS << " return llvm::opt::parseArgValue(A.getValue(), " << Member
<< ");\n";
>From 4ec28aed2262297c17534e54edbba7c32357f4cb Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 26 Sep 2026 22:28:20 -0700
Subject: [PATCH 3/6] rebase
---
llvm/docs/CommandLine.md | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/llvm/docs/CommandLine.md b/llvm/docs/CommandLine.md
index b4fa6ea01b317..174cfd1a1a86a 100644
--- a/llvm/docs/CommandLine.md
+++ b/llvm/docs/CommandLine.md
@@ -1567,9 +1567,9 @@ TODO: fill in this section
## Declaring a Library's Options in TableGen
-A library can declare its options in a `.td` file instead of as `cl::opt`
-globals. `llvm-tblgen -gen-opt-parser-defs` generates a struct with a member
-per option, the table that parses them, and the hooks through which
+A library can declare its options in a `.td` file instead of as `cl::opt` globals.
+`llvm-tblgen -gen-opt-parser-defs` generates a struct with a member per option,
+the table that parses them, and the hooks through which
`cl::ParseCommandLineOptions` parses them and `-help-hidden` lists them.
```text
@@ -1585,17 +1585,18 @@ defm : ValueField<"foo-path", "StringRef", "", "The input path">;
The struct is in namespace `llvm` unless the def names another, as in
`OptionsStruct<"mlir">`. A member is named after its option, `foo_enable` for
-`-foo-enable`, unless the `defm` names it. A `BoolField` is set by `-x` or
-`-x=true|false|1|0`; a `ValueField` of an integer type, `double`, or
-`StringRef` by `-x=value` or `-x value`. Both accept `--` for `-`. Only
-`-help-hidden` lists the options, like `cl::Hidden`; those declared in
-`let Hidden = 0 in` are also listed by `-help`.
+`-foo-enable`, unless the `defm` names it.
+
+A `BoolField` is set by `-x` or `-x=true|false|1|0`. A `ValueField` of an
+integer type, `double`, or `StringRef` is set by `-x=value` or `-x value`.
+Both accept `--` for `-`. Only `-help-hidden` lists the options, like
+`cl::Hidden`; those declared in `let Hidden = 0 in` are also listed by `-help`.
The header declares the struct after including what the member defaults need,
and one source file defines it and registers it with `cl::`.
-The library then lists `FooOptionsTableGen` under `DEPENDS` and `Option`
-under `LINK_COMPONENTS`. Code reads `FooOptions::Global.foo_enable`,
-the instance the command line sets. Keep the header in `lib/`, as private as the
-`static cl::opt` it replaces; another library that needs a value calls a
-function or takes a parameter.
+The library then lists `FooOptionsTableGen` under `DEPENDS` and `Option` under
+`LINK_COMPONENTS`. Code reads `FooOptions::Global.foo_enable`, the instance the
+command line sets. Keep the header in `lib/`, as private as the `static cl::opt`
+it replaces; another library that needs a value calls a function or takes a
+parameter.
>From fce40f9157b870433f94440ee49e5aa771c0b72d Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 28 Sep 2026 21:09:06 -0700
Subject: [PATCH 4/6] improve test, use non-empty string; test
-lib-enable=false nuance
---
llvm/docs/CommandLine.md | 7 ++++---
llvm/unittests/Option/LibraryOptionsTest.cpp | 4 ++--
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/llvm/docs/CommandLine.md b/llvm/docs/CommandLine.md
index 174cfd1a1a86a..f907b614b78ae 100644
--- a/llvm/docs/CommandLine.md
+++ b/llvm/docs/CommandLine.md
@@ -1580,7 +1580,7 @@ def FooOptions : OptionsStruct;
defm : BoolField<"foo-enable", "1", "Enable foo">;
defm : ValueField<"foo-threshold", "unsigned", "8", "The threshold">;
let Hidden = 0 in
-defm : ValueField<"foo-path", "StringRef", "", "The input path">;
+defm : ValueField<"foo-path", "StringRef", "\"-\"", "The input path">;
```
The struct is in namespace `llvm` unless the def names another, as in
@@ -1592,8 +1592,9 @@ integer type, `double`, or `StringRef` is set by `-x=value` or `-x value`.
Both accept `--` for `-`. Only `-help-hidden` lists the options, like
`cl::Hidden`; those declared in `let Hidden = 0 in` are also listed by `-help`.
-The header declares the struct after including what the member defaults need,
-and one source file defines it and registers it with `cl::`.
+A default is the member's C++ initializer, so `"\"-\""` initializes `foo_path`
+to `"-"`. The header declares the struct after including what the member
+defaults need, and one source file defines it and registers it with `cl::`.
The library then lists `FooOptionsTableGen` under `DEPENDS` and `Option` under
`LINK_COMPONENTS`. Code reads `FooOptions::Global.foo_enable`, the instance the
diff --git a/llvm/unittests/Option/LibraryOptionsTest.cpp b/llvm/unittests/Option/LibraryOptionsTest.cpp
index f995bc3c44a96..8fa6d4819853b 100644
--- a/llvm/unittests/Option/LibraryOptionsTest.cpp
+++ b/llvm/unittests/Option/LibraryOptionsTest.cpp
@@ -46,13 +46,13 @@ TEST(LibraryOptionsTest, Apply) {
EXPECT_EQ(O.lib_count, 7u);
EXPECT_EQ(O.lib_ratio, 0.25);
EXPECT_EQ(O.Path, "a=b");
- EXPECT_THAT(Apply({"-lib-enable=0"}), testing::Each(true));
+ EXPECT_THAT(Apply({"-lib-enable=false"}), testing::Each(true));
EXPECT_FALSE(O.lib_enable);
EXPECT_THAT(Apply({"-lib-enable=1"}), testing::Each(true));
EXPECT_TRUE(O.lib_enable);
// A rejected value leaves the member unchanged.
- EXPECT_THAT(Apply({"-lib-enable=2", "-lib-count=x", "-lib-ratio=y"}),
+ EXPECT_THAT(Apply({"-lib-enable=2", "-lib-count=-1", "-lib-ratio=y"}),
testing::Each(false));
EXPECT_TRUE(O.lib_enable);
EXPECT_EQ(O.lib_count, 7u);
>From 9cf554038e5c92964348b052f4a12eded5ae5b23 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Tue, 29 Sep 2026 00:30:24 -0700
Subject: [PATCH 5/6] improve CommandLine.md
---
llvm/docs/CommandLine.md | 19 ++++++++++---------
llvm/lib/Support/CommandLine.cpp | 5 ++++-
llvm/unittests/Support/CommandLineTest.cpp | 14 ++++++++++++++
3 files changed, 28 insertions(+), 10 deletions(-)
diff --git a/llvm/docs/CommandLine.md b/llvm/docs/CommandLine.md
index f907b614b78ae..4fbe223287c12 100644
--- a/llvm/docs/CommandLine.md
+++ b/llvm/docs/CommandLine.md
@@ -1577,27 +1577,28 @@ include "llvm/Option/OptParser.td"
def FooOptions : OptionsStruct;
-defm : BoolField<"foo-enable", "1", "Enable foo">;
-defm : ValueField<"foo-threshold", "unsigned", "8", "The threshold">;
+defm : BoolField<"enable-foo", "1", "Enable foo">;
+defm threshold : ValueField<"foo-threshold", "unsigned", "8", "The threshold">;
let Hidden = 0 in
defm : ValueField<"foo-path", "StringRef", "\"-\"", "The input path">;
```
The struct is in namespace `llvm` unless the def names another, as in
-`OptionsStruct<"mlir">`. A member is named after its option, `foo_enable` for
-`-foo-enable`, unless the `defm` names it.
+`OptionsStruct<"mlir">`. A member is named after its option, `enable_foo` for
+`-enable-foo`; a named `defm` such as `defm threshold` names it `threshold`.
-A `BoolField` is set by `-x` or `-x=true|false|1|0`. A `ValueField` of an
-integer type, `double`, or `StringRef` is set by `-x=value` or `-x value`.
-Both accept `--` for `-`. Only `-help-hidden` lists the options, like
-`cl::Hidden`; those declared in `let Hidden = 0 in` are also listed by `-help`.
+The `BoolField` is set by `-enable-foo` or `-enable-foo=true|false|1|0`. A
+`ValueField`, of an integer type, `double`, or `StringRef`, is set by
+`-foo-threshold=8` or `-foo-threshold 8`. Both accept `--` for `-`. Only
+`-help-hidden` lists the options, like `cl::Hidden`; those declared in
+`let Hidden = 0 in` are also listed by `-help`.
A default is the member's C++ initializer, so `"\"-\""` initializes `foo_path`
to `"-"`. The header declares the struct after including what the member
defaults need, and one source file defines it and registers it with `cl::`.
The library then lists `FooOptionsTableGen` under `DEPENDS` and `Option` under
-`LINK_COMPONENTS`. Code reads `FooOptions::Global.foo_enable`, the instance the
+`LINK_COMPONENTS`. Code reads `FooOptions::Global.enable_foo`, the instance the
command line sets. Keep the header in `lib/`, as private as the `static cl::opt`
it replaces; another library that needs a value calls a function or takes a
parameter.
diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp
index 74fc1ca42cd9b..8ae33f8897c43 100644
--- a/llvm/lib/Support/CommandLine.cpp
+++ b/llvm/lib/Support/CommandLine.cpp
@@ -227,8 +227,11 @@ class CommandLineParser {
bool HadErrors = false;
if (O->hasArgStr()) {
// Add argument to the argument map!
+ // An unregistered subcommand, such as MLIR's PassOptions, parses its
+ // own arguments.
if (!SC->OptionsMap.insert(std::make_pair(O->ArgStr, O)).second ||
- LibraryIndex.contains(O->ArgStr)) {
+ (RegisteredSubCommands.contains(SC) &&
+ LibraryIndex.contains(O->ArgStr))) {
errs() << ProgramName << ": CommandLine Error: Option '" << O->ArgStr
<< "' registered more than once!\n";
HadErrors = true;
diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index acb0073cf62b9..43d67363eb682 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -2361,6 +2361,20 @@ TEST(CommandLineTest, LibraryOptionsRegisteredWhileParsing) {
cl::ResetCommandLineParser();
}
+// An unregistered subcommand, as MLIR's PassOptions is, may reuse a library
+// option's name.
+TEST(CommandLineTest, LibraryOptionsUnregisteredSubCommand) {
+ cl::ResetCommandLineParser();
+ TestLibrary L;
+ cl::addLibraryOptions(L);
+ const char *Args[] = {"prog"};
+ EXPECT_TRUE(cl::ParseCommandLineOptions(std::size(Args), Args, StringRef(),
+ &llvm::nulls()));
+ cl::SubCommand Unregistered;
+ StackOption<int> Opt("library-flag", cl::sub(Unregistered));
+ cl::ResetCommandLineParser();
+}
+
#if GTEST_HAS_DEATH_TEST
TEST(CommandLineTest, LibraryOptionsConflict) {
cl::ResetCommandLineParser();
>From 4968dd127ff028d06d7a4b8a6b2d6d1b6d535da5 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Tue, 29 Sep 2026 01:10:48 -0700
Subject: [PATCH 6/6] .md: one sentence per line per
https://github.com/llvm/llvm-project/pull/197666/changes
---
llvm/docs/CommandLine.md | 31 ++++++++++++-------------------
1 file changed, 12 insertions(+), 19 deletions(-)
diff --git a/llvm/docs/CommandLine.md b/llvm/docs/CommandLine.md
index 4fbe223287c12..df15b9301f70d 100644
--- a/llvm/docs/CommandLine.md
+++ b/llvm/docs/CommandLine.md
@@ -1568,9 +1568,7 @@ TODO: fill in this section
## Declaring a Library's Options in TableGen
A library can declare its options in a `.td` file instead of as `cl::opt` globals.
-`llvm-tblgen -gen-opt-parser-defs` generates a struct with a member per option,
-the table that parses them, and the hooks through which
-`cl::ParseCommandLineOptions` parses them and `-help-hidden` lists them.
+`llvm-tblgen -gen-opt-parser-defs` generates a struct with a member per option, the table that parses them, and the hooks through which `cl::ParseCommandLineOptions` parses them and `-help-hidden` lists them.
```text
include "llvm/Option/OptParser.td"
@@ -1583,22 +1581,17 @@ let Hidden = 0 in
defm : ValueField<"foo-path", "StringRef", "\"-\"", "The input path">;
```
-The struct is in namespace `llvm` unless the def names another, as in
-`OptionsStruct<"mlir">`. A member is named after its option, `enable_foo` for
-`-enable-foo`; a named `defm` such as `defm threshold` names it `threshold`.
+The struct is in namespace `llvm` unless the def names another, as in `OptionsStruct<"mlir">`.
+A member is named after its option, `enable_foo` for `-enable-foo`; a named `defm` such as `defm threshold` names it `threshold`.
-The `BoolField` is set by `-enable-foo` or `-enable-foo=true|false|1|0`. A
-`ValueField`, of an integer type, `double`, or `StringRef`, is set by
-`-foo-threshold=8` or `-foo-threshold 8`. Both accept `--` for `-`. Only
-`-help-hidden` lists the options, like `cl::Hidden`; those declared in
-`let Hidden = 0 in` are also listed by `-help`.
+The `BoolField` is set by `-enable-foo` or `-enable-foo=true|false|1|0`.
+A `ValueField`, of an integer type, `double`, or `StringRef`, is set by `-foo-threshold=8` or `-foo-threshold 8`.
+Both accept `--` for `-`.
+Only `-help-hidden` lists the options, like `cl::Hidden`; those declared in `let Hidden = 0 in` are also listed by `-help`.
-A default is the member's C++ initializer, so `"\"-\""` initializes `foo_path`
-to `"-"`. The header declares the struct after including what the member
-defaults need, and one source file defines it and registers it with `cl::`.
+A default is the member's C++ initializer, so `"\"-\""` initializes `foo_path` to `"-"`.
+The header declares the struct after including what the member defaults need, and one source file defines it and registers it with `cl::`.
-The library then lists `FooOptionsTableGen` under `DEPENDS` and `Option` under
-`LINK_COMPONENTS`. Code reads `FooOptions::Global.enable_foo`, the instance the
-command line sets. Keep the header in `lib/`, as private as the `static cl::opt`
-it replaces; another library that needs a value calls a function or takes a
-parameter.
+The library then lists `FooOptionsTableGen` under `DEPENDS` and `Option` under `LINK_COMPONENTS`.
+Code reads `FooOptions::Global.enable_foo`, the instance the command line sets.
+Keep the header in `lib/`, as private as the `static cl::opt` it replaces; another library that needs a value calls a function or takes a parameter.
More information about the llvm-commits
mailing list