[llvm] [tools][llc] Add `--save-stats` option (PR #163967)

Tomer Shafir via llvm-commits llvm-commits at lists.llvm.org
Tue Oct 28 15:00:06 PDT 2025


https://github.com/tomershafir updated https://github.com/llvm/llvm-project/pull/163967

>From 56a0157a1a511e8b29eb8b90e9e5cf480d2d36aa Mon Sep 17 00:00:00 2001
From: tomershafir <tomer.shafir8 at gmail.com>
Date: Fri, 17 Oct 2025 16:46:06 +0300
Subject: [PATCH 1/2] [tools][llc] Add `--save-stats` option

This patch adds a Clang-compatible `--save-stats` option, to provide an easy to use way to save LLVM statistics files when working with llc on the backend.

Like on Clang, one can specify `--save-stats`, `--save-stats=cwd`, and `--save-stats=obj` with the same semantics and JSON format.

The implementation uses 2 methods `MaybeEnableStats` and `MaybeSaveStats` called before and after `compileModule` respectively that externally own the statistics related logic, while `compileModule` is now required to return the resolved output filename via an output param.

Note: like on Clang, the pre-existing `--stats` option is not affected.
---
 llvm/docs/CommandGuide/llc.rst    |  6 +++
 llvm/docs/ReleaseNotes.md         |  1 +
 llvm/test/tools/llc/save-stats.ll | 10 +++++
 llvm/tools/llc/llc.cpp            | 72 +++++++++++++++++++++++++++++--
 4 files changed, 85 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/tools/llc/save-stats.ll

diff --git a/llvm/docs/CommandGuide/llc.rst b/llvm/docs/CommandGuide/llc.rst
index 900649f59c401..6fe0a51292132 100644
--- a/llvm/docs/CommandGuide/llc.rst
+++ b/llvm/docs/CommandGuide/llc.rst
@@ -136,6 +136,12 @@ End-user Options
 
  Print statistics recorded by code-generation passes.
 
+.. 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 a report to standard
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 754cd409f83eb..66a0986210820 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -175,6 +175,7 @@ Changes to the LLVM tools
 * `llvm-readelf` now dumps all hex format values in lower-case mode.
 * 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.
 
 Changes to LLDB
 ---------------------------------
diff --git a/llvm/test/tools/llc/save-stats.ll b/llvm/test/tools/llc/save-stats.ll
new file mode 100644
index 0000000000000..cf0752c2c9eef
--- /dev/null
+++ b/llvm/test/tools/llc/save-stats.ll
@@ -0,0 +1,10 @@
+
+; RUN: rm -f %t.stats && llc -mtriple=arm64-apple-macosx --save-stats=obj -o %t.s %s && test -f %t.stats
+; RUN: rm -f %{t:stem}.tmp.stats && llc -mtriple=arm64-apple-macosx --save-stats=cwd -o %t.s %s && test -f %{t:stem}.tmp.stats
+; RUN: rm -f %{t:stem}.tmp.stats && llc -mtriple=arm64-apple-macosx --save-stats -o %t.s %s && test -f %{t:stem}.tmp.stats
+; RUN: not llc -mtriple=arm64-apple-macosx --save-stats=invalid -o %t.s %s 2>&1 | FileCheck %s --check-prefix=INVALID_ARG
+
+; INVALID_ARG: llc: error: Invalid --save-stats value: invalid, must be empty, 'cwd' or 'obj'
+define i32 @func() {
+  ret i32 0
+}
diff --git a/llvm/tools/llc/llc.cpp b/llvm/tools/llc/llc.cpp
index 8b03db301424b..7057c3f6c0254 100644
--- a/llvm/tools/llc/llc.cpp
+++ b/llvm/tools/llc/llc.cpp
@@ -15,6 +15,7 @@
 #include "NewPMDriver.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/Statistic.h"
 #include "llvm/Analysis/TargetLibraryInfo.h"
 #include "llvm/CodeGen/CommandFlags.h"
 #include "llvm/CodeGen/LinkAllAsmWriterComponents.h"
@@ -45,6 +46,7 @@
 #include "llvm/Support/FormattedStream.h"
 #include "llvm/Support/InitLLVM.h"
 #include "llvm/Support/PGOOptions.h"
+#include "llvm/Support/Path.h"
 #include "llvm/Support/PluginLoader.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/TargetSelect.h"
@@ -57,6 +59,7 @@
 #include "llvm/TargetParser/SubtargetFeature.h"
 #include "llvm/TargetParser/Triple.h"
 #include "llvm/Transforms/Utils/Cloning.h"
+#include <cassert>
 #include <memory>
 #include <optional>
 using namespace llvm;
@@ -203,6 +206,13 @@ static cl::opt<std::string> RemarksFormat(
     cl::desc("The format used for serializing remarks (default: YAML)"),
     cl::value_desc("format"), cl::init("yaml"));
 
+static cl::opt<std::string> 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::init(""), cl::ValueOptional);
+
 static cl::opt<bool> EnableNewPassManager(
     "enable-new-pm", cl::desc("Enable the new pass manager"), cl::init(false));
 
