[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