| //===- 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 |