2 changes: 1 addition & 1 deletion libc/src/pthread/pthread_attr_setstacksize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
#include "src/__support/common.h"

#include <errno.h>
#include <linux/param.h> // For EXEC_PAGESIZE.
#include <pthread.h>

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;
Expand Down
62 changes: 58 additions & 4 deletions libc/src/pthread/pthread_create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@

#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/macros/optimization.h"
#include "src/__support/threads/thread.h"

#include <errno.h>
Expand All @@ -20,12 +28,58 @@ static_assert(sizeof(pthread_t) == sizeof(__llvm_libc::Thread),
"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 (LIBC_UNLIKELY(__llvm_libc::pthread_attr_init(&default_attr) != 0))
return EINVAL;

attr = &default_attr;
}

void *stack;
size_t stacksize, guardsize;
int detachstate;

// As of writing this all the `pthread_attr_get*` functions always succeed.
if (LIBC_UNLIKELY(
__llvm_libc::pthread_attr_getstack(attr, &stack, &stacksize) != 0))
return EINVAL;

if (LIBC_UNLIKELY(__llvm_libc::pthread_attr_getguardsize(attr, &guardsize) !=
0))
return EINVAL;

if (LIBC_UNLIKELY(
__llvm_libc::pthread_attr_getdetachstate(attr, &detachstate) != 0))
return EINVAL;

if (attr == &default_attr)
// Should we fail here? Its non-issue as the moment as pthread_attr_destroy
// can only succeed.
if (LIBC_UNLIKELY(__llvm_libc::pthread_attr_destroy(&default_attr) != 0))
return EINVAL;

if (stacksize && stacksize < PTHREAD_STACK_MIN)
return EINVAL;

if (guardsize % EXEC_PAGESIZE != 0)
return EINVAL;

if (detachstate != PTHREAD_CREATE_DETACHED &&
detachstate != PTHREAD_CREATE_JOINABLE)
return EINVAL;

