diff --git a/src/support/threads.cpp b/src/support/threads.cpp index ceb39f3d8c6..b6ce57a2cad 100644 --- a/src/support/threads.cpp +++ b/src/support/threads.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #ifdef __linux__ #include // For sched_getaffinity @@ -29,6 +30,12 @@ #include "threads.h" #include "utilities.h" +#ifdef BINARYEN_PTHREAD_WORKERS +#include +#include +#include +#endif + // debugging tools // DEBUG_TYPE is for BYN_TRACE macro. This tracing can be enabled at runtime @@ -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 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(mainLoop, this); @@ -75,6 +137,8 @@ Thread::~Thread() { thread->join(); } +#endif // BINARYEN_PTHREAD_WORKERS + void Thread::work(std::function doWork_) { // TODO: fancy work stealing DEBUG_THREAD("send work to thread\n"); diff --git a/src/support/threads.h b/src/support/threads.h index ca13abf4e3b..e91ece7ad0c 100644 --- a/src/support/threads.h +++ b/src/support/threads.h @@ -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 +#endif + namespace wasm { // The work state of a helper thread - is there more to do, @@ -48,7 +57,11 @@ class ThreadPool; class Thread { ThreadPool* parent; +#ifdef BINARYEN_PTHREAD_WORKERS + pthread_t thread; +#else std::unique_ptr thread; +#endif std::mutex mutex; std::condition_variable condition; bool done = false; @@ -62,10 +75,14 @@ class Thread { // it returns false. void work(std::function 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. // diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index e6be5406d7b..1d64e946e13 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -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 diff --git a/test/gtest/threads.cpp b/test/gtest/threads.cpp new file mode 100644 index 00000000000..782bce0db6c --- /dev/null +++ b/test/gtest/threads.cpp @@ -0,0 +1,51 @@ +#include "support/threads.h" +#include "gtest/gtest.h" + +#ifdef BINARYEN_PTHREAD_WORKERS + +#include + +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 sizes(numThreads); + std::vector> 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