llvm-project/llvm/lib/Support/Threading.cpp
Yaxun (Sam) Liu ffc503edd0
[LLVM] Add GNU make jobserver support (#145131)
This patch introduces support for the jobserver protocol to control
parallelism for device offloading tasks.

When running a parallel build with a modern build system like `make -jN`
or `ninja -jN`, each Clang process might also be configured to use
multiple threads for its own tasks (e.g., via `--offload-jobs=4`). This
can lead to an explosion of threads (N * 4), causing heavy system load,
CPU contention, and ultimately slowing down the entire build.

This patch allows Clang to act as a cooperative client of the build
system's jobserver. It extends the `--offload-jobs` option to accept the
value 'jobserver'. With the recent addition of jobserver support to the
Ninja build system, this functionality now benefits users of both Make
and Ninja.

When `--offload-jobs=jobserver` is specified, Clang's thread pool will:
1. Parse the MAKEFLAGS environment variable to find the jobserver
details.
2. Before dispatching a task, acquire a job slot from the jobserver. If
none are available, the worker thread will block.
3. Release the job slot once the task is complete.

This ensures that the total number of active offload tasks across all
Clang processes does not exceed the limit defined by the parent build
system, leading to more efficient and controlled parallel builds.

Implementation:
- A new library, `llvm/Support/Jobserver`, is added to provide a
platform-agnostic client for the jobserver protocol, with backends for
Unix (FIFO) and Windows (semaphores).
- `llvm/Support/ThreadPool` and `llvm/Support/Parallel` are updated with
a `jobserver_concurrency` strategy to integrate this logic.
- The Clang driver and linker-wrapper are modified to recognize the
'jobserver' argument and enable the new thread pool strategy.
- New unit and integration tests are added to validate the feature.
2025-10-03 09:25:49 -04:00

120 lines
3.9 KiB
C++

//===-- llvm/Support/Threading.cpp- Control multithreading mode --*- 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 helper functions for running LLVM in a multi-threaded
// environment.
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/Threading.h"
#include "llvm/Config/config.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Support/Jobserver.h"
#include <cassert>
#include <optional>
#include <stdlib.h>
using namespace llvm;
//===----------------------------------------------------------------------===//
//=== WARNING: Implementation here must contain only TRULY operating system
//=== independent code.
//===----------------------------------------------------------------------===//
#if LLVM_ENABLE_THREADS == 0 || \
(!defined(_WIN32) && !defined(HAVE_PTHREAD_H))
uint64_t llvm::get_threadid() { return 0; }
uint32_t llvm::get_max_thread_name_length() { return 0; }
void llvm::set_thread_name(const Twine &Name) {}
void llvm::get_thread_name(SmallVectorImpl<char> &Name) { Name.clear(); }
llvm::BitVector llvm::get_thread_affinity_mask() { return {}; }
unsigned llvm::ThreadPoolStrategy::compute_thread_count() const {
// When threads are disabled, ensure clients will loop at least once.
return 1;
}
// Unknown if threading turned off
int llvm::get_physical_cores() { return -1; }
#else
static int computeHostNumHardwareThreads();
unsigned llvm::ThreadPoolStrategy::compute_thread_count() const {
if (UseJobserver)
if (auto JS = JobserverClient::getInstance())
return JS->getNumJobs();
int MaxThreadCount =
UseHyperThreads ? computeHostNumHardwareThreads() : get_physical_cores();
if (MaxThreadCount <= 0)
MaxThreadCount = 1;
if (ThreadsRequested == 0)
return MaxThreadCount;
if (!Limit)
return ThreadsRequested;
return std::min((unsigned)MaxThreadCount, ThreadsRequested);
}
// Include the platform-specific parts of this class.
#ifdef LLVM_ON_UNIX
#include "Unix/Threading.inc"
#endif
#ifdef _WIN32
#include "Windows/Threading.inc"
#endif
// Must be included after Threading.inc to provide definition for llvm::thread
// because FreeBSD's condvar.h (included by user.h) misuses the "thread"
// keyword.
#include "llvm/Support/thread.h"
#if defined(__APPLE__)
// Darwin's default stack size for threads except the main one is only 512KB,
// which is not enough for some/many normal LLVM compilations. This implements
// the same interface as std::thread but requests the same stack size as the
// main thread (8MB) before creation.
const std::optional<unsigned> llvm::thread::DefaultStackSize = 8 * 1024 * 1024;
#elif defined(_AIX)
// On AIX, the default pthread stack size limit is ~192k for 64-bit programs.
// This limit is easily reached when doing link-time thinLTO. AIX library
// developers have used 4MB, so we'll do the same.
const std::optional<unsigned> llvm::thread::DefaultStackSize = 4 * 1024 * 1024;
#else
const std::optional<unsigned> llvm::thread::DefaultStackSize;
#endif
#endif
std::optional<ThreadPoolStrategy>
llvm::get_threadpool_strategy(StringRef Num, ThreadPoolStrategy Default) {
if (Num == "all")
return llvm::hardware_concurrency();
if (Num.empty())
return Default;
unsigned V;
if (Num.getAsInteger(10, V))
return std::nullopt; // malformed 'Num' value
if (V == 0)
return Default;
// Do not take the Default into account. This effectively disables
// heavyweight_hardware_concurrency() if the user asks for any number of
// threads on the cmd-line.
ThreadPoolStrategy S = llvm::hardware_concurrency();
S.ThreadsRequested = V;
return S;
}