[llvm] [tools] Add LLVM Advisor - Comprehensive optimization analysis and performance guidance tool (PR #147451)
Miguel Cárdenas via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 7 19:53:14 PDT 2025
https://github.com/miguelcsx created https://github.com/llvm/llvm-project/pull/147451
## Summary
This PR introduces LLVM Advisor, a new tool that provides comprehensive optimization analysis and performance guidance for LLVM-based compilation. The tool goes beyond traditional optimization remarks to offer actionable insights for developers working with CPU and GPU code optimization.
**What this adds:**
**Core Infrastructure:**
- Main C++ wrapper executable with compiler integration
- CMake build configuration and installation scripts
- Comprehensive CLI interface
**Analysis Engine:**
- YAML optimization records parser with enhanced OpenMP/GPU detection
- Chrome Trace format profiling data parser for runtime analysis
- Comprehensive analysis engine combining static and dynamic optimization insights
- Performance hotspot identification and bottleneck analysis
**Web Visualization Interface:**
- Modern responsive web dashboard with performance overview
- Interactive profiling tab with tracing execution timeline
- Source code viewer with inline optimization remarks
**Key Features:**
- **Session Isolation**: Each compilation uses unique temporary directories preventing data contamination
- **Automatic Runtime Profiling**: Detects and profiles OpenMP programs with GPU offloading
- **Multi-format Support**: Handles both LLVM optimization records (YAML) and runtime profiling data (JSON) (For now)
**Use Cases:**
- Analyzing optimization opportunities in CPU-intensive code
- Profiling and optimizing OpenMP GPU offloading performance
- Understanding compiler optimization decisions and missed opportunities
- Identifying performance bottlenecks in heterogeneous computing workloads
**Integration:**
- Integrates seamlessly with existing LLVM toolchain
- Works with clang, clang++, and other LLVM-based compilers
- Supports standard optimization flags (-O2, -O3, etc.)
- Compatible with existing build systems
This tool addresses the growing need for comprehensive optimization guidance in modern heterogeneous computing environments, particularly for OpenMP and GPU programming workflows.
**Dependencies:**
- Standard LLVM libraries (Support, CommandLine, FileSystem, etc.)
- Python 3 for web server components
- No external dependencies beyond LLVM build requirements
>From 33955fc7198ba6b85a5ce37e330d633d91ba2768 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miguel=20C=C3=A1rdenas?= <miguelecsx at gmail.com>
Date: Tue, 8 Jul 2025 04:41:10 +0200
Subject: [PATCH] [llvm-advisor] create compiler wrapper to obtain remarks and
profiling
- include build configuration and source file
---
llvm/tools/llvm-advisor/CMakeLists.txt | 24 ++
llvm/tools/llvm-advisor/llvm-advisor.cpp | 490 +++++++++++++++++++++++
2 files changed, 514 insertions(+)
create mode 100644 llvm/tools/llvm-advisor/CMakeLists.txt
create mode 100644 llvm/tools/llvm-advisor/llvm-advisor.cpp
diff --git a/llvm/tools/llvm-advisor/CMakeLists.txt b/llvm/tools/llvm-advisor/CMakeLists.txt
new file mode 100644
index 0000000000000..6be215d1cfd8c
--- /dev/null
+++ b/llvm/tools/llvm-advisor/CMakeLists.txt
@@ -0,0 +1,24 @@
+set(LLVM_LINK_COMPONENTS
+ Support
+ )
+
+add_llvm_tool(llvm-advisor
+llvm-advisor.cpp
+)
+
+# Install the Python scripts alongside the binary
+install(DIRECTORY view/
+ DESTINATION ${CMAKE_INSTALL_BINDIR}/../share/llvm-advisor/view
+ FILES_MATCHING
+ PATTERN "*.py"
+ PATTERN "*.html"
+ PATTERN "*.css"
+ PATTERN "*.js"
+ PATTERN "*.yaml"
+ )
+
+# Set executable permissions for Python scripts
+install(CODE "
+ execute_process(COMMAND chmod +x
+ \${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/../share/llvm-advisor/view/cli/main.py)
+")
diff --git a/llvm/tools/llvm-advisor/llvm-advisor.cpp b/llvm/tools/llvm-advisor/llvm-advisor.cpp
new file mode 100644
index 0000000000000..7bdd4ce5b5b7d
--- /dev/null
+++ b/llvm/tools/llvm-advisor/llvm-advisor.cpp
@@ -0,0 +1,490 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// LLVM Advisor Tool - Unified C++ wrapper for LLVM optimization analysis
+// and performance guidance. Provides compiler wrapper, web viewer launcher,
+// and automated profiling capabilities.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorOr.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/Program.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/YAMLTraits.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cstdlib>
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace llvm;
+
+extern char **environ;
+
+namespace {
+
+struct RemarksConfig {
+ std::string tempDir;
+ std::string outputDir;
+ bool verbose = false;
+ bool keepTempFiles = false;
+};
+
+class CompilerWrapper {
+public:
+ CompilerWrapper(const std::string &compiler) : compiler_(compiler) {}
+
+ Expected<int> execute(const std::vector<std::string> &args,
+ RemarksConfig &config);
+
+private:
+ std::string compiler_;
+
+ std::vector<std::string>
+ buildRemarksArgs(const std::vector<std::string> &originalArgs,
+ const RemarksConfig &config);
+ Expected<std::string> createTempDir(const RemarksConfig &config);
+ void cleanup(const std::string &tempDir, bool keepFiles);
+ Expected<int>
+ tryExecuteForProfiling(const std::vector<std::string> &originalArgs,
+ const std::string &tempDir,
+ const RemarksConfig &config);
+ std::string findExecutableFromArgs(const std::vector<std::string> &args);
+ bool looksLikeOpenMPProgram(const std::vector<std::string> &args);
+};
+
+class ViewerLauncher {
+public:
+ static Expected<int> launch(const std::string &remarksDir, int port = 8080);
+
+private:
+ static Expected<std::string> findPythonExecutable();
+ static Expected<std::string> getViewerScript();
+};
+
+class SubcommandHandler {
+public:
+ virtual ~SubcommandHandler() = default;
+ virtual Expected<int> execute(const std::vector<std::string> &args) = 0;
+ virtual const char *getName() const = 0;
+ virtual const char *getDescription() const = 0;
+};
+
+class ViewSubcommand : public SubcommandHandler {
+public:
+ Expected<int> execute(const std::vector<std::string> &args) override;
+ const char *getName() const override { return "view"; }
+ const char *getDescription() const override {
+ return "Compile with remarks and launch web viewer";
+ }
+};
+
+class CompileSubcommand : public SubcommandHandler {
+public:
+ Expected<int> execute(const std::vector<std::string> &args) override;
+ const char *getName() const override { return "compile"; }
+ const char *getDescription() const override {
+ return "Compile with remarks generation";
+ }
+};
+
+} // anonymous namespace
+
+Expected<std::string>
+CompilerWrapper::createTempDir(const RemarksConfig &config) {
+ if (!config.tempDir.empty()) {
+ if (sys::fs::create_directories(config.tempDir)) {
+ return createStringError(
+ std::make_error_code(std::errc::io_error),
+ ("Failed to create temp directory: " + config.tempDir).c_str());
+ }
+ return config.tempDir;
+ }
+
+ SmallString<128> tempDir;
+ if (sys::fs::createUniqueDirectory("llvm-advisor", tempDir)) {
+ return createStringError(std::make_error_code(std::errc::io_error),
+ "Failed to create unique temp directory");
+ }
+
+ return tempDir.str().str();
+}
+
+std::vector<std::string>
+CompilerWrapper::buildRemarksArgs(const std::vector<std::string> &originalArgs,
+ const RemarksConfig &config) {
+
+ std::vector<std::string> newArgs = originalArgs;
+
+ // Add optimization remarks flags
+ newArgs.push_back("-Rpass=.*");
+ newArgs.push_back("-Rpass-missed=.*");
+ newArgs.push_back("-Rpass-analysis=.*");
+
+ // Add YAML output flags - use the newer format
+ newArgs.push_back("-fsave-optimization-record");
+
+ // Set output directory for YAML files in temp directory
+ if (!config.tempDir.empty()) {
+ newArgs.push_back("-foptimization-record-file=" + config.tempDir +
+ "/remarks");
+ }
+
+ return newArgs;
+}
+
+Expected<int> CompilerWrapper::execute(const std::vector<std::string> &args,
+ RemarksConfig &config) {
+ if (args.empty()) {
+ return createStringError(std::make_error_code(std::errc::invalid_argument),
+ "No compiler arguments provided");
+ }
+
+ auto tempDirOrErr = createTempDir(config);
+ if (!tempDirOrErr) {
+ return tempDirOrErr.takeError();
+ }
+
+ std::string tempDir = *tempDirOrErr;
+ config.tempDir = tempDir;
+ config.outputDir = tempDir;
+
+ auto remarksArgs = buildRemarksArgs(args, config);
+
+ // Execute compiler
+ auto compilerPath = sys::findProgramByName(compiler_);
+ if (!compilerPath) {
+ cleanup(tempDir, config.keepTempFiles);
+ return createStringError(
+ std::make_error_code(std::errc::no_such_file_or_directory),
+ ("Compiler not found: " + compiler_).c_str());
+ }
+
+ std::vector<StringRef> execArgs;
+ execArgs.push_back(compiler_);
+ for (const auto &arg : remarksArgs) {
+ execArgs.push_back(arg);
+ }
+
+ int result = sys::ExecuteAndWait(*compilerPath, execArgs);
+
+ if (result != 0) {
+ cleanup(tempDir, config.keepTempFiles);
+ return createStringError(
+ std::make_error_code(std::errc::io_error),
+ ("Compiler execution failed with exit code: " + std::to_string(result))
+ .c_str());
+ }
+
+ // Attempt runtime profiling
+ auto executeResult = tryExecuteForProfiling(args, tempDir, config);
+ if (!executeResult && config.verbose) {
+ outs() << "Warning: " << executeResult.takeError() << "\n";
+ }
+
+ config.outputDir = tempDir;
+ return 0;
+}
+
+Expected<int> CompilerWrapper::tryExecuteForProfiling(
+ const std::vector<std::string> &originalArgs, const std::string &tempDir,
+ const RemarksConfig &config) {
+ std::string executablePath = findExecutableFromArgs(originalArgs);
+ if (executablePath.empty() || !sys::fs::exists(executablePath)) {
+ return createStringError(
+ std::make_error_code(std::errc::no_such_file_or_directory),
+ "Executable not found for profiling");
+ }
+
+ if (!looksLikeOpenMPProgram(originalArgs)) {
+ return createStringError(
+ std::make_error_code(std::errc::operation_not_supported),
+ "Program does not use OpenMP offloading");
+ }
+
+ // Prepare environment with profiling variables
+ std::vector<StringRef> environment;
+
+ // Get current environment variables
+ char **envp = environ;
+ while (*envp) {
+ environment.emplace_back(*envp);
+ ++envp;
+ }
+
+ // Add profiling environment variable
+ std::string profilingEnv = "LIBOMPTARGET_PROFILE=profile.json";
+ environment.emplace_back(profilingEnv);
+
+ // Execute with custom environment
+ std::string execPath = sys::path::is_absolute(executablePath)
+ ? executablePath
+ : "./" + executablePath;
+ std::vector<StringRef> execArgs = {execPath};
+
+ std::optional<StringRef> redirects[] = {std::nullopt, std::nullopt,
+ std::nullopt};
+ int result =
+ sys::ExecuteAndWait(execPath, execArgs, environment, redirects, 10);
+
+ if (result != 0) {
+ return createStringError(std::make_error_code(std::errc::io_error),
+ "Program execution failed");
+ }
+
+ // Move profile.json to temp directory
+ SmallString<128> currentProfile("profile.json");
+ SmallString<128> tempProfile;
+ sys::path::append(tempProfile, tempDir, "profile.json");
+
+ if (sys::fs::exists(currentProfile)) {
+ if (auto err = sys::fs::rename(currentProfile, tempProfile)) {
+ return createStringError(err,
+ "Failed to move profile.json to temp directory");
+ }
+ }
+
+ return 0;
+}
+
+std::string
+CompilerWrapper::findExecutableFromArgs(const std::vector<std::string> &args) {
+ // Look for -o flag to find output executable
+ for (size_t i = 0; i < args.size() - 1; i++) {
+ if (args[i] == "-o") {
+ return args[i + 1];
+ }
+ }
+
+ // Default executable name if no -o specified
+ return "a.out";
+}
+
+bool CompilerWrapper::looksLikeOpenMPProgram(
+ const std::vector<std::string> &args) {
+ // Check compilation flags for OpenMP indicators
+ for (const auto &arg : args) {
+ if (arg.find("-fopenmp") != std::string::npos ||
+ arg.find("-fopenmp-targets") != std::string::npos ||
+ arg.find("-mp") != std::string::npos) { // Intel compiler flag
+ return true;
+ }
+ }
+ return false;
+}
+
+void CompilerWrapper::cleanup(const std::string &tempDir, bool keepFiles) {
+ if (!keepFiles && !tempDir.empty()) {
+ sys::fs::remove_directories(tempDir);
+ }
+}
+
+Expected<std::string> ViewerLauncher::findPythonExecutable() {
+ std::vector<std::string> candidates = {"python3", "python"};
+
+ for (const auto &candidate : candidates) {
+ if (auto path = sys::findProgramByName(candidate)) {
+ return *path;
+ }
+ }
+
+ return createStringError(
+ std::make_error_code(std::errc::no_such_file_or_directory),
+ "Python executable not found");
+}
+
+Expected<std::string> ViewerLauncher::getViewerScript() {
+ SmallString<256> scriptPath;
+
+ // Try to find the script relative to the executable
+ auto mainExecutable = sys::fs::getMainExecutable(nullptr, nullptr);
+ if (mainExecutable.empty()) {
+ return createStringError(
+ std::make_error_code(std::errc::no_such_file_or_directory),
+ "Cannot determine executable path");
+ }
+
+ // First try: relative to binary (development/build tree)
+ sys::path::append(scriptPath, sys::path::parent_path(mainExecutable));
+ sys::path::append(scriptPath, "view");
+ sys::path::append(scriptPath, "cli");
+ sys::path::append(scriptPath, "main.py");
+
+ if (sys::fs::exists(scriptPath)) {
+ return scriptPath.str().str();
+ }
+
+ // Second try: installed location
+ scriptPath.clear();
+ sys::path::append(scriptPath, sys::path::parent_path(mainExecutable));
+ sys::path::append(scriptPath, "..");
+ sys::path::append(scriptPath, "share");
+ sys::path::append(scriptPath, "llvm-advisor");
+ sys::path::append(scriptPath, "view");
+ sys::path::append(scriptPath, "cli");
+ sys::path::append(scriptPath, "main.py");
+
+ if (sys::fs::exists(scriptPath)) {
+ return scriptPath.str().str();
+ }
+
+ return createStringError(
+ std::make_error_code(std::errc::no_such_file_or_directory),
+ "Viewer script not found");
+}
+
+Expected<int> ViewerLauncher::launch(const std::string &remarksDir, int port) {
+ auto pythonOrErr = findPythonExecutable();
+ if (!pythonOrErr) {
+ return pythonOrErr.takeError();
+ }
+
+ auto scriptOrErr = getViewerScript();
+ if (!scriptOrErr) {
+ return scriptOrErr.takeError();
+ }
+
+ // Get current working directory for source files
+ SmallString<128> currentDir;
+ if (sys::fs::current_path(currentDir)) {
+ return createStringError(std::make_error_code(std::errc::io_error),
+ "Failed to get current working directory");
+ }
+
+ std::vector<StringRef> args = {
+ *pythonOrErr, *scriptOrErr, "--directory",
+ remarksDir, "--source-directory", currentDir.str(),
+ "--port", std::to_string(port)};
+
+ outs() << "Launching viewer at http://localhost:" << port << "\n";
+ outs() << "Loading remarks from: " << remarksDir << "\n";
+ outs() << "Source files from: " << currentDir.str() << "\n";
+
+ return sys::ExecuteAndWait(*pythonOrErr, args);
+}
+
+Expected<int> ViewSubcommand::execute(const std::vector<std::string> &args) {
+ if (args.empty()) {
+ return createStringError(
+ std::make_error_code(std::errc::invalid_argument),
+ "Usage: llvm-advisor view <compiler> [compiler-args...]");
+ }
+
+ std::string compiler = args[0];
+ std::vector<std::string> compilerArgs(args.begin() + 1, args.end());
+
+ RemarksConfig config;
+ config.verbose = true;
+ config.keepTempFiles = true; // Keep temp files for viewing
+ config.tempDir = ""; // Will be created automatically
+ config.outputDir = "";
+
+ CompilerWrapper wrapper(compiler);
+ auto compileResult = wrapper.execute(compilerArgs, config);
+ if (!compileResult) {
+ return compileResult.takeError();
+ }
+
+ // Launch viewer with the isolated temporary directory
+ return ViewerLauncher::launch(config.outputDir, 8080);
+}
+
+Expected<int> CompileSubcommand::execute(const std::vector<std::string> &args) {
+ if (args.empty()) {
+ return createStringError(
+ std::make_error_code(std::errc::invalid_argument),
+ "Usage: llvm-advisor compile <compiler> [compiler-args...]");
+ }
+
+ std::string compiler = args[0];
+ std::vector<std::string> compilerArgs(args.begin() + 1, args.end());
+
+ RemarksConfig config;
+ config.verbose = true;
+ config.keepTempFiles = true; // Keep for user inspection
+ config.tempDir = ""; // Will be created automatically
+ config.outputDir = "";
+
+ CompilerWrapper wrapper(compiler);
+ auto result = wrapper.execute(compilerArgs, config);
+
+ if (!result) {
+ return result.takeError();
+ }
+
+ outs() << "Remarks and profile data saved to: " << config.outputDir << "\n";
+ return result;
+}
+
+int main(int argc, char **argv) {
+ // Handle special cases first before LLVM CommandLine parsing
+ if (argc > 1) {
+ std::string firstArg = argv[1];
+ if (firstArg == "--help" || firstArg == "-h") {
+ outs() << "LLVM Advisor Compiler Wrapper\n\n";
+ outs() << "Usage:\n";
+ outs() << " llvm-advisor view <compiler> [compiler-args...] - "
+ "Compile with analysis and launch web advisor\n";
+ outs() << " llvm-advisor compile <compiler> [compiler-args...] - "
+ "Compile with optimization analysis\n";
+ outs() << " llvm-advisor <compiler> [compiler-args...] - Same "
+ "as compile\n\n";
+ outs() << "Examples:\n";
+ outs() << " llvm-advisor view clang -O2 -g -fopenmp main.c\n";
+ outs() << " llvm-advisor compile clang++ -O3 -std=c++17 app.cpp\n";
+ return 0;
+ }
+ }
+
+ // Determine subcommand and split arguments
+ bool isView = false;
+ bool isCompile = false;
+ int startIdx = 1;
+
+ if (argc > 1) {
+ std::string firstArg = argv[1];
+ if (firstArg == "view") {
+ isView = true;
+ startIdx = 2;
+ } else if (firstArg == "compile") {
+ isCompile = true;
+ startIdx = 2;
+ }
+ }
+
+ // Collect remaining arguments for the compiler
+ std::vector<std::string> compilerArgs;
+ for (int i = startIdx; i < argc; ++i) {
+ compilerArgs.push_back(argv[i]);
+ }
+
+ // Create appropriate handler and execute
+ std::unique_ptr<SubcommandHandler> handler;
+
+ if (isView) {
+ handler = std::make_unique<ViewSubcommand>();
+ } else if (isCompile) {
+ handler = std::make_unique<CompileSubcommand>();
+ } else {
+ // Default behavior - treat as compile
+ handler = std::make_unique<CompileSubcommand>();
+ }
+
+ auto result = handler->execute(compilerArgs);
+ if (!result) {
+ errs() << "Error: " << toString(result.takeError()) << "\n";
+ return 1;
+ }
+
+ return *result;
+}
More information about the llvm-commits
mailing list