// Thread::run will check validity of the `stack` argument (stack alignment is
// universal, not sure a pthread requirement).

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,
detachstate == PTHREAD_CREATE_DETACHED);
if (result != 0 && result != EPERM && result != EINVAL)
return EAGAIN;
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion libc/src/threads/thrd_create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ static_assert(sizeof(thrd_t) == sizeof(__llvm_libc::Thread),
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ void detach_simple_test() {
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.
Expand Down
31 changes: 31 additions & 0 deletions libc/test/integration/src/pthread/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,34 @@ add_integration_test(
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
)
339 changes: 339 additions & 0 deletions libc/test/integration/src/pthread/pthread_create_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
//===-- 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 <linux/param.h> // For EXEC_PAGESIZE.
#include <pthread.h>

struct TestThreadArgs {
pthread_attr_t attrs;
void *ret;
};
static __llvm_libc::AllocChecker global_ac;
static __llvm_libc::cpp::Atomic<long> global_thr_count = 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 *th_arg = reinterpret_cast<TestThreadArgs *>(Arg);
pthread_attr_t *expec_attrs = &(th_arg->attrs);
void *ret = th_arg->ret;

void *expec_stack;
size_t expec_stacksize, expec_guardsize, expec_stacksize2;
int expec_detached;

ASSERT_EQ(__llvm_libc::pthread_attr_getstack(expec_attrs, &expec_stack,
&expec_stacksize),
0);
ASSERT_EQ(libc_errno, 0);

ASSERT_EQ(
__llvm_libc::pthread_attr_getstacksize(expec_attrs, &expec_stacksize2),
0);
ASSERT_EQ(libc_errno, 0);

ASSERT_EQ(
__llvm_libc::pthread_attr_getguardsize(expec_attrs, &expec_guardsize), 0);
ASSERT_EQ(libc_errno, 0);

ASSERT_EQ(
__llvm_libc::pthread_attr_getdetachstate(expec_attrs, &expec_detached),
0);
ASSERT_EQ(libc_errno, 0);

ASSERT_EQ(expec_stacksize, expec_stacksize2);

ASSERT_TRUE(thread->attrib->stack);
if (expec_stack != nullptr) {
ASSERT_EQ(thread->attrib->stack, expec_stack);
} else {
ASSERT_EQ(reinterpret_cast<uintptr_t>(thread->attrib->stack) %
EXEC_PAGESIZE,
static_cast<uintptr_t>(0));
expec_stacksize = (expec_stacksize + EXEC_PAGESIZE - 1) & (-EXEC_PAGESIZE);
}

ASSERT_TRUE(expec_stacksize);
ASSERT_EQ(thread->attrib->stacksize, expec_stacksize);
ASSERT_EQ(thread->attrib->guardsize, expec_guardsize);

ASSERT_EQ(expec_detached == PTHREAD_CREATE_JOINABLE,
thread->attrib->detach_state.load() ==
static_cast<uint32_t>(__llvm_libc::DetachState::JOINABLE));
ASSERT_EQ(expec_detached == PTHREAD_CREATE_DETACHED,
thread->attrib->detach_state.load() ==
static_cast<uint32_t>(__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 test_stacksize = expec_stacksize - EXEC_PAGESIZE / 2;
volatile uint8_t *bytes_on_stack =
(volatile uint8_t *)__builtin_alloca(test_stacksize);

for (size_t I = 0; I < test_stacksize; ++I) {
// Write permissions
bytes_on_stack[I] = static_cast<uint8_t>(I);
}

for (size_t I = 0; I < test_stacksize; ++I) {
// Read/write permissions
bytes_on_stack[I] += static_cast<uint8_t>(I);
}
}

// TODO: If guardsize != 0 && expec_stack == nullptr we should confirm that
// [stack - expec_guardsize, stack) is both mapped and has PROT_NONE
// permissions. Maybe we can read from /proc/{self}/map?

ASSERT_EQ(__llvm_libc::pthread_attr_destroy(expec_attrs), 0);
ASSERT_EQ(libc_errno, 0);

// Arg is malloced, so free.
delete th_arg;
global_thr_count.fetch_sub(1);
return ret;
}

static void run_success_config(int detachstate, size_t guardsize,
size_t stacksize, bool customstack) {

TestThreadArgs *th_arg = new (global_ac) TestThreadArgs{};
pthread_attr_t *attr = &(th_arg->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<void *>(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 *expec_ret = nullptr;
if (detachstate == PTHREAD_CREATE_JOINABLE) {
ASSERT_EQ(__llvm_libc::getrandom(&expec_ret, sizeof(expec_ret), 0),
static_cast<ssize_t>(sizeof(expec_ret)));
ASSERT_EQ(libc_errno, 0);
}

th_arg->ret = expec_ret;
global_thr_count.fetch_add(1);

pthread_t tid;
// th_arg and attr are cleanup by the thread.
ASSERT_EQ(__llvm_libc::pthread_create(&tid, attr, successThread,
reinterpret_cast<void *>(th_arg)),
0);
ASSERT_EQ(libc_errno, 0);

if (detachstate == PTHREAD_CREATE_JOINABLE) {
void *th_ret;
ASSERT_EQ(__llvm_libc::pthread_join(tid, &th_ret), 0);
ASSERT_EQ(libc_errno, 0);
ASSERT_EQ(th_ret, expec_ret);

if (customstack) {
ASSERT_EQ(__llvm_libc::munmap(Stack, stacksize), 0);
ASSERT_EQ(libc_errno, 0);
}
} else {
ASSERT_FALSE(customstack);
}
}

static void run_success_tests() {

// Test parameters
using __llvm_libc::cpp::array;

array<int, 2> detachstates = {PTHREAD_CREATE_DETACHED,
PTHREAD_CREATE_JOINABLE};
array<size_t, 4> guardsizes = {0, EXEC_PAGESIZE, 2 * EXEC_PAGESIZE,
123 * EXEC_PAGESIZE};
array<size_t, 6> stacksizes = {PTHREAD_STACK_MIN,
PTHREAD_STACK_MIN + 16,
(1 << 16) - EXEC_PAGESIZE / 2,
(1 << 16) + EXEC_PAGESIZE / 2,
1234560,
1234560 * 2};
array<bool, 2> customstacks = {true, false};

for (int detachstate : detachstates) {
for (size_t guardsize : guardsizes) {
for (size_t stacksize : stacksizes) {
for (bool customstack : customstacks) {
if (customstack) {

// TODO: 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. NB: It's racey to just
// wait for an atomic variable at the end of the thread function as
// internal thread cleanup functions continue to use its stack.
// Maybe an `atexit` handler would work.
if (detachstate == PTHREAD_CREATE_DETACHED)
continue;

// Guardsize has no meaning with user provided stack.
if (guardsize)
continue;

run_success_config(detachstate, guardsize, stacksize, customstack);
}
}
}
}
}

// Wait for detached threads to finish testing (this is not gurantee they will
// have cleaned up)
while (global_thr_count.load())
;
}

static void *failure_thread(void *) {
// Should be unreachable;
ASSERT_TRUE(false);
return nullptr;
}

static void create_and_check_failure_thread(pthread_attr_t *attr) {
pthread_t tid;
int result = __llvm_libc::pthread_create(&tid, attr, failure_thread, 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 run_failure_config(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);

create_and_check_failure_thread(&attr);
}

static void run_failure_tests() {
// Just some tests where the user sets "valid" parameters but they fail
// (overflow or too large to allocate).
run_failure_config(SIZE_MAX, PTHREAD_STACK_MIN);
run_failure_config(SIZE_MAX - PTHREAD_STACK_MIN, PTHREAD_STACK_MIN * 2);
run_failure_config(PTHREAD_STACK_MIN, SIZE_MAX);
run_failure_config(PTHREAD_STACK_MIN, SIZE_MAX - PTHREAD_STACK_MIN);
run_failure_config(SIZE_MAX / 2, SIZE_MAX / 2);
run_failure_config(SIZE_MAX / 4, SIZE_MAX / 4);
run_failure_config(SIZE_MAX / 2 + 1234, SIZE_MAX / 2);

// Test invalid parameters that are impossible to obtain via the
// `pthread_attr_set*` API. Still test that this not entirely unlikely
// initialization doesn't cause any issues. Basically we wan't to make sure
// that `pthread_create` properly checks for input validity and doesn't rely
// on the `pthread_attr_set*` API.
pthread_attr_t attr;

// Stacksize too small.
ASSERT_EQ(__llvm_libc::pthread_attr_init(&attr), 0);
ASSERT_EQ(libc_errno, 0);
attr.__stacksize = PTHREAD_STACK_MIN - 16;
create_and_check_failure_thread(&attr);

// Stack misaligned.
ASSERT_EQ(__llvm_libc::pthread_attr_init(&attr), 0);
ASSERT_EQ(libc_errno, 0);
attr.__stack = reinterpret_cast<void *>(1);
create_and_check_failure_thread(&attr);

// Stack + stacksize misaligned.
ASSERT_EQ(__llvm_libc::pthread_attr_init(&attr), 0);
ASSERT_EQ(libc_errno, 0);
attr.__stacksize = PTHREAD_STACK_MIN + 1;
attr.__stack = reinterpret_cast<void *>(16);
create_and_check_failure_thread(&attr);

// Guardsize misaligned.
ASSERT_EQ(__llvm_libc::pthread_attr_init(&attr), 0);
ASSERT_EQ(libc_errno, 0);
attr.__guardsize = EXEC_PAGESIZE / 2;
create_and_check_failure_thread(&attr);

// Detachstate is unknown.
ASSERT_EQ(__llvm_libc::pthread_attr_init(&attr), 0);
ASSERT_EQ(libc_errno, 0);
attr.__detachstate = -1;
create_and_check_failure_thread(&attr);
}

TEST_MAIN() {
libc_errno = 0;
run_success_tests();
run_failure_tests();
return 0;
}