260 lines · cpp
1//===- llvm/Support/Jobserver.cpp - Jobserver Client Implementation -------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/Support/Jobserver.h"10#include "llvm/ADT/SmallVector.h"11#include "llvm/ADT/Statistic.h"12#include "llvm/ADT/StringExtras.h"13#include "llvm/Config/llvm-config.h"14#include "llvm/Support/Debug.h"15#include "llvm/Support/Error.h"16#include "llvm/Support/raw_ostream.h"17 18#include <atomic>19#include <memory>20#include <mutex>21#include <new>22 23#define DEBUG_TYPE "jobserver"24 25using namespace llvm;26 27namespace {28struct FdPair {29 int Read = -1;30 int Write = -1;31 bool isValid() const { return Read >= 0 && Write >= 0; }32};33 34struct JobserverConfig {35 enum Mode {36 None,37 PosixFifo,38 PosixPipe,39 Win32Semaphore,40 };41 Mode TheMode = None;42 std::string Path;43 FdPair PipeFDs;44};45 46/// A helper function that checks if `Input` starts with `Prefix`.47/// If it does, it removes the prefix from `Input`, assigns the remainder to48/// `Value`, and returns true. Otherwise, it returns false.49bool getPrefixedValue(StringRef Input, StringRef Prefix, StringRef &Value) {50 if (Input.consume_front(Prefix)) {51 Value = Input;52 return true;53 }54 return false;55}56 57/// A helper function to parse a string in the format "R,W" where R and W are58/// non-negative integers representing file descriptors. It populates the59/// `ReadFD` and `WriteFD` output parameters. Returns true on success.60static std::optional<FdPair> getFileDescriptorPair(StringRef Input) {61 FdPair FDs;62 if (Input.consumeInteger(10, FDs.Read))63 return std::nullopt;64 if (!Input.consume_front(","))65 return std::nullopt;66 if (Input.consumeInteger(10, FDs.Write))67 return std::nullopt;68 if (!Input.empty() || !FDs.isValid())69 return std::nullopt;70 return FDs;71}72 73/// Parses the `MAKEFLAGS` environment variable string to find jobserver74/// arguments. It splits the string into space-separated arguments and searches75/// for `--jobserver-auth` or `--jobserver-fds`. Based on the value of these76/// arguments, it determines the jobserver mode (Pipe, FIFO, or Semaphore) and77/// connection details (file descriptors or path).78Expected<JobserverConfig> parseNativeMakeFlags(StringRef MakeFlags) {79 JobserverConfig Config;80 if (MakeFlags.empty())81 return Config;82 83 // Split the MAKEFLAGS string into arguments.84 SmallVector<StringRef, 8> Args;85 SplitString(MakeFlags, Args);86 87 // If '-n' (dry-run) is present as a legacy flag (not starting with '-'),88 // disable the jobserver.89 if (!Args.empty() && !Args[0].starts_with("-") && Args[0].contains('n'))90 return Config;91 92 // Iterate through arguments to find jobserver flags.93 // Note that make may pass multiple --jobserver-auth flags; the last one wins.94 for (StringRef Arg : Args) {95 StringRef Value;96 if (getPrefixedValue(Arg, "--jobserver-auth=", Value)) {97 // Try to parse as a file descriptor pair first.98 if (auto FDPair = getFileDescriptorPair(Value)) {99 Config.TheMode = JobserverConfig::PosixPipe;100 Config.PipeFDs = *FDPair;101 } else {102 StringRef FifoPath;103 // If not FDs, try to parse as a named pipe (fifo).104 if (getPrefixedValue(Value, "fifo:", FifoPath)) {105 Config.TheMode = JobserverConfig::PosixFifo;106 Config.Path = FifoPath.str();107 } else {108 // Otherwise, assume it's a Windows semaphore.109 Config.TheMode = JobserverConfig::Win32Semaphore;110 Config.Path = Value.str();111 }112 }113 } else if (getPrefixedValue(Arg, "--jobserver-fds=", Value)) {114 // This is an alternative, older syntax for the pipe-based server.115 if (auto FDPair = getFileDescriptorPair(Value)) {116 Config.TheMode = JobserverConfig::PosixPipe;117 Config.PipeFDs = *FDPair;118 } else {119 return createStringError(inconvertibleErrorCode(),120 "Invalid file descriptor pair in MAKEFLAGS");121 }122 }123 }124 125// Perform platform-specific validation.126#ifdef _WIN32127 if (Config.TheMode == JobserverConfig::PosixFifo ||128 Config.TheMode == JobserverConfig::PosixPipe)129 return createStringError(130 inconvertibleErrorCode(),131 "FIFO/Pipe-based jobserver is not supported on Windows");132#else133 if (Config.TheMode == JobserverConfig::Win32Semaphore)134 return createStringError(135 inconvertibleErrorCode(),136 "Semaphore-based jobserver is not supported on this platform");137#endif138 return Config;139}140 141std::once_flag GJobserverOnceFlag;142JobserverClient *GJobserver = nullptr;143 144} // namespace145 146namespace llvm {147class JobserverClientImpl : public JobserverClient {148 bool IsInitialized = false;149 std::atomic<bool> HasImplicitSlot{true};150 unsigned NumJobs = 0;151 152public:153 JobserverClientImpl(const JobserverConfig &Config);154 ~JobserverClientImpl() override;155 156 JobSlot tryAcquire() override;157 void release(JobSlot Slot) override;158 unsigned getNumJobs() const override { return NumJobs; }159 160 bool isValid() const { return IsInitialized; }161 162private:163#if defined(LLVM_ON_UNIX)164 int ReadFD = -1;165 int WriteFD = -1;166 std::string FifoPath;167#elif defined(_WIN32)168 void *Semaphore = nullptr;169#endif170};171} // namespace llvm172 173// Include the platform-specific parts of the class.174#if defined(LLVM_ON_UNIX)175#include "Unix/Jobserver.inc"176#elif defined(_WIN32)177#include "Windows/Jobserver.inc"178#else179// Dummy implementation for unsupported platforms.180JobserverClientImpl::JobserverClientImpl(const JobserverConfig &Config) {}181JobserverClientImpl::~JobserverClientImpl() = default;182JobSlot JobserverClientImpl::tryAcquire() { return JobSlot(); }183void JobserverClientImpl::release(JobSlot Slot) {}184#endif185 186namespace llvm {187JobserverClient::~JobserverClient() = default;188 189uint8_t JobSlot::getExplicitValue() const {190 assert(isExplicit() && "Cannot get value of implicit or invalid slot");191 return static_cast<uint8_t>(Value);192}193 194/// This is the main entry point for acquiring a jobserver client. It uses a195/// std::call_once to ensure the singleton `GJobserver` instance is created196/// safely in a multi-threaded environment. On first call, it reads the197/// `MAKEFLAGS` environment variable, parses it, and attempts to construct and198/// initialize a `JobserverClientImpl`. If successful, the global instance is199/// stored in `GJobserver`. Subsequent calls will return the existing instance.200JobserverClient *JobserverClient::getInstance() {201 std::call_once(GJobserverOnceFlag, []() {202 LLVM_DEBUG(203 dbgs()204 << "JobserverClient::getInstance() called for the first time.\n");205 const char *MakeFlagsEnv = getenv("MAKEFLAGS");206 if (!MakeFlagsEnv) {207 errs() << "Warning: failed to create jobserver client due to MAKEFLAGS "208 "environment variable not found\n";209 return;210 }211 212 LLVM_DEBUG(dbgs() << "Found MAKEFLAGS = \"" << MakeFlagsEnv << "\"\n");213 214 auto ConfigOrErr = parseNativeMakeFlags(MakeFlagsEnv);215 if (Error Err = ConfigOrErr.takeError()) {216 errs() << "Warning: failed to create jobserver client due to invalid "217 "MAKEFLAGS environment variable: "218 << toString(std::move(Err)) << "\n";219 return;220 }221 222 JobserverConfig Config = *ConfigOrErr;223 if (Config.TheMode == JobserverConfig::None) {224 errs() << "Warning: failed to create jobserver client due to jobserver "225 "mode missing in MAKEFLAGS environment variable\n";226 return;227 }228 229 if (Config.TheMode == JobserverConfig::PosixPipe) {230#if defined(LLVM_ON_UNIX)231 if (!areFdsValid(Config.PipeFDs.Read, Config.PipeFDs.Write)) {232 errs() << "Warning: failed to create jobserver client due to invalid "233 "Pipe FDs in MAKEFLAGS environment variable\n";234 return;235 }236#endif237 }238 239 auto Client = std::make_unique<JobserverClientImpl>(Config);240 if (Client->isValid()) {241 LLVM_DEBUG(dbgs() << "Jobserver client created successfully!\n");242 GJobserver = Client.release();243 } else244 errs() << "Warning: jobserver client initialization failed.\n";245 });246 return GJobserver;247}248 249/// For testing purposes only. This function resets the singleton instance by250/// destroying the existing client and re-initializing the `std::once_flag`.251/// This allows tests to simulate the first-time initialization of the252/// jobserver client multiple times.253void JobserverClient::resetForTesting() {254 delete GJobserver;255 GJobserver = nullptr;256 // Re-construct the std::once_flag in place to reset the singleton state.257 new (&GJobserverOnceFlag) std::once_flag();258}259} // namespace llvm260