diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp --- a/libc/src/__support/threads/linux/thread.cpp +++ b/libc/src/__support/threads/linux/thread.cpp @@ -23,6 +23,7 @@ #include #include +#include // For EXEC_PAGESIZE. #include // For PR_SET_NAME #include // For CLONE_* flags. #include @@ -40,7 +41,6 @@ #endif static constexpr size_t NAME_SIZE_MAX = 16; // Includes the null terminator -static constexpr size_t DEFAULT_STACK_SIZE = (1 << 16); // 64KB static constexpr uint32_t CLEAR_TID_VALUE = 0xABCD1234; static constexpr unsigned CLONE_SYSCALL_FLAGS = CLONE_VM // Share the memory space with the parent. @@ -65,23 +65,64 @@ #error "CLONE_RESULT_REGISTER not defined for your target architecture" #endif -LIBC_INLINE ErrorOr alloc_stack(size_t size) { +static constexpr ErrorOr add_no_overflow(size_t lhs, size_t rhs) { + if (lhs > SIZE_MAX - rhs) + return Error{EINVAL}; + if (rhs > SIZE_MAX - lhs) + return Error{EINVAL}; + return lhs + rhs; +} + +static constexpr ErrorOr round_to_page(size_t v) { + auto vp_or_err = add_no_overflow(v, EXEC_PAGESIZE - 1); + if (!vp_or_err) + return vp_or_err; + + return vp_or_err.value() & -EXEC_PAGESIZE; +} + +LIBC_INLINE ErrorOr alloc_stack(size_t stacksize, size_t guardsize) { + + // Guard needs to be mapped with PROT_NONE + int prot = guardsize ? PROT_NONE : PROT_READ | PROT_WRITE; + // TODO: Do we need to check for overflow? + auto size_or_err = add_no_overflow(stacksize, guardsize); + if (!size_or_err) + return Error{int(size_or_err.error())}; + size_t size = size_or_err.value(); + + // TODO: Maybe add MAP_STACK? Currently unimplemented on linux but helps + // future-proof. long mmap_result = __llvm_libc::syscall_impl(MMAP_SYSCALL_NUMBER, 0, // No special address - size, - PROT_READ | PROT_WRITE, // Read and write stack - MAP_ANONYMOUS | MAP_PRIVATE, // Process private + size, prot, + MAP_ANONYMOUS | MAP_PRIVATE, // Process private. -1, // Not backed by any file 0 // No offset ); if (mmap_result < 0 && (uintptr_t(mmap_result) >= UINTPTR_MAX - size)) return Error{int(-mmap_result)}; + + if (guardsize) { + // TODO: We are assuming stack growsdown here. + // Give read/write permissions to actual stack. + long result = + __llvm_libc::syscall_impl(SYS_mprotect, mmap_result + guardsize, + stacksize, PROT_READ | PROT_WRITE); + + if (result != 0) + return Error{int(-result)}; + } + mmap_result += guardsize; return reinterpret_cast(mmap_result); } -LIBC_INLINE void free_stack(void *stack, size_t size) { - __llvm_libc::syscall_impl(SYS_munmap, stack, size); +LIBC_INLINE void free_stack(void *stack, size_t stacksize, size_t guardsize) { + uintptr_t stackaddr = reinterpret_cast(stack); + stackaddr -= guardsize; + stack = reinterpret_cast(stackaddr); + __llvm_libc::syscall_impl(SYS_munmap, stack, stacksize + guardsize); } struct Thread; @@ -102,7 +143,7 @@ // the stack. cleanup_tls(attrib->tls, attrib->tls_size); if (attrib->owned_stack) - free_stack(attrib->stack, attrib->stack_size); + free_stack(attrib->stack, attrib->stacksize, attrib->guardsize); } __attribute__((always_inline)) inline uintptr_t get_start_args_addr() { @@ -148,12 +189,24 @@ } int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack, - size_t size, bool detached) { + size_t stacksize, size_t guardsize, bool detached) { bool owned_stack = false; if (stack == nullptr) { - if (size == 0) - size = DEFAULT_STACK_SIZE; - auto alloc = alloc_stack(size); + // Roundup stacksize/guardsize to page size. + // TODO(1): Do we need to check for overflow? + // TODO(2): Should be also add sizeof(ThreadAttribute) and other internal + // meta data? + auto round_or_err = round_to_page(guardsize); + if (!round_or_err) + return round_or_err.error(); + guardsize = round_or_err.value(); + + round_or_err = round_to_page(stacksize); + if (!round_or_err) + return round_or_err.error(); + + stacksize = round_or_err.value(); + auto alloc = alloc_stack(stacksize, guardsize); if (!alloc) return alloc.error(); else @@ -173,9 +226,30 @@ // // Likewise, the actual thread state information is also stored on the // stack memory. - uintptr_t adjusted_stack = reinterpret_cast(stack) + size - - sizeof(StartArgs) - sizeof(ThreadAttributes) - - sizeof(cpp::Atomic); + + static constexpr size_t kInternalStackDataSize = + sizeof(StartArgs) + sizeof(ThreadAttributes) + + sizeof(cpp::Atomic); + + // This is pretty arbitrary, but at the moment we don't adjust user provided + // stacksize (or default) to account for this data as its assumed minimal. If + // this assert starts failing we probably should. Likewise if we can't bound + // this we may overflow when we subtract it from the top of the stack. + static_assert(kInternalStackDataSize < EXEC_PAGESIZE); + + // TODO(1): We are assuming stack growsdown here. + // TODO(2): Do we need to check for overflow here? + auto adjusted_stack_or_err = + add_no_overflow(reinterpret_cast(stack), stacksize); + if (!adjusted_stack_or_err) { + cleanup_tls(tls.addr, tls.size); + if (owned_stack) + free_stack(stack, stacksize, guardsize); + return adjusted_stack_or_err.error(); + } + + uintptr_t adjusted_stack = + adjusted_stack_or_err.value() - kInternalStackDataSize; adjusted_stack &= ~(uintptr_t(STACK_ALIGNMENT) - 1); auto *start_args = reinterpret_cast(adjusted_stack); @@ -186,7 +260,8 @@ attrib->detach_state = uint32_t(detached ? DetachState::DETACHED : DetachState::JOINABLE); attrib->stack = stack; - attrib->stack_size = size; + attrib->stacksize = stacksize; + attrib->guardsize = guardsize; attrib->owned_stack = owned_stack; attrib->tls = tls.addr; attrib->tls_size = tls.size; @@ -413,6 +488,8 @@ __llvm_libc::syscall_impl(SYS_set_tid_address, 0); } + // TODO: We may have deallocated the stack. Can we reference local variables + // that may have spilled? if (style == ThreadStyle::POSIX) __llvm_libc::syscall_impl(SYS_exit, retval.posix_retval); else diff --git a/libc/src/__support/threads/thread.h b/libc/src/__support/threads/thread.h --- a/libc/src/__support/threads/thread.h +++ b/libc/src/__support/threads/thread.h @@ -90,10 +90,11 @@ // exits. It will clean up the thread resources once the thread // exits. cpp::Atomic detach_state; - void *stack; // Pointer to the thread stack - unsigned long long stack_size; // Size of the stack - uintptr_t tls; // Address to the thread TLS memory - uintptr_t tls_size; // The size of area pointed to by |tls|. + void *stack; // Pointer to the thread stack + unsigned long long stacksize; // Size of the stack + unsigned long long guardsize; // Guard size on stack + uintptr_t tls; // Address to the thread TLS memory + uintptr_t tls_size; // The size of area pointed to by |tls|. unsigned char owned_stack; // Indicates if the thread owns this stack memory int tid; ThreadStyle style; @@ -103,9 +104,9 @@ constexpr ThreadAttributes() : detach_state(uint32_t(DetachState::DETACHED)), stack(nullptr), - stack_size(0), tls(0), tls_size(0), owned_stack(false), tid(-1), - style(ThreadStyle::POSIX), retval(), atexit_callback_mgr(nullptr), - platform_data(nullptr) {} + stacksize(0), guardsize(0), tls(0), tls_size(0), owned_stack(false), + tid(-1), style(ThreadStyle::POSIX), retval(), + atexit_callback_mgr(nullptr), platform_data(nullptr) {} }; using TSSDtor = void(void *); @@ -133,23 +134,34 @@ void *get_tss_value(unsigned int key); struct Thread { + static constexpr size_t kDefaultStackSize = 1 << 16; + // TODO: default guardsize should be system pagesize + static constexpr size_t kDefaultGuardSize = 0; + static constexpr bool kDefaultDetached = false; + ThreadAttributes *attrib; constexpr Thread() : attrib(nullptr) {} constexpr Thread(ThreadAttributes *attr) : attrib(attr) {} - int run(ThreadRunnerPosix *func, void *arg, void *stack, size_t size, - bool detached = false) { + int run(ThreadRunnerPosix *func, void *arg, void *stack = nullptr, + size_t stacksize = kDefaultStackSize, + size_t guardsize = kDefaultGuardSize, + bool detached = kDefaultDetached) { ThreadRunner runner; runner.posix_runner = func; - return run(ThreadStyle::POSIX, runner, arg, stack, size, detached); + return run(ThreadStyle::POSIX, runner, arg, stack, stacksize, guardsize, + detached); } - int run(ThreadRunnerStdc *func, void *arg, void *stack, size_t size, - bool detached = false) { + int run(ThreadRunnerStdc *func, void *arg, void *stack = nullptr, + size_t stacksize = kDefaultStackSize, + size_t guardsize = kDefaultGuardSize, + bool detached = kDefaultDetached) { ThreadRunner runner; runner.stdc_runner = func; - return run(ThreadStyle::STDC, runner, arg, stack, size, detached); + return run(ThreadStyle::STDC, runner, arg, stack, stacksize, guardsize, + detached); } int join(int *val) { @@ -176,7 +188,7 @@ // Return 0 on success or an error value on failure. int run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack, - size_t stack_size, bool detached); + size_t stacksize, size_t guardsize, bool detached); // Return 0 on success or an error value on failure. int join(ThreadReturnValue &retval); diff --git a/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt --- a/libc/src/pthread/CMakeLists.txt +++ b/libc/src/pthread/CMakeLists.txt @@ -86,6 +86,7 @@ pthread_attr_getstack.h DEPENDS libc.include.pthread + libc.src.pthread.pthread_attr_getstacksize ) add_entrypoint_object( @@ -96,6 +97,7 @@ pthread_attr_setstack.h DEPENDS libc.include.pthread + libc.src.pthread.pthread_attr_setstacksize ) add_header_library( @@ -254,6 +256,11 @@ libc.include.errno libc.include.pthread libc.src.__support.threads.thread + libc.src.pthread.pthread_attr_destroy + libc.src.pthread.pthread_attr_init + libc.src.pthread.pthread_attr_getdetachstate + libc.src.pthread.pthread_attr_getguardsize + libc.src.pthread.pthread_attr_getstack COMPILE_OPTIONS -O3 -fno-omit-frame-pointer diff --git a/libc/src/pthread/pthread_attr_getstack.cpp b/libc/src/pthread/pthread_attr_getstack.cpp --- a/libc/src/pthread/pthread_attr_getstack.cpp +++ b/libc/src/pthread/pthread_attr_getstack.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "pthread_attr_getstack.h" +#include "pthread_attr_getstacksize.h" #include "src/__support/common.h" @@ -17,8 +18,13 @@ LLVM_LIBC_FUNCTION(int, pthread_attr_getstack, (const pthread_attr_t *__restrict attr, void **__restrict stack, size_t *__restrict stacksize)) { + + // We are suppose to return EINVAL if __stacksize < PTHREAD_STACK_MIN. + // Probably unnecessary as we throw error if user tries to set invalid + // stacksize. + if (int result = __llvm_libc::pthread_attr_getstacksize(attr, stacksize)) + return result; *stack = attr->__stack; - *stacksize = attr->__stacksize; return 0; } diff --git a/libc/src/pthread/pthread_attr_getstacksize.cpp b/libc/src/pthread/pthread_attr_getstacksize.cpp --- a/libc/src/pthread/pthread_attr_getstacksize.cpp +++ b/libc/src/pthread/pthread_attr_getstacksize.cpp @@ -10,6 +10,7 @@ #include "src/__support/common.h" +#include #include namespace __llvm_libc { @@ -17,6 +18,11 @@ LLVM_LIBC_FUNCTION(int, pthread_attr_getstacksize, (const pthread_attr_t *__restrict attr, size_t *__restrict stacksize)) { + // We are suppose to return EINVAL if __stacksize < PTHREAD_STACK_MIN. + // Probably unnecessary as we throw error if user tries to set invalid + // stacksize. + if (attr->__stacksize < PTHREAD_STACK_MIN) + return EINVAL; *stacksize = attr->__stacksize; return 0; } diff --git a/libc/src/pthread/pthread_attr_init.cpp b/libc/src/pthread/pthread_attr_init.cpp --- a/libc/src/pthread/pthread_attr_init.cpp +++ b/libc/src/pthread/pthread_attr_init.cpp @@ -16,11 +16,15 @@ namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, pthread_attr_init, (pthread_attr_t * attr)) { + // NB: This is exceedingly small compared to the 2MB norm and will break many + // programs expecting the full 2MB. + static constexpr size_t DEFAULT_STACK_SIZE = 1 << 16; + *attr = pthread_attr_t{ - false, // Not detached - nullptr, // Let the thread manage its stack - 1 << 16, // 64KB stack size - EXEC_PAGESIZE, // Default page size for the guard size. + PTHREAD_CREATE_JOINABLE, // Not detached + nullptr, // Let the thread manage its stack + DEFAULT_STACK_SIZE, // stack size. + EXEC_PAGESIZE, // Default page size for the guard size. }; return 0; } diff --git a/libc/src/pthread/pthread_attr_setstack.cpp b/libc/src/pthread/pthread_attr_setstack.cpp --- a/libc/src/pthread/pthread_attr_setstack.cpp +++ b/libc/src/pthread/pthread_attr_setstack.cpp @@ -7,11 +7,11 @@ //===----------------------------------------------------------------------===// #include "pthread_attr_setstack.h" +#include "pthread_attr_setstacksize.h" #include "src/__support/common.h" #include -#include // For EXEC_PAGESIZE. #include #include @@ -20,13 +20,15 @@ LLVM_LIBC_FUNCTION(int, pthread_attr_setstack, (pthread_attr_t *__restrict attr, void *stack, size_t stacksize)) { - if (stacksize < PTHREAD_STACK_MIN) - return EINVAL; uintptr_t stackaddr = reinterpret_cast(stack); + + // TODO(1): '16' (stack alignment) should be in an target specific enum. + // TODO(2): Do we need to check for overflow on stackaddr + stacksize? if ((stackaddr % 16 != 0) || ((stackaddr + stacksize) % 16 != 0)) return EINVAL; + if (int result = __llvm_libc::pthread_attr_setstacksize(attr, stacksize)) + return result; attr->__stack = stack; - attr->__stacksize = stacksize; return 0; } diff --git a/libc/src/pthread/pthread_attr_setstacksize.cpp b/libc/src/pthread/pthread_attr_setstacksize.cpp --- a/libc/src/pthread/pthread_attr_setstacksize.cpp +++ b/libc/src/pthread/pthread_attr_setstacksize.cpp @@ -11,13 +11,13 @@ #include "src/__support/common.h" #include -#include // For EXEC_PAGESIZE. #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, pthread_attr_setstacksize, (pthread_attr_t *__restrict attr, size_t stacksize)) { + // TODO: Should we also ensure stacksize % EXEC_PAGESIZE == 0? if (stacksize < PTHREAD_STACK_MIN) return EINVAL; attr->__stack = nullptr; diff --git a/libc/src/pthread/pthread_create.cpp b/libc/src/pthread/pthread_create.cpp --- a/libc/src/pthread/pthread_create.cpp +++ b/libc/src/pthread/pthread_create.cpp @@ -8,6 +8,13 @@ #include "pthread_create.h" +#include "pthread_attr_destroy.h" +#include "pthread_attr_init.h" + +#include "pthread_attr_getdetachstate.h" +#include "pthread_attr_getguardsize.h" +#include "pthread_attr_getstack.h" + #include "src/__support/common.h" #include "src/__support/threads/thread.h" @@ -20,12 +27,41 @@ "Mismatch between pthread_t and internal Thread."); LLVM_LIBC_FUNCTION(int, pthread_create, - (pthread_t *__restrict th, const pthread_attr_t *__restrict, + (pthread_t *__restrict th, + const pthread_attr_t *__restrict attr, __pthread_start_t func, void *arg)) { + pthread_attr_t default_attr; + if (attr == nullptr) { + // We failed to initialize attributes (should be impossible) + if (__llvm_libc::pthread_attr_init(&default_attr)) + return EINVAL; + + attr = &default_attr; + } + + void *stack; + size_t stacksize, guardsize; + int detached; + + if (__llvm_libc::pthread_attr_getstack(attr, &stack, &stacksize)) + return EINVAL; + + if (__llvm_libc::pthread_attr_getguardsize(attr, &guardsize)) + return EINVAL; + + if (__llvm_libc::pthread_attr_getdetachstate(attr, &detached)) + return EINVAL; + + if (attr == &default_attr) + // Should we fail here? Its non-issue as the moment as pthread_attr_destroy + // can only succeed. + if (__llvm_libc::pthread_attr_destroy(&default_attr)) + return EINVAL; + auto *thread = reinterpret_cast<__llvm_libc::Thread *>(th); - // TODO: Use the attributes parameter to set up thread properties. - int result = thread->run(func, arg, nullptr, 0); - if (result != 0 && result != EPERM) + int result = thread->run(func, arg, stack, stacksize, guardsize, + detached == PTHREAD_CREATE_DETACHED); + if (result != 0 && result != EPERM && result != EINVAL) return EAGAIN; return result; } diff --git a/libc/src/threads/thrd_create.cpp b/libc/src/threads/thrd_create.cpp --- a/libc/src/threads/thrd_create.cpp +++ b/libc/src/threads/thrd_create.cpp @@ -11,7 +11,8 @@ #include "src/__support/threads/thread.h" #include -#include // For thrd_* type definitions. +#include // For EXEC_PAGESIZE. +#include // For thrd_* type definitions. namespace __llvm_libc { @@ -21,7 +22,7 @@ LLVM_LIBC_FUNCTION(int, thrd_create, (thrd_t * th, thrd_start_t func, void *arg)) { auto *thread = reinterpret_cast<__llvm_libc::Thread *>(th); - int result = thread->run(func, arg, nullptr, 0); + int result = thread->run(func, arg); if (result == 0) return thrd_success; else if (result == ENOMEM) diff --git a/libc/test/integration/src/__support/threads/thread_detach_test.cpp b/libc/test/integration/src/__support/threads/thread_detach_test.cpp --- a/libc/test/integration/src/__support/threads/thread_detach_test.cpp +++ b/libc/test/integration/src/__support/threads/thread_detach_test.cpp @@ -35,7 +35,7 @@ void detach_cleanup_test() { mutex.lock(); __llvm_libc::Thread th; - ASSERT_EQ(0, th.run(func, nullptr, nullptr, 0)); + ASSERT_EQ(0, th.run(func, nullptr)); // Since |mutex| is held by the current thread, we will release it // to let |th| run. diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt --- a/libc/test/integration/src/pthread/CMakeLists.txt +++ b/libc/test/integration/src/pthread/CMakeLists.txt @@ -130,3 +130,34 @@ libc.src.pthread.pthread_create libc.src.pthread.pthread_join ) + +add_integration_test( + pthread_create_test + SUITE + libc-pthread-integration-tests + SRCS + pthread_create_test.cpp + DEPENDS + libc.include.pthread + libc.include.errno + libc.src.pthread.pthread_create + libc.src.pthread.pthread_join + libc.src.pthread.pthread_attr_getdetachstate + libc.src.pthread.pthread_attr_getguardsize + libc.src.pthread.pthread_attr_getstack + libc.src.pthread.pthread_attr_getstacksize + libc.src.pthread.pthread_attr_setdetachstate + libc.src.pthread.pthread_attr_setguardsize + libc.src.pthread.pthread_attr_setstack + libc.src.pthread.pthread_attr_setstacksize + libc.src.pthread.pthread_attr_init + libc.src.pthread.pthread_attr_destroy + libc.src.pthread.pthread_self + libc.src.sys.mman.mmap + libc.src.sys.mman.munmap + libc.src.sys.random.getrandom + libc.src.__support.threads.thread + libc.src.__support.CPP.atomic + libc.src.__support.CPP.array + libc.src.__support.CPP.new +) diff --git a/libc/test/integration/src/pthread/pthread_create_test.cpp b/libc/test/integration/src/pthread/pthread_create_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/integration/src/pthread/pthread_create_test.cpp @@ -0,0 +1,293 @@ +//===-- Tests for pthread_create ------------------------------------------===// +// +// 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 "src/pthread/pthread_attr_destroy.h" +#include "src/pthread/pthread_attr_getdetachstate.h" +#include "src/pthread/pthread_attr_getguardsize.h" +#include "src/pthread/pthread_attr_getstack.h" +#include "src/pthread/pthread_attr_getstacksize.h" +#include "src/pthread/pthread_attr_init.h" +#include "src/pthread/pthread_attr_setdetachstate.h" +#include "src/pthread/pthread_attr_setguardsize.h" +#include "src/pthread/pthread_attr_setstack.h" +#include "src/pthread/pthread_attr_setstacksize.h" +#include "src/pthread/pthread_create.h" +#include "src/pthread/pthread_join.h" +#include "src/pthread/pthread_self.h" + +#include "src/sys/mman/mmap.h" +#include "src/sys/mman/munmap.h" +#include "src/sys/random/getrandom.h" + +#include "src/__support/CPP/array.h" +#include "src/__support/CPP/atomic.h" +#include "src/__support/CPP/new.h" +#include "src/__support/threads/thread.h" +#include "src/errno/libc_errno.h" + +#include "test/IntegrationTest/test.h" + +#include // For EXEC_PAGESIZE. +#include + +struct TestThreadArgs { + pthread_attr_t Attrs; + void *Ret; +}; +static __llvm_libc::AllocChecker AC; +static __llvm_libc::cpp::Atomic GlobalThrCount = 0; + +static void *successThread(void *arg) { + pthread_t Th = __llvm_libc::pthread_self(); + auto *Thread = reinterpret_cast<__llvm_libc::Thread *>(&Th); + + ASSERT_EQ(libc_errno, 0); + ASSERT_TRUE(Thread); + ASSERT_TRUE(Thread->attrib); + + TestThreadArgs *ThArg = reinterpret_cast(arg); + pthread_attr_t *ExpecAttrs = &(ThArg->Attrs); + void *Ret = ThArg->Ret; + + void *ExpecStack; + size_t ExpecStackSize, ExpecGuardSize, ExpecStackSize2; + int ExpecDetached; + + ASSERT_FALSE(__llvm_libc::pthread_attr_getstack(ExpecAttrs, &ExpecStack, + &ExpecStackSize)); + ASSERT_EQ(libc_errno, 0); + + ASSERT_FALSE( + __llvm_libc::pthread_attr_getstacksize(ExpecAttrs, &ExpecStackSize2)); + ASSERT_EQ(libc_errno, 0); + + ASSERT_FALSE( + __llvm_libc::pthread_attr_getguardsize(ExpecAttrs, &ExpecGuardSize)); + ASSERT_EQ(libc_errno, 0); + + ASSERT_FALSE( + __llvm_libc::pthread_attr_getdetachstate(ExpecAttrs, &ExpecDetached)); + ASSERT_EQ(libc_errno, 0); + + ASSERT_EQ(ExpecStackSize, ExpecStackSize2); + + ASSERT_TRUE(Thread->attrib->stack); + if (ExpecStack != nullptr) { + ASSERT_EQ(Thread->attrib->stack, ExpecStack); + } else { + ASSERT_EQ(reinterpret_cast(Thread->attrib->stack) % + EXEC_PAGESIZE, + static_cast(0)); + ExpecStackSize = (ExpecStackSize + EXEC_PAGESIZE - 1) & (-EXEC_PAGESIZE); + } + + ASSERT_TRUE(ExpecStackSize); + ASSERT_EQ(Thread->attrib->stacksize, ExpecStackSize); + ASSERT_EQ(Thread->attrib->guardsize, ExpecGuardSize); + + ASSERT_EQ(ExpecDetached == PTHREAD_CREATE_JOINABLE, + Thread->attrib->detach_state.load() == + static_cast(__llvm_libc::DetachState::JOINABLE)); + ASSERT_EQ(ExpecDetached == PTHREAD_CREATE_DETACHED, + Thread->attrib->detach_state.load() == + static_cast(__llvm_libc::DetachState::DETACHED)); + + { + // Allocate some bytes on the stack on most of the stack and make sure we + // have read/write permissions on the memory. -EXEC_PAGESIZE / 2 just as a + // buffer so we don't go beyond our limits (no nested function calls / not + // much other data on the stack so should be enough). + size_t TestStackSize = ExpecStackSize - EXEC_PAGESIZE / 2; + volatile uint8_t *BytesOnStack = + (volatile uint8_t *)__builtin_alloca(TestStackSize); + + for (size_t i = 0; i < TestStackSize; ++i) { + // Write permissions + BytesOnStack[i] = static_cast(i); + } + + for (size_t i = 0; i < TestStackSize; ++i) { + // Read/write permissions + BytesOnStack[i] += static_cast(i); + } + } + + // TODO: If guardsize != 0 && ExpecStack == nullptr we should confirm that + // [stack - ExpecGuardSize, stack) is both mapped and has PROT_NONE + // permissions. Only way I can think of is to read from /proc/{self}/map. + + ASSERT_EQ(__llvm_libc::pthread_attr_destroy(ExpecAttrs), 0); + ASSERT_EQ(libc_errno, 0); + + // Arg is malloced, so free. + delete ThArg; + GlobalThrCount.fetch_sub(1); + return Ret; +} + +static void runSuccessConfig(int detachstate, size_t GuardSize, + size_t StackSize, bool customstack) { + + TestThreadArgs *ThArg = new (AC) TestThreadArgs{}; + pthread_attr_t *attr = &(ThArg->Attrs); + + ASSERT_EQ(__llvm_libc::pthread_attr_init(attr), 0); + ASSERT_EQ(libc_errno, 0); + + ASSERT_EQ(__llvm_libc::pthread_attr_setdetachstate(attr, detachstate), 0); + ASSERT_EQ(libc_errno, 0); + + ASSERT_EQ(__llvm_libc::pthread_attr_setguardsize(attr, GuardSize), 0); + ASSERT_EQ(libc_errno, 0); + + void *stack = nullptr; + if (customstack) { + stack = __llvm_libc::mmap(nullptr, StackSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(stack, MAP_FAILED); + ASSERT_NE(stack, static_cast(nullptr)); + ASSERT_EQ(libc_errno, 0); + + ASSERT_EQ(__llvm_libc::pthread_attr_setstack(attr, stack, StackSize), 0); + ASSERT_EQ(libc_errno, 0); + } else { + ASSERT_EQ(__llvm_libc::pthread_attr_setstacksize(attr, StackSize), 0); + ASSERT_EQ(libc_errno, 0); + } + + void *ExpecRet = nullptr; + if (detachstate == PTHREAD_CREATE_JOINABLE) { + ASSERT_EQ(__llvm_libc::getrandom(&ExpecRet, sizeof(ExpecRet), 0), + static_cast(sizeof(ExpecRet))); + ASSERT_EQ(libc_errno, 0); + } + + ThArg->Ret = ExpecRet; + GlobalThrCount.fetch_add(1); + + pthread_t Tid; + // ThArg and attr are cleanup by the thread. + ASSERT_EQ(__llvm_libc::pthread_create(&Tid, attr, successThread, + reinterpret_cast(ThArg)), + 0); + ASSERT_EQ(libc_errno, 0); + + if (detachstate == PTHREAD_CREATE_JOINABLE) { + void *ThRet; + ASSERT_EQ(__llvm_libc::pthread_join(Tid, &ThRet), 0); + ASSERT_EQ(libc_errno, 0); + ASSERT_EQ(ThRet, ExpecRet); + + if (customstack) { + ASSERT_EQ(__llvm_libc::munmap(stack, StackSize), 0); + ASSERT_EQ(libc_errno, 0); + } + } else { + ASSERT_FALSE(customstack); + } +} + +static void runSuccessTests() { + + // Test parameters + using __llvm_libc::cpp::array; + + array DetachStates = {PTHREAD_CREATE_DETACHED, + PTHREAD_CREATE_JOINABLE}; + array GuardSizes = {0, EXEC_PAGESIZE, 2 * EXEC_PAGESIZE, + 123 * EXEC_PAGESIZE}; + array StackSizes = {PTHREAD_STACK_MIN, + PTHREAD_STACK_MIN + 16, + (1 << 16) - EXEC_PAGESIZE / 2, + (1 << 16) + EXEC_PAGESIZE / 2, + 1234560, + 1234560 * 2}; + array CustomStacks = {true, false}; + + for (int DetachState : DetachStates) { + for (size_t GuardSize : GuardSizes) { + for (size_t StackSize : StackSizes) { + for (bool CustomStack : CustomStacks) { + if (CustomStack) { + + // TODO: I can't figure out how to test a user allocated stack along + // with detached pthread safely. We can't let the thread deallocate + // it owns stack for obvious reasons. And there doesn't appear to be + // a good way to check if a detached thread has exited. + if (DetachState == PTHREAD_CREATE_DETACHED) + continue; + + // GuardSize has no meaning with user provided stack. + if (GuardSize) + continue; + + runSuccessConfig(DetachState, GuardSize, StackSize, CustomStack); + } + } + } + } + } + + // Wait for detached threads to finish testing (this is not gurantee they will + // have cleaned up) + while (GlobalThrCount.load()) + ; +} + +static void *failureThread(void *) { + // Should be unreachable; + ASSERT_TRUE(false); + return nullptr; +} +static void runFailureConfig(size_t GuardSize, size_t StackSize) { + pthread_attr_t Attr; + GuardSize &= -EXEC_PAGESIZE; + ASSERT_EQ(__llvm_libc::pthread_attr_init(&Attr), 0); + ASSERT_EQ(libc_errno, 0); + + ASSERT_EQ(__llvm_libc::pthread_attr_setguardsize(&Attr, GuardSize), 0); + ASSERT_EQ(libc_errno, 0); + + ASSERT_EQ(__llvm_libc::pthread_attr_setstacksize(&Attr, StackSize), 0); + ASSERT_EQ(libc_errno, 0); + + pthread_t Tid; + int Result = __llvm_libc::pthread_create(&Tid, &Attr, failureThread, nullptr); + // EINVAL if we caught on overflow or something of that nature. EAGAIN if it + // was just really larger we failed mmap. + ASSERT_TRUE(Result == EINVAL || Result == EAGAIN); + // pthread_create should NOT set errno on error + ASSERT_EQ(libc_errno, 0); + + ASSERT_EQ(__llvm_libc::pthread_attr_destroy(&Attr), 0); + ASSERT_EQ(libc_errno, 0); +} + +static void runFailureTests() { + // Just some tests where the user sets "valid" parameters but they fail + // (overflow or too large to allocate). + runFailureConfig(SIZE_MAX, PTHREAD_STACK_MIN); + runFailureConfig(SIZE_MAX - PTHREAD_STACK_MIN, PTHREAD_STACK_MIN * 2); + runFailureConfig(PTHREAD_STACK_MIN, SIZE_MAX); + runFailureConfig(PTHREAD_STACK_MIN, SIZE_MAX - PTHREAD_STACK_MIN); + runFailureConfig(SIZE_MAX / 2, SIZE_MAX / 2); + runFailureConfig(SIZE_MAX / 4, SIZE_MAX / 4); + runFailureConfig(SIZE_MAX / 2 + 1234, SIZE_MAX / 2); + + // TODO: Should we add some tests where the user sets invalid values in the + // attr field (i.e a StackSize < PTHREAD_STACK_MIN)? The pthread_attr_set* API + // should guard against that, but people might accidentaly use uninitialized + // memory or w.e) +} + +TEST_MAIN() { + libc_errno = 0; + runSuccessTests(); + runFailureTests(); + return 0; +}