21 changes: 21 additions & 0 deletions libc/src/pthread/pthread_spin_init.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===-- Implementation header for pthread_spin_init 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_PTHREAD_PTHREAD_SPIN_INIT_H
#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_INIT_H

#include "src/__support/macros/config.h"
#include <pthread.h>

namespace LIBC_NAMESPACE_DECL {

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_INIT_H
47 changes: 47 additions & 0 deletions libc/src/pthread/pthread_spin_lock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//===-- Implementation of pthread_spin_lock 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 "src/pthread/pthread_spin_lock.h"
#include "hdr/errno_macros.h"
#include "src/__support/common.h"
#include "src/__support/threads/identifier.h"
#include "src/__support/threads/spin_lock.h"

namespace LIBC_NAMESPACE_DECL {

static_assert(sizeof(pthread_spinlock_t::__lockword) == sizeof(SpinLock) &&
alignof(decltype(pthread_spinlock_t::__lockword)) ==
alignof(SpinLock),
"pthread_spinlock_t::__lockword and SpinLock must be of the same "
"size and alignment");

LLVM_LIBC_FUNCTION(int, pthread_spin_lock, (pthread_spinlock_t * lock)) {
// If an implementation detects that the value specified by the lock argument
// to pthread_spin_lock() or pthread_spin_trylock() does not refer to an
// initialized spin lock object, it is recommended that the function should
// fail and report an [EINVAL] error.
if (!lock)
return EINVAL;
auto spin_lock = reinterpret_cast<SpinLock *>(&lock->__lockword);
if (spin_lock->is_invalid())
return EINVAL;

pid_t self_tid = internal::gettid();
// If an implementation detects that the value specified by the lock argument
// to pthread_spin_lock() refers to a spin lock object for which the calling
// thread already holds the lock, it is recommended that the function should
// fail and report an [EDEADLK] error.
if (lock->__owner == self_tid)
return EDEADLK;

spin_lock->lock();
lock->__owner = self_tid;
return 0;
}

} // namespace LIBC_NAMESPACE_DECL
21 changes: 21 additions & 0 deletions libc/src/pthread/pthread_spin_lock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===-- Implementation header for pthread_spin_lock 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_PTHREAD_PTHREAD_SPIN_LOCK_H
#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_LOCK_H

#include "src/__support/macros/config.h"
#include <pthread.h>

namespace LIBC_NAMESPACE_DECL {

int pthread_spin_lock(pthread_spinlock_t *lock);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_LOCK_H
41 changes: 41 additions & 0 deletions libc/src/pthread/pthread_spin_trylock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===-- Implementation of pthread_spin_trylock 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 "src/pthread/pthread_spin_trylock.h"
#include "hdr/errno_macros.h"
#include "src/__support/common.h"
#include "src/__support/threads/identifier.h"
#include "src/__support/threads/spin_lock.h"

namespace LIBC_NAMESPACE_DECL {

static_assert(sizeof(pthread_spinlock_t::__lockword) == sizeof(SpinLock) &&
alignof(decltype(pthread_spinlock_t::__lockword)) ==
alignof(SpinLock),
"pthread_spinlock_t::__lockword and SpinLock must be of the same "
"size and alignment");

LLVM_LIBC_FUNCTION(int, pthread_spin_trylock, (pthread_spinlock_t * lock)) {
// If an implementation detects that the value specified by the lock argument
// to pthread_spin_lock() or pthread_spin_trylock() does not refer to an
// initialized spin lock object, it is recommended that the function should
// fail and report an [EINVAL] error.
if (!lock)
return EINVAL;
auto spin_lock = reinterpret_cast<SpinLock *>(&lock->__lockword);
if (!spin_lock || spin_lock->is_invalid())
return EINVAL;
// Try to acquire the lock without blocking.
if (!spin_lock->try_lock())
return EBUSY;
// We have acquired the lock. Update the owner field.
lock->__owner = internal::gettid();
return 0;
}

} // namespace LIBC_NAMESPACE_DECL
22 changes: 22 additions & 0 deletions libc/src/pthread/pthread_spin_trylock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//===-- Implementation header for pthread_spin_trylock 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_PTHREAD_PTHREAD_SPIN_TRYLOCK_H
#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_TRYLOCK_H

#include "src/__support/macros/config.h"
#include <pthread.h>

namespace LIBC_NAMESPACE_DECL {

int pthread_spin_trylock(pthread_spinlock_t *lock);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_TRYLOCK_H
44 changes: 44 additions & 0 deletions libc/src/pthread/pthread_spin_unlock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//===-- Implementation of pthread_spin_unlock 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 "src/pthread/pthread_spin_unlock.h"
#include "hdr/errno_macros.h"
#include "src/__support/common.h"
#include "src/__support/threads/identifier.h"
#include "src/__support/threads/spin_lock.h"

namespace LIBC_NAMESPACE_DECL {

static_assert(sizeof(pthread_spinlock_t::__lockword) == sizeof(SpinLock) &&
alignof(decltype(pthread_spinlock_t::__lockword)) ==
alignof(SpinLock),
"pthread_spinlock_t::__lockword and SpinLock must be of the same "
"size and alignment");

LLVM_LIBC_FUNCTION(int, pthread_spin_unlock, (pthread_spinlock_t * lock)) {
// If an implementation detects that the value specified by the lock argument
// to pthread_spin_lock() or pthread_spin_trylock() does not refer to an
// initialized spin lock object, it is recommended that the function should
// fail and report an [EINVAL] error.
if (!lock)
return EINVAL;
auto spin_lock = reinterpret_cast<SpinLock *>(&lock->__lockword);
if (!spin_lock || spin_lock->is_invalid())
return EINVAL;
// If an implementation detects that the value specified by the lock argument
// to pthread_spin_unlock() refers to a spin lock object for which the current
// thread does not hold the lock, it is recommended that the function should
// fail and report an [EPERM] error.
if (lock->__owner != internal::gettid())
return EPERM;
// Release the lock.
lock->__owner = 0;
spin_lock->unlock();
return 0;
}
} // namespace LIBC_NAMESPACE_DECL
21 changes: 21 additions & 0 deletions libc/src/pthread/pthread_spin_unlock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===-- Implementation header for pthread_spin_unlock 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_PTHREAD_PTHREAD_SPIN_UNLOCK_H
#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_UNLOCK_H

#include "src/__support/macros/config.h"
#include <pthread.h>

namespace LIBC_NAMESPACE_DECL {

int pthread_spin_unlock(pthread_spinlock_t *lock);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_UNLOCK_H
22 changes: 20 additions & 2 deletions libc/test/integration/src/pthread/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ add_integration_test(
SRCS
pthread_rwlock_test.cpp
DEPENDS
libc.hdr.time_macros
libc.hdr.errno_macros
libc.include.pthread
libc.include.time
libc.include.errno
libc.src.pthread.pthread_rwlock_destroy
libc.src.pthread.pthread_rwlock_init
libc.src.pthread.pthread_rwlock_rdlock
Expand Down Expand Up @@ -59,6 +59,24 @@ add_integration_test(
libc.src.__support.threads.sleep
)

add_integration_test(
pthread_spinlock_test
SUITE
libc-pthread-integration-tests
SRCS
pthread_spinlock_test.cpp
DEPENDS
libc.hdr.errno_macros
libc.include.pthread
libc.src.pthread.pthread_spin_init
libc.src.pthread.pthread_spin_destroy
libc.src.pthread.pthread_spin_lock
libc.src.pthread.pthread_spin_trylock
libc.src.pthread.pthread_spin_unlock
libc.src.pthread.pthread_create
libc.src.pthread.pthread_join
)

add_integration_test(
pthread_test
SUITE
Expand Down
4 changes: 2 additions & 2 deletions libc/test/integration/src/pthread/pthread_rwlock_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//
//===----------------------------------------------------------------------===//

#include "hdr/errno_macros.h"
#include "hdr/time_macros.h"
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/new.h"
#include "src/__support/OSUtil/syscall.h"
Expand Down Expand Up @@ -40,9 +42,7 @@
#include "src/time/clock_gettime.h"
#include "src/unistd/fork.h"
#include "test/IntegrationTest/test.h"
#include <errno.h>
#include <pthread.h>
#include <time.h>

namespace LIBC_NAMESPACE_DECL {
namespace rwlock {
Expand Down
145 changes: 145 additions & 0 deletions libc/test/integration/src/pthread/pthread_spinlock_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//===-- Tests for pthread_spinlock ----------------------------------------===//
//
// 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 "hdr/errno_macros.h"
#include "src/pthread/pthread_create.h"
#include "src/pthread/pthread_join.h"
#include "src/pthread/pthread_spin_destroy.h"
#include "src/pthread/pthread_spin_init.h"
#include "src/pthread/pthread_spin_lock.h"
#include "src/pthread/pthread_spin_trylock.h"
#include "src/pthread/pthread_spin_unlock.h"
#include "test/IntegrationTest/test.h"
#include <pthread.h>

namespace {
void smoke_test() {
pthread_spinlock_t lock;
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0);
}

void trylock_test() {
pthread_spinlock_t lock;
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(&lock), EBUSY);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0);
}

void destroy_held_lock_test() {
pthread_spinlock_t lock;
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), EBUSY);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0);
}

void use_after_destroy_test() {
pthread_spinlock_t lock;
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), EINVAL);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), EINVAL);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(&lock), EINVAL);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), EINVAL);
}

