#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include "rthread.h"
#include "synch.h"
#define UNLOCKED 0
#define MAXREADER 0x7ffffffe
#define WRITER 0x7fffffff
#define WAITING 0x80000000
#define COUNT(v) ((v) & WRITER)
#define SPIN_COUNT 128
#if defined(__i386__) || defined(__amd64__)
#define SPIN_WAIT() asm volatile("pause": : : "memory")
#else
#define SPIN_WAIT() do { } while (0)
#endif
static _atomic_lock_t rwlock_init_lock = _SPINLOCK_UNLOCKED;
int
pthread_rwlock_init(pthread_rwlock_t *lockp,
const pthread_rwlockattr_t *attrp __unused)
{
pthread_rwlock_t rwlock;
rwlock = calloc(1, sizeof(*rwlock));
if (!rwlock)
return (errno);
*lockp = rwlock;
return (0);
}
DEF_STD(pthread_rwlock_init);
int
pthread_rwlock_destroy(pthread_rwlock_t *lockp)
{
pthread_rwlock_t rwlock;
rwlock = *lockp;
if (rwlock) {
if (rwlock->value != UNLOCKED) {
#define MSG "pthread_rwlock_destroy on rwlock with waiters!\n"
write(2, MSG, sizeof(MSG) - 1);
#undef MSG
return (EBUSY);
}
free((void *)rwlock);
*lockp = NULL;
}
return (0);
}
static int
_rthread_rwlock_ensure_init(pthread_rwlock_t *rwlockp)
{
int ret = 0;
if (*rwlockp == NULL) {
_spinlock(&rwlock_init_lock);
if (*rwlockp == NULL)
ret = pthread_rwlock_init(rwlockp, NULL);
_spinunlock(&rwlock_init_lock);
}
return (ret);
}
static int
_rthread_rwlock_tryrdlock(pthread_rwlock_t rwlock)
{
unsigned int val;
do {
val = rwlock->value;
if (COUNT(val) == WRITER)
return (EBUSY);
if (COUNT(val) == MAXREADER)
return (EAGAIN);
} while (atomic_cas_uint(&rwlock->value, val, val + 1) != val);
membar_enter_after_atomic();
return (0);
}
static int
_rthread_rwlock_timedrdlock(pthread_rwlock_t *rwlockp, int trywait,
const struct timespec *abs, int timed)
{
pthread_t self = pthread_self();
pthread_rwlock_t rwlock;
unsigned int val, new;
int i, error;
if ((error = _rthread_rwlock_ensure_init(rwlockp)))
return (error);
rwlock = *rwlockp;
_rthread_debug(5, "%p: rwlock_%srdlock %p (%u)\n", self,
(timed ? "timed" : (trywait ? "try" : "")), (void *)rwlock,
rwlock->value);
error = _rthread_rwlock_tryrdlock(rwlock);
if (error != EBUSY || trywait)
return (error);
for (i = 0; i < SPIN_COUNT; i++) {
val = rwlock->value;
if (val == UNLOCKED || (val & WAITING))
break;
SPIN_WAIT();
}
while ((error = _rthread_rwlock_tryrdlock(rwlock)) == EBUSY) {
val = rwlock->value;
if (val == UNLOCKED || (COUNT(val)) != WRITER)
continue;
new = val | WAITING;
if (atomic_cas_uint(&rwlock->value, val, new) == val) {
error = _twait(&rwlock->value, new, CLOCK_REALTIME,
abs);
}
if (error == ETIMEDOUT)
break;
}
return (error);
}
int
pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlockp)
{
return (_rthread_rwlock_timedrdlock(rwlockp, 1, NULL, 0));
}
int
pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlockp,
const struct timespec *abs)
{
return (_rthread_rwlock_timedrdlock(rwlockp, 0, abs, 1));
}
int
pthread_rwlock_rdlock(pthread_rwlock_t *rwlockp)
{
return (_rthread_rwlock_timedrdlock(rwlockp, 0, NULL, 0));
}
static int
_rthread_rwlock_tryrwlock(pthread_rwlock_t rwlock)
{
if (atomic_cas_uint(&rwlock->value, UNLOCKED, WRITER) != UNLOCKED)
return (EBUSY);
membar_enter_after_atomic();
return (0);
}
static int
_rthread_rwlock_timedwrlock(pthread_rwlock_t *rwlockp, int trywait,
const struct timespec *abs, int timed)
{
pthread_t self = pthread_self();
pthread_rwlock_t rwlock;
unsigned int val, new;
int i, error;
if ((error = _rthread_rwlock_ensure_init(rwlockp)))
return (error);
rwlock = *rwlockp;
_rthread_debug(5, "%p: rwlock_%swrlock %p (%u)\n", self,
(timed ? "timed" : (trywait ? "try" : "")), (void *)rwlock,
rwlock->value);
error = _rthread_rwlock_tryrwlock(rwlock);
if (error != EBUSY || trywait)
return (error);
for (i = 0; i < SPIN_COUNT; i++) {
val = rwlock->value;
if (val == UNLOCKED || (val & WAITING))
break;
SPIN_WAIT();
}
while ((error = _rthread_rwlock_tryrwlock(rwlock)) == EBUSY) {
val = rwlock->value;
if (val == UNLOCKED)
continue;
new = val | WAITING;
if (atomic_cas_uint(&rwlock->value, val, new) == val) {
error = _twait(&rwlock->value, new, CLOCK_REALTIME,
abs);
}
if (error == ETIMEDOUT)
break;
}
return (error);
}
int
pthread_rwlock_trywrlock(pthread_rwlock_t *rwlockp)
{
return (_rthread_rwlock_timedwrlock(rwlockp, 1, NULL, 0));
}
int
pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlockp,
const struct timespec *abs)
{
return (_rthread_rwlock_timedwrlock(rwlockp, 0, abs, 1));
}
int
pthread_rwlock_wrlock(pthread_rwlock_t *rwlockp)
{
return (_rthread_rwlock_timedwrlock(rwlockp, 0, NULL, 0));
}
int
pthread_rwlock_unlock(pthread_rwlock_t *rwlockp)
{
pthread_t self = pthread_self();
pthread_rwlock_t rwlock;
unsigned int val, new;
rwlock = *rwlockp;
_rthread_debug(5, "%p: rwlock_unlock %p\n", self, (void *)rwlock);
membar_exit_before_atomic();
do {
val = rwlock->value;
if (COUNT(val) == WRITER || COUNT(val) == 1)
new = UNLOCKED;
else
new = val - 1;
} while (atomic_cas_uint(&rwlock->value, val, new) != val);
if (new == UNLOCKED && (val & WAITING))
_wake(&rwlock->value, INT_MAX);
return (0);
}