mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-24 06:10:12 +00:00
[libc] Add implementation of call_once from threads.h.
Reviewers: abrachet, maskray Differential Revision: https://reviews.llvm.org/D79828
This commit is contained in:
parent
17ed6dcb0c
commit
0baf0e8cfc
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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>,
|
||||
|
@ -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
|
||||
|
20
libc/src/threads/call_once.h
Normal file
20
libc/src/threads/call_once.h
Normal 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
|
@ -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
|
||||
|
58
libc/src/threads/linux/call_once.cpp
Normal file
58
libc/src/threads/linux/call_once.cpp
Normal 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, ¬_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
|
@ -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
|
||||
|
111
libc/test/src/threads/call_once_test.cpp
Normal file
111
libc/test/src/threads/call_once_test.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user