[llvm] Migrate llvm-profdata to Opt flag parsing (PR #177868)

David Zbarsky via llvm-commits llvm-commits at lists.llvm.org
Tue Jan 27 10:31:46 PST 2026


https://github.com/dzbarsky updated https://github.com/llvm/llvm-project/pull/177868

>From d49e25b1d79ee86b0a97433b81acabc63f9c2ce1 Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Sun, 25 Jan 2026 13:53:51 -0500
Subject: [PATCH 1/2] Migrate llvm-profdata to Opt flag parsing

---
 llvm/tools/llvm-profdata/CMakeLists.txt       |   11 +-
 llvm/tools/llvm-profdata/Opts.td              |  314 +++++
 llvm/tools/llvm-profdata/llvm-profdata.cpp    | 1092 ++++++++++-------
 .../llvm-project-overlay/llvm/BUILD.bazel     |   13 +-
 .../llvm-project-overlay/llvm/driver.bzl      |    1 +
 5 files changed, 952 insertions(+), 479 deletions(-)
 create mode 100644 llvm/tools/llvm-profdata/Opts.td

diff --git a/llvm/tools/llvm-profdata/CMakeLists.txt b/llvm/tools/llvm-profdata/CMakeLists.txt
index e5aa858f3d39c..6d64d48ef5db3 100644
--- a/llvm/tools/llvm-profdata/CMakeLists.txt
+++ b/llvm/tools/llvm-profdata/CMakeLists.txt
@@ -2,14 +2,23 @@ set(LLVM_LINK_COMPONENTS
   Core
   Object
   ProfileData
+  Option
   Support
   )
 
+set(LLVM_TARGET_DEFINITIONS Opts.td)
+tablegen(LLVM Opts.inc -gen-opt-parser-defs)
+add_public_tablegen_target(ProfdataOptsTableGen)
+
 add_llvm_tool(llvm-profdata
   llvm-profdata.cpp
 
   DEPENDS
   intrinsics_gen
+  ProfdataOptsTableGen
+  GENERATE_DRIVER
   )
 
-target_link_libraries(llvm-profdata PRIVATE LLVMDebuginfod)
+if(NOT LLVM_TOOL_LLVM_DRIVER_BUILD)
+  target_link_libraries(llvm-profdata PRIVATE LLVMDebuginfod)
+endif()
diff --git a/llvm/tools/llvm-profdata/Opts.td b/llvm/tools/llvm-profdata/Opts.td
new file mode 100644
index 0000000000000..8803af9a10123
--- /dev/null
+++ b/llvm/tools/llvm-profdata/Opts.td
@@ -0,0 +1,314 @@
+include "llvm/Option/OptParser.td"
+
+def Show : SubCommand<"show",
+                      "Takes a profile data file and displays the profiles. "
+                      "See detailed documentation in "
+                      "https://llvm.org/docs/CommandGuide/llvm-profdata.html"
+                      "#profdata-show">;
+def Order : SubCommand<"order",
+                       "Reads temporal profiling traces from a profile and "
+                       "outputs a function order that reduces page faults. "
+                       "See detailed documentation in "
+                       "https://llvm.org/docs/CommandGuide/llvm-profdata.html"
+                       "#profdata-order">;
+def Overlap : SubCommand<"overlap",
+                         "Computes the overlap between two profiles. See "
+                         "detailed documentation in "
+                         "https://llvm.org/docs/CommandGuide/llvm-profdata.html"
+                         "#profdata-overlap">;
+def Merge : SubCommand<"merge",
+                       "Takes several profiles and merge them together. See "
+                       "detailed documentation in "
+                       "https://llvm.org/docs/CommandGuide/llvm-profdata.html"
+                       "#profdata-merge">;
+
+def help : Flag<["-","--"], "help", [Show, Order, Overlap, Merge]>,
+           HelpText<"Display this help">;
+def help_hidden : Flag<["--"], "help-hidden", [Show, Order, Overlap, Merge]>,
+                  HelpText<"Display all help, including hidden options">;
+def version : Flag<["--"], "version", [Show, Order, Overlap, Merge]>,
+              HelpText<"Display the version">;
+
+def output : JoinedOrSeparate<["-","--"], "output",
+                               [Show, Order, Overlap, Merge]>,
+             MetaVarName<"<output>">,
+             HelpText<"Output file">;
+def : JoinedOrSeparate<["-"], "o", [Show, Order, Overlap, Merge]>,
+     Alias<output>, HelpText<"Alias for --output">;
+
+def instr : Flag<["-","--"], "instr", [Show, Overlap, Merge]>,
+            HelpText<"Instrumentation profile (default)">;
+def sample : Flag<["-","--"], "sample", [Show, Overlap, Merge]>,
+             HelpText<"Sample profile">;
+def memory : Flag<["-","--"], "memory", [Show]>,
+             HelpText<"MemProf memory access profile">;
+
+def max_debug_info_correlation_warnings
+    : JoinedOrSeparate<["-","--"], "max-debug-info-correlation-warnings",
+                       [Show, Merge]>,
+      HelpText<"Maximum number of warnings to emit when correlating profile "
+               "from debug info (0 = no limit)">,
+      MetaVarName<"<n>">;
+def profiled_binary
+    : JoinedOrSeparate<["-","--"], "profiled-binary", [Show, Merge]>,
+      HelpText<"Path to binary from which the profile was collected.">,
+      MetaVarName<"<binary>">;
+def debug_info
+    : JoinedOrSeparate<["-","--"], "debug-info", [Show, Merge]>,
+      HelpText<"For show, read and extract profile metadata from debug info. "
+               "For merge, correlate the raw profile using the provided "
+               "debug info.">,
+      MetaVarName<"<file>">;
+def binary_file
+    : JoinedOrSeparate<["-","--"], "binary-file", [Merge]>,
+      HelpText<"Use the provided unstripped binary to correlate the raw "
+               "profile.">,
+      MetaVarName<"<file>">;
+def debug_file_directory
+    : JoinedOrSeparate<["-","--"], "debug-file-directory",
+                       [Show, Order, Overlap, Merge]>,
+      HelpText<"Directories to search for object files by build ID">,
+      MetaVarName<"<dir>">;
+def debuginfod : Flag<["-","--"], "debuginfod", [Merge]>,
+                 HelpText<"Enable debuginfod">, Flags<[HelpHidden]>;
+def correlate : JoinedOrSeparate<["-","--"], "correlate",
+                                  [Show, Order, Overlap, Merge]>,
+                HelpText<"Use debug-info or binary correlation to correlate "
+                         "profiles with build id fetcher">,
+                MetaVarName<"<mode>">;
+def function : JoinedOrSeparate<["-","--"], "function",
+                                [Show, Overlap, Merge]>,
+               HelpText<"Only functions matching the filter are shown or "
+                        "merged.">,
+               MetaVarName<"<regex>">;
+
+def weighted_input
+    : JoinedOrSeparate<["-","--"], "weighted-input", [Merge]>,
+      HelpText<"<weight>,<filename>">, MetaVarName<"<weight>,<file>">;
+def binary : Flag<["-","--"], "binary", [Merge]>,
+             HelpText<"Binary encoding">;
+def extbinary : Flag<["-","--"], "extbinary", [Merge]>,
+                HelpText<"Extensible binary encoding (default)">;
+def text : Flag<["-","--"], "text", [Show, Merge]>,
+           HelpText<"Text output format">;
+def gcc : Flag<["-","--"], "gcc", [Merge]>,
+          HelpText<"GCC encoding (only meaningful for -sample)">;
+def input_files
+    : JoinedOrSeparate<["-","--"], "input-files", [Merge]>,
+      HelpText<"Path to file containing newline-separated "
+               "[<weight>,]<filename> entries">,
+      MetaVarName<"<file>">;
+def : JoinedOrSeparate<["-"], "f", [Merge]>, Alias<input_files>,
+     HelpText<"Alias for --input-files">;
+def dump_input_file_list : Flag<["-","--"], "dump-input-file-list", [Merge]>,
+                           HelpText<"Dump the list of input files and their "
+                                    "weights, then exit">,
+                           Flags<[HelpHidden]>;
+def remapping_file
+    : JoinedOrSeparate<["-","--"], "remapping-file", [Merge]>,
+      HelpText<"Symbol remapping file">, MetaVarName<"<file>">;
+def : JoinedOrSeparate<["-"], "r", [Merge]>, Alias<remapping_file>,
+     HelpText<"Alias for --remapping-file">;
+def use_md5 : Flag<["-","--"], "use-md5", [Merge]>,
+              HelpText<"Use MD5 to represent strings in the name table "
+                       "(only meaningful for -extbinary)">,
+              Flags<[HelpHidden]>;
+def compress_all_sections
+    : Flag<["-","--"], "compress-all-sections", [Merge]>,
+      HelpText<"Compress all sections when writing the profile (only "
+               "meaningful for -extbinary)">,
+      Flags<[HelpHidden]>;
+def sample_merge_cold_context
+    : Flag<["-","--"], "sample-merge-cold-context", [Merge]>,
+      HelpText<"Merge context sample profiles whose count is below cold "
+               "threshold">,
+      Flags<[HelpHidden]>;
+def sample_trim_cold_context
+    : Flag<["-","--"], "sample-trim-cold-context", [Merge]>,
+      HelpText<"Trim context sample profiles whose count is below cold "
+               "threshold">,
+      Flags<[HelpHidden]>;
+def sample_frame_depth_for_cold_context
+    : JoinedOrSeparate<["-","--"], "sample-frame-depth-for-cold-context",
+                       [Merge]>,
+      HelpText<"Keep the last K frames while merging cold profile. 1 means "
+               "the context-less base profile">,
+      Flags<[HelpHidden]>, MetaVarName<"<depth>">;
+def output_size_limit
+    : JoinedOrSeparate<["-","--"], "output-size-limit", [Merge]>,
+      HelpText<"Trim cold functions until profile size is below specified "
+               "limit in bytes">,
+      Flags<[HelpHidden]>, MetaVarName<"<bytes>">;
+def gen_partial_profile
+    : Flag<["-","--"], "gen-partial-profile", [Merge]>,
+      HelpText<"Generate a partial profile (only meaningful for -extbinary)">,
+      Flags<[HelpHidden]>;
+def split_layout : Flag<["-","--"], "split-layout", [Merge]>,
+                   HelpText<"Split the profile into sections with and without "
+                            "inlined functions (only meaningful for "
+                            "-extbinary)">,
+                   Flags<[HelpHidden]>;
+def supplement_instr_with_sample
+    : JoinedOrSeparate<["-","--"], "supplement-instr-with-sample", [Merge]>,
+      HelpText<"Supplement an instr profile with a sample profile. Output "
+               "will be in instr format.">,
+      Flags<[HelpHidden]>, MetaVarName<"<sample-profile>">;
+def zero_counter_threshold
+    : JoinedOrSeparate<["-","--"], "zero-counter-threshold", [Merge]>,
+      HelpText<"Ratio of zero counters required to drop a function when "
+               "supplementing instr profiles">,
+      Flags<[HelpHidden]>, MetaVarName<"<ratio>">;
+def suppl_min_size_threshold
+    : JoinedOrSeparate<["-","--"], "suppl-min-size-threshold", [Merge]>,
+      HelpText<"Assume functions smaller than this threshold can be inlined "
+               "and will not be adjusted based on sample profile.">,
+      Flags<[HelpHidden]>, MetaVarName<"<n>">;
+def instr_prof_cold_threshold
+    : JoinedOrSeparate<["-","--"], "instr-prof-cold-threshold", [Merge]>,
+      HelpText<"User specified cold threshold for instr profile to override "
+               "the cold threshold from profile summary.">,
+      Flags<[HelpHidden]>, MetaVarName<"<n>">;
+def temporal_profile_trace_reservoir_size
+    : JoinedOrSeparate<["-","--"], "temporal-profile-trace-reservoir-size",
+                       [Merge]>,
+      HelpText<"Maximum number of stored temporal profile traces (default: "
+               "100)">,
+      MetaVarName<"<n>">;
+def temporal_profile_max_trace_length
+    : JoinedOrSeparate<["-","--"], "temporal-profile-max-trace-length",
+                       [Merge]>,
+      HelpText<"Maximum length of a single temporal profile trace "
+               "(default: 10000)">,
+      MetaVarName<"<n>">;
+def no_function : JoinedOrSeparate<["-","--"], "no-function", [Merge]>,
+                  HelpText<"Exclude functions matching the filter from the "
+                           "output.">,
+                  MetaVarName<"<regex>">;
+def failure_mode
+    : JoinedOrSeparate<["-","--"], "failure-mode", [Merge]>,
+      HelpText<"Failure mode: warn, any, or all">,
+      MetaVarName<"<mode>">;
+def sparse : Flag<["-","--"], "sparse", [Merge]>,
+             HelpText<"Generate a sparse profile (only meaningful for -instr)">;
+def num_threads : JoinedOrSeparate<["-","--"], "num-threads", [Merge]>,
+                  HelpText<"Number of merge threads to use (default: autodetect)">,
+                  MetaVarName<"<n>">;
+def : JoinedOrSeparate<["-"], "j", [Merge]>, Alias<num_threads>,
+     HelpText<"Alias for --num-threads">;
+def prof_sym_list
+    : JoinedOrSeparate<["-","--"], "prof-sym-list", [Merge]>,
+      HelpText<"Path to file containing the list of function symbols used to "
+               "populate profile symbol list">,
+      MetaVarName<"<file>">;
+def convert_sample_profile_layout
+    : JoinedOrSeparate<["-","--"], "convert-sample-profile-layout", [Merge]>,
+      HelpText<"Convert the generated profile to a new layout: nest or flat">,
+      MetaVarName<"<layout>">;
+def drop_profile_symbol_list
+    : Flag<["-","--"], "drop-profile-symbol-list", [Merge]>,
+      HelpText<"Drop the profile symbol list when merging AutoFDO profiles "
+               "(only meaningful for -sample)">,
+      Flags<[HelpHidden]>;
+def keep_vtable_symbols
+    : Flag<["-","--"], "keep-vtable-symbols", [Merge]>,
+      HelpText<"Keep the vtable symbols in indexed profiles">,
+      Flags<[HelpHidden]>;
+def write_prev_version
+    : Flag<["-","--"], "write-prev-version", [Merge]>,
+      HelpText<"Write the previous version of indexed format for forward "
+               "compatibility.">,
+      Flags<[HelpHidden]>;
+def memprof_version
+    : JoinedOrSeparate<["-","--"], "memprof-version", [Merge]>,
+      HelpText<"Specify the version of the memprof format to use (2, 3, or 4)">,
+      Flags<[HelpHidden]>, MetaVarName<"<n>">;
+def memprof_full_schema
+    : Flag<["-","--"], "memprof-full-schema", [Merge]>,
+      HelpText<"Use the full schema for serialization">,
+      Flags<[HelpHidden]>;
+def memprof_random_hotness
+    : Flag<["-","--"], "memprof-random-hotness", [Merge]>,
+      HelpText<"Generate random hotness values">,
+      Flags<[HelpHidden]>;
+def memprof_random_hotness_seed
+    : JoinedOrSeparate<["-","--"], "memprof-random-hotness-seed", [Merge]>,
+      HelpText<"Random hotness seed to use (0 to generate new seed)">,
+      Flags<[HelpHidden]>, MetaVarName<"<n>">;
+
+def similarity_cutoff
+    : JoinedOrSeparate<["-","--"], "similarity-cutoff", [Overlap]>,
+      HelpText<"List overlapped functions with similarities below the cutoff "
+               "(percentage times 10000).">,
+      MetaVarName<"<n>">;
+def cs : Flag<["-","--"], "cs", [Overlap]>,
+         HelpText<"For context sensitive PGO counts. Does not work with "
+                  "CSSPGO.">;
+def value_cutoff
+    : JoinedOrSeparate<["-","--"], "value-cutoff", [Show, Overlap]>,
+      HelpText<"Cutoff value used for filtering. Meaning depends on subcommand">,
+      MetaVarName<"<n>">;
+
+def counts : Flag<["-","--"], "counts", [Show]>,
+             HelpText<"Show counter values for shown functions">;
+def show_format
+    : JoinedOrSeparate<["-","--"], "show-format", [Show]>,
+      HelpText<"Emit output in the selected format: text, json, or yaml">,
+      MetaVarName<"<format>">;
+def json : Flag<["-","--"], "json", [Show]>,
+           HelpText<"Show sample profile data in JSON format "
+                    "(deprecated, use --show-format=json)">;
+def ic_targets : Flag<["-","--"], "ic-targets", [Show]>,
+                 HelpText<"Show indirect call site target values">;
+def show_vtables : Flag<["-","--"], "show-vtables", [Show]>,
+                   HelpText<"Show vtable names for shown functions">;
+def memop_sizes : Flag<["-","--"], "memop-sizes", [Show]>,
+                  HelpText<"Show profiled sizes of memory intrinsic calls">;
+def detailed_summary : Flag<["-","--"], "detailed-summary", [Show]>,
+                       HelpText<"Show detailed profile summary">;
+def detailed_summary_cutoffs
+    : CommaJoined<["-","--"], "detailed-summary-cutoffs", [Show]>,
+      HelpText<"Cutoff percentages (times 10000) for generating detailed "
+               "summary">,
+      MetaVarName<"<list>">;
+def hot_func_list : Flag<["-","--"], "hot-func-list", [Show]>,
+                    HelpText<"Show profile summary of a list of hot functions">;
+def all_functions : Flag<["-","--"], "all-functions", [Show]>,
+                    HelpText<"Details for each and every function">;
+def showcs : Flag<["-","--"], "showcs", [Show]>,
+             HelpText<"Show context sensitive counts">;
+def topn : JoinedOrSeparate<["-","--"], "topn", [Show]>,
+           HelpText<"Show the list of functions with the largest internal counts">,
+           MetaVarName<"<n>">;
+def list_below_cutoff
+    : Flag<["-","--"], "list-below-cutoff", [Show]>,
+      HelpText<"Only output names of functions whose max count values are "
+               "below the cutoff value">;
+def show_prof_sym_list
+    : Flag<["-","--"], "show-prof-sym-list", [Show]>,
+      HelpText<"Show profile symbol list if it exists in the profile.">;
+def show_sec_info_only
+    : Flag<["-","--"], "show-sec-info-only", [Show]>,
+      HelpText<"Show the information of each section in the sample profile "
+               "(extbinary sample profiles only)">;
+def binary_ids : Flag<["-","--"], "binary-ids", [Show]>,
+                 HelpText<"Show binary ids in the profile.">;
+def temporal_profile_traces
+    : Flag<["-","--"], "temporal-profile-traces", [Show]>,
+      HelpText<"Show temporal profile traces in the profile.">;
+def covered : Flag<["-","--"], "covered", [Show]>,
+              HelpText<"Show only the functions that have been executed.">;
+def profile_version : Flag<["-","--"], "profile-version", [Show]>,
+                      HelpText<"Show profile version.">;
+
+def num_test_traces
+    : JoinedOrSeparate<["-","--"], "num-test-traces", [Order]>,
+      HelpText<"Keep aside the last <num-test-traces> traces when computing "
+               "function order to evaluate that order">,
+      MetaVarName<"<n>">;
+
+def fs_discriminator_pass
+    : JoinedOrSeparate<["-","--"], "fs-discriminator-pass",
+                       [Show, Overlap, Merge]>,
+      HelpText<"Zero out the discriminator bits for the FS discriminator "
+               "pass beyond this value.">,
+      Flags<[HelpHidden]>, MetaVarName<"<pass>">;
diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp
index 74c4732ca129a..4c8c7519df9ca 100644
--- a/llvm/tools/llvm-profdata/llvm-profdata.cpp
+++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp
@@ -10,13 +10,20 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSwitch.h"
 #include "llvm/Debuginfod/HTTPClient.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/Object/Binary.h"
+#include "llvm/Option/Arg.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/OptTable.h"
+#include "llvm/Option/Option.h"
 #include "llvm/ProfileData/DataAccessProf.h"
 #include "llvm/ProfileData/InstrProfCorrelator.h"
 #include "llvm/ProfileData/InstrProfReader.h"
@@ -35,11 +42,12 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/FormattedStream.h"
-#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/LLVMDriver.h"
 #include "llvm/Support/MD5.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Regex.h"
+#include "llvm/Support/StringSaver.h"
 #include "llvm/Support/ThreadPool.h"
 #include "llvm/Support/Threading.h"
 #include "llvm/Support/VirtualFileSystem.h"
@@ -47,35 +55,15 @@
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
 #include <cmath>
+#include <limits>
 #include <optional>
 
+#include "Opts.inc"
+
 using namespace llvm;
+using namespace llvm::opt;
 using ProfCorrelatorKind = InstrProfCorrelator::ProfCorrelatorKind;
 
-// https://llvm.org/docs/CommandGuide/llvm-profdata.html has documentations
-// on each subcommand.
-cl::SubCommand ShowSubcommand(
-    "show",
-    "Takes a profile data file and displays the profiles. See detailed "
-    "documentation in "
-    "https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-show");
-cl::SubCommand OrderSubcommand(
-    "order",
-    "Reads temporal profiling traces from a profile and outputs a function "
-    "order that reduces the number of page faults for those traces. See "
-    "detailed documentation in "
-    "https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-order");
-cl::SubCommand OverlapSubcommand(
-    "overlap",
-    "Computes and displays the overlap between two profiles. See detailed "
-    "documentation in "
-    "https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-overlap");
-cl::SubCommand MergeSubcommand(
-    "merge",
-    "Takes several profiles and merge them together. See detailed "
-    "documentation in "
-    "https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-merge");
-
 namespace {
 enum ProfileKinds { instr, sample, memory };
 enum FailureMode { warnOnly, failIfAnyAreInvalid, failIfAllAreInvalid };
@@ -90,408 +78,132 @@ enum ProfileFormat {
 };
 
 enum class ShowFormat { Text, Json, Yaml };
-} // namespace
 
-// Common options.
-cl::opt<std::string> OutputFilename("output", cl::value_desc("output"),
-                                    cl::init("-"), cl::desc("Output file"),
-                                    cl::sub(ShowSubcommand),
-                                    cl::sub(OrderSubcommand),
-                                    cl::sub(OverlapSubcommand),
-                                    cl::sub(MergeSubcommand));
-// NOTE: cl::alias must not have cl::sub(), since aliased option's cl::sub()
-// will be used. llvm::cl::alias::done() method asserts this condition.
-static cl::alias OutputFilenameA("o", cl::desc("Alias for --output"),
-                                 cl::aliasopt(OutputFilename));
-
-// Options common to at least two commands.
-static cl::opt<ProfileKinds> ProfileKind(
-    cl::desc("Profile kind:"), cl::sub(MergeSubcommand),
-    cl::sub(OverlapSubcommand), cl::init(instr),
-    cl::values(clEnumVal(instr, "Instrumentation profile (default)"),
-               clEnumVal(sample, "Sample profile")));
-static cl::opt<std::string> Filename(cl::Positional,
-                                     cl::desc("<profdata-file>"),
-                                     cl::sub(ShowSubcommand),
-                                     cl::sub(OrderSubcommand));
-static cl::opt<unsigned> MaxDbgCorrelationWarnings(
-    "max-debug-info-correlation-warnings",
-    cl::desc("The maximum number of warnings to emit when correlating "
-             "profile from debug info (0 = no limit)"),
-    cl::sub(MergeSubcommand), cl::sub(ShowSubcommand), cl::init(5));
-static cl::opt<std::string> ProfiledBinary(
-    "profiled-binary", cl::init(""),
-    cl::desc("Path to binary from which the profile was collected."),
-    cl::sub(ShowSubcommand), cl::sub(MergeSubcommand));
-static cl::opt<std::string> DebugInfoFilename(
-    "debug-info", cl::init(""),
-    cl::desc(
-        "For show, read and extract profile metadata from debug info and show "
-        "the functions it found. For merge, use the provided debug info to "
-        "correlate the raw profile."),
-    cl::sub(ShowSubcommand), cl::sub(MergeSubcommand));
-static cl::opt<std::string>
-    BinaryFilename("binary-file", cl::init(""),
-                   cl::desc("For merge, use the provided unstripped binary to "
-                            "correlate the raw profile."),
-                   cl::sub(MergeSubcommand));
-static cl::list<std::string> DebugFileDirectory(
-    "debug-file-directory",
-    cl::desc("Directories to search for object files by build ID"));
-static cl::opt<bool> DebugInfod("debuginfod", cl::init(false), cl::Hidden,
-                                cl::sub(MergeSubcommand),
-                                cl::desc("Enable debuginfod"));
-static cl::opt<ProfCorrelatorKind> BIDFetcherProfileCorrelate(
-    "correlate",
-    cl::desc("Use debug-info or binary correlation to correlate profiles with "
-             "build id fetcher"),
-    cl::init(InstrProfCorrelator::NONE),
-    cl::values(clEnumValN(InstrProfCorrelator::NONE, "",
-                          "No profile correlation"),
-               clEnumValN(InstrProfCorrelator::DEBUG_INFO, "debug-info",
-                          "Use debug info to correlate"),
-               clEnumValN(InstrProfCorrelator::BINARY, "binary",
-                          "Use binary to correlate")));
-static cl::opt<std::string> FuncNameFilter(
-    "function",
-    cl::desc("Only functions matching the filter are shown in the output. For "
-             "overlapping CSSPGO, this takes a function name with calling "
-             "context."),
-    cl::sub(ShowSubcommand), cl::sub(OverlapSubcommand),
-    cl::sub(MergeSubcommand));
-
-// TODO: Consider creating a template class (e.g., MergeOption, ShowOption) to
-// factor out the common cl::sub in cl::opt constructor for subcommand-specific
-// options.
-
-// Options specific to merge subcommand.
-static cl::list<std::string> InputFilenames(cl::Positional,
-                                            cl::sub(MergeSubcommand),
-                                            cl::desc("<filename...>"));
-static cl::list<std::string>
-    WeightedInputFilenames("weighted-input", cl::sub(MergeSubcommand),
-                           cl::desc("<weight>,<filename>"));
-static cl::opt<ProfileFormat> OutputFormat(
-    cl::desc("Format of output profile"), cl::sub(MergeSubcommand),
-    cl::init(PF_Ext_Binary),
-    cl::values(clEnumValN(PF_Binary, "binary", "Binary encoding"),
-               clEnumValN(PF_Ext_Binary, "extbinary",
-                          "Extensible binary encoding "
-                          "(default)"),
-               clEnumValN(PF_Text, "text", "Text encoding"),
-               clEnumValN(PF_GCC, "gcc",
-                          "GCC encoding (only meaningful for -sample)")));
-static cl::opt<std::string>
-    InputFilenamesFile("input-files", cl::init(""), cl::sub(MergeSubcommand),
-                       cl::desc("Path to file containing newline-separated "
-                                "[<weight>,]<filename> entries"));
-static cl::alias InputFilenamesFileA("f", cl::desc("Alias for --input-files"),
-                                     cl::aliasopt(InputFilenamesFile));
-static cl::opt<bool> DumpInputFileList(
-    "dump-input-file-list", cl::init(false), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("Dump the list of input files and their weights, then exit"));
-static cl::opt<std::string> RemappingFile("remapping-file",
-                                          cl::value_desc("file"),
-                                          cl::sub(MergeSubcommand),
-                                          cl::desc("Symbol remapping file"));
-static cl::alias RemappingFileA("r", cl::desc("Alias for --remapping-file"),
-                                cl::aliasopt(RemappingFile));
-static cl::opt<bool>
-    UseMD5("use-md5", cl::init(false), cl::Hidden,
-           cl::desc("Choose to use MD5 to represent string in name table (only "
-                    "meaningful for -extbinary)"),
-           cl::sub(MergeSubcommand));
-static cl::opt<bool> CompressAllSections(
-    "compress-all-sections", cl::init(false), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("Compress all sections when writing the profile (only "
-             "meaningful for -extbinary)"));
-static cl::opt<bool> SampleMergeColdContext(
-    "sample-merge-cold-context", cl::init(false), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc(
-        "Merge context sample profiles whose count is below cold threshold"));
-static cl::opt<bool> SampleTrimColdContext(
-    "sample-trim-cold-context", cl::init(false), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc(
-        "Trim context sample profiles whose count is below cold threshold"));
-static cl::opt<uint32_t> SampleColdContextFrameDepth(
-    "sample-frame-depth-for-cold-context", cl::init(1),
-    cl::sub(MergeSubcommand),
-    cl::desc("Keep the last K frames while merging cold profile. 1 means the "
-             "context-less base profile"));
-static cl::opt<size_t> OutputSizeLimit(
-    "output-size-limit", cl::init(0), cl::Hidden, cl::sub(MergeSubcommand),
-    cl::desc("Trim cold functions until profile size is below specified "
-             "limit in bytes. This uses a heursitic and functions may be "
-             "excessively trimmed"));
-static cl::opt<bool> GenPartialProfile(
-    "gen-partial-profile", cl::init(false), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("Generate a partial profile (only meaningful for -extbinary)"));
-static cl::opt<bool> SplitLayout(
-    "split-layout", cl::init(false), cl::Hidden, cl::sub(MergeSubcommand),
-    cl::desc("Split the profile to two sections with one containing sample "
-             "profiles with inlined functions and the other without (only "
-             "meaningful for -extbinary)"));
-static cl::opt<std::string> SupplInstrWithSample(
-    "supplement-instr-with-sample", cl::init(""), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("Supplement an instr profile with sample profile, to correct "
-             "the profile unrepresentativeness issue. The sample "
-             "profile is the input of the flag. Output will be in instr "
-             "format (The flag only works with -instr)"));
-static cl::opt<float> ZeroCounterThreshold(
-    "zero-counter-threshold", cl::init(0.7), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("For the function which is cold in instr profile but hot in "
-             "sample profile, if the ratio of the number of zero counters "
-             "divided by the total number of counters is above the "
-             "threshold, the profile of the function will be regarded as "
-             "being harmful for performance and will be dropped."));
-static cl::opt<unsigned> SupplMinSizeThreshold(
-    "suppl-min-size-threshold", cl::init(10), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("If the size of a function is smaller than the threshold, "
-             "assume it can be inlined by PGO early inliner and it won't "
-             "be adjusted based on sample profile."));
-static cl::opt<unsigned> InstrProfColdThreshold(
-    "instr-prof-cold-threshold", cl::init(0), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("User specified cold threshold for instr profile which will "
-             "override the cold threshold got from profile summary. "));
-// WARNING: This reservoir size value is propagated to any input indexed
-// profiles for simplicity. Changing this value between invocations could
-// result in sample bias.
-static cl::opt<uint64_t> TemporalProfTraceReservoirSize(
-    "temporal-profile-trace-reservoir-size", cl::init(100),
-    cl::sub(MergeSubcommand),
-    cl::desc("The maximum number of stored temporal profile traces (default: "
-             "100)"));
-static cl::opt<uint64_t> TemporalProfMaxTraceLength(
-    "temporal-profile-max-trace-length", cl::init(10000),
-    cl::sub(MergeSubcommand),
-    cl::desc("The maximum length of a single temporal profile trace "
-             "(default: 10000)"));
-static cl::opt<std::string> FuncNameNegativeFilter(
-    "no-function", cl::init(""), cl::sub(MergeSubcommand),
-    cl::desc("Exclude functions matching the filter from the output."));
-
-static cl::opt<FailureMode>
-    FailMode("failure-mode", cl::init(failIfAnyAreInvalid),
-             cl::desc("Failure mode:"), cl::sub(MergeSubcommand),
-             cl::values(clEnumValN(warnOnly, "warn",
-                                   "Do not fail and just print warnings."),
-                        clEnumValN(failIfAnyAreInvalid, "any",
-                                   "Fail if any profile is invalid."),
-                        clEnumValN(failIfAllAreInvalid, "all",
-                                   "Fail only if all profiles are invalid.")));
-
-static cl::opt<bool> OutputSparse(
-    "sparse", cl::init(false), cl::sub(MergeSubcommand),
-    cl::desc("Generate a sparse profile (only meaningful for -instr)"));
-static cl::opt<unsigned> NumThreads(
-    "num-threads", cl::init(0), cl::sub(MergeSubcommand),
-    cl::desc("Number of merge threads to use (default: autodetect)"));
-static cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"),
-                             cl::aliasopt(NumThreads));
-
-static cl::opt<std::string> ProfileSymbolListFile(
-    "prof-sym-list", cl::init(""), cl::sub(MergeSubcommand),
-    cl::desc("Path to file containing the list of function symbols "
-             "used to populate profile symbol list"));
-
-static cl::opt<SampleProfileLayout> ProfileLayout(
-    "convert-sample-profile-layout",
-    cl::desc("Convert the generated profile to a profile with a new layout"),
-    cl::sub(MergeSubcommand), cl::init(SPL_None),
-    cl::values(
-        clEnumValN(SPL_Nest, "nest",
-                   "Nested profile, the input should be CS flat profile"),
-        clEnumValN(SPL_Flat, "flat",
-                   "Profile with nested inlinee flatten out")));
-
-static cl::opt<bool> DropProfileSymbolList(
-    "drop-profile-symbol-list", cl::init(false), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("Drop the profile symbol list when merging AutoFDO profiles "
-             "(only meaningful for -sample)"));
-
-static cl::opt<bool> KeepVTableSymbols(
-    "keep-vtable-symbols", cl::init(false), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("If true, keep the vtable symbols in indexed profiles"));
-
-// Temporary support for writing the previous version of the format, to enable
-// some forward compatibility.
-// TODO: Consider enabling this with future version changes as well, to ease
-// deployment of newer versions of llvm-profdata.
-static cl::opt<bool> DoWritePrevVersion(
-    "write-prev-version", cl::init(false), cl::Hidden,
-    cl::desc("Write the previous version of indexed format, to enable "
-             "some forward compatibility."));
-
-static cl::opt<memprof::IndexedVersion> MemProfVersionRequested(
-    "memprof-version", cl::Hidden, cl::sub(MergeSubcommand),
-    cl::desc("Specify the version of the memprof format to use"),
-    cl::init(memprof::Version3),
-    cl::values(clEnumValN(memprof::Version2, "2", "version 2"),
-               clEnumValN(memprof::Version3, "3", "version 3"),
-               clEnumValN(memprof::Version4, "4", "version 4")));
-
-static cl::opt<bool> MemProfFullSchema(
-    "memprof-full-schema", cl::Hidden, cl::sub(MergeSubcommand),
-    cl::desc("Use the full schema for serialization"), cl::init(false));
-
-static cl::opt<bool>
-    MemprofGenerateRandomHotness("memprof-random-hotness", cl::init(false),
-                                 cl::Hidden, cl::sub(MergeSubcommand),
-                                 cl::desc("Generate random hotness values"));
-static cl::opt<unsigned> MemprofGenerateRandomHotnessSeed(
-    "memprof-random-hotness-seed", cl::init(0), cl::Hidden,
-    cl::sub(MergeSubcommand),
-    cl::desc("Random hotness seed to use (0 to generate new seed)"));
-
-// Options specific to overlap subcommand.
-static cl::opt<std::string> BaseFilename(cl::Positional, cl::Required,
-                                         cl::desc("<base profile file>"),
-                                         cl::sub(OverlapSubcommand));
-static cl::opt<std::string> TestFilename(cl::Positional, cl::Required,
-                                         cl::desc("<test profile file>"),
-                                         cl::sub(OverlapSubcommand));
-
-static cl::opt<unsigned long long> SimilarityCutoff(
-    "similarity-cutoff", cl::init(0),
-    cl::desc("For sample profiles, list function names (with calling context "
-             "for csspgo) for overlapped functions "
-             "with similarities below the cutoff (percentage times 10000)."),
-    cl::sub(OverlapSubcommand));
-
-static cl::opt<bool> IsCS(
-    "cs", cl::init(false),
-    cl::desc("For context sensitive PGO counts. Does not work with CSSPGO."),
-    cl::sub(OverlapSubcommand));
-
-static cl::opt<unsigned long long> OverlapValueCutoff(
-    "value-cutoff", cl::init(-1),
-    cl::desc(
-        "Function level overlap information for every function (with calling "
-        "context for csspgo) in test "
-        "profile with max count value greater than the parameter value"),
-    cl::sub(OverlapSubcommand));
-
-// Options specific to show subcommand.
-static cl::opt<bool>
-    ShowCounts("counts", cl::init(false),
-               cl::desc("Show counter values for shown functions"),
-               cl::sub(ShowSubcommand));
-static cl::opt<ShowFormat>
-    SFormat("show-format", cl::init(ShowFormat::Text),
-            cl::desc("Emit output in the selected format if supported"),
-            cl::sub(ShowSubcommand),
-            cl::values(clEnumValN(ShowFormat::Text, "text",
-                                  "emit normal text output (default)"),
-                       clEnumValN(ShowFormat::Json, "json", "emit JSON"),
-                       clEnumValN(ShowFormat::Yaml, "yaml", "emit YAML")));
-// TODO: Consider replacing this with `--show-format=text-encoding`.
-static cl::opt<bool>
-    TextFormat("text", cl::init(false),
-               cl::desc("Show instr profile data in text dump format"),
-               cl::sub(ShowSubcommand));
-static cl::opt<bool>
-    JsonFormat("json",
-               cl::desc("Show sample profile data in the JSON format "
-                        "(deprecated, please use --show-format=json)"),
-               cl::sub(ShowSubcommand));
-static cl::opt<bool> ShowIndirectCallTargets(
-    "ic-targets", cl::init(false),
-    cl::desc("Show indirect call site target values for shown functions"),
-    cl::sub(ShowSubcommand));
-static cl::opt<bool>
-    ShowVTables("show-vtables", cl::init(false),
-                cl::desc("Show vtable names for shown functions"),
-                cl::sub(ShowSubcommand));
-static cl::opt<bool> ShowMemOPSizes(
-    "memop-sizes", cl::init(false),
-    cl::desc("Show the profiled sizes of the memory intrinsic calls "
-             "for shown functions"),
-    cl::sub(ShowSubcommand));
-static cl::opt<bool>
-    ShowDetailedSummary("detailed-summary", cl::init(false),
-                        cl::desc("Show detailed profile summary"),
-                        cl::sub(ShowSubcommand));
-static cl::list<uint32_t> DetailedSummaryCutoffs(
-    cl::CommaSeparated, "detailed-summary-cutoffs",
-    cl::desc(
-        "Cutoff percentages (times 10000) for generating detailed summary"),
-    cl::value_desc("800000,901000,999999"), cl::sub(ShowSubcommand));
-static cl::opt<bool>
-    ShowHotFuncList("hot-func-list", cl::init(false),
-                    cl::desc("Show profile summary of a list of hot functions"),
-                    cl::sub(ShowSubcommand));
-static cl::opt<bool>
-    ShowAllFunctions("all-functions", cl::init(false),
-                     cl::desc("Details for each and every function"),
-                     cl::sub(ShowSubcommand));
-static cl::opt<bool> ShowCS("showcs", cl::init(false),
-                            cl::desc("Show context sensitive counts"),
-                            cl::sub(ShowSubcommand));
-static cl::opt<ProfileKinds> ShowProfileKind(
-    cl::desc("Profile kind supported by show:"), cl::sub(ShowSubcommand),
-    cl::init(instr),
-    cl::values(clEnumVal(instr, "Instrumentation profile (default)"),
-               clEnumVal(sample, "Sample profile"),
-               clEnumVal(memory, "MemProf memory access profile")));
-static cl::opt<uint32_t> TopNFunctions(
-    "topn", cl::init(0),
-    cl::desc("Show the list of functions with the largest internal counts"),
-    cl::sub(ShowSubcommand));
-static cl::opt<uint32_t> ShowValueCutoff(
-    "value-cutoff", cl::init(0),
-    cl::desc("Set the count value cutoff. Functions with the maximum count "
-             "less than this value will not be printed out. (Default is 0)"),
-    cl::sub(ShowSubcommand));
-static cl::opt<bool> OnlyListBelow(
-    "list-below-cutoff", cl::init(false),
-    cl::desc("Only output names of functions whose max count values are "
-             "below the cutoff value"),
-    cl::sub(ShowSubcommand));
-static cl::opt<bool> ShowProfileSymbolList(
-    "show-prof-sym-list", cl::init(false),
-    cl::desc("Show profile symbol list if it exists in the profile. "),
-    cl::sub(ShowSubcommand));
-static cl::opt<bool> ShowSectionInfoOnly(
-    "show-sec-info-only", cl::init(false),
-    cl::desc("Show the information of each section in the sample profile. "
-             "The flag is only usable when the sample profile is in "
-             "extbinary format"),
-    cl::sub(ShowSubcommand));
-static cl::opt<bool> ShowBinaryIds("binary-ids", cl::init(false),
-                                   cl::desc("Show binary ids in the profile. "),
-                                   cl::sub(ShowSubcommand));
-static cl::opt<bool> ShowTemporalProfTraces(
-    "temporal-profile-traces",
-    cl::desc("Show temporal profile traces in the profile."),
-    cl::sub(ShowSubcommand));
-
-static cl::opt<bool>
-    ShowCovered("covered", cl::init(false),
-                cl::desc("Show only the functions that have been executed."),
-                cl::sub(ShowSubcommand));
-
-static cl::opt<bool> ShowProfileVersion("profile-version", cl::init(false),
-                                        cl::desc("Show profile version. "),
-                                        cl::sub(ShowSubcommand));
-
-// Options specific to order subcommand.
-static cl::opt<unsigned>
-    NumTestTraces("num-test-traces", cl::init(0),
-                  cl::desc("Keep aside the last <num-test-traces> traces in "
-                           "the profile when computing the function order and "
-                           "instead use them to evaluate that order"),
-                  cl::sub(OrderSubcommand));
+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 opt::OptTable::Info InfoTable[] = {
+#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
+#include "Opts.inc"
+#undef OPTION
+};
+
+class ProfdataOptTable : public opt::GenericOptTable {
+public:
+  ProfdataOptTable()
+      : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable,
+                             /*IgnoreCase=*/false, OptionSubCommands,
+                             OptionSubCommandIDsTable) {
+    setGroupedShortOptions(true);
+    setDashDashParsing(true);
+  }
+};
+
+static std::string ProgramName;
+
+static std::string OutputFilename = "-";
+static ProfileKinds ProfileKind = instr;
+static std::string Filename;
+static unsigned MaxDbgCorrelationWarnings = 5;
+static std::string ProfiledBinary;
+static std::string DebugInfoFilename;
+static std::string BinaryFilename;
+static std::vector<std::string> DebugFileDirectory;
+static bool DebugInfod = false;
+static ProfCorrelatorKind BIDFetcherProfileCorrelate =
+    InstrProfCorrelator::NONE;
+static std::string FuncNameFilter;
+
+static std::vector<std::string> InputFilenames;
+static std::vector<std::string> WeightedInputFilenames;
+static ProfileFormat OutputFormat = PF_Ext_Binary;
+static std::string InputFilenamesFile;
+static bool DumpInputFileList = false;
+static std::string RemappingFile;
+static bool UseMD5 = false;
+static bool CompressAllSections = false;
+static bool SampleMergeColdContext = false;
+static bool SampleTrimColdContext = false;
+static uint32_t SampleColdContextFrameDepth = 1;
+static size_t OutputSizeLimit = 0;
+static bool GenPartialProfile = false;
+static bool SplitLayout = false;
+static std::string SupplInstrWithSample;
+static float ZeroCounterThreshold = 0.7f;
+static unsigned SupplMinSizeThreshold = 10;
+static unsigned InstrProfColdThreshold = 0;
+static uint64_t TemporalProfTraceReservoirSize = 100;
+static uint64_t TemporalProfMaxTraceLength = 10000;
+static std::string FuncNameNegativeFilter;
+static FailureMode FailMode = failIfAnyAreInvalid;
+static bool OutputSparse = false;
+static unsigned NumThreads = 0;
+static std::string ProfileSymbolListFile;
+static SampleProfileLayout ProfileLayout = SPL_None;
+static bool DropProfileSymbolList = false;
+static bool KeepVTableSymbols = false;
+static bool DoWritePrevVersion = false;
+static memprof::IndexedVersion MemProfVersionRequested = memprof::Version3;
+static bool MemProfFullSchema = false;
+static bool MemprofGenerateRandomHotness = false;
+static unsigned MemprofGenerateRandomHotnessSeed = 0;
+
+static std::string BaseFilename;
+static std::string TestFilename;
+static unsigned long long SimilarityCutoff = 0;
+static bool IsCS = false;
+static unsigned long long OverlapValueCutoff =
+    std::numeric_limits<unsigned long long>::max();
+
+static bool ShowCounts = false;
+static ShowFormat SFormat = ShowFormat::Text;
+static bool TextFormat = false;
+static bool JsonFormat = false;
+static bool ShowIndirectCallTargets = false;
+static bool ShowVTables = false;
+static bool ShowMemOPSizes = false;
+static bool ShowDetailedSummary = false;
+static std::vector<uint32_t> DetailedSummaryCutoffs;
+static bool ShowHotFuncList = false;
+static bool ShowAllFunctions = false;
+static bool ShowCS = false;
+static ProfileKinds ShowProfileKind = instr;
+static uint32_t TopNFunctions = 0;
+static uint32_t ShowValueCutoff = 0;
+static bool OnlyListBelow = false;
+static bool ShowProfileSymbolList = false;
+static bool ShowSectionInfoOnly = false;
+static bool ShowBinaryIds = false;
+static bool ShowTemporalProfTraces = false;
+static bool ShowCovered = false;
+static bool ShowProfileVersion = false;
+
+static unsigned NumTestTraces = 0;
+static FSDiscriminatorPass FSDiscriminatorPassOption = PassLast;
+} // namespace
 
 // We use this string to indicate that there are
 // multiple static functions map to the same name.
