brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.6 KiB · 9f726eb Raw
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