diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -144,7 +144,15 @@ ]; } +def ThreadStartT : TypeDecl<"thrd_start_t"> { + let Decl = "typedef int (*thrd_start_t)(void *);"; +} + def ThreadsAPI : PublicAPI<"threads.h"> { + let TypeDeclarations = [ + ThreadStartT, + ]; + let Enumerations = [ "mtx_plain", "mtx_recursive", @@ -155,4 +163,9 @@ "thrd_error", "thrd_nomem", ]; + + let Functions = [ + "thrd_create", + "thrd_join", + ]; } diff --git a/libc/include/threads.h.def b/libc/config/linux/threads.h.in copy from libc/include/threads.h.def copy to libc/config/linux/threads.h.in --- a/libc/include/threads.h.def +++ b/libc/config/linux/threads.h.in @@ -1,4 +1,4 @@ -//===---------------- C standard library header threads.h -----------------===// +//===--------- Linux specific definitions of types from threads.h ---------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,12 +6,12 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_LIBC_THREADS_H -#define LLVM_LIBC_THREADS_H +%%begin() -#include <__llvm-libc-common.h> - -%%public_api() - - -#endif // LLVM_LIBC_THREADS_H +typedef struct { + unsigned char __clear_tid[4]; + int __tid; + void *__stack; + int __stack_size; + int __retval; +} thrd_t; diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -39,8 +39,12 @@ threads_h DEF_FILE threads.h.def GEN_HDR threads.h + PARAMS + platform_threads=../config/${LIBC_TARGET_OS}/threads.h.in DEPENDS llvm_libc_common_h + DATA_FILES + ../config/${LIBC_TARGET_OS}/threads.h.in ) add_gen_header( diff --git a/libc/include/threads.h.def b/libc/include/threads.h.def --- a/libc/include/threads.h.def +++ b/libc/include/threads.h.def @@ -11,6 +11,8 @@ #include <__llvm-libc-common.h> +%%include_file(${platform_threads}) + %%public_api() diff --git a/libc/lib/CMakeLists.txt b/libc/lib/CMakeLists.txt --- a/libc/lib/CMakeLists.txt +++ b/libc/lib/CMakeLists.txt @@ -15,6 +15,10 @@ # signal.h entrypoints raise + + # threads.h entrypoints + thrd_create + thrd_join ) add_entrypoint_library( diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt --- a/libc/src/CMakeLists.txt +++ b/libc/src/CMakeLists.txt @@ -4,5 +4,6 @@ add_subdirectory(string) # TODO: Add this target conditional to the target OS. add_subdirectory(sys) +add_subdirectory(threads) add_subdirectory(__support) diff --git a/libc/src/threads/CMakeLists.txt b/libc/src/threads/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/threads/CMakeLists.txt @@ -0,0 +1,3 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) + add_subdirectory(${LIBC_TARGET_OS}) +endif() diff --git a/libc/src/threads/linux/CMakeLists.txt b/libc/src/threads/linux/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/threads/linux/CMakeLists.txt @@ -0,0 +1,37 @@ +add_header_library( + threads_utils + HDRS + thread_utils.h +) + +add_entrypoint_object( + thrd_create + SRCS + thrd_create.cpp + HDRS + ../thrd_create.h + DEPENDS + errno_h + linux_syscall_h + mmap + support_common_h + sys_syscall_h + threads_h + threads_utils + __errno_location +) + +add_entrypoint_object( + thrd_join + SRCS + thrd_join.cpp + HDRS + ../thrd_join.h + DEPENDS + linux_syscall_h + munmap + support_common_h + sys_syscall_h + threads_h + threads_utils +) diff --git a/libc/src/threads/linux/thrd_create.cpp b/libc/src/threads/linux/thrd_create.cpp new file mode 100644 --- /dev/null +++ b/libc/src/threads/linux/thrd_create.cpp @@ -0,0 +1,83 @@ +//===---------- Linux implementation of the thrd_create function ----------===// +// +// 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 "config/linux/syscall.h" // For syscall function. +#include "include/errno.h" // For E* error values. +#include "include/sys/mman.h" // For PROT_* and MAP_* definitions. +#include "include/sys/syscall.h" // For syscall numbers. +#include "include/threads.h" // For thrd_* type definitions. +#include "src/__support/common.h" +#include "src/errno/llvmlibc_errno.h" +#include "src/sys/mman/mmap.h" +#include "src/sys/mman/munmap.h" +#include "src/threads/linux/thread_utils.h" + +#include +#include // For futex operations. +#include // For CLONE_* flags. +#include + +namespace __llvm_libc { + +static void start_thread(thrd_t *thread, thrd_start_t func, void *arg) { + __llvm_libc::syscall(SYS_exit, thread->__retval = func(arg)); +} + +int LLVM_LIBC_ENTRYPOINT(thrd_create)(thrd_t *thread, thrd_start_t func, + void *arg) { + unsigned clone_flags = + CLONE_VM // Share the memory space with the parent. + | CLONE_FS // Share the file system with its parent. + | CLONE_FILES // Shares the files with the parent thread. + | CLONE_SIGHAND // Share the signal handlers with the parent. + | CLONE_THREAD // Same thread group as the parent. + | CLONE_SYSVSEM // Share a single list of System V semaphore adjustment + // values + | CLONE_PARENT_SETTID // Set child thread ID in |ptid| of the parent. + | CLONE_CHILD_CLEARTID; // Let the kernel clear the tid address and futex + // wake the joining thread. + // TODO: Add the CLONE_SETTLS flag and setup the TLS area correctly when + // making the clone syscall. + + void *stack = + __llvm_libc::mmap(nullptr, DEFAULT_STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (stack == MAP_FAILED) { + if (llvmlibc_errno == ENOMEM) + return thrd_nomem; + else + return thrd_error; + } + + thread->__stack = stack; + thread->__stack_size = DEFAULT_STACK_SIZE; + thread->__retval = -1; + FutexData *clear_tid_address = + reinterpret_cast(thread->__clear_tid); + *clear_tid_address = CLEAR_TID_VALUE; + + long clone_result = __llvm_libc::syscall( + SYS_clone, clone_flags, (long)stack + DEFAULT_STACK_SIZE - 1, + &thread->__tid, clear_tid_address, 0); + + if (clone_result == 0) { + start_thread(thread, func, arg); + } + + if (clone_result < 0) { + int error_val = -clone_result; + if (error_val == ENOMEM) + return thrd_nomem; + else + return thrd_error; + } + + return thrd_success; +} + +} // namespace __llvm_libc diff --git a/libc/src/threads/linux/thrd_join.cpp b/libc/src/threads/linux/thrd_join.cpp new file mode 100644 --- /dev/null +++ b/libc/src/threads/linux/thrd_join.cpp @@ -0,0 +1,48 @@ +//===----------- Linux implementation of the thrd_join function -----------===// +// +// 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 "config/linux/syscall.h" // For syscall function. +#include "include/sys/syscall.h" // For syscall numbers. +#include "include/threads.h" // For thrd_* type definitions. +#include "src/__support/common.h" +#include "src/sys/mman/munmap.h" +#include "src/threads/linux/thread_utils.h" + +#include // For futex operations. +#include // For atomic load. + +namespace __llvm_libc { + +extern FutexData ctid; + +int LLVM_LIBC_ENTRYPOINT(thrd_join)(thrd_t *thread, int *retval) { + FutexData *clear_tid_address = + reinterpret_cast(thread->__clear_tid); + + while (true) { + // We cannot do a FUTEX_WAIT_PRIVATE here as the kernel does a + // FUTEX_WAKE and not a FUTEX_WAKE_PRIVATE. + __llvm_libc::syscall(SYS_futex, clear_tid_address, FUTEX_WAIT, + CLEAR_TID_VALUE, nullptr); + + // The kernel should set the value at the clear tid address to zero. + // If not, it is a spurious wake and we should continue to wait on + // the futex. + if (atomic_load(clear_tid_address) == 0) + break; + } + + *retval = thread->__retval; + + if (__llvm_libc::munmap(thread->__stack, thread->__stack_size) == -1) + return thrd_error; + + return thrd_success; +} + +} // namespace __llvm_libc diff --git a/libc/src/threads/linux/thread_utils.h b/libc/src/threads/linux/thread_utils.h new file mode 100644 --- /dev/null +++ b/libc/src/threads/linux/thread_utils.h @@ -0,0 +1,20 @@ +//===--- Linux specific definitions to support mutex operations --*- C++ -*===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_THREADS_LINUX_THREAD_UTILS_H +#define LLVM_LIBC_SRC_THREADS_LINUX_THREAD_UTILS_H + +#include + +using FutexData = _Atomic uint32_t; + +#define DEFAULT_STACK_SIZE (1 << 15) // 32 KB + +#define CLEAR_TID_VALUE 0xABCD1234 + +#endif // LLVM_LIBC_SRC_THREADS_LINUX_THREAD_UTILS_H diff --git a/libc/src/threads/thrd_create.h b/libc/src/threads/thrd_create.h new file mode 100644 --- /dev/null +++ b/libc/src/threads/thrd_create.h @@ -0,0 +1,20 @@ +//===------- Implementation header for thrd_create function ------ *-C++-* ===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_THREADS_LINUX_THRD_CREATE_H +#define LLVM_LIBC_SRC_THREADS_LINUX_THRD_CREATE_H + +#include "include/threads.h" + +namespace __llvm_libc { + +int thrd_create(thrd_t *thread, thrd_start_t func, void *arg); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_THREADS_LINUX_THRD_CREATE_H diff --git a/libc/src/threads/thrd_join.h b/libc/src/threads/thrd_join.h new file mode 100644 --- /dev/null +++ b/libc/src/threads/thrd_join.h @@ -0,0 +1,20 @@ +//===-------- Implementation header for thrd_join function ------- *-C++-* ===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_THREADS_LINUX_THRD_JOIN_H +#define LLVM_LIBC_SRC_THREADS_LINUX_THRD_JOIN_H + +#include "include/threads.h" + +namespace __llvm_libc { + +int thrd_join(thrd_t *thread, int *retval); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_THREADS_LINUX_THRD_JOIN_H diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt --- a/libc/test/src/CMakeLists.txt +++ b/libc/test/src/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory(signal) add_subdirectory(string) add_subdirectory(sys) +add_subdirectory(threads) diff --git a/libc/test/src/threads/CMakeLists.txt b/libc/test/src/threads/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/src/threads/CMakeLists.txt @@ -0,0 +1,3 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) + add_subdirectory(${LIBC_TARGET_OS}) +endif() diff --git a/libc/test/src/threads/linux/CMakeLists.txt b/libc/test/src/threads/linux/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/src/threads/linux/CMakeLists.txt @@ -0,0 +1,16 @@ +add_libc_testsuite(libc_threads_unittests) + +add_libc_unittest( + thrd_test + SUITE + libc_threads_unittests + SRCS + thrd_test.cpp + DEPENDS + __errno_location + mmap + munmap + threads_h + thrd_create + thrd_join +) diff --git a/libc/test/src/threads/linux/thrd_test.cpp b/libc/test/src/threads/linux/thrd_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/threads/linux/thrd_test.cpp @@ -0,0 +1,56 @@ +//===---------------------- Unittests for thrd_t --------------------------===// +// +// 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 "include/threads.h" +#include "src/threads/thrd_create.h" +#include "src/threads/thrd_join.h" +#include "utils/UnitTest/Test.h" + +#include + +static int counter = 0; +static int thread_func(void *arg) { + ++counter; + return 0; +} + +TEST(ThreadTest, CreateAndJoin) { + while (true) { + thrd_t thread; + int old_counter_val = counter; + ASSERT_EQ(__llvm_libc::thrd_create(&thread, thread_func, nullptr), + (int)thrd_success); + int retval = 123; + ASSERT_EQ(__llvm_libc::thrd_join(&thread, &retval), (int)thrd_success); + ASSERT_EQ(retval, 0); + ASSERT_EQ(counter, old_counter_val + 1); + if (counter > 1000) + break; + } +} + +static int return_arg(void *arg) { return *reinterpret_cast(arg); } + +TEST(ThreadTest, SpawnAndJoin) { + constexpr int thread_count = 1000; + thrd_t thread_list[thread_count]; + int args[thread_count]; + + for (int i = 0; i < thread_count; ++i) { + args[i] = i; + ASSERT_EQ(__llvm_libc::thrd_create(&thread_list[i], return_arg, args + i), + (int)thrd_success); + } + + for (int i = 0; i < thread_count; ++i) { + int retval = 123; // Start with a retval we dont expect. + ASSERT_EQ(__llvm_libc::thrd_join(&thread_list[i], &retval), + (int)thrd_success); + ASSERT_EQ(retval, i); + } +}