[llvm] fdbd17d - [llvm] Add subcommand support for OptTable (#155026)

via llvm-commits llvm-commits at lists.llvm.org
Mon Oct 6 12:11:05 PDT 2025


Author: Prabhu Rajasekaran
Date: 2025-10-06T12:11:00-07:00
New Revision: fdbd17d1fb0d532e54773626cc0e65eed94440cb

URL: https://github.com/llvm/llvm-project/commit/fdbd17d1fb0d532e54773626cc0e65eed94440cb
DIFF: https://github.com/llvm/llvm-project/commit/fdbd17d1fb0d532e54773626cc0e65eed94440cb.diff

LOG: [llvm] Add subcommand support for OptTable (#155026)

Implement support for `subcommands` in OptTable to attain feature parity
with `cl`.

Design overview:
https://discourse.llvm.org/t/subcommand-feature-support-in-llvm-opttable/88098

Issue: https://github.com/llvm/llvm-project/issues/108307

Added: 
    llvm/examples/OptSubcommand/CMakeLists.txt
    llvm/examples/OptSubcommand/Opts.td
    llvm/examples/OptSubcommand/llvm-hello-sub.cpp
    llvm/unittests/Option/OptionSubCommandsTest.cpp
    llvm/unittests/Option/SubCommandOpts.td

Modified: 
    clang-tools-extra/clangd/CompileCommands.cpp
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/tools/clang-installapi/Options.h
    lld/MachO/DriverUtils.cpp
    lld/MinGW/Driver.cpp
    lld/wasm/Driver.cpp
    llvm/examples/CMakeLists.txt
    llvm/include/llvm/Option/ArgList.h
    llvm/include/llvm/Option/OptParser.td
    llvm/include/llvm/Option/OptTable.h
    llvm/include/llvm/Option/Option.h
    llvm/lib/Option/ArgList.cpp
    llvm/lib/Option/OptTable.cpp
    llvm/unittests/Option/CMakeLists.txt
    llvm/unittests/Option/OptionMarshallingTest.cpp
    llvm/utils/TableGen/OptionParserEmitter.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/CompileCommands.cpp b/clang-tools-extra/clangd/CompileCommands.cpp
index c9da98e96ccfb..c1be93730129a 100644
--- a/clang-tools-extra/clangd/CompileCommands.cpp
+++ b/clang-tools-extra/clangd/CompileCommands.cpp
@@ -466,7 +466,7 @@ llvm::ArrayRef<ArgStripper::Rule> ArgStripper::rulesFor(llvm::StringRef Arg) {
     } AliasTable[] = {
 #define OPTION(PREFIX, PREFIXED_NAME, ID, KIND, GROUP, ALIAS, ALIASARGS,       \
                FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS,       \
-               METAVAR, VALUES)                                                \
+               METAVAR, VALUES, SUBCOMMANDIDS_OFFSET)                          \
   {DriverID::OPT_##ID, DriverID::OPT_##ALIAS, ALIASARGS},
 #include "clang/Driver/Options.inc"
 #undef OPTION

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index a0e0f3771846a..50fd50aaba38d 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -533,9 +533,9 @@ static T extractMaskValue(T KeyPath) {
 #define PARSE_OPTION_WITH_MARSHALLING(                                         \
     ARGS, DIAGS, PREFIX_TYPE, SPELLING_OFFSET, ID, KIND, GROUP, ALIAS,         \
     ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS,       \
-    METAVAR, VALUES, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE,        \
-    IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \
-    TABLE_INDEX)                                                               \
+    METAVAR, VALUES, SUBCOMMANDIDS_OFFSET, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, \
+    DEFAULT_VALUE, IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER,     \
+    MERGER, EXTRACTOR, TABLE_INDEX)                                            \
   if ((VISIBILITY) & options::CC1Option) {                                     \
     KEYPATH = MERGER(KEYPATH, DEFAULT_VALUE);                                  \
     if (IMPLIED_CHECK)                                                         \
@@ -551,8 +551,9 @@ static T extractMaskValue(T KeyPath) {
 #define GENERATE_OPTION_WITH_MARSHALLING(                                      \
     CONSUMER, PREFIX_TYPE, SPELLING_OFFSET, ID, KIND, GROUP, ALIAS, ALIASARGS, \
     FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, \
-    SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, IMPLIED_CHECK,          \
-    IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, TABLE_INDEX)   \
+    SUBCOMMANDIDS_OFFSET, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE,   \
+    IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \
+    TABLE_INDEX)                                                               \
   if ((VISIBILITY) & options::CC1Option) {                                     \
     [&](const auto &Extracted) {                                               \
       if (ALWAYS_EMIT ||                                                       \

diff  --git a/clang/tools/clang-installapi/Options.h b/clang/tools/clang-installapi/Options.h
index d62f2efd3141a..f48459468372a 100644
--- a/clang/tools/clang-installapi/Options.h
+++ b/clang/tools/clang-installapi/Options.h
@@ -208,7 +208,7 @@ enum ID {
   OPT_INVALID = 0, // This is not an option ID.
 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS,         \
                VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR,     \
-               VALUES)                                                         \
+               VALUES, SUBCOMMANDIDS_OFFSET)                                   \
   OPT_##ID,
 #include "InstallAPIOpts.inc"
   LastOption

diff  --git a/lld/MachO/DriverUtils.cpp b/lld/MachO/DriverUtils.cpp
index a3b722f13daca..3ff9d96ed53ce 100644
--- a/lld/MachO/DriverUtils.cpp
+++ b/lld/MachO/DriverUtils.cpp
@@ -45,7 +45,7 @@ using namespace lld::macho;
 static constexpr OptTable::Info optInfo[] = {
 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS,         \
                VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR,     \
-               VALUES)                                                         \
+               VALUES, SUBCOMMANDIDS_OFFSET)                                   \
   {PREFIX,                                                                     \
    NAME,                                                                       \
    HELPTEXT,                                                                   \
@@ -59,7 +59,8 @@ static constexpr OptTable::Info optInfo[] = {
    OPT_##GROUP,                                                                \
    OPT_##ALIAS,                                                                \
    ALIASARGS,                                                                  \
-   VALUES},
+   VALUES,                                                                     \
+   SUBCOMMANDIDS_OFFSET},
 #include "Options.inc"
 #undef OPTION
 };

diff  --git a/lld/MinGW/Driver.cpp b/lld/MinGW/Driver.cpp
index 5098dbd77b4fd..1180097ce08cf 100644
--- a/lld/MinGW/Driver.cpp
+++ b/lld/MinGW/Driver.cpp
@@ -69,7 +69,7 @@ enum {
 static constexpr opt::OptTable::Info infoTable[] = {
 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS,         \
                VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR,     \
-               VALUES)                                                         \
+               VALUES, SUBCOMMANDIDS_OFFSET)                                   \
   {PREFIX,                                                                     \
    NAME,                                                                       \
    HELPTEXT,                                                                   \
@@ -83,7 +83,8 @@ static constexpr opt::OptTable::Info infoTable[] = {
    OPT_##GROUP,                                                                \
    OPT_##ALIAS,                                                                \
    ALIASARGS,                                                                  \
-   VALUES},
+   VALUES,                                                                     \
+   SUBCOMMANDIDS_OFFSET},
 #include "Options.inc"
 #undef OPTION
 };

