# SRCS <list of .cpp files>
# HDRS <list of .h files>
# DEPENDS <list of dependencies>
+# COMPILE_OPTIONS <list of special compile options for this target>
# )
function(add_entrypoint_object target_name)
cmake_parse_arguments(
"ADD_ENTRYPOINT_OBJ"
"REDIRECTED" # Optional argument
"NAME" # Single value arguments
- "SRCS;HDRS;DEPENDS" # Multi value arguments
+ "SRCS;HDRS;DEPENDS;COMPILE_OPTIONS" # Multi value arguments
${ARGN}
)
if(NOT ADD_ENTRYPOINT_OBJ_SRCS)
${ADD_ENTRYPOINT_OBJ_DEPENDS}
)
endif()
+ if(ADD_ENTRYPOINT_OBJ_COMPILE_OPTIONS)
+ target_compile_options(
+ ${target_name}_objects
+ PRIVATE ${ADD_ENTRYPOINT_OBJ_COMPILE_OPTIONS}
+ )
+ endif()
set(object_file_raw "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_raw.o")
set(object_file "${CMAKE_CURRENT_BINARY_DIR}/${target_name}.o")
];
}
+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",
"thrd_error",
"thrd_nomem",
];
+
+ let Functions = [
+ "thrd_create",
+ "thrd_join",
+ ];
}
--- /dev/null
+//===--------- 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.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+%%begin()
+
+typedef struct {
+ unsigned char __clear_tid[4];
+ int __tid;
+ void *__stack;
+ int __stack_size;
+ int __retval;
+} thrd_t;
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(
#include <__llvm-libc-common.h>
+%%include_file(${platform_threads})
+
%%public_api()
#endif // LLVM_LIBC_THREADS_H
# stdlib.h entrypoints
_Exit
abort
+
+ # threads.h entrypoints
+ thrd_create
+ thrd_join
)
add_entrypoint_library(
add_subdirectory(string)
# TODO: Add this target conditional to the target OS.
add_subdirectory(sys)
+add_subdirectory(threads)
add_subdirectory(__support)
--- /dev/null
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+ add_subdirectory(${LIBC_TARGET_OS})
+endif()
--- /dev/null
+add_gen_header(
+ thread_start_args_h
+ DEF_FILE thread_start_args.h.def
+ GEN_HDR thread_start_args.h
+ PARAMS
+ thread_start_args=${LIBC_TARGET_MACHINE}/thread_start_args.h.in
+ DATA_FILES
+ ${LIBC_TARGET_MACHINE}/thread_start_args.h.in
+)
+
+add_header_library(
+ threads_utils
+ HDRS
+ thread_utils.h
+ DEPENDS
+ thread_start_args_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
+ COMPILE_OPTIONS
+ -fno-omit-frame-pointer # This allows us to sniff out the thread args from
+ # the new thread's stack reliably.
+)
+
+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
+)
--- /dev/null
+//===---------- 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 <linux/futex.h> // For futex operations.
+#include <linux/sched.h> // For CLONE_* flags.
+#include <stdint.h>
+
+namespace __llvm_libc {
+
+struct StartArgs {
+ thrd_t *thread;
+ thrd_start_t func;
+ void *arg;
+};
+
+static __attribute__((noinline)) void start_thread() {
+ StartArgs *start_args = reinterpret_cast<StartArgs *>(get_start_args_addr());
+ __llvm_libc::syscall(SYS_exit, start_args->thread->__retval =
+ start_args->func(start_args->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 the parent.
+ | CLONE_FILES // Share the files with the parent.
+ | 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, ThreadParams::DefaultStackSize,
+ PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (stack == MAP_FAILED)
+ return llvmlibc_errno == ENOMEM ? thrd_nomem : thrd_error;
+
+ thread->__stack = stack;
+ thread->__stack_size = ThreadParams::DefaultStackSize;
+ thread->__retval = -1;
+ FutexData *clear_tid_address =
+ reinterpret_cast<FutexData *>(thread->__clear_tid);
+ *clear_tid_address = ThreadParams::ClearTIDValue;
+
+ // When the new thread is spawned by the kernel, the new thread gets the
+ // stack we pass to the clone syscall. However, this stack is empty and does
+ // not have any local vars present in this function. Hence, one cannot
+ // pass arguments to the thread start function, or use any local vars from
+ // here. So, we pack them into the new stack from where the thread can sniff
+ // them out.
+ uintptr_t adjusted_stack = reinterpret_cast<uintptr_t>(stack) +
+ ThreadParams::DefaultStackSize - sizeof(StartArgs);
+ StartArgs *start_args = reinterpret_cast<StartArgs *>(adjusted_stack);
+ start_args->thread = thread;
+ start_args->func = func;
+ start_args->arg = arg;
+
+ // TODO: The arguments to the clone syscall below is correct for x86_64
+ // but it might differ for other architectures. So, make this call
+ // architecture independent. May be implement a glibc like wrapper for clone
+ // and use it here.
+ long clone_result =
+ __llvm_libc::syscall(SYS_clone, clone_flags, adjusted_stack,
+ &thread->__tid, clear_tid_address, 0);
+
+ if (clone_result == 0) {
+ start_thread();
+ } else if (clone_result < 0) {
+ int error_val = -clone_result;
+ return error_val == ENOMEM ? thrd_nomem : thrd_error;
+ }
+
+ return thrd_success;
+}
+
+} // namespace __llvm_libc
--- /dev/null
+//===----------- 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 <linux/futex.h> // For futex operations.
+#include <stdatomic.h> // For atomic_load.
+
+namespace __llvm_libc {
+
+int LLVM_LIBC_ENTRYPOINT(thrd_join)(thrd_t *thread, int *retval) {
+ FutexData *clear_tid_address =
+ reinterpret_cast<FutexData *>(thread->__clear_tid);
+
+ // 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.
+ while (atomic_load(clear_tid_address) != 0) {
+ // 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,
+ ThreadParams::ClearTIDValue, nullptr);
+ }
+
+ *retval = thread->__retval;
+
+ if (__llvm_libc::munmap(thread->__stack, thread->__stack_size) == -1)
+ return thrd_error;
+
+ return thrd_success;
+}
+
+} // namespace __llvm_libc
--- /dev/null
+//===---- Implementation of the get_start_args_addr 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
+//
+//===----------------------------------------------------------------------===//
+
+#include <stdint.h>
+
+%%include_file(${thread_start_args})
--- /dev/null
+//===-- Linux specific definitions for threads implementations. --*- 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 "thread_start_args.h"
+
+#include <stdatomic.h>
+#include <stdint.h>
+
+// The futex data has to be exactly 4 bytes long. However, we use a uint type
+// here as we do not want to use `_Atomic uint32_t` as the _Atomic keyword which
+// is C only. The header stdatomic.h does not define an atomic type
+// corresponding to `uint32_t` or to something which is exaclty 4 bytes wide.
+using FutexData = atomic_uint;
+
+static_assert(sizeof(atomic_uint) == 4,
+ "Size of the `atomic_uint` type is not 4 bytes on your platform. "
+ "The implementation of the standard threads library for linux "
+ "requires that size of `atomic_uint` be 4 bytes.");
+
+struct ThreadParams {
+ static constexpr uintptr_t DefaultStackSize = 1 << 16; // 64 KB
+ static constexpr uint32_t ClearTIDValue = 0xABCD1234;
+};
+
+#endif // LLVM_LIBC_SRC_THREADS_LINUX_THREAD_UTILS_H
--- /dev/null
+//===- x86_64 implementation of the get_start_args_addr 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
+//
+//===----------------------------------------------------------------------===//
+
+%%begin()
+
+__attribute__((always_inline)) inline uintptr_t get_start_args_addr() {
+ // NOTE: For __builtin_frame_address to work reliably across compilers,
+ // architectures and various optimization levels, the TU including this file
+ // should be compiled with -fno-omit-frame-pointer.
+ return reinterpret_cast<uintptr_t>(__builtin_frame_address(0)) +
+ // The x86_64 call instruction pushes resume address on to the stack.
+ // Next, The x86_64 SysV ABI also pushes the frame pointer on the
+ // stack. Hence, we look past these items to get to the start args.
+ sizeof(uintptr_t) * 2;
+}
+
--- /dev/null
+//===------- 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
--- /dev/null
+//===-------- 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
add_subdirectory(stdlib)
add_subdirectory(string)
add_subdirectory(sys)
+add_subdirectory(threads)
--- /dev/null
+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
+)
--- /dev/null
+//===---------------------- 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"
+
+static constexpr int thread_count = 1000;
+static int counter = 0;
+static int thread_func(void *) {
+ ++counter;
+ return 0;
+}
+
+TEST(ThreadTest, CreateAndJoin) {
+ for (counter = 0; counter <= thread_count;) {
+ thrd_t thread;
+ int old_counter_val = counter;
+ ASSERT_EQ(__llvm_libc::thrd_create(&thread, thread_func, nullptr),
+ (int)thrd_success);
+ int retval = thread_count + 1; // Start with a retval we dont expect.
+ ASSERT_EQ(__llvm_libc::thrd_join(&thread, &retval), (int)thrd_success);
+ ASSERT_EQ(retval, 0);
+ ASSERT_EQ(counter, old_counter_val + 1);
+ }
+}
+
+static int return_arg(void *arg) { return *reinterpret_cast<int *>(arg); }
+
+TEST(ThreadTest, SpawnAndJoin) {
+ 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 = thread_count + 1; // Start with a retval we dont expect.
+ ASSERT_EQ(__llvm_libc::thrd_join(&thread_list[i], &retval),
+ (int)thrd_success);
+ ASSERT_EQ(retval, i);
+ }
+}