[llvm] [tools] LLVM Advisor - optimization analysis and performance guidance tool (PR #147451)
Miguel Cárdenas via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 7 19:57:59 PDT 2025
https://github.com/miguelcsx updated https://github.com/llvm/llvm-project/pull/147451
>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 1/2] [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;
+}
>From 0fe7ffb4594fbe0b9e644db9158d158271c38573 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:57:37 +0200
Subject: [PATCH 2/2] [llvm-advisor] Add parser foundation for optimization
analysis
- implement data models and yaml parsing for remarks
---
.../llvm-advisor/view/parser/__init__.py | 14 +
.../llvm-advisor/view/parser/analyzer.py | 765 ++++++++++++++++++
llvm/tools/llvm-advisor/view/parser/models.py | 306 +++++++
.../view/parser/profile_parser.py | 188 +++++
.../llvm-advisor/view/parser/yaml_parser.py | 168 ++++
5 files changed, 1441 insertions(+)
create mode 100644 llvm/tools/llvm-advisor/view/parser/__init__.py
create mode 100644 llvm/tools/llvm-advisor/view/parser/analyzer.py
create mode 100644 llvm/tools/llvm-advisor/view/parser/models.py
create mode 100644 llvm/tools/llvm-advisor/view/parser/profile_parser.py
create mode 100644 llvm/tools/llvm-advisor/view/parser/yaml_parser.py
diff --git a/llvm/tools/llvm-advisor/view/parser/__init__.py b/llvm/tools/llvm-advisor/view/parser/__init__.py
new file mode 100644
index 0000000000000..924d3457f90fe
--- /dev/null
+++ b/llvm/tools/llvm-advisor/view/parser/__init__.py
@@ -0,0 +1,14 @@
+from .yaml_parser import OptimizationRecordParser
+from .models import OptimizationRemark, CompilationUnit, Project, RemarkType, DebugLocation, RemarkArgument
+from .analyzer import RemarkAnalyzer
+
+__all__ = [
+ 'OptimizationRecordParser',
+ 'OptimizationRemark',
+ 'CompilationUnit',
+ 'Project',
+ 'RemarkType',
+ 'DebugLocation',
+ 'RemarkArgument',
+ 'RemarkAnalyzer'
+]
diff --git a/llvm/tools/llvm-advisor/view/parser/analyzer.py b/llvm/tools/llvm-advisor/view/parser/analyzer.py
new file mode 100644
index 0000000000000..4894da04cb3cf
--- /dev/null
+++ b/llvm/tools/llvm-advisor/view/parser/analyzer.py
@@ -0,0 +1,765 @@
+#===----------------------------------------------------------------------===#
+#
+# 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 Analysis Engine - Comprehensive analysis of optimization
+# remarks, profiling data, and performance insights.
+#
+#===----------------------------------------------------------------------===#
+
+from typing import Dict, List, Any, Optional
+from collections import defaultdict, Counter
+from .models import Project, RemarkType, OptimizationRemark
+from .profile_parser import ProfileParser, ProfileData
+
+
+class RemarkAnalyzer:
+ def __init__(self, project: Project, profile_path: Optional[str] = None):
+ self.project = project
+ self.profile_data = None
+ self.raw_trace_data = None
+ if profile_path:
+ parser = ProfileParser(profile_path)
+ self.profile_data = parser.parse()
+ self.raw_trace_data = parser.get_raw_data()
+
+ def analyze_optimization_opportunities(self) -> Dict[str, Any]:
+ missed_by_pass = defaultdict(list)
+
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if remark.remark_type == RemarkType.MISSED:
+ missed_by_pass[remark.pass_name].append(remark)
+
+ opportunities = []
+ for pass_name, remarks in missed_by_pass.items():
+ # Count unique files affected by this pass
+ unique_files = set()
+ for remark in remarks:
+ for unit in self.project.compilation_units:
+ if remark in unit.remarks:
+ unique_files.add(unit.source_file)
+ break
+
+ opportunities.append({
+ "pass": pass_name,
+ "missed_count": len(remarks),
+ "files_affected": len(unique_files),
+ "impact": "high" if len(remarks) > 10 else "medium" if len(remarks) > 5 else "low"
+ })
+
+ return {
+ "optimization_opportunities": sorted(opportunities, key=lambda x: x["missed_count"], reverse=True),
+ "total_missed": sum(len(remarks) for remarks in missed_by_pass.values())
+ }
+
+ def analyze_performance_hotspots(self) -> Dict[str, Any]:
+ function_remarks = defaultdict(list)
+
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ function_remarks[remark.function].append(remark)
+
+ hotspots = []
+ for function, remarks in function_remarks.items():
+ if len(remarks) > 3: # Functions with many remarks are potential hotspots
+ passed = sum(1 for r in remarks if r.remark_type == RemarkType.PASSED)
+ missed = sum(1 for r in remarks if r.remark_type == RemarkType.MISSED)
+
+ hotspots.append({
+ "function": function,
+ "total_remarks": len(remarks),
+ "passed": passed,
+ "missed": missed,
+ "optimization_ratio": passed / len(remarks) if remarks else 0
+ })
+
+ return {
+ "hotspots": sorted(hotspots, key=lambda x: x["total_remarks"], reverse=True)[:10],
+ "total_functions_analyzed": len(function_remarks)
+ }
+
+ def analyze_offloading_efficiency(self) -> Dict[str, Any]:
+ offloading_remarks = []
+
+ for unit in self.project.compilation_units:
+ offloading_remarks.extend(unit.get_offloading_remarks())
+
+ if not offloading_remarks:
+ return {"offloading_remarks": 0, "efficiency": "N/A"}
+
+ passed = sum(1 for r in offloading_remarks if r.remark_type == RemarkType.PASSED)
+ missed = sum(1 for r in offloading_remarks if r.remark_type == RemarkType.MISSED)
+
+ efficiency = passed / len(offloading_remarks) if offloading_remarks else 0
+
+ return {
+ "offloading_remarks": len(offloading_remarks),
+ "passed": passed,
+ "missed": missed,
+ "efficiency": efficiency,
+ "efficiency_rating": "excellent" if efficiency > 0.8 else "good" if efficiency > 0.6 else "needs_improvement"
+ }
+
+ def analyze_profiling_data(self) -> Dict[str, Any]:
+ """Analyze performance-related remarks for profiling insights"""
+ static_analysis = {
+ "loop_analysis": self._analyze_loops(),
+ "vectorization_analysis": self._analyze_vectorization(),
+ "inlining_analysis": self._analyze_inlining(),
+ "memory_analysis": self._analyze_memory_operations(),
+ "kernel_analysis": self._analyze_kernels(),
+ "hotspot_files": self._analyze_file_hotspots()
+ }
+
+ if self.profile_data:
+ runtime_analysis = self._analyze_runtime_profile()
+ static_analysis.update(runtime_analysis)
+
+ return static_analysis
+
+ def analyze_optimization_insights(self) -> Dict[str, Any]:
+ """Detailed optimization analysis for optimization tab"""
+ return {
+ "vectorization_opportunities": self._get_vectorization_opportunities(),
+ "loop_optimization": self._analyze_loop_optimizations(),
+ "function_optimization": self._analyze_function_optimizations(),
+ "memory_optimization": self._analyze_memory_optimizations(),
+ "parallelization_opportunities": self._get_parallelization_opportunities(),
+ "compiler_recommendations": self._generate_compiler_recommendations()
+ }
+
+ def analyze_hardware_insights(self) -> Dict[str, Any]:
+ """Hardware-specific analysis for hardware tab"""
+ return {
+ "gpu_utilization": self._analyze_gpu_utilization(),
+ "memory_hierarchy": self._analyze_memory_hierarchy(),
+ "compute_patterns": self._analyze_compute_patterns(),
+ "offloading_patterns": self._analyze_offloading_patterns(),
+ "architecture_recommendations": self._generate_architecture_recommendations()
+ }
+
+ def _analyze_loops(self) -> Dict[str, Any]:
+ """Analyze loop-related remarks"""
+ loop_remarks = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if any(keyword in remark.get_message().lower() for keyword in ['loop', 'unroll', 'vectorize']):
+ loop_remarks.append(remark)
+
+ loop_stats = Counter([r.pass_name for r in loop_remarks])
+ return {
+ "total_loops": len(loop_remarks),
+ "by_pass": dict(loop_stats),
+ "vectorized": len([r for r in loop_remarks if 'vectorize' in r.pass_name and r.remark_type == RemarkType.PASSED]),
+ "failed_vectorization": len([r for r in loop_remarks if 'vectorize' in r.pass_name and r.remark_type == RemarkType.MISSED])
+ }
+
+ def _analyze_vectorization(self) -> Dict[str, Any]:
+ """Analyze vectorization performance"""
+ vec_remarks = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if 'vectorize' in remark.pass_name.lower():
+ vec_remarks.append(remark)
+
+ successful = [r for r in vec_remarks if r.remark_type == RemarkType.PASSED]
+ failed = [r for r in vec_remarks if r.remark_type == RemarkType.MISSED]
+
+ return {
+ "total_vectorization_attempts": len(vec_remarks),
+ "successful": len(successful),
+ "failed": len(failed),
+ "success_rate": len(successful) / len(vec_remarks) if vec_remarks else 0,
+ "common_failures": self._get_common_failure_reasons(failed)
+ }
+
+ def _analyze_inlining(self) -> Dict[str, Any]:
+ """Analyze function inlining performance"""
+ inline_remarks = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if remark.pass_name == 'inline':
+ inline_remarks.append(remark)
+
+ successful = [r for r in inline_remarks if r.remark_type == RemarkType.PASSED]
+ failed = [r for r in inline_remarks if r.remark_type == RemarkType.MISSED]
+
+ return {
+ "total_inline_attempts": len(inline_remarks),
+ "successful": len(successful),
+ "failed": len(failed),
+ "success_rate": len(successful) / len(inline_remarks) if inline_remarks else 0
+ }
+
+ def _analyze_memory_operations(self) -> Dict[str, Any]:
+ """Analyze memory-related optimizations"""
+ memory_remarks = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ message = remark.get_message().lower()
+ if any(keyword in message for keyword in ['load', 'store', 'memory', 'cache', 'transfer']):
+ memory_remarks.append(remark)
+
+ return {
+ "total_memory_operations": len(memory_remarks),
+ "optimized": len([r for r in memory_remarks if r.remark_type == RemarkType.PASSED]),
+ "missed_optimizations": len([r for r in memory_remarks if r.remark_type == RemarkType.MISSED])
+ }
+
+ def _analyze_kernels(self) -> Dict[str, Any]:
+ """Analyze GPU kernel performance from both remarks and profile data"""
+ kernel_remarks = []
+ kernel_functions = set()
+ target_regions = set()
+
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ # Check multiple indicators for kernel functions
+ is_kernel = (
+ '__omp_offloading_' in remark.function or
+ 'kernel' in remark.function.lower() or
+ remark.is_offloading_related()
+ )
+
+ if is_kernel:
+ kernel_remarks.append(remark)
+
+ # Extract unique kernel functions
+ if '__omp_offloading_' in remark.function:
+ kernel_functions.add(remark.function)
+ elif remark.is_offloading_related():
+ # For OpenMP optimization remarks, create unique identifiers
+ if remark.debug_loc:
+ # Use file:line as unique kernel identifier for target regions
+ kernel_id = f"{remark.debug_loc.file}:{remark.debug_loc.line}"
+ target_regions.add(kernel_id)
+ else:
+ # Fallback to function name
+ kernel_functions.add(remark.function)
+
+ # Count from static analysis of source files for target directives
+ static_kernel_count = 0
+ for unit in self.project.compilation_units:
+ source_lines = unit.get_source_lines(".")
+ if source_lines:
+ for line in source_lines:
+ # Count OpenMP target directives directly from source
+ if '#pragma omp target' in line and not line.strip().startswith('//'):
+ static_kernel_count += 1
+
+ # Get accurate kernel count from profile data if available
+ profile_kernels = 0
+ if self.raw_trace_data and 'traceEvents' in self.raw_trace_data:
+ # Primary method: Get count from "Total Runtime: target exe" summary event
+ summary_events = [e for e in self.raw_trace_data['traceEvents']
+ if (e.get('name', '') == 'Total Runtime: target exe' and
+ e.get('args', {}).get('count') is not None)]
+
+ if summary_events:
+ # Use the count from summary which represents actual kernel executions
+ profile_kernels = summary_events[0]['args']['count']
+ else:
+ # Fallback: Count individual kernel execution events
+ kernel_events = [e for e in self.raw_trace_data['traceEvents']
+ if (e.get('name', '') == 'Runtime: target exe' and
+ e.get('ph') == 'X' and e.get('dur', 0) > 0)]
+ profile_kernels = len(kernel_events)
+
+ # Another fallback: Look for other kernel execution patterns
+ if profile_kernels == 0:
+ target_events = [e for e in self.raw_trace_data['traceEvents']
+ if (e.get('name', '').startswith('Kernel Target') and
+ e.get('ph') == 'X')]
+ profile_kernels = len(target_events)
+
+ elif self.profile_data and hasattr(self.profile_data, 'kernels'):
+ profile_kernels = len(self.profile_data.kernels)
+
+ # Prioritize profile data when available, otherwise use static analysis
+ detected_kernels = profile_kernels if profile_kernels > 0 else max(
+ len(kernel_functions),
+ len(target_regions),
+ static_kernel_count
+ )
+
+ return {
+ "total_kernels": detected_kernels,
+ "total_kernel_remarks": len(kernel_remarks),
+ "optimized_kernels": len([f for f in kernel_functions if any(
+ r.remark_type == RemarkType.PASSED for r in kernel_remarks if r.function == f
+ )]),
+ "profile_detected_kernels": profile_kernels,
+ "remarks_detected_kernels": len(kernel_functions),
+ "target_regions_detected": len(target_regions),
+ "static_analysis_kernels": static_kernel_count
+ }
+
+ def _analyze_file_hotspots(self) -> List[Dict[str, Any]]:
+ """Identify files with most optimization activity"""
+ file_stats = []
+ for unit in self.project.compilation_units:
+ passed = len([r for r in unit.remarks if r.remark_type == RemarkType.PASSED])
+ missed = len([r for r in unit.remarks if r.remark_type == RemarkType.MISSED])
+ total = len(unit.remarks)
+
+ if total > 0:
+ file_stats.append({
+ "file": unit.source_file,
+ "total_remarks": total,
+ "passed": passed,
+ "missed": missed,
+ "optimization_ratio": passed / total,
+ "activity_score": total
+ })
+
+ return sorted(file_stats, key=lambda x: x["activity_score"], reverse=True)[:10]
+
+ def _get_vectorization_opportunities(self) -> List[Dict[str, Any]]:
+ """Identify missed vectorization opportunities"""
+ opportunities = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if ('vectorize' in remark.pass_name.lower() and
+ remark.remark_type == RemarkType.MISSED):
+ opportunities.append({
+ "file": unit.source_file,
+ "line": remark.debug_loc.line if remark.debug_loc else 0,
+ "function": remark.function,
+ "reason": remark.get_message(),
+ "pass": remark.pass_name
+ })
+ return opportunities[:20] # Top 20 opportunities
+
+ def _analyze_loop_optimizations(self) -> Dict[str, Any]:
+ """Analyze loop optimization patterns"""
+ loop_passes = ['loop-vectorize', 'loop-unroll', 'licm', 'loop-idiom']
+ loop_data = {}
+
+ for pass_name in loop_passes:
+ remarks = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if remark.pass_name == pass_name:
+ remarks.append(remark)
+
+ loop_data[pass_name] = {
+ "total": len(remarks),
+ "passed": len([r for r in remarks if r.remark_type == RemarkType.PASSED]),
+ "missed": len([r for r in remarks if r.remark_type == RemarkType.MISSED])
+ }
+
+ return loop_data
+
+ def _analyze_function_optimizations(self) -> Dict[str, Any]:
+ """Analyze function-level optimizations"""
+ function_passes = ['inline', 'dce', 'dse', 'gvn']
+ func_data = {}
+
+ for pass_name in function_passes:
+ remarks = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if remark.pass_name == pass_name:
+ remarks.append(remark)
+
+ func_data[pass_name] = {
+ "total": len(remarks),
+ "passed": len([r for r in remarks if r.remark_type == RemarkType.PASSED]),
+ "missed": len([r for r in remarks if r.remark_type == RemarkType.MISSED])
+ }
+
+ return func_data
+
+ def _analyze_memory_optimizations(self) -> List[Dict[str, Any]]:
+ """Analyze memory optimization opportunities"""
+ memory_issues = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if (remark.remark_type == RemarkType.MISSED and
+ any(keyword in remark.get_message().lower() for keyword in
+ ['load', 'store', 'memory', 'cache', 'clobbered'])):
+ memory_issues.append({
+ "file": unit.source_file,
+ "line": remark.debug_loc.line if remark.debug_loc else 0,
+ "function": remark.function,
+ "issue": remark.get_message(),
+ "pass": remark.pass_name
+ })
+ return memory_issues[:15]
+
+ def _get_parallelization_opportunities(self) -> List[Dict[str, Any]]:
+ """Identify parallelization opportunities"""
+ opportunities = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if (any(keyword in remark.get_message().lower() for keyword in
+ ['parallel', 'thread', 'openmp', 'offload']) and
+ remark.remark_type == RemarkType.MISSED):
+ opportunities.append({
+ "file": unit.source_file,
+ "line": remark.debug_loc.line if remark.debug_loc else 0,
+ "function": remark.function,
+ "opportunity": remark.get_message(),
+ "pass": remark.pass_name
+ })
+ return opportunities[:10]
+
+ def _generate_compiler_recommendations(self) -> List[str]:
+ """Generate compiler optimization recommendations"""
+ recommendations = []
+
+ # Analyze common missed optimizations
+ missed_passes = Counter()
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if remark.remark_type == RemarkType.MISSED:
+ missed_passes[remark.pass_name] += 1
+
+ if missed_passes.get('loop-vectorize', 0) > 10:
+ recommendations.append("Consider using -ffast-math for aggressive vectorization")
+ if missed_passes.get('inline', 0) > 20:
+ recommendations.append("Increase inlining thresholds with -finline-limit")
+ if missed_passes.get('gvn', 0) > 5:
+ recommendations.append("Enable aggressive optimization with -O3")
+
+ return recommendations
+
+ def _analyze_gpu_utilization(self) -> Dict[str, Any]:
+ """Analyze GPU utilization patterns"""
+ gpu_remarks = []
+ for unit in self.project.compilation_units:
+ gpu_remarks.extend(unit.get_offloading_remarks())
+
+ kernel_functions = set(r.function for r in gpu_remarks if '__omp_offloading_' in r.function)
+ return {
+ "total_gpu_functions": len(kernel_functions),
+ "optimization_coverage": len([r for r in gpu_remarks if r.remark_type == RemarkType.PASSED]) / len(gpu_remarks) if gpu_remarks else 0,
+ "offloading_efficiency": self.analyze_offloading_efficiency()
+ }
+
+ def _analyze_memory_hierarchy(self) -> Dict[str, Any]:
+ """Analyze memory access patterns for GPU"""
+ memory_remarks = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if (remark.is_offloading_related() and
+ any(keyword in remark.get_message().lower() for keyword in
+ ['memory', 'load', 'store', 'cache', 'shared', 'global'])):
+ memory_remarks.append(remark)
+
+ return {
+ "memory_operations": len(memory_remarks),
+ "optimized_memory": len([r for r in memory_remarks if r.remark_type == RemarkType.PASSED]),
+ "memory_issues": len([r for r in memory_remarks if r.remark_type == RemarkType.MISSED])
+ }
+
+ def _analyze_compute_patterns(self) -> Dict[str, Any]:
+ """Analyze compute utilization patterns"""
+ compute_remarks = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if (remark.is_offloading_related() and
+ any(keyword in remark.get_message().lower() for keyword in
+ ['compute', 'thread', 'warp', 'simd', 'vector'])):
+ compute_remarks.append(remark)
+
+ return {
+ "compute_operations": len(compute_remarks),
+ "compute_efficiency": len([r for r in compute_remarks if r.remark_type == RemarkType.PASSED]) / len(compute_remarks) if compute_remarks else 0
+ }
+
+ def _analyze_offloading_patterns(self) -> Dict[str, Any]:
+ """Analyze offloading patterns and effectiveness"""
+ offload_remarks = []
+ for unit in self.project.compilation_units:
+ offload_remarks.extend(unit.get_offloading_remarks())
+
+ offload_functions = Counter(r.function for r in offload_remarks if '__omp_offloading_' in r.function)
+ return {
+ "offloaded_functions": len(offload_functions),
+ "most_active_kernels": dict(offload_functions.most_common(5)),
+ "offloading_success_rate": len([r for r in offload_remarks if r.remark_type == RemarkType.PASSED]) / len(offload_remarks) if offload_remarks else 0
+ }
+
+ def _generate_architecture_recommendations(self) -> List[str]:
+ """Generate architecture-specific recommendations"""
+ recommendations = []
+
+ offload_remarks = []
+ for unit in self.project.compilation_units:
+ offload_remarks.extend(unit.get_offloading_remarks())
+
+ if len(offload_remarks) > 0:
+ efficiency = len([r for r in offload_remarks if r.remark_type == RemarkType.PASSED]) / len(offload_remarks)
+ if efficiency < 0.7:
+ recommendations.append("Consider optimizing GPU memory access patterns")
+ recommendations.append("Review data transfer between host and device")
+
+ vectorization_remarks = []
+ for unit in self.project.compilation_units:
+ for remark in unit.remarks:
+ if 'vectorize' in remark.pass_name:
+ vectorization_remarks.append(remark)
+
+ if len(vectorization_remarks) > 0:
+ vec_efficiency = len([r for r in vectorization_remarks if r.remark_type == RemarkType.PASSED]) / len(vectorization_remarks)
+ if vec_efficiency < 0.5:
+ recommendations.append("Consider SIMD-friendly data structures")
+
+ return recommendations
+
+ def _get_common_failure_reasons(self, failed_remarks: List[OptimizationRemark]) -> List[str]:
+ """Extract common failure reasons from remarks"""
+ reasons = Counter()
+ for remark in failed_remarks:
+ message = remark.get_message().lower()
+ if 'uncountable' in message:
+ reasons['Uncountable loops'] += 1
+ elif 'definition unavailable' in message:
+ reasons['Function definition unavailable'] += 1
+ elif 'clobbered' in message:
+ reasons['Memory dependencies'] += 1
+ elif 'impossible' in message:
+ reasons['Vectorization impossible'] += 1
+ else:
+ reasons['Other'] += 1
+
+ return [f"{reason}: {count}" for reason, count in reasons.most_common(5)]
+
+ def _analyze_runtime_profile(self) -> Dict[str, Any]:
+ """Analyze runtime profiling data from LIBOMPTARGET_PROFILE"""
+ if not self.profile_data and not self.raw_trace_data:
+ return {}
+
+ # Prioritize raw Chrome Trace format data
+ if self.raw_trace_data:
+ return {
+ "trace_data": self.raw_trace_data,
+ "performance_bottlenecks": self._identify_trace_bottlenecks(self.raw_trace_data),
+ "optimization_recommendations": self._generate_trace_recommendations(self.raw_trace_data)
+ }
+
+ # Handle Chrome Trace format from profile_data
+ if hasattr(self.profile_data, 'trace_events') or isinstance(self.profile_data, dict):
+ trace_data = self.profile_data if isinstance(self.profile_data, dict) else self.profile_data.__dict__
+
+ return {
+ "trace_data": trace_data,
+ "performance_bottlenecks": self._identify_trace_bottlenecks(trace_data),
+ "optimization_recommendations": self._generate_trace_recommendations(trace_data)
+ }
+
+ # Fallback to original ProfileData structure (if implemented)
+ if self.profile_data and hasattr(self.profile_data, 'total_time'):
+ return {
+ "runtime_performance": {
+ "total_execution_time_us": self.profile_data.total_time,
+ "device_time_us": self.profile_data.device_time,
+ "host_time_us": self.profile_data.host_time,
+ "memory_transfer_time_us": self.profile_data.memory_transfer_time,
+ "device_utilization_percent": (self.profile_data.device_time / self.profile_data.total_time * 100) if self.profile_data.total_time > 0 else 0
+ },
+ "kernel_performance": [
+ {
+ "name": kernel.name,
+ "execution_time_us": kernel.execution_time,
+ "launch_time_us": kernel.launch_time,
+ "device_id": kernel.device_id,
+ "grid_size": kernel.grid_size,
+ "block_size": kernel.block_size
+ } for kernel in self.profile_data.kernels
+ ],
+ "performance_bottlenecks": self._identify_performance_bottlenecks(),
+ "optimization_recommendations": self._generate_runtime_recommendations()
+ }
+
+ return {}
+
+ def _identify_trace_bottlenecks(self, trace_data: Dict[str, Any]) -> List[Dict[str, Any]]:
+ """Identify performance bottlenecks from Chrome Trace format data"""
+ if not trace_data or 'traceEvents' not in trace_data:
+ return []
+
+ bottlenecks = []
+ events = [e for e in trace_data['traceEvents'] if e.get('ph') == 'X' and e.get('dur')]
+
+ if not events:
+ return bottlenecks
+
+ # Calculate time distributions
+ kernel_events = [e for e in events if 'target exe' in e.get('name', '')]
+ memory_events = [e for e in events if any(term in e.get('name', '') for term in ['HostToDev', 'DevToHost'])]
+ init_events = [e for e in events if 'init' in e.get('name', '').lower()]
+
+ total_time = max(e['ts'] + e['dur'] for e in events) - min(e['ts'] for e in events)
+ kernel_time = sum(e['dur'] for e in kernel_events)
+ memory_time = sum(e['dur'] for e in memory_events)
+ init_time = sum(e['dur'] for e in init_events)
+
+ # Memory transfer bottleneck
+ if memory_time > kernel_time * 0.5:
+ bottlenecks.append({
+ "type": "memory_transfer",
+ "severity": "high",
+ "description": "Memory transfers take significant time compared to kernel execution",
+ "impact_percent": (memory_time / total_time) * 100
+ })
+
+ # Initialization overhead
+ if init_time > total_time * 0.3:
+ bottlenecks.append({
+ "type": "initialization_overhead",
+ "severity": "medium",
+ "description": "Initialization takes significant portion of total execution time",
+ "impact_percent": (init_time / total_time) * 100
+ })
+
+ # Kernel utilization
+ kernel_util = (kernel_time / total_time) * 100 if total_time > 0 else 0
+ if kernel_util < 30:
+ bottlenecks.append({
+ "type": "low_kernel_utilization",
+ "severity": "medium",
+ "description": f"Kernel utilization is only {kernel_util:.1f}%",
+ "impact_percent": 100 - kernel_util
+ })
+
+ # Kernel execution imbalance
+ if len(kernel_events) > 1:
+ durations = [e['dur'] for e in kernel_events]
+ max_dur = max(durations)
+ min_dur = min(durations)
+ if max_dur > min_dur * 3: # 3x difference indicates imbalance
+ bottlenecks.append({
+ "type": "kernel_imbalance",
+ "severity": "low",
+ "description": "Significant execution time variance between kernel launches",
+ "impact_percent": ((max_dur - min_dur) / max_dur) * 100
+ })
+
+ return bottlenecks
+
+ def _generate_trace_recommendations(self, trace_data: Dict[str, Any]) -> List[str]:
+ """Generate optimization recommendations based on Chrome Trace format data"""
+ if not trace_data or 'traceEvents' not in trace_data:
+ return []
+
+ recommendations = []
+ events = [e for e in trace_data['traceEvents'] if e.get('ph') == 'X' and e.get('dur')]
+
+ if not events:
+ return recommendations
+
+ kernel_events = [e for e in events if 'target exe' in e.get('name', '')]
+ memory_events = [e for e in events if any(term in e.get('name', '') for term in ['HostToDev', 'DevToHost'])]
+
+ total_time = max(e['ts'] + e['dur'] for e in events) - min(e['ts'] for e in events)
+ kernel_time = sum(e['dur'] for e in kernel_events)
+ memory_time = sum(e['dur'] for e in memory_events)
+
+ # Memory transfer recommendations
+ if memory_time > kernel_time * 0.3:
+ recommendations.append("Consider optimizing data transfers - use unified memory or asynchronous transfers")
+ recommendations.append("Reduce data movement by keeping data on device between kernel launches")
+
+ # Kernel execution recommendations
+ if len(kernel_events) > 5:
+ avg_kernel_time = kernel_time / len(kernel_events)
+ if avg_kernel_time < 100: # Very short kernels (< 100μs)
+ recommendations.append("Consider kernel fusion to reduce launch overhead")
+
+ # Device utilization recommendations
+ kernel_util = (kernel_time / total_time) * 100 if total_time > 0 else 0
+ if kernel_util < 50:
+ recommendations.append("Increase workload size or use multiple devices to improve utilization")
+ recommendations.append("Consider overlapping computation with data transfers")
+
+ # Specific kernel analysis
+ if kernel_events:
+ for event in kernel_events:
+ detail = event.get('args', {}).get('detail', '')
+ if 'NumTeams=0' in detail:
+ recommendations.append("Some kernels launch with NumTeams=0 - verify OpenMP target directives")
+
+ return recommendations
+
+ def _identify_performance_bottlenecks(self) -> List[Dict[str, Any]]:
+ """Identify performance bottlenecks from runtime data"""
+ if not self.profile_data:
+ return []
+
+ bottlenecks = []
+
+ # Memory transfer bottleneck
+ if self.profile_data.memory_transfer_time > self.profile_data.device_time * 0.5:
+ bottlenecks.append({
+ "type": "memory_transfer",
+ "severity": "high",
+ "description": "Memory transfers take significant time compared to computation",
+ "impact_percent": (self.profile_data.memory_transfer_time / self.profile_data.total_time) * 100
+ })
+
+ # Low device utilization
+ device_util = (self.profile_data.device_time / self.profile_data.total_time) * 100 if self.profile_data.total_time > 0 else 0
+ if device_util < 50:
+ bottlenecks.append({
+ "type": "low_device_utilization",
+ "severity": "medium",
+ "description": f"Device utilization is only {device_util:.1f}%",
+ "impact_percent": 100 - device_util
+ })
+
+ # Kernel execution imbalance
+ if len(self.profile_data.kernels) > 1:
+ execution_times = [k.execution_time for k in self.profile_data.kernels]
+ max_time = max(execution_times)
+ min_time = min(execution_times)
+ if max_time > min_time * 5: # 5x difference indicates imbalance
+ bottlenecks.append({
+ "type": "kernel_imbalance",
+ "severity": "medium",
+ "description": "Significant execution time variance between kernels",
+ "impact_percent": ((max_time - min_time) / max_time) * 100
+ })
+
+ return bottlenecks
+
+ def _generate_runtime_recommendations(self) -> List[str]:
+ """Generate optimization recommendations based on runtime data"""
+ if not self.profile_data:
+ return []
+
+ recommendations = []
+
+ # Memory transfer optimization
+ if self.profile_data.memory_transfer_time > self.profile_data.device_time * 0.3:
+ recommendations.append("Consider data layout optimizations to reduce memory transfers")
+ recommendations.append("Use unified memory or async transfers where possible")
+
+ # Device utilization
+ device_util = (self.profile_data.device_time / self.profile_data.total_time) * 100 if self.profile_data.total_time > 0 else 0
+ if device_util < 70:
+ recommendations.append("Increase workload size or use multiple kernels to improve device utilization")
+
+ # Kernel optimization
+ for kernel in self.profile_data.kernels:
+ if kernel.execution_time < 1000: # Less than 1ms
+ recommendations.append(f"Kernel '{kernel.name}' has very short execution time - consider kernel fusion")
+
+ return recommendations
+
+ def get_comprehensive_analysis(self) -> Dict[str, Any]:
+ return {
+ "project_summary": self.project.get_project_summary(),
+ "optimization_opportunities": self.analyze_optimization_opportunities(),
+ "performance_hotspots": self.analyze_performance_hotspots(),
+ "offloading_efficiency": self.analyze_offloading_efficiency(),
+ "profiling_data": self.analyze_profiling_data(),
+ "optimization_insights": self.analyze_optimization_insights(),
+ "hardware_insights": self.analyze_hardware_insights()
+ }
diff --git a/llvm/tools/llvm-advisor/view/parser/models.py b/llvm/tools/llvm-advisor/view/parser/models.py
new file mode 100644
index 0000000000000..4fdbe309bf51f
--- /dev/null
+++ b/llvm/tools/llvm-advisor/view/parser/models.py
@@ -0,0 +1,306 @@
+#===----------------------------------------------------------------------===#
+#
+# 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 Data Models - Structured data models for optimization
+# analysis, remarks, and profiling information.
+#
+#===----------------------------------------------------------------------===#
+
+from dataclasses import dataclass, field
+from typing import Dict, List, Optional, Any, Union
+from enum import Enum
+from pathlib import Path
+
+
+class RemarkType(Enum):
+ PASSED = "Passed"
+ MISSED = "Missed"
+ ANALYSIS = "Analysis"
+
+
+ at dataclass
+class DebugLocation:
+ file: str
+ line: int
+ column: int
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> Optional['DebugLocation']:
+ return cls(
+ file=data.get('File', '<unknown>'),
+ line=int(data.get('Line', 0)),
+ column=int(data.get('Column', 0))
+ ) if data else None
+
+
+ at dataclass
+class RemarkArgument:
+ type: str
+ value: Any
+ debug_loc: Optional[DebugLocation] = None
+
+ @classmethod
+ def from_dict(cls, data: Union[str, Dict[str, Any]]) -> 'RemarkArgument':
+ if isinstance(data, str):
+ return cls(type="String", value=data)
+
+ # Consolidated argument type mapping
+ type_mappings = {
+ # Basic types
+ "String": "String",
+ "DebugLoc": None, # Special handling
+ # Function/code references
+ **{key: key for key in ["Callee", "Caller", "Function", "BasicBlock", "Inst",
+ "Call", "OtherAccess", "ClobberedBy", "InfavorOfValue", "Reason"]},
+ # Metrics and counts
+ **{key: key for key in ["Type", "Cost", "Threshold", "VectorizationFactor",
+ "InterleaveCount", "NumVRCopies", "TotalCopiesCost",
+ "NumStackBytes", "NumInstructions", "Line", "Column"]},
+ # OpenMP/GPU specific
+ **{key: key for key in ["ExternalNotKernel", "GlobalsSize", "LocalVarSize",
+ "NumRegions", "RegisterPressurerValue", "AssumedAddressSpace",
+ "SPMDCompatibilityTracker", "GlobalizationLevel", "AddressSpace"]}
+ }
+
+ arg_type = None
+ value = None
+ debug_loc = None
+
+ for key, val in data.items():
+ if key == "DebugLoc":
+ debug_loc = DebugLocation.from_dict(val)
+ elif key in type_mappings:
+ arg_type = type_mappings[key]
+ value = val
+ break
+
+ return cls(type=arg_type or "Unknown", value=value, debug_loc=debug_loc)
+
+
+ at dataclass
+class OptimizationRemark:
+ remark_type: RemarkType
+ pass_name: str
+ remark_name: str
+ function: str
+ debug_loc: Optional[DebugLocation]
+ args: List[RemarkArgument]
+
+ @classmethod
+ def from_dict(cls, data: Dict[str, Any]) -> 'OptimizationRemark':
+ remark_type = {
+ 'Passed': RemarkType.PASSED,
+ 'Missed': RemarkType.MISSED,
+ 'Analysis': RemarkType.ANALYSIS
+ }.get(data.get('_remark_type'), RemarkType.MISSED)
+
+ return cls(
+ remark_type=remark_type,
+ pass_name=data.get('Pass', 'unknown'),
+ remark_name=data.get('Name', 'unknown'),
+ function=data.get('Function', 'unknown'),
+ debug_loc=DebugLocation.from_dict(data.get('DebugLoc', {})),
+ args=[RemarkArgument.from_dict(arg) for arg in data.get('Args', [])]
+ )
+
+ def get_message(self) -> str:
+ parts = []
+ for arg in self.args:
+ if arg.type == "String":
+ parts.append(str(arg.value))
+ else:
+ parts.append(f"[{arg.type}: {arg.value}]")
+ return "".join(parts)
+
+ def is_offloading_related(self) -> bool:
+ """Enhanced detection of OpenMP offloading and GPU-related remarks"""
+ offloading_indicators = [
+ "__omp_offloading", "omp_outlined", "target", "kernel-info", "offload",
+ "gpu", "cuda", "opencl", "sycl", "hip", "device", "host",
+ "ExternalNotKernel", "SPMDCompatibilityTracker", "GlobalizationLevel",
+ "NumRegions", "AddressSpace", "AssumedAddressSpace"
+ ]
+
+ # OpenMP-specific pass names that indicate offloading
+ openmp_passes = [
+ "openmp-opt", "kernel-info", "openmp", "target-region",
+ "offload", "device-lower", "gpu-lower"
+ ]
+
+ # Check function name, pass name, and remark name
+ text_to_check = f"{self.function} {self.pass_name} {self.remark_name}".lower()
+ if any(indicator in text_to_check for indicator in offloading_indicators):
+ return True
+
+ # Check if this is an OpenMP-related pass
+ if any(omp_pass in self.pass_name.lower() for omp_pass in openmp_passes):
+ return True
+
+ # Check argument values for GPU/offloading specific content
+ for arg in self.args:
+ if hasattr(arg, 'value') and arg.value:
+ arg_text = str(arg.value).lower()
+ if any(indicator in arg_text for indicator in offloading_indicators):
+ return True
+
+ # Check for specific OpenMP argument types
+ if hasattr(arg, 'type') and arg.type in [
+ "ExternalNotKernel", "SPMDCompatibilityTracker", "GlobalizationLevel",
+ "NumRegions", "AddressSpace", "AssumedAddressSpace"
+ ]:
+ return True
+
+ # Check message content for OpenMP target-related keywords
+ message = self.get_message().lower()
+ target_keywords = [
+ "target region", "target directive", "offload", "device",
+ "kernel", "gpu", "accelerator", "teams", "thread_limit"
+ ]
+ if any(keyword in message for keyword in target_keywords):
+ return True
+
+ return False
+
+
+ at dataclass
+class CompilationUnit:
+ source_file: str
+ remarks: List[OptimizationRemark] = field(default_factory=list)
+ build_time: Optional[float] = None
+ optimization_level: Optional[str] = None
+ target_arch: Optional[str] = None
+
+ def get_remarks_by_pass(self) -> Dict[str, List[OptimizationRemark]]:
+ result = {}
+ for remark in self.remarks:
+ result.setdefault(remark.pass_name, []).append(remark)
+ return result
+
+ def get_remarks_by_type(self) -> Dict[RemarkType, List[OptimizationRemark]]:
+ result = {t: [] for t in RemarkType}
+ for remark in self.remarks:
+ result[remark.remark_type].append(remark)
+ return result
+
+ def get_offloading_remarks(self) -> List[OptimizationRemark]:
+ return [r for r in self.remarks if r.is_offloading_related()]
+
+ def get_summary(self) -> Dict[str, Any]:
+ by_type = self.get_remarks_by_type()
+ return {
+ "source_file": self.source_file,
+ "total_remarks": len(self.remarks),
+ "passed": len(by_type[RemarkType.PASSED]),
+ "missed": len(by_type[RemarkType.MISSED]),
+ "analysis": len(by_type[RemarkType.ANALYSIS]),
+ "offloading_remarks": len(self.get_offloading_remarks()),
+ "passes_involved": list(self.get_remarks_by_pass().keys()),
+ "build_time": self.build_time,
+ "optimization_level": self.optimization_level,
+ "target_arch": self.target_arch
+ }
+
+ def deduplicate_remarks(self):
+ """Remove duplicate remarks based on key attributes."""
+ seen = set()
+ unique_remarks = []
+
+ for remark in self.remarks:
+ # Create a key based on remark attributes
+ key = (
+ remark.remark_type,
+ remark.pass_name,
+ remark.remark_name,
+ remark.function,
+ remark.debug_loc.line if remark.debug_loc else 0,
+ remark.debug_loc.column if remark.debug_loc else 0
+ )
+
+ if key not in seen:
+ seen.add(key)
+ unique_remarks.append(remark)
+
+ self.remarks = unique_remarks
+
+ def load_source_code(self, source_directory: str = ".") -> Optional[str]:
+ """Load the actual source code for this file."""
+ possible_paths = [
+ Path(source_directory) / self.source_file,
+ Path(source_directory) / "src" / self.source_file,
+ Path(source_directory) / "source" / self.source_file,
+ ]
+
+ for path in possible_paths:
+ if path.exists() and path.is_file():
+ try:
+ with open(path, 'r', encoding='utf-8') as f:
+ return f.read()
+ except Exception:
+ continue
+ return None
+
+ def get_source_lines(self, source_directory: str = ".") -> List[str]:
+ """Get source code as list of lines."""
+ source_code = self.load_source_code(source_directory)
+ return source_code.split('\n') if source_code else []
+
+
+ at dataclass
+class Project:
+ name: str
+ compilation_units: List[CompilationUnit] = field(default_factory=list)
+ build_system: Optional[str] = None
+
+ def add_compilation_unit(self, unit: CompilationUnit):
+ self.compilation_units.append(unit)
+
+ def get_project_summary(self) -> Dict[str, Any]:
+ total_remarks = sum(len(unit.remarks) for unit in self.compilation_units)
+ total_offloading = sum(len(unit.get_offloading_remarks()) for unit in self.compilation_units)
+ all_passes = set()
+
+ # Collect type counts across all units
+ type_counts = {remark_type: 0 for remark_type in RemarkType}
+ pass_counts = {}
+
+ for unit in self.compilation_units:
+ all_passes.update(unit.get_remarks_by_pass().keys())
+ by_type = unit.get_remarks_by_type()
+ for remark_type, remarks in by_type.items():
+ type_counts[remark_type] += len(remarks)
+
+ for remark in unit.remarks:
+ pass_counts[remark.pass_name] = pass_counts.get(remark.pass_name, 0) + 1
+
+ return {
+ "project_name": self.name,
+ "compilation_units": len(self.compilation_units),
+ "total_files": len(self.compilation_units), # Added for frontend compatibility
+ "total_remarks": total_remarks,
+ "total_offloading_remarks": total_offloading,
+ "total_passed": type_counts[RemarkType.PASSED],
+ "total_missed": type_counts[RemarkType.MISSED],
+ "total_analysis": type_counts[RemarkType.ANALYSIS],
+ "unique_passes": list(all_passes),
+ "pass_counts": pass_counts,
+ "most_active_passes": sorted(pass_counts.items(), key=lambda x: x[1], reverse=True)[:10],
+ "build_system": self.build_system
+ }
+
+ def get_file_list(self) -> List[Dict[str, Any]]:
+ files = []
+ for unit in self.compilation_units:
+ summary = unit.get_summary()
+ files.append({
+ "file": unit.source_file,
+ "remarks": summary["total_remarks"],
+ "passed": summary["passed"],
+ "missed": summary["missed"],
+ "offloading": summary["offloading_remarks"]
+ })
+ return files
diff --git a/llvm/tools/llvm-advisor/view/parser/profile_parser.py b/llvm/tools/llvm-advisor/view/parser/profile_parser.py
new file mode 100644
index 0000000000000..a5e2db91568e2
--- /dev/null
+++ b/llvm/tools/llvm-advisor/view/parser/profile_parser.py
@@ -0,0 +1,188 @@
+#===----------------------------------------------------------------------===#
+#
+# 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 Profile Parser - Parses Chrome Trace format profiling
+# data generated by LLVM OpenMP runtime and other profiling tools.
+#
+#===----------------------------------------------------------------------===#
+
+import json
+import os
+from typing import Dict, List, Optional, Any
+from dataclasses import dataclass
+from pathlib import Path
+
+ at dataclass
+class ProfileEvent:
+ name: str
+ category: str
+ phase: str
+ timestamp: float
+ duration: float
+ process_id: int
+ thread_id: int
+ args: Dict[str, Any]
+
+ at dataclass
+class KernelProfile:
+ name: str
+ launch_time: float
+ execution_time: float
+ memory_transfers: List[Dict[str, Any]]
+ device_id: int
+ grid_size: Optional[tuple] = None
+ block_size: Optional[tuple] = None
+
+ at dataclass
+class ProfileData:
+ events: List[ProfileEvent]
+ kernels: List[KernelProfile]
+ total_time: float
+ device_time: float
+ host_time: float
+ memory_transfer_time: float
+
+class ProfileParser:
+ def __init__(self, profile_path: str):
+ self.profile_path = Path(profile_path)
+ self.profile_data: Optional[ProfileData] = None
+
+ def parse(self) -> Optional[ProfileData]:
+ if not self.profile_path.exists():
+ return None
+
+ try:
+ with open(self.profile_path, 'r') as f:
+ raw_data = json.load(f)
+
+ # Store raw data for Chrome Trace format compatibility
+ self.raw_data = raw_data
+
+ events = self._parse_events(raw_data.get('traceEvents', []))
+ kernels = self._extract_kernels(events)
+
+ self.profile_data = ProfileData(
+ events=events,
+ kernels=kernels,
+ total_time=self._calculate_total_time(events),
+ device_time=self._calculate_device_time(events),
+ host_time=self._calculate_host_time(events),
+ memory_transfer_time=self._calculate_memory_transfer_time(events)
+ )
+
+ return self.profile_data
+
+ except (json.JSONDecodeError, KeyError, IOError) as e:
+ print(f"Error parsing profile file: {e}")
+ return None
+
+ def get_raw_data(self) -> Optional[Dict[str, Any]]:
+ """Return raw Chrome Trace format data for frontend processing"""
+ return getattr(self, 'raw_data', None)
+
+ def _parse_events(self, trace_events: List[Dict]) -> List[ProfileEvent]:
+ events = []
+ for event in trace_events:
+ try:
+ events.append(ProfileEvent(
+ name=event.get('name', ''),
+ category=event.get('cat', ''),
+ phase=event.get('ph', ''),
+ timestamp=float(event.get('ts', 0)),
+ duration=float(event.get('dur', 0)),
+ process_id=int(event.get('pid', 0)),
+ thread_id=int(event.get('tid', 0)),
+ args=event.get('args', {})
+ ))
+ except (ValueError, TypeError):
+ continue
+ return events
+
+ def _extract_kernels(self, events: List[ProfileEvent]) -> List[KernelProfile]:
+ kernels = []
+ # Look for OpenMP target execution events
+ kernel_events = [e for e in events if 'target exe' in e.name or 'Runtime: target exe' in e.name]
+
+ for event in kernel_events:
+ if event.phase == 'X': # Complete event
+ # Parse kernel details from args.detail
+ detail = event.args.get('detail', '')
+ parts = detail.split(';') if detail else []
+
+ kernel = KernelProfile(
+ name=event.name,
+ launch_time=event.timestamp,
+ execution_time=event.duration,
+ memory_transfers=[],
+ device_id=event.args.get('device_id', 0),
+ grid_size=self._parse_grid_size_from_detail(parts),
+ block_size=self._parse_block_size_from_detail(parts)
+ )
+ kernels.append(kernel)
+
+ return kernels
+
+ def _parse_grid_size_from_detail(self, parts: List[str]) -> Optional[tuple]:
+ # Extract grid size from OpenMP detail string if available
+ for part in parts:
+ if 'NumTeams=' in part:
+ try:
+ teams = int(part.split('=')[1])
+ return (teams,) if teams > 0 else None
+ except (ValueError, IndexError):
+ pass
+ return None
+
+ def _parse_block_size_from_detail(self, parts: List[str]) -> Optional[tuple]:
+ # Extract block size from OpenMP detail string if available
+ # OpenMP doesn't directly map to CUDA block sizes, but we can use thread info if available
+ return None # Not available in OpenMP trace format
+
+ def _parse_grid_size(self, args: Dict) -> Optional[tuple]:
+ if 'grid_size' in args:
+ return tuple(args['grid_size'])
+ return None
+
+ def _parse_block_size(self, args: Dict) -> Optional[tuple]:
+ if 'block_size' in args:
+ return tuple(args['block_size'])
+ return None
+
+ def _calculate_total_time(self, events: List[ProfileEvent]) -> float:
+ if not events:
+ return 0.0
+ start_time = min(e.timestamp for e in events)
+ end_time = max(e.timestamp + e.duration for e in events if e.duration > 0)
+ return end_time - start_time
+
+ def _calculate_device_time(self, events: List[ProfileEvent]) -> float:
+ # For OpenMP, consider target execution as device time
+ return sum(e.duration for e in events if 'target exe' in e.name or 'kernel' in e.name.lower())
+
+ def _calculate_host_time(self, events: List[ProfileEvent]) -> float:
+ # Host time is everything that's not device execution
+ device_time = self._calculate_device_time(events)
+ total_time = self._calculate_total_time(events)
+ return total_time - device_time
+
+ def _calculate_memory_transfer_time(self, events: List[ProfileEvent]) -> float:
+ return sum(e.duration for e in events if 'HostToDev' in e.name or 'DevToHost' in e.name)
+
+ def get_summary(self) -> Dict[str, Any]:
+ if not self.profile_data:
+ return {}
+
+ return {
+ 'total_kernels': len(self.profile_data.kernels),
+ 'total_time_us': self.profile_data.total_time,
+ 'device_time_us': self.profile_data.device_time,
+ 'host_time_us': self.profile_data.host_time,
+ 'memory_transfer_time_us': self.profile_data.memory_transfer_time,
+ 'device_utilization': (self.profile_data.device_time / self.profile_data.total_time * 100) if self.profile_data.total_time > 0 else 0,
+ 'top_kernels': sorted(self.profile_data.kernels, key=lambda k: k.execution_time, reverse=True)[:10]
+ }
\ No newline at end of file
diff --git a/llvm/tools/llvm-advisor/view/parser/yaml_parser.py b/llvm/tools/llvm-advisor/view/parser/yaml_parser.py
new file mode 100644
index 0000000000000..05cd2cfd692ae
--- /dev/null
+++ b/llvm/tools/llvm-advisor/view/parser/yaml_parser.py
@@ -0,0 +1,168 @@
+#===----------------------------------------------------------------------===#
+#
+# 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 Parser - Parses YAML optimization data and profiling
+# files generated by LLVM and creates structured data models.
+#
+#===----------------------------------------------------------------------===#
+
+import yaml
+import glob
+import json
+from typing import List, Dict, Any, Optional
+from pathlib import Path
+from .models import OptimizationRemark, CompilationUnit, Project, RemarkType, DebugLocation
+
+
+class YAMLLoader(yaml.SafeLoader):
+ pass
+
+
+def construct_remark(tag):
+ def constructor(loader, node):
+ value = loader.construct_mapping(node)
+ value['_remark_type'] = tag
+ return value
+ return constructor
+
+
+for tag in ['Passed', 'Missed', 'Analysis']:
+ YAMLLoader.add_constructor(f'!{tag}', construct_remark(tag))
+
+
+class OptimizationRecordParser:
+ def __init__(self):
+ self.remark_type_map = {
+ 'Passed': RemarkType.PASSED,
+ 'Missed': RemarkType.MISSED,
+ 'Analysis': RemarkType.ANALYSIS
+ }
+ self.source_extensions = {'c', 'cpp', 'cc', 'cxx', 'cu', 'f', 'f90', 'f95'}
+
+ def parse_yaml_file(self, yaml_path: str) -> List[OptimizationRemark]:
+ """Parse a single YAML file and return list of remarks"""
+ remarks = []
+ try:
+ with open(yaml_path, 'r', encoding='utf-8') as f:
+ documents = yaml.load_all(f, Loader=YAMLLoader)
+
+ for doc in documents:
+ if not doc:
+ continue
+
+ remark_type = self.remark_type_map.get(
+ doc.get('_remark_type'),
+ RemarkType.MISSED
+ )
+
+ remark = OptimizationRemark.from_dict(doc)
+ remark.remark_type = remark_type
+ remarks.append(remark)
+
+ except Exception as e:
+ print(f"Error parsing {yaml_path}: {e}")
+
+ return remarks
+
+ def parse_directory(self, directory: str, pattern: str = "*.yaml") -> Project:
+ """Parse all YAML files in directory and return project"""
+ project = Project(name=Path(directory).name)
+
+ # Collect all YAML files
+ yaml_files = set(Path(directory).glob(pattern)) | set(Path(directory).rglob(pattern))
+
+ # Group remarks by source file
+ units_by_source = {}
+
+ for yaml_path in yaml_files:
+ remarks = self.parse_yaml_file(str(yaml_path))
+ if not remarks:
+ continue
+
+ source_file = self._extract_source_file(str(yaml_path), remarks)
+
+ if source_file in units_by_source:
+ units_by_source[source_file].remarks.extend(remarks)
+ else:
+ units_by_source[source_file] = CompilationUnit(
+ source_file=source_file,
+ remarks=remarks
+ )
+
+ # Add deduplicated units to project
+ for unit in units_by_source.values():
+ unit.deduplicate_remarks()
+ project.add_compilation_unit(unit)
+
+ return project
+
+ def _extract_source_file(self, yaml_path: str, remarks: List[OptimizationRemark]) -> str:
+ """Extract source file name from remarks or YAML path"""
+ # Try to extract from debug info first
+ for remark in remarks:
+ if (remark.debug_loc and
+ remark.debug_loc.file != '<unknown>' and
+ not remark.debug_loc.file.startswith(('tmp', '.'))):
+ return Path(remark.debug_loc.file).name
+
+ # Fallback: derive from YAML filename
+ yaml_stem = Path(yaml_path).stem
+ if '.' in yaml_stem:
+ parts = yaml_stem.split('.')
+ for i, part in enumerate(parts):
+ if part in self.source_extensions:
+ return '.'.join(parts[:i+1])
+
+ return yaml_stem or 'unknown'
+
+ def parse_single_file(self, yaml_path: str, source_file: Optional[str] = None) -> CompilationUnit:
+ """Parse single YAML file and return compilation unit"""
+ remarks = self.parse_yaml_file(yaml_path)
+ source_file = source_file or self._extract_source_file(yaml_path, remarks)
+ return CompilationUnit(source_file=source_file, remarks=remarks)
+
+ def get_statistics(self, project: Project) -> Dict[str, Any]:
+ """Generate comprehensive statistics for project"""
+ stats = {
+ "total_files": len(project.compilation_units),
+ "total_remarks": 0,
+ "by_type": {t.value: 0 for t in RemarkType},
+ "by_pass": {},
+ "most_active_passes": []
+ }
+
+ for unit in project.compilation_units:
+ stats["total_remarks"] += len(unit.remarks)
+
+ for remark in unit.remarks:
+ stats["by_type"][remark.remark_type.value] += 1
+ stats["by_pass"][remark.pass_name] = stats["by_pass"].get(remark.pass_name, 0) + 1
+
+ stats["most_active_passes"] = sorted(
+ stats["by_pass"].items(),
+ key=lambda x: x[1],
+ reverse=True
+ )[:10]
+
+ return stats
+
+
+
+
+
+if __name__ == "__main__":
+ parser = OptimizationRecordParser()
+ project = parser.parse_directory(".")
+
+ if not project.compilation_units:
+ print("No YAML files found. Run compiler with optimization remarks enabled.")
+ exit(1)
+
+ print(f"Project: {project.name}, Units: {len(project.compilation_units)}")
+ stats = parser.get_statistics(project)
+ print(f"Statistics: {stats}")
More information about the llvm-commits
mailing list