andre@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ andre@0: /* This Source Code Form is subject to the terms of the Mozilla Public andre@0: * License, v. 2.0. If a copy of the MPL was not distributed with this andre@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ andre@0: andre@0: /* andre@0: ** PR Atomic operations andre@0: */ andre@0: andre@0: andre@0: #include "pratom.h" andre@0: #include "primpl.h" andre@0: andre@0: #include andre@0: andre@0: /* andre@0: * The following is a fallback implementation that emulates andre@0: * atomic operations for platforms without atomic operations. andre@0: * If a platform has atomic operations, it should define the andre@0: * macro _PR_HAVE_ATOMIC_OPS, and the following will not be andre@0: * compiled in. andre@0: */ andre@0: andre@0: #if !defined(_PR_HAVE_ATOMIC_OPS) andre@0: andre@0: #if defined(_PR_PTHREADS) && !defined(_PR_DCETHREADS) andre@0: /* andre@0: * PR_AtomicDecrement() is used in NSPR's thread-specific data andre@0: * destructor. Because thread-specific data destructors may be andre@0: * invoked after a PR_Cleanup() call, we need an implementation andre@0: * of the atomic routines that doesn't need NSPR to be initialized. andre@0: */ andre@0: andre@0: /* andre@0: * We use a set of locks for all the emulated atomic operations. andre@0: * By hashing on the address of the integer to be locked the andre@0: * contention between multiple threads should be lessened. andre@0: * andre@0: * The number of atomic locks can be set by the environment variable andre@0: * NSPR_ATOMIC_HASH_LOCKS andre@0: */ andre@0: andre@0: /* andre@0: * lock counts should be a power of 2 andre@0: */ andre@0: #define DEFAULT_ATOMIC_LOCKS 16 /* should be in sync with the number of initializers andre@0: below */ andre@0: #define MAX_ATOMIC_LOCKS (4 * 1024) andre@0: andre@0: static pthread_mutex_t static_atomic_locks[DEFAULT_ATOMIC_LOCKS] = { andre@0: PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, andre@0: PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, andre@0: PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, andre@0: PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, andre@0: PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, andre@0: PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, andre@0: PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, andre@0: PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }; andre@0: andre@0: #ifdef DEBUG andre@0: static PRInt32 static_hash_lock_counts[DEFAULT_ATOMIC_LOCKS]; andre@0: static PRInt32 *hash_lock_counts = static_hash_lock_counts; andre@0: #endif andre@0: andre@0: static PRUint32 num_atomic_locks = DEFAULT_ATOMIC_LOCKS; andre@0: static pthread_mutex_t *atomic_locks = static_atomic_locks; andre@0: static PRUint32 atomic_hash_mask = DEFAULT_ATOMIC_LOCKS - 1; andre@0: andre@0: #define _PR_HASH_FOR_LOCK(ptr) \ andre@0: ((PRUint32) (((PRUptrdiff) (ptr) >> 2) ^ \ andre@0: ((PRUptrdiff) (ptr) >> 8)) & \ andre@0: atomic_hash_mask) andre@0: andre@0: void _PR_MD_INIT_ATOMIC() andre@0: { andre@0: char *eval; andre@0: int index; andre@0: andre@0: andre@0: PR_ASSERT(PR_FloorLog2(MAX_ATOMIC_LOCKS) == andre@0: PR_CeilingLog2(MAX_ATOMIC_LOCKS)); andre@0: andre@0: PR_ASSERT(PR_FloorLog2(DEFAULT_ATOMIC_LOCKS) == andre@0: PR_CeilingLog2(DEFAULT_ATOMIC_LOCKS)); andre@0: andre@0: if (((eval = getenv("NSPR_ATOMIC_HASH_LOCKS")) != NULL) && andre@0: ((num_atomic_locks = atoi(eval)) != DEFAULT_ATOMIC_LOCKS)) { andre@0: andre@0: if (num_atomic_locks > MAX_ATOMIC_LOCKS) andre@0: num_atomic_locks = MAX_ATOMIC_LOCKS; andre@0: else if (num_atomic_locks < 1) andre@0: num_atomic_locks = 1; andre@0: else { andre@0: num_atomic_locks = PR_FloorLog2(num_atomic_locks); andre@0: num_atomic_locks = 1L << num_atomic_locks; andre@0: } andre@0: atomic_locks = (pthread_mutex_t *) PR_Malloc(sizeof(pthread_mutex_t) * andre@0: num_atomic_locks); andre@0: if (atomic_locks) { andre@0: for (index = 0; index < num_atomic_locks; index++) { andre@0: if (pthread_mutex_init(&atomic_locks[index], NULL)) { andre@0: PR_DELETE(atomic_locks); andre@0: atomic_locks = NULL; andre@0: break; andre@0: } andre@0: } andre@0: } andre@0: #ifdef DEBUG andre@0: if (atomic_locks) { andre@0: hash_lock_counts = PR_CALLOC(num_atomic_locks * sizeof(PRInt32)); andre@0: if (hash_lock_counts == NULL) { andre@0: PR_DELETE(atomic_locks); andre@0: atomic_locks = NULL; andre@0: } andre@0: } andre@0: #endif andre@0: if (atomic_locks == NULL) { andre@0: /* andre@0: * Use statically allocated locks andre@0: */ andre@0: atomic_locks = static_atomic_locks; andre@0: num_atomic_locks = DEFAULT_ATOMIC_LOCKS; andre@0: #ifdef DEBUG andre@0: hash_lock_counts = static_hash_lock_counts; andre@0: #endif andre@0: } andre@0: atomic_hash_mask = num_atomic_locks - 1; andre@0: } andre@0: PR_ASSERT(PR_FloorLog2(num_atomic_locks) == andre@0: PR_CeilingLog2(num_atomic_locks)); andre@0: } andre@0: andre@0: PRInt32 andre@0: _PR_MD_ATOMIC_INCREMENT(PRInt32 *val) andre@0: { andre@0: PRInt32 rv; andre@0: PRInt32 idx = _PR_HASH_FOR_LOCK(val); andre@0: andre@0: pthread_mutex_lock(&atomic_locks[idx]); andre@0: rv = ++(*val); andre@0: #ifdef DEBUG andre@0: hash_lock_counts[idx]++; andre@0: #endif andre@0: pthread_mutex_unlock(&atomic_locks[idx]); andre@0: return rv; andre@0: } andre@0: andre@0: PRInt32 andre@0: _PR_MD_ATOMIC_ADD(PRInt32 *ptr, PRInt32 val) andre@0: { andre@0: PRInt32 rv; andre@0: PRInt32 idx = _PR_HASH_FOR_LOCK(ptr); andre@0: andre@0: pthread_mutex_lock(&atomic_locks[idx]); andre@0: rv = ((*ptr) += val); andre@0: #ifdef DEBUG andre@0: hash_lock_counts[idx]++; andre@0: #endif andre@0: pthread_mutex_unlock(&atomic_locks[idx]); andre@0: return rv; andre@0: } andre@0: andre@0: PRInt32 andre@0: _PR_MD_ATOMIC_DECREMENT(PRInt32 *val) andre@0: { andre@0: PRInt32 rv; andre@0: PRInt32 idx = _PR_HASH_FOR_LOCK(val); andre@0: andre@0: pthread_mutex_lock(&atomic_locks[idx]); andre@0: rv = --(*val); andre@0: #ifdef DEBUG andre@0: hash_lock_counts[idx]++; andre@0: #endif andre@0: pthread_mutex_unlock(&atomic_locks[idx]); andre@0: return rv; andre@0: } andre@0: andre@0: PRInt32 andre@0: _PR_MD_ATOMIC_SET(PRInt32 *val, PRInt32 newval) andre@0: { andre@0: PRInt32 rv; andre@0: PRInt32 idx = _PR_HASH_FOR_LOCK(val); andre@0: andre@0: pthread_mutex_lock(&atomic_locks[idx]); andre@0: rv = *val; andre@0: *val = newval; andre@0: #ifdef DEBUG andre@0: hash_lock_counts[idx]++; andre@0: #endif andre@0: pthread_mutex_unlock(&atomic_locks[idx]); andre@0: return rv; andre@0: } andre@0: #else /* _PR_PTHREADS && !_PR_DCETHREADS */ andre@0: /* andre@0: * We use a single lock for all the emulated atomic operations. andre@0: * The lock contention should be acceptable. andre@0: */ andre@0: static PRLock *atomic_lock = NULL; andre@0: void _PR_MD_INIT_ATOMIC(void) andre@0: { andre@0: if (atomic_lock == NULL) { andre@0: atomic_lock = PR_NewLock(); andre@0: } andre@0: } andre@0: andre@0: PRInt32 andre@0: _PR_MD_ATOMIC_INCREMENT(PRInt32 *val) andre@0: { andre@0: PRInt32 rv; andre@0: andre@0: if (!_pr_initialized) { andre@0: _PR_ImplicitInitialization(); andre@0: } andre@0: PR_Lock(atomic_lock); andre@0: rv = ++(*val); andre@0: PR_Unlock(atomic_lock); andre@0: return rv; andre@0: } andre@0: andre@0: PRInt32 andre@0: _PR_MD_ATOMIC_ADD(PRInt32 *ptr, PRInt32 val) andre@0: { andre@0: PRInt32 rv; andre@0: andre@0: if (!_pr_initialized) { andre@0: _PR_ImplicitInitialization(); andre@0: } andre@0: PR_Lock(atomic_lock); andre@0: rv = ((*ptr) += val); andre@0: PR_Unlock(atomic_lock); andre@0: return rv; andre@0: } andre@0: andre@0: PRInt32 andre@0: _PR_MD_ATOMIC_DECREMENT(PRInt32 *val) andre@0: { andre@0: PRInt32 rv; andre@0: andre@0: if (!_pr_initialized) { andre@0: _PR_ImplicitInitialization(); andre@0: } andre@0: PR_Lock(atomic_lock); andre@0: rv = --(*val); andre@0: PR_Unlock(atomic_lock); andre@0: return rv; andre@0: } andre@0: andre@0: PRInt32 andre@0: _PR_MD_ATOMIC_SET(PRInt32 *val, PRInt32 newval) andre@0: { andre@0: PRInt32 rv; andre@0: andre@0: if (!_pr_initialized) { andre@0: _PR_ImplicitInitialization(); andre@0: } andre@0: PR_Lock(atomic_lock); andre@0: rv = *val; andre@0: *val = newval; andre@0: PR_Unlock(atomic_lock); andre@0: return rv; andre@0: } andre@0: #endif /* _PR_PTHREADS && !_PR_DCETHREADS */ andre@0: andre@0: #endif /* !_PR_HAVE_ATOMIC_OPS */ andre@0: andre@0: void _PR_InitAtomic(void) andre@0: { andre@0: _PR_MD_INIT_ATOMIC(); andre@0: } andre@0: andre@0: PR_IMPLEMENT(PRInt32) andre@0: PR_AtomicIncrement(PRInt32 *val) andre@0: { andre@0: return _PR_MD_ATOMIC_INCREMENT(val); andre@0: } andre@0: andre@0: PR_IMPLEMENT(PRInt32) andre@0: PR_AtomicDecrement(PRInt32 *val) andre@0: { andre@0: return _PR_MD_ATOMIC_DECREMENT(val); andre@0: } andre@0: andre@0: PR_IMPLEMENT(PRInt32) andre@0: PR_AtomicSet(PRInt32 *val, PRInt32 newval) andre@0: { andre@0: return _PR_MD_ATOMIC_SET(val, newval); andre@0: } andre@0: andre@0: PR_IMPLEMENT(PRInt32) andre@0: PR_AtomicAdd(PRInt32 *ptr, PRInt32 val) andre@0: { andre@0: return _PR_MD_ATOMIC_ADD(ptr, val); andre@0: } andre@0: /* andre@0: * For platforms, which don't support the CAS (compare-and-swap) instruction andre@0: * (or an equivalent), the stack operations are implemented by use of PRLock andre@0: */ andre@0: andre@0: PR_IMPLEMENT(PRStack *) andre@0: PR_CreateStack(const char *stack_name) andre@0: { andre@0: PRStack *stack; andre@0: andre@0: if (!_pr_initialized) { andre@0: _PR_ImplicitInitialization(); andre@0: } andre@0: andre@0: if ((stack = PR_NEW(PRStack)) == NULL) { andre@0: return NULL; andre@0: } andre@0: if (stack_name) { andre@0: stack->prstk_name = (char *) PR_Malloc(strlen(stack_name) + 1); andre@0: if (stack->prstk_name == NULL) { andre@0: PR_DELETE(stack); andre@0: return NULL; andre@0: } andre@0: strcpy(stack->prstk_name, stack_name); andre@0: } else andre@0: stack->prstk_name = NULL; andre@0: andre@0: #ifndef _PR_HAVE_ATOMIC_CAS andre@0: stack->prstk_lock = PR_NewLock(); andre@0: if (stack->prstk_lock == NULL) { andre@0: PR_Free(stack->prstk_name); andre@0: PR_DELETE(stack); andre@0: return NULL; andre@0: } andre@0: #endif /* !_PR_HAVE_ATOMIC_CAS */ andre@0: andre@0: stack->prstk_head.prstk_elem_next = NULL; andre@0: andre@0: return stack; andre@0: } andre@0: andre@0: PR_IMPLEMENT(PRStatus) andre@0: PR_DestroyStack(PRStack *stack) andre@0: { andre@0: if (stack->prstk_head.prstk_elem_next != NULL) { andre@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); andre@0: return PR_FAILURE; andre@0: } andre@0: andre@0: if (stack->prstk_name) andre@0: PR_Free(stack->prstk_name); andre@0: #ifndef _PR_HAVE_ATOMIC_CAS andre@0: PR_DestroyLock(stack->prstk_lock); andre@0: #endif /* !_PR_HAVE_ATOMIC_CAS */ andre@0: PR_DELETE(stack); andre@0: andre@0: return PR_SUCCESS; andre@0: } andre@0: andre@0: #ifndef _PR_HAVE_ATOMIC_CAS andre@0: andre@0: PR_IMPLEMENT(void) andre@0: PR_StackPush(PRStack *stack, PRStackElem *stack_elem) andre@0: { andre@0: PR_Lock(stack->prstk_lock); andre@0: stack_elem->prstk_elem_next = stack->prstk_head.prstk_elem_next; andre@0: stack->prstk_head.prstk_elem_next = stack_elem; andre@0: PR_Unlock(stack->prstk_lock); andre@0: return; andre@0: } andre@0: andre@0: PR_IMPLEMENT(PRStackElem *) andre@0: PR_StackPop(PRStack *stack) andre@0: { andre@0: PRStackElem *element; andre@0: andre@0: PR_Lock(stack->prstk_lock); andre@0: element = stack->prstk_head.prstk_elem_next; andre@0: if (element != NULL) { andre@0: stack->prstk_head.prstk_elem_next = element->prstk_elem_next; andre@0: element->prstk_elem_next = NULL; /* debugging aid */ andre@0: } andre@0: PR_Unlock(stack->prstk_lock); andre@0: return element; andre@0: } andre@0: #endif /* !_PR_HAVE_ATOMIC_CAS */