[libc-commits] [libc] 0baf0e8 - [libc] Add implementation of call_once from threads.h.
Siva Chandra Reddy via libc-commits
libc-commits at lists.llvm.org
Thu May 28 23:46:30 PDT 2020
Author: Siva Chandra Reddy
Date: 2020-05-28T23:45:09-07:00
New Revision: 0baf0e8cfc1845ef92d397c1ae43793bf9e6aaad
URL: https://github.com/llvm/llvm-project/commit/0baf0e8cfc1845ef92d397c1ae43793bf9e6aaad
DIFF: https://github.com/llvm/llvm-project/commit/0baf0e8cfc1845ef92d397c1ae43793bf9e6aaad.diff
LOG: [libc] Add implementation of call_once from threads.h.
Reviewers: abrachet, maskray
Differential Revision: https://reviews.llvm.org/D79828
Added:
libc/src/threads/call_once.h
libc/src/threads/linux/call_once.cpp
libc/test/src/threads/call_once_test.cpp
Modified:
libc/config/linux/api.td
libc/lib/CMakeLists.txt
libc/spec/stdc.td
libc/src/threads/CMakeLists.txt
libc/src/threads/linux/CMakeLists.txt
libc/test/src/threads/CMakeLists.txt
Removed:
################################################################################
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<CharType>;
ConstType ConstCharRestrictedPtr = ConstType<CharRestrictedPtr>;
+ NamedType OnceFlagType = NamedType<"once_flag">;
+ PtrType OnceFlagTypePtr = PtrType<OnceFlagType>;
+ // 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<MtxTType>;
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<VoidType>,
+ [
+ ArgSpec<OnceFlagTypePtr>,
+ ArgSpec<CallOnceFuncType>,
+ ]
+ >,
FunctionSpec<
"mtx_init",
RetValSpec<IntType>,
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 <limits.h>
+#include <linux/futex.h>
+#include <stdatomic.h>
+
+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<FutexData *>(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 <stdatomic.h>
+
+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<int>(thrd_success));
+ }
+
+ for (unsigned int i = 0; i < num_threads; ++i) {
+ int retval;
+ ASSERT_EQ(__llvm_libc::thrd_join(threads + i, &retval),
+ static_cast<int>(thrd_success));
+ ASSERT_EQ(retval, 0);
+ }
+
+ EXPECT_EQ(static_cast<unsigned int>(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<int>(thrd_success));
+ // Lock the blocking mutex so that the once func blocks.
+ ASSERT_EQ(__llvm_libc::mtx_lock(&once_func_blocker),
+ static_cast<int>(thrd_success));
+
+ thrd_t t1, t2;
+ ASSERT_EQ(__llvm_libc::thrd_create(&t1, once_func_caller, nullptr),
+ static_cast<int>(thrd_success));
+ ASSERT_EQ(__llvm_libc::thrd_create(&t2, once_func_caller, nullptr),
+ static_cast<int>(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<unsigned int>(done_count), 0U);
+
+ // Unlock the blocking mutex so that the once func blocks.
+ ASSERT_EQ(__llvm_libc::mtx_unlock(&once_func_blocker),
+ static_cast<int>(thrd_success));
+
+ int retval;
+ ASSERT_EQ(__llvm_libc::thrd_join(&t1, &retval),
+ static_cast<int>(thrd_success));
+ ASSERT_EQ(retval, 0);
+ ASSERT_EQ(__llvm_libc::thrd_join(&t2, &retval),
+ static_cast<int>(thrd_success));
+ ASSERT_EQ(retval, 0);
+
+ ASSERT_EQ(static_cast<unsigned int>(done_count), 2U);
+}
More information about the libc-commits
mailing list