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 @@ -297,6 +297,12 @@ ]; } +def OnceFlag : TypeDecl<"once_flag"> { + let Decl = [{ + typedef unsigned int once_flag; + }]; +} + def MtxT : TypeDecl<"mtx_t"> { let Decl = [{ typedef struct { @@ -310,8 +316,20 @@ 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, ]; @@ -328,6 +346,7 @@ ]; let Functions = [ + "call_once", "mtx_init", "mtx_lock", "mtx_unlock", diff --git a/libc/lib/CMakeLists.txt b/libc/lib/CMakeLists.txt --- a/libc/lib/CMakeLists.txt +++ b/libc/lib/CMakeLists.txt @@ -33,6 +33,7 @@ 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 --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -8,6 +8,11 @@ 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">; @@ -261,8 +266,12 @@ HeaderSpec Threads = HeaderSpec< "threads.h", - [], // Macros [ + Macro<"ONCE_FLAG_INIT">, + ], + [ + OnceFlagType, + CallOnceFuncType, MtxTType, ThrdStartTType, ThrdTType, @@ -278,6 +287,14 @@ 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 --- a/libc/src/threads/CMakeLists.txt +++ b/libc/src/threads/CMakeLists.txt @@ -2,6 +2,16 @@ add_subdirectory(${LIBC_TARGET_OS}) endif() +add_entrypoint_object( + call_once + SRCS + call_once.cpp + HDRS + call_once.h + DEPENDS + libc.include.threads +) + 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 --- /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/call_once.cpp b/libc/src/threads/call_once.cpp new file mode 100644 --- /dev/null +++ b/libc/src/threads/call_once.cpp @@ -0,0 +1,27 @@ +//===-- 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 "include/threads.h" // For call_once related type definition. +#include "src/__support/common.h" + +#include +#include + +namespace __llvm_libc { + +static constexpr unsigned int CALLED = 0xCA11ED; + +void LLVM_LIBC_ENTRYPOINT(call_once)(once_flag *flag, __call_once_func_t func) { + atomic_uint *atomic_flag = reinterpret_cast(flag); + unsigned int not_called = ONCE_FLAG_INIT; + + if (::atomic_compare_exchange_strong(atomic_flag, ¬_called, CALLED)) + func(); +} + +} // namespace __llvm_libc diff --git a/libc/test/src/threads/CMakeLists.txt b/libc/test/src/threads/CMakeLists.txt --- 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 --- /dev/null +++ b/libc/test/src/threads/call_once_test.cpp @@ -0,0 +1,52 @@ +//===-- 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/thrd_create.h" +#include "src/threads/thrd_join.h" +#include "utils/UnitTest/Test.h" + +#include + +static constexpr unsigned int num_threads = 5; + +static unsigned int call_count = 0; +static void call_once_func() { ++call_count; } + +static atomic_uint thread_count = 0; +static int func(void *) { + static once_flag flag = ONCE_FLAG_INIT; + __llvm_libc::call_once(&flag, call_once_func); + + ::atomic_fetch_add(&thread_count, 1); + + return 0; +} + +TEST(CallOnceTest, CallFrom5Threads) { + // Ensure the call count and thread count are 0 to begin with. + EXPECT_EQ(call_count, 0U); + EXPECT_EQ(static_cast(thread_count), 0U); + + 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); +}