Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/support/threads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <algorithm>
#include <iostream>
#include <string>
#include <system_error>

#ifdef __linux__
#include <sched.h> // For sched_getaffinity
Expand All @@ -29,6 +30,12 @@
#include "threads.h"
#include "utilities.h"

#ifdef BINARYEN_PTHREAD_WORKERS
#include <limits.h>
#include <sys/resource.h>
#include <unistd.h>
#endif

// debugging tools

// DEBUG_TYPE is for BYN_TRACE macro. This tracing can be enabled at runtime
Expand Down Expand Up @@ -60,6 +67,61 @@ namespace wasm {

// Thread

#ifdef BINARYEN_PTHREAD_WORKERS

size_t getWorkerThreadStackSize() {
static const size_t size = []() -> size_t {
// Matches glibc's default for new threads, which also falls back to a
// fixed size when the limit is unbounded.
const size_t defaultSize = 8 * 1024 * 1024;
struct rlimit limit;
size_t size = defaultSize;
if (getrlimit(RLIMIT_STACK, &limit) == 0 &&
limit.rlim_cur != RLIM_INFINITY) {
size = limit.rlim_cur;
}
size = std::max(size, size_t(PTHREAD_STACK_MIN));
// Some platforms (macOS) require a page-aligned size.
long page = sysconf(_SC_PAGESIZE);
if (page > 0) {
size = (size + page - 1) / page * page;
}
return size;
}();
return size;
}

static void* threadEntry(void* self) {
Thread::mainLoop(self);
return nullptr;
}

Thread::Thread(ThreadPool* parent) : parent(parent) {
assert(!parent->isRunning());
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, getWorkerThreadStackSize());
int err = pthread_create(&thread, &attr, threadEntry, this);
pthread_attr_destroy(&attr);
if (err != 0) {
throw std::system_error(err, std::generic_category());
}
}

Thread::~Thread() {
{
std::lock_guard<std::mutex> lock(mutex);
// notify the thread that it can exit
done = true;
condition.notify_one();
}
pthread_join(thread, nullptr);
}

#else

size_t getWorkerThreadStackSize() { return 0; }

Thread::Thread(ThreadPool* parent) : parent(parent) {
assert(!parent->isRunning());
thread = std::make_unique<std::thread>(mainLoop, this);
Expand All @@ -75,6 +137,8 @@ Thread::~Thread() {
thread->join();
}

#endif // BINARYEN_PTHREAD_WORKERS

void Thread::work(std::function<ThreadWorkState()> doWork_) {
// TODO: fancy work stealing
DEBUG_THREAD("send work to thread\n");
Expand Down
19 changes: 18 additions & 1 deletion src/support/threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@

#include "compiler-support.h"

// On POSIX we create worker threads with pthreads directly so that we can set
// their stack size to match the main thread. std::thread uses the platform
// default, which on macOS is only 512KB for secondary threads (Linux uses
// RLIMIT_STACK, and Windows uses the linker's /STACK for all threads).
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
#define BINARYEN_PTHREAD_WORKERS 1
#include <pthread.h>
#endif

namespace wasm {

// The work state of a helper thread - is there more to do,
Expand All @@ -48,7 +57,11 @@ class ThreadPool;

class Thread {
ThreadPool* parent;
#ifdef BINARYEN_PTHREAD_WORKERS
pthread_t thread;
#else
std::unique_ptr<std::thread> thread;
#endif
std::mutex mutex;
std::condition_variable condition;
bool done = false;
Expand All @@ -62,10 +75,14 @@ class Thread {
// it returns false.
void work(std::function<ThreadWorkState()> doWork);

private:
static void mainLoop(void* self);
};

// The stack size requested for worker threads, or 0 if the platform default is
// used. On POSIX this is the RLIMIT_STACK soft limit, so that workers can
// recurse as deeply as the main thread and `ulimit -s` applies to them too.
size_t getWorkerThreadStackSize();

//
// A pool of helper threads.
//
Expand Down
1 change: 1 addition & 0 deletions test/gtest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ set(unittest_SOURCES
stringify.cpp
subtype-exprs.cpp
suffix_tree.cpp
threads.cpp
topological-sort.cpp
type-builder.cpp
type-updating.cpp
Expand Down
51 changes: 51 additions & 0 deletions test/gtest/threads.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "support/threads.h"
#include "gtest/gtest.h"

#ifdef BINARYEN_PTHREAD_WORKERS

#include <pthread.h>

using namespace wasm;

namespace {

size_t getCurrentThreadStackSize() {
#ifdef __APPLE__
return pthread_get_stacksize_np(pthread_self());
#else
pthread_attr_t attr;
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
return 0;
}
size_t size = 0;
pthread_attr_getstacksize(&attr, &size);
pthread_attr_destroy(&attr);
return size;
#endif
}

} // anonymous namespace

TEST(ThreadsTest, WorkerStackSize) {
auto* pool = ThreadPool::get();
size_t numThreads = pool->size();
if (numThreads == 1) {
GTEST_SKIP() << "no worker threads";
}
std::vector<size_t> sizes(numThreads);
std::vector<std::function<ThreadWorkState()>> workers;
for (size_t i = 0; i < numThreads; i++) {
workers.push_back([&sizes, i]() {
sizes[i] = getCurrentThreadStackSize();
return ThreadWorkState::Finished;
});
}
pool->work(workers);
size_t expected = getWorkerThreadStackSize();
ASSERT_GT(expected, 0u);
for (auto size : sizes) {
EXPECT_GE(size, expected);
}
}

#endif // BINARYEN_PTHREAD_WORKERS