[llvm] f8f6c0b - Revert "[LLVM] Add GNU make jobserver support (#145131)"
Yaxun Liu via llvm-commits
llvm-commits at lists.llvm.org
Fri Oct 3 07:36:55 PDT 2025
Author: Yaxun (Sam) Liu
Date: 2025-10-03T10:35:03-04:00
New Revision: f8f6c0b6ecb9d87ead48246d4fadf6048207375d
URL: https://github.com/llvm/llvm-project/commit/f8f6c0b6ecb9d87ead48246d4fadf6048207375d
DIFF: https://github.com/llvm/llvm-project/commit/f8f6c0b6ecb9d87ead48246d4fadf6048207375d.diff
LOG: Revert "[LLVM] Add GNU make jobserver support (#145131)"
revert this patch due to failure in unittests/Support, e.g.
https://lab.llvm.org/buildbot/#/builders/33/builds/24178/steps/6/logs/FAIL__LLVM-Unit__SupportTests_61
This reverts commit ffc503edd0a2d07121232fe204e480fc29631a90.
Added:
Modified:
clang/include/clang/Driver/Options.td
clang/lib/Driver/ToolChains/Clang.cpp
clang/test/Driver/hip-options.hip
clang/test/Driver/linker-wrapper.c
clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
llvm/include/llvm/Support/ThreadPool.h
llvm/include/llvm/Support/Threading.h
llvm/lib/Support/CMakeLists.txt
llvm/lib/Support/Parallel.cpp
llvm/lib/Support/ThreadPool.cpp
llvm/lib/Support/Threading.cpp
llvm/unittests/Support/CMakeLists.txt
Removed:
llvm/include/llvm/Support/Jobserver.h
llvm/lib/Support/Jobserver.cpp
llvm/lib/Support/Unix/Jobserver.inc
llvm/lib/Support/Windows/Jobserver.inc
llvm/unittests/Support/JobserverTest.cpp
################################################################################
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 5a48f0bcf65e5..2ef609831637e 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -1258,9 +1258,8 @@ def offload_compression_level_EQ : Joined<["--"], "offload-compression-level=">,
HelpText<"Compression level for offload device binaries (HIP only)">;
def offload_jobs_EQ : Joined<["--"], "offload-jobs=">,
- HelpText<"Specify the number of threads to use for device offloading tasks "
- "during compilation. Can be a positive integer or the string "
- "'jobserver' to use the make-style jobserver from the environment.">;
+ HelpText<"Specify the number of threads to use for device offloading tasks"
+ " during compilation.">;
defm offload_via_llvm : BoolFOption<"offload-via-llvm",
LangOpts<"OffloadViaLLVM">, DefaultFalse,
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 684cc0902916f..412a176006bc0 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -9224,20 +9224,14 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
addOffloadCompressArgs(Args, CmdArgs);
if (Arg *A = Args.getLastArg(options::OPT_offload_jobs_EQ)) {
- StringRef Val = A->getValue();
-
- if (Val.equals_insensitive("jobserver"))
- CmdArgs.push_back(Args.MakeArgString("--wrapper-jobs=jobserver"));
- else {
- int NumThreads;
- if (Val.getAsInteger(10, NumThreads) || NumThreads <= 0) {
- C.getDriver().Diag(diag::err_drv_invalid_int_value)
- << A->getAsString(Args) << Val;
- } else {
- CmdArgs.push_back(
- Args.MakeArgString("--wrapper-jobs=" + Twine(NumThreads)));
- }
- }
+ int NumThreads;
+ if (StringRef(A->getValue()).getAsInteger(10, NumThreads) ||
+ NumThreads <= 0)
+ C.getDriver().Diag(diag::err_drv_invalid_int_value)
+ << A->getAsString(Args) << A->getValue();
+ else
+ CmdArgs.push_back(
+ Args.MakeArgString("--wrapper-jobs=" + Twine(NumThreads)));
}
const char *Exec =
diff --git a/clang/test/Driver/hip-options.hip b/clang/test/Driver/hip-options.hip
index 09f1ffa62d348..6206020d76db6 100644
--- a/clang/test/Driver/hip-options.hip
+++ b/clang/test/Driver/hip-options.hip
@@ -254,9 +254,3 @@
// RUN: --offload-arch=gfx1100 --offload-new-driver --offload-jobs=0x4 %s 2>&1 | \
// RUN: FileCheck -check-prefix=INVJOBS %s
// INVJOBS: clang: error: invalid integral value '0x4' in '--offload-jobs=0x4'
-
-// RUN: %clang -### -Werror --target=x86_64-unknown-linux-gnu -nogpuinc -nogpulib \
-// RUN: --offload-arch=gfx1100 --offload-new-driver --offload-jobs=jobserver %s 2>&1 | \
-// RUN: FileCheck -check-prefix=JOBSV %s
-// JOBSV: clang-linker-wrapper{{.*}} "--wrapper-jobs=jobserver"
-
diff --git a/clang/test/Driver/linker-wrapper.c b/clang/test/Driver/linker-wrapper.c
index 1c0fb9644ef54..c060dae7bb154 100644
--- a/clang/test/Driver/linker-wrapper.c
+++ b/clang/test/Driver/linker-wrapper.c
@@ -114,8 +114,6 @@ __attribute__((visibility("protected"), used)) int x;
// RUN: -fembed-offload-object=%t.out
// RUN: clang-linker-wrapper --dry-run --host-triple=x86_64-unknown-linux-gnu --wrapper-jobs=4 \
// RUN: --linker-path=/usr/bin/ld %t.o -o a.out 2>&1 | FileCheck %s --check-prefix=CUDA-PAR
-// RUN: clang-linker-wrapper --dry-run --host-triple=x86_64-unknown-linux-gnu --wrapper-jobs=jobserver \
-// RUN: --linker-path=/usr/bin/ld %t.o -o a.out 2>&1 | FileCheck %s --check-prefix=CUDA-PAR
// CUDA-PAR: fatbinary{{.*}}-64 --create {{.*}}.fatbin
diff --git a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
index 4d5b956031674..1419b8c90a625 100644
--- a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
+++ b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
@@ -1295,18 +1295,12 @@ int main(int Argc, char **Argv) {
parallel::strategy = hardware_concurrency(1);
if (auto *Arg = Args.getLastArg(OPT_wrapper_jobs)) {
- StringRef Val = Arg->getValue();
- if (Val.equals_insensitive("jobserver"))
- parallel::strategy = jobserver_concurrency();
- else {
- unsigned Threads = 0;
- if (!llvm::to_integer(Val, Threads) || Threads == 0)
- reportError(createStringError(
- "%s: expected a positive integer or 'jobserver', got '%s'",
- Arg->getSpelling().data(), Val.data()));
- else
- parallel::strategy = hardware_concurrency(Threads);
- }
+ unsigned Threads = 0;
+ if (!llvm::to_integer(Arg->getValue(), Threads) || Threads == 0)
+ reportError(createStringError("%s: expected a positive integer, got '%s'",
+ Arg->getSpelling().data(),
+ Arg->getValue()));
+ parallel::strategy = hardware_concurrency(Threads);
}
if (Args.hasArg(OPT_wrapper_time_trace_eq)) {
diff --git a/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td b/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
index 87f911c749bf6..fa73e02fd5178 100644
--- a/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
+++ b/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
@@ -53,8 +53,7 @@ def wrapper_time_trace_granularity : Joined<["--"], "wrapper-time-trace-granular
def wrapper_jobs : Joined<["--"], "wrapper-jobs=">,
Flags<[WrapperOnlyOption]>, MetaVarName<"<number>">,
- HelpText<"Sets the number of parallel jobs for device linking. Can be a "
- "positive integer or 'jobserver'.">;
+ HelpText<"Sets the number of parallel jobs to use for device linking">;
def override_image : Joined<["--"], "override-image=">,
Flags<[WrapperOnlyOption]>, MetaVarName<"<kind=file>">,
diff --git a/llvm/include/llvm/Support/Jobserver.h b/llvm/include/llvm/Support/Jobserver.h
deleted file mode 100644
index 6bee3b5671d55..0000000000000
--- a/llvm/include/llvm/Support/Jobserver.h
+++ /dev/null
@@ -1,162 +0,0 @@
-//===- llvm/Support/Jobserver.h - Jobserver Client --------------*- 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 defines a client for the GNU Make jobserver protocol. This allows
-// LLVM tools to coordinate parallel execution with a parent `make` process.
-//
-// The jobserver protocol is a mechanism for GNU Make to share its pool of
-// available "job slots" with the subprocesses it invokes. This is particularly
-// useful for tools that can perform parallel operations themselves (e.g., a
-// multi-threaded linker or compiler). By participating in this protocol, a
-// tool can ensure the total number of concurrent jobs does not exceed the
-// limit specified by the user (e.g., `make -j8`).
-//
-// How it works:
-//
-// 1. Establishment:
-// A child process discovers the jobserver by inspecting the `MAKEFLAGS`
-// environment variable. If a jobserver is active, this variable will
-// contain a `--jobserver-auth=<value>` argument. The format of `<value>`
-// determines how to communicate with the server.
-//
-// 2. The Implicit Slot:
-// Every command invoked by `make` is granted one "implicit" job slot. This
-// means a tool can always perform at least one unit of work without needing
-// to communicate with the jobserver. This implicit slot should NEVER be
-// released back to the jobserver.
-//
-// 3. Acquiring and Releasing Slots:
-// On POSIX systems, the jobserver is implemented as a pipe. The
-// `--jobserver-auth` value specifies either a path to a named pipe
-// (`fifo:PATH`) or a pair of file descriptors (`R,W`). The pipe is
-// pre-loaded with single-character tokens, one for each available job slot.
-//
-// - To acquire an additional slot, a client reads a single-character token
-// from the pipe.
-// - To release a slot, the client must write the *exact same* character
-// token back to the pipe.
-//
-// It is critical that a client releases all acquired slots before it exits,
-// even in cases of error, to avoid deadlocking the build.
-//
-// Example:
-// A multi-threaded linker invoked by `make -j8` wants to use multiple
-// threads. It first checks for the jobserver. It knows it has one implicit
-// slot, so it can use one thread. It then tries to acquire 7 more slots by
-// reading 7 tokens from the jobserver pipe. If it only receives 3 tokens,
-// it knows it can use a total of 1 (implicit) + 3 (acquired) = 4 threads.
-// Before exiting, it must write the 3 tokens it read back to the pipe.
-//
-// For more context, see:
-// - GNU Make manual on job slots:
-// https://www.gnu.org/software/make/manual/html_node/Job-Slots.html
-// - LLVM RFC discussion on jobserver support:
-// https://discourse.llvm.org/t/rfc-adding-gnu-make-jobserver-
-// support-to-llvm-for-coordinated-parallelism/87034
-// - Ninja’s jobserver support PR:
-// https://github.com/ninja-build/ninja/pull/2506
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_SUPPORT_JOBSERVER_H
-#define LLVM_SUPPORT_JOBSERVER_H
-
-#include "llvm/ADT/StringRef.h"
-#include <memory>
-#include <string>
-
-namespace llvm {
-
-/// A JobSlot represents a single job slot that can be acquired from or released
-/// to a jobserver pool. This class is move-only.
-class JobSlot {
-public:
- /// Default constructor creates an invalid instance.
- JobSlot() = default;
-
- // Move operations are allowed.
- JobSlot(JobSlot &&Other) noexcept : Value(Other.Value) {
- Other.Value = kInvalidValue;
- }
- JobSlot &operator=(JobSlot &&Other) noexcept {
- if (this != &Other) {
- this->Value = Other.Value;
- Other.Value = kInvalidValue;
- }
- return *this;
- }
-
- // Copy operations are disallowed.
- JobSlot(const JobSlot &) = delete;
- JobSlot &operator=(const JobSlot &) = delete;
-
- /// Returns true if this instance is valid (either implicit or explicit).
- bool isValid() const { return Value >= 0; }
-
- /// Returns true if this instance represents the implicit job slot.
- bool isImplicit() const { return Value == kImplicitValue; }
-
- static JobSlot createExplicit(uint8_t V) {
- return JobSlot(static_cast<int16_t>(V));
- }
-
- static JobSlot createImplicit() { return JobSlot(kImplicitValue); }
-
- uint8_t getExplicitValue() const;
- bool isExplicit() const { return isValid() && !isImplicit(); }
-
-private:
- friend class JobserverClient;
- friend class JobserverClientImpl;
-
- JobSlot(int16_t V) : Value(V) {}
-
- /// The jobserver pipe carries explicit tokens (bytes 0–255). We reserve two
- /// sentinels in Value for special cases:
- /// kInvalidValue (-1): no slot held
- /// kImplicitValue (INT16_MAX): implicit slot granted at startup (no pipe
- /// I/O)
- ///
- /// We use int16_t so Value can store 0–255 explicit tokens and
- /// sentinels without overflow, enforces fixed 16-bit width, and avoids
- /// unsigned/signed mix-ups.
- static constexpr int16_t kInvalidValue = -1;
- static constexpr int16_t kImplicitValue = INT16_MAX;
- int16_t Value = kInvalidValue;
-};
-
-/// The public interface for a jobserver client.
-/// This client is a lazy-initialized singleton that is created on first use.
-class JobserverClient {
-public:
- virtual ~JobserverClient();
-
- /// Tries to acquire a job slot from the pool. On failure (e.g., if the pool
- /// is empty), this returns an invalid JobSlot instance. The first successful
- /// call will always return the implicit slot.
- virtual JobSlot tryAcquire() = 0;
-
- /// Releases a job slot back to the pool.
- virtual void release(JobSlot Slot) = 0;
-
- /// Returns the number of job slots available, as determined on first use.
- /// This value is cached. Returns 0 if no jobserver is active.
- virtual unsigned getNumJobs() const = 0;
-
- /// Returns the singleton instance of the JobserverClient.
- /// The instance is created on the first call to this function.
- /// Returns a nullptr if no jobserver is configured or an error occurs.
- static JobserverClient *getInstance();
-
- /// Resets the singleton instance. For testing purposes only.
- static void resetForTesting();
-};
-
-} // end namespace llvm
-
-#endif // LLVM_SUPPORT_JOBSERVER_H
diff --git a/llvm/include/llvm/Support/ThreadPool.h b/llvm/include/llvm/Support/ThreadPool.h
index c20efc7396b79..c26681c25c8f6 100644
--- a/llvm/include/llvm/Support/ThreadPool.h
+++ b/llvm/include/llvm/Support/ThreadPool.h
@@ -16,7 +16,6 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Support/Compiler.h"
-#include "llvm/Support/Jobserver.h"
#include "llvm/Support/RWMutex.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/thread.h"
@@ -181,7 +180,6 @@ class LLVM_ABI StdThreadPool : public ThreadPoolInterface {
void grow(int requested);
void processTasks(ThreadPoolTaskGroup *WaitingForGroup);
- void processTasksWithJobserver();
/// Threads in flight
std::vector<llvm::thread> Threads;
@@ -210,8 +208,6 @@ class LLVM_ABI StdThreadPool : public ThreadPoolInterface {
/// Maximum number of threads to potentially grow this pool to.
const unsigned MaxThreadCount;
-
- JobserverClient *TheJobserver = nullptr;
};
#endif // LLVM_ENABLE_THREADS
diff --git a/llvm/include/llvm/Support/Threading.h b/llvm/include/llvm/Support/Threading.h
index 88846807f111a..d3fe0a57ee44e 100644
--- a/llvm/include/llvm/Support/Threading.h
+++ b/llvm/include/llvm/Support/Threading.h
@@ -142,11 +142,6 @@ constexpr bool llvm_is_multithreaded() { return LLVM_ENABLE_THREADS; }
/// the thread shall remain on the actual CPU socket.
LLVM_ABI std::optional<unsigned>
compute_cpu_socket(unsigned ThreadPoolNum) const;
-
- /// If true, the thread pool will attempt to coordinate with a GNU Make
- /// jobserver, acquiring a job slot before processing a task. If no
- /// jobserver is found in the environment, this is ignored.
- bool UseJobserver = false;
};
/// Build a strategy from a number of threads as a string provided in \p Num.
@@ -215,19 +210,6 @@ constexpr bool llvm_is_multithreaded() { return LLVM_ENABLE_THREADS; }
return S;
}
- /// Returns a thread strategy that attempts to coordinate with a GNU Make
- /// jobserver. The number of active threads will be limited by the number of
- /// available job slots. If no jobserver is detected in the environment, this
- /// strategy falls back to the default hardware_concurrency() behavior.
- inline ThreadPoolStrategy jobserver_concurrency() {
- ThreadPoolStrategy S;
- S.UseJobserver = true;
- // We can still request all threads be created, as they will simply
- // block waiting for a job slot if the jobserver is the limiting factor.
- S.ThreadsRequested = 0; // 0 means 'use all available'
- return S;
- }
-
/// Return the current thread id, as used in various OS system calls.
/// Note that not all platforms guarantee that the value returned will be
/// unique across the entire system, so portable code should not assume
diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index 42b21b5e62029..7da972f372c5b 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -207,7 +207,6 @@ add_llvm_component_library(LLVMSupport
InstructionCost.cpp
IntEqClasses.cpp
IntervalMap.cpp
- Jobserver.cpp
JSON.cpp
KnownBits.cpp
KnownFPClass.cpp
diff --git a/llvm/lib/Support/Jobserver.cpp b/llvm/lib/Support/Jobserver.cpp
deleted file mode 100644
index 9f726eb37506f..0000000000000
--- a/llvm/lib/Support/Jobserver.cpp
+++ /dev/null
@@ -1,259 +0,0 @@
-//===- llvm/Support/Jobserver.cpp - Jobserver Client Implementation -------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "llvm/Support/Jobserver.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/Statistic.h"
-#include "llvm/ADT/StringExtras.h"
-#include "llvm/Config/llvm-config.h"
-#include "llvm/Support/Debug.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Support/raw_ostream.h"
-
-#include <atomic>
-#include <memory>
-#include <mutex>
-#include <new>
-
-#define DEBUG_TYPE "jobserver"
-
-using namespace llvm;
-
-namespace {
-struct FdPair {
- int Read = -1;
- int Write = -1;
- bool isValid() const { return Read >= 0 && Write >= 0; }
-};
-
-struct JobserverConfig {
- enum Mode {
- None,
- PosixFifo,
- PosixPipe,
- Win32Semaphore,
- };
- Mode TheMode = None;
- std::string Path;
- FdPair PipeFDs;
-};
-
-/// A helper function that checks if `Input` starts with `Prefix`.
-/// If it does, it removes the prefix from `Input`, assigns the remainder to
-/// `Value`, and returns true. Otherwise, it returns false.
-bool getPrefixedValue(StringRef Input, StringRef Prefix, StringRef &Value) {
- if (Input.consume_front(Prefix)) {
- Value = Input;
- return true;
- }
- return false;
-}
-
-/// A helper function to parse a string in the format "R,W" where R and W are
-/// non-negative integers representing file descriptors. It populates the
-/// `ReadFD` and `WriteFD` output parameters. Returns true on success.
-static std::optional<FdPair> getFileDescriptorPair(StringRef Input) {
- FdPair FDs;
- if (Input.consumeInteger(10, FDs.Read))
- return std::nullopt;
- if (!Input.consume_front(","))
- return std::nullopt;
- if (Input.consumeInteger(10, FDs.Write))
- return std::nullopt;
- if (!Input.empty() || !FDs.isValid())
- return std::nullopt;
- return FDs;
-}
-
-/// Parses the `MAKEFLAGS` environment variable string to find jobserver
-/// arguments. It splits the string into space-separated arguments and searches
-/// for `--jobserver-auth` or `--jobserver-fds`. Based on the value of these
-/// arguments, it determines the jobserver mode (Pipe, FIFO, or Semaphore) and
-/// connection details (file descriptors or path).
-Expected<JobserverConfig> parseNativeMakeFlags(StringRef MakeFlags) {
- JobserverConfig Config;
- if (MakeFlags.empty())
- return Config;
-
- // Split the MAKEFLAGS string into arguments.
- SmallVector<StringRef, 8> Args;
- SplitString(MakeFlags, Args);
-
- // If '-n' (dry-run) is present as a legacy flag (not starting with '-'),
- // disable the jobserver.
- if (!Args.empty() && !Args[0].starts_with("-") && Args[0].contains('n'))
- return Config;
-
- // Iterate through arguments to find jobserver flags.
- // Note that make may pass multiple --jobserver-auth flags; the last one wins.
- for (StringRef Arg : Args) {
- StringRef Value;
- if (getPrefixedValue(Arg, "--jobserver-auth=", Value)) {
- // Try to parse as a file descriptor pair first.
- if (auto FDPair = getFileDescriptorPair(Value)) {
- Config.TheMode = JobserverConfig::PosixPipe;
- Config.PipeFDs = *FDPair;
- } else {
- StringRef FifoPath;
- // If not FDs, try to parse as a named pipe (fifo).
- if (getPrefixedValue(Value, "fifo:", FifoPath)) {
- Config.TheMode = JobserverConfig::PosixFifo;
- Config.Path = FifoPath.str();
- } else {
- // Otherwise, assume it's a Windows semaphore.
- Config.TheMode = JobserverConfig::Win32Semaphore;
- Config.Path = Value.str();
- }
- }
- } else if (getPrefixedValue(Arg, "--jobserver-fds=", Value)) {
- // This is an alternative, older syntax for the pipe-based server.
- if (auto FDPair = getFileDescriptorPair(Value)) {
- Config.TheMode = JobserverConfig::PosixPipe;
- Config.PipeFDs = *FDPair;
- } else {
- return createStringError(inconvertibleErrorCode(),
- "Invalid file descriptor pair in MAKEFLAGS");
- }
- }
- }
-
-// Perform platform-specific validation.
-#ifdef _WIN32
- if (Config.TheMode == JobserverConfig::PosixFifo ||
- Config.TheMode == JobserverConfig::PosixPipe)
- return createStringError(
- inconvertibleErrorCode(),
- "FIFO/Pipe-based jobserver is not supported on Windows");
-#else
- if (Config.TheMode == JobserverConfig::Win32Semaphore)
- return createStringError(
- inconvertibleErrorCode(),
- "Semaphore-based jobserver is not supported on this platform");
-#endif
- return Config;
-}
-
-std::once_flag GJobserverOnceFlag;
-JobserverClient *GJobserver = nullptr;
-
-} // namespace
-
-namespace llvm {
-class JobserverClientImpl : public JobserverClient {
- bool IsInitialized = false;
- std::atomic<bool> HasImplicitSlot{true};
- unsigned NumJobs = 0;
-
-public:
- JobserverClientImpl(const JobserverConfig &Config);
- ~JobserverClientImpl() override;
-
- JobSlot tryAcquire() override;
- void release(JobSlot Slot) override;
- unsigned getNumJobs() const override { return NumJobs; }
-
- bool isValid() const { return IsInitialized; }
-
-private:
-#if defined(LLVM_ON_UNIX)
- int ReadFD = -1;
- int WriteFD = -1;
- std::string FifoPath;
-#elif defined(_WIN32)
- void *Semaphore = nullptr;
-#endif
-};
-} // namespace llvm
-
-// Include the platform-specific parts of the class.
-#if defined(LLVM_ON_UNIX)
-#include "Unix/Jobserver.inc"
-#elif defined(_WIN32)
-#include "Windows/Jobserver.inc"
-#else
-// Dummy implementation for unsupported platforms.
-JobserverClientImpl::JobserverClientImpl(const JobserverConfig &Config) {}
-JobserverClientImpl::~JobserverClientImpl() = default;
-JobSlot JobserverClientImpl::tryAcquire() { return JobSlot(); }
-void JobserverClientImpl::release(JobSlot Slot) {}
-#endif
-
-namespace llvm {
-JobserverClient::~JobserverClient() = default;
-
-uint8_t JobSlot::getExplicitValue() const {
- assert(isExplicit() && "Cannot get value of implicit or invalid slot");
- return static_cast<uint8_t>(Value);
-}
-
-/// This is the main entry point for acquiring a jobserver client. It uses a
-/// std::call_once to ensure the singleton `GJobserver` instance is created
-/// safely in a multi-threaded environment. On first call, it reads the
-/// `MAKEFLAGS` environment variable, parses it, and attempts to construct and
-/// initialize a `JobserverClientImpl`. If successful, the global instance is
-/// stored in `GJobserver`. Subsequent calls will return the existing instance.
-JobserverClient *JobserverClient::getInstance() {
- std::call_once(GJobserverOnceFlag, []() {
- LLVM_DEBUG(
- dbgs()
- << "JobserverClient::getInstance() called for the first time.\n");
- const char *MakeFlagsEnv = getenv("MAKEFLAGS");
- if (!MakeFlagsEnv) {
- errs() << "Warning: failed to create jobserver client due to MAKEFLAGS "
- "environment variable not found\n";
- return;
- }
-
- LLVM_DEBUG(dbgs() << "Found MAKEFLAGS = \"" << MakeFlagsEnv << "\"\n");
-
- auto ConfigOrErr = parseNativeMakeFlags(MakeFlagsEnv);
- if (Error Err = ConfigOrErr.takeError()) {
- errs() << "Warning: failed to create jobserver client due to invalid "
- "MAKEFLAGS environment variable: "
- << toString(std::move(Err)) << "\n";
- return;
- }
-
- JobserverConfig Config = *ConfigOrErr;
- if (Config.TheMode == JobserverConfig::None) {
- errs() << "Warning: failed to create jobserver client due to jobserver "
- "mode missing in MAKEFLAGS environment variable\n";
- return;
- }
-
- if (Config.TheMode == JobserverConfig::PosixPipe) {
-#if defined(LLVM_ON_UNIX)
- if (!areFdsValid(Config.PipeFDs.Read, Config.PipeFDs.Write)) {
- errs() << "Warning: failed to create jobserver client due to invalid "
- "Pipe FDs in MAKEFLAGS environment variable\n";
- return;
- }
-#endif
- }
-
- auto Client = std::make_unique<JobserverClientImpl>(Config);
- if (Client->isValid()) {
- LLVM_DEBUG(dbgs() << "Jobserver client created successfully!\n");
- GJobserver = Client.release();
- } else
- errs() << "Warning: jobserver client initialization failed.\n";
- });
- return GJobserver;
-}
-
-/// For testing purposes only. This function resets the singleton instance by
-/// destroying the existing client and re-initializing the `std::once_flag`.
-/// This allows tests to simulate the first-time initialization of the
-/// jobserver client multiple times.
-void JobserverClient::resetForTesting() {
- delete GJobserver;
- GJobserver = nullptr;
- // Re-construct the std::once_flag in place to reset the singleton state.
- new (&GJobserverOnceFlag) std::once_flag();
-}
-} // namespace llvm
diff --git a/llvm/lib/Support/Parallel.cpp b/llvm/lib/Support/Parallel.cpp
index 8e0c724accb36..3ac6fc74fd3e0 100644
--- a/llvm/lib/Support/Parallel.cpp
+++ b/llvm/lib/Support/Parallel.cpp
@@ -7,17 +7,12 @@
//===----------------------------------------------------------------------===//
#include "llvm/Support/Parallel.h"
-#include "llvm/ADT/ScopeExit.h"
#include "llvm/Config/llvm-config.h"
-#include "llvm/Support/ExponentialBackoff.h"
-#include "llvm/Support/Jobserver.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/Threading.h"
#include <atomic>
#include <future>
-#include <memory>
-#include <mutex>
#include <thread>
#include <vector>
@@ -54,9 +49,6 @@ class Executor {
class ThreadPoolExecutor : public Executor {
public:
explicit ThreadPoolExecutor(ThreadPoolStrategy S) {
- if (S.UseJobserver)
- TheJobserver = JobserverClient::getInstance();
-
ThreadCount = S.compute_thread_count();
// Spawn all but one of the threads in another thread as spawning threads
// can take a while.
@@ -77,10 +69,6 @@ class ThreadPoolExecutor : public Executor {
});
}
- // To make sure the thread pool executor can only be created with a parallel
- // strategy.
- ThreadPoolExecutor() = delete;
-
void stop() {
{
std::lock_guard<std::mutex> Lock(Mutex);
@@ -123,62 +111,15 @@ class ThreadPoolExecutor : public Executor {
void work(ThreadPoolStrategy S, unsigned ThreadID) {
threadIndex = ThreadID;
S.apply_thread_strategy(ThreadID);
- // Note on jobserver deadlock avoidance:
- // GNU Make grants each invoked process one implicit job slot. Our
- // JobserverClient models this by returning an implicit JobSlot on the
- // first successful tryAcquire() in a process. This guarantees forward
- // progress without requiring a dedicated "always-on" thread here.
-
- static thread_local std::unique_ptr<ExponentialBackoff> Backoff;
-
while (true) {
- if (TheJobserver) {
- // Jobserver-mode scheduling:
- // - Acquire one job slot (with exponential backoff to avoid busy-wait).
- // - While holding the slot, drain and run tasks from the local queue.
- // - Release the slot when the queue is empty or when shutting down.
- // Rationale: Holding a slot amortizes acquire/release overhead over
- // multiple tasks and avoids requeue/yield churn, while still enforcing
- // the jobserver’s global concurrency limit. With K available slots,
- // up to K workers run tasks in parallel; within each worker tasks run
- // sequentially until the local queue is empty.
- ExponentialBackoff Backoff(std::chrono::hours(24));
- JobSlot Slot;
- do {
- if (Stop)
- return;
- Slot = TheJobserver->tryAcquire();
- if (Slot.isValid())
- break;
- } while (Backoff.waitForNextAttempt());
-
- auto SlotReleaser = llvm::make_scope_exit(
- [&] { TheJobserver->release(std::move(Slot)); });
-
- while (true) {
- std::function<void()> Task;
- {
- std::unique_lock<std::mutex> Lock(Mutex);
- Cond.wait(Lock, [&] { return Stop || !WorkStack.empty(); });
- if (Stop && WorkStack.empty())
- return;
- if (WorkStack.empty())
- break;
- Task = std::move(WorkStack.back());
- WorkStack.pop_back();
- }
- Task();
- }
- } else {
- std::unique_lock<std::mutex> Lock(Mutex);
- Cond.wait(Lock, [&] { return Stop || !WorkStack.empty(); });
- if (Stop)
- break;
- auto Task = std::move(WorkStack.back());
- WorkStack.pop_back();
- Lock.unlock();
- Task();
- }
+ std::unique_lock<std::mutex> Lock(Mutex);
+ Cond.wait(Lock, [&] { return Stop || !WorkStack.empty(); });
+ if (Stop)
+ break;
+ auto Task = std::move(WorkStack.back());
+ WorkStack.pop_back();
+ Lock.unlock();
+ Task();
}
}
@@ -189,20 +130,9 @@ class ThreadPoolExecutor : public Executor {
std::promise<void> ThreadsCreated;
std::vector<std::thread> Threads;
unsigned ThreadCount;
-
- JobserverClient *TheJobserver = nullptr;
};
-// A global raw pointer to the executor. Lifetime is managed by the
-// objects created within createExecutor().
-static Executor *TheExec = nullptr;
-static std::once_flag Flag;
-
-// This function will be called exactly once to create the executor.
-// It contains the necessary platform-specific logic. Since functions
-// called by std::call_once cannot return value, we have to set the
-// executor as a global variable.
-void createExecutor() {
+Executor *Executor::getDefaultExecutor() {
#ifdef _WIN32
// The ManagedStatic enables the ThreadPoolExecutor to be stopped via
// llvm_shutdown() which allows a "clean" fast exit, e.g. via _exit(). This
@@ -226,22 +156,16 @@ void createExecutor() {
ThreadPoolExecutor::Deleter>
ManagedExec;
static std::unique_ptr<ThreadPoolExecutor> Exec(&(*ManagedExec));
- TheExec = Exec.get();
+ return Exec.get();
#else
// ManagedStatic is not desired on other platforms. When `Exec` is destroyed
// by llvm_shutdown(), worker threads will clean up and invoke TLS
// destructors. This can lead to race conditions if other threads attempt to
// access TLS objects that have already been destroyed.
static ThreadPoolExecutor Exec(strategy);
- TheExec = &Exec;
+ return &Exec;
#endif
}
-
-Executor *Executor::getDefaultExecutor() {
- // Use std::call_once to lazily and safely initialize the executor.
- std::call_once(Flag, createExecutor);
- return TheExec;
-}
} // namespace
} // namespace detail
diff --git a/llvm/lib/Support/ThreadPool.cpp b/llvm/lib/Support/ThreadPool.cpp
index 69602688cf3fd..c304f0f45360b 100644
--- a/llvm/lib/Support/ThreadPool.cpp
+++ b/llvm/lib/Support/ThreadPool.cpp
@@ -6,7 +6,6 @@
//
//===----------------------------------------------------------------------===//
//
-//
// This file implements a crude C++11 based thread pool.
//
//===----------------------------------------------------------------------===//
@@ -15,8 +14,6 @@
#include "llvm/Config/llvm-config.h"
-#include "llvm/ADT/ScopeExit.h"
-#include "llvm/Support/ExponentialBackoff.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/raw_ostream.h"
@@ -36,10 +33,7 @@ ThreadPoolInterface::~ThreadPoolInterface() = default;
#if LLVM_ENABLE_THREADS
StdThreadPool::StdThreadPool(ThreadPoolStrategy S)
- : Strategy(S), MaxThreadCount(S.compute_thread_count()) {
- if (Strategy.UseJobserver)
- TheJobserver = JobserverClient::getInstance();
-}
+ : Strategy(S), MaxThreadCount(S.compute_thread_count()) {}
void StdThreadPool::grow(int requested) {
llvm::sys::ScopedWriter LockGuard(ThreadsLock);
@@ -51,15 +45,7 @@ void StdThreadPool::grow(int requested) {
Threads.emplace_back([this, ThreadID] {
set_thread_name(formatv("llvm-worker-{0}", ThreadID));
Strategy.apply_thread_strategy(ThreadID);
- // Note on jobserver deadlock avoidance:
- // GNU Make grants each invoked process one implicit job slot.
- // JobserverClient::tryAcquire() returns that implicit slot on the first
- // successful call in a process, ensuring forward progress without a
- // dedicated "always-on" thread.
- if (TheJobserver)
- processTasksWithJobserver();
- else
- processTasks(nullptr);
+ processTasks(nullptr);
});
}
}
@@ -147,96 +133,6 @@ void StdThreadPool::processTasks(ThreadPoolTaskGroup *WaitingForGroup) {
}
}
-/// Main loop for worker threads when using a jobserver.
-/// This function uses a two-level queue; it first acquires a job slot from the
-/// external jobserver, then retrieves a task from the internal queue.
-/// This allows the thread pool to cooperate with build systems like `make -j`.
-void StdThreadPool::processTasksWithJobserver() {
- while (true) {
- // Acquire a job slot from the external jobserver.
- // This polls for a slot and yields the thread to avoid a high-CPU wait.
- JobSlot Slot;
- // The timeout for the backoff can be very long, as the shutdown
- // is checked on each iteration. The sleep duration is capped by MaxWait
- // in ExponentialBackoff, so shutdown latency is not a problem.
- ExponentialBackoff Backoff(std::chrono::hours(24));
- bool AcquiredToken = false;
- do {
- // Return if the thread pool is shutting down.
- {
- std::unique_lock<std::mutex> LockGuard(QueueLock);
- if (!EnableFlag)
- return;
- }
-
- Slot = TheJobserver->tryAcquire();
- if (Slot.isValid()) {
- AcquiredToken = true;
- break;
- }
- } while (Backoff.waitForNextAttempt());
-
- if (!AcquiredToken) {
- // This is practically unreachable with a 24h timeout and indicates a
- // deeper problem if hit.
- report_fatal_error("Timed out waiting for jobserver token.");
- }
-
- // `make_scope_exit` guarantees the job slot is released, even if the
- // task throws or we exit early. This prevents deadlocking the build.
- auto SlotReleaser =
- make_scope_exit([&] { TheJobserver->release(std::move(Slot)); });
-
- // While we hold a job slot, process tasks from the internal queue.
- while (true) {
- std::function<void()> Task;
- ThreadPoolTaskGroup *GroupOfTask = nullptr;
-
- {
- std::unique_lock<std::mutex> LockGuard(QueueLock);
-
- // Wait until a task is available or the pool is shutting down.
- QueueCondition.wait(LockGuard,
- [&] { return !EnableFlag || !Tasks.empty(); });
-
- // If shutting down and the queue is empty, the thread can terminate.
- if (!EnableFlag && Tasks.empty())
- return;
-
- // If the queue is empty, we're done processing tasks for now.
- // Break the inner loop to release the job slot.
- if (Tasks.empty())
- break;
-
- // A task is available. Mark it as active before releasing the lock
- // to prevent race conditions with `wait()`.
- ++ActiveThreads;
- Task = std::move(Tasks.front().first);
- GroupOfTask = Tasks.front().second;
- if (GroupOfTask != nullptr)
- ++ActiveGroups[GroupOfTask];
- Tasks.pop_front();
- } // The queue lock is released.
-
- // Run the task. The job slot remains acquired during execution.
- Task();
-
- // The task has finished. Update the active count and notify any waiters.
- {
- std::lock_guard<std::mutex> LockGuard(QueueLock);
- --ActiveThreads;
- if (GroupOfTask != nullptr) {
- auto A = ActiveGroups.find(GroupOfTask);
- if (--(A->second) == 0)
- ActiveGroups.erase(A);
- }
- // If all tasks are complete, notify any waiting threads.
- if (workCompletedUnlocked(nullptr))
- CompletionCondition.notify_all();
- }
- }
- }
-}
bool StdThreadPool::workCompletedUnlocked(ThreadPoolTaskGroup *Group) const {
if (Group == nullptr)
return !ActiveThreads && Tasks.empty();
diff --git a/llvm/lib/Support/Threading.cpp b/llvm/lib/Support/Threading.cpp
index 9da357a7ebb91..693de0e6400fb 100644
--- a/llvm/lib/Support/Threading.cpp
+++ b/llvm/lib/Support/Threading.cpp
@@ -14,7 +14,6 @@
#include "llvm/Support/Threading.h"
#include "llvm/Config/config.h"
#include "llvm/Config/llvm-config.h"
-#include "llvm/Support/Jobserver.h"
#include <cassert>
#include <optional>
@@ -52,10 +51,6 @@ int llvm::get_physical_cores() { return -1; }
static int computeHostNumHardwareThreads();
unsigned llvm::ThreadPoolStrategy::compute_thread_count() const {
- if (UseJobserver)
- if (auto JS = JobserverClient::getInstance())
- return JS->getNumJobs();
-
int MaxThreadCount =
UseHyperThreads ? computeHostNumHardwareThreads() : get_physical_cores();
if (MaxThreadCount <= 0)
diff --git a/llvm/lib/Support/Unix/Jobserver.inc b/llvm/lib/Support/Unix/Jobserver.inc
deleted file mode 100644
index 53bf7f288ca1f..0000000000000
--- a/llvm/lib/Support/Unix/Jobserver.inc
+++ /dev/null
@@ -1,195 +0,0 @@
-//===- llvm/Support/Unix/Jobserver.inc - Unix Jobserver Impl ----*- 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 UNIX-specific parts of the JobserverClient class.
-//
-//===----------------------------------------------------------------------===//
-
-#include <atomic>
-#include <cassert>
-#include <cerrno>
-#include <fcntl.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-namespace {
-/// Returns true if the given file descriptor is a FIFO (named pipe).
-bool isFifo(int FD) {
- struct stat StatBuf;
- if (::fstat(FD, &StatBuf) != 0)
- return false;
- return S_ISFIFO(StatBuf.st_mode);
-}
-
-/// Returns true if the given file descriptors are valid.
-bool areFdsValid(int ReadFD, int WriteFD) {
- if (ReadFD == -1 || WriteFD == -1)
- return false;
- // Check if the file descriptors are actually valid by checking their flags.
- return ::fcntl(ReadFD, F_GETFD) != -1 && ::fcntl(WriteFD, F_GETFD) != -1;
-}
-} // namespace
-
-/// The constructor sets up the client based on the provided configuration.
-/// For pipe-based jobservers, it duplicates the inherited file descriptors,
-/// sets them to close-on-exec, and makes the read descriptor non-blocking.
-/// For FIFO-based jobservers, it opens the named pipe. After setup, it drains
-/// all available tokens from the jobserver to determine the total number of
-/// available jobs (`NumJobs`), then immediately releases them back.
-JobserverClientImpl::JobserverClientImpl(const JobserverConfig &Config) {
- switch (Config.TheMode) {
- case JobserverConfig::PosixPipe: {
- // Duplicate the read and write file descriptors.
- int NewReadFD = ::dup(Config.PipeFDs.Read);
- if (NewReadFD < 0)
- return;
- int NewWriteFD = ::dup(Config.PipeFDs.Write);
- if (NewWriteFD < 0) {
- ::close(NewReadFD);
- return;
- }
- // Set the new descriptors to be closed automatically on exec().
- if (::fcntl(NewReadFD, F_SETFD, FD_CLOEXEC) == -1 ||
- ::fcntl(NewWriteFD, F_SETFD, FD_CLOEXEC) == -1) {
- ::close(NewReadFD);
- ::close(NewWriteFD);
- return;
- }
- // Set the read descriptor to non-blocking.
- int flags = ::fcntl(NewReadFD, F_GETFL, 0);
- if (flags == -1 || ::fcntl(NewReadFD, F_SETFL, flags | O_NONBLOCK) == -1) {
- ::close(NewReadFD);
- ::close(NewWriteFD);
- return;
- }
- ReadFD = NewReadFD;
- WriteFD = NewWriteFD;
- break;
- }
- case JobserverConfig::PosixFifo:
- // Open the FIFO for reading. It must be non-blocking and close-on-exec.
- ReadFD = ::open(Config.Path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC);
- if (ReadFD < 0 || !isFifo(ReadFD)) {
- if (ReadFD >= 0)
- ::close(ReadFD);
- ReadFD = -1;
- return;
- }
- FifoPath = Config.Path;
- // The write FD is opened on-demand in release().
- WriteFD = -1;
- break;
- default:
- return;
- }
-
- IsInitialized = true;
- // Determine the total number of jobs by acquiring all available slots and
- // then immediately releasing them.
- SmallVector<JobSlot, 8> Slots;
- while (true) {
- auto S = tryAcquire();
- if (!S.isValid())
- break;
- Slots.push_back(std::move(S));
- }
- NumJobs = Slots.size();
- assert(NumJobs >= 1 && "Invalid number of jobs");
- for (auto &S : Slots)
- release(std::move(S));
-}
-
-/// The destructor closes any open file descriptors.
-JobserverClientImpl::~JobserverClientImpl() {
- if (ReadFD >= 0)
- ::close(ReadFD);
- if (WriteFD >= 0)
- ::close(WriteFD);
-}
-
-/// Tries to acquire a job slot. The first call to this function will always
-/// successfully acquire the single "implicit" slot that is granted to every
-/// process started by `make`. Subsequent calls attempt to read a one-byte
-/// token from the jobserver's read pipe. A successful read grants one
-/// explicit job slot. The read is non-blocking; if no token is available,
-/// it fails and returns an invalid JobSlot.
-JobSlot JobserverClientImpl::tryAcquire() {
- if (!IsInitialized)
- return JobSlot();
-
- // The first acquisition is always for the implicit slot.
- if (HasImplicitSlot.exchange(false, std::memory_order_acquire)) {
- LLVM_DEBUG(dbgs() << "Acquired implicit job slot.\n");
- return JobSlot::createImplicit();
- }
-
- char Token;
- ssize_t Ret;
- LLVM_DEBUG(dbgs() << "Attempting to read token from FD " << ReadFD << ".\n");
- // Loop to retry on EINTR (interrupted system call).
- do {
- Ret = ::read(ReadFD, &Token, 1);
- } while (Ret < 0 && errno == EINTR);
-
- if (Ret == 1) {
- LLVM_DEBUG(dbgs() << "Acquired explicit token '" << Token << "'.\n");
- return JobSlot::createExplicit(static_cast<uint8_t>(Token));
- }
-
- LLVM_DEBUG(dbgs() << "Failed to acquire job slot, read returned " << Ret
- << ".\n");
- return JobSlot();
-}
-
-/// Releases a job slot back to the pool. If the slot is implicit, it simply
-/// resets a flag. If the slot is explicit, it writes the character token
-/// associated with the slot back into the jobserver's write pipe. For FIFO
-/// jobservers, this may require opening the FIFO for writing if it hasn't
-/// been already.
-void JobserverClientImpl::release(JobSlot Slot) {
- if (!Slot.isValid())
- return;
-
- // Releasing the implicit slot just makes it available for the next acquire.
- if (Slot.isImplicit()) {
- LLVM_DEBUG(dbgs() << "Released implicit job slot.\n");
- [[maybe_unused]] bool was_already_released =
- HasImplicitSlot.exchange(true, std::memory_order_release);
- assert(!was_already_released && "Implicit slot released twice");
- return;
- }
-
- uint8_t Token = Slot.getExplicitValue();
- LLVM_DEBUG(dbgs() << "Releasing explicit token '" << (char)Token << "' to FD "
- << WriteFD << ".\n");
-
- // For FIFO-based jobservers, the write FD might not be open yet.
- // Open it on the first release.
- if (WriteFD < 0) {
- LLVM_DEBUG(dbgs() << "WriteFD is invalid, opening FIFO: " << FifoPath
- << "\n");
- WriteFD = ::open(FifoPath.c_str(), O_WRONLY | O_CLOEXEC);
- if (WriteFD < 0) {
- LLVM_DEBUG(dbgs() << "Failed to open FIFO for writing.\n");
- return;
- }
- LLVM_DEBUG(dbgs() << "Opened FIFO as new WriteFD: " << WriteFD << "\n");
- }
-
- ssize_t Written;
- // Loop to retry on EINTR (interrupted system call).
- do {
- Written = ::write(WriteFD, &Token, 1);
- } while (Written < 0 && errno == EINTR);
-
- if (Written <= 0) {
- LLVM_DEBUG(dbgs() << "Failed to write token to pipe, write returned "
- << Written << "\n");
- }
-}
diff --git a/llvm/lib/Support/Windows/Jobserver.inc b/llvm/lib/Support/Windows/Jobserver.inc
deleted file mode 100644
index 79028eee4b302..0000000000000
--- a/llvm/lib/Support/Windows/Jobserver.inc
+++ /dev/null
@@ -1,79 +0,0 @@
-//==- llvm/Support/Windows/Jobserver.inc - Windows Jobserver Impl -*- 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 Windows-specific parts of the JobserverClient class.
-// On Windows, the jobserver is implemented using a named semaphore.
-//
-//===----------------------------------------------------------------------===//
-
-#include "llvm/Support/Windows/WindowsSupport.h"
-#include <atomic>
-#include <cassert>
-
-namespace llvm {
-/// The constructor for the Windows jobserver client. It attempts to open a
-/// handle to an existing named semaphore, the name of which is provided by
-/// GNU make in the --jobserver-auth argument. If the semaphore is opened
-/// successfully, the client is marked as initialized.
-JobserverClientImpl::JobserverClientImpl(const JobserverConfig &Config) {
- Semaphore = (void *)::OpenSemaphoreA(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE,
- FALSE, Config.Path.c_str());
- if (Semaphore != nullptr)
- IsInitialized = true;
-}
-
-/// The destructor closes the handle to the semaphore, releasing the resource.
-JobserverClientImpl::~JobserverClientImpl() {
- if (Semaphore != nullptr)
- ::CloseHandle((HANDLE)Semaphore);
-}
-
-/// Tries to acquire a job slot. The first call always returns the implicit
-/// slot. Subsequent calls use a non-blocking wait on the semaphore
-/// (`WaitForSingleObject` with a timeout of 0). If the wait succeeds, the
-/// semaphore's count is decremented, and an explicit job slot is acquired.
-/// If the wait times out, it means no slots are available, and an invalid
-/// slot is returned.
-JobSlot JobserverClientImpl::tryAcquire() {
- if (!IsInitialized)
- return JobSlot();
-
- // First, grant the implicit slot.
- if (HasImplicitSlot.exchange(false, std::memory_order_acquire)) {
- return JobSlot::createImplicit();
- }
-
- // Try to acquire a slot from the semaphore without blocking.
- if (::WaitForSingleObject((HANDLE)Semaphore, 0) == WAIT_OBJECT_0) {
- // The explicit token value is arbitrary on Windows, as the semaphore
- // count is the real resource.
- return JobSlot::createExplicit(1);
- }
-
- return JobSlot(); // Invalid slot
-}
-
-/// Releases a job slot back to the pool. If the slot is implicit, it simply
-/// resets a flag. For an explicit slot, it increments the semaphore's count
-/// by one using `ReleaseSemaphore`, making the slot available to other
-/// processes.
-void JobserverClientImpl::release(JobSlot Slot) {
- if (!IsInitialized || !Slot.isValid())
- return;
-
- if (Slot.isImplicit()) {
- [[maybe_unused]] bool was_already_released =
- HasImplicitSlot.exchange(true, std::memory_order_release);
- assert(!was_already_released && "Implicit slot released twice");
- return;
- }
-
- // Release the slot by incrementing the semaphore count.
- (void)::ReleaseSemaphore((HANDLE)Semaphore, 1, NULL);
-}
-} // namespace llvm
diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt
index 25efa00c5abfd..d1dfb1dc4a722 100644
--- a/llvm/unittests/Support/CMakeLists.txt
+++ b/llvm/unittests/Support/CMakeLists.txt
@@ -52,7 +52,6 @@ add_llvm_unittest(SupportTests
IndexedAccessorTest.cpp
InstructionCostTest.cpp
InterleavedRangeTest.cpp
- JobserverTest.cpp
JSONTest.cpp
KnownBitsTest.cpp
LEB128Test.cpp
diff --git a/llvm/unittests/Support/JobserverTest.cpp b/llvm/unittests/Support/JobserverTest.cpp
deleted file mode 100644
index 36d5f2e68dab5..0000000000000
--- a/llvm/unittests/Support/JobserverTest.cpp
+++ /dev/null
@@ -1,437 +0,0 @@
-//===- llvm/unittest/Support/JobserverTest.cpp ----------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-///
-/// \file
-/// Jobserver.h unit tests.
-///
-//===----------------------------------------------------------------------===//
-
-#include "llvm/Support/Jobserver.h"
-#include "llvm/Config/llvm-config.h"
-#include "llvm/Support/Debug.h"
-#include "llvm/Support/Parallel.h"
-#include "llvm/Support/ThreadPool.h"
-#include "llvm/Support/raw_ostream.h"
-#include "gtest/gtest.h"
-#include <future>
-#include <random>
-#include <stdlib.h>
-
-#if defined(LLVM_ON_UNIX)
-#include "llvm/ADT/SmallString.h"
-#include "llvm/Support/FileSystem.h"
-#include <atomic>
-#include <condition_variable>
-#include <fcntl.h>
-#include <mutex>
-#include <sys/stat.h>
-#include <thread>
-#include <unistd.h>
-#elif defined(_WIN32)
-#include <windows.h>
-#endif
-
-#define DEBUG_TYPE "jobserver-test"
-
-using namespace llvm;
-
-namespace {
-
-// RAII helper to set an environment variable for the duration of a test.
-class ScopedEnvironment {
- std::string Name;
- std::string OldValue;
- bool HadOldValue;
-
-public:
- ScopedEnvironment(const char *Name, const char *Value) : Name(Name) {
-#if defined(_WIN32)
- char *Old = nullptr;
- size_t OldLen;
- errno_t err = _dupenv_s(&Old, &OldLen, Name);
- if (err == 0 && Old != nullptr) {
- HadOldValue = true;
- OldValue = Old;
- free(Old);
- } else {
- HadOldValue = false;
- }
- _putenv_s(Name, Value);
-#else
- const char *Old = getenv(Name);
- if (Old) {
- HadOldValue = true;
- OldValue = Old;
- } else {
- HadOldValue = false;
- }
- setenv(Name, Value, 1);
-#endif
- }
-
- ~ScopedEnvironment() {
-#if defined(_WIN32)
- if (HadOldValue)
- _putenv_s(Name.c_str(), OldValue.c_str());
- else
- // On Windows, setting an environment variable to an empty string
- // unsets it, making getenv() return NULL.
- _putenv_s(Name.c_str(), "");
-#else
- if (HadOldValue)
- setenv(Name.c_str(), OldValue.c_str(), 1);
- else
- unsetenv(Name.c_str());
-#endif
- }
-};
-
-TEST(Jobserver, Slot) {
- // Default constructor creates an invalid slot.
- JobSlot S1;
- EXPECT_FALSE(S1.isValid());
- EXPECT_FALSE(S1.isImplicit());
-
- // Create an implicit slot.
- JobSlot S2 = JobSlot::createImplicit();
- EXPECT_TRUE(S2.isValid());
- EXPECT_TRUE(S2.isImplicit());
-
- // Create an explicit slot.
- JobSlot S3 = JobSlot::createExplicit(42);
- EXPECT_TRUE(S3.isValid());
- EXPECT_FALSE(S3.isImplicit());
-
- // Test move construction.
- JobSlot S4 = std::move(S2);
- EXPECT_TRUE(S4.isValid());
- EXPECT_TRUE(S4.isImplicit());
- EXPECT_FALSE(S2.isValid()); // S2 is now invalid.
-
- // Test move assignment.
- S1 = std::move(S3);
- EXPECT_TRUE(S1.isValid());
- EXPECT_FALSE(S1.isImplicit());
- EXPECT_FALSE(S3.isValid()); // S3 is now invalid.
-}
-
-// Test fixture for parsing tests to ensure the singleton state is
-// reset between each test case.
-class JobserverParsingTest : public ::testing::Test {
-protected:
- void TearDown() override { JobserverClient::resetForTesting(); }
-};
-
-TEST_F(JobserverParsingTest, NoMakeflags) {
- // No MAKEFLAGS, should be null.
- ScopedEnvironment Env("MAKEFLAGS", "");
- // On Unix, setting an env var to "" makes getenv() return an empty
- // string, not NULL. We must call unsetenv() to test the case where
- // the variable is truly not present.
-#if !defined(_WIN32)
- unsetenv("MAKEFLAGS");
-#endif
- EXPECT_EQ(JobserverClient::getInstance(), nullptr);
-}
-
-TEST_F(JobserverParsingTest, EmptyMakeflags) {
- // Empty MAKEFLAGS, should be null.
- ScopedEnvironment Env("MAKEFLAGS", "");
- EXPECT_EQ(JobserverClient::getInstance(), nullptr);
-}
-
-TEST_F(JobserverParsingTest, DryRunFlag) {
- // Dry-run flag 'n', should be null.
- ScopedEnvironment Env("MAKEFLAGS", "n -j --jobserver-auth=fifo:/tmp/foo");
- EXPECT_EQ(JobserverClient::getInstance(), nullptr);
-}
-
-// Separate fixture for non-threaded client tests.
-class JobserverClientTest : public JobserverParsingTest {};
-
-#if defined(LLVM_ON_UNIX)
-// RAII helper to create and clean up a temporary FIFO file.
-class ScopedFifo {
- SmallString<128> Path;
- bool IsValid = false;
-
-public:
- ScopedFifo() {
- // To get a unique, non-colliding name for a FIFO, we use the
- // createTemporaryFile function to reserve a name in the filesystem.
- std::error_code EC =
- sys::fs::createTemporaryFile("jobserver-test", "fifo", Path);
- if (EC)
- return;
- // Then we immediately remove the regular file it created, but keep the
- // unique path.
- sys::fs::remove(Path);
- // Finally, we create the FIFO at that safe, unique path.
- if (mkfifo(Path.c_str(), 0600) != 0)
- return;
- IsValid = true;
- }
-
- ~ScopedFifo() {
- if (IsValid)
- sys::fs::remove(Path);
- }
-
- const char *c_str() const { return Path.data(); }
- bool isValid() const { return IsValid; }
-};
-
-TEST_F(JobserverClientTest, UnixClientFifo) {
- // This test covers basic FIFO client creation and behavior with an empty
- // FIFO. No job tokens are available.
- ScopedFifo F;
- ASSERT_TRUE(F.isValid());
-
- // Intentionally inserted \t in environment string.
- std::string Makeflags = " \t -j4\t \t--jobserver-auth=fifo:";
- Makeflags += F.c_str();
- ScopedEnvironment Env("MAKEFLAGS", Makeflags.c_str());
-
- JobserverClient *Client = JobserverClient::getInstance();
- ASSERT_NE(Client, nullptr);
-
- // Get the implicit token.
- JobSlot S1 = Client->tryAcquire();
- EXPECT_TRUE(S1.isValid());
- EXPECT_TRUE(S1.isImplicit());
-
- // FIFO is empty, next acquire fails.
- JobSlot S2 = Client->tryAcquire();
- EXPECT_FALSE(S2.isValid());
-
- // Release does not write to the pipe for the implicit token.
- Client->release(std::move(S1));
-
- // Re-acquire the implicit token.
- S1 = Client->tryAcquire();
- EXPECT_TRUE(S1.isValid());
-}
-
-#if LLVM_ENABLE_THREADS
-// Test fixture for tests that use the jobserver strategy. It creates a
-// temporary FIFO, sets MAKEFLAGS, and provides a helper to pre-load the FIFO
-// with job tokens, simulating `make -jN`.
-class JobserverStrategyTest : public JobserverParsingTest {
-protected:
- std::unique_ptr<ScopedFifo> TheFifo;
- std::thread MakeThread;
- std::atomic<bool> StopMakeThread{false};
-
- void SetUp() override {
- TheFifo = std::make_unique<ScopedFifo>();
- ASSERT_TRUE(TheFifo->isValid());
-
- std::string MakeFlags = "--jobserver-auth=fifo:";
- MakeFlags += TheFifo->c_str();
- setenv("MAKEFLAGS", MakeFlags.c_str(), 1);
- }
-
- void TearDown() override {
- if (MakeThread.joinable()) {
- StopMakeThread = true;
- MakeThread.join();
- }
- unsetenv("MAKEFLAGS");
- TheFifo.reset();
- JobserverClient::resetForTesting();
- }
-
- // Starts a background thread that emulates `make`. It populates the FIFO
- // with initial tokens and then recycles tokens released by clients.
- void startMakeProxy(int NumInitialJobs) {
- MakeThread = std::thread([this, NumInitialJobs]() {
- LLVM_DEBUG(dbgs() << "[MakeProxy] Thread started.\n");
- // Open the FIFO for reading and writing. This call does not block.
- int RWFd = open(TheFifo->c_str(), O_RDWR);
- LLVM_DEBUG(dbgs() << "[MakeProxy] Opened FIFO " << TheFifo->c_str()
- << " with O_RDWR, FD=" << RWFd << "\n");
- if (RWFd == -1) {
- LLVM_DEBUG(
- dbgs()
- << "[MakeProxy] ERROR: Failed to open FIFO with O_RDWR. Errno: "
- << errno << "\n");
- return;
- }
-
- // Populate with initial jobs.
- LLVM_DEBUG(dbgs() << "[MakeProxy] Writing " << NumInitialJobs
- << " initial tokens.\n");
- for (int i = 0; i < NumInitialJobs; ++i) {
- if (write(RWFd, "+", 1) != 1) {
- LLVM_DEBUG(dbgs()
- << "[MakeProxy] ERROR: Failed to write initial token " << i
- << ".\n");
- close(RWFd);
- return;
- }
- }
- LLVM_DEBUG(dbgs() << "[MakeProxy] Finished writing initial tokens.\n");
-
- // Make the read non-blocking so we can periodically check StopMakeThread.
- int flags = fcntl(RWFd, F_GETFL, 0);
- fcntl(RWFd, F_SETFL, flags | O_NONBLOCK);
-
- while (!StopMakeThread) {
- char Token;
- ssize_t Ret = read(RWFd, &Token, 1);
- if (Ret == 1) {
- LLVM_DEBUG(dbgs() << "[MakeProxy] Read token '" << Token
- << "' to recycle.\n");
- // A client released a token, 'make' makes it available again.
- std::this_thread::sleep_for(std::chrono::microseconds(100));
- ssize_t WRet;
- do {
- WRet = write(RWFd, &Token, 1);
- } while (WRet < 0 && errno == EINTR);
- if (WRet <= 0) {
- LLVM_DEBUG(
- dbgs()
- << "[MakeProxy] ERROR: Failed to write recycled token.\n");
- break; // Error, stop the proxy.
- }
- LLVM_DEBUG(dbgs()
- << "[MakeProxy] Wrote token '" << Token << "' back.\n");
- } else if (Ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
- LLVM_DEBUG(dbgs() << "[MakeProxy] ERROR: Read failed with errno "
- << errno << ".\n");
- break; // Error, stop the proxy.
- }
- // Yield to prevent this thread from busy-waiting.
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
- }
- LLVM_DEBUG(dbgs() << "[MakeProxy] Thread stopping.\n");
- close(RWFd);
- });
-
- // Give the proxy thread a moment to start and populate the FIFO.
- // This is a simple way to avoid a race condition where the client starts
- // before the initial tokens are in the pipe.
- std::this_thread::sleep_for(std::chrono::milliseconds(50));
- }
-};
-
-TEST_F(JobserverStrategyTest, ThreadPoolConcurrencyIsLimited) {
- // This test simulates `make -j3`. We will have 1 implicit job slot and
- // we will add 2 explicit job tokens to the FIFO, for a total of 3.
- const int NumExplicitJobs = 2;
- const int ConcurrencyLimit = NumExplicitJobs + 1; // +1 for the implicit slot
- const int NumTasks = 8; // More tasks than available slots.
-
- LLVM_DEBUG(dbgs() << "Calling startMakeProxy with " << NumExplicitJobs
- << " jobs.\n");
- startMakeProxy(NumExplicitJobs);
- LLVM_DEBUG(dbgs() << "MakeProxy is running.\n");
-
- // Create the thread pool. Its constructor will call jobserver_concurrency()
- // and create a client that reads from our pre-loaded FIFO.
- StdThreadPool Pool(jobserver_concurrency());
-
- std::atomic<int> ActiveTasks{0};
- std::atomic<int> MaxActiveTasks{0};
- std::atomic<int> CompletedTasks{0};
- std::mutex M;
- std::condition_variable CV;
-
- // Dispatch more tasks than there are job slots. The pool should block
- // and only run up to `ConcurrencyLimit` tasks at once.
- for (int i = 0; i < NumTasks; ++i) {
- Pool.async([&, i] {
- // Track the number of concurrently running tasks.
- int CurrentActive = ++ActiveTasks;
- LLVM_DEBUG(dbgs() << "Task " << i << ": Active tasks: " << CurrentActive
- << "\n");
- int OldMax = MaxActiveTasks.load();
- while (CurrentActive > OldMax)
- MaxActiveTasks.compare_exchange_weak(OldMax, CurrentActive);
-
- std::this_thread::sleep_for(std::chrono::milliseconds(25));
-
- --ActiveTasks;
- if (++CompletedTasks == NumTasks) {
- std::lock_guard<std::mutex> Lock(M);
- CV.notify_one();
- }
- });
- }
-
- // Wait for all tasks to complete.
- std::unique_lock<std::mutex> Lock(M);
- CV.wait(Lock, [&] { return CompletedTasks == NumTasks; });
-
- LLVM_DEBUG(dbgs() << "Test finished. Max active tasks was " << MaxActiveTasks
- << ".\n");
- // The key assertion: the maximum number of concurrent tasks should
- // not have exceeded the limit imposed by the jobserver.
- EXPECT_LE(MaxActiveTasks, ConcurrencyLimit);
- EXPECT_EQ(CompletedTasks, NumTasks);
-}
-
-TEST_F(JobserverStrategyTest, ParallelForIsLimited) {
- // This test verifies that llvm::parallelFor respects the jobserver limit.
- const int NumExplicitJobs = 3;
- const int ConcurrencyLimit = NumExplicitJobs + 1; // +1 implicit
- const int NumTasks = 20;
-
- LLVM_DEBUG(dbgs() << "Calling startMakeProxy with " << NumExplicitJobs
- << " jobs.\n");
- startMakeProxy(NumExplicitJobs);
- LLVM_DEBUG(dbgs() << "MakeProxy is running.\n");
-
- // Set the global strategy. parallelFor will use this.
- parallel::strategy = jobserver_concurrency();
-
- std::atomic<int> ActiveTasks{0};
- std::atomic<int> MaxActiveTasks{0};
-
- parallelFor(0, NumTasks, [&](int i) {
- int CurrentActive = ++ActiveTasks;
- LLVM_DEBUG(dbgs() << "Task " << i << ": Active tasks: " << CurrentActive
- << "\n");
- int OldMax = MaxActiveTasks.load();
- while (CurrentActive > OldMax)
- MaxActiveTasks.compare_exchange_weak(OldMax, CurrentActive);
-
- std::this_thread::sleep_for(std::chrono::milliseconds(20));
- --ActiveTasks;
- });
-
- LLVM_DEBUG(dbgs() << "ParallelFor finished. Max active tasks was "
- << MaxActiveTasks << ".\n");
- EXPECT_LE(MaxActiveTasks, ConcurrencyLimit);
-}
-
-TEST_F(JobserverStrategyTest, ParallelSortIsLimited) {
- // This test serves as an integration test to ensure parallelSort completes
- // correctly when running under the jobserver strategy. It doesn't directly
- // measure concurrency but verifies correctness.
- const int NumExplicitJobs = 3;
- startMakeProxy(NumExplicitJobs);
-
- parallel::strategy = jobserver_concurrency();
-
- std::vector<int> V(1024);
- // Fill with random data
- std::mt19937 randEngine;
- std::uniform_int_distribution<int> dist;
- for (int &i : V)
- i = dist(randEngine);
-
- parallelSort(V.begin(), V.end());
- ASSERT_TRUE(llvm::is_sorted(V));
-}
-
-#endif // LLVM_ENABLE_THREADS
-
-#endif // defined(LLVM_ON_UNIX)
-
-} // end anonymous namespace
More information about the llvm-commits
mailing list