diff  --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 46c848d5c1232..9c0e1b58e62f9 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -157,7 +157,7 @@ bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
 static constexpr opt::OptTable::Info optInfo[] = {
 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS,         \
                VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR,     \
-               VALUES)                                                         \
+               VALUES, SUBCOMMANDIDS_OFFSET)                                   \
   {PREFIX,                                                                     \
    NAME,                                                                       \
    HELPTEXT,                                                                   \
@@ -171,7 +171,8 @@ static constexpr opt::OptTable::Info optInfo[] = {
    OPT_##GROUP,                                                                \
    OPT_##ALIAS,                                                                \
    ALIASARGS,                                                                  \
-   VALUES},
+   VALUES,                                                                     \
+   SUBCOMMANDIDS_OFFSET},
 #include "Options.inc"
 #undef OPTION
 };

diff  --git a/llvm/examples/CMakeLists.txt b/llvm/examples/CMakeLists.txt
index 74613bd1350bd..b10a94c5493b8 100644
--- a/llvm/examples/CMakeLists.txt
+++ b/llvm/examples/CMakeLists.txt
@@ -8,6 +8,7 @@ add_subdirectory(ModuleMaker)
 add_subdirectory(OrcV2Examples)
 add_subdirectory(SpeculativeJIT)
 add_subdirectory(Bye)
+add_subdirectory(OptSubcommand)
 
 if(LLVM_ENABLE_EH AND (NOT WIN32) AND (NOT "${LLVM_NATIVE_ARCH}" STREQUAL "ARM"))
     add_subdirectory(ExceptionDemo)

diff  --git a/llvm/examples/OptSubcommand/CMakeLists.txt b/llvm/examples/OptSubcommand/CMakeLists.txt
new file mode 100644
index 0000000000000..debc948611866
--- /dev/null
+++ b/llvm/examples/OptSubcommand/CMakeLists.txt
@@ -0,0 +1,19 @@
+# Set the .td file to be processed for this target.
+set(LLVM_TARGET_DEFINITIONS Opts.td)
+
+tablegen(LLVM Opts.inc -gen-opt-parser-defs)
+add_public_tablegen_target(HelloSubTableGen)
+
+set(LLVM_LINK_COMPONENTS  
+  Support
+  Option  
+  )
+
+add_llvm_example(OptSubcommand
+  llvm-hello-sub.cpp  
+  )
+
+target_include_directories(OptSubcommand
+  PRIVATE
+  ${CMAKE_CURRENT_BINARY_DIR}
+  )

diff  --git a/llvm/examples/OptSubcommand/Opts.td b/llvm/examples/OptSubcommand/Opts.td
new file mode 100644
index 0000000000000..7c980ee7a0e7f
--- /dev/null
+++ b/llvm/examples/OptSubcommand/Opts.td
@@ -0,0 +1,18 @@
+include "llvm/Option/OptParser.td"
+
+def sc_foo : SubCommand<"foo", "HelpText for SubCommand foo.">;
+
+def sc_bar : SubCommand<"bar", "HelpText for SubCommand bar.",
+                        "OptSubcommand bar <options>">;
+
+def help : Flag<["--"], "help">,
+           HelpText<"OptSubcommand <subcommand> <options>">;
+
+def version : Flag<["-"], "version">,
+              HelpText<"Toplevel Display the version number">;
+
+def uppercase : Flag<["-"], "uppercase", [sc_foo, sc_bar]>,
+                HelpText<"Print in uppercase">;
+
+def lowercase : Flag<["-"], "lowercase", [sc_foo]>,
+                HelpText<"Print in lowercase">;

diff  --git a/llvm/examples/OptSubcommand/llvm-hello-sub.cpp b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
new file mode 100644
index 0000000000000..8071f56cb3685
--- /dev/null
+++ b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp
@@ -0,0 +1,137 @@
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/OptTable.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+using namespace llvm::opt;
+
+namespace {
+enum ID {
+  OPT_INVALID = 0,
+#define OPTION(PREFIXES, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS,       \
+               VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR,     \
+               VALUES, SUBCOMMANDIDS_OFFSET)                                   \
+  OPT_##ID,
+#include "Opts.inc"
+#undef OPTION
+};
+#define OPTTABLE_STR_TABLE_CODE
+#include "Opts.inc"
+#undef OPTTABLE_STR_TABLE_CODE
+
+#define OPTTABLE_PREFIXES_TABLE_CODE
+#include "Opts.inc"
+#undef OPTTABLE_PREFIXES_TABLE_CODE
+
+#define OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE
+#include "Opts.inc"
+#undef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE
+
+#define OPTTABLE_SUBCOMMANDS_CODE
+#include "Opts.inc"
+#undef OPTTABLE_SUBCOMMANDS_CODE
+
+static constexpr OptTable::Info InfoTable[] = {
+#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
+#include "Opts.inc"
+#undef OPTION
+};
+
+class HelloSubOptTable : public GenericOptTable {
+public:
+  HelloSubOptTable()
+      : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable,
+                        /*IgnoreCase=*/false, OptionSubCommands,
+                        OptionSubCommandIDsTable) {}
+};
+} // namespace
+
+int main(int argc, char **argv) {
+  InitLLVM X(argc, argv);
+  HelloSubOptTable T;
+  unsigned MissingArgIndex, MissingArgCount;
+
+  auto HandleMultipleSubcommands = [](ArrayRef<StringRef> SubCommands) {
+    assert(SubCommands.size() > 1);
+    llvm::errs() << "error: more than one subcommand passed [\n";
+    for (auto SC : SubCommands)
+      llvm::errs() << " `" << SC << "`\n";
+    llvm::errs() << "]\n";
+    llvm::errs() << "See --help.\n";
+    exit(1);
+  };
+
+  auto HandleOtherPositionals = [](ArrayRef<StringRef> Positionals) {
+    assert(!Positionals.empty());
+    llvm::errs() << "error: unknown positional argument(s) [\n";
+    for (auto SC : Positionals)
+      llvm::errs() << " `" << SC << "`\n";
+    llvm::errs() << "]\n";
+    llvm::errs() << "See --help.\n";
+    exit(1);
+  };
+
+  InputArgList Args = T.ParseArgs(ArrayRef(argv + 1, argc - 1), MissingArgIndex,
+                                  MissingArgCount);
+
+  StringRef SubCommand = Args.getSubCommand(
+      T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
+  // Handle help. When help options is found, ignore all other options and exit
+  // after printing help.
+
+  if (Args.hasArg(OPT_help)) {
+    T.printHelp(llvm::outs(), "llvm-hello-sub [subcommand] [options]",
+                "LLVM Hello SubCommand Example", false, false, Visibility(),
+                SubCommand);
+    return 0;
+  }
+
+  auto HandleSubCommandArg = [&](ID OptionType) {
+    if (!Args.hasArg(OptionType))
+      return false;
+    auto O = T.getOption(OptionType);
+    if (!O.isRegisteredSC(SubCommand)) {
+      llvm::errs() << "Option [" << O.getName()
+                   << "] is not valid for SubCommand [" << SubCommand << "]\n";
+      return false;
+    }
+    return true;
+  };
+
+  bool HasUnknownOptions = false;
+  for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
+    HasUnknownOptions = true;
+    llvm::errs() << "Unknown option `" << A->getAsString(Args) << "'\n";
+  }
+  if (HasUnknownOptions) {
+    llvm::errs() << "See `OptSubcommand --help`.\n";
+    return 1;
+  }
+  if (SubCommand.empty()) {
+    if (Args.hasArg(OPT_version))
+      llvm::outs() << "LLVM Hello SubCommand Example 1.0\n";
+  } else if (SubCommand == "foo") {
+    if (HandleSubCommandArg(OPT_uppercase))
+      llvm::outs() << "FOO\n";
+    else if (HandleSubCommandArg(OPT_lowercase))
+      llvm::outs() << "foo\n";
+
+    if (HandleSubCommandArg(OPT_version))
+      llvm::outs() << "LLVM Hello SubCommand foo Example 1.0\n";
+
+  } else if (SubCommand == "bar") {
+    if (HandleSubCommandArg(OPT_lowercase))
+      llvm::outs() << "bar\n";
+    else if (HandleSubCommandArg(OPT_uppercase))
+      llvm::outs() << "BAR\n";
+
+    if (HandleSubCommandArg(OPT_version))
+      llvm::outs() << "LLVM Hello SubCommand bar Example 1.0\n";
+  }
+
+  return 0;
+}

