[libc] Add implementation of call_once from threads.h.

Reviewers: abrachet, maskray

Differential Revision: https://reviews.llvm.org/D79828
This commit is contained in:
Siva Chandra Reddy 2020-05-12 16:01:28 -07:00
parent 17ed6dcb0c
commit 0baf0e8cfc
9 changed files with 263 additions and 1 deletions

View File

@ -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",

View File

@ -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

View File

@ -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>,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}