[llvm] Llvm profdata opttable (PR #200887)

Prabhu Rajasekaran via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 1 10:52:20 PDT 2026


https://github.com/Prabhuk created https://github.com/llvm/llvm-project/pull/200887

Completely LLM generated patch and not ready for review yet. There is an existing patch upstream under review and would like to collaborate with the author of that patch to decide on how best to port llvm-profdata to opttable. 

>From ffe289482209b5e28eb9bac3c192e34f721ef507 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 26 May 2026 15:58:42 -0700
Subject: [PATCH 1/3] llvm-profdata generated code

---
 llvm/tools/llvm-profdata/CMakeLists.txt    |    6 +
 llvm/tools/llvm-profdata/Opts.td           |  100 ++
 llvm/tools/llvm-profdata/llvm-profdata.cpp | 1040 +++++++++++---------
 3 files changed, 683 insertions(+), 463 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 be3daadf38716..bbfa555909977 100644
--- a/llvm/tools/llvm-profdata/CMakeLists.txt
+++ b/llvm/tools/llvm-profdata/CMakeLists.txt
@@ -1,15 +1,21 @@
 set(LLVM_LINK_COMPONENTS
   Core
   Object
+  Option
   ProfileData
   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
   )
 
 target_link_libraries(llvm-profdata PRIVATE LLVMDebuginfod LLVMHTTP)
diff --git a/llvm/tools/llvm-profdata/Opts.td b/llvm/tools/llvm-profdata/Opts.td
new file mode 100644
index 0000000000000..767482f07d40b
--- /dev/null
+++ b/llvm/tools/llvm-profdata/Opts.td
@@ -0,0 +1,100 @@
+include "llvm/Option/OptParser.td"
+
+class F<string name, string help, list<SubCommand> subcommands = []> : Flag<["-", "--"], name, subcommands>, HelpText<help>;
+class FF<string name, string help, list<SubCommand> subcommands = []> : Flag<["--"], name, subcommands>, HelpText<help>;
+
+// Positional Subcommands
+def show : SubCommand<"show", "Show summary or details of a profile.", "llvm-profdata show [options] <profdata-file>">;
+def merge : SubCommand<"merge", "Merge multiple profiles together.", "llvm-profdata merge [options] <filename...>">;
+def overlap : SubCommand<"overlap", "Compare and calculate the overlap of two profiles.", "llvm-profdata overlap [options] <base profile file> <test profile file>">;
+def order : SubCommand<"order", "Generate function order list based on temporal profiling traces.", "llvm-profdata order [options] <profdata-file>">;
+
+// Common Options
+def help : Flag<["-", "--"], "help", [show, merge, overlap, order]>, HelpText<"Display this help">;
+def : Flag<["-"], "h", [show, merge, overlap, order]>, Alias<help>;
+
+def version : Flag<["-", "--"], "version", [show, merge, overlap, order]>, HelpText<"Display the version">;
+def : Flag<["-"], "v", [show, merge, overlap, order]>, Alias<version>;
+
+def output : JoinedOrSeparate<["-", "--"], "output", [show, merge, overlap, order]>, HelpText<"Output file">, MetaVarName<"<file>">;
+def : JoinedOrSeparate<["-"], "o", [show, merge, overlap, order]>, Alias<output>;
+
+// Show subcommand options
+def counts : F<"counts", "Show counter values for shown functions", [show]>;
+def show_format : JoinedOrSeparate<["-", "--"], "show-format", [show]>, HelpText<"Emit output in the selected format if supported (text, json, yaml)">, MetaVarName<"<format>">;
+def text : F<"text", "Show instr profile data in text dump format", [show]>;
+def json : F<"json", "Show sample profile data in the JSON format (deprecated, please use --show-format=json)", [show]>;
+def ic_targets : F<"ic-targets", "Show indirect call site target values for shown functions", [show]>;
+def show_vtables : F<"show-vtables", "Show vtable names for shown functions", [show]>;
+def memop_sizes : F<"memop-sizes", "Show the profiled sizes of the memory intrinsic calls for shown functions", [show]>;
+def detailed_summary : F<"detailed-summary", "Show detailed profile summary", [show]>;
+def detailed_summary_cutoffs : JoinedOrSeparate<["-", "--"], "detailed-summary-cutoffs", [show]>, HelpText<"Cutoff percentages (times 10000) for generating detailed summary">, MetaVarName<"<cutoffs>">;
+def hot_func_list : F<"hot-func-list", "Show profile summary of a list of hot functions", [show]>;
+def all_functions : F<"all-functions", "Details for each and every function", [show]>;
+def showcs : F<"showcs", "Show context sensitive counts", [show]>;
+def topn : JoinedOrSeparate<["-", "--"], "topn", [show]>, HelpText<"Show the list of functions with the largest internal counts">, MetaVarName<"<n>">;
+def list_below_cutoff : F<"list-below-cutoff", "Only output names of functions whose max count values are below the cutoff value", [show]>;
+def show_prof_sym_list : F<"show-prof-sym-list", "Show profile symbol list if it exists in the profile.", [show]>;
+def show_sec_info_only : F<"show-sec-info-only", "Show the information of each section in the sample profile. The flag is only usable when the sample profile is in extbinary format", [show]>;
+def binary_ids : F<"binary-ids", "Show binary ids in the profile.", [show]>;
+def temporal_profile_traces : F<"temporal-profile-traces", "Show temporal profile traces in the profile.", [show]>;
+def covered : F<"covered", "Show only the functions that have been executed.", [show]>;
+def profile_version : F<"profile-version", "Show profile version.", [show]>;
+
+// Overlap subcommand options
+def cs : F<"cs", "For context sensitive PGO counts. Does not work with CSSPGO.", [overlap]>;
+def similarity_cutoff : JoinedOrSeparate<["-", "--"], "similarity-cutoff", [overlap]>, HelpText<"For sample profiles, list function names (with calling context for csspgo) for overlapped functions with similarities below the cutoff (percentage times 10000).">, MetaVarName<"<cutoff>">;
+
+
+// Order subcommand options
+def num_test_traces : JoinedOrSeparate<["-", "--"], "num-test-traces", [order]>, HelpText<"Keep aside the last <num-test-traces> traces in the profile when computing the function order and instead use them to evaluate that order">, MetaVarName<"<traces>">;
+
+// Multi-subcommand Shared Options
+def profile_kind : JoinedOrSeparate<["-", "--"], "profile-kind", [show, merge, overlap]>, HelpText<"Profile kind: instr, sample (show also supports memory)">, MetaVarName<"<kind>">;
+def value_cutoff : JoinedOrSeparate<["-", "--"], "value-cutoff", [show, overlap]>, HelpText<"Set the count value cutoff. Functions with the maximum count less than this value will not be printed out. (Default is 0)">, MetaVarName<"<cutoff>">;
+def max_debug_info_correlation_warnings : JoinedOrSeparate<["-", "--"], "max-debug-info-correlation-warnings", [show, merge]>, HelpText<"The maximum number of warnings to emit when correlating profile from debug info (0 = no limit)">, MetaVarName<"<warnings>">;
+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<"Extract profile metadata from debug info (for show) or correlate raw profile (for merge).">, MetaVarName<"<file>">;
+def debug_file_directory : JoinedOrSeparate<["-", "--"], "debug-file-directory", [show, merge]>, HelpText<"Directories to search for object files by build ID">, MetaVarName<"<dir>">;
+def correlate : JoinedOrSeparate<["-", "--"], "correlate", [show, merge]>, HelpText<"Use debug-info or binary correlation to correlate profiles with build id fetcher">, MetaVarName<"<kind>">;
+def function : JoinedOrSeparate<["-", "--"], "function", [show, merge, overlap]>, HelpText<"Only functions matching the filter are shown in the output.">, MetaVarName<"<filter>">;
+def fs_discriminator_pass : JoinedOrSeparate<["-", "--"], "fs-discriminator-pass", [show, merge]>, HelpText<"Zero out the discriminator bits for the FS discriminator pass beyond this value: base, pass1, pass2, pass3, passlast">, MetaVarName<"<pass>">;
+
+// Merge subcommand options
+def binary_file : JoinedOrSeparate<["-", "--"], "binary-file", [merge]>, HelpText<"Use the provided unstripped binary to correlate the raw profile.">, MetaVarName<"<file>">;
+def debuginfod : F<"debuginfod", "Enable debuginfod", [merge]>;
+def weighted_input : JoinedOrSeparate<["-", "--"], "weighted-input", [merge]>, HelpText<"Input file with weight specified as <weight>,<filename>">, MetaVarName<"<weight,filename>">;
+def format : JoinedOrSeparate<["-", "--"], "format", [merge]>, HelpText<"Format of output profile: binary, extbinary (default), text, gcc (only for -sample)">, MetaVarName<"<format>">;
+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>;
+def dump_input_file_list : F<"dump-input-file-list", "Dump the list of input files and their weights, then exit", [merge]>;
+def remapping_file : JoinedOrSeparate<["-", "--"], "remapping-file", [merge]>, HelpText<"Symbol remapping file">, MetaVarName<"<file>">;
+def : JoinedOrSeparate<["-"], "r", [merge]>, Alias<remapping_file>;
+def use_md5 : F<"use-md5", "Choose to use MD5 to represent string in name table (only meaningful for -extbinary)", [merge]>;
+def compress_all_sections : F<"compress-all-sections", "Compress all sections when writing the profile (only meaningful for -extbinary)", [merge]>;
+def sample_merge_cold_context : F<"sample-merge-cold-context", "Merge context sample profiles whose count is below cold threshold", [merge]>;
+def sample_trim_cold_context : F<"sample-trim-cold-context", "Trim context sample profiles whose count is below cold threshold", [merge]>;
+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">, MetaVarName<"<depth>">;
+def output_size_limit : JoinedOrSeparate<["-", "--"], "output-size-limit", [merge]>, HelpText<"Trim cold functions until profile size is below specified limit in bytes. This uses a heuristic and functions may be excessively trimmed">, MetaVarName<"<limit>">;
+def gen_partial_profile : F<"gen-partial-profile", "Generate a partial profile (only meaningful for -extbinary)", [merge]>;
+def split_layout : F<"split-layout", "Split the profile to two sections with one containing sample profiles with inlined functions and the other without (only meaningful for -extbinary)", [merge]>;
+def supplement_instr_with_sample : JoinedOrSeparate<["-", "--"], "supplement-instr-with-sample", [merge]>, HelpText<"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)">, MetaVarName<"<file>">;
+def zero_counter_threshold : JoinedOrSeparate<["-", "--"], "zero-counter-threshold", [merge]>, HelpText<"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.">, MetaVarName<"<threshold>">;
+def suppl_min_size_threshold : JoinedOrSeparate<["-", "--"], "suppl-min-size-threshold", [merge]>, HelpText<"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.">, MetaVarName<"<threshold>">;
+def instr_prof_cold_threshold : JoinedOrSeparate<["-", "--"], "instr-prof-cold-threshold", [merge]>, HelpText<"User specified cold threshold for instr profile which will override the cold threshold got from profile summary. ">, MetaVarName<"<threshold>">;
+def temporal_profile_trace_reservoir_size : JoinedOrSeparate<["-", "--"], "temporal-profile-trace-reservoir-size", [merge]>, HelpText<"The maximum number of stored temporal profile traces (default: 100)">, MetaVarName<"<size>">;
+def temporal_profile_max_trace_length : JoinedOrSeparate<["-", "--"], "temporal-profile-max-trace-length", [merge]>, HelpText<"The maximum length of a single temporal profile trace (default: 10000)">, MetaVarName<"<length>">;
+def no_function : JoinedOrSeparate<["-", "--"], "no-function", [merge]>, HelpText<"Exclude functions matching the filter from the output.">, MetaVarName<"<filter>">;
+def failure_mode : JoinedOrSeparate<["-", "--"], "failure-mode", [merge]>, HelpText<"Failure mode: warn (Do not fail and just print warnings.), any (Fail if any profile is invalid.), all (Fail only if all profiles are invalid.)">, MetaVarName<"<mode>">;
+def sparse : F<"sparse", "Generate a sparse profile (only meaningful for -instr)", [merge]>;
+def num_threads : JoinedOrSeparate<["-", "--"], "num-threads", [merge]>, HelpText<"Number of merge threads to use (default: autodetect)">, MetaVarName<"<threads>">;
+def : JoinedOrSeparate<["-"], "j", [merge]>, Alias<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 profile with a new layout: nest, flat">, MetaVarName<"<layout>">;
+def drop_profile_symbol_list : F<"drop-profile-symbol-list", "Drop the profile symbol list when merging AutoFDO profiles (only meaningful for -sample)", [merge]>;
+def keep_vtable_symbols : F<"keep-vtable-symbols", "If true, keep the vtable symbols in indexed profiles", [merge]>;
+def write_prev_version : F<"write-prev-version", "Write the previous version of indexed format, to enable some forward compatibility.", [merge]>;
+def memprof_version : JoinedOrSeparate<["-", "--"], "memprof-version", [merge]>, HelpText<"Specify the version of the memprof format to use: 2, 3, 4">, MetaVarName<"<version>">;
+def memprof_full_schema : F<"memprof-full-schema", "Use the full schema for serialization", [merge]>;
+def memprof_random_hotness : F<"memprof-random-hotness", "Generate random hotness values", [merge]>;
+def memprof_random_hotness_seed : JoinedOrSeparate<["-", "--"], "memprof-random-hotness-seed", [merge]>, HelpText<"Random hotness seed to use (0 to generate new seed)">, MetaVarName<"<seed>">;
diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp
index 6bf3179696e4a..e6479a64596c2 100644
--- a/llvm/tools/llvm-profdata/llvm-profdata.cpp
+++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp
@@ -29,6 +29,17 @@
 #include "llvm/ProfileData/SampleProfReader.h"
 #include "llvm/ProfileData/SampleProfWriter.h"
 #include "llvm/Support/BalancedPartitioning.h"
+//===----------------------------------------------------------------------===//
+// Option parsing using OptTable.
+//===----------------------------------------------------------------------===//
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/OptTable.h"
+#include "llvm/Option/Option.h"
+
+using namespace llvm;
+using ProfCorrelatorKind = InstrProfCorrelator::ProfCorrelatorKind;
+
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Discriminator.h"
 #include "llvm/Support/Errc.h"
@@ -49,32 +60,18 @@
 #include <cmath>
 #include <optional>
 
-using namespace llvm;
-using ProfCorrelatorKind = InstrProfCorrelator::ProfCorrelatorKind;
+// Forward declarations for exits and warnings parsing helpers
+static void exitWithError(Twine Message, StringRef Whence = "",
+                          StringRef Hint = "");
+static void exitWithError(Error E, StringRef Whence = "");
+static void exitWithErrorCode(std::error_code EC, StringRef Whence = "");
+static void warn(Twine Message, StringRef Whence = "", StringRef Hint = "");
+static void warn(Error E, StringRef Whence = "");
 
-// 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");
+static bool ShowSubcommand = false;
+static bool OrderSubcommand = false;
+static bool OverlapSubcommand = false;
+static bool MergeSubcommand = false;
 
 namespace {
 enum ProfileKinds { instr, sample, memory };
@@ -92,412 +89,549 @@ 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));
+// Plain C++ option variables mapping exactly to old cl ones.
+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 sampleprof::SampleProfileLayout ProfileLayout = sampleprof::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 = (unsigned long long)-1;
+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 sampleprof::FSDiscriminatorPass FSDiscriminatorPassOption =
+    sampleprof::PassLast;
+
+namespace opts {
+enum ID {
+  OPT_INVALID = 0, // This is not an option ID.
+#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
+#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_SUBCOMMANDS_CODE
+#include "Opts.inc"
+#undef OPTTABLE_SUBCOMMANDS_CODE
+
+#define OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE
+#include "Opts.inc"
+#undef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE
+
+using namespace llvm::opt;
+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()
+      : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable,
+                        /*IgnoreCase=*/false, OptionSubCommands,
+                        OptionSubCommandIDsTable) {
+    setGroupedShortOptions(true);
+  }
+};
+} // namespace opts
+
+static void parseArgs(int argc, char *const *argv) {
+  opts::ProfDataOptTable Tbl;
+  llvm::BumpPtrAllocator A;
+  llvm::StringSaver Saver{A};
+  llvm::opt::InputArgList Args =
+      Tbl.parseArgs(argc, argv, opts::OPT_UNKNOWN, Saver, [&](StringRef Msg) {
+        llvm::errs() << Msg << '\n';
+        std::exit(1);
+      });
+
+  StringRef Subcommand = Args.getSubCommand(
+      opts::OptionSubCommands,
+      [&](ArrayRef<StringRef> Subcmds) {
+        exitWithError("multiple subcommands specified: " + join(Subcmds, ", "));
+      },
+      [&](ArrayRef<StringRef> Positionals) {
+        // Positionals are handled separately.
+      });
+
+  if (Args.hasArg(opts::OPT_help)) {
+    Tbl.printHelp(
+        llvm::outs(),
+        Subcommand.empty()
+            ? "llvm-profdata <subcommand> [options]"
+            : ("llvm-profdata " + Subcommand + " [options]").str().c_str(),
+        "LLVM profile data tool",
+        /*ShowHidden=*/false, /*ShowAllAliases=*/false,
+        /*VisibilityMask=*/opt::Visibility(opt::DefaultVis), Subcommand);
+    std::exit(0);
+  }
+  if (Args.hasArg(opts::OPT_version)) {
+    cl::PrintVersionMessage();
+    std::exit(0);
+  }
+
+  if (Subcommand.empty()) {
+    exitWithError(
+        "No subcommand specified! Run llvm-profdata --help for usage.");
+  }
+
+  ShowSubcommand = (Subcommand == "show");
+  OrderSubcommand = (Subcommand == "order");
+  OverlapSubcommand = (Subcommand == "overlap");
+  MergeSubcommand = (Subcommand == "merge");
+
+  // Validate subcommand options!
+  for (const opt::Arg *A : Args) {
+    if (A->getOption().getKind() == opt::Option::InputClass)
+      continue;
+    if (A->getOption().matches(opts::OPT_help) ||
+        A->getOption().matches(opts::OPT_version))
+      continue;
+    if (!A->getOption().isRegisteredSC(Subcommand)) {
+      exitWithError("option '" + A->getSpelling() +
+                    "' is not supported by the '" + Subcommand +
+                    "' subcommand");
+    }
+  }
+
+  // Common Options
+  if (opt::Arg *A = Args.getLastArg(opts::OPT_output)) {
+    OutputFilename = A->getValue();
+  }
+
+  if (ShowSubcommand) {
+    ShowCounts = Args.hasArg(opts::OPT_counts);
+    TextFormat = Args.hasArg(opts::OPT_text);
+    JsonFormat = Args.hasArg(opts::OPT_json);
+    ShowIndirectCallTargets = Args.hasArg(opts::OPT_ic_targets);
+    ShowVTables = Args.hasArg(opts::OPT_show_vtables);
+    ShowMemOPSizes = Args.hasArg(opts::OPT_memop_sizes);
+    ShowDetailedSummary = Args.hasArg(opts::OPT_detailed_summary);
+    ShowHotFuncList = Args.hasArg(opts::OPT_hot_func_list);
+    ShowAllFunctions = Args.hasArg(opts::OPT_all_functions);
+    ShowCS = Args.hasArg(opts::OPT_showcs);
+    OnlyListBelow = Args.hasArg(opts::OPT_list_below_cutoff);
+    ShowProfileSymbolList = Args.hasArg(opts::OPT_show_prof_sym_list);
+    ShowSectionInfoOnly = Args.hasArg(opts::OPT_show_sec_info_only);
+    ShowBinaryIds = Args.hasArg(opts::OPT_binary_ids);
+    ShowTemporalProfTraces = Args.hasArg(opts::OPT_temporal_profile_traces);
+    ShowCovered = Args.hasArg(opts::OPT_covered);
+    ShowProfileVersion = Args.hasArg(opts::OPT_profile_version);
+
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_show_format)) {
+      StringRef SF = A->getValue();
+      SFormat = StringSwitch<ShowFormat>(SF)
+                    .Case("text", ShowFormat::Text)
+                    .Case("json", ShowFormat::Json)
+                    .Case("yaml", ShowFormat::Yaml)
+                    .Default(ShowFormat::Text);
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_profile_kind)) {
+      StringRef PK = A->getValue();
+      ShowProfileKind = StringSwitch<ProfileKinds>(PK)
+                            .Case("instr", instr)
+                            .Case("sample", sample)
+                            .Case("memory", memory)
+                            .Default(instr);
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_topn)) {
+      if (StringRef(A->getValue()).getAsInteger(10, TopNFunctions))
+        exitWithError("invalid value for --topn: " + StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_value_cutoff)) {
+      if (StringRef(A->getValue()).getAsInteger(10, ShowValueCutoff))
+        exitWithError("invalid value for --value-cutoff: " +
+                      StringRef(A->getValue()));
+    }
+    if (opt::Arg *A =
+            Args.getLastArg(opts::OPT_max_debug_info_correlation_warnings)) {
+      if (StringRef(A->getValue()).getAsInteger(10, MaxDbgCorrelationWarnings))
+        exitWithError(
+            "invalid value for --max-debug-info-correlation-warnings: " +
+            StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_profiled_binary)) {
+      ProfiledBinary = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_debug_info)) {
+      DebugInfoFilename = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_correlate)) {
+      StringRef CK = A->getValue();
+      BIDFetcherProfileCorrelate =
+          StringSwitch<ProfCorrelatorKind>(CK)
+              .Case("debug-info", InstrProfCorrelator::DEBUG_INFO)
+              .Case("binary", InstrProfCorrelator::BINARY)
+              .Default(InstrProfCorrelator::NONE);
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_function)) {
+      FuncNameFilter = A->getValue();
+    }
+    for (opt::Arg *A : Args.filtered(opts::OPT_debug_file_directory)) {
+      DebugFileDirectory.push_back(A->getValue());
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_detailed_summary_cutoffs)) {
+      for (StringRef CutoffStr : llvm::split(A->getValue(), ",")) {
+        uint32_t CutoffVal;
+        if (CutoffStr.trim().getAsInteger(10, CutoffVal))
+          exitWithError("invalid value for --detailed-summary-cutoffs: " +
+                        CutoffStr);
+        DetailedSummaryCutoffs.push_back(CutoffVal);
+      }
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_fs_discriminator_pass)) {
+      StringRef Pass = A->getValue();
+      FSDiscriminatorPassOption =
+          StringSwitch<sampleprof::FSDiscriminatorPass>(Pass)
+              .Cases({"Base", "base"}, sampleprof::Base)
+              .Cases({"Pass1", "pass1"}, sampleprof::Pass1)
+              .Cases({"Pass2", "pass2"}, sampleprof::Pass2)
+              .Cases({"Pass3", "pass3"}, sampleprof::Pass3)
+              .Cases({"PassLast", "passlast"}, sampleprof::PassLast)
+              .Default(sampleprof::PassLast);
+    }
+
+    std::vector<std::string> Inputs;
+    for (const opt::Arg *A : Args.filtered(opts::OPT_INPUT)) {
+      if (StringRef(A->getValue()) != Subcommand) {
+        Inputs.push_back(A->getValue());
+      }
+    }
+    if (!Inputs.empty()) {
+      Filename = Inputs[0];
+      if (Inputs.size() > 1) {
+        exitWithError("only one input file is allowed under show.");
+      }
+    }
+  }
+
+  if (MergeSubcommand) {
+    DebugInfod = Args.hasArg(opts::OPT_debuginfod);
+    DumpInputFileList = Args.hasArg(opts::OPT_dump_input_file_list);
+    UseMD5 = Args.hasArg(opts::OPT_use_md5);
+    CompressAllSections = Args.hasArg(opts::OPT_compress_all_sections);
+    SampleMergeColdContext = Args.hasArg(opts::OPT_sample_merge_cold_context);
+    SampleTrimColdContext = Args.hasArg(opts::OPT_sample_trim_cold_context);
+    GenPartialProfile = Args.hasArg(opts::OPT_gen_partial_profile);
+    SplitLayout = Args.hasArg(opts::OPT_split_layout);
+    OutputSparse = Args.hasArg(opts::OPT_sparse);
+    DropProfileSymbolList = Args.hasArg(opts::OPT_drop_profile_symbol_list);
+    KeepVTableSymbols = Args.hasArg(opts::OPT_keep_vtable_symbols);
+    DoWritePrevVersion = Args.hasArg(opts::OPT_write_prev_version);
+    MemProfFullSchema = Args.hasArg(opts::OPT_memprof_full_schema);
+    MemprofGenerateRandomHotness =
+        Args.hasArg(opts::OPT_memprof_random_hotness);
+
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_profile_kind)) {
+      StringRef PK = A->getValue();
+      ProfileKind = StringSwitch<ProfileKinds>(PK)
+                        .Case("instr", instr)
+                        .Case("sample", sample)
+                        .Default(instr);
+    }
+    if (opt::Arg *A =
+            Args.getLastArg(opts::OPT_max_debug_info_correlation_warnings)) {
+      if (StringRef(A->getValue()).getAsInteger(10, MaxDbgCorrelationWarnings))
+        exitWithError(
+            "invalid value for --max-debug-info-correlation-warnings: " +
+            StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_profiled_binary)) {
+      ProfiledBinary = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_debug_info)) {
+      DebugInfoFilename = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_binary_file)) {
+      BinaryFilename = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_correlate)) {
+      StringRef CK = A->getValue();
+      BIDFetcherProfileCorrelate =
+          StringSwitch<ProfCorrelatorKind>(CK)
+              .Case("debug-info", InstrProfCorrelator::DEBUG_INFO)
+              .Case("binary", InstrProfCorrelator::BINARY)
+              .Default(InstrProfCorrelator::NONE);
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_function)) {
+      FuncNameFilter = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_format)) {
+      StringRef F = A->getValue();
+      OutputFormat = StringSwitch<ProfileFormat>(F)
+                         .Case("binary", PF_Binary)
+                         .Case("extbinary", PF_Ext_Binary)
+                         .Case("text", PF_Text)
+                         .Case("gcc", PF_GCC)
+                         .Default(PF_None);
+      if (OutputFormat == PF_None)
+        exitWithError("unsupported format '" + F + "'");
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_input_files)) {
+      InputFilenamesFile = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_remapping_file)) {
+      RemappingFile = A->getValue();
+    }
+    if (opt::Arg *A =
+            Args.getLastArg(opts::OPT_sample_frame_depth_for_cold_context)) {
+      if (StringRef(A->getValue())
+              .getAsInteger(10, SampleColdContextFrameDepth))
+        exitWithError(
+            "invalid value for --sample-frame-depth-for-cold-context: " +
+            StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_output_size_limit)) {
+      if (StringRef(A->getValue()).getAsInteger(10, OutputSizeLimit))
+        exitWithError("invalid value for --output-size-limit: " +
+                      StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_supplement_instr_with_sample)) {
+      SupplInstrWithSample = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_zero_counter_threshold)) {
+      double Val;
+      if (StringRef(A->getValue()).getAsDouble(Val))
+        exitWithError("invalid value for --zero-counter-threshold: " +
+                      StringRef(A->getValue()));
+      ZeroCounterThreshold = Val;
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_suppl_min_size_threshold)) {
+      if (StringRef(A->getValue()).getAsInteger(10, SupplMinSizeThreshold))
+        exitWithError("invalid value for --suppl-min-size-threshold: " +
+                      StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_instr_prof_cold_threshold)) {
+      if (StringRef(A->getValue()).getAsInteger(10, InstrProfColdThreshold))
+        exitWithError("invalid value for --instr-prof-cold-threshold: " +
+                      StringRef(A->getValue()));
+    }
+    if (opt::Arg *A =
+            Args.getLastArg(opts::OPT_temporal_profile_trace_reservoir_size)) {
+      if (StringRef(A->getValue())
+              .getAsInteger(10, TemporalProfTraceReservoirSize))
+        exitWithError(
+            "invalid value for --temporal-profile-trace-reservoir-size: " +
+            StringRef(A->getValue()));
+    }
+    if (opt::Arg *A =
+            Args.getLastArg(opts::OPT_temporal_profile_max_trace_length)) {
+      if (StringRef(A->getValue()).getAsInteger(10, TemporalProfMaxTraceLength))
+        exitWithError(
+            "invalid value for --temporal-profile-max-trace-length: " +
+            StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_no_function)) {
+      FuncNameNegativeFilter = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_failure_mode)) {
+      StringRef FM = A->getValue();
+      FailMode = StringSwitch<FailureMode>(FM)
+                     .Case("warn", warnOnly)
+                     .Case("any", failIfAnyAreInvalid)
+                     .Case("all", failIfAllAreInvalid)
+                     .Default(failIfAnyAreInvalid);
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_num_threads)) {
+      if (StringRef(A->getValue()).getAsInteger(10, NumThreads))
+        exitWithError("invalid value for --num-threads: " +
+                      StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_prof_sym_list)) {
+      ProfileSymbolListFile = A->getValue();
+    }
+    if (opt::Arg *A =
+            Args.getLastArg(opts::OPT_convert_sample_profile_layout)) {
+      StringRef PL = A->getValue();
+      ProfileLayout = StringSwitch<sampleprof::SampleProfileLayout>(PL)
+                          .Case("nest", sampleprof::SPL_Nest)
+                          .Case("flat", sampleprof::SPL_Flat)
+                          .Default(sampleprof::SPL_None);
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_memprof_version)) {
+      StringRef MPV = A->getValue();
+      MemProfVersionRequested = StringSwitch<memprof::IndexedVersion>(MPV)
+                                    .Case("2", memprof::Version2)
+                                    .Case("3", memprof::Version3)
+                                    .Case("4", memprof::Version4)
+                                    .Default(memprof::Version3);
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_memprof_random_hotness_seed)) {
+      if (StringRef(A->getValue())
+              .getAsInteger(10, MemprofGenerateRandomHotnessSeed))
+        exitWithError("invalid value for --memprof-random-hotness-seed: " +
+                      StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_fs_discriminator_pass)) {
+      StringRef Pass = A->getValue();
+      FSDiscriminatorPassOption =
+          StringSwitch<sampleprof::FSDiscriminatorPass>(Pass)
+              .Cases({"Base", "base"}, sampleprof::Base)
+              .Cases({"Pass1", "pass1"}, sampleprof::Pass1)
+              .Cases({"Pass2", "pass2"}, sampleprof::Pass2)
+              .Cases({"Pass3", "pass3"}, sampleprof::Pass3)
+              .Cases({"PassLast", "passlast"}, sampleprof::PassLast)
+              .Default(sampleprof::PassLast);
+    }
+
+    for (opt::Arg *A : Args.filtered(opts::OPT_debug_file_directory)) {
+      DebugFileDirectory.push_back(A->getValue());
+    }
+    for (opt::Arg *A : Args.filtered(opts::OPT_weighted_input)) {
+      WeightedInputFilenames.push_back(A->getValue());
+    }
+
+    // Positional inputs for merge
+    for (opt::Arg *A : Args.filtered(opts::OPT_INPUT)) {
+      if (StringRef(A->getValue()) != Subcommand) {
+        InputFilenames.push_back(A->getValue());
+      }
+    }
+  }
+
+  if (OverlapSubcommand) {
+    IsCS = Args.hasArg(opts::OPT_cs);
+
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_profile_kind)) {
+      StringRef PK = A->getValue();
+      ProfileKind = StringSwitch<ProfileKinds>(PK)
+                        .Case("instr", instr)
+                        .Case("sample", sample)
+                        .Default(instr);
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_function)) {
+      FuncNameFilter = A->getValue();
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_similarity_cutoff)) {
+      if (StringRef(A->getValue()).getAsInteger(10, SimilarityCutoff))
+        exitWithError("invalid value for --similarity-cutoff: " +
+                      StringRef(A->getValue()));
+    }
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_value_cutoff)) {
+      if (StringRef(A->getValue()).getAsInteger(10, OverlapValueCutoff))
+        exitWithError("invalid value for --value-cutoff: " +
+                      StringRef(A->getValue()));
+    }
+
+    // Positional inputs for overlap
+    std::vector<std::string> Inputs;
+    for (const opt::Arg *A : Args.filtered(opts::OPT_INPUT)) {
+      if (StringRef(A->getValue()) != Subcommand) {
+        Inputs.push_back(A->getValue());
+      }
+    }
+    if (Inputs.size() < 2) {
+      exitWithError(
+          "both <base profile file> and <test profile file> are required.");
+    }
+    if (Inputs.size() > 2) {
+      exitWithError("too many positional arguments under overlap.");
+    }
+    BaseFilename = Inputs[0];
+    TestFilename = Inputs[1];
+  }
+
+  if (OrderSubcommand) {
+    if (opt::Arg *A = Args.getLastArg(opts::OPT_num_test_traces)) {
+      if (StringRef(A->getValue()).getAsInteger(10, NumTestTraces))
+        exitWithError("invalid value for --num-test-traces: " +
+                      StringRef(A->getValue()));
+    }
+
+    // Positional inputs for order
+    std::vector<std::string> Inputs;
+    for (const opt::Arg *A : Args.filtered(opts::OPT_INPUT)) {
+      if (StringRef(A->getValue()) != Subcommand) {
+        Inputs.push_back(A->getValue());
+      }
+    }
+    if (!Inputs.empty()) {
+      Filename = Inputs[0];
+      if (Inputs.size() > 1) {
+        exitWithError("only one input file is allowed under order.");
+      }
+    }
+  }
+}
 
 // We use this string to indicate that there are
 // multiple static functions map to the same name.
 const std::string DuplicateNameStr = "----";
 
