https://github.com/Prabhuk created https://github.com/llvm/llvm-project/pull/155026
Implement support for `subcommands` in OptTable to attain feature parity with `cl`. Issue: https://github.com/llvm/llvm-project/issues/108307 >From 3584c6aacac629c51c4d1e8e08258c0c24f3a165 Mon Sep 17 00:00:00 2001 From: prabhukr <prabh...@google.com> Date: Tue, 19 Aug 2025 15:48:47 -0700 Subject: [PATCH] [OptTable] Subcommand support. TODO: Add tests. --- clang-tools-extra/clangd/CompileCommands.cpp | 2 +- clang/lib/Frontend/CompilerInvocation.cpp | 11 +- clang/tools/clang-installapi/Options.h | 2 +- lld/MachO/DriverUtils.cpp | 5 +- lld/MinGW/Driver.cpp | 5 +- lld/wasm/Driver.cpp | 5 +- llvm/examples/CMakeLists.txt | 1 + llvm/examples/OptSubcommand/CMakeLists.txt | 19 +++ llvm/examples/OptSubcommand/Opts.td | 16 +++ .../examples/OptSubcommand/llvm-hello-sub.cpp | 94 ++++++++++++++ llvm/include/llvm/Option/ArgList.h | 3 + llvm/include/llvm/Option/OptParser.td | 58 ++++++--- llvm/include/llvm/Option/OptTable.h | 85 ++++++++++--- llvm/lib/Option/ArgList.cpp | 12 ++ llvm/lib/Option/OptTable.cpp | 116 +++++++++++++++--- .../Option/OptionMarshallingTest.cpp | 5 +- llvm/utils/TableGen/OptionParserEmitter.cpp | 99 ++++++++++++++- 17 files changed, 473 insertions(+), 65 deletions(-) create mode 100644 llvm/examples/OptSubcommand/CMakeLists.txt create mode 100644 llvm/examples/OptSubcommand/Opts.td create mode 100644 llvm/examples/OptSubcommand/llvm-hello-sub.cpp diff --git a/clang-tools-extra/clangd/CompileCommands.cpp b/clang-tools-extra/clangd/CompileCommands.cpp index 80391fe8cce25..7ab04894a63b5 100644 --- a/clang-tools-extra/clangd/CompileCommands.cpp +++ b/clang-tools-extra/clangd/CompileCommands.cpp @@ -465,7 +465,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, COMMANDIDS_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 a4d18966be35f..681feda59c623 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, COMMANDIDS_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) \ + COMMANDIDS_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..918723a334a09 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, COMMANDIDS_OFFSET) \ OPT_##ID, #include "InstallAPIOpts.inc" LastOption diff --git a/lld/MachO/DriverUtils.cpp b/lld/MachO/DriverUtils.cpp index a3b722f13daca..da7653d15b7f2 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, COMMANDIDS_OFFSET) \ {PREFIX, \ NAME, \ HELPTEXT, \ @@ -59,7 +59,8 @@ static constexpr OptTable::Info optInfo[] = { OPT_##GROUP, \ OPT_##ALIAS, \ ALIASARGS, \ - VALUES}, + VALUES, \ + COMMANDIDS_OFFSET}, #include "Options.inc" #undef OPTION }; diff --git a/lld/MinGW/Driver.cpp b/lld/MinGW/Driver.cpp index 5098dbd77b4fd..306c55135b677 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, COMMANDIDS_OFFSET) \ {PREFIX, \ NAME, \ HELPTEXT, \ @@ -83,7 +83,8 @@ static constexpr opt::OptTable::Info infoTable[] = { OPT_##GROUP, \ OPT_##ALIAS, \ ALIASARGS, \ - VALUES}, + VALUES, \ + COMMANDIDS_OFFSET}, #include "Options.inc" #undef OPTION }; diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp index 1c5d21c06f5af..baf61da44fad9 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, COMMANDIDS_OFFSET) \ {PREFIX, \ NAME, \ HELPTEXT, \ @@ -171,7 +171,8 @@ static constexpr opt::OptTable::Info optInfo[] = { OPT_##GROUP, \ OPT_##ALIAS, \ ALIASARGS, \ - VALUES}, + VALUES, \ + COMMANDIDS_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..8c4f66b8c3043 --- /dev/null +++ b/llvm/examples/OptSubcommand/Opts.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.">; + +def help : Flag<["--"], "help">, HelpText<"Top Level Help Text for the tool.">; + +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..aecc981e485c1 --- /dev/null +++ b/llvm/examples/OptSubcommand/llvm-hello-sub.cpp @@ -0,0 +1,94 @@ +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.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, COMMANDIDS_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_COMMAND_IDS_TABLE_CODE +#include "Opts.inc" +#undef OPTTABLE_COMMAND_IDS_TABLE_CODE + +#define OPTTABLE_COMMANDS_CODE +#include "Opts.inc" +#undef OPTTABLE_COMMANDS_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, + OptionCommands, OptionCommandIDsTable) {} +}; +} // namespace + +int main(int argc, char **argv) { + InitLLVM X(argc, argv); + HelloSubOptTable T; + unsigned MissingArgIndex, MissingArgCount; + InputArgList Args = T.ParseArgs(ArrayRef(argv + 1, argc - 1), MissingArgIndex, + MissingArgCount); + + StringRef Subcommand = Args.getSubcommand(); + if (Args.hasArg(OPT_help)) { + T.printHelp(llvm::outs(), "llvm-hello-sub [subcommand] [options]", + "LLVM Hello Subcommand Example", false, false, Visibility(), + Subcommand); + return 0; + } + + if (Args.hasArg(OPT_version)) { + llvm::outs() << "LLVM Hello Subcommand Example 1.0\n"; + return 0; + } + + if (Subcommand == "foo") { + if (Args.hasArg(OPT_uppercase)) + llvm::outs() << "FOO\n"; + else if (Args.hasArg(OPT_lowercase)) + llvm::outs() << "foo\n"; + else + llvm::errs() << "error: unknown option for subcommand '" << Subcommand + << "'. See -help.\n"; + return 1; + } else if (Subcommand == "bar") { + if (Args.hasArg(OPT_lowercase)) + llvm::outs() << "bar\n"; + else if (Args.hasArg(OPT_uppercase)) + llvm::outs() << "BAR\n"; + else + llvm::errs() << "error: unknown option for subcommand '" << Subcommand + << "'. See -help.\n"; + } else { + llvm::errs() << "error: unknown subcommand '" << Subcommand + << "'. See --help.\n"; + return 1; + } + + return 0; +} diff --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h index 313164bc29689..2394b2e8301b8 100644 --- a/llvm/include/llvm/Option/ArgList.h +++ b/llvm/include/llvm/Option/ArgList.h @@ -280,6 +280,9 @@ class ArgList { /// list. virtual unsigned getNumInputArgStrings() const = 0; + /// getSubcommand - Return the active subcommand, if one exists. + LLVM_ABI StringRef getSubcommand() const; + /// @} /// @name Argument Lookup Utilities /// @{ diff --git a/llvm/include/llvm/Option/OptParser.td b/llvm/include/llvm/Option/OptParser.td index 9fd606b0d6fcb..2bf920be8d946 100644 --- a/llvm/include/llvm/Option/OptParser.td +++ b/llvm/include/llvm/Option/OptParser.td @@ -98,7 +98,21 @@ class HelpTextVariant<list<OptionVisibility> visibilities, string text> { string Text = text; } -class Option<list<string> prefixes, string name, OptionKind kind> { +// Base class for TopLevelCommand and Subcommands. +class Command<string name> { string Name = name; } + +// Class definition for positional subcommands. +class Subcommand<string name, string helpText> : Command<name> { + string HelpText = helpText; +} + +// Compile time representation for top level command (aka toolname). +// Offers backward compatibility with existing Option class definitions before +// introduction of commandGroup in Option class to support subcommands. +def TopLevelCommand : Command<"TopLevelCommand">; + +class Option<list<string> prefixes, string name, OptionKind kind, + list<Command> commandGroup = [TopLevelCommand]> { string EnumName = ?; // Uses the def name if undefined. list<string> Prefixes = prefixes; string Name = name; @@ -129,26 +143,34 @@ class Option<list<string> prefixes, string name, OptionKind kind> { code ValueMerger = "mergeForwardValue"; code ValueExtractor = "extractForwardValue"; list<code> NormalizedValues = ?; + list<Command> CommandGroup = commandGroup; } // 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<Command> commandGroup = [TopLevelCommand]> + : Option<prefixes, name, KIND_FLAG, commandGroup>; +class Joined<list<string> prefixes, string name, + list<Command> commandGroup = [TopLevelCommand]> + : Option<prefixes, name, KIND_JOINED, commandGroup>; +class Separate<list<string> prefixes, string name, + list<Command> commandGroup = [TopLevelCommand]> + : Option<prefixes, name, KIND_SEPARATE, commandGroup>; +class CommaJoined<list<string> prefixes, string name, + list<Command> commandGroup = [TopLevelCommand]> + : Option<prefixes, name, KIND_COMMAJOINED, commandGroup>; +class MultiArg<list<string> prefixes, string name, int numargs, + list<Command> commandGroup = [TopLevelCommand]> + : Option<prefixes, name, KIND_MULTIARG, commandGroup> { 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<Command> commandGroup = [TopLevelCommand]> + : Option<prefixes, name, KIND_JOINED_OR_SEPARATE, commandGroup>; +class JoinedAndSeparate<list<string> prefixes, string name, + list<Command> commandGroup = [TopLevelCommand]> + : Option<prefixes, name, KIND_JOINED_AND_SEPARATE, commandGroup>; // Mix-ins for adding optional attributes. @@ -271,7 +293,7 @@ class ValueExtractor<code extractor> { code ValueExtractor = extractor; } // FIXME: Have generator validate that these appear in correct position (and // aren't duplicated). -def INPUT : Option<[], "<input>", KIND_INPUT>; -def UNKNOWN : Option<[], "<unknown>", KIND_UNKNOWN>; +def INPUT : Option<[], "<input>", KIND_INPUT, [TopLevelCommand]>; +def UNKNOWN : Option<[], "<unknown>", KIND_UNKNOWN, [TopLevelCommand]>; -#endif // LLVM_OPTION_OPTPARSER_TD +#endif // LLVM_OPTION_OPTPARSER_TD \ No newline at end of file diff --git a/llvm/include/llvm/Option/OptTable.h b/llvm/include/llvm/Option/OptTable.h index df42ee341ee58..3f362521a7e64 100644 --- a/llvm/include/llvm/Option/OptTable.h +++ b/llvm/include/llvm/Option/OptTable.h @@ -10,6 +10,7 @@ #define LLVM_OPTION_OPTTABLE_H #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringTable.h" @@ -53,6 +54,12 @@ 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 Command { + const char *Name; + const char *HelpText; + }; + /// Entry for a single option instance in the option data table. struct Info { unsigned PrefixesOffset; @@ -79,6 +86,7 @@ class LLVM_ABI OptTable { unsigned short AliasID; const char *AliasArgs; const char *Values; + unsigned CommandIDsOffset; bool hasNoPrefix() const { return PrefixesOffset == 0; } @@ -94,6 +102,20 @@ class LLVM_ABI OptTable { getNumPrefixes(PrefixesTable)); } + bool hasCommands() const { return CommandIDsOffset != 0; } + + unsigned getNumCommandIDs(ArrayRef<unsigned> CommandIDsTable) const { + // We embed the number of command IDs in the value of the first offset. + return CommandIDsTable[CommandIDsOffset]; + } + + ArrayRef<unsigned> getCommandIDs(ArrayRef<unsigned> CommandIDsTable) const { + return hasCommands() + ? CommandIDsTable.slice(CommandIDsOffset + 1, + getNumCommandIDs(CommandIDsTable)) + : ArrayRef<unsigned>(); + } + void appendPrefixes(const StringTable &StrTable, ArrayRef<StringTable::Offset> PrefixesTable, SmallVectorImpl<StringRef> &Prefixes) const { @@ -133,6 +155,12 @@ class LLVM_ABI OptTable { /// The option information table. ArrayRef<Info> OptionInfos; + /// The command information table. + ArrayRef<Command> Commands; + + /// The command IDs table. + ArrayRef<unsigned> CommandIDsTable; + bool IgnoreCase; bool GroupedShortOptions = false; bool DashDashParsing = false; @@ -169,6 +197,10 @@ class LLVM_ABI OptTable { OptTable(const StringTable &StrTable, ArrayRef<StringTable::Offset> PrefixesTable, ArrayRef<Info> OptionInfos, bool IgnoreCase = false); + OptTable(const StringTable &StrTable, + ArrayRef<StringTable::Offset> PrefixesTable, + ArrayRef<Info> OptionInfos, ArrayRef<Command> Commands, + ArrayRef<unsigned> CommandIDsTable, bool IgnoreCase = false); /// Build (or rebuild) the PrefixChars member. void buildPrefixChars(); @@ -350,6 +382,7 @@ class LLVM_ABI OptTable { private: std::unique_ptr<Arg> internalParseOneArg(const ArgList &Args, unsigned &Index, + const Command *ActiveCommand, std::function<bool(const Option &)> ExcludeOption) const; public: @@ -410,7 +443,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 +452,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,21 +463,38 @@ 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) + : GenericOptTable(StrTable, PrefixesTable, OptionInfos, {}, {}, + IgnoreCase) {} + LLVM_ABI GenericOptTable(const StringTable &StrTable, + ArrayRef<StringTable::Offset> PrefixesTable, + ArrayRef<Info> OptionInfos, + ArrayRef<Command> Commands, + ArrayRef<unsigned> CommandIDsTable, + bool IgnoreCase = false); }; class PrecomputedOptTable : public OptTable { protected: PrecomputedOptTable(const StringTable &StrTable, ArrayRef<StringTable::Offset> PrefixesTable, - ArrayRef<Info> OptionInfos, + ArrayRef<Info> OptionInfos, ArrayRef<Command> Commands, + ArrayRef<unsigned> CommandIDsTable, ArrayRef<StringTable::Offset> PrefixesUnionOffsets, bool IgnoreCase = false) - : OptTable(StrTable, PrefixesTable, OptionInfos, IgnoreCase) { + : OptTable(StrTable, PrefixesTable, OptionInfos, Commands, + CommandIDsTable, IgnoreCase) { for (auto PrefixOffset : PrefixesUnionOffsets) PrefixesUnion.push_back(StrTable[PrefixOffset]); buildPrefixChars(); } + PrecomputedOptTable(const StringTable &StrTable, + ArrayRef<StringTable::Offset> PrefixesTable, + ArrayRef<Info> OptionInfos, + ArrayRef<StringTable::Offset> PrefixesUnionOffsets, + bool IgnoreCase = false) + : PrecomputedOptTable(StrTable, PrefixesTable, OptionInfos, {}, {}, + PrefixesUnionOffsets, IgnoreCase) {} }; } // end namespace opt @@ -452,33 +504,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, COMMANDIDS_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, \ + COMMANDIDS_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, COMMANDIDS_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, COMMANDIDS_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, \ + COMMANDIDS_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, \ + COMMANDIDS_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, COMMANDIDS_OFFSET) #endif // LLVM_OPTION_OPTTABLE_H diff --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp index c4188b3b12112..7c94e78c41e67 100644 --- a/llvm/lib/Option/ArgList.cpp +++ b/llvm/lib/Option/ArgList.cpp @@ -20,6 +20,7 @@ #include "llvm/Support/raw_ostream.h" #include <algorithm> #include <cassert> +#include <cstddef> #include <memory> #include <string> #include <utility> @@ -202,6 +203,17 @@ void ArgList::print(raw_ostream &O) const { LLVM_DUMP_METHOD void ArgList::dump() const { print(dbgs()); } #endif +StringRef ArgList::getSubcommand() const { + for (const Arg *A : *this) { + if (A->getOption().getKind() == Option::InputClass) { + if (StringRef(A->getValue()).empty()) + return StringRef(); + return A->getValue(); + } + } + return StringRef(); +} + 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..80c36407e3b70 100644 --- a/llvm/lib/Option/OptTable.cpp +++ b/llvm/lib/Option/OptTable.cpp @@ -80,8 +80,15 @@ OptSpecifier::OptSpecifier(const Option *Opt) : ID(Opt->getID()) {} OptTable::OptTable(const StringTable &StrTable, ArrayRef<StringTable::Offset> PrefixesTable, ArrayRef<Info> OptionInfos, bool IgnoreCase) + : OptTable(StrTable, PrefixesTable, OptionInfos, {}, {}, IgnoreCase) {} + +OptTable::OptTable(const StringTable &StrTable, + ArrayRef<StringTable::Offset> PrefixesTable, + ArrayRef<Info> OptionInfos, ArrayRef<Command> Commands, + ArrayRef<unsigned> CommandIDsTable, bool IgnoreCase) : StrTable(&StrTable), PrefixesTable(PrefixesTable), - OptionInfos(OptionInfos), IgnoreCase(IgnoreCase) { + OptionInfos(OptionInfos), Commands(Commands), + CommandIDsTable(CommandIDsTable), IgnoreCase(IgnoreCase) { // Explicitly zero initialize the error to work around a bug in array // value-initialization on MinGW with gcc 4.3.5. @@ -415,16 +422,18 @@ std::unique_ptr<Arg> OptTable::parseOneArgGrouped(InputArgList &Args, std::unique_ptr<Arg> OptTable::ParseOneArg(const ArgList &Args, unsigned &Index, Visibility VisibilityMask) const { - return internalParseOneArg(Args, Index, [VisibilityMask](const Option &Opt) { - return !Opt.hasVisibilityFlag(VisibilityMask); - }); + return internalParseOneArg(Args, Index, nullptr, + [VisibilityMask](const Option &Opt) { + return !Opt.hasVisibilityFlag(VisibilityMask); + }); } std::unique_ptr<Arg> OptTable::ParseOneArg(const ArgList &Args, unsigned &Index, unsigned FlagsToInclude, unsigned FlagsToExclude) const { return internalParseOneArg( - Args, Index, [FlagsToInclude, FlagsToExclude](const Option &Opt) { + Args, Index, nullptr, + [FlagsToInclude, FlagsToExclude](const Option &Opt) { if (FlagsToInclude && !Opt.hasFlag(FlagsToInclude)) return true; if (Opt.hasFlag(FlagsToExclude)) @@ -434,7 +443,7 @@ std::unique_ptr<Arg> OptTable::ParseOneArg(const ArgList &Args, unsigned &Index, } std::unique_ptr<Arg> OptTable::internalParseOneArg( - const ArgList &Args, unsigned &Index, + const ArgList &Args, unsigned &Index, const Command *ActiveCommand, std::function<bool(const Option &)> ExcludeOption) const { unsigned Prev = Index; StringRef Str = Args.getArgString(Index); @@ -476,6 +485,18 @@ std::unique_ptr<Arg> OptTable::internalParseOneArg( if (ExcludeOption(Opt)) continue; + // If a command is active, accept options for that command. + if (ActiveCommand) { + unsigned ActiveCommandID = ActiveCommand - Commands.data(); + ArrayRef<unsigned> CommandIDs = Start->getCommandIDs(CommandIDsTable); + bool IsInCommand = is_contained(CommandIDs, ActiveCommandID); + // Command ID 0 is the top level command. + bool IsGlobal = is_contained(CommandIDs, 0); + // If not part of the command and not a global option, continue. + if (!IsInCommand && !IsGlobal) + continue; + } + // See if this option matches. if (std::unique_ptr<Arg> A = Opt.accept(Args, StringRef(Args.getArgString(Index), ArgSize), @@ -534,6 +555,21 @@ InputArgList OptTable::internalParseArgs( MissingArgIndex = MissingArgCount = 0; unsigned Index = 0, End = ArgArr.size(); + const Command *ActiveCommand = nullptr; + + // Look for subcommand which is positional. + if (!Commands.empty() && Index < End) { + StringRef FirstArg = Args.getArgString(Index); + if (isInput(PrefixesUnion, FirstArg)) { + for (const auto &C : Commands) { + if (FirstArg == C.Name) { + ActiveCommand = &C; + break; + } + } + } + } + while (Index < End) { // Ingore nullptrs, they are response file's EOL markers if (Args.getArgString(Index) == nullptr) { @@ -558,9 +594,10 @@ InputArgList OptTable::internalParseArgs( } unsigned Prev = Index; - std::unique_ptr<Arg> A = GroupedShortOptions - ? parseOneArgGrouped(Args, Index) - : internalParseOneArg(Args, Index, ExcludeOption); + std::unique_ptr<Arg> A = + GroupedShortOptions + ? parseOneArgGrouped(Args, Index) + : internalParseOneArg(Args, Index, ActiveCommand, ExcludeOption); assert((Index > Prev || GroupedShortOptions) && "Parser failed to consume argument."); @@ -715,9 +752,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 +768,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, {}, ShowHidden, ShowAllAliases, [FlagsToInclude, FlagsToExclude](const Info &CandidateInfo) { if (FlagsToInclude && !(CandidateInfo.Flags & FlagsToInclude)) return true; @@ -742,8 +780,9 @@ 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"; @@ -751,6 +790,35 @@ void OptTable::internalPrintHelp( // Render help text into a map of group-name to a list of (option, help) // pairs. std::map<std::string, std::vector<OptionInfo>> GroupedOptionHelp; + StringRef TopLevelCommandName = "TopLevelCommand"; + if (Subcommand.empty()) { + // Assume top level command (toolname) by default. + Subcommand = TopLevelCommandName; + } + + const Command *ActiveCommand = nullptr; + for (const auto &C : Commands) { + if (Subcommand == C.Name) { + ActiveCommand = &C; + if (ActiveCommand->HelpText) + OS << ActiveCommand->HelpText << "\n\n"; + // TODO: Need to sortout how to maintain helptext for toplevel and + // subcommands and show them in view. What does existing tool do? + break; + } + } + + if ((!ActiveCommand || ActiveCommand->Name == TopLevelCommandName) && + Commands.size() > 1) { + OS << "SUBCOMMANDS:\n\n"; + for (const auto &C : Commands) { + if (C.Name == TopLevelCommandName) + continue; + // TODO(prabhuk): This should be better aligned in UI using a helper + OS << C.Name << " - " << C.HelpText << "\n"; + } + OS << "\n"; + } for (unsigned Id = 1, e = getNumOptions() + 1; Id != e; ++Id) { // FIXME: Split out option groups. @@ -764,6 +832,18 @@ void OptTable::internalPrintHelp( if (ExcludeOption(CandidateInfo)) continue; + if (ActiveCommand) { + // ActiveCommand won't be set for tools that did not create command group + // info table. + // TODO: Move this to a lambda outside the loop. + ArrayRef<unsigned> CommandIDs = + CandidateInfo.getCommandIDs(CommandIDsTable); + unsigned ActiveCommandID = ActiveCommand - Commands.data(); + bool IsInCommand = is_contained(CommandIDs, ActiveCommandID); + if (!IsInCommand) + 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 +871,12 @@ 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, + ArrayRef<Command> Commands, + ArrayRef<unsigned> CommandIDsTable, + bool IgnoreCase) + : OptTable(StrTable, PrefixesTable, OptionInfos, Commands, CommandIDsTable, + IgnoreCase) { std::set<StringRef> TmpPrefixesUnion; for (auto const &Info : OptionInfos.drop_front(FirstSearchableIndex)) diff --git a/llvm/unittests/Option/OptionMarshallingTest.cpp b/llvm/unittests/Option/OptionMarshallingTest.cpp index 005144b91bf7f..1f17ad217e9ee 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) \ + COMMANDIDS_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/utils/TableGen/OptionParserEmitter.cpp b/llvm/utils/TableGen/OptionParserEmitter.cpp index a470fbbcadd58..a89688c9ff799 100644 --- a/llvm/utils/TableGen/OptionParserEmitter.cpp +++ b/llvm/utils/TableGen/OptionParserEmitter.cpp @@ -9,6 +9,7 @@ #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/Support/InterleavedRange.h" @@ -258,6 +259,13 @@ 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 *> Commands = + Records.getAllDerivedDefinitions("Command"); + // TopLevelCommand should come first. + std::stable_partition(Commands.begin(), Commands.end(), [](const Record *R) { + return R->getName() == "TopLevelCommand"; + }); + emitSourceFileHeader("Option Parsing Definitions", OS); // Generate prefix groups. @@ -271,6 +279,20 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { Prefixes.try_emplace(PrefixKey, 0); } + // Generate command groups. + typedef SmallVector<StringRef, 2> CommandKeyT; + typedef std::map<CommandKeyT, unsigned> CommandIDsT; + CommandIDsT CommandIDs; + CommandIDs.try_emplace(CommandKeyT(), 0); + for (const Record &R : llvm::make_pointee_range(Opts)) { + std::vector<const Record *> RCommands = + R.getValueAsListOfDefs("CommandGroup"); + CommandKeyT CommandKey; + for (const auto &Command : RCommands) + CommandKey.push_back(Command->getName()); + CommandIDs.try_emplace(CommandKey, 0); + } + DenseSet<StringRef> PrefixesUnionSet; for (const auto &[Prefix, _] : Prefixes) PrefixesUnionSet.insert_range(Prefix); @@ -323,6 +345,39 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { OS << "\n};\n"; OS << "#endif // OPTTABLE_PREFIXES_TABLE_CODE\n\n"; + // Dump command IDs. + OS << "/////////"; + OS << "// Command IDs\n\n"; + OS << "#ifdef OPTTABLE_COMMAND_IDS_TABLE_CODE\n"; + OS << "static constexpr unsigned OptionCommandIDsTable[] = {\n"; + { + // Ensure the first command set is always empty. + assert(!CommandIDs.empty() && + "We should always emit an empty set of commands"); + assert(CommandIDs.begin()->first.empty() && + "First command set should always be empty"); + llvm::ListSeparator Sep(",\n"); + unsigned CurIndex = 0; + for (auto &[Command, CommandIndex] : CommandIDs) { + // First emit the number of command strings in this list of commands. + OS << Sep << " " << Command.size() << " /* commands */"; + CommandIndex = CurIndex; + assert((CurIndex == 0 || !Command.empty()) && + "Only first command set should be empty!"); + for (const auto &CommandKey : Command) { + auto It = llvm::find_if(Commands, [&](const Record *R) { + return R->getName() == CommandKey; + }); + assert(It != Commands.end() && "Command not found"); + OS << ", " << std::distance(Commands.begin(), It) << " /* '" + << CommandKey << "' */"; + } + CurIndex += Command.size() + 1; + } + } + OS << "\n};\n"; + OS << "#endif // OPTTABLE_COMMAND_IDS_TABLE_CODE\n\n"; + // Dump prefixes union. OS << "/////////\n"; OS << "// Prefix Union\n\n"; @@ -400,7 +455,22 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { OS << ", nullptr"; // The option Values (unused for groups). - OS << ", nullptr)\n"; + OS << ", nullptr"; + + // The option CommandIDsOffset. + OS << ", "; + if (R.getValue("CommandGroup") != nullptr) { + std::vector<const Record *> CommandGroup = + R.getValueAsListOfDefs("CommandGroup"); + CommandKeyT CommandKey; + for (const auto &Command : CommandGroup) + CommandKey.push_back(Command->getName()); + OS << CommandIDs[CommandKey]; + } else { + // The option CommandIDsOffset (for default top level toolname is 0). + OS << " 0"; + } + OS << ")\n"; } OS << "\n"; @@ -527,6 +597,20 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { OS << getOptionName(R) << "_Values"; else OS << "nullptr"; + + // The option CommandIDsOffset. + OS << ", "; + if (R.getValue("CommandGroup") != nullptr) { + std::vector<const Record *> CommandGroup = + R.getValueAsListOfDefs("CommandGroup"); + CommandKeyT CommandKey; + for (const auto &Command : CommandGroup) + CommandKey.push_back(Command->getName()); + OS << CommandIDs[CommandKey]; + } else { + // The option CommandIDsOffset (for default top level toolname is 0). + OS << " 0"; + } }; auto IsMarshallingOption = [](const Record &R) { @@ -595,6 +679,19 @@ static void emitOptionParser(const RecordKeeper &Records, raw_ostream &OS) { OS << "#endif // SIMPLE_ENUM_VALUE_TABLE\n"; OS << "\n"; + OS << "/////////\n"; + OS << "\n// Commands\n\n"; + OS << "#ifdef OPTTABLE_COMMANDS_CODE\n"; + OS << "static constexpr llvm::opt::OptTable::Command OptionCommands[] = {\n"; + for (const Record *Command : Commands) { + OS << " { \"" << Command->getValueAsString("Name") << "\", "; + if (Command->isSubClassOf("Subcommand")) + OS << "\"" << Command->getValueAsString("HelpText") << "\" },\n"; + else + OS << "nullptr },\n"; + } + OS << "};\n"; + OS << "#endif // OPTTABLE_COMMANDS_CODE\n\n"; OS << "\n"; } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits