[llvm] [llvm-advisor] Add tool foundation (PR #207625)
Miguel Cárdenas via llvm-commits
llvm-commits at lists.llvm.org
Sun Jul 5 17:12:37 PDT 2026
https://github.com/miguelcsx created https://github.com/llvm/llvm-project/pull/207625
Split from llvm/llvm-project#147451.
This patch adds the llvm-advisor foundation: the tool target, shared types, core records, and common utilities.
>From e7fcea9fdd1409a362366ee1652bf825f3c3f3d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miguel=20C=C3=A1rdenas?= <miguelecsx at gmail.com>
Date: Tue, 28 Apr 2026 22:53:17 -0500
Subject: [PATCH] [llvm-advisor] Add tool foundation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Miguel Cárdenas <miguelecsx at gmail.com>
---
llvm/tools/llvm-advisor/CMakeLists.txt | 8 +
llvm/tools/llvm-advisor/src/AdvisorCommon.h | 30 +++
llvm/tools/llvm-advisor/src/CMakeLists.txt | 19 ++
.../llvm-advisor/src/Core/AdvisorTypes.cpp | 253 ++++++++++++++++++
.../llvm-advisor/src/Core/AdvisorTypes.h | 92 +++++++
.../llvm-advisor/src/Core/JobManager.cpp | 104 +++++++
llvm/tools/llvm-advisor/src/Core/JobManager.h | 59 ++++
.../llvm-advisor/src/Core/SnapshotManager.h | 35 +++
.../llvm-advisor/src/Core/UnitIdentity.cpp | 23 ++
.../llvm-advisor/src/Core/UnitIdentity.h | 26 ++
llvm/tools/llvm-advisor/src/Utils/Hashing.cpp | 81 ++++++
llvm/tools/llvm-advisor/src/Utils/Hashing.h | 43 +++
llvm/tools/llvm-advisor/src/Utils/JSON.cpp | 100 +++++++
llvm/tools/llvm-advisor/src/Utils/JSON.h | 45 ++++
llvm/tools/llvm-advisor/src/Utils/Logging.cpp | 36 +++
llvm/tools/llvm-advisor/src/Utils/Logging.h | 38 +++
llvm/tools/llvm-advisor/src/Utils/Metrics.cpp | 35 +++
llvm/tools/llvm-advisor/src/Utils/Metrics.h | 38 +++
.../llvm-advisor/src/Utils/Normalization.cpp | 111 ++++++++
.../llvm-advisor/src/Utils/Normalization.h | 43 +++
.../llvm-advisor/src/Utils/ProcessRunner.cpp | 125 +++++++++
.../llvm-advisor/src/Utils/ProcessRunner.h | 64 +++++
.../llvm-advisor/src/Utils/Redaction.cpp | 58 ++++
llvm/tools/llvm-advisor/src/Utils/Redaction.h | 26 ++
llvm/tools/llvm-advisor/src/llvm-advisor.cpp | 27 ++
25 files changed, 1519 insertions(+)
create mode 100644 llvm/tools/llvm-advisor/CMakeLists.txt
create mode 100644 llvm/tools/llvm-advisor/src/AdvisorCommon.h
create mode 100644 llvm/tools/llvm-advisor/src/CMakeLists.txt
create mode 100644 llvm/tools/llvm-advisor/src/Core/AdvisorTypes.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Core/AdvisorTypes.h
create mode 100644 llvm/tools/llvm-advisor/src/Core/JobManager.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Core/JobManager.h
create mode 100644 llvm/tools/llvm-advisor/src/Core/SnapshotManager.h
create mode 100644 llvm/tools/llvm-advisor/src/Core/UnitIdentity.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Core/UnitIdentity.h
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Hashing.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Hashing.h
create mode 100644 llvm/tools/llvm-advisor/src/Utils/JSON.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Utils/JSON.h
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Logging.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Logging.h
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Metrics.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Metrics.h
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Normalization.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Normalization.h
create mode 100644 llvm/tools/llvm-advisor/src/Utils/ProcessRunner.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Utils/ProcessRunner.h
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Redaction.cpp
create mode 100644 llvm/tools/llvm-advisor/src/Utils/Redaction.h
create mode 100644 llvm/tools/llvm-advisor/src/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..b8016f723430f
--- /dev/null
+++ b/llvm/tools/llvm-advisor/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(LLVM_TOOL_LLVM_ADVISOR_BUILD_DEFAULT ON)
+set(LLVM_REQUIRE_EXE_NAMES llvm-advisor)
+
+add_subdirectory(src)
+
+install(TARGETS llvm-advisor
+ RUNTIME DESTINATION bin
+ COMPONENT llvm-advisor)
diff --git a/llvm/tools/llvm-advisor/src/AdvisorCommon.h b/llvm/tools/llvm-advisor/src/AdvisorCommon.h
new file mode 100644
index 0000000000000..6f1e478637bb0
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/AdvisorCommon.h
@@ -0,0 +1,30 @@
+//===------------------- AdvisorCommon.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Common LLVM headers used throughout llvm-advisor. Included by every advisor
+// header so individual files do not need to repeat the boilerplate.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSet.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
diff --git a/llvm/tools/llvm-advisor/src/CMakeLists.txt b/llvm/tools/llvm-advisor/src/CMakeLists.txt
new file mode 100644
index 0000000000000..6902bdb0dafff
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/CMakeLists.txt
@@ -0,0 +1,19 @@
+set(LLVM_LINK_COMPONENTS
+ Support
+)
+
+file(GLOB_RECURSE LLVM_ADVISOR_SOURCES CONFIGURE_DEPENDS
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
+)
+
+if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/Storage/StorageManager.h)
+ list(FILTER LLVM_ADVISOR_SOURCES EXCLUDE REGEX ".*/Core/JobManager\\.cpp$")
+endif()
+
+add_llvm_tool(llvm-advisor
+ ${LLVM_ADVISOR_SOURCES}
+)
+
+target_include_directories(llvm-advisor PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+)
diff --git a/llvm/tools/llvm-advisor/src/Core/AdvisorTypes.cpp b/llvm/tools/llvm-advisor/src/Core/AdvisorTypes.cpp
new file mode 100644
index 0000000000000..236859af07653
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Core/AdvisorTypes.cpp
@@ -0,0 +1,253 @@
+//===------------------- AdvisorTypes.cpp - LLVM Advisor
+//-------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Data types for compilation commands, snapshots, and unit records.
+// Used throughout the system for representing build entities.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Core/AdvisorTypes.h"
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+static Expected<std::string> readString(const json::Object &Object,
+ StringRef Key) {
+ std::optional<StringRef> Value = Object.getString(Key);
+ if (!Value)
+ return createStringError(inconvertibleErrorCode(), "missing '%s'",
+ Key.str().c_str());
+ return Value->str();
+}
+
+static json::Array stringsToJSON(ArrayRef<std::string> Values) {
+ json::Array Out;
+ for (const std::string &Value : Values)
+ Out.push_back(Value);
+ return Out;
+}
+
+static SmallVector<std::string, 16> stringsFromJSON(const json::Object &Object,
+ StringRef Key) {
+ SmallVector<std::string, 16> Out;
+ const json::Array *Array = Object.getArray(Key);
+ if (!Array)
+ return Out;
+ for (const json::Value &Value : *Array) {
+ if (std::optional<StringRef> String = Value.getAsString())
+ Out.push_back(String->str());
+ // Non-string elements are silently skipped (lenient parsing).
+ }
+ return Out;
+}
+
+static StringRef jobStateToString(JobRecord::State S) {
+ switch (S) {
+ case JobRecord::Queued:
+ return "queued";
+ case JobRecord::Running:
+ return "running";
+ case JobRecord::Succeeded:
+ return "succeeded";
+ case JobRecord::Failed:
+ return "failed";
+ case JobRecord::Cancelled:
+ return "cancelled";
+ }
+ return "queued"; // Unreachable.
+}
+
+static Expected<JobRecord::State> jobStateFromString(StringRef State) {
+ if (State == "queued")
+ return JobRecord::Queued;
+ if (State == "running")
+ return JobRecord::Running;
+ if (State == "succeeded")
+ return JobRecord::Succeeded;
+ if (State == "failed")
+ return JobRecord::Failed;
+ if (State == "cancelled")
+ return JobRecord::Cancelled;
+ return createStringError(inconvertibleErrorCode(), "unknown job state '%s'",
+ State.str().c_str());
+}
+
+json::Value llvm::advisor::toJSON(const CompileCommand &Command) {
+ return json::Object{{"directory", Command.Directory},
+ {"file", Command.File},
+ {"arguments", stringsToJSON(Command.Arguments)}};
+}
+
+json::Value llvm::advisor::toJSON(const SnapshotRecord &Snapshot) {
+ return json::Object{
+ {"id", Snapshot.ID},
+ {"source_root", Snapshot.SourceRoot},
+ {"build_root", Snapshot.BuildRoot},
+ {"parent_id", Snapshot.ParentID},
+ {"created_unix", static_cast<int64_t>(Snapshot.CreatedUnix)}};
+}
+
+json::Value llvm::advisor::toJSON(const UnitRecord &Unit) {
+ return json::Object{{"id", Unit.ID},
+ {"snapshot_id", Unit.SnapshotID},
+ {"source_path", Unit.SourcePath},
+ {"directory", Unit.Directory},
+ {"object_path", Unit.ObjectPath},
+ {"ir_path", Unit.IRPath},
+ {"remarks_path", Unit.RemarksPath},
+ {"language", Unit.Language},
+ {"target_triple", Unit.TargetTriple},
+ {"toolchain_version", Unit.ToolchainVersion},
+ {"source_content_hash", Unit.SourceContentHash},
+ {"command_fingerprint", Unit.CommandFingerprint},
+ {"arguments", stringsToJSON(Unit.Arguments)}};
+}
+
+json::Value llvm::advisor::toJSON(const JobRecord &Job) {
+ return json::Object{{"id", Job.ID},
+ {"state", jobStateToString(Job.Current)},
+ {"message", Job.Message}};
+}
+
+json::Value llvm::advisor::toJSON(const EntityRecord &Entity) {
+ // Entities are polymorphic: the kind is stored both as `entity_kind`
+ // (canonical) and `kind` (for backward compatibility with older clients).
+ json::Object Object = Entity.Data;
+ Object["entity_kind"] = Entity.Kind;
+ if (!Object.get("kind"))
+ Object["kind"] = Entity.Kind;
+ Object["id"] = Entity.ID;
+ Object["snapshot_id"] = Entity.SnapshotID;
+ Object["unit_id"] = Entity.UnitID;
+ Object["owner_id"] = Entity.OwnerID;
+ return Object;
+}
+
+json::Value llvm::advisor::toJSON(const HealthStatus &Health) {
+ return json::Object{{"ok", Health.OK},
+ {"store", Health.Store},
+ {"snapshots", static_cast<int64_t>(Health.Snapshots)},
+ {"units", static_cast<int64_t>(Health.Units)}};
+}
+
+Expected<SnapshotRecord>
+llvm::advisor::snapshotFromJSON(const json::Value &Value) {
+ const json::Object *Object = Value.getAsObject();
+ if (!Object)
+ return createStringError(inconvertibleErrorCode(),
+ "snapshot is not an object");
+ SnapshotRecord Snapshot;
+ if (Expected<std::string> ID = readString(*Object, "id"))
+ Snapshot.ID = *ID;
+ else
+ return ID.takeError();
+ if (Expected<std::string> SourceRoot = readString(*Object, "source_root"))
+ Snapshot.SourceRoot = *SourceRoot;
+ else
+ return SourceRoot.takeError();
+ if (Expected<std::string> BuildRoot = readString(*Object, "build_root"))
+ Snapshot.BuildRoot = *BuildRoot;
+ else
+ return BuildRoot.takeError();
+ if (std::optional<StringRef> ParentID = Object->getString("parent_id"))
+ Snapshot.ParentID = ParentID->str();
+ if (std::optional<int64_t> Created = Object->getInteger("created_unix"))
+ Snapshot.CreatedUnix = static_cast<uint64_t>(*Created);
+ return Snapshot;
+}
+
+Expected<UnitRecord> llvm::advisor::unitFromJSON(const json::Value &Value) {
+ const json::Object *Object = Value.getAsObject();
+ if (!Object)
+ return createStringError(inconvertibleErrorCode(), "unit is not an object");
+ UnitRecord Unit;
+ if (Expected<std::string> ID = readString(*Object, "id"))
+ Unit.ID = *ID;
+ else
+ return ID.takeError();
+ if (Expected<std::string> SnapshotID = readString(*Object, "snapshot_id"))
+ Unit.SnapshotID = *SnapshotID;
+ else
+ return SnapshotID.takeError();
+ if (Expected<std::string> SourcePath = readString(*Object, "source_path"))
+ Unit.SourcePath = *SourcePath;
+ else
+ return SourcePath.takeError();
+ if (std::optional<StringRef> Directory = Object->getString("directory"))
+ Unit.Directory = Directory->str();
+ if (std::optional<StringRef> ObjectPath = Object->getString("object_path"))
+ Unit.ObjectPath = ObjectPath->str();
+ if (std::optional<StringRef> IRPath = Object->getString("ir_path"))
+ Unit.IRPath = IRPath->str();
+ if (std::optional<StringRef> RemarksPath = Object->getString("remarks_path"))
+ Unit.RemarksPath = RemarksPath->str();
+ if (std::optional<StringRef> Language = Object->getString("language"))
+ Unit.Language = Language->str();
+ if (std::optional<StringRef> TargetTriple =
+ Object->getString("target_triple"))
+ Unit.TargetTriple = TargetTriple->str();
+ if (std::optional<StringRef> ToolchainVersion =
+ Object->getString("toolchain_version"))
+ Unit.ToolchainVersion = ToolchainVersion->str();
+ if (std::optional<StringRef> SourceContentHash =
+ Object->getString("source_content_hash"))
+ Unit.SourceContentHash = SourceContentHash->str();
+ if (std::optional<StringRef> CommandFingerprint =
+ Object->getString("command_fingerprint"))
+ Unit.CommandFingerprint = CommandFingerprint->str();
+ Unit.Arguments = stringsFromJSON(*Object, "arguments");
+ return Unit;
+}
+
+Expected<JobRecord> llvm::advisor::jobFromJSON(const json::Value &Value) {
+ const json::Object *Object = Value.getAsObject();
+ if (!Object)
+ return createStringError(inconvertibleErrorCode(), "job is not an object");
+ JobRecord Job;
+ if (Expected<std::string> ID = readString(*Object, "id"))
+ Job.ID = *ID;
+ else
+ return ID.takeError();
+ if (std::optional<StringRef> Message = Object->getString("message"))
+ Job.Message = Message->str();
+ if (std::optional<StringRef> State = Object->getString("state")) {
+ if (Expected<JobRecord::State> S = jobStateFromString(*State))
+ Job.Current = *S;
+ else
+ return S.takeError();
+ }
+ return Job;
+}
+
+Expected<EntityRecord> llvm::advisor::entityFromJSON(const json::Value &Value) {
+ const json::Object *Object = Value.getAsObject();
+ if (!Object)
+ return createStringError(inconvertibleErrorCode(),
+ "entity is not an object");
+
+ EntityRecord Entity;
+ if (std::optional<StringRef> Kind = Object->getString("entity_kind"))
+ Entity.Kind = Kind->str();
+ else if (Expected<std::string> Kind = readString(*Object, "kind"))
+ Entity.Kind = *Kind;
+ else
+ return Kind.takeError();
+ if (Expected<std::string> ID = readString(*Object, "id"))
+ Entity.ID = *ID;
+ else
+ return ID.takeError();
+ if (std::optional<StringRef> SnapshotID = Object->getString("snapshot_id"))
+ Entity.SnapshotID = SnapshotID->str();
+ if (std::optional<StringRef> UnitID = Object->getString("unit_id"))
+ Entity.UnitID = UnitID->str();
+ if (std::optional<StringRef> OwnerID = Object->getString("owner_id"))
+ Entity.OwnerID = OwnerID->str();
+ Entity.Data = *Object;
+ return Entity;
+}
diff --git a/llvm/tools/llvm-advisor/src/Core/AdvisorTypes.h b/llvm/tools/llvm-advisor/src/Core/AdvisorTypes.h
new file mode 100644
index 0000000000000..c9bd9797c71d3
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Core/AdvisorTypes.h
@@ -0,0 +1,92 @@
+//===------------------- AdvisorTypes.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Data types for compilation commands, snapshots, and unit records.
+// Used throughout the system for representing build entities.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+
+namespace llvm::advisor {
+
+struct CompileCommand {
+ std::string Directory;
+ std::string File;
+ SmallVector<std::string, 16> Arguments;
+};
+
+struct SnapshotRecord {
+ std::string ID;
+ std::string SourceRoot;
+ std::string BuildRoot;
+ std::string ParentID;
+ uint64_t CreatedUnix = 0;
+};
+
+struct UnitRecord {
+ std::string ID;
+ std::string SnapshotID;
+ std::string SourcePath;
+ std::string Directory;
+ std::string ObjectPath;
+ std::string IRPath;
+ std::string RemarksPath;
+ std::string Language;
+ std::string TargetTriple;
+ std::string ToolchainVersion;
+ std::string SourceContentHash;
+ std::string CommandFingerprint;
+ SmallVector<std::string, 16> Arguments;
+};
+
+struct CapabilityRequest {
+ std::string SnapshotID;
+ std::string UnitID;
+ SmallVector<std::string, 8> CapabilityIDs;
+};
+
+struct JobRecord {
+ enum State { Queued, Running, Succeeded, Failed, Cancelled };
+
+ std::string ID;
+ State Current = Queued;
+ std::string Message;
+};
+
+struct EntityRecord {
+ std::string Kind;
+ std::string ID;
+ std::string SnapshotID;
+ std::string UnitID;
+ std::string OwnerID;
+ json::Object Data;
+};
+
+struct HealthStatus {
+ bool OK = true;
+ std::string Store;
+ uint64_t Snapshots = 0;
+ uint64_t Units = 0;
+};
+
+json::Value toJSON(const CompileCommand &Command);
+json::Value toJSON(const SnapshotRecord &Snapshot);
+json::Value toJSON(const UnitRecord &Unit);
+json::Value toJSON(const JobRecord &Job);
+json::Value toJSON(const EntityRecord &Entity);
+json::Value toJSON(const HealthStatus &Health);
+
+Expected<SnapshotRecord> snapshotFromJSON(const json::Value &Value);
+Expected<UnitRecord> unitFromJSON(const json::Value &Value);
+Expected<JobRecord> jobFromJSON(const json::Value &Value);
+Expected<EntityRecord> entityFromJSON(const json::Value &Value);
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Core/JobManager.cpp b/llvm/tools/llvm-advisor/src/Core/JobManager.cpp
new file mode 100644
index 0000000000000..83d07b3c8820d
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Core/JobManager.cpp
@@ -0,0 +1,104 @@
+//===------------------- JobManager.cpp - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Manages job queue and executes capability nodes in parallel.
+// Provides worker pool and streaming result dispatch.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Core/JobManager.h"
+#include "Utils/Hashing.h"
+#include "Utils/JSON.h"
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+JobManager::JobManager(StorageManager &Storage, unsigned MaxInFlight)
+ : Storage(Storage), Pool(hardware_concurrency()), MaxInFlight(MaxInFlight) {
+}
+
+Expected<JobRecord>
+JobManager::submit(StringRef Type, json::Value Request,
+ unique_function<Error(CancellationToken &)> Work) {
+ if (Queued.load() >= MaxInFlight)
+ return createStringError(inconvertibleErrorCode(), "job queue is full");
+
+ JobRecord Job;
+ unsigned Seq = NextJobID.fetch_add(1);
+ Job.ID =
+ "job_" +
+ hashString((Twine(Type) + stringifyJSON(Request) + Twine(Seq)).str());
+ Job.Current = JobRecord::Queued;
+ Job.Message = Type.str();
+ if (Error Err = Storage.metadata().putJob(Job))
+ return std::move(Err);
+
+ ++Queued;
+ std::shared_ptr<CancellationToken> Token =
+ std::make_shared<CancellationToken>();
+ Pool.async([this, Job, Token, Work = std::move(Work)]() mutable {
+ {
+ std::lock_guard<std::mutex> Guard(TokensLock);
+ Tokens[Job.ID] = Token;
+ }
+
+ if (Error Err = transition(Job.ID, JobRecord::Running, Job.Message)) {
+ consumeError(std::move(Err));
+ removeJobTracking(Job.ID);
+ return;
+ }
+
+ Error Err = Token->isCancelled() ? Error::success() : Work(*Token);
+ removeJobTracking(Job.ID);
+
+ if (Token->isCancelled()) {
+ consumeError(transition(Job.ID, JobRecord::Cancelled, "cancelled"));
+ return;
+ }
+
+ if (Err) {
+ std::string Message = toString(std::move(Err));
+ consumeError(transition(Job.ID, JobRecord::Failed, Message));
+ return;
+ }
+ consumeError(transition(Job.ID, JobRecord::Succeeded, Job.Message));
+ });
+
+ return Job;
+}
+
+void JobManager::removeJobTracking(StringRef JobID) {
+ --Queued;
+ std::lock_guard<std::mutex> Guard(TokensLock);
+ Tokens.erase(JobID);
+}
+
+Error JobManager::transition(StringRef JobID, JobRecord::State State,
+ StringRef Message) {
+ Expected<JobRecord> Job = Storage.metadata().getJob(JobID);
+ if (!Job)
+ return Job.takeError();
+ Job->Current = State;
+ Job->Message = Message.str();
+ return Storage.metadata().putJob(*Job);
+}
+
+Error JobManager::cancel(StringRef JobID) {
+ {
+ std::lock_guard<std::mutex> Guard(TokensLock);
+ StringMap<std::shared_ptr<CancellationToken>>::iterator I =
+ Tokens.find(JobID);
+ if (I != Tokens.end())
+ I->second->cancel();
+ }
+ return transition(JobID, JobRecord::Cancelled, "cancelled");
+}
+
+SmallVector<JobRecord, 16> JobManager::list() const {
+ return Storage.metadata().listJobs();
+}
diff --git a/llvm/tools/llvm-advisor/src/Core/JobManager.h b/llvm/tools/llvm-advisor/src/Core/JobManager.h
new file mode 100644
index 0000000000000..27c35bd39ff7d
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Core/JobManager.h
@@ -0,0 +1,59 @@
+//===------------------- JobManager.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Manages job queue and executes capability nodes in parallel.
+// Provides worker pool and streaming result dispatch.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+#include "Core/AdvisorTypes.h"
+#include "Storage/StorageManager.h"
+#include "llvm/Support/ThreadPool.h"
+#include <atomic>
+#include <memory>
+#include <mutex>
+
+namespace llvm::advisor {
+
+class CancellationToken {
+public:
+ void cancel() { Cancelled.store(true); }
+ bool isCancelled() const { return Cancelled.load(); }
+
+private:
+ std::atomic<bool> Cancelled{false};
+};
+
+class JobManager {
+public:
+ /// JobManager must outlive all submitted jobs. Destroying JobManager while
+ /// jobs are active is undefined behavior.
+ explicit JobManager(StorageManager &Storage, unsigned MaxInFlight = 1000);
+
+ Expected<JobRecord> submit(StringRef Type, json::Value Request,
+ unique_function<Error(CancellationToken &)> Work);
+ Error cancel(StringRef JobID);
+ SmallVector<JobRecord, 16> list() const;
+
+private:
+ Error transition(StringRef JobID, JobRecord::State State, StringRef Message);
+ void removeJobTracking(StringRef JobID);
+
+ StorageManager &Storage;
+ DefaultThreadPool Pool;
+ unsigned MaxInFlight;
+ std::atomic<unsigned> Queued{0};
+ std::atomic<unsigned> NextJobID{0};
+ mutable std::mutex TokensLock;
+ StringMap<std::shared_ptr<CancellationToken>> Tokens;
+};
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Core/SnapshotManager.h b/llvm/tools/llvm-advisor/src/Core/SnapshotManager.h
new file mode 100644
index 0000000000000..77a8cd77ea417
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Core/SnapshotManager.h
@@ -0,0 +1,35 @@
+//===------------------- SnapshotManager.h - LLVM Advisor
+//-------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Snapshot lifecycle: create, list, delete, and tier management.
+// Handles snapshot retention and cleanup policies.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+#include "Core/AdvisorTypes.h"
+#include "Storage/StorageManager.h"
+
+namespace llvm::advisor {
+
+class SnapshotManager {
+public:
+ explicit SnapshotManager(StorageManager &Storage) : Storage(Storage) {}
+
+ SmallVector<SnapshotRecord, 16> list() const {
+ return Storage.metadata().listSnapshots();
+ }
+
+private:
+ StorageManager &Storage;
+};
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Core/UnitIdentity.cpp b/llvm/tools/llvm-advisor/src/Core/UnitIdentity.cpp
new file mode 100644
index 0000000000000..82b0ba0273b71
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Core/UnitIdentity.cpp
@@ -0,0 +1,23 @@
+//===------------------- UnitIdentity.cpp - LLVM Advisor
+//-------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Computes UnitID and CapabilityRunKey for build units.
+// Uniquely identifies a compilation unit across builds.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Core/UnitIdentity.h"
+#include "Utils/Hashing.h"
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+std::string UnitIdentity::compute(const UnitRecord &Unit) const {
+ return computeUnitID(Unit);
+}
diff --git a/llvm/tools/llvm-advisor/src/Core/UnitIdentity.h b/llvm/tools/llvm-advisor/src/Core/UnitIdentity.h
new file mode 100644
index 0000000000000..0309add1b1565
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Core/UnitIdentity.h
@@ -0,0 +1,26 @@
+//===------------------- UnitIdentity.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Computes UnitID and CapabilityRunKey for build units.
+// Uniquely identifies a compilation unit across builds.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+#include "Core/AdvisorTypes.h"
+
+namespace llvm::advisor {
+
+class UnitIdentity {
+public:
+ std::string compute(const UnitRecord &Unit) const;
+};
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Utils/Hashing.cpp b/llvm/tools/llvm-advisor/src/Utils/Hashing.cpp
new file mode 100644
index 0000000000000..cfe75727f2b12
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Hashing.cpp
@@ -0,0 +1,81 @@
+//===------------------- Hashing.cpp - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Provides hashing functions for UnitID, CapabilityRunKey, and content hashing.
+// Uses BLAKE3 for fast, secure hashing.
+//
+//===----------------------------------------------------------------------===//
+#include "Utils/Hashing.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/BLAKE3.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+std::string llvm::advisor::hashString(StringRef Data) {
+ BLAKE3 Hasher;
+ Hasher.update(Data);
+ BLAKE3Result<> Result = Hasher.final();
+ return toHex(Result, true);
+}
+
+Expected<std::string> llvm::advisor::hashFile(StringRef Path) {
+ ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = MemoryBuffer::getFile(Path);
+ if (!Buffer)
+ return createStringError(Buffer.getError(), "cannot read '%s'",
+ Path.str().c_str());
+ return hashString((*Buffer)->getBuffer());
+}
+
+std::string llvm::advisor::hashJSON(const json::Value &Value) {
+ std::string Storage;
+ raw_string_ostream OS(Storage);
+ OS << Value;
+ return hashString(OS.str());
+}
+
+std::string llvm::advisor::computeUnitID(const UnitRecord &Unit) {
+ BLAKE3 Hasher;
+ Hasher.update(Unit.SourcePath);
+ Hasher.update("\0");
+ Hasher.update(Unit.Language);
+ Hasher.update("\0");
+ Hasher.update(Unit.TargetTriple);
+ for (const std::string &Arg : Unit.Arguments) {
+ Hasher.update("\0");
+ Hasher.update(Arg);
+ }
+ BLAKE3Result<> Result = Hasher.final();
+ return toHex(Result, true);
+}
+
+std::string llvm::advisor::computeSnapshotID(StringRef SourceRoot,
+ StringRef BuildRoot,
+ uint64_t CreatedUnix) {
+ std::string Data;
+ raw_string_ostream OS(Data);
+ OS << SourceRoot << '\0' << BuildRoot << '\0' << CreatedUnix;
+ return hashString(OS.str());
+}
+
+std::string llvm::advisor::computeCapabilityRunKey(const UnitRecord &Unit,
+ StringRef CapabilityID,
+ StringRef CapabilityVersion,
+ StringRef InputDigest) {
+ BLAKE3 Hasher;
+ Hasher.update(Unit.ID);
+ Hasher.update("\0");
+ Hasher.update(CapabilityID);
+ Hasher.update("\0");
+ Hasher.update(CapabilityVersion);
+ Hasher.update("\0");
+ Hasher.update(InputDigest);
+ BLAKE3Result<> Result = Hasher.final();
+ return toHex(Result, true);
+}
diff --git a/llvm/tools/llvm-advisor/src/Utils/Hashing.h b/llvm/tools/llvm-advisor/src/Utils/Hashing.h
new file mode 100644
index 0000000000000..3a65dbd49e3dd
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Hashing.h
@@ -0,0 +1,43 @@
+//===------------------- Hashing.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Provides hashing functions for UnitID, CapabilityRunKey, and content hashing.
+// Uses BLAKE3 for fast, secure hashing.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+#include "Core/AdvisorTypes.h"
+
+namespace llvm::advisor {
+
+/// Return the BLAKE3 hash of a string as a hex string.
+std::string hashString(StringRef Data);
+
+/// Return the BLAKE3 hash of a file's contents as a hex string.
+Expected<std::string> hashFile(StringRef Path);
+
+/// Return the BLAKE3 hash of a JSON value as a hex string.
+std::string hashJSON(const json::Value &Value);
+
+/// Compute a deterministic hash-based ID for a compilation unit.
+std::string computeUnitID(const UnitRecord &Unit);
+
+/// Compute a deterministic snapshot ID from root paths and a timestamp.
+std::string computeSnapshotID(StringRef SourceRoot, StringRef BuildRoot,
+ uint64_t CreatedUnix);
+
+/// Compute a deterministic run key from a unit, capability, and input digest.
+std::string computeCapabilityRunKey(const UnitRecord &Unit,
+ StringRef CapabilityID,
+ StringRef CapabilityVersion,
+ StringRef InputDigest);
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Utils/JSON.cpp b/llvm/tools/llvm-advisor/src/Utils/JSON.cpp
new file mode 100644
index 0000000000000..06e46dc7099c8
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/JSON.cpp
@@ -0,0 +1,100 @@
+//===------------------- JSON.cpp - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Typed JSON helpers on top of LLVM's JSON support.
+// Provides convenient wrappers for reading and writing JSON files.
+//
+//===----------------------------------------------------------------------===//
+#include "Utils/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/ToolOutputFile.h"
+
+#include <chrono>
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+Expected<json::Value> llvm::advisor::parseJSONFile(StringRef Path) {
+ ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = MemoryBuffer::getFile(Path);
+ if (!Buffer)
+ return createStringError(Buffer.getError(), "cannot read '%s'",
+ Path.str().c_str());
+
+ Expected<json::Value> Parsed = json::parse((*Buffer)->getBuffer());
+ if (!Parsed)
+ return Parsed.takeError();
+ return std::move(*Parsed);
+}
+
+Error llvm::advisor::writeJSONFile(StringRef Path, const json::Value &Value) {
+ std::error_code EC;
+ ToolOutputFile Out(Path, EC, sys::fs::OF_Text);
+ if (EC)
+ return createStringError(EC, "cannot write '%s'", Path.str().c_str());
+ Out.os() << Value << '\n';
+ Out.keep();
+ return Error::success();
+}
+
+Expected<std::string> llvm::advisor::getString(const json::Object &Object,
+ StringRef Key) {
+ std::optional<StringRef> Value = Object.getString(Key);
+ if (!Value)
+ return createStringError(inconvertibleErrorCode(), "missing string '%s'",
+ Key.str().c_str());
+ return Value->str();
+}
+
+SmallVector<std::string, 8>
+llvm::advisor::getStringArray(const json::Object &Object, StringRef Key) {
+ SmallVector<std::string, 8> Out;
+ const json::Array *Array = Object.getArray(Key);
+ if (!Array)
+ return Out;
+
+ for (const json::Value &Value : *Array) {
+ std::optional<StringRef> String = Value.getAsString();
+ if (String)
+ Out.push_back(String->str());
+ }
+ return Out;
+}
+
+std::string llvm::advisor::stringifyJSON(const json::Value &Value) {
+ std::string Storage;
+ raw_string_ostream OS(Storage);
+ OS << Value;
+ return OS.str();
+}
+
+static int64_t unixNow() {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+}
+
+static std::string uniqueRequestID() {
+ static uint64_t Counter = 0;
+ int64_t Now = unixNow();
+ return ("req_" + Twine(Now) + "_" + Twine(Counter++)).str();
+}
+
+json::Value llvm::advisor::successEnvelope(json::Value Data) {
+ return json::Object{{"request_id", uniqueRequestID()},
+ {"timestamp_unix", unixNow()},
+ {"status", "success"},
+ {"data", std::move(Data)}};
+}
+
+json::Value llvm::advisor::errorEnvelope(StringRef Code, StringRef Message) {
+ return json::Object{
+ {"request_id", uniqueRequestID()},
+ {"timestamp_unix", unixNow()},
+ {"status", "error"},
+ {"error", json::Object{{"code", Code}, {"message", Message}}}};
+}
diff --git a/llvm/tools/llvm-advisor/src/Utils/JSON.h b/llvm/tools/llvm-advisor/src/Utils/JSON.h
new file mode 100644
index 0000000000000..75ce8f5ce899f
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/JSON.h
@@ -0,0 +1,45 @@
+//===------------------- JSON.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Typed JSON helpers on top of LLVM's JSON support.
+// Provides convenient wrappers for reading and writing JSON files.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/ToolOutputFile.h"
+
+namespace llvm::advisor {
+
+/// Parse a JSON file from disk.
+Expected<json::Value> parseJSONFile(StringRef Path);
+
+/// Write a JSON value to disk atomically.
+Error writeJSONFile(StringRef Path, const json::Value &Value);
+
+/// Extract a string value from a JSON object.
+Expected<std::string> getString(const json::Object &Object, StringRef Key);
+
+/// Extract an array of strings from a JSON object.
+/// Non-string elements are silently skipped.
+SmallVector<std::string, 8> getStringArray(const json::Object &Object,
+ StringRef Key);
+
+/// Serialize a JSON value to a string.
+std::string stringifyJSON(const json::Value &Value);
+
+/// Wrap data in a standard success envelope with request metadata.
+json::Value successEnvelope(json::Value Data);
+
+/// Wrap an error in a standard error envelope with request metadata.
+json::Value errorEnvelope(StringRef Code, StringRef Message);
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Utils/Logging.cpp b/llvm/tools/llvm-advisor/src/Utils/Logging.cpp
new file mode 100644
index 0000000000000..d28a90aa7f8ea
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Logging.cpp
@@ -0,0 +1,36 @@
+//===------------------- Logging.cpp - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Structured logging with multiple severity levels.
+// Provides logging infrastructure for the entire advisor.
+//
+//===----------------------------------------------------------------------===//
+#include "Utils/Logging.h"
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+static StringRef levelLabel(LogLevel Level) {
+ switch (Level) {
+ case LogLevel::Error:
+ return "error";
+ case LogLevel::Warning:
+ return "warning";
+ case LogLevel::Info:
+ return "info";
+ case LogLevel::Debug:
+ return "debug";
+ }
+ llvm_unreachable("Unknown log level");
+}
+
+void Logger::log(LogLevel Level, StringRef Message) {
+ if (Level == LogLevel::Debug && !Verbose)
+ return;
+ OS << "llvm-advisor: " << levelLabel(Level) << ": " << Message << '\n';
+}
diff --git a/llvm/tools/llvm-advisor/src/Utils/Logging.h b/llvm/tools/llvm-advisor/src/Utils/Logging.h
new file mode 100644
index 0000000000000..8dce3495e7fe9
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Logging.h
@@ -0,0 +1,38 @@
+//===------------------- Logging.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Structured logging with multiple severity levels.
+// Provides logging infrastructure for the entire advisor.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+
+namespace llvm::advisor {
+
+enum class LogLevel { Error, Warning, Info, Debug };
+
+/// Simple logger that writes messages to a stream with severity labels.
+class Logger {
+public:
+ explicit Logger(raw_ostream &OS) : OS(OS) {}
+
+ /// Toggle verbose (debug) output.
+ void setVerbose(bool Value) { Verbose = Value; }
+
+ /// Write a message at the given severity level.
+ void log(LogLevel Level, StringRef Message);
+
+private:
+ raw_ostream &OS;
+ bool Verbose = false;
+};
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Utils/Metrics.cpp b/llvm/tools/llvm-advisor/src/Utils/Metrics.cpp
new file mode 100644
index 0000000000000..bd60c61dbe01b
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Metrics.cpp
@@ -0,0 +1,35 @@
+//===------------------- Metrics.cpp - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Prometheus-compatible metrics counters for observability.
+// Tracks performance and operational metrics.
+//
+//===----------------------------------------------------------------------===//
+#include "Utils/Metrics.h"
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+void Metrics::increment(StringRef Name, uint64_t Delta) {
+ Counters[Name] += Delta;
+}
+
+uint64_t Metrics::get(StringRef Name) const {
+ StringMap<uint64_t>::const_iterator I = Counters.find(Name);
+ if (I == Counters.end())
+ return 0;
+ return I->second;
+}
+
+std::string Metrics::toText() const {
+ std::string Storage;
+ raw_string_ostream OS(Storage);
+ for (const StringMapEntry<uint64_t> &Entry : Counters)
+ OS << Entry.first() << ' ' << Entry.second << '\n';
+ return OS.str();
+}
diff --git a/llvm/tools/llvm-advisor/src/Utils/Metrics.h b/llvm/tools/llvm-advisor/src/Utils/Metrics.h
new file mode 100644
index 0000000000000..2c91403433f1a
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Metrics.h
@@ -0,0 +1,38 @@
+//===------------------- Metrics.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Prometheus-compatible metrics counters for observability.
+// Tracks performance and operational metrics.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+
+namespace llvm::advisor {
+
+/// Simple counter-based metrics tracker.
+class Metrics {
+public:
+ /// Increment a named counter by the given delta.
+ void increment(StringRef Name, uint64_t Delta = 1);
+
+ /// Read the current value of a named counter.
+ uint64_t get(StringRef Name) const;
+
+ /// Export all counters as simple text lines.
+ ///
+ /// Each line is "name value".
+ std::string toText() const;
+
+private:
+ StringMap<uint64_t> Counters;
+};
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Utils/Normalization.cpp b/llvm/tools/llvm-advisor/src/Utils/Normalization.cpp
new file mode 100644
index 0000000000000..251db9a9a1137
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Normalization.cpp
@@ -0,0 +1,111 @@
+//===------------------- Normalization.cpp - LLVM Advisor
+//-------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Command line normalization and path canonicalization.
+// Normalizes paths and commands for consistent comparison.
+//
+//===----------------------------------------------------------------------===//
+#include "Utils/Normalization.h"
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+std::string llvm::advisor::normalizePath(StringRef Path, StringRef Base) {
+ SmallString<256> Result;
+ if (!sys::path::is_absolute(Path) && !Base.empty())
+ Result = Base;
+ sys::path::append(Result, Path);
+ sys::path::remove_dots(Result, true);
+ sys::path::native(Result);
+ return Result.str().str();
+}
+
+static bool isInside(StringRef Path, StringRef Root) {
+ if (Path == Root)
+ return true;
+ if (!Path.starts_with(Root))
+ return false;
+ return sys::path::is_separator(Path.drop_front(Root.size()).front());
+}
+
+Expected<std::string>
+llvm::advisor::canonicalizePath(StringRef Path,
+ ArrayRef<StringRef> AllowedRoots) {
+ SmallString<256> RealPath;
+ if (std::error_code EC = sys::fs::real_path(Path, RealPath))
+ return createStringError(EC, Twine("path does not exist: ") + Path);
+ sys::path::remove_dots(RealPath, true);
+
+ for (StringRef Root : AllowedRoots) {
+ SmallString<256> RealRoot;
+ if (std::error_code EC = sys::fs::real_path(Root, RealRoot))
+ return createStringError(EC, Twine("path root does not exist: ") + Root);
+ sys::path::remove_dots(RealRoot, true);
+ if (isInside(RealPath, RealRoot))
+ return RealPath.str().str();
+ }
+
+ return createStringError(inconvertibleErrorCode(),
+ Twine("path escapes allowed roots: ") + Path);
+}
+
+SmallVector<std::string, 16>
+llvm::advisor::normalizeCommand(ArrayRef<std::string> Arguments) {
+ SmallVector<std::string, 16> Out;
+ for (const std::string &Arg : Arguments) {
+ StringRef Ref(Arg);
+ if (Ref == "-ftime-trace-granularity=0")
+ continue;
+ if (Ref.starts_with("-fdebug-compilation-dir="))
+ continue;
+ Out.push_back(Arg);
+ }
+ return Out;
+}
+
+std::string llvm::advisor::inferLanguage(StringRef Path) {
+ StringRef Ext = sys::path::extension(Path);
+ if (Ext == ".c")
+ return "c";
+ if (Ext == ".m")
+ return "objective-c";
+ if (Ext == ".mm")
+ return "objective-c++";
+ if (Ext == ".s" || Ext == ".S")
+ return "assembly";
+ return "c++";
+}
+
+std::string llvm::advisor::inferTargetTriple(ArrayRef<std::string> Arguments) {
+ constexpr StringRef LongTargetPrefix = "--target=";
+ constexpr StringRef ShortTargetPrefix = "-target=";
+
+ for (size_t I = 0, E = Arguments.size(); I != E; ++I) {
+ StringRef Arg(Arguments[I]);
+ if (Arg == "-target" && I + 1 != E)
+ return Arguments[I + 1];
+ if (Arg.starts_with(LongTargetPrefix))
+ return Arg.drop_front(LongTargetPrefix.size()).str();
+ if (Arg.starts_with(ShortTargetPrefix))
+ return Arg.drop_front(ShortTargetPrefix.size()).str();
+ }
+ return {};
+}
+
+std::string llvm::advisor::resolveOutputPath(ArrayRef<std::string> Arguments,
+ StringRef BaseDir) {
+ for (size_t I = 0, E = Arguments.size(); I != E; ++I) {
+ StringRef Arg(Arguments[I]);
+ if (Arg == "-o" && I + 1 < E)
+ return normalizePath(Arguments[I + 1], BaseDir);
+ if (Arg.starts_with("-o") && Arg.size() > 2)
+ return normalizePath(Arg.drop_front(2), BaseDir);
+ }
+ return {};
+}
diff --git a/llvm/tools/llvm-advisor/src/Utils/Normalization.h b/llvm/tools/llvm-advisor/src/Utils/Normalization.h
new file mode 100644
index 0000000000000..95da8a81aa5eb
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Normalization.h
@@ -0,0 +1,43 @@
+//===------------------- Normalization.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Command line normalization and path canonicalization.
+// Normalizes paths and commands for consistent comparison.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+
+namespace llvm::advisor {
+
+/// Resolve a path relative to Base and normalize separators/dots.
+std::string normalizePath(StringRef Path, StringRef Base = {});
+
+/// Resolve a path to its real path and verify it is within AllowedRoots.
+Expected<std::string> canonicalizePath(StringRef Path,
+ ArrayRef<StringRef> AllowedRoots);
+
+/// Remove non-deterministic flags from a command line.
+SmallVector<std::string, 16> normalizeCommand(ArrayRef<std::string> Arguments);
+
+/// Detect language from file extension.
+std::string inferLanguage(StringRef Path);
+
+/// Extract the target triple from compiler arguments.
+std::string inferTargetTriple(ArrayRef<std::string> Arguments);
+
+/// Parse compiler arguments to find the output path specified by -o.
+/// Returns an empty string if no output flag is present.
+std::string resolveOutputPath(ArrayRef<std::string> Arguments,
+ StringRef BaseDir = {});
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Utils/ProcessRunner.cpp b/llvm/tools/llvm-advisor/src/Utils/ProcessRunner.cpp
new file mode 100644
index 0000000000000..43d36870cecc7
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/ProcessRunner.cpp
@@ -0,0 +1,125 @@
+//===------------------- ProcessRunner.cpp - LLVM Advisor
+//-------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Safe, allowlisted subprocess execution for external_fallback capabilities.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Utils/ProcessRunner.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Program.h"
+
+#include <chrono>
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+namespace {
+
+/// RAII guard that removes a temporary file on destruction.
+struct TempFileGuard {
+ StringRef Path;
+ explicit TempFileGuard(StringRef Path) : Path(Path) {}
+ ~TempFileGuard() { sys::fs::remove(Path); }
+};
+
+} // namespace
+
+void ProcessRunner::allow(StringRef Program) {
+ ToolPolicy &Policy = AllowList[Program];
+ Policy.AllowAll = true;
+ Policy.Flags.clear();
+}
+
+void ProcessRunner::allow(StringRef Program, ArrayRef<StringRef> Flags) {
+ ToolPolicy &Policy = AllowList[Program];
+ Policy.AllowAll = false;
+ Policy.Flags.clear();
+ for (StringRef Flag : Flags)
+ Policy.Flags.insert(Flag);
+}
+
+bool ProcessRunner::isAllowedArg(StringRef Program, StringRef Arg) const {
+ if (Arg.empty() || Arg.contains('\0') || Arg.contains('\n') ||
+ Arg.contains('\r'))
+ return false;
+ if (!Arg.starts_with("-"))
+ return true;
+
+ StringMap<ToolPolicy>::const_iterator I = AllowList.find(Program);
+ if (I == AllowList.end())
+ return false;
+ if (I->second.AllowAll)
+ return true;
+ if (I->second.Flags.empty())
+ return false;
+ if (I->second.Flags.contains(Arg))
+ return true;
+
+ StringRef Name = Arg.take_until([](char C) { return C == '='; });
+ return I->second.Flags.contains(Name);
+}
+
+Expected<ProcessResult> ProcessRunner::run(StringRef Program,
+ ArrayRef<std::string> Arguments,
+ unsigned TimeoutSeconds) const {
+ if (!AllowList.contains(Program))
+ return createStringError(inconvertibleErrorCode(),
+ "program not allowlisted: %s",
+ Program.str().c_str());
+
+ ErrorOr<std::string> Resolved = sys::findProgramByName(Program);
+ if (!Resolved)
+ return createStringError(Resolved.getError(), "cannot find program: %s",
+ Program.str().c_str());
+
+ SmallVector<StringRef, 16> Args;
+ Args.push_back(*Resolved);
+ for (const auto &Arg : Arguments) {
+ if (!isAllowedArg(Program, Arg))
+ return createStringError(inconvertibleErrorCode(),
+ "argument not allowlisted: %s", Arg.c_str());
+ Args.push_back(Arg);
+ }
+
+ SmallString<128> StdoutPath, StderrPath;
+ if (auto EC = sys::fs::createTemporaryFile("advisor-out", "tmp", StdoutPath))
+ return createStringError(EC, "failed to create stdout temp file");
+ TempFileGuard StdoutGuard(StdoutPath);
+
+ if (auto EC = sys::fs::createTemporaryFile("advisor-err", "tmp", StderrPath))
+ return createStringError(EC, "failed to create stderr temp file");
+ TempFileGuard StderrGuard(StderrPath);
+
+ std::optional<StringRef> Redirects[] = {
+ std::nullopt, // stdin
+ StringRef(StdoutPath), // stdout
+ StringRef(StderrPath) // stderr
+ };
+
+ auto StartTime = std::chrono::steady_clock::now();
+ int ExitCode = sys::ExecuteAndWait(*Resolved, Args, std::nullopt, Redirects,
+ TimeoutSeconds);
+ auto EndTime = std::chrono::steady_clock::now();
+
+ ProcessResult Result;
+ Result.ExitCode = ExitCode;
+ Result.WallTimeNs =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(EndTime - StartTime)
+ .count();
+
+ if (auto Buf = MemoryBuffer::getFile(StdoutPath))
+ Result.Stdout = (*Buf)->getBuffer().str();
+ if (auto Buf = MemoryBuffer::getFile(StderrPath))
+ Result.Stderr = (*Buf)->getBuffer().str();
+
+ return Result;
+}
diff --git a/llvm/tools/llvm-advisor/src/Utils/ProcessRunner.h b/llvm/tools/llvm-advisor/src/Utils/ProcessRunner.h
new file mode 100644
index 0000000000000..eb684476d4266
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/ProcessRunner.h
@@ -0,0 +1,64 @@
+//===------------------- ProcessRunner.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Safe, allowlisted subprocess execution for external_fallback capabilities.
+// The only file in the entire codebase that may spawn child processes.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringSet.h"
+#include "llvm/Support/Error.h"
+
+#include <string>
+
+namespace llvm::advisor {
+
+/// Result of an external process invocation.
+struct ProcessResult {
+ int ExitCode = -1;
+ std::string Stdout;
+ std::string Stderr;
+ uint64_t WallTimeNs = 0;
+};
+
+/// Runs external tools with an explicit allowlist.
+///
+/// Programs and their arguments must be registered before execution.
+/// Any argument not in the allowlist causes the run to fail.
+class ProcessRunner {
+public:
+ /// Allow all arguments for a given program.
+ void allow(StringRef Program);
+
+ /// Allow only specific flags for a given program.
+ /// Overrides any previous allow-all setting.
+ void allow(StringRef Program, ArrayRef<StringRef> Flags);
+
+ /// Execute a program with the given arguments.
+ ///
+ /// Fails if the program or any argument is not allowlisted.
+ /// If TimeoutSeconds is zero, no timeout is applied.
+ Expected<ProcessResult> run(StringRef Program,
+ ArrayRef<std::string> Arguments,
+ unsigned TimeoutSeconds = 0) const;
+
+private:
+ struct ToolPolicy {
+ StringSet<> Flags;
+ bool AllowAll = false;
+ };
+
+ bool isAllowedArg(StringRef Program, StringRef Arg) const;
+ StringMap<ToolPolicy> AllowList;
+};
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/Utils/Redaction.cpp b/llvm/tools/llvm-advisor/src/Utils/Redaction.cpp
new file mode 100644
index 0000000000000..a5fcc304058b8
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Redaction.cpp
@@ -0,0 +1,58 @@
+//===------------------- Redaction.cpp - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Sensitive argument and path redaction for secure logging.
+// Removes secrets from stored command lines.
+//
+//===----------------------------------------------------------------------===//
+#include "Utils/Redaction.h"
+
+using namespace llvm;
+using namespace llvm::advisor;
+
+/// Return true if Haystack contains Needle ignoring ASCII case.
+static bool containsInsensitive(StringRef Haystack, StringRef Needle) {
+ if (Needle.size() > Haystack.size())
+ return false;
+ for (size_t I = 0, E = Haystack.size() - Needle.size() + 1; I != E; ++I) {
+ bool Match = true;
+ for (size_t J = 0; J != Needle.size(); ++J) {
+ if (std::tolower(static_cast<unsigned char>(Haystack[I + J])) !=
+ std::tolower(static_cast<unsigned char>(Needle[J]))) {
+ Match = false;
+ break;
+ }
+ }
+ if (Match)
+ return true;
+ }
+ return false;
+}
+
+static bool isSensitive(StringRef Value) {
+ static const StringRef Keywords[] = {"secret", "token", "password", "apikey",
+ "api_key"};
+ for (StringRef Keyword : Keywords)
+ if (containsInsensitive(Value, Keyword))
+ return true;
+ return false;
+}
+
+std::string llvm::advisor::redactString(StringRef Value) {
+ if (isSensitive(Value))
+ return "<redacted>";
+ return Value.str();
+}
+
+SmallVector<std::string, 16>
+llvm::advisor::redactCommand(ArrayRef<std::string> Arguments) {
+ SmallVector<std::string, 16> Out;
+ for (const std::string &Arg : Arguments)
+ Out.push_back(redactString(Arg));
+ return Out;
+}
diff --git a/llvm/tools/llvm-advisor/src/Utils/Redaction.h b/llvm/tools/llvm-advisor/src/Utils/Redaction.h
new file mode 100644
index 0000000000000..f496b1691b2e7
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/Utils/Redaction.h
@@ -0,0 +1,26 @@
+//===------------------- Redaction.h - LLVM Advisor -------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Sensitive argument and path redaction for secure logging.
+// Removes secrets from stored command lines.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "AdvisorCommon.h"
+
+namespace llvm::advisor {
+
+/// Redact a single string if it contains sensitive keywords.
+std::string redactString(StringRef Value);
+
+/// Redact sensitive values from a command line argument list.
+SmallVector<std::string, 16> redactCommand(ArrayRef<std::string> Arguments);
+
+} // namespace llvm::advisor
diff --git a/llvm/tools/llvm-advisor/src/llvm-advisor.cpp b/llvm/tools/llvm-advisor/src/llvm-advisor.cpp
new file mode 100644
index 0000000000000..211c333590639
--- /dev/null
+++ b/llvm/tools/llvm-advisor/src/llvm-advisor.cpp
@@ -0,0 +1,27 @@
+//===--- llvm-advisor.cpp - LLVM Advisor ---------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+
+namespace {
+
+cl::OptionCategory AdvisorCategory("llvm-advisor options");
+
+} // namespace
+
+int main(int argc, char **argv) {
+ InitLLVM X(argc, argv);
+ cl::HideUnrelatedOptions(AdvisorCategory);
+ cl::ParseCommandLineOptions(argc, argv, "LLVM Advisor\n");
+ outs() << "llvm-advisor foundation is available.\n";
+ return 0;
+}
More information about the llvm-commits
mailing list