-static void warn(Twine Message, StringRef Whence = "", StringRef Hint = "") {
+static void warn(Twine Message, StringRef Whence, StringRef Hint) {
   WithColor::warning();
   if (!Whence.empty())
     errs() << Whence << ": ";
@@ -506,7 +640,7 @@ static void warn(Twine Message, StringRef Whence = "", StringRef Hint = "") {
     WithColor::note() << Hint << "\n";
 }
 
-static void warn(Error E, StringRef Whence = "") {
+static void warn(Error E, StringRef Whence) {
   if (E.isA<InstrProfError>()) {
     handleAllErrors(std::move(E), [&](const InstrProfError &IPE) {
       warn(IPE.message(), Whence);
@@ -514,8 +648,7 @@ static void warn(Error E, StringRef Whence = "") {
   }
 }
 
-static void exitWithError(Twine Message, StringRef Whence = "",
-                          StringRef Hint = "") {
+static void exitWithError(Twine Message, StringRef Whence, StringRef Hint) {
   WithColor::error();
   if (!Whence.empty())
     errs() << Whence << ": ";
@@ -525,7 +658,7 @@ static void exitWithError(Twine Message, StringRef Whence = "",
   ::exit(1);
 }
 
-static void exitWithError(Error E, StringRef Whence = "") {
+static void exitWithError(Error E, StringRef Whence) {
   if (E.isA<InstrProfError>()) {
     handleAllErrors(std::move(E), [&](const InstrProfError &IPE) {
       instrprof_error instrError = IPE.get();
@@ -542,12 +675,12 @@ static void exitWithError(Error E, StringRef Whence = "") {
   exitWithError(toString(std::move(E)), Whence);
 }
 
-static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") {
+static void exitWithErrorCode(std::error_code EC, StringRef Whence) {
   exitWithError(EC.message(), Whence);
 }
 
 static void warnOrExitGivenError(FailureMode FailMode, std::error_code EC,
-                                 StringRef Whence = "") {
+                                 StringRef Whence) {
   if (FailMode == failIfAnyAreInvalid)
     exitWithErrorCode(EC, Whence);
   else
@@ -973,8 +1106,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 &&
@@ -1173,19 +1306,10 @@ 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)")));
+// FSDiscriminatorPassOption is defined globally.
 
 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
@@ -3375,9 +3499,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
@@ -3423,10 +3546,9 @@ static int order_main() {
   }
   ArrayRef Traces = Reader->getTemporalProfTraces();
   if (NumTestTraces && NumTestTraces >= Traces.size())
-    exitWithError(
-        "--" + NumTestTraces.ArgStr +
-        " must be smaller than the total number of traces: expected: < " +
-        Twine(Traces.size()) + ", actual: " + Twine(NumTestTraces));
+    exitWithError("--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);
   Traces = Traces.drop_back(NumTestTraces);
 
@@ -3475,14 +3597,8 @@ int main(int argc, const char *argv[]) {
   InitLLVM X(argc, argv);
   StringRef ProgName(sys::path::filename(argv[0]));
 
-  if (argc < 2) {
-    errs()
-        << ProgName
-        << ": No subcommand specified! Run llvm-profdata --help for usage.\n";
-    return 1;
-  }
-
-  cl::ParseCommandLineOptions(argc, argv, "LLVM profile data\n");
+  char *const *argv_ptr = const_cast<char *const *>(argv);
+  parseArgs(argc, argv_ptr);
 
   if (ShowSubcommand)
     return show_main(ProgName);
@@ -3496,7 +3612,5 @@ int main(int argc, const char *argv[]) {
   if (MergeSubcommand)
     return merge_main(ProgName);
 
-  errs() << ProgName
-         << ": Unknown command. Run llvm-profdata --help for usage.\n";
-  return 1;
+  llvm_unreachable("unrecognized subcommand");
 }

>From cfb975f4cb19e358dd13a8e691c357bea4b5f9eb Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 26 May 2026 16:34:17 -0700
Subject: [PATCH 2/3] Fix llvm tests

---
 llvm/tools/llvm-profdata/Opts.td           |  88 +++---
 llvm/tools/llvm-profdata/llvm-profdata.cpp | 304 ++++++++++++++++-----
 2 files changed, 289 insertions(+), 103 deletions(-)

diff --git a/llvm/tools/llvm-profdata/Opts.td b/llvm/tools/llvm-profdata/Opts.td
index 767482f07d40b..410d44147dc76 100644
--- a/llvm/tools/llvm-profdata/Opts.td
+++ b/llvm/tools/llvm-profdata/Opts.td
@@ -3,6 +3,15 @@ include "llvm/Option/OptParser.td"
 class F<string name, string help, list<SubCommand> subcommands = []> : Flag<["-", "--"], name, subcommands>, HelpText<help>;
 class FF<string name, string help, list<SubCommand> subcommands = []> : Flag<["--"], name, subcommands>, HelpText<help>;
 
+// Multiclass for value options that support both separate and joined format (with equals).
+// E.g. --option value, --option=value, -option value, -option=value
+multiclass Eq<string name, string help, list<SubCommand> subcommands = []> {
+  def NAME : Separate<["-", "--"], name, subcommands>;
+  def NAME #_eq : Joined<["-", "--"], name #"=", subcommands>,
+                  Alias<!cast<Separate>(NAME)>,
+                  HelpText<help>;
+}
+
 // Positional Subcommands
 def show : SubCommand<"show", "Show summary or details of a profile.", "llvm-profdata show [options] <profdata-file>">;
 def merge : SubCommand<"merge", "Merge multiple profiles together.", "llvm-profdata merge [options] <filename...>">;
@@ -16,23 +25,23 @@ def : Flag<["-"], "h", [show, merge, overlap, order]>, Alias<help>;
 def version : Flag<["-", "--"], "version", [show, merge, overlap, order]>, HelpText<"Display the version">;
 def : Flag<["-"], "v", [show, merge, overlap, order]>, Alias<version>;
 
-def output : JoinedOrSeparate<["-", "--"], "output", [show, merge, overlap, order]>, HelpText<"Output file">, MetaVarName<"<file>">;
+defm output : Eq<"output", "Output file", [show, merge, overlap, order]>;
 def : JoinedOrSeparate<["-"], "o", [show, merge, overlap, order]>, Alias<output>;
 
 // Show subcommand options
 def counts : F<"counts", "Show counter values for shown functions", [show]>;
-def show_format : JoinedOrSeparate<["-", "--"], "show-format", [show]>, HelpText<"Emit output in the selected format if supported (text, json, yaml)">, MetaVarName<"<format>">;
-def text : F<"text", "Show instr profile data in text dump format", [show]>;
+defm show_format : Eq<"show-format", "Emit output in the selected format if supported (text, json, yaml)", [show]>;
+def text : F<"text", "Show instr profile data in text dump format (for show) or output text format (for merge)", [show, merge]>;
 def json : F<"json", "Show sample profile data in the JSON format (deprecated, please use --show-format=json)", [show]>;
 def ic_targets : F<"ic-targets", "Show indirect call site target values for shown functions", [show]>;
 def show_vtables : F<"show-vtables", "Show vtable names for shown functions", [show]>;
 def memop_sizes : F<"memop-sizes", "Show the profiled sizes of the memory intrinsic calls for shown functions", [show]>;
 def detailed_summary : F<"detailed-summary", "Show detailed profile summary", [show]>;
-def detailed_summary_cutoffs : JoinedOrSeparate<["-", "--"], "detailed-summary-cutoffs", [show]>, HelpText<"Cutoff percentages (times 10000) for generating detailed summary">, MetaVarName<"<cutoffs>">;
+defm detailed_summary_cutoffs : Eq<"detailed-summary-cutoffs", "Cutoff percentages (times 10000) for generating detailed summary", [show]>;
 def hot_func_list : F<"hot-func-list", "Show profile summary of a list of hot functions", [show]>;
 def all_functions : F<"all-functions", "Details for each and every function", [show]>;
 def showcs : F<"showcs", "Show context sensitive counts", [show]>;
-def topn : JoinedOrSeparate<["-", "--"], "topn", [show]>, HelpText<"Show the list of functions with the largest internal counts">, MetaVarName<"<n>">;
+defm topn : Eq<"topn", "Show the list of functions with the largest internal counts", [show]>;
 def list_below_cutoff : F<"list-below-cutoff", "Only output names of functions whose max count values are below the cutoff value", [show]>;
 def show_prof_sym_list : F<"show-prof-sym-list", "Show profile symbol list if it exists in the profile.", [show]>;
 def show_sec_info_only : F<"show-sec-info-only", "Show the information of each section in the sample profile. The flag is only usable when the sample profile is in extbinary format", [show]>;
@@ -43,58 +52,63 @@ def profile_version : F<"profile-version", "Show profile version.", [show]>;
 
 // Overlap subcommand options
 def cs : F<"cs", "For context sensitive PGO counts. Does not work with CSSPGO.", [overlap]>;
-def similarity_cutoff : JoinedOrSeparate<["-", "--"], "similarity-cutoff", [overlap]>, HelpText<"For sample profiles, list function names (with calling context for csspgo) for overlapped functions with similarities below the cutoff (percentage times 10000).">, MetaVarName<"<cutoff>">;
-
+defm similarity_cutoff : Eq<"similarity-cutoff", "For sample profiles, list function names (with calling context for csspgo) for overlapped functions with similarities below the cutoff (percentage times 10000).", [overlap]>;
 
 // Order subcommand options
-def num_test_traces : JoinedOrSeparate<["-", "--"], "num-test-traces", [order]>, HelpText<"Keep aside the last <num-test-traces> traces in the profile when computing the function order and instead use them to evaluate that order">, MetaVarName<"<traces>">;
+defm num_test_traces : Eq<"num-test-traces", "Keep aside the last <num-test-traces> traces in the profile when computing the function order and instead use them to evaluate that order", [order]>;
+
+// Profile Kind standalone options (legacy cl compatibility)
+def sample : Flag<["-", "--"], "sample", [show, merge, overlap]>, HelpText<"Sample profile">;
+def instr : Flag<["-", "--"], "instr", [show, merge, overlap]>, HelpText<"Instrumentation profile">;
+def memory : Flag<["-", "--"], "memory", [show]>, HelpText<"MemProf memory access profile">;
 
 // Multi-subcommand Shared Options
-def profile_kind : JoinedOrSeparate<["-", "--"], "profile-kind", [show, merge, overlap]>, HelpText<"Profile kind: instr, sample (show also supports memory)">, MetaVarName<"<kind>">;
-def value_cutoff : JoinedOrSeparate<["-", "--"], "value-cutoff", [show, overlap]>, HelpText<"Set the count value cutoff. Functions with the maximum count less than this value will not be printed out. (Default is 0)">, MetaVarName<"<cutoff>">;
-def max_debug_info_correlation_warnings : JoinedOrSeparate<["-", "--"], "max-debug-info-correlation-warnings", [show, merge]>, HelpText<"The maximum number of warnings to emit when correlating profile from debug info (0 = no limit)">, MetaVarName<"<warnings>">;
-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<"Extract profile metadata from debug info (for show) or correlate raw profile (for merge).">, MetaVarName<"<file>">;
-def debug_file_directory : JoinedOrSeparate<["-", "--"], "debug-file-directory", [show, merge]>, HelpText<"Directories to search for object files by build ID">, MetaVarName<"<dir>">;
-def correlate : JoinedOrSeparate<["-", "--"], "correlate", [show, merge]>, HelpText<"Use debug-info or binary correlation to correlate profiles with build id fetcher">, MetaVarName<"<kind>">;
-def function : JoinedOrSeparate<["-", "--"], "function", [show, merge, overlap]>, HelpText<"Only functions matching the filter are shown in the output.">, MetaVarName<"<filter>">;
-def fs_discriminator_pass : JoinedOrSeparate<["-", "--"], "fs-discriminator-pass", [show, merge]>, HelpText<"Zero out the discriminator bits for the FS discriminator pass beyond this value: base, pass1, pass2, pass3, passlast">, MetaVarName<"<pass>">;
+defm value_cutoff : Eq<"value-cutoff", "Set the count value cutoff. Functions with the maximum count less than this value will not be printed out. (Default is 0)", [show, overlap]>;
+defm max_debug_info_correlation_warnings : Eq<"max-debug-info-correlation-warnings", "The maximum number of warnings to emit when correlating profile from debug info (0 = no limit)", [show, merge]>;
+defm profiled_binary : Eq<"profiled-binary", "Path to binary from which the profile was collected.", [show, merge]>;
+defm debug_info : Eq<"debug-info", "Extract profile metadata from debug info (for show) or correlate raw profile (for merge).", [show, merge]>;
+defm debug_file_directory : Eq<"debug-file-directory", "Directories to search for object files by build ID", [show, merge]>;
+defm correlate : Eq<"correlate", "Use debug-info or binary correlation to correlate profiles with build id fetcher", [show, merge]>;
+defm function : Eq<"function", "Only functions matching the filter are shown in the output.", [show, merge, overlap]>;
+defm fs_discriminator_pass : Eq<"fs-discriminator-pass", "Zero out the discriminator bits for the FS discriminator pass beyond this value: base, pass1, pass2, pass3, passlast", [show, merge]>;
 
 // Merge subcommand options
-def binary_file : JoinedOrSeparate<["-", "--"], "binary-file", [merge]>, HelpText<"Use the provided unstripped binary to correlate the raw profile.">, MetaVarName<"<file>">;
+defm binary_file : Eq<"binary-file", "Use the provided unstripped binary to correlate the raw profile.", [merge]>;
 def debuginfod : F<"debuginfod", "Enable debuginfod", [merge]>;
-def weighted_input : JoinedOrSeparate<["-", "--"], "weighted-input", [merge]>, HelpText<"Input file with weight specified as <weight>,<filename>">, MetaVarName<"<weight,filename>">;
-def format : JoinedOrSeparate<["-", "--"], "format", [merge]>, HelpText<"Format of output profile: binary, extbinary (default), text, gcc (only for -sample)">, MetaVarName<"<format>">;
-def input_files : JoinedOrSeparate<["-", "--"], "input-files", [merge]>, HelpText<"Path to file containing newline-separated [<weight>,]<filename> entries">, MetaVarName<"<file>">;
+defm weighted_input : Eq<"weighted-input", "Input file with weight specified as <weight>,<filename>", [merge]>;
+def binary : Flag<["-", "--"], "binary", [merge]>, HelpText<"Output binary format">;
+def extbinary : Flag<["-", "--"], "extbinary", [merge]>, HelpText<"Output extbinary format">;
+def gcc : Flag<["-", "--"], "gcc", [merge]>, HelpText<"Output gcc format (only meaningful for -sample)">;
+defm input_files : Eq<"input-files", "Path to file containing newline-separated [<weight>,]<filename> entries", [merge]>;
 def : JoinedOrSeparate<["-"], "f", [merge]>, Alias<input_files>;
 def dump_input_file_list : F<"dump-input-file-list", "Dump the list of input files and their weights, then exit", [merge]>;
-def remapping_file : JoinedOrSeparate<["-", "--"], "remapping-file", [merge]>, HelpText<"Symbol remapping file">, MetaVarName<"<file>">;
+defm remapping_file : Eq<"remapping-file", "Symbol remapping file", [merge]>;
 def : JoinedOrSeparate<["-"], "r", [merge]>, Alias<remapping_file>;
 def use_md5 : F<"use-md5", "Choose to use MD5 to represent string in name table (only meaningful for -extbinary)", [merge]>;
 def compress_all_sections : F<"compress-all-sections", "Compress all sections when writing the profile (only meaningful for -extbinary)", [merge]>;
 def sample_merge_cold_context : F<"sample-merge-cold-context", "Merge context sample profiles whose count is below cold threshold", [merge]>;
 def sample_trim_cold_context : F<"sample-trim-cold-context", "Trim context sample profiles whose count is below cold threshold", [merge]>;
-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">, MetaVarName<"<depth>">;
-def output_size_limit : JoinedOrSeparate<["-", "--"], "output-size-limit", [merge]>, HelpText<"Trim cold functions until profile size is below specified limit in bytes. This uses a heuristic and functions may be excessively trimmed">, MetaVarName<"<limit>">;
+defm sample_frame_depth_for_cold_context : Eq<"sample-frame-depth-for-cold-context", "Keep the last K frames while merging cold profile. 1 means the context-less base profile", [merge]>;
+defm output_size_limit : Eq<"output-size-limit", "Trim cold functions until profile size is below specified limit in bytes. This uses a heuristic and functions may be excessively trimmed", [merge]>;
 def gen_partial_profile : F<"gen-partial-profile", "Generate a partial profile (only meaningful for -extbinary)", [merge]>;
 def split_layout : F<"split-layout", "Split the profile to two sections with one containing sample profiles with inlined functions and the other without (only meaningful for -extbinary)", [merge]>;
-def supplement_instr_with_sample : JoinedOrSeparate<["-", "--"], "supplement-instr-with-sample", [merge]>, HelpText<"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)">, MetaVarName<"<file>">;
-def zero_counter_threshold : JoinedOrSeparate<["-", "--"], "zero-counter-threshold", [merge]>, HelpText<"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.">, MetaVarName<"<threshold>">;
-def suppl_min_size_threshold : JoinedOrSeparate<["-", "--"], "suppl-min-size-threshold", [merge]>, HelpText<"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.">, MetaVarName<"<threshold>">;
-def instr_prof_cold_threshold : JoinedOrSeparate<["-", "--"], "instr-prof-cold-threshold", [merge]>, HelpText<"User specified cold threshold for instr profile which will override the cold threshold got from profile summary. ">, MetaVarName<"<threshold>">;
-def temporal_profile_trace_reservoir_size : JoinedOrSeparate<["-", "--"], "temporal-profile-trace-reservoir-size", [merge]>, HelpText<"The maximum number of stored temporal profile traces (default: 100)">, MetaVarName<"<size>">;
-def temporal_profile_max_trace_length : JoinedOrSeparate<["-", "--"], "temporal-profile-max-trace-length", [merge]>, HelpText<"The maximum length of a single temporal profile trace (default: 10000)">, MetaVarName<"<length>">;
-def no_function : JoinedOrSeparate<["-", "--"], "no-function", [merge]>, HelpText<"Exclude functions matching the filter from the output.">, MetaVarName<"<filter>">;
-def failure_mode : JoinedOrSeparate<["-", "--"], "failure-mode", [merge]>, HelpText<"Failure mode: warn (Do not fail and just print warnings.), any (Fail if any profile is invalid.), all (Fail only if all profiles are invalid.)">, MetaVarName<"<mode>">;
+defm supplement_instr_with_sample : Eq<"supplement-instr-with-sample", "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)", [merge]>;
+defm zero_counter_threshold : Eq<"zero-counter-threshold", "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.", [merge]>;
+defm suppl_min_size_threshold : Eq<"suppl-min-size-threshold", "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.", [merge]>;
+defm instr_prof_cold_threshold : Eq<"instr-prof-cold-threshold", "User specified cold threshold for instr profile which will override the cold threshold got from profile summary. ", [merge]>;
+defm temporal_profile_trace_reservoir_size : Eq<"temporal-profile-trace-reservoir-size", "The maximum number of stored temporal profile traces (default: 100)", [merge]>;
+defm temporal_profile_max_trace_length : Eq<"temporal-profile-max-trace-length", "The maximum length of a single temporal profile trace (default: 10000)", [merge]>;
+defm no_function : Eq<"no-function", "Exclude functions matching the filter from the output.", [merge]>;
+defm failure_mode : Eq<"failure-mode", "Failure mode: warn (Do not fail and just print warnings.), any (Fail if any profile is invalid.), all (Fail only if all profiles are invalid.)", [merge]>;
 def sparse : F<"sparse", "Generate a sparse profile (only meaningful for -instr)", [merge]>;
-def num_threads : JoinedOrSeparate<["-", "--"], "num-threads", [merge]>, HelpText<"Number of merge threads to use (default: autodetect)">, MetaVarName<"<threads>">;
+defm num_threads : Eq<"num-threads", "Number of merge threads to use (default: autodetect)", [merge]>;
 def : JoinedOrSeparate<["-"], "j", [merge]>, Alias<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 profile with a new layout: nest, flat">, MetaVarName<"<layout>">;
+defm prof_sym_list : Eq<"prof-sym-list", "Path to file containing the list of function symbols used to populate profile symbol list", [merge]>;
+defm convert_sample_profile_layout : Eq<"convert-sample-profile-layout", "Convert the generated profile to a profile with a new layout: nest, flat", [merge]>;
 def drop_profile_symbol_list : F<"drop-profile-symbol-list", "Drop the profile symbol list when merging AutoFDO profiles (only meaningful for -sample)", [merge]>;
 def keep_vtable_symbols : F<"keep-vtable-symbols", "If true, keep the vtable symbols in indexed profiles", [merge]>;
 def write_prev_version : F<"write-prev-version", "Write the previous version of indexed format, to enable some forward compatibility.", [merge]>;
-def memprof_version : JoinedOrSeparate<["-", "--"], "memprof-version", [merge]>, HelpText<"Specify the version of the memprof format to use: 2, 3, 4">, MetaVarName<"<version>">;
+defm memprof_version : Eq<"memprof-version", "Specify the version of the memprof format to use: 2, 3, 4", [merge]>;
 def memprof_full_schema : F<"memprof-full-schema", "Use the full schema for serialization", [merge]>;
 def memprof_random_hotness : F<"memprof-random-hotness", "Generate random hotness values", [merge]>;
-def memprof_random_hotness_seed : JoinedOrSeparate<["-", "--"], "memprof-random-hotness-seed", [merge]>, HelpText<"Random hotness seed to use (0 to generate new seed)">, MetaVarName<"<seed>">;
+defm memprof_random_hotness_seed : Eq<"memprof-random-hotness-seed", "Random hotness seed to use (0 to generate new seed)", [merge]>;
diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp
index e6479a64596c2..bc81043287e69 100644
--- a/llvm/tools/llvm-profdata/llvm-profdata.cpp
+++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp
@@ -203,7 +203,7 @@ class ProfDataOptTable : public opt::GenericOptTable {
       : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable,
                         /*IgnoreCase=*/false, OptionSubCommands,
                         OptionSubCommandIDsTable) {
-    setGroupedShortOptions(true);
+    setGroupedShortOptions(false);
   }
 };
 } // namespace opts
@@ -213,11 +213,157 @@ static void parseArgs(int argc, char *const *argv) {
   llvm::BumpPtrAllocator A;
   llvm::StringSaver Saver{A};
   llvm::opt::InputArgList Args =
-      Tbl.parseArgs(argc, argv, opts::OPT_UNKNOWN, Saver, [&](StringRef Msg) {
+      Tbl.parseArgs(argc, argv, opts::OPT_INVALID, Saver, [&](StringRef Msg) {
         llvm::errs() << Msg << '\n';
         std::exit(1);
       });
 
+  // Delegate unrecognized support library options to the cl option registry!
+  auto &CLOpts = cl::getRegisteredOptions();
+  for (const opt::Arg *Arg : Args.filtered(opts::OPT_UNKNOWN)) {
+    StringRef Spelling = Arg->getSpelling();
+
+    // Ignore positional inputs (which map to OPT_UNKNOWN but do not start with
+    // a dash!)
+    if (Spelling.empty() || Spelling[0] != '-')
+      continue;
+
+    // Strip leading dashes
+    StringRef ArgStr = Spelling.ltrim('-');
+
+    // Split by equals sign
+    auto [OptName, OptVal] = ArgStr.split('=');
+
+    // If the option has no value, treat as true boolean flag
+    if (OptVal.empty() && !ArgStr.contains('=')) {
+      OptVal = "true";
+    }
+
+    static const SmallSet<StringRef, 32> ToolBoolOpts = {
+        "sparse",
+        "compress-all-sections",
+        "sample-merge-cold-context",
+        "sample-trim-cold-context",
+        "gen-partial-profile",
+        "split-layout",
+        "drop-profile-symbol-list",
+        "keep-vtable-symbols",
+        "write-prev-version",
+        "memprof-full-schema",
+        "memprof-random-hotness",
+        "debuginfod",
+        "dump-input-file-list",
+        "use-md5",
+        "counts",
+        "text",
+        "json",
+        "ic-targets",
+        "show-vtables",
+        "memop-sizes",
+        "detailed-summary",
+        "hot-func-list",
+        "all-functions",
+        "showcs",
+        "list-below-cutoff",
+        "show-prof-sym-list",
+        "show-sec-info-only",
+        "binary-ids",
+        "temporal-profile-traces",
+        "covered",
+        "profile-version",
+        "cs"};
+
+    if (ToolBoolOpts.count(OptName)) {
+      bool BoolVal = StringSwitch<bool>(OptVal)
+                         .Cases({"true", "1"}, true)
+                         .Cases({"false", "0"}, false)
+                         .Default(false);
+      if (OptName == "sparse")
+        OutputSparse = BoolVal;
+      else if (OptName == "compress-all-sections")
+        CompressAllSections = BoolVal;
+      else if (OptName == "sample-merge-cold-context")
+        SampleMergeColdContext = BoolVal;
+      else if (OptName == "sample-trim-cold-context")
+        SampleTrimColdContext = BoolVal;
+      else if (OptName == "gen-partial-profile")
+        GenPartialProfile = BoolVal;
+      else if (OptName == "split-layout")
+        SplitLayout = BoolVal;
+      else if (OptName == "drop-profile-symbol-list")
+        DropProfileSymbolList = BoolVal;
+      else if (OptName == "keep-vtable-symbols")
+        KeepVTableSymbols = BoolVal;
+      else if (OptName == "write-prev-version")
+        DoWritePrevVersion = BoolVal;
+      else if (OptName == "memprof-full-schema")
+        MemProfFullSchema = BoolVal;
+      else if (OptName == "memprof-random-hotness")
+        MemprofGenerateRandomHotness = BoolVal;
+      else if (OptName == "debuginfod")
+        DebugInfod = BoolVal;
+      else if (OptName == "dump-input-file-list")
+        DumpInputFileList = BoolVal;
+      else if (OptName == "use-md5")
+        UseMD5 = BoolVal;
+      else if (OptName == "counts")
+        ShowCounts = BoolVal;
+      else if (OptName == "text")
+        TextFormat = BoolVal;
+      else if (OptName == "json")
+        JsonFormat = BoolVal;
+      else if (OptName == "ic-targets")
+        ShowIndirectCallTargets = BoolVal;
+      else if (OptName == "show-vtables")
+        ShowVTables = BoolVal;
+      else if (OptName == "memop-sizes")
+        ShowMemOPSizes = BoolVal;
+      else if (OptName == "detailed-summary")
+        ShowDetailedSummary = BoolVal;
+      else if (OptName == "hot-func-list")
+        ShowHotFuncList = BoolVal;
+      else if (OptName == "all-functions")
+        ShowAllFunctions = BoolVal;
+      else if (OptName == "showcs")
+        ShowCS = BoolVal;
+      else if (OptName == "list-below-cutoff")
+        OnlyListBelow = BoolVal;
+      else if (OptName == "show-prof-sym-list")
+        ShowProfileSymbolList = BoolVal;
+      else if (OptName == "show-sec-info-only")
+        ShowSectionInfoOnly = BoolVal;
+      else if (OptName == "binary-ids")
+        ShowBinaryIds = BoolVal;
+      else if (OptName == "temporal-profile-traces")
+        ShowTemporalProfTraces = BoolVal;
+      else if (OptName == "covered")
+        ShowCovered = BoolVal;
+      else if (OptName == "profile-version")
+        ShowProfileVersion = BoolVal;
+      else if (OptName == "cs")
+        IsCS = BoolVal;
+      continue;
+    }
+
+    auto It = CLOpts.find(OptName);
+    if (It == CLOpts.end()) {
+      // Unrecognized option: report error exactly as expected!
+      std::string Nearest;
+      std::string NearestSpelling = Arg->getAsString(Args);
+      if (Tbl.findNearest(NearestSpelling, Nearest) > 1) {
+        exitWithError("unknown argument '" + NearestSpelling + "'");
+      } else {
+        exitWithError("unknown argument '" + NearestSpelling +
+                      "', did you mean '" + Nearest + "'?");
+      }
+    }
+
+    // Natively delegate matching value to support library cl::Option registry!
+    if (It->second->addOccurrence(Arg->getIndex(), OptName, OptVal)) {
+      exitWithError("invalid value for option '" + Spelling.str() + "'");
+    }
+  }
+
   StringRef Subcommand = Args.getSubCommand(
       opts::OptionSubCommands,
       [&](ArrayRef<StringRef> Subcmds) {
@@ -257,6 +403,8 @@ static void parseArgs(int argc, char *const *argv) {
   for (const opt::Arg *A : Args) {
     if (A->getOption().getKind() == opt::Option::InputClass)
       continue;
+    if (A->getOption().getID() == opts::OPT_UNKNOWN)
+      continue;
     if (A->getOption().matches(opts::OPT_help) ||
         A->getOption().matches(opts::OPT_version))
       continue;
@@ -273,23 +421,40 @@ static void parseArgs(int argc, char *const *argv) {
   }
 
   if (ShowSubcommand) {
-    ShowCounts = Args.hasArg(opts::OPT_counts);
-    TextFormat = Args.hasArg(opts::OPT_text);
-    JsonFormat = Args.hasArg(opts::OPT_json);
-    ShowIndirectCallTargets = Args.hasArg(opts::OPT_ic_targets);
-    ShowVTables = Args.hasArg(opts::OPT_show_vtables);
-    ShowMemOPSizes = Args.hasArg(opts::OPT_memop_sizes);
-    ShowDetailedSummary = Args.hasArg(opts::OPT_detailed_summary);
-    ShowHotFuncList = Args.hasArg(opts::OPT_hot_func_list);
-    ShowAllFunctions = Args.hasArg(opts::OPT_all_functions);
-    ShowCS = Args.hasArg(opts::OPT_showcs);
-    OnlyListBelow = Args.hasArg(opts::OPT_list_below_cutoff);
-    ShowProfileSymbolList = Args.hasArg(opts::OPT_show_prof_sym_list);
-    ShowSectionInfoOnly = Args.hasArg(opts::OPT_show_sec_info_only);
-    ShowBinaryIds = Args.hasArg(opts::OPT_binary_ids);
-    ShowTemporalProfTraces = Args.hasArg(opts::OPT_temporal_profile_traces);
-    ShowCovered = Args.hasArg(opts::OPT_covered);
-    ShowProfileVersion = Args.hasArg(opts::OPT_profile_version);
+    if (Args.hasArg(opts::OPT_counts))
+      ShowCounts = true;
+    if (Args.hasArg(opts::OPT_text))
+      TextFormat = true;
+    if (Args.hasArg(opts::OPT_json))
+      JsonFormat = true;
+    if (Args.hasArg(opts::OPT_ic_targets))
+      ShowIndirectCallTargets = true;
+    if (Args.hasArg(opts::OPT_show_vtables))
+      ShowVTables = true;
+    if (Args.hasArg(opts::OPT_memop_sizes))
+      ShowMemOPSizes = true;
+    if (Args.hasArg(opts::OPT_detailed_summary))
+      ShowDetailedSummary = true;
+    if (Args.hasArg(opts::OPT_hot_func_list))
+      ShowHotFuncList = true;
+    if (Args.hasArg(opts::OPT_all_functions))
+      ShowAllFunctions = true;
+    if (Args.hasArg(opts::OPT_showcs))
+      ShowCS = true;
+    if (Args.hasArg(opts::OPT_list_below_cutoff))
+      OnlyListBelow = true;
+    if (Args.hasArg(opts::OPT_show_prof_sym_list))
+      ShowProfileSymbolList = true;
+    if (Args.hasArg(opts::OPT_show_sec_info_only))
+      ShowSectionInfoOnly = true;
+    if (Args.hasArg(opts::OPT_binary_ids))
+      ShowBinaryIds = true;
+    if (Args.hasArg(opts::OPT_temporal_profile_traces))
+      ShowTemporalProfTraces = true;
+    if (Args.hasArg(opts::OPT_covered))
+      ShowCovered = true;
+    if (Args.hasArg(opts::OPT_profile_version))
+      ShowProfileVersion = true;
 
     if (opt::Arg *A = Args.getLastArg(opts::OPT_show_format)) {
       StringRef SF = A->getValue();
@@ -299,13 +464,12 @@ static void parseArgs(int argc, char *const *argv) {
                     .Case("yaml", ShowFormat::Yaml)
                     .Default(ShowFormat::Text);
     }
-    if (opt::Arg *A = Args.getLastArg(opts::OPT_profile_kind)) {
-      StringRef PK = A->getValue();
-      ShowProfileKind = StringSwitch<ProfileKinds>(PK)
-                            .Case("instr", instr)
-                            .Case("sample", sample)
-                            .Case("memory", memory)
-                            .Default(instr);
+    if (Args.hasArg(opts::OPT_sample)) {
+      ShowProfileKind = sample;
+    } else if (Args.hasArg(opts::OPT_memory)) {
+      ShowProfileKind = memory;
+    } else if (Args.hasArg(opts::OPT_instr)) {
+      ShowProfileKind = instr;
     }
     if (opt::Arg *A = Args.getLastArg(opts::OPT_topn)) {
       if (StringRef(A->getValue()).getAsInteger(10, TopNFunctions))
@@ -379,28 +543,39 @@ static void parseArgs(int argc, char *const *argv) {
   }
 
   if (MergeSubcommand) {
-    DebugInfod = Args.hasArg(opts::OPT_debuginfod);
-    DumpInputFileList = Args.hasArg(opts::OPT_dump_input_file_list);
-    UseMD5 = Args.hasArg(opts::OPT_use_md5);
-    CompressAllSections = Args.hasArg(opts::OPT_compress_all_sections);
-    SampleMergeColdContext = Args.hasArg(opts::OPT_sample_merge_cold_context);
-    SampleTrimColdContext = Args.hasArg(opts::OPT_sample_trim_cold_context);
-    GenPartialProfile = Args.hasArg(opts::OPT_gen_partial_profile);
-    SplitLayout = Args.hasArg(opts::OPT_split_layout);
-    OutputSparse = Args.hasArg(opts::OPT_sparse);
-    DropProfileSymbolList = Args.hasArg(opts::OPT_drop_profile_symbol_list);
-    KeepVTableSymbols = Args.hasArg(opts::OPT_keep_vtable_symbols);
-    DoWritePrevVersion = Args.hasArg(opts::OPT_write_prev_version);
-    MemProfFullSchema = Args.hasArg(opts::OPT_memprof_full_schema);
-    MemprofGenerateRandomHotness =
-        Args.hasArg(opts::OPT_memprof_random_hotness);
-
-    if (opt::Arg *A = Args.getLastArg(opts::OPT_profile_kind)) {
-      StringRef PK = A->getValue();
-      ProfileKind = StringSwitch<ProfileKinds>(PK)
-                        .Case("instr", instr)
-                        .Case("sample", sample)
-                        .Default(instr);
+    if (Args.hasArg(opts::OPT_debuginfod))
+      DebugInfod = true;
+    if (Args.hasArg(opts::OPT_dump_input_file_list))
+      DumpInputFileList = true;
+    if (Args.hasArg(opts::OPT_use_md5))
+      UseMD5 = true;
+    if (Args.hasArg(opts::OPT_compress_all_sections))
+      CompressAllSections = true;
+    if (Args.hasArg(opts::OPT_sample_merge_cold_context))
+      SampleMergeColdContext = true;
+    if (Args.hasArg(opts::OPT_sample_trim_cold_context))
+      SampleTrimColdContext = true;
+    if (Args.hasArg(opts::OPT_gen_partial_profile))
+      GenPartialProfile = true;
+    if (Args.hasArg(opts::OPT_split_layout))
+      SplitLayout = true;
+    if (Args.hasArg(opts::OPT_sparse))
+      OutputSparse = true;
+    if (Args.hasArg(opts::OPT_drop_profile_symbol_list))
+      DropProfileSymbolList = true;
+    if (Args.hasArg(opts::OPT_keep_vtable_symbols))
+      KeepVTableSymbols = true;
+    if (Args.hasArg(opts::OPT_write_prev_version))
+      DoWritePrevVersion = true;
+    if (Args.hasArg(opts::OPT_memprof_full_schema))
+      MemProfFullSchema = true;
+    if (Args.hasArg(opts::OPT_memprof_random_hotness))
+      MemprofGenerateRandomHotness = true;
+
+    if (Args.hasArg(opts::OPT_sample)) {
+      ProfileKind = sample;
+    } else if (Args.hasArg(opts::OPT_instr)) {
+      ProfileKind = instr;
     }
     if (opt::Arg *A =
             Args.getLastArg(opts::OPT_max_debug_info_correlation_warnings)) {
@@ -429,16 +604,14 @@ static void parseArgs(int argc, char *const *argv) {
     if (opt::Arg *A = Args.getLastArg(opts::OPT_function)) {
       FuncNameFilter = A->getValue();
     }
-    if (opt::Arg *A = Args.getLastArg(opts::OPT_format)) {
-      StringRef F = A->getValue();
-      OutputFormat = StringSwitch<ProfileFormat>(F)
-                         .Case("binary", PF_Binary)
-                         .Case("extbinary", PF_Ext_Binary)
-                         .Case("text", PF_Text)
-                         .Case("gcc", PF_GCC)
-                         .Default(PF_None);
-      if (OutputFormat == PF_None)
-        exitWithError("unsupported format '" + F + "'");
+    if (Args.hasArg(opts::OPT_text)) {
+      OutputFormat = PF_Text;
+    } else if (Args.hasArg(opts::OPT_binary)) {
+      OutputFormat = PF_Binary;
+    } else if (Args.hasArg(opts::OPT_extbinary)) {
+      OutputFormat = PF_Ext_Binary;
+    } else if (Args.hasArg(opts::OPT_gcc)) {
+      OutputFormat = PF_GCC;
     }
     if (opt::Arg *A = Args.getLastArg(opts::OPT_input_files)) {
       InputFilenamesFile = A->getValue();
@@ -563,14 +736,13 @@ static void parseArgs(int argc, char *const *argv) {
   }
 
   if (OverlapSubcommand) {
-    IsCS = Args.hasArg(opts::OPT_cs);
-
-    if (opt::Arg *A = Args.getLastArg(opts::OPT_profile_kind)) {
-      StringRef PK = A->getValue();
-      ProfileKind = StringSwitch<ProfileKinds>(PK)
-                        .Case("instr", instr)
-                        .Case("sample", sample)
-                        .Default(instr);
+    if (Args.hasArg(opts::OPT_cs))
+      IsCS = true;
+
+    if (Args.hasArg(opts::OPT_sample)) {
+      ProfileKind = sample;
+    } else if (Args.hasArg(opts::OPT_instr)) {
+      ProfileKind = instr;
     }
     if (opt::Arg *A = Args.getLastArg(opts::OPT_function)) {
       FuncNameFilter = A->getValue();

>From 5124ae38e9c9b992778258fc617f1b0856dde09f Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 26 May 2026 16:54:08 -0700
Subject: [PATCH 3/3] Registry pattern for reducing verbosity

---
 llvm/tools/llvm-profdata/llvm-profdata.cpp | 172 +++++++++------------
 1 file changed, 69 insertions(+), 103 deletions(-)

diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp
index bc81043287e69..2c3c913ceac45 100644
--- a/llvm/tools/llvm-profdata/llvm-profdata.cpp
+++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp
@@ -209,6 +209,54 @@ class ProfDataOptTable : public opt::GenericOptTable {
 } // namespace opts
 
 static void parseArgs(int argc, char *const *argv) {
+  struct BoolMapEntry {
+    StringRef Name;
+    unsigned ID;
+    bool &ValRef;
+  };
+
+  static const BoolMapEntry BoolMap[] = {
+      {"sparse", opts::OPT_sparse, OutputSparse},
+      {"compress-all-sections", opts::OPT_compress_all_sections,
+       CompressAllSections},
+      {"sample-merge-cold-context", opts::OPT_sample_merge_cold_context,
+       SampleMergeColdContext},
+      {"sample-trim-cold-context", opts::OPT_sample_trim_cold_context,
+       SampleTrimColdContext},
+      {"gen-partial-profile", opts::OPT_gen_partial_profile, GenPartialProfile},
+      {"split-layout", opts::OPT_split_layout, SplitLayout},
+      {"drop-profile-symbol-list", opts::OPT_drop_profile_symbol_list,
+       DropProfileSymbolList},
+      {"keep-vtable-symbols", opts::OPT_keep_vtable_symbols, KeepVTableSymbols},
+      {"write-prev-version", opts::OPT_write_prev_version, DoWritePrevVersion},
+      {"memprof-full-schema", opts::OPT_memprof_full_schema, MemProfFullSchema},
+      {"memprof-random-hotness", opts::OPT_memprof_random_hotness,
+       MemprofGenerateRandomHotness},
+      {"debuginfod", opts::OPT_debuginfod, DebugInfod},
+      {"dump-input-file-list", opts::OPT_dump_input_file_list,
+       DumpInputFileList},
+      {"use-md5", opts::OPT_use_md5, UseMD5},
+      {"counts", opts::OPT_counts, ShowCounts},
+      {"text", opts::OPT_text, TextFormat},
+      {"json", opts::OPT_json, JsonFormat},
+      {"ic-targets", opts::OPT_ic_targets, ShowIndirectCallTargets},
+      {"show-vtables", opts::OPT_show_vtables, ShowVTables},
+      {"memop-sizes", opts::OPT_memop_sizes, ShowMemOPSizes},
+      {"detailed-summary", opts::OPT_detailed_summary, ShowDetailedSummary},
+      {"hot-func-list", opts::OPT_hot_func_list, ShowHotFuncList},
+      {"all-functions", opts::OPT_all_functions, ShowAllFunctions},
+      {"showcs", opts::OPT_showcs, ShowCS},
+      {"list-below-cutoff", opts::OPT_list_below_cutoff, OnlyListBelow},
+      {"show-prof-sym-list", opts::OPT_show_prof_sym_list,
+       ShowProfileSymbolList},
+      {"show-sec-info-only", opts::OPT_show_sec_info_only, ShowSectionInfoOnly},
+      {"binary-ids", opts::OPT_binary_ids, ShowBinaryIds},
+      {"temporal-profile-traces", opts::OPT_temporal_profile_traces,
+       ShowTemporalProfTraces},
+      {"covered", opts::OPT_covered, ShowCovered},
+      {"profile-version", opts::OPT_profile_version, ShowProfileVersion},
+      {"cs", opts::OPT_cs, IsCS}};
+
   opts::ProfDataOptTable Tbl;
   llvm::BumpPtrAllocator A;
   llvm::StringSaver Saver{A};
@@ -218,6 +266,13 @@ static void parseArgs(int argc, char *const *argv) {
         std::exit(1);
       });
 
+  // Populate standard matched flags
+  for (const auto &E : BoolMap) {
+    if (Args.hasArg(E.ID)) {
+      E.ValRef = true;
+    }
+  }
+
   // Delegate unrecognized support library options to the cl option registry!
   auto &CLOpts = cl::getRegisteredOptions();
   for (const opt::Arg *Arg : Args.filtered(opts::OPT_UNKNOWN)) {
@@ -239,109 +294,20 @@ static void parseArgs(int argc, char *const *argv) {
       OptVal = "true";
     }
 
-    static const SmallSet<StringRef, 32> ToolBoolOpts = {
-        "sparse",
-        "compress-all-sections",
-        "sample-merge-cold-context",
-        "sample-trim-cold-context",
-        "gen-partial-profile",
-        "split-layout",
-        "drop-profile-symbol-list",
-        "keep-vtable-symbols",
-        "write-prev-version",
-        "memprof-full-schema",
-        "memprof-random-hotness",
-        "debuginfod",
-        "dump-input-file-list",
-        "use-md5",
-        "counts",
-        "text",
-        "json",
-        "ic-targets",
-        "show-vtables",
-        "memop-sizes",
-        "detailed-summary",
-        "hot-func-list",
-        "all-functions",
-        "showcs",
-        "list-below-cutoff",
-        "show-prof-sym-list",
-        "show-sec-info-only",
-        "binary-ids",
-        "temporal-profile-traces",
-        "covered",
-        "profile-version",
-        "cs"};
-
-    if (ToolBoolOpts.count(OptName)) {
-      bool BoolVal = StringSwitch<bool>(OptVal)
-                         .Cases({"true", "1"}, true)
-                         .Cases({"false", "0"}, false)
-                         .Default(false);
-      if (OptName == "sparse")
-        OutputSparse = BoolVal;
-      else if (OptName == "compress-all-sections")
-        CompressAllSections = BoolVal;
-      else if (OptName == "sample-merge-cold-context")
-        SampleMergeColdContext = BoolVal;
-      else if (OptName == "sample-trim-cold-context")
-        SampleTrimColdContext = BoolVal;
-      else if (OptName == "gen-partial-profile")
-        GenPartialProfile = BoolVal;
-      else if (OptName == "split-layout")
-        SplitLayout = BoolVal;
-      else if (OptName == "drop-profile-symbol-list")
-        DropProfileSymbolList = BoolVal;
-      else if (OptName == "keep-vtable-symbols")
-        KeepVTableSymbols = BoolVal;
-      else if (OptName == "write-prev-version")
-        DoWritePrevVersion = BoolVal;
-      else if (OptName == "memprof-full-schema")
-        MemProfFullSchema = BoolVal;
-      else if (OptName == "memprof-random-hotness")
-        MemprofGenerateRandomHotness = BoolVal;
-      else if (OptName == "debuginfod")
-        DebugInfod = BoolVal;
-      else if (OptName == "dump-input-file-list")
-        DumpInputFileList = BoolVal;
-      else if (OptName == "use-md5")
-        UseMD5 = BoolVal;
-      else if (OptName == "counts")
-        ShowCounts = BoolVal;
-      else if (OptName == "text")
-        TextFormat = BoolVal;
-      else if (OptName == "json")
-        JsonFormat = BoolVal;
-      else if (OptName == "ic-targets")
-        ShowIndirectCallTargets = BoolVal;
-      else if (OptName == "show-vtables")
-        ShowVTables = BoolVal;
-      else if (OptName == "memop-sizes")
-        ShowMemOPSizes = BoolVal;
-      else if (OptName == "detailed-summary")
-        ShowDetailedSummary = BoolVal;
-      else if (OptName == "hot-func-list")
-        ShowHotFuncList = BoolVal;
-      else if (OptName == "all-functions")
-        ShowAllFunctions = BoolVal;
-      else if (OptName == "showcs")
-        ShowCS = BoolVal;
-      else if (OptName == "list-below-cutoff")
-        OnlyListBelow = BoolVal;
-      else if (OptName == "show-prof-sym-list")
-        ShowProfileSymbolList = BoolVal;
-      else if (OptName == "show-sec-info-only")
-        ShowSectionInfoOnly = BoolVal;
-      else if (OptName == "binary-ids")
-        ShowBinaryIds = BoolVal;
-      else if (OptName == "temporal-profile-traces")
-        ShowTemporalProfTraces = BoolVal;
-      else if (OptName == "covered")
-        ShowCovered = BoolVal;
-      else if (OptName == "profile-version")
-        ShowProfileVersion = BoolVal;
-      else if (OptName == "cs")
-        IsCS = BoolVal;
+    // Find matching boolean option in the BoolMap
+    const BoolMapEntry *Entry = nullptr;
+    for (const auto &E : BoolMap) {
+      if (E.Name == OptName) {
+        Entry = &E;
+        break;
+      }
+    }
+
+    if (Entry) {
+      Entry->ValRef = StringSwitch<bool>(OptVal)
+                          .Cases({"true", "1"}, true)
+                          .Cases({"false", "0"}, false)
+                          .Default(false);
       continue;
     }
 



More information about the llvm-commits mailing list