//===- 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=` argument. The format of `` // 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" 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(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. LLVM_ABI_FOR_TEST static JobserverClient *getInstance(); /// Resets the singleton instance. For testing purposes only. LLVM_ABI_FOR_TEST static void resetForTesting(); }; } // end namespace llvm #endif // LLVM_SUPPORT_JOBSERVER_H