[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