[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, &not_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