@@ -276,7 +286,8 @@ static void setPGOOptions(TargetMachine &TM) {
     TM.setPGOOption(PGOOpt);
 }
 
-static int compileModule(char **, LLVMContext &);
+static int compileModule(char **argv, LLVMContext &Context,
+                         std::string &OutputFilename);
 
 [[noreturn]] static void reportError(Twine Msg, StringRef Filename = "") {
   SmallString<256> Prefix;
@@ -355,6 +366,50 @@ static std::unique_ptr<ToolOutputFile> GetOutputStream(const char *TargetName,
   return FDOut;
 }
 
+static int MaybeEnableStats() {
+  if (SaveStats.getNumOccurrences() > 0) {
+    if (SaveStats.empty() || SaveStats == "cwd" || SaveStats == "obj") {
+      llvm::EnableStatistics(false);
+    } else {
+      WithColor::error(errs(), "llc")
+          << "Invalid --save-stats value: " << SaveStats
+          << ", must be empty, 'cwd' or 'obj'\n";
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static int MaybeSaveStats(std::string &&OutputFilename) {
+  if (SaveStats.getNumOccurrences() > 0) {
+    SmallString<128> StatsFilename;
+    if (SaveStats == "obj") {
+      StatsFilename = OutputFilename;
+      llvm::sys::path::remove_filename(StatsFilename);
+    } else {
+      assert((SaveStats.empty() || SaveStats == "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;
+    } else {
+      llvm::PrintStatisticsJSON(*StatsOS);
+    }
+  }
+  return 0;
+}
+
 // main - Entry point for the llc compiler.
 //
 int main(int argc, char **argv) {
@@ -432,18 +487,23 @@ int main(int argc, char **argv) {
     reportError(std::move(E), RemarksFilename);
   LLVMRemarkFileHandle RemarksFile = std::move(*RemarksFileOrErr);
 
+  if (int RetVal = MaybeEnableStats())
+    return RetVal;
+  std::string OutputFilename;
+
   if (InputLanguage != "" && InputLanguage != "ir" && InputLanguage != "mir")
     reportError("input language must be '', 'IR' or 'MIR'");
 
   // Compile the module TimeCompilations times to give better compile time
   // metrics.
   for (unsigned I = TimeCompilations; I; --I)
-    if (int RetVal = compileModule(argv, Context))
+    if (int RetVal = compileModule(argv, Context, OutputFilename))
       return RetVal;
 
   if (RemarksFile)
     RemarksFile->keep();
-  return 0;
+
+  return MaybeSaveStats(std::move(OutputFilename));
 }
 
 static bool addPass(PassManagerBase &PM, const char *argv0, StringRef PassName,
@@ -475,7 +535,8 @@ static bool addPass(PassManagerBase &PM, const char *argv0, StringRef PassName,
   return false;
 }
 
-static int compileModule(char **argv, LLVMContext &Context) {
+static int compileModule(char **argv, LLVMContext &Context,
+                         std::string &OutputFilename) {
   // Load the module to be compiled...
   SMDiagnostic Err;
   std::unique_ptr<Module> M;
@@ -659,6 +720,9 @@ static int compileModule(char **argv, LLVMContext &Context) {
   // Ensure the filename is passed down to CodeViewDebug.
   Target->Options.ObjectFilenameForDebug = Out->outputFilename();
 
+  // Return a copy of the output filename via the output param
+  OutputFilename = Out->outputFilename();
+
   // Tell target that this tool is not necessarily used with argument ABI
   // compliance (i.e. narrow integer argument extensions).
   Target->Options.VerifyArgABICompliance = 0;

>From a377f76ebcd42c4066365f34c21063ea4d663e14 Mon Sep 17 00:00:00 2001
From: tomershafir <tomer.shafir8 at gmail.com>
Date: Tue, 28 Oct 2025 11:47:32 +0200
Subject: [PATCH 2/2] use enum based option and flatten control flow

---
 llvm/test/tools/llc/save-stats.ll |  2 +-
 llvm/tools/llc/llc.cpp            | 72 ++++++++++++++++---------------
 2 files changed, 39 insertions(+), 35 deletions(-)

diff --git a/llvm/test/tools/llc/save-stats.ll b/llvm/test/tools/llc/save-stats.ll
index cf0752c2c9eef..4d5b969cf2036 100644
--- a/llvm/test/tools/llc/save-stats.ll
+++ b/llvm/test/tools/llc/save-stats.ll
@@ -4,7 +4,7 @@
 ; RUN: rm -f %{t:stem}.tmp.stats && llc -mtriple=arm64-apple-macosx --save-stats -o %t.s %s && test -f %{t:stem}.tmp.stats
 ; RUN: not llc -mtriple=arm64-apple-macosx --save-stats=invalid -o %t.s %s 2>&1 | FileCheck %s --check-prefix=INVALID_ARG
 
-; INVALID_ARG: llc: error: Invalid --save-stats value: invalid, must be empty, 'cwd' or 'obj'
+; INVALID_ARG: {{.*}}llc{{.*}}: 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 7057c3f6c0254..f0266e7464e04 100644
--- a/llvm/tools/llc/llc.cpp
+++ b/llvm/tools/llc/llc.cpp
@@ -206,12 +206,19 @@ static cl::opt<std::string> RemarksFormat(
     cl::desc("The format used for serializing remarks (default: YAML)"),
     cl::value_desc("format"), cl::init("yaml"));
 
-static cl::opt<std::string> SaveStats(
+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::init(""), cl::ValueOptional);
+    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));
@@ -367,46 +374,43 @@ static std::unique_ptr<ToolOutputFile> GetOutputStream(const char *TargetName,
 }
 
 static int MaybeEnableStats() {
-  if (SaveStats.getNumOccurrences() > 0) {
-    if (SaveStats.empty() || SaveStats == "cwd" || SaveStats == "obj") {
-      llvm::EnableStatistics(false);
-    } else {
-      WithColor::error(errs(), "llc")
-          << "Invalid --save-stats value: " << SaveStats
-          << ", must be empty, 'cwd' or 'obj'\n";
-      return 1;
-    }
+  if (SaveStats == SaveStatsMode::None) {
+    return 0;
   }
+
+  llvm::EnableStatistics(false);
   return 0;
 }
 
 static int MaybeSaveStats(std::string &&OutputFilename) {
-  if (SaveStats.getNumOccurrences() > 0) {
-    SmallString<128> StatsFilename;
-    if (SaveStats == "obj") {
-      StatsFilename = OutputFilename;
-      llvm::sys::path::remove_filename(StatsFilename);
-    } else {
-      assert((SaveStats.empty() || SaveStats == "cwd") &&
-             "Should have been a valid --save-stats value");
-    }
+  if (SaveStats == SaveStatsMode::None) {
+    return 0;
+  }
 
-    auto BaseName = llvm::sys::path::filename(OutputFilename);
-    llvm::sys::path::append(StatsFilename, BaseName);
-    llvm::sys::path::replace_extension(StatsFilename, "stats");
+  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 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;
-    } else {
-      llvm::PrintStatisticsJSON(*StatsOS);
-    }
+  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;
 }
 



More information about the llvm-commits mailing list