[llvm] [ORC] Add WaitingOnGraph record / replay facility and test tool. (PR #185275)
Lang Hames via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 9 19:20:30 PDT 2026
https://github.com/lhames updated https://github.com/llvm/llvm-project/pull/185275
>From ba68f2e9297583d3e0a00365a078cab52f899f87 Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Sun, 8 Mar 2026 23:33:55 +1100
Subject: [PATCH 1/4] [ORC] Add WaitingOnGraph record / replay facility and
test tool.
This patch adds APIs and a test tool for recording and replaying operations on
ORC's WaitingOnGraph data structure.
WaitingOnGraph is critical to the performance of LLVM's JIT (see e.g.
https://github.com/llvm/llvm-project/issues/179611), and these facilities will
make it easier to capture and investigate test cases, and build a performance
regression suite.
WaitingOnGraph::Recorder provides an interface for classes that want to capture
the essential WaitingOnGraph operations: simplify, emit, and fail.
WaitingOnGraphStreamRecorder is a WaitingOnGraph::Recorder implementation that
writes these operations (using plain ints to encode the containers / elements)
to a raw_ostream.
WaitingOnGraphBufferReplay reads a buffer containing WaitingOnGraph operations
in the format produced by WaitingOnGraphStreamRecorder, and replays these
operations on an instance of WaitingOnGraph.
---
.../llvm/ExecutionEngine/Orc/WaitingOnGraph.h | 20 +-
.../Orc/WaitingOnGraphReplay.h | 230 ++++++++++++++++++
.../CMakeLists.txt | 10 +
.../llvm-orc-WaitingOnGraph-replay.cpp | 50 ++++
.../Orc/WaitingOnGraphTest.cpp | 40 +++
5 files changed, 348 insertions(+), 2 deletions(-)
create mode 100644 llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h
create mode 100644 llvm/tools/llvm-orc-WaitingOnGraph-replay/CMakeLists.txt
create mode 100644 llvm/tools/llvm-orc-WaitingOnGraph-replay/llvm-orc-WaitingOnGraph-replay.cpp
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraph.h b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraph.h
index 67b50eca87ac0..328ca13824249 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraph.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraph.h
@@ -465,8 +465,21 @@ template <typename ContainerIdT, typename ElementIdT> class WaitingOnGraph {
ElemToSuperNodeMap ElemToSN;
};
+ class Recorder {
+ public:
+ virtual ~Recorder() = default;
+ virtual void
+ recordSimplify(const std::vector<std::unique_ptr<SuperNode>> &SNs) = 0;
+ virtual void recordFail(const ContainerElementsMap &Failed) = 0;
+ virtual void recordEnd() = 0;
+ };
+
/// Preprocess a list of SuperNodes to remove all intra-SN dependencies.
- static SimplifyResult simplify(std::vector<std::unique_ptr<SuperNode>> SNs) {
+ static SimplifyResult simplify(std::vector<std::unique_ptr<SuperNode>> SNs,
+ Recorder *Rec = nullptr) {
+ if (Rec)
+ Rec->recordSimplify(SNs);
+
// Build ElemToSN map.
ElemToSuperNodeMap ElemToSN;
for (auto &SN : SNs)
@@ -553,7 +566,10 @@ template <typename ContainerIdT, typename ElementIdT> class WaitingOnGraph {
/// result, so clients should take whatever actions are needed to mark
/// this as failed in their external representation.
std::vector<std::unique_ptr<SuperNode>>
- fail(const ContainerElementsMap &Failed) {
+ fail(const ContainerElementsMap &Failed, Recorder *Rec = nullptr) {
+ if (Rec)
+ Rec->recordFail(Failed);
+
std::vector<std::unique_ptr<SuperNode>> FailedSNs;
visitWithRemoval(PendingSNs, [&](std::unique_ptr<SuperNode> &SN) {
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h
new file mode 100644
index 0000000000000..e1bc31670a61a
--- /dev/null
+++ b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h
@@ -0,0 +1,230 @@
+//===-------- WaitingOnGraphReplay.h - Record/replay APIs -------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines WaitingOnGraphStreamRecorder and WaitingOnGraphBufferReplayer.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_EXECUTIONENGINE_ORC_WAITINGONGRAPHREPLAY_H
+#define LLVM_EXECUTIONENGINE_ORC_WAITINGONGRAPHREPLAY_H
+
+#include "llvm/ExecutionEngine/Orc/WaitingOnGraph.h"
+#include "llvm/Support/Error.h"
+
+namespace llvm::orc::detail {
+
+/// Records WaitingOnGraph operations to a text format on a raw_ostream.
+template <typename ContainerIdT, typename ElementIdT>
+class WaitingOnGraphStreamRecorder
+ : public WaitingOnGraph<ContainerIdT, ElementIdT>::Recorder {
+ using WOG = WaitingOnGraph<ContainerIdT, ElementIdT>;
+ using SuperNode = typename WOG::SuperNode;
+ using ContainerId = typename WOG::ContainerId;
+ using ElementId = typename WOG::ElementId;
+ using ContainerElementsMap = typename WOG::ContainerElementsMap;
+
+public:
+ WaitingOnGraphStreamRecorder(raw_ostream &OS) : OS(OS) {}
+
+ void
+ recordSimplify(const std::vector<std::unique_ptr<SuperNode>> &SNs) override {
+ recordSuperNodes("simplify-and-emit", SNs);
+ }
+
+ void recordFail(const ContainerElementsMap &Failed) override {
+ SmallVector<std::pair<ContainerId, ElementId>> Pairs;
+ for (auto &[Container, Elements] : Failed)
+ for (auto &Elem : Elements)
+ Pairs.push_back({Container, Elem});
+ llvm::sort(Pairs);
+ OS << "fail " << Pairs.size() << "\n";
+ for (auto &[Container, Elem] : Pairs)
+ OS << " " << Container << " " << Elem << "\n";
+ }
+
+ void recordEnd() override { OS << "end\n"; }
+
+private:
+ void recordSuperNodes(StringRef OpName,
+ const std::vector<std::unique_ptr<SuperNode>> &SNs) {
+ OS << OpName << " " << SNs.size() << "\n";
+ for (size_t I = 0; I != SNs.size(); ++I) {
+ auto &SN = *SNs[I];
+ OS << " sn " << I << "\n";
+
+ SmallVector<std::pair<ContainerId, ElementId>> Defs;
+ for (auto &[Container, Elements] : SN.defs())
+ for (auto &Elem : Elements)
+ Defs.push_back({Container, Elem});
+ llvm::sort(Defs);
+ OS << " defs " << Defs.size() << "\n";
+ for (auto &[Container, Elem] : Defs)
+ OS << " " << Container << " " << Elem << "\n";
+
+ SmallVector<std::pair<ContainerId, ElementId>> Deps;
+ for (auto &[Container, Elements] : SN.deps())
+ for (auto &Elem : Elements)
+ Deps.push_back({Container, Elem});
+ llvm::sort(Deps);
+ OS << " deps " << Deps.size() << "\n";
+ for (auto &[Container, Elem] : Deps)
+ OS << " " << Container << " " << Elem << "\n";
+ }
+ }
+
+ raw_ostream &OS;
+};
+
+/// Replays WaitingOnGraph operations from a text buffer produced by
+/// WaitingOnGraphStreamRecorder.
+///
+/// Supports integer ContainerId/ElementId types only (via consumeInteger).
+template <typename ContainerIdT, typename ElementIdT>
+class WaitingOnGraphBufferReplayer {
+ using WOG = WaitingOnGraph<ContainerIdT, ElementIdT>;
+ using SuperNode = typename WOG::SuperNode;
+ using ContainerId = typename WOG::ContainerId;
+ using ElementId = typename WOG::ElementId;
+ using ContainerElementsMap = typename WOG::ContainerElementsMap;
+ using ExternalState = typename WOG::ExternalState;
+
+public:
+ WaitingOnGraphBufferReplayer(WOG &G) : G(G) {}
+
+ Error replay(StringRef Input) {
+ while (!Input.empty()) {
+ StringRef Line = getNextLine(Input);
+ if (Line.empty())
+ continue;
+
+ if (Line.consume_front("simplify-and-emit ")) {
+ size_t NumSNs;
+ if (Line.trim().consumeInteger(10, NumSNs))
+ return make_error<StringError>(
+ "expected supernode count after 'simplify-and-emit'",
+ inconvertibleErrorCode());
+ std::vector<std::unique_ptr<SuperNode>> SNs;
+ if (auto Err = parseSuperNodes(Input, NumSNs, SNs))
+ return Err;
+ auto SR = WOG::simplify(std::move(SNs));
+ auto ER = G.emit(std::move(SR),
+ [this](ContainerId C, ElementId E) -> ExternalState {
+ {
+ auto I = Failed.find(C);
+ if (I != Failed.end() && I->second.count(E))
+ return ExternalState::Failed;
+ }
+ {
+ auto I = Ready.find(C);
+ if (I != Ready.end() && I->second.count(E))
+ return ExternalState::Ready;
+ }
+ return ExternalState::None;
+ });
+ for (auto &SN : ER.Ready)
+ for (auto &[Container, Elems] : SN->defs())
+ Ready[Container].insert(Elems.begin(), Elems.end());
+ for (auto &SN : ER.Failed)
+ for (auto &[Container, Elems] : SN->defs())
+ Failed[Container].insert(Elems.begin(), Elems.end());
+ } else if (Line.consume_front("fail ")) {
+ size_t NumPairs;
+ if (Line.trim().consumeInteger(10, NumPairs))
+ return make_error<StringError>("expected pair count after 'fail'",
+ inconvertibleErrorCode());
+ ContainerElementsMap FailElems;
+ if (auto Err = parsePairs(Input, NumPairs, FailElems))
+ return Err;
+ auto FailedSNs = G.fail(FailElems);
+ for (auto &SN : FailedSNs)
+ for (auto &[Container, Elems] : SN->defs())
+ Failed[Container].insert(Elems.begin(), Elems.end());
+ } else if (Line.trim() == "end") {
+ return Error::success();
+ } else {
+ return make_error<StringError>("unexpected line: '" + Line + "'",
+ inconvertibleErrorCode());
+ }
+ }
+ return make_error<StringError>("unexpected end of input (missing 'end')",
+ inconvertibleErrorCode());
+ }
+
+private:
+ StringRef getNextLine(StringRef &Input) {
+ StringRef Line;
+ std::tie(Line, Input) = Input.split('\n');
+ return Line.trim();
+ }
+
+ Error parseSuperNodes(StringRef &Input, size_t NumSNs,
+ std::vector<std::unique_ptr<SuperNode>> &SNs) {
+ for (size_t I = 0; I != NumSNs; ++I) {
+ // Parse "sn <index>"
+ StringRef Line = getNextLine(Input);
+ if (!Line.consume_front("sn "))
+ return make_error<StringError>("expected 'sn " + Twine(I) + "'",
+ inconvertibleErrorCode());
+
+ // Parse "defs <count>"
+ Line = getNextLine(Input);
+ if (!Line.consume_front("defs "))
+ return make_error<StringError>("expected 'defs <count>'",
+ inconvertibleErrorCode());
+ size_t NumDefs;
+ if (Line.trim().consumeInteger(10, NumDefs))
+ return make_error<StringError>("expected defs count",
+ inconvertibleErrorCode());
+ ContainerElementsMap Defs;
+ if (auto Err = parsePairs(Input, NumDefs, Defs))
+ return Err;
+
+ // Parse "deps <count>"
+ Line = getNextLine(Input);
+ if (!Line.consume_front("deps "))
+ return make_error<StringError>("expected 'deps <count>'",
+ inconvertibleErrorCode());
+ size_t NumDeps;
+ if (Line.trim().consumeInteger(10, NumDeps))
+ return make_error<StringError>("expected deps count",
+ inconvertibleErrorCode());
+ ContainerElementsMap Deps;
+ if (auto Err = parsePairs(Input, NumDeps, Deps))
+ return Err;
+
+ SNs.push_back(
+ std::make_unique<SuperNode>(std::move(Defs), std::move(Deps)));
+ }
+ return Error::success();
+ }
+
+ Error parsePairs(StringRef &Input, size_t NumPairs,
+ ContainerElementsMap &CEM) {
+ for (size_t I = 0; I != NumPairs; ++I) {
+ StringRef Line = getNextLine(Input);
+ ContainerId C;
+ if (Line.consumeInteger(10, C))
+ return make_error<StringError>("expected container id",
+ inconvertibleErrorCode());
+ Line = Line.trim();
+ ElementId E;
+ if (Line.consumeInteger(10, E))
+ return make_error<StringError>("expected element id",
+ inconvertibleErrorCode());
+ CEM[C].insert(E);
+ }
+ return Error::success();
+ }
+
+ WOG &G;
+ ContainerElementsMap Ready;
+ ContainerElementsMap Failed;
+};
+
+} // namespace llvm::orc::detail
+
+#endif // LLVM_EXECUTIONENGINE_ORC_WAITINGONGRAPHREPLAY_H
diff --git a/llvm/tools/llvm-orc-WaitingOnGraph-replay/CMakeLists.txt b/llvm/tools/llvm-orc-WaitingOnGraph-replay/CMakeLists.txt
new file mode 100644
index 0000000000000..7e5e34e5bd36b
--- /dev/null
+++ b/llvm/tools/llvm-orc-WaitingOnGraph-replay/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(LLVM_LINK_COMPONENTS
+ Support
+ )
+
+add_llvm_utility(llvm-orc-WaitingOnGraph-replay
+ llvm-orc-WaitingOnGraph-replay.cpp
+
+ DEPENDS
+ intrinsics_gen
+)
diff --git a/llvm/tools/llvm-orc-WaitingOnGraph-replay/llvm-orc-WaitingOnGraph-replay.cpp b/llvm/tools/llvm-orc-WaitingOnGraph-replay/llvm-orc-WaitingOnGraph-replay.cpp
new file mode 100644
index 0000000000000..f91c526778874
--- /dev/null
+++ b/llvm/tools/llvm-orc-WaitingOnGraph-replay/llvm-orc-WaitingOnGraph-replay.cpp
@@ -0,0 +1,50 @@
+//===-- llvm-orc-WaitingOnGraph-replay.cpp - WaitingOnGraph replay tool ---===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Replays a WaitingOnGraph recording produced by a
+// WaitingOnGraphStreamRecorder.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+
+static cl::opt<std::string> InputFile(cl::Positional, cl::desc("<input file>"),
+ cl::Required);
+
+Expected<std::unique_ptr<MemoryBuffer>> getFile(const Twine &FileName) {
+ if (auto F = MemoryBuffer::getFile(FileName))
+ return std::move(*F);
+ else
+ return createFileError(FileName, F.getError());
+}
+
+int main(int argc, char *argv[]) {
+
+ InitLLVM X(argc, argv);
+ cl::ParseCommandLineOptions(argc, argv, "WaitingOnGraph replay tool\n");
+
+ ExitOnError ExitOnErr;
+ ExitOnErr.setBanner(std::string(argv[0]) + ": ");
+
+ auto InputBuffer = ExitOnErr(getFile(InputFile));
+
+ using TestGraph = orc::detail::WaitingOnGraph<uintptr_t, uintptr_t>;
+ TestGraph G;
+ orc::detail::WaitingOnGraphBufferReplayer<uintptr_t, uintptr_t> Replayer(G);
+
+ ExitOnErr(Replayer.replay(InputBuffer->getBuffer()));
+
+ return 0;
+}
diff --git a/llvm/unittests/ExecutionEngine/Orc/WaitingOnGraphTest.cpp b/llvm/unittests/ExecutionEngine/Orc/WaitingOnGraphTest.cpp
index 84d5a5fd9e3ed..0a4eaeecd8e4f 100644
--- a/llvm/unittests/ExecutionEngine/Orc/WaitingOnGraphTest.cpp
+++ b/llvm/unittests/ExecutionEngine/Orc/WaitingOnGraphTest.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "llvm/ExecutionEngine/Orc/WaitingOnGraph.h"
+#include "llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h"
+#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
namespace llvm::orc::detail {
@@ -813,3 +815,41 @@ TEST_F(WaitingOnGraphTest, Fail_ZigZag) {
EXPECT_EQ(ER1.Ready.size(), 0U);
EXPECT_EQ(collapseDefs(ER1.Failed, false), merge(Defs0, Defs1));
}
+
+TEST_F(WaitingOnGraphTest, RecordAndReplay) {
+ // Record a sequence of operations, then replay them on a fresh graph.
+ std::string RecordBuf;
+ raw_string_ostream RecordOS(RecordBuf);
+ WaitingOnGraphStreamRecorder<uintptr_t, uintptr_t> Rec(RecordOS);
+
+ SuperNodeBuilder B;
+
+ // Emit a node with no deps -- becomes Ready immediately.
+ ContainerElementsMap Defs0({{0, {0}}});
+ B.add(Defs0, ContainerElementsMap());
+ auto ER0 = integrate(
+ G.emit(TestGraph::simplify(B.takeSuperNodes(), &Rec), GetExternalState));
+ EXPECT_EQ(collapseDefs(ER0.Ready), Defs0);
+ EXPECT_EQ(ER0.Failed.size(), 0U);
+
+ // Emit a node depending on an external dep -- stays Pending.
+ ContainerElementsMap Defs1({{0, {1}}});
+ ContainerElementsMap Deps1({{1, {0}}});
+ B.add(Defs1, Deps1);
+ auto ER1 = integrate(
+ G.emit(TestGraph::simplify(B.takeSuperNodes(), &Rec), GetExternalState));
+ EXPECT_EQ(ER1.Ready.size(), 0U);
+ EXPECT_EQ(ER1.Failed.size(), 0U);
+
+ // Fail the external dep -- causes the pending node to fail.
+ ContainerElementsMap FailElems({{1, {0}}});
+ auto FailedSNs = G.fail(FailElems, &Rec);
+ EXPECT_EQ(FailedSNs.size(), 1U);
+
+ Rec.recordEnd();
+
+ // Now replay on a fresh graph.
+ TestGraph G2;
+ WaitingOnGraphBufferReplayer<uintptr_t, uintptr_t> Replayer(G2);
+ EXPECT_THAT_ERROR(Replayer.replay(RecordBuf), Succeeded());
+}
>From 0d57bacfe6e774cd8f4e36d23185b8012d45869b Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Mon, 9 Mar 2026 17:35:12 +1100
Subject: [PATCH 2/4] Update output format, add capture and replay to
llvm-jitlink, remove standalone test tool.
---
llvm/include/llvm/ExecutionEngine/Orc/Core.h | 10 +
.../Orc/WaitingOnGraphReplay.h | 307 +++++++++++-------
llvm/lib/ExecutionEngine/Orc/Core.cpp | 14 +-
llvm/tools/llvm-jitlink/llvm-jitlink.cpp | 69 +++-
llvm/tools/llvm-jitlink/llvm-jitlink.h | 26 ++
.../CMakeLists.txt | 10 -
.../llvm-orc-WaitingOnGraph-replay.cpp | 50 ---
7 files changed, 306 insertions(+), 180 deletions(-)
delete mode 100644 llvm/tools/llvm-orc-WaitingOnGraph-replay/CMakeLists.txt
delete mode 100644 llvm/tools/llvm-orc-WaitingOnGraph-replay/llvm-orc-WaitingOnGraph-replay.cpp
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/Core.h b/llvm/include/llvm/ExecutionEngine/Orc/Core.h
index 5ca5d347b3088..14e4618cd5afb 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/Core.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/Core.h
@@ -1408,6 +1408,15 @@ class ExecutionSession {
/// Add a symbol name to the SymbolStringPool and return a pointer to it.
SymbolStringPtr intern(StringRef SymName) { return EPC->intern(SymName); }
+ /// Set a WaitingOnGraph::Recorder to capture WaitingOnGraph operations.
+ ///
+ /// This method can be called at most once. If called, it should be called
+ /// before any symbols are materialized.
+ void setWaitingOnGraphRecorder(WaitingOnGraph::Recorder &R) {
+ assert(!GRecorder && "WaitingOnGraph recorder already set");
+ GRecorder = &R;
+ }
+
/// Set the Platform for this ExecutionSession.
void setPlatform(std::unique_ptr<Platform> P) { this->P = std::move(P); }
@@ -1827,6 +1836,7 @@ class ExecutionSession {
std::vector<JITDylibSP> JDs;
WaitingOnGraph G;
+ WaitingOnGraph::Recorder *GRecorder = nullptr;
// FIXME: Remove this (and runOutstandingMUs) once the linking layer works
// with callbacks from asynchronous queries.
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h
index e1bc31670a61a..4cd8acf1466ed 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h
@@ -15,67 +15,94 @@
#include "llvm/ExecutionEngine/Orc/WaitingOnGraph.h"
#include "llvm/Support/Error.h"
+#include <mutex>
+
namespace llvm::orc::detail {
/// Records WaitingOnGraph operations to a text format on a raw_ostream.
template <typename ContainerIdT, typename ElementIdT>
class WaitingOnGraphStreamRecorder
- : public WaitingOnGraph<ContainerIdT, ElementIdT>::Recorder {
- using WOG = WaitingOnGraph<ContainerIdT, ElementIdT>;
+ : public detail::WaitingOnGraph<ContainerIdT, ElementIdT>::Recorder {
+ using WOG = detail::WaitingOnGraph<ContainerIdT, ElementIdT>;
using SuperNode = typename WOG::SuperNode;
using ContainerId = typename WOG::ContainerId;
using ElementId = typename WOG::ElementId;
using ContainerElementsMap = typename WOG::ContainerElementsMap;
+ using ElementSet = typename WOG::ElementSet;
public:
WaitingOnGraphStreamRecorder(raw_ostream &OS) : OS(OS) {}
void
recordSimplify(const std::vector<std::unique_ptr<SuperNode>> &SNs) override {
+ std::scoped_lock<std::mutex> Lock(M);
recordSuperNodes("simplify-and-emit", SNs);
}
void recordFail(const ContainerElementsMap &Failed) override {
- SmallVector<std::pair<ContainerId, ElementId>> Pairs;
- for (auto &[Container, Elements] : Failed)
- for (auto &Elem : Elements)
- Pairs.push_back({Container, Elem});
- llvm::sort(Pairs);
- OS << "fail " << Pairs.size() << "\n";
- for (auto &[Container, Elem] : Pairs)
- OS << " " << Container << " " << Elem << "\n";
+ std::scoped_lock<std::mutex> Lock(M);
+ OS << "fail\n";
+ recordContainerElementsMap(" ", "failed", Failed);
+ }
+
+ void recordEnd() override {
+ std::scoped_lock<std::mutex> Lock(M);
+ OS << "end\n";
}
- void recordEnd() override { OS << "end\n"; }
+ // Should render the container id as a string.
+ virtual void printContainer(const ContainerId &C) {
+ auto I =
+ ContainerIdMap.insert(std::make_pair(C, ContainerIdMap.size())).first;
+ OS << I->second.Id;
+ }
+
+ // Should render the elements of C as a space-separated list (with a space
+ // before the first element).
+ virtual void printElementsIn(const ContainerId &C,
+ const ElementSet &Elements) {
+ assert(ContainerIdMap.count(C));
+ auto &ElementIdMap = ContainerIdMap[C].ElementIdMap;
+ for (auto &E : Elements) {
+ auto I =
+ ElementIdMap.insert(std::make_pair(E, ElementIdMap.size())).first;
+ OS << " " << I->second;
+ }
+ }
private:
+ struct ContainerIdInfo {
+ ContainerIdInfo() = default;
+ ContainerIdInfo(size_t Id) : Id(Id) {}
+ size_t Id = 0;
+ DenseMap<ElementId, size_t> ElementIdMap;
+ };
+ DenseMap<ContainerId, ContainerIdInfo> ContainerIdMap;
+
void recordSuperNodes(StringRef OpName,
const std::vector<std::unique_ptr<SuperNode>> &SNs) {
OS << OpName << " " << SNs.size() << "\n";
for (size_t I = 0; I != SNs.size(); ++I) {
- auto &SN = *SNs[I];
OS << " sn " << I << "\n";
+ recordContainerElementsMap(" ", "defs", SNs[I]->defs());
+ recordContainerElementsMap(" ", "deps", SNs[I]->deps());
+ }
+ }
- SmallVector<std::pair<ContainerId, ElementId>> Defs;
- for (auto &[Container, Elements] : SN.defs())
- for (auto &Elem : Elements)
- Defs.push_back({Container, Elem});
- llvm::sort(Defs);
- OS << " defs " << Defs.size() << "\n";
- for (auto &[Container, Elem] : Defs)
- OS << " " << Container << " " << Elem << "\n";
-
- SmallVector<std::pair<ContainerId, ElementId>> Deps;
- for (auto &[Container, Elements] : SN.deps())
- for (auto &Elem : Elements)
- Deps.push_back({Container, Elem});
- llvm::sort(Deps);
- OS << " deps " << Deps.size() << "\n";
- for (auto &[Container, Elem] : Deps)
- OS << " " << Container << " " << Elem << "\n";
+ void recordContainerElementsMap(StringRef Indent, StringRef MapName,
+ const ContainerElementsMap &M) {
+ OS << Indent << MapName << " " << M.size() << "\n";
+ for (auto &[Container, Elements] : M) {
+ OS << Indent << " container ";
+ printContainer(Container);
+ OS << " " << Elements.size() << "\n";
+ OS << Indent << " elements ";
+ printElementsIn(Container, Elements);
+ OS << "\n";
}
}
+ std::mutex M;
raw_ostream &OS;
};
@@ -84,8 +111,8 @@ class WaitingOnGraphStreamRecorder
///
/// Supports integer ContainerId/ElementId types only (via consumeInteger).
template <typename ContainerIdT, typename ElementIdT>
-class WaitingOnGraphBufferReplayer {
- using WOG = WaitingOnGraph<ContainerIdT, ElementIdT>;
+class WaitingOnGraphBufferReader {
+ using WOG = detail::WaitingOnGraph<ContainerIdT, ElementIdT>;
using SuperNode = typename WOG::SuperNode;
using ContainerId = typename WOG::ContainerId;
using ElementId = typename WOG::ElementId;
@@ -93,9 +120,60 @@ class WaitingOnGraphBufferReplayer {
using ExternalState = typename WOG::ExternalState;
public:
- WaitingOnGraphBufferReplayer(WOG &G) : G(G) {}
+ class Replayer {
+ public:
+ virtual ~Replayer() = default;
+ virtual void
+ simplifyAndEmit(std::vector<std::unique_ptr<SuperNode>> SNs) = 0;
+ virtual void fail(ContainerElementsMap NewlyFailed) = 0;
+ };
+
+ /// Replay all operations on a single thread.
+ class SimpleReplayer : public Replayer {
+ public:
+ void simplifyAndEmit(std::vector<std::unique_ptr<SuperNode>> SNs) override {
+ auto SR = WOG::simplify(std::move(SNs));
+ auto ER = G.emit(std::move(SR),
+ [this](ContainerId C, ElementId E) -> ExternalState {
+ {
+ auto I = Failed.find(C);
+ if (I != Failed.end() && I->second.count(E))
+ return ExternalState::Failed;
+ }
+ {
+ auto I = Ready.find(C);
+ if (I != Ready.end() && I->second.count(E))
+ return ExternalState::Ready;
+ }
+ return ExternalState::None;
+ });
+ for (auto &SN : ER.Ready)
+ for (auto &[Container, Elems] : SN->defs())
+ Ready[Container].insert(Elems.begin(), Elems.end());
+ for (auto &SN : ER.Failed)
+ for (auto &[Container, Elems] : SN->defs())
+ Failed[Container].insert(Elems.begin(), Elems.end());
+ }
- Error replay(StringRef Input) {
+ void fail(ContainerElementsMap NewlyFailed) override {
+ for (auto &[Container, Elems] : NewlyFailed)
+ Failed[Container].insert(Elems.begin(), Elems.end());
+
+ auto FailedSNs = G.fail(NewlyFailed);
+ for (auto &SN : FailedSNs)
+ for (auto &[Container, Elems] : SN->defs())
+ Failed[Container].insert(Elems.begin(), Elems.end());
+ }
+
+ private:
+ WOG G;
+ ContainerElementsMap Ready;
+ ContainerElementsMap Failed;
+ };
+
+ /// Read dumped WaitingOnGraph ops.
+ /// For each operation the relevant callback be called.
+ static Error replay(StringRef Input, Replayer &R) {
while (!Input.empty()) {
StringRef Line = getNextLine(Input);
if (Line.empty())
@@ -107,62 +185,35 @@ class WaitingOnGraphBufferReplayer {
return make_error<StringError>(
"expected supernode count after 'simplify-and-emit'",
inconvertibleErrorCode());
- std::vector<std::unique_ptr<SuperNode>> SNs;
- if (auto Err = parseSuperNodes(Input, NumSNs, SNs))
- return Err;
- auto SR = WOG::simplify(std::move(SNs));
- auto ER = G.emit(std::move(SR),
- [this](ContainerId C, ElementId E) -> ExternalState {
- {
- auto I = Failed.find(C);
- if (I != Failed.end() && I->second.count(E))
- return ExternalState::Failed;
- }
- {
- auto I = Ready.find(C);
- if (I != Ready.end() && I->second.count(E))
- return ExternalState::Ready;
- }
- return ExternalState::None;
- });
- for (auto &SN : ER.Ready)
- for (auto &[Container, Elems] : SN->defs())
- Ready[Container].insert(Elems.begin(), Elems.end());
- for (auto &SN : ER.Failed)
- for (auto &[Container, Elems] : SN->defs())
- Failed[Container].insert(Elems.begin(), Elems.end());
- } else if (Line.consume_front("fail ")) {
- size_t NumPairs;
- if (Line.trim().consumeInteger(10, NumPairs))
- return make_error<StringError>("expected pair count after 'fail'",
- inconvertibleErrorCode());
- ContainerElementsMap FailElems;
- if (auto Err = parsePairs(Input, NumPairs, FailElems))
- return Err;
- auto FailedSNs = G.fail(FailElems);
- for (auto &SN : FailedSNs)
- for (auto &[Container, Elems] : SN->defs())
- Failed[Container].insert(Elems.begin(), Elems.end());
+ auto SNs = parseSuperNodes(Input, NumSNs);
+ if (!SNs)
+ return SNs.takeError();
+ R.simplifyAndEmit(std::move(*SNs));
+ } else if (Line.trim() == "fail") {
+ auto FailElems = parseContainerElementsMap("failed", Input);
+ if (!FailElems)
+ return FailElems.takeError();
+ R.fail(std::move(*FailElems));
} else if (Line.trim() == "end") {
return Error::success();
- } else {
+ } else
return make_error<StringError>("unexpected line: '" + Line + "'",
inconvertibleErrorCode());
- }
}
return make_error<StringError>("unexpected end of input (missing 'end')",
inconvertibleErrorCode());
}
private:
- StringRef getNextLine(StringRef &Input) {
+ static StringRef getNextLine(StringRef &Input) {
StringRef Line;
std::tie(Line, Input) = Input.split('\n');
return Line.trim();
}
- Error parseSuperNodes(StringRef &Input, size_t NumSNs,
- std::vector<std::unique_ptr<SuperNode>> &SNs) {
+ static Expected<std::vector<std::unique_ptr<SuperNode>>>
+ parseSuperNodes(StringRef &Input, size_t NumSNs) {
+ std::vector<std::unique_ptr<SuperNode>> SNs;
for (size_t I = 0; I != NumSNs; ++I) {
// Parse "sn <index>"
StringRef Line = getNextLine(Input);
@@ -170,59 +221,81 @@ class WaitingOnGraphBufferReplayer {
return make_error<StringError>("expected 'sn " + Twine(I) + "'",
inconvertibleErrorCode());
- // Parse "defs <count>"
- Line = getNextLine(Input);
- if (!Line.consume_front("defs "))
- return make_error<StringError>("expected 'defs <count>'",
- inconvertibleErrorCode());
- size_t NumDefs;
- if (Line.trim().consumeInteger(10, NumDefs))
- return make_error<StringError>("expected defs count",
- inconvertibleErrorCode());
- ContainerElementsMap Defs;
- if (auto Err = parsePairs(Input, NumDefs, Defs))
- return Err;
-
- // Parse "deps <count>"
- Line = getNextLine(Input);
- if (!Line.consume_front("deps "))
- return make_error<StringError>("expected 'deps <count>'",
- inconvertibleErrorCode());
- size_t NumDeps;
- if (Line.trim().consumeInteger(10, NumDeps))
- return make_error<StringError>("expected deps count",
- inconvertibleErrorCode());
- ContainerElementsMap Deps;
- if (auto Err = parsePairs(Input, NumDeps, Deps))
- return Err;
+ auto Defs = parseContainerElementsMap("defs", Input);
+ if (!Defs)
+ return Defs.takeError();
+ auto Deps = parseContainerElementsMap("deps", Input);
+ if (!Deps)
+ return Deps.takeError();
SNs.push_back(
- std::make_unique<SuperNode>(std::move(Defs), std::move(Deps)));
+ std::make_unique<SuperNode>(std::move(*Defs), std::move(*Deps)));
}
- return Error::success();
+ return SNs;
}
- Error parsePairs(StringRef &Input, size_t NumPairs,
- ContainerElementsMap &CEM) {
- for (size_t I = 0; I != NumPairs; ++I) {
- StringRef Line = getNextLine(Input);
- ContainerId C;
- if (Line.consumeInteger(10, C))
- return make_error<StringError>("expected container id",
+ static Expected<ContainerElementsMap>
+ parseContainerElementsMap(StringRef ArgName, StringRef &Input) {
+ // Parse "defs <count>"
+ auto Line = getNextLine(Input);
+ if (!Line.consume_front(ArgName))
+ return make_error<StringError>("expected '" + ArgName + " <count>'",
+ inconvertibleErrorCode());
+ size_t NumContainers;
+ if (Line.trim().consumeInteger(10, NumContainers))
+ return make_error<StringError>("expected " + ArgName + " count",
+ inconvertibleErrorCode());
+
+ ContainerElementsMap M;
+ for (size_t I = 0; I != NumContainers; ++I) {
+ Line = getNextLine(Input);
+ if (!Line.consume_front("container "))
+ return make_error<StringError>("expected 'container <id> <count>'",
inconvertibleErrorCode());
+
+ size_t Container;
Line = Line.trim();
- ElementId E;
- if (Line.consumeInteger(10, E))
- return make_error<StringError>("expected element id",
+ if (Line.consumeInteger(10, Container))
+ return make_error<StringError>("expected container id",
+ inconvertibleErrorCode());
+
+ if (M.count(Container))
+ return make_error<StringError>(
+ "expected container id to be unique within " + ArgName,
+ inconvertibleErrorCode());
+
+ size_t NumElements;
+ if (Line.trim().consumeInteger(10, NumElements))
+ return make_error<StringError>("expected elements count",
inconvertibleErrorCode());
- CEM[C].insert(E);
+ if (NumElements == 0)
+ return make_error<StringError>("number of elements for container " +
+ Twine(Container) + " must be > 0",
+ inconvertibleErrorCode());
+
+ Line = getNextLine(Input);
+ if (!Line.consume_front("elements "))
+ return make_error<StringError>("expected 'elements ...'",
+ inconvertibleErrorCode());
+
+ auto &Elements = M[Container];
+ for (size_t J = 0; J != NumElements; ++J) {
+ size_t Elem;
+ Line = Line.trim();
+ if (Line.consumeInteger(10, Elem))
+ return make_error<StringError>("expected element id",
+ inconvertibleErrorCode());
+ if (Elements.count(Elem))
+ return make_error<StringError>(
+ "expected element id to be unique within container " +
+ Twine(Container),
+ inconvertibleErrorCode());
+ Elements.insert(Elem);
+ }
}
- return Error::success();
- }
- WOG &G;
- ContainerElementsMap Ready;
- ContainerElementsMap Failed;
+ return M;
+ }
};
} // namespace llvm::orc::detail
diff --git a/llvm/lib/ExecutionEngine/Orc/Core.cpp b/llvm/lib/ExecutionEngine/Orc/Core.cpp
index 07bb570822d2a..0ade9150dff9e 100644
--- a/llvm/lib/ExecutionEngine/Orc/Core.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Core.cpp
@@ -1590,12 +1590,15 @@ ExecutionSession::~ExecutionSession() {
Error ExecutionSession::endSession() {
LLVM_DEBUG(dbgs() << "Ending ExecutionSession " << this << "\n");
+ WaitingOnGraph::Recorder *GRecorderToEnd = nullptr;
auto JDsToRemove = runSessionLocked([&] {
#ifdef EXPENSIVE_CHECKS
verifySessionState("Entering ExecutionSession::endSession");
#endif
+ if (SessionOpen)
+ GRecorderToEnd = GRecorder;
SessionOpen = false;
return JDs;
});
@@ -1606,6 +1609,9 @@ Error ExecutionSession::endSession() {
Err = joinErrors(std::move(Err), EPC->disconnect());
+ if (GRecorderToEnd)
+ GRecorderToEnd->recordEnd();
+
return Err;
}
@@ -2988,7 +2994,7 @@ Error ExecutionSession::OL_notifyEmitted(
std::move(Residual), WaitingOnGraph::ContainerElementsMap()));
}
- auto SR = WaitingOnGraph::simplify(std::move(SNs));
+ auto SR = WaitingOnGraph::simplify(std::move(SNs), GRecorder);
LLVM_DEBUG({
dbgs() << " Simplified dependencies:\n";
@@ -3106,6 +3112,10 @@ ExecutionSession::IL_failSymbols(JITDylib &JD,
verifySessionState("entering ExecutionSession::IL_failSymbols");
#endif
+ // Early out in the easy case.
+ if (SymbolsToFail.empty())
+ return {};
+
JITDylib::AsynchronousSymbolQuerySet FailedQueries;
auto Fail = [&](JITDylib *FailJD, NonOwningSymbolStringPtr FailSym) {
auto I = FailJD->Symbols.find_as(FailSym);
@@ -3134,7 +3144,7 @@ ExecutionSession::IL_failSymbols(JITDylib &JD,
for (auto &Sym : SymbolsToFail)
JDToFail.insert(NonOwningSymbolStringPtr(Sym));
- auto FailedSNs = G.fail(ToFail);
+ auto FailedSNs = G.fail(ToFail, GRecorder);
for (auto &SN : FailedSNs) {
for (auto &[FailJD, Defs] : SN->defs()) {
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
index f6b86307798cc..cd0218fc6f850 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
@@ -86,7 +86,7 @@ using namespace llvm::orc;
static cl::OptionCategory JITLinkCategory("JITLink Options");
-static cl::list<std::string> InputFiles(cl::Positional, cl::OneOrMore,
+static cl::list<std::string> InputFiles(cl::Positional, cl::ZeroOrMore,
cl::desc("input files"),
cl::cat(JITLinkCategory));
@@ -353,6 +353,16 @@ static cl::opt<bool> ForceLoadObjC(
"classes or extensions"),
cl::init(false), cl::cat(JITLinkCategory));
+static cl::opt<std::string> WaitingOnGraphCapture(
+ "waiting-on-graph-capture",
+ cl::desc("Record WaitingOnGraph operations to the given file"),
+ cl::init(""), cl::cat(JITLinkCategory));
+
+static cl::opt<std::string> WaitingOnGraphReplay(
+ "waiting-on-graph-replay",
+ cl::desc("Replay WaitingOnGraph operations from the given file"),
+ cl::init(""), cl::cat(JITLinkCategory));
+
static ExitOnError ExitOnErr;
static LLVM_ATTRIBUTE_USED void linkComponents() {
@@ -1214,6 +1224,18 @@ Session::Session(std::unique_ptr<ExecutorProcessControl> EPC, Error &Err)
ES.setErrorReporter(reportLLVMJITLinkError);
+ // Attach WaitingOnGraph recorder if requested.
+ if (!WaitingOnGraphCapture.empty()) {
+ if (auto GRecorderOrErr =
+ WaitingOnGraphRecorder::Create(WaitingOnGraphCapture)) {
+ GRecorder = std::move(*GRecorderOrErr);
+ ES.setWaitingOnGraphRecorder(*GRecorder);
+ } else {
+ Err = GRecorderOrErr.takeError();
+ return;
+ }
+ }
+
if (!NoProcessSymbols)
ExitOnErr(loadProcessSymbols(*this));
@@ -1757,6 +1779,15 @@ static std::pair<Triple, SubtargetFeatures> getFirstFileTripleAndFeatures() {
static Error sanitizeArguments(const Triple &TT, const char *ArgV0) {
+ if (InputFiles.empty())
+ return make_error<StringError>(
+ "Not enough positional command line arguments specified! (see "
+ "llvm-jitlink --help)",
+ inconvertibleErrorCode());
+
+ // If we're in replay mode we should never get here.
+ assert(WaitingOnGraphReplay.empty());
+
// -noexec and --args should not be used together.
if (NoExec && !InputArgv.empty())
errs() << "Warning: --args passed to -noexec run will be ignored.\n";
@@ -2874,6 +2905,36 @@ static Error symbolicateBacktraces() {
return Error::success();
}
+static Error waitingOnGraphReplay() {
+ // Warn about ignored options.
+ {
+ bool PrintedHeader = false;
+ for (auto &[OptName, Opt] : cl::getRegisteredOptions()) {
+ if (Opt == &WaitingOnGraphReplay)
+ continue;
+ if (Opt->getNumOccurrences()) {
+ if (!PrintedHeader) {
+ errs() << "Warning: Running in -waiting-on-graph-replay mode. "
+ "The following options will be ignored:\n";
+ PrintedHeader = true;
+ }
+ errs() << " " << OptName << "\n";
+ }
+ }
+ }
+
+ // Read the replay buffer file.
+ auto GraphOpsBuffer = getFile(WaitingOnGraphReplay);
+ if (!GraphOpsBuffer)
+ return GraphOpsBuffer.takeError();
+
+ // Parse and perform ops.
+ using ReplayReader =
+ orc::detail::WaitingOnGraphBufferReader<uintptr_t, uintptr_t>;
+ ReplayReader::SimpleReplayer Replayer;
+ return ReplayReader::replay((*GraphOpsBuffer)->getBuffer(), Replayer);
+}
+
namespace {
struct JITLinkTimers {
TimerGroup JITLinkTG{"llvm-jitlink timers", "timers for llvm-jitlink phases"};
@@ -2894,6 +2955,12 @@ int main(int argc, char *argv[]) {
cl::ParseCommandLineOptions(argc, argv, "llvm jitlink tool");
ExitOnErr.setBanner(std::string(argv[0]) + ": ");
+ // Check for WaitingOnGraph replay mode.
+ if (!WaitingOnGraphReplay.empty()) {
+ ExitOnErr(waitingOnGraphReplay());
+ return 0;
+ }
+
/// If timers are enabled, create a JITLinkTimers instance.
std::unique_ptr<JITLinkTimers> Timers =
ShowTimes ? std::make_unique<JITLinkTimers>() : nullptr;
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.h b/llvm/tools/llvm-jitlink/llvm-jitlink.h
index b3faf9fbe9f5f..58351184d39f8 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.h
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.h
@@ -22,6 +22,7 @@
#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/Orc/RedirectionManager.h"
#include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h"
+#include "llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h"
#include "llvm/ExecutionEngine/RuntimeDyldChecker.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Regex.h"
@@ -33,6 +34,29 @@ namespace llvm {
struct Session {
+ class WaitingOnGraphRecorder
+ : public orc::detail::WaitingOnGraphStreamRecorder<
+ orc::JITDylib *, orc::NonOwningSymbolStringPtr> {
+ public:
+ static Expected<std::unique_ptr<WaitingOnGraphRecorder>>
+ Create(StringRef Path) {
+ std::error_code EC;
+ std::unique_ptr<WaitingOnGraphRecorder> Instance(
+ new WaitingOnGraphRecorder(Path, EC));
+
+ if (EC)
+ return createFileError(Path, EC);
+ return Instance;
+ }
+
+ private:
+ WaitingOnGraphRecorder(StringRef Path, std::error_code EC)
+ : orc::detail::WaitingOnGraphStreamRecorder<
+ orc::JITDylib *, orc::NonOwningSymbolStringPtr>(OutStream),
+ OutStream(Path, EC) {}
+ raw_fd_ostream OutStream;
+ };
+
struct LazyLinkingSupport {
LazyLinkingSupport(
std::unique_ptr<orc::RedirectableSymbolManager> RSMgr,
@@ -143,6 +167,8 @@ struct Session {
private:
Session(std::unique_ptr<orc::ExecutorProcessControl> EPC, Error &Err);
+
+ std::unique_ptr<WaitingOnGraphRecorder> GRecorder;
};
/// Record symbols, GOT entries, stubs, and sections for ELF file.
diff --git a/llvm/tools/llvm-orc-WaitingOnGraph-replay/CMakeLists.txt b/llvm/tools/llvm-orc-WaitingOnGraph-replay/CMakeLists.txt
deleted file mode 100644
index 7e5e34e5bd36b..0000000000000
--- a/llvm/tools/llvm-orc-WaitingOnGraph-replay/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-set(LLVM_LINK_COMPONENTS
- Support
- )
-
-add_llvm_utility(llvm-orc-WaitingOnGraph-replay
- llvm-orc-WaitingOnGraph-replay.cpp
-
- DEPENDS
- intrinsics_gen
-)
diff --git a/llvm/tools/llvm-orc-WaitingOnGraph-replay/llvm-orc-WaitingOnGraph-replay.cpp b/llvm/tools/llvm-orc-WaitingOnGraph-replay/llvm-orc-WaitingOnGraph-replay.cpp
deleted file mode 100644
index f91c526778874..0000000000000
--- a/llvm/tools/llvm-orc-WaitingOnGraph-replay/llvm-orc-WaitingOnGraph-replay.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-//===-- llvm-orc-WaitingOnGraph-replay.cpp - WaitingOnGraph replay tool ---===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-//
-// Replays a WaitingOnGraph recording produced by a
-// WaitingOnGraphStreamRecorder.
-//
-//===----------------------------------------------------------------------===//
-
-#include "llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h"
-#include "llvm/Support/CommandLine.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Support/InitLLVM.h"
-#include "llvm/Support/MemoryBuffer.h"
-#include "llvm/Support/raw_ostream.h"
-
-using namespace llvm;
-
-static cl::opt<std::string> InputFile(cl::Positional, cl::desc("<input file>"),
- cl::Required);
-
-Expected<std::unique_ptr<MemoryBuffer>> getFile(const Twine &FileName) {
- if (auto F = MemoryBuffer::getFile(FileName))
- return std::move(*F);
- else
- return createFileError(FileName, F.getError());
-}
-
-int main(int argc, char *argv[]) {
-
- InitLLVM X(argc, argv);
- cl::ParseCommandLineOptions(argc, argv, "WaitingOnGraph replay tool\n");
-
- ExitOnError ExitOnErr;
- ExitOnErr.setBanner(std::string(argv[0]) + ": ");
-
- auto InputBuffer = ExitOnErr(getFile(InputFile));
-
- using TestGraph = orc::detail::WaitingOnGraph<uintptr_t, uintptr_t>;
- TestGraph G;
- orc::detail::WaitingOnGraphBufferReplayer<uintptr_t, uintptr_t> Replayer(G);
-
- ExitOnErr(Replayer.replay(InputBuffer->getBuffer()));
-
- return 0;
-}
>From 340b7767d97b78c4f21dd609ee8b7d7aec2b62f3 Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Mon, 9 Mar 2026 23:09:44 +1100
Subject: [PATCH 3/4] Switch to fallible iterator pattern, rename some types
for readability, report times from llvm-jtilink replay.
---
llvm/include/llvm/ExecutionEngine/Orc/Core.h | 8 +-
.../llvm/ExecutionEngine/Orc/WaitingOnGraph.h | 8 +-
.../Orc/WaitingOnGraphOpReplay.h | 453 ++++++++++++++++++
.../Orc/WaitingOnGraphReplay.h | 303 ------------
llvm/lib/ExecutionEngine/Orc/Core.cpp | 12 +-
llvm/tools/llvm-jitlink/llvm-jitlink.cpp | 42 +-
llvm/tools/llvm-jitlink/llvm-jitlink.h | 18 +-
.../Orc/WaitingOnGraphTest.cpp | 12 +-
8 files changed, 518 insertions(+), 338 deletions(-)
create mode 100644 llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h
delete mode 100644 llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/Core.h b/llvm/include/llvm/ExecutionEngine/Orc/Core.h
index 14e4618cd5afb..a2805471a7ba5 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/Core.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/Core.h
@@ -1412,9 +1412,9 @@ class ExecutionSession {
///
/// This method can be called at most once. If called, it should be called
/// before any symbols are materialized.
- void setWaitingOnGraphRecorder(WaitingOnGraph::Recorder &R) {
- assert(!GRecorder && "WaitingOnGraph recorder already set");
- GRecorder = &R;
+ void setWaitingOnGraphOpRecorder(WaitingOnGraph::OpRecorder &R) {
+ assert(!GOpRecorder && "WaitingOnGraph recorder already set");
+ GOpRecorder = &R;
}
/// Set the Platform for this ExecutionSession.
@@ -1836,7 +1836,7 @@ class ExecutionSession {
std::vector<JITDylibSP> JDs;
WaitingOnGraph G;
- WaitingOnGraph::Recorder *GRecorder = nullptr;
+ WaitingOnGraph::OpRecorder *GOpRecorder = nullptr;
// FIXME: Remove this (and runOutstandingMUs) once the linking layer works
// with callbacks from asynchronous queries.
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraph.h b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraph.h
index 328ca13824249..42fc96e6bc18d 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraph.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraph.h
@@ -465,9 +465,9 @@ template <typename ContainerIdT, typename ElementIdT> class WaitingOnGraph {
ElemToSuperNodeMap ElemToSN;
};
- class Recorder {
+ class OpRecorder {
public:
- virtual ~Recorder() = default;
+ virtual ~OpRecorder() = default;
virtual void
recordSimplify(const std::vector<std::unique_ptr<SuperNode>> &SNs) = 0;
virtual void recordFail(const ContainerElementsMap &Failed) = 0;
@@ -476,7 +476,7 @@ template <typename ContainerIdT, typename ElementIdT> class WaitingOnGraph {
/// Preprocess a list of SuperNodes to remove all intra-SN dependencies.
static SimplifyResult simplify(std::vector<std::unique_ptr<SuperNode>> SNs,
- Recorder *Rec = nullptr) {
+ OpRecorder *Rec = nullptr) {
if (Rec)
Rec->recordSimplify(SNs);
@@ -566,7 +566,7 @@ template <typename ContainerIdT, typename ElementIdT> class WaitingOnGraph {
/// result, so clients should take whatever actions are needed to mark
/// this as failed in their external representation.
std::vector<std::unique_ptr<SuperNode>>
- fail(const ContainerElementsMap &Failed, Recorder *Rec = nullptr) {
+ fail(const ContainerElementsMap &Failed, OpRecorder *Rec = nullptr) {
if (Rec)
Rec->recordFail(Failed);
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h
new file mode 100644
index 0000000000000..225c595b39405
--- /dev/null
+++ b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h
@@ -0,0 +1,453 @@
+//===------ WaitingOnGraphOpReplay.h - Record/replay APIs -------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Utilities for capturing and replaying WaitingOnGraph operations.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_EXECUTIONENGINE_ORC_WAITINGONGRAPHOPREPLAY_H
+#define LLVM_EXECUTIONENGINE_ORC_WAITINGONGRAPHOPREPLAY_H
+
+#include "llvm/ADT/fallible_iterator.h"
+#include "llvm/ExecutionEngine/Orc/WaitingOnGraph.h"
+#include "llvm/Support/Error.h"
+
+#include <mutex>
+#include <optional>
+#include <variant>
+
+namespace llvm::orc::detail {
+
+/// Records WaitingOnGraph operations to a line-oriented text format on a
+/// raw_ostream. The format is a sequence of operations terminated by "end":
+///
+/// simplify-and-emit <num-supernodes>
+/// sn <index>
+/// defs <num-containers>
+/// container <id> <num-elements>
+/// elements <elem-id>...
+/// deps <num-containers>
+/// ...
+/// fail
+/// failed <num-containers>
+/// container <id> <num-elements>
+/// elements <elem-id>...
+/// end
+///
+/// Container and element ids are integers assigned sequentially by the
+/// recorder. Leading/trailing whitespace on each line is ignored.
+template <typename ContainerIdT, typename ElementIdT>
+class WaitingOnGraphOpStreamRecorder
+ : public detail::WaitingOnGraph<ContainerIdT, ElementIdT>::OpRecorder {
+ using WOG = detail::WaitingOnGraph<ContainerIdT, ElementIdT>;
+ using SuperNode = typename WOG::SuperNode;
+ using ContainerId = typename WOG::ContainerId;
+ using ElementId = typename WOG::ElementId;
+ using ContainerElementsMap = typename WOG::ContainerElementsMap;
+ using ElementSet = typename WOG::ElementSet;
+
+public:
+ WaitingOnGraphOpStreamRecorder(raw_ostream &OS) : OS(OS) {}
+
+ void
+ recordSimplify(const std::vector<std::unique_ptr<SuperNode>> &SNs) override {
+ std::scoped_lock<std::mutex> Lock(M);
+ recordSuperNodes("simplify-and-emit", SNs);
+ }
+
+ void recordFail(const ContainerElementsMap &Failed) override {
+ std::scoped_lock<std::mutex> Lock(M);
+ OS << "fail\n";
+ recordContainerElementsMap(" ", "failed", Failed);
+ }
+
+ void recordEnd() override {
+ std::scoped_lock<std::mutex> Lock(M);
+ OS << "end\n";
+ }
+
+ // Should render the container id as a string.
+ virtual void printContainer(const ContainerId &C) {
+ auto I =
+ ContainerIdMap.insert(std::make_pair(C, ContainerIdMap.size())).first;
+ OS << I->second.Id;
+ }
+
+ // Should render the elements of C as a space-separated list (with a space
+ // before the first element).
+ virtual void printElementsIn(const ContainerId &C,
+ const ElementSet &Elements) {
+ assert(ContainerIdMap.count(C));
+ auto &ElementIdMap = ContainerIdMap[C].ElementIdMap;
+ for (auto &E : Elements) {
+ auto I =
+ ElementIdMap.insert(std::make_pair(E, ElementIdMap.size())).first;
+ OS << " " << I->second;
+ }
+ }
+
+private:
+ struct ContainerIdInfo {
+ ContainerIdInfo() = default;
+ ContainerIdInfo(size_t Id) : Id(Id) {}
+ size_t Id = 0;
+ DenseMap<ElementId, size_t> ElementIdMap;
+ };
+ DenseMap<ContainerId, ContainerIdInfo> ContainerIdMap;
+
+ void recordSuperNodes(StringRef OpName,
+ const std::vector<std::unique_ptr<SuperNode>> &SNs) {
+ OS << OpName << " " << SNs.size() << "\n";
+ for (size_t I = 0; I != SNs.size(); ++I) {
+ OS << " sn " << I << "\n";
+ recordContainerElementsMap(" ", "defs", SNs[I]->defs());
+ recordContainerElementsMap(" ", "deps", SNs[I]->deps());
+ }
+ }
+
+ void recordContainerElementsMap(StringRef Indent, StringRef MapName,
+ const ContainerElementsMap &M) {
+ OS << Indent << MapName << " " << M.size() << "\n";
+ for (auto &[Container, Elements] : M) {
+ OS << Indent << " container ";
+ printContainer(Container);
+ OS << " " << Elements.size() << "\n";
+ OS << Indent << " elements ";
+ printElementsIn(Container, Elements);
+ OS << "\n";
+ }
+ }
+
+ std::mutex M;
+ raw_ostream &OS;
+};
+
+template <typename ContainerIdT, typename ElementIdT>
+class WaitingOnGraphOpReplay {
+public:
+ using Graph = WaitingOnGraph<ContainerIdT, ElementIdT>;
+ using SuperNode = typename Graph::SuperNode;
+ using ContainerId = typename Graph::ContainerId;
+ using ElementId = typename Graph::ElementId;
+ using ContainerElementsMap = typename Graph::ContainerElementsMap;
+ using ExternalState = typename Graph::ExternalState;
+
+ /// A simplify-and-emit operation parsed from the input.
+ struct SimplifyAndEmitOp {
+ std::vector<std::unique_ptr<SuperNode>> SNs;
+ };
+
+ /// A fail operation parsed from the input.
+ struct FailOp {
+ ContainerElementsMap Failed;
+ };
+
+ /// A parsed operation -- either a simplify-and-emit or a fail.
+ using Op = std::variant<SimplifyAndEmitOp, FailOp>;
+
+ /// Replay ops on a given graph.
+ struct Replayer {
+ Replayer(Graph &G) : G(G) {}
+
+ void replay(Op O) {
+ if (auto *SimplifyAndEmit = std::get_if<SimplifyAndEmitOp>(&O))
+ replaySimplifyAndEmit(std::move(SimplifyAndEmit->SNs));
+ else if (auto *Fail = std::get_if<FailOp>(&O))
+ replayFail(std::move(Fail->Failed));
+ }
+
+ void replaySimplifyAndEmit(std::vector<std::unique_ptr<SuperNode>> SNs) {
+ auto SR = Graph::simplify(std::move(SNs));
+ auto ER = G.emit(std::move(SR),
+ [this](ContainerId C, ElementId E) -> ExternalState {
+ {
+ auto I = Failed.find(C);
+ if (I != Failed.end() && I->second.count(E))
+ return ExternalState::Failed;
+ }
+ {
+ auto I = Ready.find(C);
+ if (I != Ready.end() && I->second.count(E))
+ return ExternalState::Ready;
+ }
+ return ExternalState::None;
+ });
+ for (auto &SN : ER.Ready)
+ for (auto &[Container, Elems] : SN->defs())
+ Ready[Container].insert(Elems.begin(), Elems.end());
+ for (auto &SN : ER.Failed)
+ for (auto &[Container, Elems] : SN->defs())
+ Failed[Container].insert(Elems.begin(), Elems.end());
+ }
+
+ void replayFail(ContainerElementsMap NewlyFailed) {
+ for (auto &[Container, Elems] : NewlyFailed)
+ Failed[Container].insert(Elems.begin(), Elems.end());
+
+ auto FailedSNs = G.fail(NewlyFailed);
+ for (auto &SN : FailedSNs)
+ for (auto &[Container, Elems] : SN->defs())
+ Failed[Container].insert(Elems.begin(), Elems.end());
+ }
+
+ Graph &G;
+ ContainerElementsMap Ready;
+ ContainerElementsMap Failed;
+ };
+
+ /// Parser for input buffer.
+ class OpParser {
+ public:
+ using ParseResult = std::pair<std::optional<Op>, StringRef>;
+ virtual ~OpParser() = default;
+ virtual Expected<ParseResult> parseNext(StringRef Input) = 0;
+
+ protected:
+ Expected<ParseResult>
+ parsedSimplifyAndEmit(std::vector<std::unique_ptr<SuperNode>> SNs,
+ StringRef Input) {
+ return ParseResult(SimplifyAndEmitOp{std::move(SNs)}, Input);
+ }
+
+ Expected<ParseResult> parsedFail(ContainerElementsMap NewlyFailed,
+ StringRef Input) {
+ return ParseResult(FailOp{std::move(NewlyFailed)}, Input);
+ }
+
+ Expected<ParseResult> parsedEnd(StringRef Input) {
+ return ParseResult(std::nullopt, Input);
+ }
+ };
+
+ /// Fallible iterator for iterating over WaitingOnGraph ops.
+ class OpIterator {
+ public:
+ /// Default constructed fallible iterator. Serves as end value.
+ OpIterator() = default;
+
+ /// Construct a fallible iterator reading from the given input buffer using
+ /// the given parser.
+ OpIterator(std::shared_ptr<OpParser> P, StringRef Input)
+ : P(std::move(P)), Input(Input), PrevInput(Input) {}
+
+ OpIterator(const OpIterator &Other)
+ : P(Other.P), Input(Other.PrevInput), PrevInput(Other.PrevInput) {
+ // We can't just copy Op, we need to re-parse.
+ if (this->P)
+ cantFail(inc());
+ }
+
+ OpIterator &operator=(const OpIterator &Other) {
+ P = Other.P;
+ Input = PrevInput = Other.PrevInput;
+ if (this->P)
+ cantFail(inc());
+ return *this;
+ }
+
+ OpIterator(OpIterator &&) = default;
+ OpIterator &operator=(OpIterator &&) = default;
+
+ /// Move to next record.
+ Error inc() {
+ PrevInput = Input;
+ auto PR = P->parseNext(Input);
+ if (!PR)
+ return PR.takeError();
+ std::tie(CurOp, Input) = std::move(*PR);
+ if (!CurOp) {
+ P = nullptr;
+ Input = "";
+ }
+ return Error::success();
+ }
+
+ // Dereference. Note: Moves op type.
+ Op &operator*() {
+ assert(CurOp && "Dereferencing end/invalid iterator");
+ return *CurOp;
+ }
+
+ // Dereference. Note: Moves op type.
+ const Op &operator*() const {
+ assert(CurOp && "Dereferencing end/invalid iterator");
+ return *CurOp;
+ }
+
+ /// Compare iterators. End iterators compare equal.
+ friend bool operator==(const OpIterator &LHS, const OpIterator &RHS) {
+ return LHS.P == RHS.P && LHS.Input == RHS.Input;
+ }
+
+ friend bool operator!=(const OpIterator &LHS, const OpIterator &RHS) {
+ return !(LHS == RHS);
+ }
+
+ private:
+ std::shared_ptr<OpParser> P;
+ StringRef Input, PrevInput;
+ std::optional<Op> CurOp;
+ };
+};
+
+/// Returns a fallible iterator range over the operations in the given buffer.
+/// The buffer should contain text in the format produced by
+/// WaitingOnGraphOpStreamRecorder. Parsing errors are reported through Err.
+template <typename ContainerIdT, typename ElementIdT>
+iterator_range<fallible_iterator<
+ typename WaitingOnGraphOpReplay<ContainerIdT, ElementIdT>::OpIterator>>
+readWaitingOnGraphOpsFromBuffer(StringRef InputBuffer, Error &Err) {
+
+ using Replay = WaitingOnGraphOpReplay<ContainerIdT, ElementIdT>;
+
+ class Parser : public Replay::OpParser {
+ public:
+ using ParseResult = typename Replay::OpParser::ParseResult;
+ using SuperNode = typename Replay::SuperNode;
+ using ContainerElementsMap = typename Replay::ContainerElementsMap;
+
+ /// Parse the next operation from Input into CurrentOp.
+ /// Sets IsEnd on "end" keyword. Returns Error on parse failure.
+ Expected<ParseResult> parseNext(StringRef Input) override {
+ auto Line = getNextLine(Input);
+
+ if (Line.empty())
+ return make_error<StringError>(
+ "unexpected end of input (missing 'end')",
+ inconvertibleErrorCode());
+
+ if (Line.consume_front("simplify-and-emit ")) {
+ size_t NumSNs;
+ if (Line.trim().consumeInteger(10, NumSNs))
+ return make_error<StringError>(
+ "expected supernode count after 'simplify-and-emit'",
+ inconvertibleErrorCode());
+ auto SNs = parseSuperNodes(Input, NumSNs);
+ if (!SNs)
+ return SNs.takeError();
+ return this->parsedSimplifyAndEmit(std::move(*SNs), Input);
+ } else if (Line.trim() == "fail") {
+ auto FailElems = parseContainerElementsMap("failed", Input);
+ if (!FailElems)
+ return FailElems.takeError();
+ return this->parsedFail(std::move(*FailElems), Input);
+ } else if (Line.trim() == "end")
+ return this->parsedEnd(Input);
+ else
+ return make_error<StringError>("unexpected line: '" + Line + "'",
+ inconvertibleErrorCode());
+ }
+
+ private:
+ static StringRef getNextLine(StringRef &Input) {
+ StringRef Line;
+ // Parse skipping blank lines.
+ do {
+ std::tie(Line, Input) = Input.split('\n');
+ Line = Line.trim();
+ } while (Line.empty() && !Input.empty());
+ return Line;
+ }
+
+ static Expected<std::vector<std::unique_ptr<SuperNode>>>
+ parseSuperNodes(StringRef &Input, size_t NumSNs) {
+ std::vector<std::unique_ptr<SuperNode>> SNs;
+ for (size_t I = 0; I != NumSNs; ++I) {
+ // Parse "sn <index>"
+ StringRef Line = getNextLine(Input);
+ if (!Line.consume_front("sn "))
+ return make_error<StringError>("expected 'sn " + Twine(I) + "'",
+ inconvertibleErrorCode());
+
+ auto Defs = parseContainerElementsMap("defs", Input);
+ if (!Defs)
+ return Defs.takeError();
+ auto Deps = parseContainerElementsMap("deps", Input);
+ if (!Deps)
+ return Deps.takeError();
+
+ SNs.push_back(
+ std::make_unique<SuperNode>(std::move(*Defs), std::move(*Deps)));
+ }
+ return SNs;
+ }
+
+ static Expected<ContainerElementsMap>
+ parseContainerElementsMap(StringRef ArgName, StringRef &Input) {
+ // Parse "defs <count>"
+ auto Line = getNextLine(Input);
+ if (!Line.consume_front(ArgName))
+ return make_error<StringError>("expected '" + ArgName + " <count>'",
+ inconvertibleErrorCode());
+ size_t NumContainers;
+ if (Line.trim().consumeInteger(10, NumContainers))
+ return make_error<StringError>("expected " + ArgName + " count",
+ inconvertibleErrorCode());
+
+ ContainerElementsMap M;
+ for (size_t I = 0; I != NumContainers; ++I) {
+ Line = getNextLine(Input);
+ if (!Line.consume_front("container "))
+ return make_error<StringError>("expected 'container <id> <count>'",
+ inconvertibleErrorCode());
+
+ size_t Container;
+ Line = Line.trim();
+ if (Line.consumeInteger(10, Container))
+ return make_error<StringError>("expected container id",
+ inconvertibleErrorCode());
+
+ if (M.count(Container))
+ return make_error<StringError>(
+ "expected container id to be unique within " + ArgName,
+ inconvertibleErrorCode());
+
+ size_t NumElements;
+ if (Line.trim().consumeInteger(10, NumElements))
+ return make_error<StringError>("expected elements count",
+ inconvertibleErrorCode());
+ if (NumElements == 0)
+ return make_error<StringError>("number of elements for container " +
+ Twine(Container) + " must be > 0",
+ inconvertibleErrorCode());
+
+ Line = getNextLine(Input);
+ if (!Line.consume_front("elements "))
+ return make_error<StringError>("expected 'elements ...'",
+ inconvertibleErrorCode());
+
+ auto &Elements = M[Container];
+ for (size_t J = 0; J != NumElements; ++J) {
+ size_t Elem;
+ Line = Line.trim();
+ if (Line.consumeInteger(10, Elem))
+ return make_error<StringError>("expected element id",
+ inconvertibleErrorCode());
+ if (Elements.count(Elem))
+ return make_error<StringError>(
+ "expected element id to be unique within container " +
+ Twine(Container),
+ inconvertibleErrorCode());
+ Elements.insert(Elem);
+ }
+ }
+
+ return M;
+ }
+ };
+
+ ErrorAsOutParameter _(Err);
+ typename Replay::OpIterator Begin(std::make_shared<Parser>(), InputBuffer);
+ typename Replay::OpIterator End;
+ if ((Err = Begin.inc())) // Parse first operation.
+ Begin = End;
+ return make_fallible_range(std::move(Begin), std::move(End), Err);
+}
+
+} // namespace llvm::orc::detail
+
+#endif // LLVM_EXECUTIONENGINE_ORC_WAITINGONGRAPHOPREPLAY_H
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h
deleted file mode 100644
index 4cd8acf1466ed..0000000000000
--- a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h
+++ /dev/null
@@ -1,303 +0,0 @@
-//===-------- WaitingOnGraphReplay.h - Record/replay APIs -------*- C++ -*-===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-//
-// Defines WaitingOnGraphStreamRecorder and WaitingOnGraphBufferReplayer.
-//
-//===----------------------------------------------------------------------===//
-#ifndef LLVM_EXECUTIONENGINE_ORC_WAITINGONGRAPHREPLAY_H
-#define LLVM_EXECUTIONENGINE_ORC_WAITINGONGRAPHREPLAY_H
-
-#include "llvm/ExecutionEngine/Orc/WaitingOnGraph.h"
-#include "llvm/Support/Error.h"
-
-#include <mutex>
-
-namespace llvm::orc::detail {
-
-/// Records WaitingOnGraph operations to a text format on a raw_ostream.
-template <typename ContainerIdT, typename ElementIdT>
-class WaitingOnGraphStreamRecorder
- : public detail::WaitingOnGraph<ContainerIdT, ElementIdT>::Recorder {
- using WOG = detail::WaitingOnGraph<ContainerIdT, ElementIdT>;
- using SuperNode = typename WOG::SuperNode;
- using ContainerId = typename WOG::ContainerId;
- using ElementId = typename WOG::ElementId;
- using ContainerElementsMap = typename WOG::ContainerElementsMap;
- using ElementSet = typename WOG::ElementSet;
-
-public:
- WaitingOnGraphStreamRecorder(raw_ostream &OS) : OS(OS) {}
-
- void
- recordSimplify(const std::vector<std::unique_ptr<SuperNode>> &SNs) override {
- std::scoped_lock<std::mutex> Lock(M);
- recordSuperNodes("simplify-and-emit", SNs);
- }
-
- void recordFail(const ContainerElementsMap &Failed) override {
- std::scoped_lock<std::mutex> Lock(M);
- OS << "fail\n";
- recordContainerElementsMap(" ", "failed", Failed);
- }
-
- void recordEnd() override {
- std::scoped_lock<std::mutex> Lock(M);
- OS << "end\n";
- }
-
- // Should render the container id as a string.
- virtual void printContainer(const ContainerId &C) {
- auto I =
- ContainerIdMap.insert(std::make_pair(C, ContainerIdMap.size())).first;
- OS << I->second.Id;
- }
-
- // Should render the elements of C as a space-separated list (with a space
- // before the first element).
- virtual void printElementsIn(const ContainerId &C,
- const ElementSet &Elements) {
- assert(ContainerIdMap.count(C));
- auto &ElementIdMap = ContainerIdMap[C].ElementIdMap;
- for (auto &E : Elements) {
- auto I =
- ElementIdMap.insert(std::make_pair(E, ElementIdMap.size())).first;
- OS << " " << I->second;
- }
- }
-
-private:
- struct ContainerIdInfo {
- ContainerIdInfo() = default;
- ContainerIdInfo(size_t Id) : Id(Id) {}
- size_t Id = 0;
- DenseMap<ElementId, size_t> ElementIdMap;
- };
- DenseMap<ContainerId, ContainerIdInfo> ContainerIdMap;
-
- void recordSuperNodes(StringRef OpName,
- const std::vector<std::unique_ptr<SuperNode>> &SNs) {
- OS << OpName << " " << SNs.size() << "\n";
- for (size_t I = 0; I != SNs.size(); ++I) {
- OS << " sn " << I << "\n";
- recordContainerElementsMap(" ", "defs", SNs[I]->defs());
- recordContainerElementsMap(" ", "deps", SNs[I]->deps());
- }
- }
-
- void recordContainerElementsMap(StringRef Indent, StringRef MapName,
- const ContainerElementsMap &M) {
- OS << Indent << MapName << " " << M.size() << "\n";
- for (auto &[Container, Elements] : M) {
- OS << Indent << " container ";
- printContainer(Container);
- OS << " " << Elements.size() << "\n";
- OS << Indent << " elements ";
- printElementsIn(Container, Elements);
- OS << "\n";
- }
- }
-
- std::mutex M;
- raw_ostream &OS;
-};
-
-/// Replays WaitingOnGraph operations from a text buffer produced by
-/// WaitingOnGraphStreamRecorder.
-///
-/// Supports integer ContainerId/ElementId types only (via consumeInteger).
-template <typename ContainerIdT, typename ElementIdT>
-class WaitingOnGraphBufferReader {
- using WOG = detail::WaitingOnGraph<ContainerIdT, ElementIdT>;
- using SuperNode = typename WOG::SuperNode;
- using ContainerId = typename WOG::ContainerId;
- using ElementId = typename WOG::ElementId;
- using ContainerElementsMap = typename WOG::ContainerElementsMap;
- using ExternalState = typename WOG::ExternalState;
-
-public:
- class Replayer {
- public:
- virtual ~Replayer() = default;
- virtual void
- simplifyAndEmit(std::vector<std::unique_ptr<SuperNode>> SNs) = 0;
- virtual void fail(ContainerElementsMap NewlyFailed) = 0;
- };
-
- /// Replay all operations on a single thread.
- class SimpleReplayer : public Replayer {
- public:
- void simplifyAndEmit(std::vector<std::unique_ptr<SuperNode>> SNs) override {
- auto SR = WOG::simplify(std::move(SNs));
- auto ER = G.emit(std::move(SR),
- [this](ContainerId C, ElementId E) -> ExternalState {
- {
- auto I = Failed.find(C);
- if (I != Failed.end() && I->second.count(E))
- return ExternalState::Failed;
- }
- {
- auto I = Ready.find(C);
- if (I != Ready.end() && I->second.count(E))
- return ExternalState::Ready;
- }
- return ExternalState::None;
- });
- for (auto &SN : ER.Ready)
- for (auto &[Container, Elems] : SN->defs())
- Ready[Container].insert(Elems.begin(), Elems.end());
- for (auto &SN : ER.Failed)
- for (auto &[Container, Elems] : SN->defs())
- Failed[Container].insert(Elems.begin(), Elems.end());
- }
-
- void fail(ContainerElementsMap NewlyFailed) override {
- for (auto &[Container, Elems] : NewlyFailed)
- Failed[Container].insert(Elems.begin(), Elems.end());
-
- auto FailedSNs = G.fail(NewlyFailed);
- for (auto &SN : FailedSNs)
- for (auto &[Container, Elems] : SN->defs())
- Failed[Container].insert(Elems.begin(), Elems.end());
- }
-
- private:
- WOG G;
- ContainerElementsMap Ready;
- ContainerElementsMap Failed;
- };
-
- /// Read dumped WaitingOnGraph ops.
- /// For each operation the relevant callback be called.
- static Error replay(StringRef Input, Replayer &R) {
- while (!Input.empty()) {
- StringRef Line = getNextLine(Input);
- if (Line.empty())
- continue;
-
- if (Line.consume_front("simplify-and-emit ")) {
- size_t NumSNs;
- if (Line.trim().consumeInteger(10, NumSNs))
- return make_error<StringError>(
- "expected supernode count after 'simplify-and-emit'",
- inconvertibleErrorCode());
- auto SNs = parseSuperNodes(Input, NumSNs);
- if (!SNs)
- return SNs.takeError();
- R.simplifyAndEmit(std::move(*SNs));
- } else if (Line.trim() == "fail") {
- auto FailElems = parseContainerElementsMap("failed", Input);
- if (!FailElems)
- return FailElems.takeError();
- R.fail(std::move(*FailElems));
- } else if (Line.trim() == "end") {
- return Error::success();
- } else
- return make_error<StringError>("unexpected line: '" + Line + "'",
- inconvertibleErrorCode());
- }
- return make_error<StringError>("unexpected end of input (missing 'end')",
- inconvertibleErrorCode());
- }
-
-private:
- static StringRef getNextLine(StringRef &Input) {
- StringRef Line;
- std::tie(Line, Input) = Input.split('\n');
- return Line.trim();
- }
-
- static Expected<std::vector<std::unique_ptr<SuperNode>>>
- parseSuperNodes(StringRef &Input, size_t NumSNs) {
- std::vector<std::unique_ptr<SuperNode>> SNs;
- for (size_t I = 0; I != NumSNs; ++I) {
- // Parse "sn <index>"
- StringRef Line = getNextLine(Input);
- if (!Line.consume_front("sn "))
- return make_error<StringError>("expected 'sn " + Twine(I) + "'",
- inconvertibleErrorCode());
-
- auto Defs = parseContainerElementsMap("defs", Input);
- if (!Defs)
- return Defs.takeError();
- auto Deps = parseContainerElementsMap("deps", Input);
- if (!Deps)
- return Deps.takeError();
-
- SNs.push_back(
- std::make_unique<SuperNode>(std::move(*Defs), std::move(*Deps)));
- }
- return SNs;
- }
-
- static Expected<ContainerElementsMap>
- parseContainerElementsMap(StringRef ArgName, StringRef &Input) {
- // Parse "defs <count>"
- auto Line = getNextLine(Input);
- if (!Line.consume_front(ArgName))
- return make_error<StringError>("expected '" + ArgName + " <count>'",
- inconvertibleErrorCode());
- size_t NumContainers;
- if (Line.trim().consumeInteger(10, NumContainers))
- return make_error<StringError>("expected " + ArgName + " count",
- inconvertibleErrorCode());
-
- ContainerElementsMap M;
- for (size_t I = 0; I != NumContainers; ++I) {
- Line = getNextLine(Input);
- if (!Line.consume_front("container "))
- return make_error<StringError>("expected 'container <id> <count>'",
- inconvertibleErrorCode());
-
- size_t Container;
- Line = Line.trim();
- if (Line.consumeInteger(10, Container))
- return make_error<StringError>("expected container id",
- inconvertibleErrorCode());
-
- if (M.count(Container))
- return make_error<StringError>(
- "expected container id to be unique within " + ArgName,
- inconvertibleErrorCode());
-
- size_t NumElements;
- if (Line.trim().consumeInteger(10, NumElements))
- return make_error<StringError>("expected elements count",
- inconvertibleErrorCode());
- if (NumElements == 0)
- return make_error<StringError>("number of elements for container " +
- Twine(Container) + " must be > 0",
- inconvertibleErrorCode());
-
- Line = getNextLine(Input);
- if (!Line.consume_front("elements "))
- return make_error<StringError>("expected 'elements ...'",
- inconvertibleErrorCode());
-
- auto &Elements = M[Container];
- for (size_t J = 0; J != NumElements; ++J) {
- size_t Elem;
- Line = Line.trim();
- if (Line.consumeInteger(10, Elem))
- return make_error<StringError>("expected element id",
- inconvertibleErrorCode());
- if (Elements.count(Elem))
- return make_error<StringError>(
- "expected element id to be unique within container " +
- Twine(Container),
- inconvertibleErrorCode());
- Elements.insert(Elem);
- }
- }
-
- return M;
- }
-};
-
-} // namespace llvm::orc::detail
-
-#endif // LLVM_EXECUTIONENGINE_ORC_WAITINGONGRAPHREPLAY_H
diff --git a/llvm/lib/ExecutionEngine/Orc/Core.cpp b/llvm/lib/ExecutionEngine/Orc/Core.cpp
index 0ade9150dff9e..0704ca16282d2 100644
--- a/llvm/lib/ExecutionEngine/Orc/Core.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Core.cpp
@@ -1590,7 +1590,7 @@ ExecutionSession::~ExecutionSession() {
Error ExecutionSession::endSession() {
LLVM_DEBUG(dbgs() << "Ending ExecutionSession " << this << "\n");
- WaitingOnGraph::Recorder *GRecorderToEnd = nullptr;
+ WaitingOnGraph::OpRecorder *GOpRecorderToEnd = nullptr;
auto JDsToRemove = runSessionLocked([&] {
#ifdef EXPENSIVE_CHECKS
@@ -1598,7 +1598,7 @@ Error ExecutionSession::endSession() {
#endif
if (SessionOpen)
- GRecorderToEnd = GRecorder;
+ GOpRecorderToEnd = GOpRecorder;
SessionOpen = false;
return JDs;
});
@@ -1609,8 +1609,8 @@ Error ExecutionSession::endSession() {
Err = joinErrors(std::move(Err), EPC->disconnect());
- if (GRecorderToEnd)
- GRecorderToEnd->recordEnd();
+ if (GOpRecorderToEnd)
+ GOpRecorderToEnd->recordEnd();
return Err;
}
@@ -2994,7 +2994,7 @@ Error ExecutionSession::OL_notifyEmitted(
std::move(Residual), WaitingOnGraph::ContainerElementsMap()));
}
- auto SR = WaitingOnGraph::simplify(std::move(SNs), GRecorder);
+ auto SR = WaitingOnGraph::simplify(std::move(SNs), GOpRecorder);
LLVM_DEBUG({
dbgs() << " Simplified dependencies:\n";
@@ -3144,7 +3144,7 @@ ExecutionSession::IL_failSymbols(JITDylib &JD,
for (auto &Sym : SymbolsToFail)
JDToFail.insert(NonOwningSymbolStringPtr(Sym));
- auto FailedSNs = G.fail(ToFail, GRecorder);
+ auto FailedSNs = G.fail(ToFail, GOpRecorder);
for (auto &SN : FailedSNs) {
for (auto &[FailJD, Defs] : SN->defs()) {
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
index cd0218fc6f850..1da5113030a80 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
@@ -67,6 +67,7 @@
#include "llvm/Support/Process.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/Timer.h"
+#include <chrono>
#include <cstring>
#include <deque>
#include <string>
@@ -1227,9 +1228,9 @@ Session::Session(std::unique_ptr<ExecutorProcessControl> EPC, Error &Err)
// Attach WaitingOnGraph recorder if requested.
if (!WaitingOnGraphCapture.empty()) {
if (auto GRecorderOrErr =
- WaitingOnGraphRecorder::Create(WaitingOnGraphCapture)) {
- GRecorder = std::move(*GRecorderOrErr);
- ES.setWaitingOnGraphRecorder(*GRecorder);
+ WaitingOnGraphOpRecorder::Create(WaitingOnGraphCapture)) {
+ GOpRecorder = std::move(*GRecorderOrErr);
+ ES.setWaitingOnGraphOpRecorder(*GOpRecorder);
} else {
Err = GRecorderOrErr.takeError();
return;
@@ -2928,11 +2929,36 @@ static Error waitingOnGraphReplay() {
if (!GraphOpsBuffer)
return GraphOpsBuffer.takeError();
- // Parse and perform ops.
- using ReplayReader =
- orc::detail::WaitingOnGraphBufferReader<uintptr_t, uintptr_t>;
- ReplayReader::SimpleReplayer Replayer;
- return ReplayReader::replay((*GraphOpsBuffer)->getBuffer(), Replayer);
+ using Replay = orc::detail::WaitingOnGraphOpReplay<uintptr_t, uintptr_t>;
+ using Graph = typename Replay::Graph;
+ using Replayer = typename Replay::Replayer;
+
+ std::vector<typename Replay::Op> RecordedOps;
+
+ // First read the buffer to build the Ops vector. Doing this up-front allows
+ // us to avoid polluting the timings below with the cost of parsing.
+ Error Err = Error::success();
+ for (auto &Op :
+ orc::detail::readWaitingOnGraphOpsFromBuffer<uintptr_t, uintptr_t>(
+ (*GraphOpsBuffer)->getBuffer(), Err))
+ RecordedOps.push_back(std::move(Op));
+ if (Err)
+ return Err;
+
+ // Now replay the Ops:
+ Graph G;
+ Replayer R(G);
+
+ outs() << "Replaying WaitingOnGraph operations from " << WaitingOnGraphReplay
+ << "...\n";
+ auto ReplayStart = std::chrono::high_resolution_clock::now();
+ for (auto &Op : RecordedOps)
+ R.replay(std::move(Op));
+ auto ReplayEnd = std::chrono::high_resolution_clock::now();
+ std::chrono::duration<double> ReplayDiff = ReplayEnd - ReplayStart;
+ outs() << ReplayDiff.count() << "s to replay " << RecordedOps.size()
+ << " ops (wall-clock time)\n";
+ return Error::success();
}
namespace {
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.h b/llvm/tools/llvm-jitlink/llvm-jitlink.h
index 58351184d39f8..420eb756c0b0d 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.h
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.h
@@ -22,7 +22,7 @@
#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/Orc/RedirectionManager.h"
#include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h"
-#include "llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h"
+#include "llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h"
#include "llvm/ExecutionEngine/RuntimeDyldChecker.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Regex.h"
@@ -34,15 +34,15 @@ namespace llvm {
struct Session {
- class WaitingOnGraphRecorder
- : public orc::detail::WaitingOnGraphStreamRecorder<
+ class WaitingOnGraphOpRecorder
+ : public orc::detail::WaitingOnGraphOpStreamRecorder<
orc::JITDylib *, orc::NonOwningSymbolStringPtr> {
public:
- static Expected<std::unique_ptr<WaitingOnGraphRecorder>>
+ static Expected<std::unique_ptr<WaitingOnGraphOpRecorder>>
Create(StringRef Path) {
std::error_code EC;
- std::unique_ptr<WaitingOnGraphRecorder> Instance(
- new WaitingOnGraphRecorder(Path, EC));
+ std::unique_ptr<WaitingOnGraphOpRecorder> Instance(
+ new WaitingOnGraphOpRecorder(Path, EC));
if (EC)
return createFileError(Path, EC);
@@ -50,8 +50,8 @@ struct Session {
}
private:
- WaitingOnGraphRecorder(StringRef Path, std::error_code EC)
- : orc::detail::WaitingOnGraphStreamRecorder<
+ WaitingOnGraphOpRecorder(StringRef Path, std::error_code EC)
+ : orc::detail::WaitingOnGraphOpStreamRecorder<
orc::JITDylib *, orc::NonOwningSymbolStringPtr>(OutStream),
OutStream(Path, EC) {}
raw_fd_ostream OutStream;
@@ -168,7 +168,7 @@ struct Session {
private:
Session(std::unique_ptr<orc::ExecutorProcessControl> EPC, Error &Err);
- std::unique_ptr<WaitingOnGraphRecorder> GRecorder;
+ std::unique_ptr<WaitingOnGraphOpRecorder> GOpRecorder;
};
/// Record symbols, GOT entries, stubs, and sections for ELF file.
diff --git a/llvm/unittests/ExecutionEngine/Orc/WaitingOnGraphTest.cpp b/llvm/unittests/ExecutionEngine/Orc/WaitingOnGraphTest.cpp
index 0a4eaeecd8e4f..4a87550383d47 100644
--- a/llvm/unittests/ExecutionEngine/Orc/WaitingOnGraphTest.cpp
+++ b/llvm/unittests/ExecutionEngine/Orc/WaitingOnGraphTest.cpp
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
#include "llvm/ExecutionEngine/Orc/WaitingOnGraph.h"
-#include "llvm/ExecutionEngine/Orc/WaitingOnGraphReplay.h"
+#include "llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
@@ -820,7 +820,7 @@ TEST_F(WaitingOnGraphTest, RecordAndReplay) {
// Record a sequence of operations, then replay them on a fresh graph.
std::string RecordBuf;
raw_string_ostream RecordOS(RecordBuf);
- WaitingOnGraphStreamRecorder<uintptr_t, uintptr_t> Rec(RecordOS);
+ WaitingOnGraphOpStreamRecorder<uintptr_t, uintptr_t> Rec(RecordOS);
SuperNodeBuilder B;
@@ -850,6 +850,10 @@ TEST_F(WaitingOnGraphTest, RecordAndReplay) {
// Now replay on a fresh graph.
TestGraph G2;
- WaitingOnGraphBufferReplayer<uintptr_t, uintptr_t> Replayer(G2);
- EXPECT_THAT_ERROR(Replayer.replay(RecordBuf), Succeeded());
+ typename WaitingOnGraphOpReplay<uintptr_t, uintptr_t>::Replayer R(G2);
+ Error Err = Error::success();
+ for (auto &Op :
+ readWaitingOnGraphOpsFromBuffer<uintptr_t, uintptr_t>(RecordBuf, Err))
+ R.replay(std::move(Op));
+ EXPECT_THAT_ERROR(std::move(Err), Succeeded());
}
>From 9254c108cd77c8af671d39330570415aa1297ad6 Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Tue, 10 Mar 2026 11:38:40 +1100
Subject: [PATCH 4/4] Add explicit move constructors to try to address bot
failures
---
.../llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h
index 225c595b39405..f7c4c7bd95684 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h
@@ -138,11 +138,20 @@ class WaitingOnGraphOpReplay {
/// A simplify-and-emit operation parsed from the input.
struct SimplifyAndEmitOp {
+ SimplifyAndEmitOp() = default;
+ SimplifyAndEmitOp(SimplifyAndEmitOp &&) = default;
+ SimplifyAndEmitOp &operator=(SimplifyAndEmitOp &&) = default;
+ SimplifyAndEmitOp(std::vector<std::unique_ptr<SuperNode>> SNs)
+ : SNs(std::move(SNs)) {}
std::vector<std::unique_ptr<SuperNode>> SNs;
};
/// A fail operation parsed from the input.
struct FailOp {
+ FailOp() = default;
+ FailOp(FailOp &&) = default;
+ FailOp &operator=(FailOp &&) = default;
+ FailOp(ContainerElementsMap Failed) : Failed(std::move(Failed)) {}
ContainerElementsMap Failed;
};
More information about the llvm-commits
mailing list