@@ -637,7 +349,7 @@ class SymbolRemapper {
     return New.empty() ? Name : FunctionId(New);
   }
 };
-}
+} // namespace
 
 struct WeightedFile {
   std::string Filename;
@@ -897,13 +609,11 @@ getFuncName(const StringMap<InstrProfWriter::ProfilingData>::value_type &Val) {
   return Val.first();
 }
 
-static std::string
-getFuncName(const SampleProfileMap::value_type &Val) {
+static std::string getFuncName(const SampleProfileMap::value_type &Val) {
   return Val.second.getContext().toString();
 }
 
-template <typename T>
-static void filterFunctions(T &ProfileMap) {
+template <typename T> static void filterFunctions(T &ProfileMap) {
   bool hasFilter = !FuncNameFilter.empty();
   bool hasNegativeFilter = !FuncNameNegativeFilter.empty();
   if (!hasFilter && !hasNegativeFilter)
@@ -973,8 +683,8 @@ static void mergeInstrProfile(const WeightedFileVector &Inputs,
                               SymbolRemapper *Remapper,
                               int MaxDbgCorrelationWarnings,
                               const StringRef ProfiledBinary) {
-  const uint64_t TraceReservoirSize = TemporalProfTraceReservoirSize.getValue();
-  const uint64_t MaxTraceLength = TemporalProfMaxTraceLength.getValue();
+  const uint64_t TraceReservoirSize = TemporalProfTraceReservoirSize;
+  const uint64_t MaxTraceLength = TemporalProfMaxTraceLength;
   if (OutputFormat == PF_Compact_Binary)
     exitWithError("Compact Binary is deprecated");
   if (OutputFormat != PF_Binary && OutputFormat != PF_Ext_Binary &&
@@ -1171,21 +881,8 @@ const uint64_t HotPercentileIdx = 11;
 
 using sampleprof::FSDiscriminatorPass;
 
-// Internal options to set FSDiscriminatorPass. Used in merge and show
-// commands.
-static cl::opt<FSDiscriminatorPass> FSDiscriminatorPassOption(
-    "fs-discriminator-pass", cl::init(PassLast), cl::Hidden,
-    cl::desc("Zero out the discriminator bits for the FS discrimiantor "
-             "pass beyond this value. The enum values are defined in "
-             "Support/Discriminator.h"),
-    cl::values(clEnumVal(Base, "Use base discriminators only"),
-               clEnumVal(Pass1, "Use base and pass 1 discriminators"),
-               clEnumVal(Pass2, "Use base and pass 1-2 discriminators"),
-               clEnumVal(Pass3, "Use base and pass 1-3 discriminators"),
-               clEnumVal(PassLast, "Use all discriminator bits (default)")));
-
 static unsigned getDiscriminatorMask() {
-  return getN1Bits(getFSPassBitEnd(FSDiscriminatorPassOption.getValue()));
+  return getN1Bits(getFSPassBitEnd(FSDiscriminatorPassOption));
 }
 
 /// Adjust the instr profile in \p WC based on the sample profile in
@@ -1504,8 +1201,8 @@ remapSamples(const sampleprof::FunctionSamples &Samples,
                           BodySample.second.getSamples());
     for (const auto &Target : BodySample.second.getCallTargets()) {
       Result.addCalledTargetSamples(BodySample.first.LineOffset,
-                                    MaskedDiscriminator,
-                                    Remapper(Target.first), Target.second);
+                                    MaskedDiscriminator, Remapper(Target.first),
+                                    Target.second);
     }
   }
   for (const auto &CallsiteSamples : Samples.getCallsiteSamples()) {
@@ -1522,12 +1219,8 @@ remapSamples(const sampleprof::FunctionSamples &Samples,
 }
 
 static sampleprof::SampleProfileFormat FormatMap[] = {
-    sampleprof::SPF_None,
-    sampleprof::SPF_Text,
-    sampleprof::SPF_None,
-    sampleprof::SPF_Ext_Binary,
-    sampleprof::SPF_GCC,
-    sampleprof::SPF_Binary};
+    sampleprof::SPF_None,       sampleprof::SPF_Text, sampleprof::SPF_None,
+    sampleprof::SPF_Ext_Binary, sampleprof::SPF_GCC,  sampleprof::SPF_Binary};
 
 static std::unique_ptr<MemoryBuffer>
 getInputFileBuf(const StringRef &InputFile) {
@@ -3375,9 +3068,8 @@ static int showDebugInfoCorrelation(const std::string &Filename,
 
 static int show_main(StringRef ProgName) {
   if (Filename.empty() && DebugInfoFilename.empty())
-    exitWithError(
-        "the positional argument '<profdata-file>' is required unless '--" +
-        DebugInfoFilename.ArgStr + "' is provided");
+    exitWithError("the positional argument '<profdata-file>' is required "
+                  "unless '--debug-info' is provided");
 
   if (Filename == OutputFilename) {
     errs() << ProgName
@@ -3394,7 +3086,8 @@ static int show_main(StringRef ProgName) {
     exitWithErrorCode(EC, OutputFilename);
 
   if (ShowAllFunctions && !FuncNameFilter.empty())
-    WithColor::warning() << "-function argument ignored: showing all functions\n";
+    WithColor::warning()
+        << "-function argument ignored: showing all functions\n";
 
   if (!DebugInfoFilename.empty())
     return showDebugInfoCorrelation(DebugInfoFilename, SFormat, OS);
@@ -3424,7 +3117,7 @@ static int order_main() {
   ArrayRef Traces = Reader->getTemporalProfTraces();
   if (NumTestTraces && NumTestTraces >= Traces.size())
     exitWithError(
-        "--" + NumTestTraces.ArgStr +
+        "--num-test-traces"
         " must be smaller than the total number of traces: expected: < " +
         Twine(Traces.size()) + ", actual: " + Twine(NumTestTraces));
   ArrayRef TestTraces = Traces.take_back(NumTestTraces);
@@ -3471,32 +3164,477 @@ static int order_main() {
   return 0;
 }
 
-int main(int argc, const char *argv[]) {
-  InitLLVM X(argc, argv);
-  StringRef ProgName(sys::path::filename(argv[0]));
+static void reportCmdLineError(const Twine &Message) {
+  WithColor::error(errs(), ProgramName) << Message << "\n";
+}
+
+template <typename T>
+static bool parseNumericOption(const opt::Arg *A, T &Value) {
+  if (!A)
+    return true;
+  StringRef V = A->getValue();
+  T Parsed{};
+  if (!llvm::to_integer(V, Parsed, 0)) {
+    if (!std::numeric_limits<T>::is_signed && V == "-1") {
+      Value = std::numeric_limits<T>::max();
+      return true;
+    }
+    reportCmdLineError(Twine("invalid argument '") + V + "' for option '" +
+                       A->getSpelling() + "'");
+    return false;
+  }
+  Value = Parsed;
+  return true;
+}
+
+static bool parseFloatOption(const opt::Arg *A, float &Value) {
+  if (!A)
+    return true;
+  StringRef V = A->getValue();
+  double Parsed;
+  if (V.getAsDouble(Parsed)) {
+    reportCmdLineError(Twine("invalid argument '") + V + "' for option '" +
+                       A->getSpelling() + "'");
+    return false;
+  }
+  Value = static_cast<float>(Parsed);
+  return true;
+}
+
+static bool parseCutoffValues(const opt::InputArgList &Args,
+                              std::vector<uint32_t> &Cutoffs) {
+  Cutoffs.clear();
+  for (const opt::Arg *A : Args.filtered(OPT_detailed_summary_cutoffs)) {
+    SmallVector<StringRef, 4> Parts;
+    StringRef(A->getValue())
+        .split(Parts, ',', /*MaxSplit=*/-1,
+               /*KeepEmpty=*/false);
+    for (StringRef Part : Parts) {
+      uint32_t Parsed;
+      if (!llvm::to_integer(Part, Parsed, 0)) {
+        reportCmdLineError(Twine("invalid argument '") + Part +
+                           "' for option '" + A->getSpelling() + "'");
+        return false;
+      }
+      Cutoffs.push_back(Parsed);
+    }
+  }
+  return true;
+}
+
+static bool parseFSDiscriminatorPassArg(const opt::InputArgList &Args) {
+  const opt::Arg *A = Args.getLastArg(OPT_fs_discriminator_pass);
+  if (!A)
+    return true;
+
+  StringRef Value = A->getValue();
+  auto Parsed = StringSwitch<std::optional<FSDiscriminatorPass>>(Value)
+                    .Case("Base", FSDiscriminatorPass::Base)
+                    .Case("base", FSDiscriminatorPass::Base)
+                    .Case("Pass1", FSDiscriminatorPass::Pass1)
+                    .Case("pass1", FSDiscriminatorPass::Pass1)
+                    .Case("Pass2", FSDiscriminatorPass::Pass2)
+                    .Case("pass2", FSDiscriminatorPass::Pass2)
+                    .Case("Pass3", FSDiscriminatorPass::Pass3)
+                    .Case("pass3", FSDiscriminatorPass::Pass3)
+                    .Case("PassLast", FSDiscriminatorPass::PassLast)
+                    .Case("pass-last", FSDiscriminatorPass::PassLast)
+                    .Case("passlast", FSDiscriminatorPass::PassLast)
+                    .Default(std::nullopt);
+  if (!Parsed) {
+    reportCmdLineError(Twine("invalid argument '") + Value + "' for option '" +
+                       A->getSpelling() + "'");
+    return false;
+  }
+  FSDiscriminatorPassOption = *Parsed;
+  return true;
+}
+
+static bool validateSubcommandOptions(const opt::InputArgList &Args,
+                                      StringRef Subcommand) {
+  bool Valid = true;
+  for (const opt::Arg *A : Args) {
+    if (A->getOption().matches(OPT_UNKNOWN) ||
+        A->getOption().matches(OPT_INPUT))
+      continue;
+    if (A->getOption().isRegisteredSC(Subcommand))
+      continue;
+    reportCmdLineError(Twine("unknown command line argument '") +
+                       A->getSpelling() + "' for subcommand '" + Subcommand +
+                       "'. Try: '" + ProgramName + " " + Subcommand +
+                       " --help'");
+    Valid = false;
+  }
+  return Valid;
+}
+
+static void printSubcommandHelp(const ProfdataOptTable &Tbl,
+                                StringRef Subcommand, bool ShowHidden) {
+  SmallString<64> Usage;
+  Usage += ProgramName;
+  Usage += " ";
+  Usage += Subcommand;
+  Usage += " [options]";
+  Tbl.printHelp(outs(), Usage.c_str(), "LLVM profile data", ShowHidden,
+                /*ShowAllAliases=*/false, Visibility(), Subcommand);
+  outs() << "\nPass @FILE as argument to read options from FILE.\n";
+}
+
+static void printTopLevelHelp() {
+  outs() << "OVERVIEW: LLVM profile data\n\n"
+         << "USAGE: " << ProgramName << " <subcommand> [options]\n\n"
+         << "SUBCOMMANDS:\n";
+  for (const auto &SC : OptionSubCommands)
+    outs() << "  " << SC.Name << " - " << SC.HelpText << "\n";
+  outs() << "\n  Type \"" << ProgramName
+         << " <subcommand> --help\" to get more help on a specific "
+            "subcommand\n\n"
+         << "OPTIONS:\n  --help        Display this message\n"
+         << "  --version     Display the version\n";
+}
+
+static bool parseMergeOptions(const opt::InputArgList &Args) {
+  OutputFilename = Args.getLastArgValue(OPT_output, "-").str();
+  if (const opt::Arg *A = Args.getLastArg(OPT_sample, OPT_instr))
+    ProfileKind = A->getOption().matches(OPT_sample) ? sample : instr;
+
+  if (!parseNumericOption(
+          Args.getLastArg(OPT_max_debug_info_correlation_warnings),
+          MaxDbgCorrelationWarnings))
+    return false;
+  ProfiledBinary = Args.getLastArgValue(OPT_profiled_binary).str();
+  DebugInfoFilename = Args.getLastArgValue(OPT_debug_info).str();
+  BinaryFilename = Args.getLastArgValue(OPT_binary_file).str();
+  DebugFileDirectory = Args.getAllArgValues(OPT_debug_file_directory);
+  DebugInfod = Args.hasArg(OPT_debuginfod);
+
+  if (const opt::Arg *A = Args.getLastArg(OPT_correlate)) {
+    StringRef V = A->getValue();
+    auto Parsed = StringSwitch<std::optional<ProfCorrelatorKind>>(V)
+                      .Case("", InstrProfCorrelator::NONE)
+                      .Case("debug-info", InstrProfCorrelator::DEBUG_INFO)
+                      .Case("binary", InstrProfCorrelator::BINARY)
+                      .Default(std::nullopt);
+    if (!Parsed) {
+      reportCmdLineError(Twine("invalid argument '") + V + "' for option '" +
+                         A->getSpelling() + "'");
+      return false;
+    }
+    BIDFetcherProfileCorrelate = *Parsed;
+  }
+
+  FuncNameFilter = Args.getLastArgValue(OPT_function).str();
+  InputFilenames = Args.getAllArgValues(OPT_INPUT);
+  WeightedInputFilenames = Args.getAllArgValues(OPT_weighted_input);
+
+  OutputFormat = PF_Ext_Binary;
+  if (const opt::Arg *Fmt =
+          Args.getLastArg(OPT_binary, OPT_extbinary, OPT_text, OPT_gcc)) {
+    if (Fmt->getOption().matches(OPT_binary))
+      OutputFormat = PF_Binary;
+    else if (Fmt->getOption().matches(OPT_gcc))
+      OutputFormat = PF_GCC;
+    else if (Fmt->getOption().matches(OPT_text))
+      OutputFormat = PF_Text;
+    else
+      OutputFormat = PF_Ext_Binary;
+  }
+
+  InputFilenamesFile = Args.getLastArgValue(OPT_input_files).str();
+  DumpInputFileList = Args.hasArg(OPT_dump_input_file_list);
+  RemappingFile = Args.getLastArgValue(OPT_remapping_file).str();
+  UseMD5 = Args.hasArg(OPT_use_md5);
+  CompressAllSections = Args.hasArg(OPT_compress_all_sections);
+  SampleMergeColdContext = Args.hasArg(OPT_sample_merge_cold_context);
+  SampleTrimColdContext = Args.hasArg(OPT_sample_trim_cold_context);
+  if (!parseNumericOption(
+          Args.getLastArg(OPT_sample_frame_depth_for_cold_context),
+          SampleColdContextFrameDepth))
+    return false;
+  if (!parseNumericOption(Args.getLastArg(OPT_output_size_limit),
+                          OutputSizeLimit))
+    return false;
+  GenPartialProfile = Args.hasArg(OPT_gen_partial_profile);
+  SplitLayout = Args.hasArg(OPT_split_layout);
+  SupplInstrWithSample =
+      Args.getLastArgValue(OPT_supplement_instr_with_sample).str();
+  if (!parseFloatOption(Args.getLastArg(OPT_zero_counter_threshold),
+                        ZeroCounterThreshold))
+    return false;
+  if (!parseNumericOption(Args.getLastArg(OPT_suppl_min_size_threshold),
+                          SupplMinSizeThreshold))
+    return false;
+  if (!parseNumericOption(Args.getLastArg(OPT_instr_prof_cold_threshold),
+                          InstrProfColdThreshold))
+    return false;
+  if (!parseNumericOption(
+          Args.getLastArg(OPT_temporal_profile_trace_reservoir_size),
+          TemporalProfTraceReservoirSize))
+    return false;
+  if (!parseNumericOption(
+          Args.getLastArg(OPT_temporal_profile_max_trace_length),
+          TemporalProfMaxTraceLength))
+    return false;
+  FuncNameNegativeFilter = Args.getLastArgValue(OPT_no_function).str();
+
+  StringRef FailureModeValue = Args.getLastArgValue(OPT_failure_mode, "any");
+  auto ParsedFailMode =
+      StringSwitch<std::optional<FailureMode>>(FailureModeValue)
+          .Case("warn", warnOnly)
+          .Case("any", failIfAnyAreInvalid)
+          .Case("all", failIfAllAreInvalid)
+          .Default(std::nullopt);
+  if (!ParsedFailMode) {
+    reportCmdLineError(Twine("invalid argument '") + FailureModeValue +
+                       "' for option '--failure-mode'");
+    return false;
+  }
+  FailMode = *ParsedFailMode;
+
+  OutputSparse = Args.hasArg(OPT_sparse);
+  if (!parseNumericOption(Args.getLastArg(OPT_num_threads), NumThreads))
+    return false;
+  ProfileSymbolListFile = Args.getLastArgValue(OPT_prof_sym_list).str();
+
+  if (const opt::Arg *A = Args.getLastArg(OPT_convert_sample_profile_layout)) {
+    StringRef Layout = A->getValue();
+    auto ParsedLayout = StringSwitch<std::optional<SampleProfileLayout>>(Layout)
+                            .Case("nest", SPL_Nest)
+                            .Case("flat", SPL_Flat)
+                            .Default(std::nullopt);
+    if (!ParsedLayout) {
+      reportCmdLineError(Twine("invalid argument '") + Layout +
+                         "' for option '" + A->getSpelling() + "'");
+      return false;
+    }
+    ProfileLayout = *ParsedLayout;
+  }
+
+  DropProfileSymbolList = Args.hasArg(OPT_drop_profile_symbol_list);
+  KeepVTableSymbols = Args.hasArg(OPT_keep_vtable_symbols);
+  DoWritePrevVersion = Args.hasArg(OPT_write_prev_version);
+
+  if (const opt::Arg *A = Args.getLastArg(OPT_memprof_version)) {
+    StringRef Version = A->getValue();
+    auto ParsedVersion =
+        StringSwitch<std::optional<memprof::IndexedVersion>>(Version)
+            .Case("2", memprof::Version2)
+            .Case("3", memprof::Version3)
+            .Case("4", memprof::Version4)
+            .Default(std::nullopt);
+    if (!ParsedVersion) {
+      reportCmdLineError(Twine("invalid argument '") + Version +
+                         "' for option '" + A->getSpelling() + "'");
+      return false;
+    }
+    MemProfVersionRequested = *ParsedVersion;
+  }
+
+  MemProfFullSchema = Args.hasArg(OPT_memprof_full_schema);
+  MemprofGenerateRandomHotness = Args.hasArg(OPT_memprof_random_hotness);
+  if (!parseNumericOption(Args.getLastArg(OPT_memprof_random_hotness_seed),
+                          MemprofGenerateRandomHotnessSeed))
+    return false;
+
+  if (!parseFSDiscriminatorPassArg(Args))
+    return false;
+
+  return true;
+}
+
+static bool parseShowOptions(const opt::InputArgList &Args) {
+  OutputFilename = Args.getLastArgValue(OPT_output, "-").str();
+  std::vector<std::string> Inputs = Args.getAllArgValues(OPT_INPUT);
+  if (!Inputs.empty()) {
+    Filename = Inputs.front();
+    if (Inputs.size() > 1) {
+      reportCmdLineError("too many positional arguments");
+      return false;
+    }
+  }
+
+  if (!parseNumericOption(
+          Args.getLastArg(OPT_max_debug_info_correlation_warnings),
+          MaxDbgCorrelationWarnings))
+    return false;
+  ProfiledBinary = Args.getLastArgValue(OPT_profiled_binary).str();
+  DebugInfoFilename = Args.getLastArgValue(OPT_debug_info).str();
+  FuncNameFilter = Args.getLastArgValue(OPT_function).str();
+
+  ShowCounts = Args.hasArg(OPT_counts);
+  if (const opt::Arg *A = Args.getLastArg(OPT_show_format)) {
+    StringRef Value = A->getValue();
+    auto Parsed = StringSwitch<std::optional<ShowFormat>>(Value)
+                      .Case("text", ShowFormat::Text)
+                      .Case("json", ShowFormat::Json)
+                      .Case("yaml", ShowFormat::Yaml)
+                      .Default(std::nullopt);
+    if (!Parsed) {
+      reportCmdLineError(Twine("invalid argument '") + Value +
+                         "' for option '" + A->getSpelling() + "'");
+      return false;
+    }
+    SFormat = *Parsed;
+  }
+  TextFormat = Args.hasArg(OPT_text);
+  JsonFormat = Args.hasArg(OPT_json);
+  ShowIndirectCallTargets = Args.hasArg(OPT_ic_targets);
+  ShowVTables = Args.hasArg(OPT_show_vtables);
+  ShowMemOPSizes = Args.hasArg(OPT_memop_sizes);
+  ShowDetailedSummary = Args.hasArg(OPT_detailed_summary);
+  if (!parseCutoffValues(Args, DetailedSummaryCutoffs))
+    return false;
+  ShowHotFuncList = Args.hasArg(OPT_hot_func_list);
+  ShowAllFunctions = Args.hasArg(OPT_all_functions);
+  ShowCS = Args.hasArg(OPT_showcs);
+  if (!parseNumericOption(Args.getLastArg(OPT_topn), TopNFunctions))
+    return false;
+  if (!parseNumericOption(Args.getLastArg(OPT_value_cutoff), ShowValueCutoff))
+    return false;
+  OnlyListBelow = Args.hasArg(OPT_list_below_cutoff);
+  ShowProfileSymbolList = Args.hasArg(OPT_show_prof_sym_list);
+  ShowSectionInfoOnly = Args.hasArg(OPT_show_sec_info_only);
+  ShowBinaryIds = Args.hasArg(OPT_binary_ids);
+  ShowTemporalProfTraces = Args.hasArg(OPT_temporal_profile_traces);
+  ShowCovered = Args.hasArg(OPT_covered);
+  ShowProfileVersion = Args.hasArg(OPT_profile_version);
+
+  if (const opt::Arg *A = Args.getLastArg(OPT_memory, OPT_sample, OPT_instr)) {
+    if (A->getOption().matches(OPT_memory))
+      ShowProfileKind = memory;
+    else if (A->getOption().matches(OPT_sample))
+      ShowProfileKind = sample;
+    else
+      ShowProfileKind = instr;
+  }
+
+  if (!parseFSDiscriminatorPassArg(Args))
+    return false;
+
+  return true;
+}
+
+static bool parseOverlapOptions(const opt::InputArgList &Args) {
+  OutputFilename = Args.getLastArgValue(OPT_output, "-").str();
+  std::vector<std::string> Inputs = Args.getAllArgValues(OPT_INPUT);
+  if (Inputs.size() != 2) {
+    reportCmdLineError("overlap requires two positional profile filenames");
+    return false;
+  }
+  BaseFilename = Inputs[0];
+  TestFilename = Inputs[1];
+
+  if (const opt::Arg *A = Args.getLastArg(OPT_sample, OPT_instr))
+    ProfileKind = A->getOption().matches(OPT_sample) ? sample : instr;
+
+  FuncNameFilter = Args.getLastArgValue(OPT_function).str();
+  if (!parseNumericOption(Args.getLastArg(OPT_similarity_cutoff),
+                          SimilarityCutoff))
+    return false;
+  IsCS = Args.hasArg(OPT_cs);
+  if (!parseNumericOption(Args.getLastArg(OPT_value_cutoff),
+                          OverlapValueCutoff))
+    return false;
+
+  if (!parseFSDiscriminatorPassArg(Args))
+    return false;
+
+  return true;
+}
+
+static bool parseOrderOptions(const opt::InputArgList &Args) {
+  OutputFilename = Args.getLastArgValue(OPT_output, "-").str();
+  std::vector<std::string> Inputs = Args.getAllArgValues(OPT_INPUT);
+  if (!Inputs.empty()) {
+    Filename = Inputs.front();
+    if (Inputs.size() > 1) {
+      reportCmdLineError("too many positional arguments");
+      return false;
+    }
+  }
+  if (!parseNumericOption(Args.getLastArg(OPT_num_test_traces), NumTestTraces))
+    return false;
+  return true;
+}
+
+int llvm_profdata_main(int argc, char **argv, const llvm::ToolContext &) {
+  BumpPtrAllocator Alloc;
+  StringSaver Saver(Alloc);
+
+  ProgramName = sys::path::filename(argv[0]).str();
 
   if (argc < 2) {
-    errs()
-        << ProgName
-        << ": No subcommand specified! Run llvm-profdata --help for usage.\n";
+    errs() << ProgramName << ": No subcommand specified! Run " << ProgramName
+           << " --help for usage.\n";
     return 1;
   }
 
-  cl::ParseCommandLineOptions(argc, argv, "LLVM profile data\n");
+  StringRef Subcommand = argv[1];
+  if (Subcommand == "--help" || Subcommand == "-help" || Subcommand == "-h") {
+    printTopLevelHelp();
+    return 0;
+  }
 
-  if (ShowSubcommand)
-    return show_main(ProgName);
+  if (Subcommand == "--version" || Subcommand == "-version") {
+    outs() << ProgramName << '\n';
+    cl::PrintVersionMessage();
+    return 0;
+  }
 
-  if (OrderSubcommand)
-    return order_main();
+  if (Subcommand != "show" && Subcommand != "order" &&
+      Subcommand != "overlap" && Subcommand != "merge") {
+    errs() << ProgramName << ": Unknown command. Run " << ProgramName
+           << " --help for usage.\n";
+    return 1;
+  }
 
-  if (OverlapSubcommand)
-    return overlap_main();
+  ProfdataOptTable Tbl;
+  bool HadParseError = false;
+  opt::InputArgList Args =
+      Tbl.parseArgs(argc - 2, argv + 2, OPT_UNKNOWN, Saver, [&](StringRef Msg) {
+        WithColor::error(errs(), ProgramName) << Msg << "\n";
+        HadParseError = true;
+      });
+  if (HadParseError)
+    return 1;
 
-  if (MergeSubcommand)
-    return merge_main(ProgName);
+  bool ShowHidden = Args.hasArg(OPT_help_hidden);
+  if (Args.hasArg(OPT_help) || ShowHidden) {
+    printSubcommandHelp(Tbl, Subcommand, ShowHidden);
+    return 0;
+  }
+
+  if (!validateSubcommandOptions(Args, Subcommand))
+    return 1;
+
+  if (Args.hasArg(OPT_version)) {
+    outs() << ProgramName << '\n';
+    cl::PrintVersionMessage();
+    return 0;
+  }
+
+  if (Subcommand == "merge") {
+    if (!parseMergeOptions(Args))
+      return 1;
+    return merge_main(ProgramName);
+  }
+  if (Subcommand == "show") {
+    if (!parseShowOptions(Args))
+      return 1;
+    return show_main(ProgramName);
+  }
+  if (Subcommand == "overlap") {
+    if (!parseOverlapOptions(Args))
+      return 1;
+    return overlap_main();
+  }
+  if (Subcommand == "order") {
+    if (!parseOrderOptions(Args))
+      return 1;
+    return order_main();
+  }
 
-  errs() << ProgName
-         << ": Unknown command. Run llvm-profdata --help for usage.\n";
+  errs() << ProgramName << ": Unknown command. Run " << ProgramName
+         << " --help for usage.\n";
   return 1;
 }
diff --git a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
index e83a237a5397f..e02631d34a382 100644
--- a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
@@ -5843,12 +5843,23 @@ cc_library(
         ":Core",
         ":Debuginfod",
         ":Object",
+        ":Option",
+        ":ProfdataOptsTableGen",
         ":ProfileData",
         ":Support",
     ],
 )
 
-cc_binary(
+gentbl_cc_library(
+    name = "ProfdataOptsTableGen",
+    strip_include_prefix = "tools/llvm-profdata",
+    tbl_outs = {"tools/llvm-profdata/Opts.inc": ["-gen-opt-parser-defs"]},
+    tblgen = ":llvm-tblgen",
+    td_file = "tools/llvm-profdata/Opts.td",
+    deps = [":OptParserTdFiles"],
+)
+
+llvm_driver_cc_binary(
     name = "llvm-profdata",
     stamp = 0,
     deps = [":llvm-profdata-lib"],
diff --git a/utils/bazel/llvm-project-overlay/llvm/driver.bzl b/utils/bazel/llvm-project-overlay/llvm/driver.bzl
index e38c681370ed1..331e3f7e0db1f 100644
--- a/utils/bazel/llvm-project-overlay/llvm/driver.bzl
+++ b/utils/bazel/llvm-project-overlay/llvm/driver.bzl
@@ -28,6 +28,7 @@ _TOOLS = {
     "llvm-nm": "//llvm:llvm-nm-lib",
     "llvm-objcopy": "//llvm:llvm-objcopy-lib",
     "llvm-objdump": "//llvm:llvm-objdump-lib",
+    "llvm-profdata": "//llvm:llvm-profdata-lib",
     "llvm-rc": "//llvm:llvm-rc-lib",
     "llvm-readobj": "//llvm:llvm-readobj-lib",
     "llvm-size": "//llvm:llvm-size-lib",

>From c402d4a268be7e5e2761745f7a353ef31dfc6a01 Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Mon, 26 Jan 2026 13:53:58 -0500
Subject: [PATCH 2/2] CR feedback

---
 llvm/test/tools/llvm-profdata/help.test    |  22 ++
 llvm/tools/llvm-profdata/llvm-profdata.cpp | 230 +++++++++++++--------
 2 files changed, 171 insertions(+), 81 deletions(-)
 create mode 100644 llvm/test/tools/llvm-profdata/help.test

diff --git a/llvm/test/tools/llvm-profdata/help.test b/llvm/test/tools/llvm-profdata/help.test
new file mode 100644
index 0000000000000..b1b1c6da8dd72
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/help.test
@@ -0,0 +1,22 @@
+# RUN: llvm-profdata --help | FileCheck %s --check-prefix=TOP
+# RUN: llvm-profdata merge --help | FileCheck %s --check-prefix=MERGE
+# RUN: llvm-profdata show --help | FileCheck %s --check-prefix=SHOW
+
+# TOP: OVERVIEW: LLVM profile data
+# TOP: USAGE: llvm-profdata [subcommand] [options]
+# TOP: SUBCOMMANDS:
+# TOP: merge - Takes several profiles and merge them together.
+# TOP: show - Takes a profile data file and displays the profiles.
+
+# MERGE: OVERVIEW: LLVM profile data
+# MERGE: Takes several profiles and merge them together.
+# MERGE: OPTIONS:
+# MERGE: -instr
+# MERGE: -output <output>
+# MERGE: -sample
+
+# SHOW: OVERVIEW: LLVM profile data
+# SHOW: Takes a profile data file and displays the profiles.
+# SHOW: OPTIONS:
+# SHOW: -output <output>
+# SHOW: -show-format
diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp
index 4c8c7519df9ca..97c1fcdf98ec4 100644
--- a/llvm/tools/llvm-profdata/llvm-profdata.cpp
+++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp
@@ -3168,11 +3168,17 @@ static void reportCmdLineError(const Twine &Message) {
   WithColor::error(errs(), ProgramName) << Message << "\n";
 }
 
+static StringRef getOptionValue(const opt::Arg *A) {
+  StringRef V = A->getValue();
+  V.consume_front("=");
+  return V;
+}
+
 template <typename T>
 static bool parseNumericOption(const opt::Arg *A, T &Value) {
   if (!A)
     return true;
-  StringRef V = A->getValue();
+  StringRef V = getOptionValue(A);
   T Parsed{};
   if (!llvm::to_integer(V, Parsed, 0)) {
     if (!std::numeric_limits<T>::is_signed && V == "-1") {
@@ -3190,7 +3196,7 @@ static bool parseNumericOption(const opt::Arg *A, T &Value) {
 static bool parseFloatOption(const opt::Arg *A, float &Value) {
   if (!A)
     return true;
-  StringRef V = A->getValue();
+  StringRef V = getOptionValue(A);
   double Parsed;
   if (V.getAsDouble(Parsed)) {
     reportCmdLineError(Twine("invalid argument '") + V + "' for option '" +
@@ -3206,9 +3212,8 @@ static bool parseCutoffValues(const opt::InputArgList &Args,
   Cutoffs.clear();
   for (const opt::Arg *A : Args.filtered(OPT_detailed_summary_cutoffs)) {
     SmallVector<StringRef, 4> Parts;
-    StringRef(A->getValue())
-        .split(Parts, ',', /*MaxSplit=*/-1,
-               /*KeepEmpty=*/false);
+    getOptionValue(A).split(Parts, ',', /*MaxSplit=*/-1,
+                            /*KeepEmpty=*/false);
     for (StringRef Part : Parts) {
       uint32_t Parsed;
       if (!llvm::to_integer(Part, Parsed, 0)) {
@@ -3268,32 +3273,8 @@ static bool validateSubcommandOptions(const opt::InputArgList &Args,
   return Valid;
 }
 
-static void printSubcommandHelp(const ProfdataOptTable &Tbl,
-                                StringRef Subcommand, bool ShowHidden) {
-  SmallString<64> Usage;
-  Usage += ProgramName;
-  Usage += " ";
-  Usage += Subcommand;
-  Usage += " [options]";
-  Tbl.printHelp(outs(), Usage.c_str(), "LLVM profile data", ShowHidden,
-                /*ShowAllAliases=*/false, Visibility(), Subcommand);
-  outs() << "\nPass @FILE as argument to read options from FILE.\n";
-}
-
-static void printTopLevelHelp() {
-  outs() << "OVERVIEW: LLVM profile data\n\n"
-         << "USAGE: " << ProgramName << " <subcommand> [options]\n\n"
-         << "SUBCOMMANDS:\n";
-  for (const auto &SC : OptionSubCommands)
-    outs() << "  " << SC.Name << " - " << SC.HelpText << "\n";
-  outs() << "\n  Type \"" << ProgramName
-         << " <subcommand> --help\" to get more help on a specific "
-            "subcommand\n\n"
-         << "OPTIONS:\n  --help        Display this message\n"
-         << "  --version     Display the version\n";
-}
-
-static bool parseMergeOptions(const opt::InputArgList &Args) {
+static bool parseMergeOptions(const opt::InputArgList &Args,
+                              ArrayRef<StringRef> Positionals) {
   OutputFilename = Args.getLastArgValue(OPT_output, "-").str();
   if (const opt::Arg *A = Args.getLastArg(OPT_sample, OPT_instr))
     ProfileKind = A->getOption().matches(OPT_sample) ? sample : instr;
@@ -3309,7 +3290,7 @@ static bool parseMergeOptions(const opt::InputArgList &Args) {
   DebugInfod = Args.hasArg(OPT_debuginfod);
 
   if (const opt::Arg *A = Args.getLastArg(OPT_correlate)) {
-    StringRef V = A->getValue();
+    StringRef V = getOptionValue(A);
     auto Parsed = StringSwitch<std::optional<ProfCorrelatorKind>>(V)
                       .Case("", InstrProfCorrelator::NONE)
                       .Case("debug-info", InstrProfCorrelator::DEBUG_INFO)
@@ -3324,7 +3305,10 @@ static bool parseMergeOptions(const opt::InputArgList &Args) {
   }
 
   FuncNameFilter = Args.getLastArgValue(OPT_function).str();
-  InputFilenames = Args.getAllArgValues(OPT_INPUT);
+  InputFilenames.clear();
+  InputFilenames.reserve(Positionals.size());
+  for (StringRef Pos : Positionals)
+    InputFilenames.emplace_back(Pos.str());
   WeightedInputFilenames = Args.getAllArgValues(OPT_weighted_input);
 
   OutputFormat = PF_Ext_Binary;
@@ -3397,7 +3381,7 @@ static bool parseMergeOptions(const opt::InputArgList &Args) {
   ProfileSymbolListFile = Args.getLastArgValue(OPT_prof_sym_list).str();
 
   if (const opt::Arg *A = Args.getLastArg(OPT_convert_sample_profile_layout)) {
-    StringRef Layout = A->getValue();
+    StringRef Layout = getOptionValue(A);
     auto ParsedLayout = StringSwitch<std::optional<SampleProfileLayout>>(Layout)
                             .Case("nest", SPL_Nest)
                             .Case("flat", SPL_Flat)
@@ -3415,7 +3399,7 @@ static bool parseMergeOptions(const opt::InputArgList &Args) {
   DoWritePrevVersion = Args.hasArg(OPT_write_prev_version);
 
   if (const opt::Arg *A = Args.getLastArg(OPT_memprof_version)) {
-    StringRef Version = A->getValue();
+    StringRef Version = getOptionValue(A);
     auto ParsedVersion =
         StringSwitch<std::optional<memprof::IndexedVersion>>(Version)
             .Case("2", memprof::Version2)
@@ -3442,12 +3426,12 @@ static bool parseMergeOptions(const opt::InputArgList &Args) {
   return true;
 }
 
-static bool parseShowOptions(const opt::InputArgList &Args) {
+static bool parseShowOptions(const opt::InputArgList &Args,
+                             ArrayRef<StringRef> Positionals) {
   OutputFilename = Args.getLastArgValue(OPT_output, "-").str();
-  std::vector<std::string> Inputs = Args.getAllArgValues(OPT_INPUT);
-  if (!Inputs.empty()) {
-    Filename = Inputs.front();
-    if (Inputs.size() > 1) {
+  if (!Positionals.empty()) {
+    Filename = Positionals.front().str();
+    if (Positionals.size() > 1) {
       reportCmdLineError("too many positional arguments");
       return false;
     }
@@ -3463,7 +3447,7 @@ static bool parseShowOptions(const opt::InputArgList &Args) {
 
   ShowCounts = Args.hasArg(OPT_counts);
   if (const opt::Arg *A = Args.getLastArg(OPT_show_format)) {
-    StringRef Value = A->getValue();
+    StringRef Value = getOptionValue(A);
     auto Parsed = StringSwitch<std::optional<ShowFormat>>(Value)
                       .Case("text", ShowFormat::Text)
                       .Case("json", ShowFormat::Json)
@@ -3514,15 +3498,15 @@ static bool parseShowOptions(const opt::InputArgList &Args) {
   return true;
 }
 
-static bool parseOverlapOptions(const opt::InputArgList &Args) {
+static bool parseOverlapOptions(const opt::InputArgList &Args,
+                                ArrayRef<StringRef> Positionals) {
   OutputFilename = Args.getLastArgValue(OPT_output, "-").str();
-  std::vector<std::string> Inputs = Args.getAllArgValues(OPT_INPUT);
-  if (Inputs.size() != 2) {
+  if (Positionals.size() != 2) {
     reportCmdLineError("overlap requires two positional profile filenames");
     return false;
   }
-  BaseFilename = Inputs[0];
-  TestFilename = Inputs[1];
+  BaseFilename = Positionals[0].str();
+  TestFilename = Positionals[1].str();
 
   if (const opt::Arg *A = Args.getLastArg(OPT_sample, OPT_instr))
     ProfileKind = A->getOption().matches(OPT_sample) ? sample : instr;
@@ -3542,12 +3526,12 @@ static bool parseOverlapOptions(const opt::InputArgList &Args) {
   return true;
 }
 
-static bool parseOrderOptions(const opt::InputArgList &Args) {
+static bool parseOrderOptions(const opt::InputArgList &Args,
+                              ArrayRef<StringRef> Positionals) {
   OutputFilename = Args.getLastArgValue(OPT_output, "-").str();
-  std::vector<std::string> Inputs = Args.getAllArgValues(OPT_INPUT);
-  if (!Inputs.empty()) {
-    Filename = Inputs.front();
-    if (Inputs.size() > 1) {
+  if (!Positionals.empty()) {
+    Filename = Positionals.front().str();
+    if (Positionals.size() > 1) {
       reportCmdLineError("too many positional arguments");
       return false;
     }
@@ -3563,47 +3547,131 @@ int llvm_profdata_main(int argc, char **argv, const llvm::ToolContext &) {
 
   ProgramName = sys::path::filename(argv[0]).str();
 
-  if (argc < 2) {
-    errs() << ProgramName << ": No subcommand specified! Run " << ProgramName
-           << " --help for usage.\n";
-    return 1;
-  }
-
-  StringRef Subcommand = argv[1];
-  if (Subcommand == "--help" || Subcommand == "-help" || Subcommand == "-h") {
-    printTopLevelHelp();
-    return 0;
-  }
-
-  if (Subcommand == "--version" || Subcommand == "-version") {
-    outs() << ProgramName << '\n';
-    cl::PrintVersionMessage();
-    return 0;
-  }
-
-  if (Subcommand != "show" && Subcommand != "order" &&
-      Subcommand != "overlap" && Subcommand != "merge") {
-    errs() << ProgramName << ": Unknown command. Run " << ProgramName
-           << " --help for usage.\n";
-    return 1;
+  ProfdataOptTable Tbl;
+  ArrayRef<opt::OptTable::SubCommand> KnownSubcommands = Tbl.getSubCommands();
+  auto IsKnownSubcommand = [&](StringRef Name) {
+    return llvm::any_of(
+        KnownSubcommands,
+        [&](const opt::OptTable::SubCommand &SC) { return Name == SC.Name; });
+  };
+  ArrayRef<const char *> RawArgs(argv + 1, argv + argc);
+  SmallVector<StringRef, 4> RawSubcommands;
+  for (const char *Arg : RawArgs) {
+    StringRef ArgRef(Arg);
+    if (!ArgRef.starts_with("-") && IsKnownSubcommand(ArgRef))
+      RawSubcommands.push_back(ArgRef);
+  }
+  auto HasFlag = [&](StringRef Flag) {
+    return llvm::is_contained(RawArgs, Flag);
+  };
+  auto HasAnySubcommand = [&]() { return !RawSubcommands.empty(); };
+
+  if (!HasAnySubcommand()) {
+    if (HasFlag("--help") || HasFlag("-help") || HasFlag("-h")) {
+      std::string Usage = ProgramName + " [subcommand] [options]";
+      Tbl.printHelp(outs(), Usage.c_str(), "LLVM profile data",
+                    /*ShowHidden=*/false, /*ShowAllAliases=*/false,
+                    Visibility(), /*Subcommand=*/{});
+      return 0;
+    }
+    if (HasFlag("--help-hidden")) {
+      std::string Usage = ProgramName + " [subcommand] [options]";
+      Tbl.printHelp(outs(), Usage.c_str(), "LLVM profile data",
+                    /*ShowHidden=*/true, /*ShowAllAliases=*/false, Visibility(),
+                    /*Subcommand=*/{});
+      return 0;
+    }
+    if (HasFlag("--version") || HasFlag("-version")) {
+      outs() << ProgramName << '\n';
+      cl::PrintVersionMessage();
+      return 0;
+    }
   }
 
-  ProfdataOptTable Tbl;
   bool HadParseError = false;
   opt::InputArgList Args =
-      Tbl.parseArgs(argc - 2, argv + 2, OPT_UNKNOWN, Saver, [&](StringRef Msg) {
+      Tbl.parseArgs(argc - 1, argv + 1, OPT_UNKNOWN, Saver, [&](StringRef Msg) {
         WithColor::error(errs(), ProgramName) << Msg << "\n";
         HadParseError = true;
       });
   if (HadParseError)
     return 1;
 
+  bool HadSubcommandError = false;
+  SmallVector<StringRef, 4> OtherPositionals;
+  auto HandleMultipleSubcommands = [&](ArrayRef<StringRef> SubCommands) {
+    HadSubcommandError = true;
+    WithColor::error(errs(), ProgramName) << "multiple subcommands specified:";
+    for (StringRef SC : SubCommands)
+      errs() << " '" << SC << "'";
+    errs() << ".\n";
+  };
+  auto HandleOtherPositionals = [&](ArrayRef<StringRef> Positionals) {
+    OtherPositionals.append(Positionals.begin(), Positionals.end());
+  };
+
+  StringRef Subcommand = Args.getSubCommand(
+      Tbl.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
+  if (HadSubcommandError)
+    return 1;
+
+  if (Subcommand.empty() && RawSubcommands.size() > 1) {
+    HadSubcommandError = true;
+    HandleMultipleSubcommands(RawSubcommands);
+  }
+  if (HadSubcommandError)
+    return 1;
+
+  if (Subcommand.empty() && argc > 1 && IsKnownSubcommand(argv[1]))
+    Subcommand = argv[1];
+  if (Subcommand.empty() && !OtherPositionals.empty() &&
+      IsKnownSubcommand(OtherPositionals.front())) {
+    Subcommand = OtherPositionals.front();
+    OtherPositionals.erase(OtherPositionals.begin());
+  }
+  if (Subcommand.empty() && RawSubcommands.size() == 1)
+    Subcommand = RawSubcommands.front();
+  if (!Subcommand.empty()) {
+    auto It = llvm::find(OtherPositionals, Subcommand);
+    if (It != OtherPositionals.end())
+      OtherPositionals.erase(It);
+  }
+
+  ArrayRef<StringRef> Positionals = OtherPositionals;
+
   bool ShowHidden = Args.hasArg(OPT_help_hidden);
   if (Args.hasArg(OPT_help) || ShowHidden) {
-    printSubcommandHelp(Tbl, Subcommand, ShowHidden);
+    std::string Usage = ProgramName + " [subcommand] [options]";
+    Tbl.printHelp(outs(), Usage.c_str(), "LLVM profile data",
+                  /*ShowHidden=*/ShowHidden, /*ShowAllAliases=*/false,
+                  Visibility(), Subcommand);
     return 0;
   }
 
+  if (Subcommand.empty()) {
+    if (Args.hasArg(OPT_version)) {
+      outs() << ProgramName << '\n';
+      cl::PrintVersionMessage();
+      return 0;
+    }
+    if (!OtherPositionals.empty()) {
+      WithColor::error(errs(), ProgramName) << "Unknown command";
+      if (OtherPositionals.size() != 1)
+        errs() << "s";
+      errs() << " ";
+      for (size_t I = 0; I < OtherPositionals.size(); ++I) {
+        if (I)
+          errs() << ", ";
+        errs() << "'" << OtherPositionals[I] << "'";
+      }
+      errs() << ". Run " << ProgramName << " --help for usage.\n";
+    } else {
+      errs() << ProgramName << ": No subcommand specified! Run " << ProgramName
+             << " --help for usage.\n";
+    }
+    return 1;
+  }
+
   if (!validateSubcommandOptions(Args, Subcommand))
     return 1;
 
@@ -3614,22 +3682,22 @@ int llvm_profdata_main(int argc, char **argv, const llvm::ToolContext &) {
   }
 
   if (Subcommand == "merge") {
-    if (!parseMergeOptions(Args))
+    if (!parseMergeOptions(Args, Positionals))
       return 1;
     return merge_main(ProgramName);
   }
   if (Subcommand == "show") {
-    if (!parseShowOptions(Args))
+    if (!parseShowOptions(Args, Positionals))
       return 1;
     return show_main(ProgramName);
   }
   if (Subcommand == "overlap") {
-    if (!parseOverlapOptions(Args))
+    if (!parseOverlapOptions(Args, Positionals))
       return 1;
     return overlap_main();
   }
   if (Subcommand == "order") {
-    if (!parseOrderOptions(Args))
+    if (!parseOrderOptions(Args, Positionals))
       return 1;
     return order_main();
   }



More information about the llvm-commits mailing list