255 lines
8.4 KiB
C++
255 lines
8.4 KiB
C++
//===- 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/StringExtras.h"
|
|
#include "llvm/Support/Error.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
|