Support: add llvm::thread class that supports specifying stack size.

This adds a new llvm::thread class with the same interface as std::thread
except there is an extra constructor that allows us to set the new thread's
stack size. On Darwin even the default size is boosted to 8MB to match the main
thread.

It also switches all users of the older C-style `llvm_execute_on_thread` API
family over to `llvm::thread` followed by either a `detach` or `join` call and
removes the old API.
This commit is contained in:
Tim Northover 2021-05-26 11:25:11 +01:00
parent 7445f1e4dc
commit 727e1c9be3
11 changed files with 296 additions and 147 deletions

View File

@ -3,6 +3,7 @@
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/thread.h"
#include <atomic>
#include <thread>
#ifdef __USE_POSIX
@ -95,8 +96,10 @@ void AsyncTaskRunner::runAsync(const llvm::Twine &Name,
};
// Ensure our worker threads have big enough stacks to run clang.
llvm::llvm_execute_on_thread_async(std::move(Task),
/*clang::DesiredStackSize*/ 8 << 20);
llvm::thread Thread(
/*clang::DesiredStackSize*/ llvm::Optional<unsigned>(8 << 20),
std::move(Task));
Thread.detach();
}
Deadline timeoutSeconds(llvm::Optional<double> Seconds) {

View File

@ -55,6 +55,7 @@
#include "llvm/Support/Threading.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/thread.h"
#include <mutex>
#if LLVM_ENABLE_THREADS != 0 && defined(__APPLE__)
@ -6785,10 +6786,10 @@ void clang_enableStackTraces(void) {
void clang_executeOnThread(void (*fn)(void *), void *user_data,
unsigned stack_size) {
llvm::llvm_execute_on_thread(fn, user_data,
stack_size == 0
? clang::DesiredStackSize
: llvm::Optional<unsigned>(stack_size));
llvm::thread Thread(stack_size == 0 ? clang::DesiredStackSize
: llvm::Optional<unsigned>(stack_size),
fn, user_data);
Thread.join();
}
//===----------------------------------------------------------------------===//

View File

@ -87,7 +87,7 @@ public:
/// a protected context which is run in another thread (optionally with a
/// requested stack size).
///
/// See RunSafely() and llvm_execute_on_thread().
/// See RunSafely().
///
/// On Darwin, if PRIO_DARWIN_BG is set on the calling thread, it will be
/// propagated to the new thread as well.

View File

@ -55,36 +55,6 @@ class Twine;
/// false otherwise.
bool llvm_is_multithreaded();
/// Execute the given \p UserFn on a separate thread, passing it the provided \p
/// UserData and waits for thread completion.
///
/// This function does not guarantee that the code will actually be executed
/// on a separate thread or honoring the requested stack size, but tries to do
/// so where system support is available.
///
/// \param UserFn - The callback to execute.
/// \param UserData - An argument to pass to the callback function.
/// \param StackSizeInBytes - A requested size (in bytes) for the thread stack
/// (or None for default)
void llvm_execute_on_thread(
void (*UserFn)(void *), void *UserData,
llvm::Optional<unsigned> StackSizeInBytes = llvm::None);
/// Schedule the given \p Func for execution on a separate thread, then return
/// to the caller immediately. Roughly equivalent to
/// `std::thread(Func).detach()`, except it allows requesting a specific stack
/// size, if supported for the platform.
///
/// This function would report a fatal error if it can't execute the code
/// on a separate thread.
///
/// \param Func - The callback to execute.
/// \param StackSizeInBytes - A requested size (in bytes) for the thread stack
/// (or None for default)
void llvm_execute_on_thread_async(
llvm::unique_function<void()> Func,
llvm::Optional<unsigned> StackSizeInBytes = llvm::None);
#if LLVM_THREADING_USE_STD_CALL_ONCE
typedef std::once_flag once_flag;

View File

@ -16,16 +16,215 @@
#ifndef LLVM_SUPPORT_THREAD_H
#define LLVM_SUPPORT_THREAD_H
#include "llvm/ADT/Optional.h"
#include "llvm/Config/llvm-config.h"
#ifdef _WIN32
typedef unsigned long DWORD;
typedef void *PVOID;
typedef PVOID HANDLE;
#endif
#if LLVM_ENABLE_THREADS
#include <thread>
namespace llvm {
typedef std::thread thread;
#if LLVM_ON_UNIX || _WIN32
/// LLVM thread following std::thread interface with added constructor to
/// specify stack size.
class thread {
template <typename FPtr, typename... Args, size_t... Indices>
static void Apply(std::tuple<FPtr, Args...> &Callee,
std::index_sequence<Indices...>) {
std::move(std::get<0>(Callee))(std::move(std::get<Indices + 1>(Callee))...);
}
template <typename CalleeTuple> static void GenericThreadProxy(void *Ptr) {
std::unique_ptr<CalleeTuple> Callee(static_cast<CalleeTuple *>(Ptr));
// FIXME: use std::apply when C++17 is allowed.
std::make_index_sequence<std::tuple_size<CalleeTuple>() - 1> Indices{};
Apply(*Callee.get(), Indices);
}
public:
#if LLVM_ON_UNIX
using native_handle_type = pthread_t;
using id = pthread_t;
using start_routine_type = void *(*)(void *);
template <typename CalleeTuple> static void *ThreadProxy(void *Ptr) {
GenericThreadProxy<CalleeTuple>(Ptr);
return nullptr;
}
#elif _WIN32
using native_handle_type = HANDLE;
using id = DWORD;
using start_routine_type = unsigned(__stdcall *)(void *);
template <typename CalleeTuple>
static unsigned __stdcall ThreadProxy(void *Ptr) {
GenericThreadProxy<CalleeTuple>(Ptr);
return 0;
}
#endif
#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.
static const constexpr llvm::Optional<unsigned> DefaultStackSize =
8 * 1024 * 1024;
#else
static const constexpr llvm::Optional<unsigned> DefaultStackSize = None;
#endif
thread() : Thread(native_handle_type()) {}
thread(thread &&Other) noexcept
: Thread(std::exchange(Other.Thread, native_handle_type())) {}
template <class Function, class... Args>
explicit thread(Function &&f, Args &&...args)
: thread(DefaultStackSize, f, args...) {}
template <class Function, class... Args>
explicit thread(llvm::Optional<unsigned> StackSizeInBytes, Function &&f,
Args &&...args);
thread(const thread &) = delete;
~thread() {
if (joinable())
std::terminate();
}
thread &operator=(thread &&Other) noexcept {
if (joinable())
std::terminate();
Thread = std::exchange(Other.Thread, native_handle_type());
return *this;
}
bool joinable() const noexcept { return Thread != native_handle_type(); }
inline id get_id() const noexcept;
native_handle_type native_handle() const noexcept { return Thread; }
static unsigned hardware_concurrency() {
return std::thread::hardware_concurrency();
};
inline void join();
inline void detach();
void swap(llvm::thread &Other) noexcept { std::swap(Thread, Other.Thread); }
private:
native_handle_type Thread;
};
thread::native_handle_type
llvm_execute_on_thread_impl(thread::start_routine_type ThreadFunc, void *Arg,
llvm::Optional<unsigned> StackSizeInBytes);
void llvm_thread_join_impl(thread::native_handle_type Thread);
void llvm_thread_detach_impl(thread::native_handle_type Thread);
thread::id llvm_thread_get_id_impl(thread::native_handle_type Thread);
thread::id llvm_thread_get_current_id_impl();
template <class Function, class... Args>
thread::thread(llvm::Optional<unsigned> StackSizeInBytes, Function &&f,
Args &&...args) {
typedef std::tuple<typename std::decay<Function>::type,
typename std::decay<Args>::type...>
CalleeTuple;
std::unique_ptr<CalleeTuple> Callee(
new CalleeTuple(std::forward<Function>(f), std::forward<Args>(args)...));
Thread = llvm_execute_on_thread_impl(ThreadProxy<CalleeTuple>, Callee.get(),
StackSizeInBytes);
if (Thread != native_handle_type())
Callee.release();
}
thread::id thread::get_id() const noexcept {
return llvm_thread_get_id_impl(Thread);
}
void thread::join() {
llvm_thread_join_impl(Thread);
Thread = native_handle_type();
}
void thread::detach() {
llvm_thread_detach_impl(Thread);
Thread = native_handle_type();
}
namespace this_thread {
inline thread::id get_id() { return llvm_thread_get_current_id_impl(); }
} // namespace this_thread
#else // !LLVM_ON_UNIX && !_WIN32
/// std::thread backed implementation of llvm::thread interface that ignores the
/// stack size request.
class thread {
public:
using native_handle_type = std::thread::native_handle_type;
using id = std::thread::id;
thread() : Thread(std::thread()) {}
thread(thread &&Other) noexcept
: Thread(std::exchange(Other.Thread, std::thread())) {}
template <class Function, class... Args>
explicit thread(llvm::Optional<unsigned> StackSizeInBytes, Function &&f,
Args &&...args)
: Thread(std::forward<Function>(f), std::forward<Args>(args)...) {}
template <class Function, class... Args>
explicit thread(Function &&f, Args &&...args) : Thread(f, args...) {}
thread(const thread &) = delete;
~thread() {}
thread &operator=(thread &&Other) noexcept {
Thread = std::exchange(Other.Thread, std::thread());
return *this;
}
bool joinable() const noexcept { return Thread.joinable(); }
id get_id() const noexcept { return Thread.get_id(); }
native_handle_type native_handle() noexcept { return Thread.native_handle(); }
static unsigned hardware_concurrency() {
return std::thread::hardware_concurrency();
};
inline void join() { Thread.join(); }
inline void detach() { Thread.detach(); }
void swap(llvm::thread &Other) noexcept { std::swap(Thread, Other.Thread); }
private:
std::thread Thread;
};
namespace this_thread {
inline thread::id get_id() { return std::this_thread::get_id(); }
}
#endif // LLVM_ON_UNIX || _WIN32
} // namespace llvm
#else // !LLVM_ENABLE_THREADS
#include <utility>
@ -36,17 +235,26 @@ struct thread {
thread() {}
thread(thread &&other) {}
template <class Function, class... Args>
explicit thread(llvm::Optional<unsigned> StackSizeInBytes, Function &&f,
Args &&...args) {
f(std::forward<Args>(args)...);
}
template <class Function, class... Args>
explicit thread(Function &&f, Args &&...args) {
f(std::forward<Args>(args)...);
}
thread(const thread &) = delete;
void detach() {
report_fatal_error("Detaching from a thread does not make sense with no "
"threading support");
}
void join() {}
static unsigned hardware_concurrency() { return 1; };
};
}
} // namespace llvm
#endif // LLVM_ENABLE_THREADS
#endif
#endif // LLVM_SUPPORT_THREAD_H

