[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