[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