View File

@ -13,6 +13,7 @@
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/ThreadLocal.h"
#include "llvm/Support/thread.h"
#include <mutex>
#include <setjmp.h>
@ -500,10 +501,12 @@ bool CrashRecoveryContext::RunSafelyOnThread(function_ref<void()> Fn,
unsigned RequestedStackSize) {
bool UseBackgroundPriority = hasThreadBackgroundPriority();
RunSafelyOnThreadInfo Info = { Fn, this, UseBackgroundPriority, false };
llvm_execute_on_thread(RunSafelyOnThread_Dispatch, &Info,
RequestedStackSize == 0
llvm::thread Thread(RequestedStackSize == 0
? llvm::None
: llvm::Optional<unsigned>(RequestedStackSize));
: llvm::Optional<unsigned>(RequestedStackSize),
RunSafelyOnThread_Dispatch, &Info);
Thread.join();
if (CrashRecoveryContextImpl *CRC = (CrashRecoveryContextImpl *)Impl)
CRC->setSwitchedThread();
return Info.Result;

View File

@ -73,8 +73,8 @@ void ThreadPool::wait() {
}
bool ThreadPool::isWorkerThread() const {
std::thread::id CurrentThreadId = std::this_thread::get_id();
for (const std::thread &Thread : Threads)
llvm::thread::id CurrentThreadId = llvm::this_thread::get_id();
for (const llvm::thread &Thread : Threads)
if (CurrentThreadId == Thread.get_id())
return true;
return false;

View File

@ -15,6 +15,7 @@
#include "llvm/ADT/Optional.h"
#include "llvm/Config/config.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/thread.h"
#include <cassert>
#include <errno.h>
@ -38,13 +39,6 @@ bool llvm::llvm_is_multithreaded() {
#if LLVM_ENABLE_THREADS == 0 || \
(!defined(_WIN32) && !defined(HAVE_PTHREAD_H))
// Support for non-Win32, non-pthread implementation.
void llvm::llvm_execute_on_thread(void (*Fn)(void *), void *UserData,
llvm::Optional<unsigned> StackSizeInBytes) {
(void)StackSizeInBytes;
Fn(UserData);
}
uint64_t llvm::get_threadid() { return 0; }
uint32_t llvm::get_max_thread_name_length() { return 0; }
@ -60,25 +54,6 @@ unsigned llvm::ThreadPoolStrategy::compute_thread_count() const {
return 1;
}
#if LLVM_ENABLE_THREADS == 0
void llvm::llvm_execute_on_thread_async(
llvm::unique_function<void()> Func,
llvm::Optional<unsigned> StackSizeInBytes) {
(void)Func;
(void)StackSizeInBytes;
report_fatal_error("Spawning a detached thread doesn't make sense with no "
"threading support");
}
#else
// Support for non-Win32, non-pthread implementation.
void llvm::llvm_execute_on_thread_async(
llvm::unique_function<void()> Func,
llvm::Optional<unsigned> StackSizeInBytes) {
(void)StackSizeInBytes;
std::thread(std::move(Func)).detach();
}
#endif
#else
int computeHostNumHardwareThreads();
@ -95,17 +70,6 @@ unsigned llvm::ThreadPoolStrategy::compute_thread_count() const {
return std::min((unsigned)MaxThreadCount, ThreadsRequested);
}
namespace {
struct SyncThreadInfo {
void (*UserFn)(void *);
void *UserData;
};
using AsyncThreadInfo = llvm::unique_function<void()>;
enum class JoiningPolicy { Join, Detach };
} // namespace
// Include the platform-specific parts of this class.
#ifdef LLVM_ON_UNIX
#include "Unix/Threading.inc"
@ -114,22 +78,6 @@ enum class JoiningPolicy { Join, Detach };
#include "Windows/Threading.inc"
#endif
void llvm::llvm_execute_on_thread(void (*Fn)(void *), void *UserData,
llvm::Optional<unsigned> StackSizeInBytes) {
SyncThreadInfo Info = {Fn, UserData};
llvm_execute_on_thread_impl(threadFuncSync, &Info, StackSizeInBytes,
JoiningPolicy::Join);
}
void llvm::llvm_execute_on_thread_async(
llvm::unique_function<void()> Func,
llvm::Optional<unsigned> StackSizeInBytes) {
llvm_execute_on_thread_impl(&threadFuncAsync,
new AsyncThreadInfo(std::move(Func)),
StackSizeInBytes, JoiningPolicy::Detach);
}
#endif
Optional<ThreadPoolStrategy>

View File

@ -48,22 +48,9 @@
#include <unistd.h> // For syscall()
#endif
static void *threadFuncSync(void *Arg) {
SyncThreadInfo *TI = static_cast<SyncThreadInfo *>(Arg);
TI->UserFn(TI->UserData);
return nullptr;
}
static void *threadFuncAsync(void *Arg) {
std::unique_ptr<AsyncThreadInfo> Info(static_cast<AsyncThreadInfo *>(Arg));
(*Info)();
return nullptr;
}
static void
llvm_execute_on_thread_impl(void *(*ThreadFunc)(void *), void *Arg,
llvm::Optional<unsigned> StackSizeInBytes,
JoiningPolicy JP) {
pthread_t
llvm::llvm_execute_on_thread_impl(void *(*ThreadFunc)(void *), void *Arg,
llvm::Optional<unsigned> StackSizeInBytes) {
int errnum;
// Construct the attributes object.
@ -90,16 +77,31 @@ llvm_execute_on_thread_impl(void *(*ThreadFunc)(void *), void *Arg,
if ((errnum = ::pthread_create(&Thread, &Attr, ThreadFunc, Arg)) != 0)
ReportErrnumFatal("pthread_create failed", errnum);
if (JP == JoiningPolicy::Join) {
// Wait for the thread
if ((errnum = ::pthread_join(Thread, nullptr)) != 0) {
ReportErrnumFatal("pthread_join failed", errnum);
return Thread;
}
} else if (JP == JoiningPolicy::Detach) {
void llvm::llvm_thread_detach_impl(pthread_t Thread) {
int errnum;
if ((errnum = ::pthread_detach(Thread)) != 0) {
ReportErrnumFatal("pthread_detach failed", errnum);
}
}
void llvm::llvm_thread_join_impl(pthread_t Thread) {
int errnum;
if ((errnum = ::pthread_join(Thread, nullptr)) != 0) {
ReportErrnumFatal("pthread_join failed", errnum);
}
}
pthread_t llvm::llvm_thread_get_id_impl(pthread_t Thread) {
return Thread;
}
pthread_t llvm::llvm_thread_get_current_id_impl() {
return ::pthread_self();
}
uint64_t llvm::get_threadid() {

View File

@ -23,22 +23,10 @@
#undef MemoryFence
#endif
static unsigned __stdcall threadFuncSync(void *Arg) {
SyncThreadInfo *TI = static_cast<SyncThreadInfo *>(Arg);
TI->UserFn(TI->UserData);
return 0;
}
static unsigned __stdcall threadFuncAsync(void *Arg) {
std::unique_ptr<AsyncThreadInfo> Info(static_cast<AsyncThreadInfo *>(Arg));
(*Info)();
return 0;
}
static void
llvm_execute_on_thread_impl(unsigned (__stdcall *ThreadFunc)(void *), void *Arg,
llvm::Optional<unsigned> StackSizeInBytes,
JoiningPolicy JP) {
HANDLE
llvm::llvm_execute_on_thread_impl(unsigned(__stdcall *ThreadFunc)(void *),
void *Arg,
llvm::Optional<unsigned> StackSizeInBytes) {
HANDLE hThread = (HANDLE)::_beginthreadex(
NULL, StackSizeInBytes.getValueOr(0), ThreadFunc, Arg, 0, NULL);
@ -46,16 +34,29 @@ llvm_execute_on_thread_impl(unsigned (__stdcall *ThreadFunc)(void *), void *Arg,
ReportLastErrorFatal("_beginthreadex failed");
}
if (JP == JoiningPolicy::Join) {
return hThread;
}
void llvm::llvm_thread_join_impl(HANDLE hThread) {
if (::WaitForSingleObject(hThread, INFINITE) == WAIT_FAILED) {
ReportLastErrorFatal("WaitForSingleObject failed");
}
}
void llvm::llvm_thread_detach_impl(HANDLE hThread) {
if (::CloseHandle(hThread) == FALSE) {
ReportLastErrorFatal("CloseHandle failed");
}
}
DWORD llvm::llvm_thread_get_id_impl(HANDLE hThread) {
return ::GetThreadId(hThread);
}
DWORD llvm::llvm_thread_get_current_id_impl() {
return ::GetCurrentThreadId();
}
uint64_t llvm::get_threadid() {
return uint64_t(::GetCurrentThreadId());
}

View File

@ -63,7 +63,8 @@ TEST(Threading, RunOnThreadSyncAsync) {
ThreadFinished.notify();
};
llvm::llvm_execute_on_thread_async(ThreadFunc);
llvm::thread Thread(ThreadFunc);
Thread.detach();
ASSERT_TRUE(ThreadStarted.wait());
ThreadAdvanced.notify();
ASSERT_TRUE(ThreadFinished.wait());
@ -71,11 +72,23 @@ TEST(Threading, RunOnThreadSyncAsync) {
TEST(Threading, RunOnThreadSync) {
std::atomic_bool Executed(false);
llvm::llvm_execute_on_thread(
llvm::thread Thread(
[](void *Arg) { *static_cast<std::atomic_bool *>(Arg) = true; },
&Executed);
Thread.join();
ASSERT_EQ(Executed, true);
}
#if defined(__APPLE__)
TEST(Threading, AppleStackSize) {
llvm::thread Thread([] {
volatile unsigned char Var[8 * 1024 * 1024 - 1024];
Var[0] = 0xff;
ASSERT_EQ(Var[0], 0xff);
});
Thread.join();
}
#endif
#endif
} // end anon namespace