diff  --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h
index 3e80574355b87..db36509125d21 100644
--- a/llvm/include/llvm/Option/ArgList.h
+++ b/llvm/include/llvm/Option/ArgList.h
@@ -20,6 +20,7 @@
 #include "llvm/Option/OptSpecifier.h"
 #include "llvm/Option/Option.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/Error.h"
 #include <algorithm>
 #include <cstddef>
 #include <initializer_list>
@@ -280,6 +281,22 @@ class ArgList {
   /// list.
   virtual unsigned getNumInputArgStrings() const = 0;
 
+  /// getSubCommand - Find subcommand from the arguments if the usage is valid.
+  ///
+  /// \param AllSubCommands - A list of all valid subcommands.
+  /// \param HandleMultipleSubcommands - A callback for the case where multiple
+  /// subcommands are present in the arguments. It gets a list of all found
+  /// subcommands.
+  /// \param HandleOtherPositionals - A callback for the case where positional
+  /// arguments that are not subcommands are present.
+  /// \return The name of the subcommand found. If no subcommand is found,
+  /// this returns an empty StringRef. If multiple subcommands are found, the
+  /// first one is returned.
+  StringRef getSubCommand(
+      ArrayRef<OptTable::SubCommand> AllSubCommands,
+      std::function<void(ArrayRef<StringRef>)> HandleMultipleSubcommands,
+      std::function<void(ArrayRef<StringRef>)> HandleOtherPositionals) const;
+
   /// @}
   /// @name Argument Lookup Utilities
   /// @{

diff  --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td
index 9fd606b0d6fcb..8f32fb4493511 100644
--- a/llvm/include/llvm/Option/OptParser.td
+++ b/llvm/include/llvm/Option/OptParser.td
@@ -98,7 +98,15 @@ class HelpTextVariant<list<OptionVisibility> visibilities, string text> {
   string Text = text;
 }
 
-class Option<list<string> prefixes, string name, OptionKind kind> {
+// Class definition for positional subcommands.
+class SubCommand<string name, string helpText, string usage = ""> {
+  string Name = name;
+  string HelpText = helpText;
+  string Usage = usage;
+}
+
+class Option<list<string> prefixes, string name, OptionKind kind,
+             list<SubCommand> subcommands = []> {
   string EnumName = ?; // Uses the def name if undefined.
   list<string> Prefixes = prefixes;
   string Name = name;
@@ -129,26 +137,34 @@ class Option<list<string> prefixes, string name, OptionKind kind> {
   code ValueMerger = "mergeForwardValue";
   code ValueExtractor = "extractForwardValue";
   list<code> NormalizedValues = ?;
+  list<SubCommand> SubCommands = subcommands;
 }
 
 // Helpers for defining options.
 
-class Flag<list<string> prefixes, string name>
-  : Option<prefixes, name, KIND_FLAG>;
-class Joined<list<string> prefixes, string name>
-  : Option<prefixes, name, KIND_JOINED>;
-class Separate<list<string> prefixes, string name>
-  : Option<prefixes, name, KIND_SEPARATE>;
-class CommaJoined<list<string> prefixes, string name>
-  : Option<prefixes, name, KIND_COMMAJOINED>;
-class MultiArg<list<string> prefixes, string name, int numargs>
-  : Option<prefixes, name, KIND_MULTIARG> {
+class Flag<list<string> prefixes, string name,
+           list<SubCommand> subcommands = []>
+    : Option<prefixes, name, KIND_FLAG, subcommands>;
+class Joined<list<string> prefixes, string name,
+             list<SubCommand> subcommands = []>
+    : Option<prefixes, name, KIND_JOINED, subcommands>;
+class Separate<list<string> prefixes, string name,
+               list<SubCommand> subcommands = []>
+    : Option<prefixes, name, KIND_SEPARATE, subcommands>;
+class CommaJoined<list<string> prefixes, string name,
+                  list<SubCommand> subcommands = []>
+    : Option<prefixes, name, KIND_COMMAJOINED, subcommands>;
+class MultiArg<list<string> prefixes, string name, int numargs,
+               list<SubCommand> subcommands = []>
+    : Option<prefixes, name, KIND_MULTIARG, subcommands> {
   int NumArgs = numargs;
 }
-class JoinedOrSeparate<list<string> prefixes, string name>
-  : Option<prefixes, name, KIND_JOINED_OR_SEPARATE>;
-class JoinedAndSeparate<list<string> prefixes, string name>
-  : Option<prefixes, name, KIND_JOINED_AND_SEPARATE>;
+class JoinedOrSeparate<list<string> prefixes, string name,
+                       list<SubCommand> subcommands = []>
+    : Option<prefixes, name, KIND_JOINED_OR_SEPARATE, subcommands>;
+class JoinedAndSeparate<list<string> prefixes, string name,
+                        list<SubCommand> subcommands = []>
+    : Option<prefixes, name, KIND_JOINED_AND_SEPARATE, subcommands>;
 
 // Mix-ins for adding optional attributes.
 

diff  --git a/llvm/include/llvm/Option/OptTable.h b/llvm/include/llvm/Option/OptTable.h
index df42ee341ee58..f641ca4ac08d3 100644
--- a/llvm/include/llvm/Option/OptTable.h
+++ b/llvm/include/llvm/Option/OptTable.h
@@ -53,6 +53,13 @@ class Visibility {
 /// parts of the driver still use Option instances where convenient.
 class LLVM_ABI OptTable {
 public:
+  /// Represents a subcommand and its options in the option table.
+  struct SubCommand {
+    const char *Name;
+    const char *HelpText;
+    const char *Usage;
+  };
+
   /// Entry for a single option instance in the option data table.
   struct Info {
     unsigned PrefixesOffset;
@@ -79,6 +86,8 @@ class LLVM_ABI OptTable {
     unsigned short AliasID;
     const char *AliasArgs;
     const char *Values;
+    // Offset into OptTable's SubCommandIDsTable.
+    unsigned SubCommandIDsOffset;
 
     bool hasNoPrefix() const { return PrefixesOffset == 0; }
 
@@ -94,6 +103,21 @@ class LLVM_ABI OptTable {
                                                  getNumPrefixes(PrefixesTable));
     }
 
+    bool hasSubCommands() const { return SubCommandIDsOffset != 0; }
+
+    unsigned getNumSubCommandIDs(ArrayRef<unsigned> SubCommandIDsTable) const {
+      // We embed the number of subcommand IDs in the value of the first offset.
+      return SubCommandIDsTable[SubCommandIDsOffset];
+    }
+
+    ArrayRef<unsigned>
+    getSubCommandIDs(ArrayRef<unsigned> SubCommandIDsTable) const {
+      return hasSubCommands() ? SubCommandIDsTable.slice(
+                                    SubCommandIDsOffset + 1,
+                                    getNumSubCommandIDs(SubCommandIDsTable))
+                              : ArrayRef<unsigned>();
+    }
+
     void appendPrefixes(const StringTable &StrTable,
                         ArrayRef<StringTable::Offset> PrefixesTable,
                         SmallVectorImpl<StringRef> &Prefixes) const {
@@ -119,6 +143,22 @@ class LLVM_ABI OptTable {
     }
   };
 
+public:
+  bool isValidForSubCommand(const Info *CandidateInfo,
+                            StringRef SubCommand) const {
+    assert(!SubCommand.empty() &&
+           "This helper is only for valid registered subcommands.");
+    auto SCIT =
+        std::find_if(SubCommands.begin(), SubCommands.end(),
+                     [&](const auto &C) { return SubCommand == C.Name; });
+    assert(SCIT != SubCommands.end() &&
+           "This helper is only for valid registered subcommands.");
+    auto SubCommandIDs = CandidateInfo->getSubCommandIDs(SubCommandIDsTable);
+    unsigned CurrentSubCommandID = SCIT - &SubCommands[0];
+    return std::find(SubCommandIDs.begin(), SubCommandIDs.end(),
+                     CurrentSubCommandID) != SubCommandIDs.end();
+  }
+
 private:
   // A unified string table for these options. Individual strings are stored as
   // null terminated C-strings at offsets within this table.
@@ -134,6 +174,13 @@ class LLVM_ABI OptTable {
   ArrayRef<Info> OptionInfos;
 
   bool IgnoreCase;
+
+  /// The subcommand information table.
+  ArrayRef<SubCommand> SubCommands;
+
+  /// The subcommand IDs table.
+  ArrayRef<unsigned> SubCommandIDsTable;
+
   bool GroupedShortOptions = false;
   bool DashDashParsing = false;
   const char *EnvVar = nullptr;
@@ -168,7 +215,9 @@ class LLVM_ABI OptTable {
   /// manually call \c buildPrefixChars once they are fully constructed.
   OptTable(const StringTable &StrTable,
            ArrayRef<StringTable::Offset> PrefixesTable,
-           ArrayRef<Info> OptionInfos, bool IgnoreCase = false);
+           ArrayRef<Info> OptionInfos, bool IgnoreCase = false,
+           ArrayRef<SubCommand> SubCommands = {},
+           ArrayRef<unsigned> SubCommandIDsTable = {});
 
   /// Build (or rebuild) the PrefixChars member.
   void buildPrefixChars();
@@ -179,6 +228,8 @@ class LLVM_ABI OptTable {
   /// Return the string table used for option names.
   const StringTable &getStrTable() const { return *StrTable; }
 
+  ArrayRef<SubCommand> getSubCommands() const { return SubCommands; }
+
   /// Return the prefixes table used for option names.
   ArrayRef<StringTable::Offset> getPrefixesTable() const {
     return PrefixesTable;
@@ -410,7 +461,8 @@ class LLVM_ABI OptTable {
   ///                         texts.
   void printHelp(raw_ostream &OS, const char *Usage, const char *Title,
                  bool ShowHidden = false, bool ShowAllAliases = false,
-                 Visibility VisibilityMask = Visibility()) const;
+                 Visibility VisibilityMask = Visibility(),
+                 StringRef SubCommand = {}) const;
 
   void printHelp(raw_ostream &OS, const char *Usage, const char *Title,
                  unsigned FlagsToInclude, unsigned FlagsToExclude,
@@ -418,7 +470,8 @@ class LLVM_ABI OptTable {
 
 private:
   void internalPrintHelp(raw_ostream &OS, const char *Usage, const char *Title,
-                         bool ShowHidden, bool ShowAllAliases,
+                         StringRef SubCommand, bool ShowHidden,
+                         bool ShowAllAliases,
                          std::function<bool(const Info &)> ExcludeOption,
                          Visibility VisibilityMask) const;
 };
@@ -428,7 +481,9 @@ class GenericOptTable : public OptTable {
 protected:
   LLVM_ABI GenericOptTable(const StringTable &StrTable,
                            ArrayRef<StringTable::Offset> PrefixesTable,
-                           ArrayRef<Info> OptionInfos, bool IgnoreCase = false);
+                           ArrayRef<Info> OptionInfos, bool IgnoreCase = false,
+                           ArrayRef<SubCommand> SubCommands = {},
+                           ArrayRef<unsigned> SubCommandIDsTable = {});
 };
 
 class PrecomputedOptTable : public OptTable {
@@ -437,8 +492,11 @@ class PrecomputedOptTable : public OptTable {
                       ArrayRef<StringTable::Offset> PrefixesTable,
                       ArrayRef<Info> OptionInfos,
                       ArrayRef<StringTable::Offset> PrefixesUnionOffsets,
-                      bool IgnoreCase = false)
-      : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase) {
+                      bool IgnoreCase = false,
+                      ArrayRef<SubCommand> SubCommands = {},
+                      ArrayRef<unsigned> SubCommandIDsTable = {})
+      : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase, SubCommands,
+                 SubCommandIDsTable) {
     for (auto PrefixOffset : PrefixesUnionOffsets)
       PrefixesUnion.push_back(StrTable[PrefixOffset]);
     buildPrefixChars();
@@ -452,33 +510,36 @@ class PrecomputedOptTable : public OptTable {
 #define LLVM_MAKE_OPT_ID_WITH_ID_PREFIX(                                       \
     ID_PREFIX, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS,  \
     ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS,       \
-    METAVAR, VALUES)                                                           \
+    METAVAR, VALUES, SUBCOMMANDIDS_OFFSET)                                     \
   ID_PREFIX##ID
 
 #define LLVM_MAKE_OPT_ID(PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND,      \
                          GROUP, ALIAS, ALIASARGS, FLAGS, VISIBILITY, PARAM,    \
-                         HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES)      \
-  LLVM_MAKE_OPT_ID_WITH_ID_PREFIX(OPT_, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, \
-                                  ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS,    \
-                                  VISIBILITY, PARAM, HELPTEXT,                 \
-                                  HELPTEXTSFORVARIANTS, METAVAR, VALUES)
+                         HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES,      \
+                         SUBCOMMANDIDS_OFFSET)                                 \
+  LLVM_MAKE_OPT_ID_WITH_ID_PREFIX(                                             \
+      OPT_, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS,     \
+      ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS,     \
+      METAVAR, VALUES, SUBCOMMANDIDS_OFFSET)
 
 #define LLVM_CONSTRUCT_OPT_INFO_WITH_ID_PREFIX(                                \
     ID_PREFIX, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS,  \
     ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS,       \
-    METAVAR, VALUES)                                                           \
+    METAVAR, VALUES, SUBCOMMANDIDS_OFFSET)                                     \
   llvm::opt::OptTable::Info {                                                  \
     PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, HELPTEXT, HELPTEXTSFORVARIANTS,     \
         METAVAR, ID_PREFIX##ID, llvm::opt::Option::KIND##Class, PARAM, FLAGS,  \
-        VISIBILITY, ID_PREFIX##GROUP, ID_PREFIX##ALIAS, ALIASARGS, VALUES      \
+        VISIBILITY, ID_PREFIX##GROUP, ID_PREFIX##ALIAS, ALIASARGS, VALUES,     \
+        SUBCOMMANDIDS_OFFSET                                                   \
   }
 
 #define LLVM_CONSTRUCT_OPT_INFO(                                               \
     PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, ALIASARGS,  \
-    FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES) \
+    FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, \
+    SUBCOMMANDIDS_OFFSET)                                                      \
   LLVM_CONSTRUCT_OPT_INFO_WITH_ID_PREFIX(                                      \
       OPT_, PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS,     \
       ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS,     \
-      METAVAR, VALUES)
+      METAVAR, VALUES, SUBCOMMANDIDS_OFFSET)
 
 #endif // LLVM_OPTION_OPTTABLE_H

diff  --git a/llvm/include/llvm/Option/Option.h b/llvm/include/llvm/Option/Option.h
index 51c330a90813c..192cb3c96700a 100644
--- a/llvm/include/llvm/Option/Option.h
+++ b/llvm/include/llvm/Option/Option.h
@@ -216,6 +216,12 @@ class Option {
   /// always be false.
   LLVM_ABI bool matches(OptSpecifier ID) const;
 
+  LLVM_ABI bool isRegisteredSC(StringRef SubCommand) const {
+    assert(Info && "Must have a valid info!");
+    assert(Owner && "Must have a valid owner!");
+    return Owner->isValidForSubCommand(Info, SubCommand);
+  }
+
   /// Potentially accept the current argument, returning a new Arg instance,
   /// or 0 if the option does not accept this argument (or the argument is
   /// missing values).

diff  --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp
index c4188b3b12112..2f4e21257af09 100644
--- a/llvm/lib/Option/ArgList.cpp
+++ b/llvm/lib/Option/ArgList.cpp
@@ -14,12 +14,14 @@
 #include "llvm/Config/llvm-config.h"
 #include "llvm/Option/Arg.h"
 #include "llvm/Option/OptSpecifier.h"
+#include "llvm/Option/OptTable.h"
 #include "llvm/Option/Option.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
 #include <cassert>
+#include <cstddef>
 #include <memory>
 #include <string>
 #include <utility>
@@ -202,6 +204,42 @@ void ArgList::print(raw_ostream &O) const {
 LLVM_DUMP_METHOD void ArgList::dump() const { print(dbgs()); }
 #endif
 
+StringRef ArgList::getSubCommand(
+    ArrayRef<OptTable::SubCommand> AllSubCommands,
+    std::function<void(ArrayRef<StringRef>)> HandleMultipleSubcommands,
+    std::function<void(ArrayRef<StringRef>)> HandleOtherPositionals) const {
+
+  SmallVector<StringRef, 4> SubCommands;
+  SmallVector<StringRef, 4> OtherPositionals;
+  for (const Arg *A : *this) {
+    if (A->getOption().getKind() != Option::InputClass)
+      continue;
+
+    size_t OldSize = SubCommands.size();
+    for (const OptTable::SubCommand &CMD : AllSubCommands) {
+      if (StringRef(CMD.Name) == A->getValue())
+        SubCommands.push_back(A->getValue());
+    }
+
+    if (SubCommands.size() == OldSize)
+      OtherPositionals.push_back(A->getValue());
+  }
+
+  // Invoke callbacks if necessary.
+  if (SubCommands.size() > 1) {
+    HandleMultipleSubcommands(SubCommands);
+    return {};
+  }
+  if (!OtherPositionals.empty()) {
+    HandleOtherPositionals(OtherPositionals);
+    return {};
+  }
+
+  if (SubCommands.size() == 1)
+    return SubCommands.front();
+  return {}; // No valid usage of subcommand found.
+}
+
 void InputArgList::releaseMemory() {
   // An InputArgList always owns its arguments.
   for (Arg *A : *this)

diff  --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp
index 6d10e6154147e..14e3b0d60886d 100644
--- a/llvm/lib/Option/OptTable.cpp
+++ b/llvm/lib/Option/OptTable.cpp
@@ -79,9 +79,12 @@ OptSpecifier::OptSpecifier(const Option *Opt) : ID(Opt->getID()) {}
 
 OptTable::OptTable(const StringTable &StrTable,
                    ArrayRef<StringTable::Offset> PrefixesTable,
-                   ArrayRef<Info> OptionInfos, bool IgnoreCase)
+                   ArrayRef<Info> OptionInfos, bool IgnoreCase,
+                   ArrayRef<SubCommand> SubCommands,
+                   ArrayRef<unsigned> SubCommandIDsTable)
     : StrTable(&StrTable), PrefixesTable(PrefixesTable),
-      OptionInfos(OptionInfos), IgnoreCase(IgnoreCase) {
+      OptionInfos(OptionInfos), IgnoreCase(IgnoreCase),
+      SubCommands(SubCommands), SubCommandIDsTable(SubCommandIDsTable) {
   // Explicitly zero initialize the error to work around a bug in array
   // value-initialization on MinGW with gcc 4.3.5.
 
@@ -715,9 +718,10 @@ static const char *getOptionHelpGroup(const OptTable &Opts, OptSpecifier Id) {
 
 void OptTable::printHelp(raw_ostream &OS, const char *Usage, const char *Title,
                          bool ShowHidden, bool ShowAllAliases,
-                         Visibility VisibilityMask) const {
+                         Visibility VisibilityMask,
+                         StringRef SubCommand) const {
   return internalPrintHelp(
-      OS, Usage, Title, ShowHidden, ShowAllAliases,
+      OS, Usage, Title, SubCommand, ShowHidden, ShowAllAliases,
       [VisibilityMask](const Info &CandidateInfo) -> bool {
         return (CandidateInfo.Visibility & VisibilityMask) == 0;
       },
@@ -730,7 +734,7 @@ void OptTable::printHelp(raw_ostream &OS, const char *Usage, const char *Title,
   bool ShowHidden = !(FlagsToExclude & HelpHidden);
   FlagsToExclude &= ~HelpHidden;
   return internalPrintHelp(
-      OS, Usage, Title, ShowHidden, ShowAllAliases,
+      OS, Usage, Title, /*SubCommand=*/{}, ShowHidden, ShowAllAliases,
       [FlagsToInclude, FlagsToExclude](const Info &CandidateInfo) {
         if (FlagsToInclude && !(CandidateInfo.Flags & FlagsToInclude))
           return true;
@@ -742,16 +746,62 @@ void OptTable::printHelp(raw_ostream &OS, const char *Usage, const char *Title,
 }
 
 void OptTable::internalPrintHelp(
-    raw_ostream &OS, const char *Usage, const char *Title, bool ShowHidden,
-    bool ShowAllAliases, std::function<bool(const Info &)> ExcludeOption,
+    raw_ostream &OS, const char *Usage, const char *Title, StringRef SubCommand,
+    bool ShowHidden, bool ShowAllAliases,
+    std::function<bool(const Info &)> ExcludeOption,
     Visibility VisibilityMask) const {
   OS << "OVERVIEW: " << Title << "\n\n";
-  OS << "USAGE: " << Usage << "\n\n";
 
   // Render help text into a map of group-name to a list of (option, help)
   // pairs.
   std::map<std::string, std::vector<OptionInfo>> GroupedOptionHelp;
 
+  auto ActiveSubCommand =
+      std::find_if(SubCommands.begin(), SubCommands.end(),
+                   [&](const auto &C) { return SubCommand == C.Name; });
+  if (!SubCommand.empty()) {
+    assert(ActiveSubCommand != SubCommands.end() &&
+           "Not a valid registered subcommand.");
+    OS << ActiveSubCommand->HelpText << "\n\n";
+    if (!StringRef(ActiveSubCommand->Usage).empty())
+      OS << "USAGE: " << ActiveSubCommand->Usage << "\n\n";
+  } else {
+    OS << "USAGE: " << Usage << "\n\n";
+    if (SubCommands.size() > 1) {
+      OS << "SUBCOMMANDS:\n\n";
+      for (const auto &C : SubCommands)
+        OS << C.Name << " - " << C.HelpText << "\n";
+      OS << "\n";
+    }
+  }
+
+  auto DoesOptionBelongToSubcommand = [&](const Info &CandidateInfo) {
+    // Retrieve the SubCommandIDs registered to the given current CandidateInfo
+    // Option.
+    ArrayRef<unsigned> SubCommandIDs =
+        CandidateInfo.getSubCommandIDs(SubCommandIDsTable);
+
+    // If no registered subcommands, then only global options are to be printed.
+    // If no valid SubCommand (empty) in commandline then print the current
+    // global CandidateInfo Option.
+    if (SubCommandIDs.empty())
+      return SubCommand.empty();
+
+    // Handle CandidateInfo Option which has at least one registered SubCommand.
+    // If no valid SubCommand (empty) in commandline, this CandidateInfo option
+    // should not be printed.
+    if (SubCommand.empty())
+      return false;
+
+    // Find the ID of the valid subcommand passed in commandline (its index in
+    // the SubCommands table which contains all subcommands).
+    unsigned ActiveSubCommandID = ActiveSubCommand - &SubCommands[0];
+    // Print if the ActiveSubCommandID is registered with the CandidateInfo
+    // Option.
+    return std::find(SubCommandIDs.begin(), SubCommandIDs.end(),
+                     ActiveSubCommandID) != SubCommandIDs.end();
+  };
+
   for (unsigned Id = 1, e = getNumOptions() + 1; Id != e; ++Id) {
     // FIXME: Split out option groups.
     if (getOptionKind(Id) == Option::GroupClass)
@@ -764,6 +814,9 @@ void OptTable::internalPrintHelp(
     if (ExcludeOption(CandidateInfo))
       continue;
 
+    if (!DoesOptionBelongToSubcommand(CandidateInfo))
+      continue;
+
     // If an alias doesn't have a help text, show a help text for the aliased
     // option instead.
     const char *HelpText = getOptionHelpText(Id, VisibilityMask);
@@ -791,8 +844,11 @@ void OptTable::internalPrintHelp(
 
 GenericOptTable::GenericOptTable(const StringTable &StrTable,
                                  ArrayRef<StringTable::Offset> PrefixesTable,
-                                 ArrayRef<Info> OptionInfos, bool IgnoreCase)
-    : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase) {
+                                 ArrayRef<Info> OptionInfos, bool IgnoreCase,
+                                 ArrayRef<SubCommand> SubCommands,
+                                 ArrayRef<unsigned> SubCommandIDsTable)
+    : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase, SubCommands,
+               SubCommandIDsTable) {
 
   std::set<StringRef> TmpPrefixesUnion;
   for (auto const &Info : OptionInfos.drop_front(FirstSearchableIndex))

diff  --git a/llvm/unittests/Option/CMakeLists.txt b/llvm/unittests/Option/CMakeLists.txt
index 7be4300c0f3d2..5fefb5e85afde 100644
--- a/llvm/unittests/Option/CMakeLists.txt
+++ b/llvm/unittests/Option/CMakeLists.txt
@@ -4,11 +4,15 @@ set(LLVM_LINK_COMPONENTS
   )
 
 set(LLVM_TARGET_DEFINITIONS Opts.td)
-
 tablegen(LLVM Opts.inc -gen-opt-parser-defs)
+
+set(LLVM_TARGET_DEFINITIONS SubCommandOpts.td)
+tablegen(LLVM SubCommandOpts.inc -gen-opt-parser-defs)
+
 add_public_tablegen_target(OptsTestTableGen)
 
 add_llvm_unittest(OptionTests
   OptionParsingTest.cpp
   OptionMarshallingTest.cpp
+  OptionSubCommandsTest.cpp
   )

diff  --git a/llvm/unittests/Option/OptionMarshallingTest.cpp b/llvm/unittests/Option/OptionMarshallingTest.cpp
index 005144b91bf7f..15917cc05c51e 100644
--- a/llvm/unittests/Option/OptionMarshallingTest.cpp
+++ b/llvm/unittests/Option/OptionMarshallingTest.cpp
@@ -29,8 +29,9 @@ static const OptionWithMarshallingInfo MarshallingTable[] = {
 #define OPTION_WITH_MARSHALLING(                                               \
     PREFIX_TYPE, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, ALIASARGS,      \
     FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, \
-    SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, IMPLIED_CHECK,          \
-    IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, TABLE_INDEX)   \
+    SUBCOMMANDIDS_OFFSET, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE,   \
+    IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \
+    TABLE_INDEX)                                                               \
   {PREFIXED_NAME_OFFSET, #KEYPATH, #IMPLIED_CHECK, #IMPLIED_VALUE},
 #include "Opts.inc"
 #undef OPTION_WITH_MARSHALLING

diff  --git a/llvm/unittests/Option/OptionSubCommandsTest.cpp b/llvm/unittests/Option/OptionSubCommandsTest.cpp
new file mode 100644
index 0000000000000..e31a3262f135e
--- /dev/null
+++ b/llvm/unittests/Option/OptionSubCommandsTest.cpp
@@ -0,0 +1,252 @@
+//===- unittest/Support/OptionParsingTest.cpp - OptTable tests ------------===//
+//
+// 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/ADT/STLExtras.h"
+#include "llvm/Option/Arg.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/OptTable.h"
+#include "llvm/Option/Option.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::opt;
+
+#if defined(__clang__)
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+namespace {
+enum ID {
+  OPT_INVALID = 0,
+#define OPTION(PREFIXES, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS,       \
+               VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR,     \
+               VALUES, SUBCOMMANDIDS_OFFSET)                                   \
+  OPT_##ID,
+#include "SubCommandOpts.inc"
+#undef OPTION
+};
+#define OPTTABLE_STR_TABLE_CODE
+#include "SubCommandOpts.inc"
+#undef OPTTABLE_STR_TABLE_CODE
+
+#define OPTTABLE_PREFIXES_TABLE_CODE
+#include "SubCommandOpts.inc"
+#undef OPTTABLE_PREFIXES_TABLE_CODE
+
+#define OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE
+#include "SubCommandOpts.inc"
+#undef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE
+
+#define OPTTABLE_SUBCOMMANDS_CODE
+#include "SubCommandOpts.inc"
+#undef OPTTABLE_SUBCOMMANDS_CODE
+
+static constexpr OptTable::Info InfoTable[] = {
+#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
+#include "SubCommandOpts.inc"
+#undef OPTION
+};
+
+class TestOptSubCommandTable : public GenericOptTable {
+public:
+  TestOptSubCommandTable(bool IgnoreCase = false)
+      : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable,
+                        /*IgnoreCase=*/false, OptionSubCommands,
+                        OptionSubCommandIDsTable) {}
+};
+
+// Test fixture
+template <typename T> class OptSubCommandTableTest : public ::testing::Test {};
+
+// Test both precomputed and computed OptTables with the same suite of tests.
+using OptSubCommandTableTestTypes = ::testing::Types<TestOptSubCommandTable>;
+
+TYPED_TEST_SUITE(OptSubCommandTableTest, OptSubCommandTableTestTypes, );
+
+TYPED_TEST(OptSubCommandTableTest, SubCommandParsing) {
+  TypeParam T;
+  unsigned MAI, MAC;
+
+  std::string ErrMsg;
+  raw_string_ostream RSO1(ErrMsg);
+
+  auto HandleMultipleSubcommands = [&](ArrayRef<StringRef> SubCommands) {
+    ErrMsg.clear();
+    RSO1 << "Multiple subcommands passed\n";
+    for (auto SC : SubCommands)
+      RSO1 << "\n" << SC;
+  };
+
+  auto HandleOtherPositionals = [&](ArrayRef<StringRef> Positionals) {
+    ErrMsg.clear();
+    RSO1 << "Unregistered positionals passed\n";
+    for (auto SC : Positionals)
+      RSO1 << "\n" << SC;
+  };
+
+  {
+    // Test case 1: Toplevel option, no subcommand
+    const char *Args[] = {"-version"};
+    InputArgList AL = T.ParseArgs(Args, MAI, MAC);
+    EXPECT_TRUE(AL.hasArg(OPT_version));
+    StringRef SC = AL.getSubCommand(
+        T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
+    EXPECT_TRUE(SC.empty());
+    EXPECT_FALSE(AL.hasArg(OPT_uppercase));
+    EXPECT_FALSE(AL.hasArg(OPT_lowercase));
+  }
+
+  {
+    // Test case 2: Subcommand 'foo' with its valid options
+    const char *Args[] = {"foo", "-uppercase"};
+    InputArgList AL = T.ParseArgs(Args, MAI, MAC);
+    StringRef SC = AL.getSubCommand(
+        T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
+    EXPECT_EQ(SC, "foo");
+    EXPECT_TRUE(AL.hasArg(OPT_uppercase));
+    EXPECT_FALSE(AL.hasArg(OPT_lowercase));
+    EXPECT_FALSE(AL.hasArg(OPT_version));
+    EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed"))
+        << "Did not expect error message as this is a valid use case.";
+    EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed"))
+        << "Did not expect error message as this is a valid use case.";
+  }
+
+  {
+    // Test case 3: Check valid use of subcommand which follows a valid
+    // subcommand option.
+    const char *Args[] = {"-uppercase", "foo"};
+    InputArgList AL = T.ParseArgs(Args, MAI, MAC);
+    StringRef SC = AL.getSubCommand(
+        T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
+    EXPECT_EQ(SC, "foo");
+    EXPECT_TRUE(AL.hasArg(OPT_uppercase));
+    EXPECT_FALSE(AL.hasArg(OPT_lowercase));
+    EXPECT_FALSE(AL.hasArg(OPT_version));
+    EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed"))
+        << "Did not expect error message as this is a valid use case.";
+    EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed"))
+        << "Did not expect error message as this is a valid use case.";
+  }
+
+  {
+    // Test case 4: Check invalid use of passing multiple subcommands.
+    const char *Args[] = {"-uppercase", "foo", "bar"};
+    InputArgList AL = T.ParseArgs(Args, MAI, MAC);
+    StringRef SC = AL.getSubCommand(
+        T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
+    // No valid subcommand should be returned as this is an invalid invocation.
+    EXPECT_TRUE(SC.empty());
+    // Expect the multiple subcommands error message.
+    EXPECT_NE(std::string::npos, ErrMsg.find("Multiple subcommands passed"));
+    EXPECT_NE(std::string::npos, ErrMsg.find("foo"));
+    EXPECT_NE(std::string::npos, ErrMsg.find("bar"));
+    EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed"))
+        << "Did not expect error message as this is a valid use case.";
+  }
+
+  {
+    // Test case 5: Check invalid use of passing unregistered subcommands.
+    const char *Args[] = {"foobar"};
+    InputArgList AL = T.ParseArgs(Args, MAI, MAC);
+    StringRef SC = AL.getSubCommand(
+        T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
+    // No valid subcommand should be returned as this is an invalid invocation.
+    EXPECT_TRUE(SC.empty());
+    // Expect the unregistered subcommands error message.
+    EXPECT_NE(std::string::npos,
+              ErrMsg.find("Unregistered positionals passed"));
+    EXPECT_NE(std::string::npos, ErrMsg.find("foobar"));
+  }
+
+  {
+    // Test case 6: Check invalid use of a valid subcommand which follows a
+    // valid subcommand option but the option is not registered with the given
+    // subcommand.
+    const char *Args[] = {"-lowercase", "bar"};
+    InputArgList AL = T.ParseArgs(Args, MAI, MAC);
+    StringRef SC = AL.getSubCommand(
+        T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
+    auto HandleSubCommandArg = [&](ID OptionType) {
+      if (!AL.hasArg(OptionType))
+        return false;
+      auto O = T.getOption(OptionType);
+      if (!O.isRegisteredSC(SC)) {
+        ErrMsg.clear();
+        RSO1 << "Option [" << O.getName() << "] is not valid for SubCommand ["
+             << SC << "]\n";
+        return false;
+      }
+      return true;
+    };
+    EXPECT_EQ(SC, "bar");                  // valid subcommand
+    EXPECT_TRUE(AL.hasArg(OPT_lowercase)); // valid option
+    EXPECT_FALSE(HandleSubCommandArg(OPT_lowercase));
+    EXPECT_NE(
+        std::string::npos,
+        ErrMsg.find("Option [lowercase] is not valid for SubCommand [bar]"));
+  }
+}
+
+TYPED_TEST(OptSubCommandTableTest, SubCommandHelp) {
+  TypeParam T;
+  std::string Help;
+  raw_string_ostream RSO(Help);
+
+  // Toplevel help
+  T.printHelp(RSO, "Test Usage String", "OverviewString");
+  EXPECT_NE(std::string::npos, Help.find("OVERVIEW:"));
+  EXPECT_NE(std::string::npos, Help.find("OverviewString"));
+  EXPECT_NE(std::string::npos, Help.find("USAGE:"));
+  EXPECT_NE(std::string::npos, Help.find("Test Usage String"));
+  EXPECT_NE(std::string::npos, Help.find("SUBCOMMANDS:"));
+  EXPECT_NE(std::string::npos, Help.find("foo"));
+  EXPECT_NE(std::string::npos, Help.find("bar"));
+  EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo."));
+  EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar."));
+  EXPECT_NE(std::string::npos, Help.find("OPTIONS:"));
+  EXPECT_NE(std::string::npos, Help.find("--help"));
+  EXPECT_NE(std::string::npos, Help.find("-version"));
+  // uppercase is not a global option and should not be shown.
+  EXPECT_EQ(std::string::npos, Help.find("-uppercase"));
+
+  // Help for subcommand foo
+  Help.clear();
+  StringRef SC1 = "foo";
+  T.printHelp(RSO, "Test Usage String", "OverviewString", false, false,
+              Visibility(), SC1);
+  EXPECT_NE(std::string::npos, Help.find("OVERVIEW:"));
+  EXPECT_NE(std::string::npos, Help.find("OverviewString"));
+  // SubCommand "foo" definition for tablegen has NO dedicated usage string so
+  // not expected to see USAGE.
+  EXPECT_EQ(std::string::npos, Help.find("USAGE:"));
+  EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo."));
+  EXPECT_NE(std::string::npos, Help.find("-uppercase"));
+  EXPECT_NE(std::string::npos, Help.find("-lowercase"));
+  EXPECT_EQ(std::string::npos, Help.find("-version"));
+  EXPECT_EQ(std::string::npos, Help.find("SUBCOMMANDS:"));
+
+  // Help for subcommand bar
+  Help.clear();
+  StringRef SC2 = "bar";
+  T.printHelp(RSO, "Test Usage String", "OverviewString", false, false,
+              Visibility(), SC2);
+  EXPECT_NE(std::string::npos, Help.find("OVERVIEW:"));
+  EXPECT_NE(std::string::npos, Help.find("OverviewString"));
+  // SubCommand "bar" definition for tablegen has a dedicated usage string.
+  EXPECT_NE(std::string::npos, Help.find("USAGE:"));
+  EXPECT_NE(std::string::npos, Help.find("Subcommand bar <options>"));
+  EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar."));
+  EXPECT_NE(std::string::npos, Help.find("-uppercase"));
+  // lowercase is not an option for bar and should not be shown.
+  EXPECT_EQ(std::string::npos, Help.find("-lowercase"));
+  // version is a global option and should not be shown.
+  EXPECT_EQ(std::string::npos, Help.find("-version"));
+}
+} // end anonymous namespace

diff  --git a/llvm/unittests/Option/SubCommandOpts.td b/llvm/unittests/Option/SubCommandOpts.td
new file mode 100644
index 0000000000000..b9750da0f3558
--- /dev/null
+++ b/llvm/unittests/Option/SubCommandOpts.td
@@ -0,0 +1,16 @@
+include "llvm/Option/OptParser.td"
+
+def sc_foo : SubCommand<"foo", "HelpText for SubCommand foo.">;
+
+def sc_bar : SubCommand<"bar", "HelpText for SubCommand bar.",
+                        "Subcommand bar <options>">;
+
+def help : Flag<["--"], "help">, HelpText<"Subcommand <subcommand> <options>">;
+
+def version : Flag<["-"], "version">, HelpText<"Display the version number">;
+
+def uppercase : Flag<["-"], "uppercase", [sc_foo, sc_bar]>,
+                HelpText<"Print in uppercase">;
+
+def lowercase : Flag<["-"], "lowercase", [sc_foo]>,
+                HelpText<"Print in lowercase">;

diff  --git a/llvm/utils/TableGen/OptionParserEmitter.cpp b/llvm/utils/TableGen/OptionParserEmitter.cpp
index a470fbbcadd58..48ae1a0a92b1c 100644
--- a/llvm/utils/TableGen/OptionParserEmitter.cpp
+++ b/llvm/utils/TableGen/OptionParserEmitter.cpp
@@ -9,8 +9,10 @@
 #include "Common/OptEmitter.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/Twine.h"
+#include "llvm/Option/OptTable.h"
 #include "llvm/Support/InterleavedRange.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TableGen/Record.h"
@@ -258,6 +260,9 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
   std::vector<const Record *> Opts = Records.getAllDerivedDefinitions("Option");
   llvm::sort(Opts, IsOptionRecordsLess);
 
+  std::vector<const Record *> SubCommands =
+      Records.getAllDerivedDefinitions("SubCommand");
+
   emitSourceFileHeader("Option Parsing Definitions", OS);
 
   // Generate prefix groups.
@@ -271,6 +276,35 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
     Prefixes.try_emplace(PrefixKey, 0);
   }
 
+  // Generate sub command groups.
+  typedef SmallVector<StringRef, 2> SubCommandKeyT;
+  typedef std::map<SubCommandKeyT, unsigned> SubCommandIDsT;
+  SubCommandIDsT SubCommandIDs;
+
+  auto PrintSubCommandIdsOffset = [&SubCommandIDs, &OS](const Record &R) {
+    if (R.getValue("SubCommands") != nullptr) {
+      std::vector<const Record *> SubCommands =
+          R.getValueAsListOfDefs("SubCommands");
+      SubCommandKeyT SubCommandKey;
+      for (const auto &SubCommand : SubCommands)
+        SubCommandKey.push_back(SubCommand->getName());
+      OS << SubCommandIDs[SubCommandKey];
+    } else {
+      // The option SubCommandIDsOffset (for default top level toolname is 0).
+      OS << " 0";
+    }
+  };
+
+  SubCommandIDs.try_emplace(SubCommandKeyT(), 0);
+  for (const Record &R : llvm::make_pointee_range(Opts)) {
+    std::vector<const Record *> RSubCommands =
+        R.getValueAsListOfDefs("SubCommands");
+    SubCommandKeyT SubCommandKey;
+    for (const auto &SubCommand : RSubCommands)
+      SubCommandKey.push_back(SubCommand->getName());
+    SubCommandIDs.try_emplace(SubCommandKey, 0);
+  }
+
   DenseSet<StringRef> PrefixesUnionSet;
   for (const auto &[Prefix, _] : Prefixes)
     PrefixesUnionSet.insert_range(Prefix);
@@ -323,6 +357,40 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
   OS << "\n};\n";
   OS << "#endif // OPTTABLE_PREFIXES_TABLE_CODE\n\n";
 
+  // Dump subcommand IDs.
+  OS << "/////////";
+  OS << "// SubCommand IDs\n\n";
+  OS << "#ifdef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE\n";
+  OS << "static constexpr unsigned OptionSubCommandIDsTable[] = {\n";
+  {
+    // Ensure the first subcommand set is always empty.
+    assert(!SubCommandIDs.empty() &&
+           "We should always emit an empty set of subcommands");
+    assert(SubCommandIDs.begin()->first.empty() &&
+           "First subcommand set should always be empty");
+    llvm::ListSeparator Sep(",\n");
+    unsigned CurIndex = 0;
+    for (auto &[SubCommand, SubCommandIndex] : SubCommandIDs) {
+      // First emit the number of subcommand strings in this list of
+      // subcommands.
+      OS << Sep << "  " << SubCommand.size() << " /* subcommands */";
+      SubCommandIndex = CurIndex;
+      assert((CurIndex == 0 || !SubCommand.empty()) &&
+             "Only first subcommand set should be empty!");
+      for (const auto &SubCommandKey : SubCommand) {
+        auto It = std::find_if(
+            SubCommands.begin(), SubCommands.end(),
+            [&](const Record *R) { return R->getName() == SubCommandKey; });
+        assert(It != SubCommands.end() && "SubCommand not found");
+        OS << ", " << std::distance(SubCommands.begin(), It) << " /* '"
+           << SubCommandKey << "' */";
+      }
+      CurIndex += SubCommand.size() + 1;
+    }
+  }
+  OS << "\n};\n";
+  OS << "#endif // OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE\n\n";
+
   // Dump prefixes union.
   OS << "/////////\n";
   OS << "// Prefix Union\n\n";
@@ -400,7 +468,12 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
     OS << ", nullptr";
 
     // The option Values (unused for groups).
-    OS << ", nullptr)\n";
+    OS << ", nullptr";
+
+    // The option SubCommandIDsOffset.
+    OS << ", ";
+    PrintSubCommandIdsOffset(R);
+    OS << ")\n";
   }
   OS << "\n";
 
@@ -527,6 +600,10 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
       OS << getOptionName(R) << "_Values";
     else
       OS << "nullptr";
+
+    // The option SubCommandIDsOffset.
+    OS << ", ";
+    PrintSubCommandIdsOffset(R);
   };
 
   auto IsMarshallingOption = [](const Record &R) {
@@ -595,6 +672,19 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) {
 
   OS << "#endif // SIMPLE_ENUM_VALUE_TABLE\n";
   OS << "\n";
+  OS << "/////////\n";
+  OS << "\n// SubCommands\n\n";
+  OS << "#ifdef OPTTABLE_SUBCOMMANDS_CODE\n";
+  OS << "static constexpr llvm::opt::OptTable::SubCommand OptionSubCommands[] "
+        "= "
+        "{\n";
+  for (const Record *SubCommand : SubCommands) {
+    OS << "  { \"" << SubCommand->getValueAsString("Name") << "\", ";
+    OS << "\"" << SubCommand->getValueAsString("HelpText") << "\", ";
+    OS << "\"" << SubCommand->getValueAsString("Usage") << "\" },\n";
+  }
+  OS << "};\n";
+  OS << "#endif // OPTTABLE_SUBCOMMANDS_CODE\n\n";
 
   OS << "\n";
 }


        


More information about the llvm-commits mailing list