[llvm] [opt] Add --save-stats option (PR #167304)
Tomer Shafir via llvm-commits
llvm-commits at lists.llvm.org
Mon Nov 10 03:59:20 PST 2025
https://github.com/tomershafir created https://github.com/llvm/llvm-project/pull/167304
This patch adds a Clang-compatible --save-stats option to opt, to provide an easy to use way to save LLVM statistics files when working with opt on the middle end.
This is a follow up on the addition to `llc`: https://github.com/llvm/llvm-project/pull/163967
Like on Clang, one can specify --save-stats, --save-stats=cwd, and --save-stats=obj with the same semantics and JSON format. The pre-existing --stats option is not affected.
The implementation extracts the flag and its methods into the common `CodeGen/CommandFlags` as `LLVM_ABI`, using a new registration class to conservatively enable opt-in rather than let all tools take it. Its only needed for llc and opt for now. Then it refactors llc and adds support for opt.
>From c97d8a2a9f85f897bc880515b033fbe87ef2dc76 Mon Sep 17 00:00:00 2001
From: tomershafir <tomer.shafir8 at gmail.com>
Date: Mon, 10 Nov 2025 13:51:40 +0200
Subject: [PATCH] [opt] Add --save-stats option
This patch adds a Clang-compatible --save-stats option to opt, to provide an easy to use way to save LLVM statistics files when working with opt on the middle end.
This is a follow up on the addition to `llc`: https://github.com/llvm/llvm-project/pull/163967
Like on Clang, one can specify --save-stats, --save-stats=cwd, and --save-stats=obj with the same semantics and JSON format. The pre-existing --stats option is not affected.
The implementation extracts the flag and its methods into the common `CodeGen/CommandFlags` as `LLVM_ABI`, using a new registration class to conservatively enable opt-in rather than let all tools take it. Its only needed for llc and opt for now. Then it refactors llc and adds support for opt.
---
llvm/docs/CommandGuide/opt.rst | 6 ++
llvm/docs/ReleaseNotes.md | 1 +
llvm/include/llvm/CodeGen/CommandFlags.h | 20 +++++++
llvm/lib/CodeGen/CommandFlags.cpp | 74 ++++++++++++++++++++++--
llvm/test/tools/opt/save-stats.ll | 15 +++++
llvm/tools/llc/llc.cpp | 59 +------------------
llvm/tools/opt/optdriver.cpp | 14 +++--
7 files changed, 123 insertions(+), 66 deletions(-)
create mode 100644 llvm/test/tools/opt/save-stats.ll
diff --git a/llvm/docs/CommandGuide/opt.rst b/llvm/docs/CommandGuide/opt.rst
index da93b8e4e9c54..eb15f0ea43634 100644
--- a/llvm/docs/CommandGuide/opt.rst
+++ b/llvm/docs/CommandGuide/opt.rst
@@ -70,6 +70,12 @@ OPTIONS
Print statistics.
+.. option:: --save-stats, --save-stats=cwd, --save-stats=obj
+
+ Save LLVM statistics to a file in the current directory
+ (:option:`--save-stats`/"--save-stats=cwd") or the directory
+ of the output file ("--save-stats=obj") in JSON format.
+
.. option:: -time-passes
Record the amount of time needed for each pass and print it to standard
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index fd78c97c86d24..1ae539fec0fef 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -183,6 +183,7 @@ Changes to the LLVM tools
* Some code paths for supporting Python 2.7 in `llvm-lit` have been removed.
* Support for `%T` in lit has been removed.
* Add `--save-stats` option to `llc` to save LLVM statistics to a file. Compatible with the Clang option.
+* Add `--save-stats` option to `opt` to save LLVM statistics to a file. Compatible with the Clang option.
* `llvm-config` gained a new flag `--quote-paths` which quotes and escapes paths
emitted on stdout, to account for spaces or other special characters in path.
diff --git a/llvm/include/llvm/CodeGen/CommandFlags.h b/llvm/include/llvm/CodeGen/CommandFlags.h
index af66f2d5776ba..e46628bdf1d6e 100644
--- a/llvm/include/llvm/CodeGen/CommandFlags.h
+++ b/llvm/include/llvm/CodeGen/CommandFlags.h
@@ -154,12 +154,22 @@ LLVM_ABI bool getJMCInstrument();
LLVM_ABI bool getXCOFFReadOnlyPointers();
+enum SaveStatsMode { None, Cwd, Obj };
+
+LLVM_ABI SaveStatsMode getSaveStats();
+
/// Create this object with static storage to register codegen-related command
/// line options.
struct RegisterCodeGenFlags {
LLVM_ABI RegisterCodeGenFlags();
};
+/// Tools that support stats saving should create this object with static storage
+/// to register the --save-stats command line option.
+struct RegisterSaveStatsFlag {
+ LLVM_ABI RegisterSaveStatsFlag();
+};
+
LLVM_ABI bool getEnableBBAddrMap();
LLVM_ABI llvm::BasicBlockSection
@@ -203,6 +213,16 @@ LLVM_ABI Expected<std::unique_ptr<TargetMachine>> createTargetMachineForTriple(
StringRef TargetTriple,
CodeGenOptLevel OptLevel = CodeGenOptLevel::Default);
+/// Conditionally enables the collection of LLVM statistics during the tool run,
+/// based on the value of the flag. Must be call before the tool run to actually
+/// collect data.
+LLVM_ABI int MaybeEnableStatistics();
+
+/// Conditionally saves the collected LLVM statistics to the recevied output file,
+/// based on the value of the flag. Shuould be called after the tool run, and must
+/// follow a call to `MaybeEnableStats()` to actually have data to write.
+LLVM_ABI int MaybeSaveStatistics(StringRef OutputFilename, StringRef ToolName);
+
} // namespace codegen
} // namespace llvm
diff --git a/llvm/lib/CodeGen/CommandFlags.cpp b/llvm/lib/CodeGen/CommandFlags.cpp
index c1365f499dcf5..caa2f3cf8cd89 100644
--- a/llvm/lib/CodeGen/CommandFlags.cpp
+++ b/llvm/lib/CodeGen/CommandFlags.cpp
@@ -13,33 +13,43 @@
//===----------------------------------------------------------------------===//
#include "llvm/CodeGen/CommandFlags.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"
#include "llvm/MC/MCTargetOptionsCommandFlags.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Support/WithColor.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/TargetParser/Host.h"
#include "llvm/TargetParser/SubtargetFeature.h"
#include "llvm/TargetParser/Triple.h"
+#include <cassert>
+#include <memory>
#include <optional>
+#include <system_error>
using namespace llvm;
#define CGOPT(TY, NAME) \
static cl::opt<TY> *NAME##View; \
TY codegen::get##NAME() { \
- assert(NAME##View && "RegisterCodeGenFlags not created."); \
+ assert(NAME##View && "Flag not registered."); \
return *NAME##View; \
}
#define CGLIST(TY, NAME) \
static cl::list<TY> *NAME##View; \
std::vector<TY> codegen::get##NAME() { \
- assert(NAME##View && "RegisterCodeGenFlags not created."); \
+ assert(NAME##View && "Flag not registered."); \
return *NAME##View; \
}
@@ -110,13 +120,14 @@ CGOPT(bool, DebugStrictDwarf)
CGOPT(unsigned, AlignLoops)
CGOPT(bool, JMCInstrument)
CGOPT(bool, XCOFFReadOnlyPointers)
+CGOPT(codegen::SaveStatsMode, SaveStats)
-codegen::RegisterCodeGenFlags::RegisterCodeGenFlags() {
#define CGBINDOPT(NAME) \
do { \
NAME##View = std::addressof(NAME); \
} while (0)
+codegen::RegisterCodeGenFlags::RegisterCodeGenFlags() {
static cl::opt<std::string> MArch(
"march", cl::desc("Architecture to generate code for (see --version)"));
CGBINDOPT(MArch);
@@ -515,11 +526,24 @@ codegen::RegisterCodeGenFlags::RegisterCodeGenFlags() {
cl::init(false));
CGBINDOPT(DisableIntegratedAS);
-#undef CGBINDOPT
-
mc::RegisterMCTargetOptionsFlags();
}
+codegen::RegisterSaveStatsFlag::RegisterSaveStatsFlag() {
+ static cl::opt<SaveStatsMode> SaveStats(
+ "save-stats",
+ cl::desc("Save LLVM statistics to a file in the current directory"
+ "(`-save-stats`/`-save-stats=cwd`) or the directory of the output"
+ "file (`-save-stats=obj`). (default: cwd)"),
+ cl::values(clEnumValN(SaveStatsMode::Cwd, "cwd",
+ "Save to the current working directory"),
+ clEnumValN(SaveStatsMode::Cwd, "", ""),
+ clEnumValN(SaveStatsMode::Obj, "obj",
+ "Save to the output file directory")),
+ cl::init(SaveStatsMode::None), cl::ValueOptional);
+ CGBINDOPT(SaveStats);
+}
+
llvm::BasicBlockSection
codegen::getBBSectionsMode(llvm::TargetOptions &Options) {
if (getBBSections() == "all")
@@ -763,3 +787,43 @@ codegen::createTargetMachineForTriple(StringRef TargetTriple,
TargetTriple);
return std::unique_ptr<TargetMachine>(Target);
}
+
+int codegen::MaybeEnableStatistics() {
+ if (getSaveStats() == SaveStatsMode::None)
+ return 0;
+
+ llvm::EnableStatistics(false);
+ return 0;
+}
+
+int codegen::MaybeSaveStatistics(StringRef OutputFilename, StringRef ToolName) {
+ auto SaveStatsValue = getSaveStats();
+ if (SaveStatsValue == codegen::SaveStatsMode::None)
+ return 0;
+
+ SmallString<128> StatsFilename;
+ if (SaveStatsValue == codegen::SaveStatsMode::Obj) {
+ StatsFilename = OutputFilename;
+ llvm::sys::path::remove_filename(StatsFilename);
+ } else {
+ assert(SaveStatsValue == codegen::SaveStatsMode::Cwd &&
+ "Should have been a valid --save-stats value");
+ }
+
+ auto BaseName = llvm::sys::path::filename(OutputFilename);
+ llvm::sys::path::append(StatsFilename, BaseName);
+ llvm::sys::path::replace_extension(StatsFilename, "stats");
+
+ auto FileFlags = llvm::sys::fs::OF_TextWithCRLF;
+ std::error_code EC;
+ auto StatsOS =
+ std::make_unique<llvm::raw_fd_ostream>(StatsFilename, EC, FileFlags);
+ if (EC) {
+ WithColor::error(errs(), ToolName)
+ << "Unable to open statistics file: " << EC.message() << "\n";
+ return 1;
+ }
+
+ llvm::PrintStatisticsJSON(*StatsOS);
+ return 0;
+}
diff --git a/llvm/test/tools/opt/save-stats.ll b/llvm/test/tools/opt/save-stats.ll
new file mode 100644
index 0000000000000..7dd0c4535c5d3
--- /dev/null
+++ b/llvm/test/tools/opt/save-stats.ll
@@ -0,0 +1,15 @@
+; REQUIRES: asserts
+
+; RUN: opt -S -passes=instcombine --save-stats=obj -o %t.ll %s && cat %t.stats | FileCheck %s
+; RUN: opt -S -passes=instcombine --save-stats=cwd -o %t.ll %s && cat %{t:stem}.tmp.stats | FileCheck %s
+; RUN: opt -S -passes=instcombine --save-stats -o %t.ll %s && cat %{t:stem}.tmp.stats | FileCheck %s
+; RUN: not opt -S --save-stats=invalid -o %t.ll %s 2>&1 | FileCheck %s --check-prefix=INVALID_ARG
+
+; CHECK: {
+; CHECK: "instcombine.NumWorklistIterations":
+; CHECK: }
+
+; INVALID_ARG: {{.*}}opt{{.*}}: for the --save-stats option: Cannot find option named 'invalid'!
+define i32 @func() {
+ ret i32 0
+}
diff --git a/llvm/tools/llc/llc.cpp b/llvm/tools/llc/llc.cpp
index dc2f878830863..cd14bf561df8d 100644
--- a/llvm/tools/llc/llc.cpp
+++ b/llvm/tools/llc/llc.cpp
@@ -65,6 +65,7 @@
using namespace llvm;
static codegen::RegisterCodeGenFlags CGF;
+static codegen::RegisterSaveStatsFlag SSF;
// General options for llc. Other pass-specific options are specified
// within the corresponding llc passes, and target-specific options
@@ -211,20 +212,6 @@ static cl::opt<std::string> RemarksFormat(
cl::desc("The format used for serializing remarks (default: YAML)"),
cl::value_desc("format"), cl::init("yaml"));
-enum SaveStatsMode { None, Cwd, Obj };
-
-static cl::opt<SaveStatsMode> SaveStats(
- "save-stats",
- cl::desc("Save LLVM statistics to a file in the current directory"
- "(`-save-stats`/`-save-stats=cwd`) or the directory of the output"
- "file (`-save-stats=obj`). (default: cwd)"),
- cl::values(clEnumValN(SaveStatsMode::Cwd, "cwd",
- "Save to the current working directory"),
- clEnumValN(SaveStatsMode::Cwd, "", ""),
- clEnumValN(SaveStatsMode::Obj, "obj",
- "Save to the output file directory")),
- cl::init(SaveStatsMode::None), cl::ValueOptional);
-
static cl::opt<bool> EnableNewPassManager(
"enable-new-pm", cl::desc("Enable the new pass manager"), cl::init(false));
@@ -377,46 +364,6 @@ static std::unique_ptr<ToolOutputFile> GetOutputStream(const char *TargetName,
return FDOut;
}
-
-static int MaybeEnableStats() {
- if (SaveStats == SaveStatsMode::None)
- return 0;
-
- llvm::EnableStatistics(false);
- return 0;
-}
-
-static int MaybeSaveStats(std::string &&OutputFilename) {
- if (SaveStats == SaveStatsMode::None)
- return 0;
-
- SmallString<128> StatsFilename;
- if (SaveStats == SaveStatsMode::Obj) {
- StatsFilename = OutputFilename;
- llvm::sys::path::remove_filename(StatsFilename);
- } else {
- assert(SaveStats == SaveStatsMode::Cwd &&
- "Should have been a valid --save-stats value");
- }
-
- auto BaseName = llvm::sys::path::filename(OutputFilename);
- llvm::sys::path::append(StatsFilename, BaseName);
- llvm::sys::path::replace_extension(StatsFilename, "stats");
-
- auto FileFlags = llvm::sys::fs::OF_TextWithCRLF;
- std::error_code EC;
- auto StatsOS =
- std::make_unique<llvm::raw_fd_ostream>(StatsFilename, EC, FileFlags);
- if (EC) {
- WithColor::error(errs(), "llc")
- << "Unable to open statistics file: " << EC.message() << "\n";
- return 1;
- }
-
- llvm::PrintStatisticsJSON(*StatsOS);
- return 0;
-}
-
// main - Entry point for the llc compiler.
//
int main(int argc, char **argv) {
@@ -494,7 +441,7 @@ int main(int argc, char **argv) {
reportError(std::move(E), RemarksFilename);
LLVMRemarkFileHandle RemarksFile = std::move(*RemarksFileOrErr);
- if (int RetVal = MaybeEnableStats())
+ if (int RetVal = codegen::MaybeEnableStatistics())
return RetVal;
std::string OutputFilename;
@@ -510,7 +457,7 @@ int main(int argc, char **argv) {
if (RemarksFile)
RemarksFile->keep();
- return MaybeSaveStats(std::move(OutputFilename));
+ return codegen::MaybeSaveStatistics(OutputFilename, "llc");
}
static bool addPass(PassManagerBase &PM, const char *argv0, StringRef PassName,
diff --git a/llvm/tools/opt/optdriver.cpp b/llvm/tools/opt/optdriver.cpp
index f70db3133f69d..18897959d7c78 100644
--- a/llvm/tools/opt/optdriver.cpp
+++ b/llvm/tools/opt/optdriver.cpp
@@ -64,6 +64,7 @@ using namespace llvm;
using namespace opt_tool;
static codegen::RegisterCodeGenFlags CFG;
+static codegen::RegisterSaveStatsFlag SSF;
// The OptimizationList is automatically populated with registered Passes by the
// PassNameParser.
@@ -512,6 +513,9 @@ optMain(int argc, char **argv,
}
LLVMRemarkFileHandle RemarksFile = std::move(*RemarksFileOrErr);
+ if (int RetVal = codegen::MaybeEnableStatistics())
+ return RetVal;
+
// Load the input module...
auto SetDataLayout = [&](StringRef IRTriple,
StringRef IRLayout) -> std::optional<std::string> {
@@ -742,15 +746,15 @@ optMain(int argc, char **argv,
// The user has asked to use the new pass manager and provided a pipeline
// string. Hand off the rest of the functionality to the new code for that
// layer.
- return runPassPipeline(
+ if (!runPassPipeline(
argv[0], *M, TM.get(), &TLII, Out.get(), ThinLinkOut.get(),
RemarksFile.get(), Pipeline, PluginList, PassBuilderCallbacks,
OK, VK, /* ShouldPreserveAssemblyUseListOrder */ false,
/* ShouldPreserveBitcodeUseListOrder */ true, EmitSummaryIndex,
EmitModuleHash, EnableDebugify, VerifyDebugInfoPreserve,
- EnableProfileVerification, UnifiedLTO)
- ? 0
- : 1;
+ EnableProfileVerification, UnifiedLTO))
+ return 1;
+ return codegen::MaybeSaveStatistics(OutputFilename, "opt");
}
if (OptLevelO0 || OptLevelO1 || OptLevelO2 || OptLevelOs || OptLevelOz ||
@@ -928,5 +932,5 @@ optMain(int argc, char **argv,
if (ThinLinkOut)
ThinLinkOut->keep();
- return 0;
+ return codegen::MaybeSaveStatistics(OutputFilename, "opt");
}
More information about the llvm-commits
mailing list