From 0baf0e8cfc1845ef92d397c1ae43793bf9e6aaad Mon Sep 17 00:00:00 2001 From: Siva Chandra Reddy Date: Tue, 12 May 2020 16:01:28 -0700 Subject: [PATCH] [libc] Add implementation of call_once from threads.h. Reviewers: abrachet, maskray Differential Revision: https://reviews.llvm.org/D79828 --- libc/config/linux/api.td | 19 ++++ libc/lib/CMakeLists.txt | 1 + libc/spec/stdc.td | 19 +++- libc/src/threads/CMakeLists.txt | 7 ++ libc/src/threads/call_once.h | 20 ++++ libc/src/threads/linux/CMakeLists.txt | 13 +++ libc/src/threads/linux/call_once.cpp | 58 ++++++++++++ libc/test/src/threads/CMakeLists.txt | 16 ++++ libc/test/src/threads/call_once_test.cpp | 111 +++++++++++++++++++++++ 9 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 libc/src/threads/call_once.h create mode 100644 libc/src/threads/linux/call_once.cpp create mode 100644 libc/test/src/threads/call_once_test.cpp diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td index cb070e1466ad..d45be84fa080 100644 --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -301,6 +301,12 @@ def SignalAPI : PublicAPI<"signal.h"> { ]; } +def OnceFlag : TypeDecl<"once_flag"> { + let Decl = [{ + typedef unsigned int once_flag; + }]; +} + def MtxT : TypeDecl<"mtx_t"> { let Decl = [{ typedef struct { @@ -314,8 +320,20 @@ def ThreadStartT : TypeDecl<"thrd_start_t"> { let Decl = "typedef int (*thrd_start_t)(void *);"; } +def CallOnceFuncT : TypeDecl<"__call_once_func_t"> { + let Decl = [{ + typedef void(*__call_once_func_t)(void); + }]; +} + def ThreadsAPI : PublicAPI<"threads.h"> { + let Macros = [ + SimpleMacroDef<"ONCE_FLAG_INIT", "0">, + ]; + let TypeDeclarations = [ + OnceFlag, + CallOnceFuncT, MtxT, ThreadStartT, ]; @@ -332,6 +350,7 @@ def ThreadsAPI : PublicAPI<"threads.h"> { ]; let Functions = [ + "call_once", "mtx_init", "mtx_lock", "mtx_unlock", diff --git a/libc/lib/CMakeLists.txt b/libc/lib/CMakeLists.txt index 1c500ba3e5da..51f587a2a70a 100644 --- a/libc/lib/CMakeLists.txt +++ b/libc/lib/CMakeLists.txt @@ -35,6 +35,7 @@ add_entrypoint_library( libc.src.sys.mman.munmap # threads.h entrypoints + libc.src.threads.call_once libc.src.threads.mtx_init libc.src.threads.mtx_lock libc.src.threads.mtx_unlock diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td index 139a1af84c58..4e6bfbfac160 100644 --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -8,6 +8,11 @@ def StdC : StandardSpec<"stdc"> { RestrictedPtrType CharRestrictedPtr = RestrictedPtrType; ConstType ConstCharRestrictedPtr = ConstType; + NamedType OnceFlagType = NamedType<"once_flag">; + PtrType OnceFlagTypePtr = PtrType; + // TODO(sivachandra): Remove this non-standard type when a formal + // way to describe callable types is available. + NamedType CallOnceFuncType = NamedType<"__call_once_func_t">; NamedType MtxTType = NamedType<"mtx_t">; PtrType MtxTTypePtr = PtrType; NamedType ThrdStartTType = NamedType<"thrd_start_t">; @@ -267,8 +272,12 @@ def StdC : StandardSpec<"stdc"> { HeaderSpec Threads = HeaderSpec< "threads.h", - [], // Macros [ + Macro<"ONCE_FLAG_INIT">, + ], + [ + OnceFlagType, + CallOnceFuncType, MtxTType, ThrdStartTType, ThrdTType, @@ -284,6 +293,14 @@ def StdC : StandardSpec<"stdc"> { EnumeratedNameValue<"thrd_nomem">, ], [ + FunctionSpec< + "call_once", + RetValSpec, + [ + ArgSpec, + ArgSpec, + ] + >, FunctionSpec< "mtx_init", RetValSpec, diff --git a/libc/src/threads/CMakeLists.txt b/libc/src/threads/CMakeLists.txt index 966e41ec9d55..276aa51cfbd5 100644 --- a/libc/src/threads/CMakeLists.txt +++ b/libc/src/threads/CMakeLists.txt @@ -2,6 +2,13 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) add_subdirectory(${LIBC_TARGET_OS}) endif() +add_entrypoint_object( + call_once + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.call_once +) + add_entrypoint_object( thrd_create ALIAS diff --git a/libc/src/threads/call_once.h b/libc/src/threads/call_once.h new file mode 100644 index 000000000000..f6602df68197 --- /dev/null +++ b/libc/src/threads/call_once.h @@ -0,0 +1,20 @@ +//===-- Implementation header for call_once 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_CALL_ONCE_H +#define LLVM_LIBC_SRC_THREADS_CALL_ONCE_H + +#include "include/threads.h" + +namespace __llvm_libc { + +void call_once(once_flag *flag, __call_once_func_t func); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_THREADS_CALL_ONCE_H diff --git a/libc/src/threads/linux/CMakeLists.txt b/libc/src/threads/linux/CMakeLists.txt index 63e956ec1500..08a04c604a36 100644 --- a/libc/src/threads/linux/CMakeLists.txt +++ b/libc/src/threads/linux/CMakeLists.txt @@ -8,6 +8,19 @@ add_gen_header( ${LIBC_TARGET_MACHINE}/thread_start_args.h.in ) +add_entrypoint_object( + call_once + SRCS + call_once.cpp + HDRS + ../call_once.h + DEPENDS + .threads_utils + libc.config.linux.linux_syscall_h + libc.include.sys_syscall + libc.include.threads +) + add_header_library( threads_utils HDRS diff --git a/libc/src/threads/linux/call_once.cpp b/libc/src/threads/linux/call_once.cpp new file mode 100644 index 000000000000..058f3700ef7e --- /dev/null +++ b/libc/src/threads/linux/call_once.cpp @@ -0,0 +1,58 @@ +//===-- Linux implementation of the call_once 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 functions. +#include "include/sys/syscall.h" // For syscall numbers. +#include "include/threads.h" // For call_once related type definition. +#include "src/__support/common.h" +#include "src/threads/linux/thread_utils.h" + +#include +#include +#include + +namespace __llvm_libc { + +static constexpr unsigned START = 0x11; +static constexpr unsigned WAITING = 0x22; +static constexpr unsigned FINISH = 0x33; + +void LLVM_LIBC_ENTRYPOINT(call_once)(once_flag *flag, __call_once_func_t func) { + FutexData *futex_word = reinterpret_cast(flag); + unsigned int not_called = ONCE_FLAG_INIT; + + // The C standard wording says: + // + // The completion of the function func synchronizes with all + // previous or subsequent calls to call_once with the same + // flag variable. + // + // What this means is that, the call_once call can return only after + // the called function |func| returns. So, we use futexes to synchronize + // calls with the same flag value. + if (::atomic_compare_exchange_strong(futex_word, ¬_called, START)) { + func(); + auto status = ::atomic_exchange(futex_word, FINISH); + if (status == WAITING) { + __llvm_libc::syscall(SYS_futex, futex_word, FUTEX_WAKE_PRIVATE, + INT_MAX, // Wake all waiters. + 0, 0, 0); + } + return; + } + + unsigned int status = START; + if (::atomic_compare_exchange_strong(futex_word, &status, WAITING) || + status == WAITING) { + __llvm_libc::syscall(SYS_futex, futex_word, FUTEX_WAIT_PRIVATE, + WAITING, // Block only if status is still |WAITING|. + 0, 0, 0); + } +} + +} // namespace __llvm_libc diff --git a/libc/test/src/threads/CMakeLists.txt b/libc/test/src/threads/CMakeLists.txt index 178323e92714..6511efe2c33b 100644 --- a/libc/test/src/threads/CMakeLists.txt +++ b/libc/test/src/threads/CMakeLists.txt @@ -1,5 +1,21 @@ add_libc_testsuite(libc_threads_unittests) +add_libc_unittest( + call_once_test + SUITE + libc_threads_unittests + SRCS + call_once_test.cpp + DEPENDS + libc.include.threads + libc.src.threads.call_once + libc.src.threads.mtx_init + libc.src.threads.mtx_lock + libc.src.threads.mtx_unlock + libc.src.threads.thrd_create + libc.src.threads.thrd_join +) + add_libc_unittest( thrd_test SUITE diff --git a/libc/test/src/threads/call_once_test.cpp b/libc/test/src/threads/call_once_test.cpp new file mode 100644 index 000000000000..bb5f14899d9d --- /dev/null +++ b/libc/test/src/threads/call_once_test.cpp @@ -0,0 +1,111 @@ +//===-- Unittests for call_once -------------------------------------------===// +// +// 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/call_once.h" +#include "src/threads/mtx_init.h" +#include "src/threads/mtx_lock.h" +#include "src/threads/mtx_unlock.h" +#include "src/threads/thrd_create.h" +#include "src/threads/thrd_join.h" +#include "utils/UnitTest/Test.h" + +#include + +static constexpr unsigned int num_threads = 5; +static atomic_uint thread_count; + +static unsigned int call_count; +static void call_once_func() { ++call_count; } + +static int func(void *) { + static once_flag flag = ONCE_FLAG_INIT; + __llvm_libc::call_once(&flag, call_once_func); + + ++thread_count; // This is a an atomic update. + + return 0; +} + +TEST(CallOnceTest, CallFrom5Threads) { + // Ensure the call count and thread count are 0 to begin with. + call_count = 0; + thread_count = 0; + + thrd_t threads[num_threads]; + for (unsigned int i = 0; i < num_threads; ++i) { + ASSERT_EQ(__llvm_libc::thrd_create(threads + i, func, nullptr), + static_cast(thrd_success)); + } + + for (unsigned int i = 0; i < num_threads; ++i) { + int retval; + ASSERT_EQ(__llvm_libc::thrd_join(threads + i, &retval), + static_cast(thrd_success)); + ASSERT_EQ(retval, 0); + } + + EXPECT_EQ(static_cast(thread_count), 5U); + EXPECT_EQ(call_count, 1U); +} + +static mtx_t once_func_blocker; +static void blocking_once_func() { + __llvm_libc::mtx_lock(&once_func_blocker); + __llvm_libc::mtx_unlock(&once_func_blocker); +} + +static atomic_uint start_count; +static atomic_uint done_count; +static int once_func_caller(void *) { + static once_flag flag; + ++start_count; + __llvm_libc::call_once(&flag, blocking_once_func); + ++done_count; + return 0; +} + +// Test the synchronization aspect of the call_once function. +// This is not a fool proof test, but something which might be +// useful when we add a flakiness detection scheme to UnitTest. +TEST(CallOnceTest, TestSynchronization) { + start_count = 0; + done_count = 0; + + ASSERT_EQ(__llvm_libc::mtx_init(&once_func_blocker, mtx_plain), + static_cast(thrd_success)); + // Lock the blocking mutex so that the once func blocks. + ASSERT_EQ(__llvm_libc::mtx_lock(&once_func_blocker), + static_cast(thrd_success)); + + thrd_t t1, t2; + ASSERT_EQ(__llvm_libc::thrd_create(&t1, once_func_caller, nullptr), + static_cast(thrd_success)); + ASSERT_EQ(__llvm_libc::thrd_create(&t2, once_func_caller, nullptr), + static_cast(thrd_success)); + + while (start_count != 2) + ; // Spin until both threads start. + + // Since the once func is blocked, the threads should not be done yet. + EXPECT_EQ(static_cast(done_count), 0U); + + // Unlock the blocking mutex so that the once func blocks. + ASSERT_EQ(__llvm_libc::mtx_unlock(&once_func_blocker), + static_cast(thrd_success)); + + int retval; + ASSERT_EQ(__llvm_libc::thrd_join(&t1, &retval), + static_cast(thrd_success)); + ASSERT_EQ(retval, 0); + ASSERT_EQ(__llvm_libc::thrd_join(&t2, &retval), + static_cast(thrd_success)); + ASSERT_EQ(retval, 0); + + ASSERT_EQ(static_cast(done_count), 2U); +}