[llvm] [Support] Introduce driver for running tools as daemons (PR #193706)

Benjamin Stott via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 23 03:01:41 PDT 2026


https://github.com/BStott6 created https://github.com/llvm/llvm-project/pull/193706

This PR introduces the general LLVM-side code for the [test daemonization project](https://discourse.llvm.org/t/rfc-reducing-process-creation-overhead-in-llvm-regression-tests/88612/9), including the implementation for the daemon IPC protocol. `lib/Support/DaemonDriver.cpp` implements the main function for the daemon, and `include/llvm/Support/ToolInterface.h` introduces the interface that tools can adopt to support daemonization.

Tools must implement the `LLVMTool` interface to support daemonization. A new utility target `ExampleDaemon` is also introduced, which is a faux tool used for testing the daemon IPC implementation - see `test/Support/DaemonDriver`.

See `llvm/docs/DaemonMode.rst` for more information. A working demo of this project for the `llvm/Transforms` tests can be found [here](https://github.com/BStott6/llvm-project/tree/new-daemonized-testing-demo).

>From fdf832d12ea78baa5d52e1c099f8532d6ff2355a Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Thu, 22 Jan 2026 16:26:57 +0000
Subject: [PATCH 1/5] Introduce interface for LLVM tools to be called by the
 daemon driver

---
 llvm/include/llvm/Support/ToolInterface.h | 99 +++++++++++++++++++++++
 llvm/lib/Support/CMakeLists.txt           |  1 +
 llvm/lib/Support/ToolInterface.cpp        | 68 ++++++++++++++++
 3 files changed, 168 insertions(+)
 create mode 100644 llvm/include/llvm/Support/ToolInterface.h
 create mode 100644 llvm/lib/Support/ToolInterface.cpp

diff --git a/llvm/include/llvm/Support/ToolInterface.h b/llvm/include/llvm/Support/ToolInterface.h
new file mode 100644
index 0000000000000..eead2e4a3d6e5
--- /dev/null
+++ b/llvm/include/llvm/Support/ToolInterface.h
@@ -0,0 +1,99 @@
+//===- llvm/Support/ToolInterface.h - Abstract tool interface ---*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Abstract class for LLVM tools that can be re-invoked in the same process.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_TOOLINTERFACE_H
+#define LLVM_SUPPORT_TOOLINTERFACE_H
+
+#include "llvm/Support/ErrorOr.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include <memory>
+
+namespace llvm {
+
+/// Many tools operate on standard input, which is problematic for in-process
+/// execution/daemonization as the process may not want to close its standard
+/// input stream. This class allows the tools to function as if a different
+/// source, either an in-memory string or a file on disk, were standard input.
+/// Tool implementations should read from this class instead of standard input,
+/// for example using `StandardInputSource::getInput` instead of
+/// `MemoryBuffer::getSTDIN` and `StandardInputSource::getFileOrInput` instead
+/// of `MemoryBuffer::getFileOrSTDIN`.
+class LLVM_ABI StandardInputSource final {
+public:
+  /// Construct a `StandardInputSource` that draws input from the process's
+  /// standard input stream. This is used for regular tool invocations.
+  static StandardInputSource fromStdin() {
+    return StandardInputSource(Kind::Stdin, std::string());
+  }
+
+  /// Construct a `StandardInputSource` that draws input from a file.
+  static StandardInputSource fromFile(std::string &&Filename) {
+    return StandardInputSource(Kind::File, std::move(Filename));
+  }
+
+  /// Construct a `StandardInputSource` that pretends that the given string
+  /// is the contents of standard input.
+  static StandardInputSource fromString(std::string &&StringValue) {
+    return StandardInputSource(Kind::String, std::move(StringValue));
+  }
+
+  /// Replacement for `MemoryBuffer::getSTDIN`. The returned memory buffer
+  /// always has the identifier "<stdin>".
+  ErrorOr<std::unique_ptr<MemoryBuffer>> getInput() const;
+
+  /// Replacement for `MemoryBuffer::getFileOrInput`. Returns `getInput()` if
+  /// the file name is "-", otherwise returns a memory buffer of the file.
+  ErrorOr<std::unique_ptr<MemoryBuffer>> getFileOrInput(StringRef Filename,
+                                                        bool IsText) const {
+    if (Filename == "-")
+      return getInput();
+
+    return MemoryBuffer::getFile(Filename, IsText);
+  }
+
+private:
+  enum class Kind {
+    Stdin,
+    File,
+    String,
+  };
+
+  StandardInputSource(Kind SourceKind, std::string &&StringValue)
+      : SourceKind(SourceKind), StringValue(StringValue) {}
+
+  Kind SourceKind;
+  std::string StringValue;
+};
+
+/// This class represents a shared interface for LLVM tools that can be
+/// re-invoked in the same process, for example when run by the daemon driver.
+class LLVM_ABI LLVMTool {
+public:
+  virtual ~LLVMTool() = default;
+
+  /// This is the function called to run the tool with a given input and set of
+  /// arguments. All code to run the tool except for one-time initialization and
+  /// finalization (for example `InitLLVM`) should be done in this function.
+  ///
+  /// Note that as `run` may use global state, it is not thread-safe.
+  virtual int run(int Argc, char **Argv,
+                  const StandardInputSource &InputSource) = 0;
+
+  /// This function is called between `run` invocations to reset any persistent
+  /// state, for example static command line options, statistics and debug
+  /// counters, for the next invocation. This function is not called when the
+  /// tool is run normally.
+  virtual void resetState() = 0;
+};
+} // namespace llvm
+
+#endif // LLVM_SUPPORT_TOOLINTERFACE_H
diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index e8d505f218b69..3f6562167e2a5 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -269,6 +269,7 @@ add_llvm_component_library(LLVMSupport
   ThreadPool.cpp
   TimeProfiler.cpp
   Timer.cpp
+  ToolInterface.cpp
   ToolOutputFile.cpp
   TrieRawHashMap.cpp
   Twine.cpp
diff --git a/llvm/lib/Support/ToolInterface.cpp b/llvm/lib/Support/ToolInterface.cpp
new file mode 100644
index 0000000000000..ea8c6a9a5cc65
--- /dev/null
+++ b/llvm/lib/Support/ToolInterface.cpp
@@ -0,0 +1,68 @@
+//===- llvm/Support/ToolInterface.cpp - Abstract tool interface -*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Abstract class for LLVM tools that can be re-invoked in the same process.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/ToolInterface.h"
+
+using namespace llvm;
+
+namespace {
+/// Wraps a `MemoryBuffer`, overriding the identifier returned from
+/// `getBufferIdentifier`. This is needed to daemonize lots of tests which
+/// include the buffer identifier in check lines as the module ID.
+class NameOverrideMemoryBuffer : public MemoryBuffer {
+public:
+  NameOverrideMemoryBuffer(std::unique_ptr<MemoryBuffer> &&MB,
+                           StringRef Identifier)
+      : MemoryBuffer(), Inner(std::move(MB)), Identifier(Identifier) {
+    init(Inner->getBufferStart(), Inner->getBufferEnd(),
+         /*RequiresNullTerminator=*/false);
+  }
+
+  StringRef getBufferIdentifier() const override { return Identifier; }
+
+  void dontNeedIfMmap() override { return Inner->dontNeedIfMmap(); }
+
+  void willNeedIfMmap() override { return Inner->willNeedIfMmap(); }
+
+  BufferKind getBufferKind() const override { return Inner->getBufferKind(); }
+
+private:
+  std::unique_ptr<MemoryBuffer> Inner;
+  std::string Identifier;
+};
+
+} // namespace
+
+ErrorOr<std::unique_ptr<MemoryBuffer>>
+llvm::StandardInputSource::getInput() const {
+  // Always pretend to be standard input, so that tests which are affected by
+  // the buffer identifier do not get broken. (For example if there is a check
+  // string that includes the module name)
+  constexpr StringRef BufferIdentifier = "<stdin>";
+
+  switch (SourceKind) {
+  case Kind::Stdin: {
+    return MemoryBuffer::getSTDIN();
+  }
+  case Kind::File: {
+    auto Buffer = MemoryBuffer::getFile(StringValue, /*IsText=*/true);
+    if (!Buffer)
+      return Buffer.getError();
+
+    return std::make_unique<NameOverrideMemoryBuffer>(std::move(*Buffer),
+                                                      BufferIdentifier);
+  }
+  case Kind::String: {
+    return MemoryBuffer::getMemBuffer(StringValue, BufferIdentifier);
+  }
+  }
+}

>From 9456da0a0a3d3749432c28312c62c1a3189461ba Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Thu, 22 Jan 2026 17:20:25 +0000
Subject: [PATCH 2/5] Add documentation for the daemon driver

---
 llvm/docs/DaemonMode.rst | 135 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 llvm/docs/DaemonMode.rst

diff --git a/llvm/docs/DaemonMode.rst b/llvm/docs/DaemonMode.rst
new file mode 100644
index 0000000000000..625ff35ce1326
--- /dev/null
+++ b/llvm/docs/DaemonMode.rst
@@ -0,0 +1,135 @@
+============================
+Running Tools in Daemon Mode
+============================
+
+.. contents::
+   :local:
+
+Introduction
+============
+
+Process creation incurs a significant overhead, especially on Windows. This
+document describes the "daemon mode" execution model for LLVM tools, where
+tools run as persistent daemon processes that can perform many tasks, with
+different arguments and input, in the same process. The original motivation
+for this work is to reduce Lit testing time by replacing tool invocations with
+commands for the daemon - this means that the daemonized tool must be able to
+produce the exact same output as the regular version of the tool.
+
+Adding Daemon Support to a Tool
+===============================
+
+Tools can support daemon mode by implementing the ``LLVMTool`` interface and
+calling ``runWithDaemonSupport(Tool)`` in the main function.
+
+First, the tool's ``main`` function must be refactored into a class implementing
+the ``LLVMTool`` interface. This has two functions, ``run`` and ``resetState``.
+The majority of the body of main (everything aside from ``InitLLVM`` and other
+one-time initialization) must be moved to ``run``. This is the function that is
+called by the daemon for each invocation, and it is also invoked once when the
+tool is run normally. The handling of standard input must also be reworked so that
+the ``StandardInputSource`` provided to ``run`` is respected as standard input.
+For example, ``MemoryBuffer::getSTDIN`` should be replaced by
+``StandardInputSource::getInput()`` and ``MemoryBuffer::getFileOrStdin`` should
+be replaced by ``StandardInputSource::getFileOrInput()``. This is how the daemon
+provides input to the tool, as the input cannot be read from stdin normally
+because the pipe never closes.
+
+``resetState`` must also be implemented, which is responsible for resetting any
+application state, for example command line options, statistics and debug
+counters, for the next invocation. This is not called when the process is run
+normally. Any persistent state which may affect the output and is not reset by
+``resetState`` will cause flaky tests.
+
+Finally, the contents of main that were refactored into ``run`` can be replaced
+by ``runWithDaemonSupport(Tool)``, which will detect the daemon argument and
+either run the tool in daemon mode or run it normally by deferring to ``run``.
+
+Daemon IPC Protocol
+===================
+
+The communication protocol between the daemon and the Lit tester uses four
+pipes: the daemon's ``stdin``, ``stdout`` and ``stderr`` and another pipe,
+called the `status pipe`, for the daemon to communicate responses to Lit.
+
+When the daemon first starts it will send a ``ready`` response to indicate that
+it has initialized correctly and is ready to receive commands.
+
+The daemon accepts `commands` on ``stdin``. Commands must be separated by a
+newline (\n; \r is ignored). The following commands are accepted:
+
+* ``run``: This command takes no arguments. Upon receiving this command, the
+  daemon will run the tool, whose output will be sent on ``stdout`` and ``stderr``
+  as usual (unless ``stderr`` is redirected). When the tool returns, a
+  ``returned`` response indicating the exit code is sent.
+
+* ``arg``: This command is followed by an integer indicating a number of
+  bytes. The daemon will read this many bytes from ``stdin``; this string will
+  be appended to the list of arguments for the next invocation. Arguments are
+  framed in this way to avoid having to worry about escaping a separator char.
+
+* ``input_string``: This command is followed by an integer indicating a number of
+  bytes. The daemon will read this many bytes from ``stdin``; this string will
+  be used as standard input for the next invocation.
+
+* ``input_file``: This command is followed by a file name. The content of this
+  file will be used as standard input for the next invocation.
+
+* ``cd``: This command is followed by a directory name. The current directory
+  for the daemon is changed to the provided directory until the next invocation
+  finishes. This controls the working directory for the tool, and following
+  ``input_file`` and ``cd`` commands until the next ``run``. start from this
+  directory too.
+
+* ``redirect_stderr_to_stdout``: This causes writes to ``stderr`` to be
+  directed through ``stdout`` for the next invocation.
+
+* ``exit``: The daemon will exit.
+
+The daemon may send the following `responses` along the status
+pipe:
+
+* ``ready``: This is sent when the daemon finishes initialization, to
+  indicate it is ready to receive commands.
+
+* ``error``: This is sent when the daemon encounters an error, for example a
+  badly formed command. The daemon will exit after encountering an error. The
+  managing process should re-raise the error, as this indicates incorrect use of
+  the daemon. The response is followed by a string describing the error.
+
+* ``returned``. This is sent when a task finishes executing. The response is
+  followed by an integer representing the exit code from the task.
+
+If the daemon exits unexpectedly while running the tool, this means that the
+tool itself caused the process to exit. The exit code from the daemon should be
+taken as the exit code for the task, and the daemon should be restarted.
+
+Each command and response has a space before its argument. An example exchange
+may look like:
+
+* Command: ``arg 3`` followed by ``opt``
+
+* Command: ``arg 2`` followed by ``-S``
+
+* Command: ``arg 20`` followed by ``--passes=instcombine``
+
+* Command: ``input_file llvm/test/Transforms/InstCombine/range-check.ll``
+
+* Command: ``run``
+
+* (``stdout`` and ``stderr`` are sent as usual.)
+
+* Response: ``returned 0``
+
+* Command: ``bad command``
+
+* Response: ``error Unexpected command: bad command``
+
+Running a Daemon
+================
+
+Tools are invoked in daemon mode by passing ``--daemon`` as the first command
+line argument. Additionally, ``--daemon-status-pipe`` argument must be provided
+to set the file descriptor or Windows file handle on which the status messages
+are sent. This may take the form ``fd:{N}`` indicating a Unix/CRT file descriptor
+or ``handle:{N}`` indicating a Windows file handle.

>From acf6dd455bdac1622f9659ddbb197f7d0242fec4 Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Thu, 22 Jan 2026 17:02:31 +0000
Subject: [PATCH 3/5] Introduce daemon driver in llvm/Support

---
 llvm/include/llvm/Support/DaemonDriver.h |  35 ++
 llvm/lib/Support/CMakeLists.txt          |   1 +
 llvm/lib/Support/DaemonDriver.cpp        | 530 +++++++++++++++++++++++
 3 files changed, 566 insertions(+)
 create mode 100644 llvm/include/llvm/Support/DaemonDriver.h
 create mode 100644 llvm/lib/Support/DaemonDriver.cpp

diff --git a/llvm/include/llvm/Support/DaemonDriver.h b/llvm/include/llvm/Support/DaemonDriver.h
new file mode 100644
index 0000000000000..40fc24f4e19df
--- /dev/null
+++ b/llvm/include/llvm/Support/DaemonDriver.h
@@ -0,0 +1,35 @@
+//===- llvm/Support/DaemonDriver.h - Daemon driver interface ----*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file provides the function `runWithDaemonSupport` to run a tool
+// which implements `LLVMTool` as a daemon, as described in
+// docs/DaemonDriver.rst.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_DAEMONDRIVER_H
+#define LLVM_SUPPORT_DAEMONDRIVER_H
+
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/ToolInterface.h"
+
+namespace llvm {
+/// This function checks for the `--daemon` command line option and, if it is
+/// present, runs the given tool in daemon mode, as described in
+/// `docs/DaemonMode.rst`. Otherwise, the tool is run
+/// with standard input as the input source - a normal invocation. One-time
+/// initialization logic, for example constructing `InitLLVM` and initializing
+/// passes, should be performed before this function is called. Per-invocation
+/// initialization logic, for example parsing command line options or setting up
+/// the pass pipeline, should be performed inside of the tool's `run` function.
+/// The invoke function is responsible for resetting all global state that may
+/// affect its output on the next invocation.
+LLVM_ABI int runWithDaemonSupport(LLVMTool &Tool, int Argc, char **Argv);
+} // namespace llvm
+
+#endif // LLVM_SUPPORT_DAEMONDRIVER_H
diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index 3f6562167e2a5..2808801c566d6 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -178,6 +178,7 @@ add_llvm_component_library(LLVMSupport
   CrashRecoveryContext.cpp
   CSKYAttributes.cpp
   CSKYAttributeParser.cpp
+  DaemonDriver.cpp
   DataExtractor.cpp
   Debug.cpp
   DebugCounter.cpp
diff --git a/llvm/lib/Support/DaemonDriver.cpp b/llvm/lib/Support/DaemonDriver.cpp
new file mode 100644
index 0000000000000..199a1b0daacb7
--- /dev/null
+++ b/llvm/lib/Support/DaemonDriver.cpp
@@ -0,0 +1,530 @@
+//===- llvm/Support/DaemonDriver.cpp - Daemon driver interface ---- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the interface for tools to be run in "daemon mode",
+// following the IPC protocol as described in docs/DaemonMode.rst.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/DaemonDriver.h"
+
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/Program.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <filesystem>
+#include <optional>
+#include <system_error>
+
+#if defined _WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+using namespace llvm;
+
+// Windows emulated POSIX functions are prefixed by `_`.
+#ifdef _WIN32
+#define CLOSE_FN ::_close
+#define DUP_FN ::_dup
+#define DUP2_FN ::_dup2
+#else
+#define CLOSE_FN ::close
+#define DUP_FN ::dup
+#define DUP2_FN ::dup2
+#endif
+
+// Standard stream fileno macros are not defined on Windows.
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#endif
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+namespace {
+namespace util {
+/// RAII mechanism to redirect a file descriptor to the file pointed to by a
+/// different file descriptor. The destructor will reset the file descriptor to
+/// its original file.
+class ScopedFileRedirect {
+public:
+  /// Redirect `FromFd` to the same file as `ToFd` for the lifetime of this
+  /// object.
+  ScopedFileRedirect(int FromFd, int ToFd) : FromFd(FromFd) {
+    // Store a copy of the original file so that we can restore the original fd
+    // to this copy.
+    CopyFd = DUP_FN(FromFd);
+
+    // Close the source fd and reopen it to the target fd.
+    DUP2_FN(ToFd, FromFd);
+  }
+
+  ~ScopedFileRedirect() {
+    if (Moved)
+      return;
+
+    // Close the source fd and reopen it to the original file.
+    DUP2_FN(CopyFd, FromFd);
+
+    // Close the copied file descriptor, as it's no longer needed.
+    CLOSE_FN(CopyFd);
+  }
+
+  ScopedFileRedirect(ScopedFileRedirect &&Other) {
+    *this = Other;
+    Other.Moved = true;
+  }
+  ScopedFileRedirect &operator=(ScopedFileRedirect &&Other) {
+    *this = Other;
+    Other.Moved = true;
+    return *this;
+  }
+
+private:
+  ScopedFileRedirect(const ScopedFileRedirect &Other) = default;
+  ScopedFileRedirect &operator=(const ScopedFileRedirect &Other) = default;
+
+  int FromFd;
+  int CopyFd;
+  bool Moved = false;
+};
+
+static ErrorOr<std::string> readNextLine(FILE *File) {
+  std::string Result;
+  raw_string_ostream ResultOS(Result);
+
+  constexpr size_t BufSize = 512;
+  char Buf[BufSize];
+
+  // Read from the file, appending to the result, until a new line character
+  // is found.
+  while (std::fgets(Buf, BufSize, File)) {
+    ResultOS << Buf;
+
+    if (std::strchr(Buf, '\n'))
+      break;
+  }
+
+  if (std::ferror(File))
+    return std::make_error_code(static_cast<std::errc>(errno));
+
+  return Result;
+}
+
+} // namespace util
+
+/// Status code returned if the daemon fails to initialize, for example due to
+/// incorrect command line arguments.
+constexpr int StatusInitError = 2;
+/// Status code returned if the daemon receives a malformed command.
+constexpr int StatusCommandError = 3;
+
+/// This should only be used before the status pipe is set up - after,
+/// errors are reported to the user via the status pipe.
+[[noreturn]] static void reportInitError(const Twine &Err) {
+  errs() << "[daemon] Error: " << Err << "\n";
+  std::exit(StatusInitError);
+}
+
+/// Returns true if ``--daemon`` is passed as the first command line argument.
+static bool detectDaemonArg(int Argc, char **Argv) {
+  // `--daemon` must be the first argument.
+  return Argc >= 2 && Argv[1] == StringRef("--daemon");
+}
+
+struct DaemonCommandLineOptions {
+  bool DaemonModeEnabled;
+  std::string StatusPipe;
+};
+
+// Creates the command line options for configuring the daemon. The returned
+// reference points to a static struct where the option values will be stored.
+static const DaemonCommandLineOptions &initializeDaemonCommandLineOptions() {
+  // This is declared as a static local variables in this function rather than
+  // a static global variable as is common in LLVM to avoid adding global
+  // constructors and destructors to the program, which is forbidden in the
+  // Support library.
+  static DaemonCommandLineOptions Options;
+
+  static cl::opt<bool, true> DaemonModeEnabledOpt(
+      "daemon", cl::location(Options.DaemonModeEnabled), cl::init(false));
+
+  static cl::opt<std::string, true> StatusPipeOpt(
+      "daemon-status-pipe",
+      cl::desc("File to which the daemon tool will send status messages. May "
+               "be 'path:{filepath}', 'fd:{file descriptor}' or "
+               "'handle:{Windows file handle}'"),
+      cl::location(Options.StatusPipe), cl::init(""));
+
+  return Options;
+}
+
+/// Command sent to invoke the tool.
+struct RunCommand {
+  static constexpr StringRef Prefix = "run";
+
+  static Expected<RunCommand> parse(StringRef Remaining) {
+    Remaining = Remaining.trim();
+
+    if (!Remaining.trim().empty())
+      return createStringError("Unexpected trailing characters in command");
+
+    return RunCommand();
+  }
+};
+
+/// Command providing a command line argument for the tool as a framed string.
+struct ArgCommand {
+  static constexpr StringRef Prefix = "arg";
+
+  static Expected<ArgCommand> parse(StringRef Remaining) {
+    Remaining = Remaining.trim();
+
+    size_t ExpectedLength;
+    if (Remaining.consumeInteger(10, ExpectedLength))
+      return createStringError("Expected integer");
+
+    if (!Remaining.empty())
+      return createStringError("Unexpected trailing characters in command");
+
+    return ArgCommand{ExpectedLength};
+  }
+
+  size_t ExpectedLength;
+};
+
+// Command providing standard input for the tool as a framed string.
+struct InputStringCommand {
+  static constexpr StringRef Prefix = "input_string";
+
+  static Expected<InputStringCommand> parse(StringRef Remaining) {
+    Remaining = Remaining.trim();
+
+    size_t ExpectedLength;
+    if (Remaining.consumeInteger(10, ExpectedLength))
+      return createStringError("Expected integer");
+
+    if (!Remaining.trim().empty())
+      return createStringError("Unexpected trailing characters in command");
+
+    return InputStringCommand{ExpectedLength};
+  }
+
+  size_t ExpectedLength;
+};
+
+// Command providing standard input for the tool to be loaded from a file.
+struct InputFileCommand {
+  static constexpr StringRef Prefix = "input_file";
+
+  static Expected<InputFileCommand> parse(StringRef Remaining) {
+    Remaining = Remaining.trim();
+
+    return InputFileCommand{Remaining};
+  }
+
+  StringRef Path;
+};
+
+// Command changing the current working directory for the daemon.
+struct ChangeDirectoryCommand {
+  static constexpr StringRef Prefix = "cd";
+
+  static Expected<ChangeDirectoryCommand> parse(StringRef Remaining) {
+    Remaining = Remaining.trim();
+
+    return ChangeDirectoryCommand{Remaining};
+  }
+
+  StringRef Path;
+};
+
+/// Command telling the daemon to send the tool's standard error content through
+/// its standard output stream, to maintain the ordering between stderr and
+/// stdout.
+struct RedirectStderrToStdoutCommand {
+  static constexpr StringRef Prefix = "redirect_stderr_to_stdout";
+
+  static Expected<RedirectStderrToStdoutCommand> parse(StringRef Remaining) {
+    Remaining = Remaining.trim();
+
+    if (!Remaining.empty())
+      return createStringError("Unexpected trailing characters in command");
+
+    return RedirectStderrToStdoutCommand();
+  }
+};
+
+/// Command telling the daemon to exit.
+struct ExitCommand {
+  static constexpr StringRef Prefix = "exit";
+
+  static Expected<ExitCommand> parse(StringRef Remaining) {
+    Remaining = Remaining.trim();
+
+    if (!Remaining.empty())
+      return createStringError("Unexpected trailing characters in command");
+
+    return ExitCommand();
+  }
+};
+
+/// State to be configured for the next invocation.
+struct InvocationOptions {
+  void reset() { *this = InvocationOptions(); }
+
+  /// String which tool shall pretend is the contents of stdin.
+  std::vector<std::string> Args;
+  StandardInputSource InputSource = StandardInputSource::fromString("");
+  bool RedirectStderrToStdout = false;
+};
+
+/// This class implements the daemon driver functionality.
+class DaemonDriver {
+public:
+  DaemonDriver(LLVMTool &Tool, const DaemonCommandLineOptions &Options)
+      : Tool(Tool), StatusPipeOS(createStatusPipeOS(Options.StatusPipe)),
+        OriginalWorkingDirectory(std::filesystem::current_path()),
+        WorkingDirectoryChanged(false) {};
+
+  int run() {
+    // Ensure stdin is in binary mode to prevent newline translation on Windows
+    // - this not only breaks binary input but also muddles the number of
+    // characters read.
+    if (const std::error_code EC = sys::ChangeStdinToBinary())
+      exitWithError(Twine("Couldn't switch stdin to binary mode: ") +
+                    EC.message());
+
+    // Inform the user that the daemon is ready to receive commands.
+    respondReady();
+
+    while (!feof(stdin)) {
+      const ErrorOr<std::string> Command = util::readNextLine(stdin);
+      if (!Command) {
+        exitWithError(Twine("Error reading standard input: ") +
+                      Command.getError().message());
+      }
+
+      // The command is parsed from left to right, with surrounding whitespace
+      // ignored.
+      StringRef Remaining = StringRef(*Command).trim();
+
+      if (Remaining.empty() || Remaining.starts_with(';')) {
+        // Empty command or comment. These are supported so that tests can be
+        // made more readable.
+        continue;
+      }
+
+      if (tryParseCommand<RunCommand>(Remaining)) {
+        const int ExitCode = runTool();
+        respondReturned(ExitCode);
+        NextInvocation.reset();
+        resetWorkingDirectory();
+      } else if (const std::optional<ArgCommand> Parsed =
+                     tryParseCommand<ArgCommand>(Remaining)) {
+        NextInvocation.Args.push_back(
+            readStringFromStdin(Parsed->ExpectedLength));
+      } else if (const std::optional<InputStringCommand> Parsed =
+                     tryParseCommand<InputStringCommand>(Remaining)) {
+        NextInvocation.InputSource = StandardInputSource::fromString(
+            readStringFromStdin(Parsed->ExpectedLength));
+      } else if (const std::optional<InputFileCommand> Parsed =
+                     tryParseCommand<InputFileCommand>(Remaining)) {
+        NextInvocation.InputSource =
+            StandardInputSource::fromFile(std::string(Parsed->Path));
+      } else if (const std::optional<ChangeDirectoryCommand> Parsed =
+                     tryParseCommand<ChangeDirectoryCommand>(Remaining)) {
+        changeWorkingDirectory(Parsed->Path);
+      } else if (tryParseCommand<RedirectStderrToStdoutCommand>(Remaining)) {
+        NextInvocation.RedirectStderrToStdout = true;
+      } else if (tryParseCommand<ExitCommand>(Remaining)) {
+        break;
+      } else {
+        exitWithError("Unexpected command: " + *Command);
+      }
+    }
+
+    return 0;
+  }
+
+private:
+  static constexpr StringRef ResponseReady = "ready";
+  static constexpr StringRef ResponseReturned = "returned";
+  static constexpr StringRef ResponseError = "error";
+
+  static std::unique_ptr<raw_ostream>
+  createStatusPipeOS(StringRef StatusPipeString) {
+    // `StatusPipeString` may be:
+    // - "path:{file path}"
+    // - "fd:{file descriptor}"
+    // - "handle:{Windows file handle}" (Windows-only)
+    constexpr StringRef ErrorContext = "Parsing option 'daemon-status-pipe': ";
+
+    if (StatusPipeString.consume_front("path:")) {
+      std::error_code EC;
+      auto Writer =
+          std::make_unique<raw_fd_ostream>(StatusPipeString.trim(), EC);
+      if (EC) {
+        reportInitError(ErrorContext + "Couldn't open file '" +
+                        StatusPipeString + "': " + EC.message());
+      }
+      Writer->SetUnbuffered();
+      return Writer;
+    }
+
+    int Fd;
+    if (StatusPipeString.consume_front("fd:")) {
+      const bool Err = StatusPipeString.consumeInteger(10, Fd);
+      if (Err) {
+        reportInitError(ErrorContext + "expected integer "
+                                       "after 'fd:'.");
+      }
+    } else if (StatusPipeString.consume_front("handle:")) {
+#ifdef _WIN32
+      int Handle;
+      bool Err = StatusPipeString.consumeInteger(10, Handle);
+      if (Err) {
+        reportInitError(ErrorContext +
+                        "Parsing option 'daemon-status-pipe': expected integer "
+                        "after 'handle:'.");
+      }
+
+      Fd = _open_osfhandle(Handle, 0);
+#else
+      reportInitError(ErrorContext + "'handle' may only "
+                                     "be specified on Windows");
+#endif
+    } else {
+      reportInitError(ErrorContext + "Unexpected value : '" + StatusPipeString +
+                      "'");
+    }
+
+    // Only close the status pipe if it is not a standard stream.
+    const bool ShouldClose = Fd != STDOUT_FILENO && Fd != STDERR_FILENO;
+    return std::make_unique<raw_fd_ostream>(Fd, ShouldClose,
+                                            /*unbuffered=*/true);
+  }
+
+  template <typename Command>
+  std::optional<Command> tryParseCommand(StringRef Remaining) const {
+    if (!Remaining.consume_front(Command::Prefix))
+      return {};
+
+    Expected<Command> CommandOrError = Command::parse(Remaining);
+    if (!CommandOrError)
+      exitWithError(CommandOrError.takeError());
+
+    return *CommandOrError;
+  }
+
+  template <typename E> [[noreturn]] void exitWithError(const E &Err) const {
+    *StatusPipeOS << ResponseError << " " << Err << "\n";
+    StatusPipeOS->flush();
+    std::exit(StatusCommandError);
+  }
+
+  void respondReady() const {
+    *StatusPipeOS << ResponseReady << "\n";
+    StatusPipeOS->flush();
+  }
+
+  void respondReturned(const int ExitCode) const {
+    *StatusPipeOS << ResponseReturned << ' ' << ExitCode << "\n";
+    StatusPipeOS->flush();
+  }
+
+  int runTool() const {
+    // Convert arguments to C strings, so that they can be passed through
+    // `argc`.
+    SmallVector<char *, 16> ArgsCStr;
+    ArgsCStr.reserve(NextInvocation.Args.size());
+    for (const std::string &Arg : NextInvocation.Args) {
+      // NB: Since C++11, `std::string` is required to have a null terminator
+      // after the data.
+      ArgsCStr.push_back(const_cast<char *>(Arg.data()));
+    }
+
+    std::optional<util::ScopedFileRedirect> StderrRedirect;
+    if (NextInvocation.RedirectStderrToStdout)
+      StderrRedirect.emplace(STDERR_FILENO, STDOUT_FILENO);
+
+    const int ExitCode =
+        Tool.run(ArgsCStr.size(), ArgsCStr.data(), NextInvocation.InputSource);
+
+    Tool.resetState();
+
+    // Important to flush `stdout` and `stderr` after `resetState()`, in case
+    // `resetState()` had any output (for example if an error was encountered).
+    outs().flush();
+    errs().flush();
+
+    return ExitCode;
+  }
+
+  std::string readStringFromStdin(const size_t Len) const {
+    // Read `Len` bytes into `Dest`.
+    std::string Dest;
+    Dest.resize(Len);
+    const size_t Read = fread(Dest.data(), sizeof(char), Len, stdin);
+
+    // Make sure the expected number of bytes was read.
+    if (Read != Len)
+      exitWithError("Missing bytes: expected " + Twine(Len) + " got " +
+                    Twine(Read));
+
+    return Dest;
+  }
+
+  void changeWorkingDirectory(const StringRef Path) {
+    std::filesystem::path FsPath(Path.str());
+    if (!std::filesystem::is_directory(FsPath))
+      exitWithError(Twine("cd: '") + Path + "' is not a directory.");
+
+    std::filesystem::current_path(FsPath);
+    WorkingDirectoryChanged = true;
+  }
+
+  void resetWorkingDirectory() {
+    if (!WorkingDirectoryChanged)
+      return;
+
+    std::filesystem::current_path(OriginalWorkingDirectory);
+    WorkingDirectoryChanged = false;
+  }
+
+  LLVMTool &Tool;
+  InvocationOptions NextInvocation;
+  std::unique_ptr<raw_ostream> StatusPipeOS;
+  std::filesystem::path OriginalWorkingDirectory;
+  bool WorkingDirectoryChanged;
+};
+
+static int runDaemonMode(LLVMTool &Tool, int Argc, char **Argv) {
+  // Parse daemon command line options.
+  const DaemonCommandLineOptions &Options =
+      initializeDaemonCommandLineOptions();
+  cl::ParseCommandLineOptions(Argc, Argv);
+
+  return DaemonDriver(Tool, Options).run();
+}
+} // namespace
+
+LLVM_ABI int llvm::runWithDaemonSupport(LLVMTool &Tool, int Argc, char **Argv) {
+  if (detectDaemonArg(Argc, Argv))
+    return runDaemonMode(Tool, Argc, Argv);
+
+  return Tool.run(Argc, Argv, StandardInputSource::fromStdin());
+}

>From dc9e81f52da3c76c8d00a15a62d6f79fcdc06336 Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Thu, 22 Jan 2026 17:10:01 +0000
Subject: [PATCH 4/5] Introduce 'example daemon' in llvm/utils, to be used to
 test the daemon functionality

---
 llvm/CMakeLists.txt                        |   1 +
 llvm/test/CMakeLists.txt                   |   1 +
 llvm/utils/ExampleDaemon/CMakeLists.txt    |   6 ++
 llvm/utils/ExampleDaemon/ExampleDaemon.cpp | 104 +++++++++++++++++++++
 llvm/utils/ExampleDaemon/README            |   6 ++
 5 files changed, 118 insertions(+)
 create mode 100644 llvm/utils/ExampleDaemon/CMakeLists.txt
 create mode 100644 llvm/utils/ExampleDaemon/ExampleDaemon.cpp
 create mode 100644 llvm/utils/ExampleDaemon/README

diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt
index 6001928f92e37..b4a46194ef903 100644
--- a/llvm/CMakeLists.txt
+++ b/llvm/CMakeLists.txt
@@ -1380,6 +1380,7 @@ add_subdirectory(include)
 add_subdirectory(lib)
 
 if( LLVM_INCLUDE_UTILS )
+  add_subdirectory(utils/ExampleDaemon)
   add_subdirectory(utils/FileCheck)
   add_subdirectory(utils/PerfectShuffle)
   add_subdirectory(utils/count)
diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt
index a027cd754135c..5b879f792452a 100644
--- a/llvm/test/CMakeLists.txt
+++ b/llvm/test/CMakeLists.txt
@@ -72,6 +72,7 @@ set(LLVM_TEST_DEPENDS_COMMON
 set(LLVM_TEST_DEPENDS
   ${LLVM_TEST_DEPENDS_COMMON}
   LLVMWindowsDriver
+  ExampleDaemon
   llc
   lli
   lli-child-target
diff --git a/llvm/utils/ExampleDaemon/CMakeLists.txt b/llvm/utils/ExampleDaemon/CMakeLists.txt
new file mode 100644
index 0000000000000..8ed22b140f88e
--- /dev/null
+++ b/llvm/utils/ExampleDaemon/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_llvm_utility(ExampleDaemon
+    ExampleDaemon.cpp
+  )
+
+target_link_libraries(ExampleDaemon PRIVATE LLVMSupport)
+
diff --git a/llvm/utils/ExampleDaemon/ExampleDaemon.cpp b/llvm/utils/ExampleDaemon/ExampleDaemon.cpp
new file mode 100644
index 0000000000000..dda68031a18b0
--- /dev/null
+++ b/llvm/utils/ExampleDaemon/ExampleDaemon.cpp
@@ -0,0 +1,104 @@
+//===- ExampleDaemon.cpp - Example tool for testing daemon driverexampledaemon.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This is an example tool used for testing the daemon driver functionality.
+// The tool reads input from stdin character-by-character and prints
+// uppercase letters on `stderr` and everything else on `stdout`. It
+// returns the number of times a letter was printed to `stderr`.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/DaemonDriver.h"
+#include "llvm/Support/ErrorOr.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/ToolInterface.h"
+#include <cctype>
+#include <cstdlib>
+#include <filesystem>
+using namespace llvm;
+
+// Global variable used to check that `resetState` is properly called between
+// invocations.
+constexpr int PersistentStateInit = 0;
+static int PersistentState = PersistentStateInit;
+
+static cl::opt<bool> SeparateLowercaseInstead(
+    "separate-lowercase-instead",
+    cl::desc("This option is used to test that command line arguments are "
+             "passed correctly by the daemon driver."),
+    cl::init(false));
+
+static cl::opt<bool>
+    PrintCurrentDirectory("print-current-directory",
+                          cl::desc("This option is used to test the ability of "
+                                   "the daemon to change working directory."),
+                          cl::init(false));
+
+static cl::opt<bool>
+    ExitProcess("exit-process",
+                cl::desc("This option is used to test the behaviour of "
+                         "the daemon when the tool itself exits."),
+                cl::init(false));
+
+class ExampleTool : public LLVMTool {
+public:
+  virtual int run(int Argc, char **Argv,
+                  const StandardInputSource &InputSource) override {
+    // Make sure that `PersistentState` has been reset.
+    assert(PersistentState == PersistentStateInit &&
+           "Persistent state should have been reset.");
+    PersistentState = 1;
+
+    cl::ParseCommandLineOptions(Argc, Argv);
+
+    if (PrintCurrentDirectory)
+      llvm::outs() << std::filesystem::current_path().string() << "\n";
+
+    if (ExitProcess)
+      std::exit(0);
+
+    // Read standard input or get it from `StdinOverride`.
+    std::unique_ptr<MemoryBuffer> InputContent;
+    ErrorOr<std::unique_ptr<MemoryBuffer>> FileOrErr =
+        InputSource.getFileOrInput("-", /*IsText=*/false);
+    if (const std::error_code Err = FileOrErr.getError()) {
+      errs() << "Error reading standard input: " << Err.message() << "\n";
+      return 1;
+    }
+    InputContent.swap(FileOrErr.get());
+
+    int StderrCount = 0;
+    for (const char Char : InputContent->getBuffer()) {
+      if (SeparateLowercaseInstead ? islower(Char) : isupper(Char)) {
+        errs() << Char;
+        errs().flush();
+        StderrCount += 1;
+      } else {
+        outs() << Char;
+        outs().flush();
+      }
+    }
+
+    return StderrCount;
+  };
+
+  virtual void resetState() override {
+    PersistentState = PersistentStateInit;
+    cl::ResetAllOptionOccurrences();
+  }
+};
+
+int main(int argc, char **argv) {
+  InitLLVM X(argc, argv);
+
+  ExampleTool Tool;
+  return runWithDaemonSupport(Tool, argc, argv);
+}
diff --git a/llvm/utils/ExampleDaemon/README b/llvm/utils/ExampleDaemon/README
new file mode 100644
index 0000000000000..edc7ab0c9a3c0
--- /dev/null
+++ b/llvm/utils/ExampleDaemon/README
@@ -0,0 +1,6 @@
+This is an example tool used for testing the daemon driver functionality.
+The tool reads input from stdin character-by-character and prints
+uppercase letters on `stderr` and everything else on `stdout`. It
+returns the number of times a letter was printed to `stderr`. It also takes
+one command line argument which causes it to print lowercase letters to
+`stderr` instead.

>From 6ea48b8b6b21444e2674aee95daf83ed857ca585 Mon Sep 17 00:00:00 2001
From: BStott <Benjamin.Stott at sony.com>
Date: Thu, 22 Jan 2026 17:11:19 +0000
Subject: [PATCH 5/5] Add some tests for the daemon driver using the example
 daemon

---
 .../DaemonDriver/Inputs/change-directory.txt  | 32 +++++++++++++++++++
 .../DaemonDriver/Inputs/file-input-1.txt      |  1 +
 .../DaemonDriver/Inputs/file-input-2.txt      |  1 +
 .../DaemonDriver/Inputs/file-input-3.txt      |  1 +
 .../DaemonDriver/Inputs/file-input.txt        | 18 +++++++++++
 .../DaemonDriver/Inputs/redirect-stderr.txt   | 14 ++++++++
 .../Support/DaemonDriver/Inputs/run-twice.txt |  7 ++++
 .../Support/DaemonDriver/Inputs/str-input.txt | 21 ++++++++++++
 .../Inputs/tool-exits-process.txt             |  5 +++
 .../DaemonDriver/change-directory.test        | 23 +++++++++++++
 .../error-status-pipe-handle-unix.test        |  5 +++
 .../error-status-pipe-invalid-file.test       |  4 +++
 .../test/Support/DaemonDriver/file-input.test | 22 +++++++++++++
 .../Support/DaemonDriver/redirect-stderr.test | 18 +++++++++++
 llvm/test/Support/DaemonDriver/run-twice.test |  7 ++++
 llvm/test/Support/DaemonDriver/str-input.test | 23 +++++++++++++
 .../DaemonDriver/tool-exits-process.test      |  5 +++
 .../DaemonDriver/verify-example-daemon.test   |  9 ++++++
 18 files changed, 216 insertions(+)
 create mode 100644 llvm/test/Support/DaemonDriver/Inputs/change-directory.txt
 create mode 100644 llvm/test/Support/DaemonDriver/Inputs/file-input-1.txt
 create mode 100644 llvm/test/Support/DaemonDriver/Inputs/file-input-2.txt
 create mode 100644 llvm/test/Support/DaemonDriver/Inputs/file-input-3.txt
 create mode 100644 llvm/test/Support/DaemonDriver/Inputs/file-input.txt
 create mode 100644 llvm/test/Support/DaemonDriver/Inputs/redirect-stderr.txt
 create mode 100644 llvm/test/Support/DaemonDriver/Inputs/run-twice.txt
 create mode 100644 llvm/test/Support/DaemonDriver/Inputs/str-input.txt
 create mode 100644 llvm/test/Support/DaemonDriver/Inputs/tool-exits-process.txt
 create mode 100644 llvm/test/Support/DaemonDriver/change-directory.test
 create mode 100644 llvm/test/Support/DaemonDriver/error-status-pipe-handle-unix.test
 create mode 100644 llvm/test/Support/DaemonDriver/error-status-pipe-invalid-file.test
 create mode 100644 llvm/test/Support/DaemonDriver/file-input.test
 create mode 100644 llvm/test/Support/DaemonDriver/redirect-stderr.test
 create mode 100644 llvm/test/Support/DaemonDriver/run-twice.test
 create mode 100644 llvm/test/Support/DaemonDriver/str-input.test
 create mode 100644 llvm/test/Support/DaemonDriver/tool-exits-process.test
 create mode 100644 llvm/test/Support/DaemonDriver/verify-example-daemon.test

diff --git a/llvm/test/Support/DaemonDriver/Inputs/change-directory.txt b/llvm/test/Support/DaemonDriver/Inputs/change-directory.txt
new file mode 100644
index 0000000000000..2d5f0fe775b5a
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/Inputs/change-directory.txt
@@ -0,0 +1,32 @@
+arg 13
+ExampleDaemon
+arg 25
+--print-current-directory
+run
+
+; Checking that we can change directory.
+cd ./A
+arg 13
+ExampleDaemon
+arg 25
+--print-current-directory
+run
+
+; Checking that the directory is properly reset.
+arg 13
+ExampleDaemon
+arg 25
+--print-current-directory
+run
+
+; Checking that input_file is properly scoped to the current directory.
+cd ./A
+arg 13
+ExampleDaemon
+input_file ./b
+run
+
+cd ./DoesNotExist
+arg 13
+ExampleDaemon
+run
diff --git a/llvm/test/Support/DaemonDriver/Inputs/file-input-1.txt b/llvm/test/Support/DaemonDriver/Inputs/file-input-1.txt
new file mode 100644
index 0000000000000..422a0a894cbdd
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/Inputs/file-input-1.txt
@@ -0,0 +1 @@
+aBcDe
diff --git a/llvm/test/Support/DaemonDriver/Inputs/file-input-2.txt b/llvm/test/Support/DaemonDriver/Inputs/file-input-2.txt
new file mode 100644
index 0000000000000..9d453bbdc2c4d
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/Inputs/file-input-2.txt
@@ -0,0 +1 @@
+aBcDeFgH
diff --git a/llvm/test/Support/DaemonDriver/Inputs/file-input-3.txt b/llvm/test/Support/DaemonDriver/Inputs/file-input-3.txt
new file mode 100644
index 0000000000000..5d308e1d060b0
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/Inputs/file-input-3.txt
@@ -0,0 +1 @@
+aaaa
diff --git a/llvm/test/Support/DaemonDriver/Inputs/file-input.txt b/llvm/test/Support/DaemonDriver/Inputs/file-input.txt
new file mode 100644
index 0000000000000..cbf96462c3936
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/Inputs/file-input.txt
@@ -0,0 +1,18 @@
+arg 13
+ExampleDaemon
+input_file SOURCEDIR/Inputs/file-input-1.txt
+run
+
+arg 13
+ExampleDaemon
+arg 28
+--separate-lowercase-instead
+input_file SOURCEDIR/Inputs/file-input-2.txt
+run
+
+arg 13
+ExampleDaemon
+input_file SOURCEDIR/Inputs/file-input-3.txt
+run
+
+exit
diff --git a/llvm/test/Support/DaemonDriver/Inputs/redirect-stderr.txt b/llvm/test/Support/DaemonDriver/Inputs/redirect-stderr.txt
new file mode 100644
index 0000000000000..bb08173546f66
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/Inputs/redirect-stderr.txt
@@ -0,0 +1,14 @@
+arg 13
+ExampleDaemon
+input_string 8
+aBcDeFgH
+redirect_stderr_to_stdout
+run
+
+; Checking that stderr is correctly reset.
+arg 13
+ExampleDaemon
+input_string 8
+aaaaAAAA
+run
+exit
diff --git a/llvm/test/Support/DaemonDriver/Inputs/run-twice.txt b/llvm/test/Support/DaemonDriver/Inputs/run-twice.txt
new file mode 100644
index 0000000000000..b180650785eea
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/Inputs/run-twice.txt
@@ -0,0 +1,7 @@
+arg 13
+ExampleDaemon
+run
+arg 13
+ExampleDaemon
+run
+exit
diff --git a/llvm/test/Support/DaemonDriver/Inputs/str-input.txt b/llvm/test/Support/DaemonDriver/Inputs/str-input.txt
new file mode 100644
index 0000000000000..5d65ca43b2692
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/Inputs/str-input.txt
@@ -0,0 +1,21 @@
+arg 13
+ExampleDaemon
+input_string 5
+aBcDe
+run
+
+arg 13
+ExampleDaemon
+arg 28
+--separate-lowercase-instead
+input_string 8
+aBcDeFgH
+run 
+
+arg 13
+ExampleDaemon
+input_string 4
+aaaa
+run
+
+exit
diff --git a/llvm/test/Support/DaemonDriver/Inputs/tool-exits-process.txt b/llvm/test/Support/DaemonDriver/Inputs/tool-exits-process.txt
new file mode 100644
index 0000000000000..2c39389f57259
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/Inputs/tool-exits-process.txt
@@ -0,0 +1,5 @@
+arg 13
+ExampleDaemon
+arg 14
+--exit-process
+run 
diff --git a/llvm/test/Support/DaemonDriver/change-directory.test b/llvm/test/Support/DaemonDriver/change-directory.test
new file mode 100644
index 0000000000000..0a60e69a3ce74
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/change-directory.test
@@ -0,0 +1,23 @@
+RUN: mkdir %t
+RUN: mkdir %t/A
+RUN: echo "ABC" > %t/A/b
+RUN: cd %t
+RUN: not ExampleDaemon < %S/Inputs/change-directory.txt --daemon --daemon-status-pipe=path:%t.status > %t.stdout 2> %t.stderr
+RUN: FileCheck < %t.status %s --check-prefix=CHECK-STATUS --strict-whitespace
+RUN: FileCheck < %t.stdout %s --check-prefix=CHECK-STDOUT --strict-whitespace -DTEST_DIR=%S -DTEMP_DIR=%t
+END.
+
+CHECK-STATUS: ready
+
+CHECK-STATUS-NEXT: returned 0
+CHECK-STDOUT: [[TEMP_DIR]]
+
+CHECK-STATUS-NEXT: returned 0
+CHECK-STDOUT-NEXT: [[TEMP_DIR]]{{[\/\\]}}A
+
+CHECK-STATUS-NEXT: returned 0
+CHECK-STDOUT-NEXT: [[TEMP_DIR]]
+
+CHECK-STATUS-NEXT: returned 3
+
+CHECK-STATUS-NEXT: error cd: './DoesNotExist' is not a directory.
diff --git a/llvm/test/Support/DaemonDriver/error-status-pipe-handle-unix.test b/llvm/test/Support/DaemonDriver/error-status-pipe-handle-unix.test
new file mode 100644
index 0000000000000..6a539dc4a9c32
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/error-status-pipe-handle-unix.test
@@ -0,0 +1,5 @@
+# This test verifies that the daemon gives the correct error message when a
+# Windows file handle is provided for `--daemon-status-pipe` on Unix
+REQUIRES: !system-windows
+RUN: not ExampleDaemon --daemon --daemon-status-pipe=handle:0 2>&1 | FileCheck %s --match-full-lines
+CHECK: [daemon] Error: Parsing option 'daemon-status-pipe': 'handle' may only be specified on Windows
diff --git a/llvm/test/Support/DaemonDriver/error-status-pipe-invalid-file.test b/llvm/test/Support/DaemonDriver/error-status-pipe-invalid-file.test
new file mode 100644
index 0000000000000..94e7f0c4872af
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/error-status-pipe-invalid-file.test
@@ -0,0 +1,4 @@
+# This test verifies that the daemon gives the correct error message when an
+# invalid path is provided for the status pipe.
+RUN: not ExampleDaemon --daemon --daemon-status-pipe=path:does/not/exist 2>&1 | FileCheck %s --match-full-lines
+CHECK: [daemon] Error: Parsing option 'daemon-status-pipe': Couldn't open file 'does/not/exist': {{.*}}
diff --git a/llvm/test/Support/DaemonDriver/file-input.test b/llvm/test/Support/DaemonDriver/file-input.test
new file mode 100644
index 0000000000000..e05115ae10a4d
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/file-input.test
@@ -0,0 +1,22 @@
+# This test verifies that the tool gives the correct output when the input is
+# provided with "in.file".
+RUN: sed 's at SOURCEDIR@%{/S:regex_replacement}@g' %S/Inputs/file-input.txt | ExampleDaemon --daemon --daemon-status-pipe=path:%t.status > %t.stdout 2> %t.stderr
+RUN: FileCheck < %t.stdout %s --check-prefix=CHECK-STDOUT --strict-whitespace
+RUN: FileCheck < %t.stderr %s --check-prefix=CHECK-STDERR --strict-whitespace
+RUN: FileCheck < %t.status %s --check-prefix=CHECK-STATUS --strict-whitespace
+END.
+CHECK-STATUS: ready
+
+# 1st command.
+CHECK-STATUS-NEXT: returned 2
+CHECK-STDOUT: ace
+CHECK-STDERR: BD
+
+# 2nd command.
+CHECK-STATUS-NEXT: returned 4
+CHECK-STDOUT-NEXT: BDFH
+CHECK-STDERR-SAME: aceg
+
+# 3rd command.
+CHECK-STATUS-NEXT: returned 0
+CHECK-STDOUT-NEXT: aaaa
diff --git a/llvm/test/Support/DaemonDriver/redirect-stderr.test b/llvm/test/Support/DaemonDriver/redirect-stderr.test
new file mode 100644
index 0000000000000..364043f475d8b
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/redirect-stderr.test
@@ -0,0 +1,18 @@
+# This test verifies the "redirect_stdout_to_stderr" command does as it says,
+# and that stderr is properly reset for the next command.
+RUN: ExampleDaemon --daemon --daemon-status-pipe=path:%t.status < %S/Inputs/redirect-stderr.txt > %t.stdout 2> %t.stderr
+RUN: FileCheck < %t.stdout %s --check-prefix=CHECK-STDOUT --strict-whitespace
+RUN: FileCheck < %t.stderr %s --check-prefix=CHECK-STDERR --strict-whitespace
+RUN: FileCheck < %t.status %s --check-prefix=CHECK-STATUS --strict-whitespace
+END.
+CHECK-STATUS: ready
+
+# 1nd command.
+CHECK-STATUS-NEXT: returned 4
+CHECK-STDOUT: aBcDeFgH
+
+# 2nd command.
+CHECK-STATUS-NEXT: returned 4
+CHECK-STDOUT-SAME: aaaa
+CHECK-STDERR: AAAA
+
diff --git a/llvm/test/Support/DaemonDriver/run-twice.test b/llvm/test/Support/DaemonDriver/run-twice.test
new file mode 100644
index 0000000000000..516079fb2b105
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/run-twice.test
@@ -0,0 +1,7 @@
+# This test verifies that the tool can be run twice. The `ExampleDaemon` makes 
+# sure that `resetState()` was called.
+RUN: ExampleDaemon --daemon --daemon-status-pipe=path:%t.status < %S/Inputs/run-twice.txt
+RUN: FileCheck %s < %t.status --match-full-lines
+CHECK: ready
+CHECK-NEXT: returned 0
+CHECK-NEXT: returned 0
diff --git a/llvm/test/Support/DaemonDriver/str-input.test b/llvm/test/Support/DaemonDriver/str-input.test
new file mode 100644
index 0000000000000..441258833d85f
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/str-input.test
@@ -0,0 +1,23 @@
+# This test verifies that the tool gives the correct output when the input is
+# provided with "in.str".
+RUN: ExampleDaemon --daemon --daemon-status-pipe=path:%t.status < %S/Inputs/str-input.txt > %t.stdout 2> %t.stderr
+RUN: FileCheck < %t.stdout %s --check-prefix=CHECK-STDOUT --strict-whitespace
+RUN: FileCheck < %t.stderr %s --check-prefix=CHECK-STDERR --strict-whitespace
+RUN: FileCheck < %t.status %s --check-prefix=CHECK-STATUS --strict-whitespace
+END.
+CHECK-STATUS: ready
+
+# 1st command.
+CHECK-STATUS-NEXT: returned 2
+CHECK-STDOUT: ace
+CHECK-STDERR: BD
+
+# 2nd command.
+CHECK-STATUS-NEXT: returned 4
+CHECK-STDOUT-SAME: BDFH
+CHECK-STDERR-SAME: aceg
+
+# 3rd command.
+CHECK-STATUS-NEXT: returned 0
+CHECK-STDOUT-SAME: aaaa
+
diff --git a/llvm/test/Support/DaemonDriver/tool-exits-process.test b/llvm/test/Support/DaemonDriver/tool-exits-process.test
new file mode 100644
index 0000000000000..0efc8ea8543c4
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/tool-exits-process.test
@@ -0,0 +1,5 @@
+# This test verifies that the daemon driver behaves as expected when the tool
+# itself causes the process to exit. (In this case, the daemon driver exits)
+RUN: ExampleDaemon --daemon --daemon-status-pipe=path:%t.status < %S/Inputs/tool-exits-process.txt
+RUN: FileCheck < %t.status %s
+CHECK: ready
diff --git a/llvm/test/Support/DaemonDriver/verify-example-daemon.test b/llvm/test/Support/DaemonDriver/verify-example-daemon.test
new file mode 100644
index 0000000000000..60c7e059d1809
--- /dev/null
+++ b/llvm/test/Support/DaemonDriver/verify-example-daemon.test
@@ -0,0 +1,9 @@
+This test is verifying that the "ExampleDaemon" tool works as expected when run
+normally (not in daemon mode).
+RUN: echo "testTESTtestTEST" | not ExampleDaemon >%t.stdout 2>%t.stderr
+RUN: FileCheck < %t.stdout %s --check-prefix=CHECK-STDOUT-1
+END.
+CHECK-STDOUT-1: testtest
+RUN: FileCheck < %t.stderr %s --check-prefix=CHECK-STDERR-1
+CHECK-STDERR-1: TESTTEST
+



More information about the llvm-commits mailing list