void unlock_without_holding_test() {
pthread_spinlock_t lock;
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), EPERM);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0);
}

void deadlock_test() {
pthread_spinlock_t lock;
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), EDEADLK);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0);
}

void null_lock_test() {
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(nullptr, 0), EINVAL);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(nullptr), EINVAL);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(nullptr), EINVAL);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(nullptr), EINVAL);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(nullptr), EINVAL);
}

void pshared_attribute_test() {
pthread_spinlock_t lock;
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_SHARED),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0);

ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE),
0);
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0);

ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, -1), EINVAL);
}

void multi_thread_test() {
struct shared_data {
pthread_spinlock_t lock;
int count = 0;
} shared;
pthread_t thread[10];
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&shared.lock, 0), 0);
for (int i = 0; i < 10; ++i) {
ASSERT_EQ(
LIBC_NAMESPACE::pthread_create(
&thread[i], nullptr,
[](void *arg) -> void * {
auto *data = static_cast<shared_data *>(arg);
for (int j = 0; j < 1000; ++j) {
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&data->lock), 0);
data->count += j;
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&data->lock), 0);
}
return nullptr;
},
&shared),
0);
}
for (int i = 0; i < 10; ++i) {
ASSERT_EQ(LIBC_NAMESPACE::pthread_join(thread[i], nullptr), 0);
}
ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&shared.lock), 0);
ASSERT_EQ(shared.count, 1000 * 999 * 5);
}

} // namespace

TEST_MAIN() {
smoke_test();
trylock_test();
destroy_held_lock_test();
use_after_destroy_test();
unlock_without_holding_test();
deadlock_test();
multi_thread_test();
null_lock_test();
pshared_attribute_test();
return 0;
}