/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifdef JS_THREADSAFE /* * JS locking stubs. */ #include #include #ifdef XP_WIN # include "jswin.h" #else # include #endif #include "jspubtd.h" #include "jstypes.h" #include "jsutil.h" #include "jsstdint.h" #include "jscntxt.h" #include "jsgc.h" #include "jslock.h" #include "jsscope.h" #include "jsstr.h" #include "jsscopeinlines.h" using namespace js; #define ReadWord(W) (W) #if !defined(__GNUC__) # define __asm__ asm # define __volatile__ volatile #endif /* Implement NativeCompareAndSwap. */ #if defined(_MSC_VER) && defined(_M_IX86) #pragma warning( disable : 4035 ) JS_BEGIN_EXTERN_C extern long __cdecl _InterlockedCompareExchange(long *volatile dest, long exchange, long comp); JS_END_EXTERN_C #pragma intrinsic(_InterlockedCompareExchange) JS_STATIC_ASSERT(sizeof(jsword) == sizeof(long)); static JS_ALWAYS_INLINE int NativeCompareAndSwapHelper(volatile jsword *w, jsword ov, jsword nv) { _InterlockedCompareExchange((long*) w, nv, ov); __asm { sete al } } static JS_ALWAYS_INLINE int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { return (NativeCompareAndSwapHelper(w, ov, nv) & 1); } #elif defined(_MSC_VER) && (defined(_M_AMD64) || defined(_M_X64)) /* * Compared with the _InterlockedCompareExchange in the 32 bit case above MSVC * declares _InterlockedCompareExchange64 through . */ #pragma intrinsic(_InterlockedCompareExchange64) JS_STATIC_ASSERT(sizeof(jsword) == sizeof(long long)); static JS_ALWAYS_INLINE int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { return _InterlockedCompareExchange64((long long *volatile)w, nv, ov) == ov; } #elif defined(XP_MACOSX) || defined(DARWIN) #include static JS_ALWAYS_INLINE int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { /* Details on these functions available in the manpage for atomic */ return OSAtomicCompareAndSwapPtrBarrier(reinterpret_cast(ov), reinterpret_cast(nv), reinterpret_cast(w)); } #elif defined(__i386) && (defined(__GNUC__) || defined(__SUNPRO_CC)) /* Note: This fails on 386 cpus, cmpxchgl is a >= 486 instruction */ static JS_ALWAYS_INLINE int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { unsigned int res; __asm__ __volatile__ ( "lock\n" "cmpxchgl %2, (%1)\n" "sete %%al\n" "andl $1, %%eax\n" : "=a" (res) #ifdef __SUNPRO_CC /* Different code for Sun Studio because of a bug of SS12U1 */ : "c" (w), "d" (nv), "a" (ov) #else : "r" (w), "r" (nv), "a" (ov) #endif : "cc", "memory"); return (int)res; } #elif defined(__x86_64) && (defined(__GNUC__) || defined(__SUNPRO_CC)) static JS_ALWAYS_INLINE int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { unsigned int res; __asm__ __volatile__ ( "lock\n" "cmpxchgq %2, (%1)\n" "sete %%al\n" "movzbl %%al, %%eax\n" : "=a" (res) : "r" (w), "r" (nv), "a" (ov) : "cc", "memory"); return (int)res; } #elif defined(__sparc) #if defined(__GNUC__) static JS_ALWAYS_INLINE int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { unsigned int res; __asm__ __volatile__ ( "membar #StoreLoad | #LoadLoad\n" #if JS_BITS_PER_WORD == 32 "cas [%1],%2,%3\n" #else "casx [%1],%2,%3\n" #endif "membar #StoreLoad | #LoadLoad\n" "cmp %2,%3\n" "be,a 1f\n" "mov 1,%0\n" "mov 0,%0\n" "1:" : "=r" (res) : "r" (w), "r" (ov), "r" (nv)); return (int)res; } #elif defined(__SUNPRO_CC) /* Implementation in lock_sparc*.il */ extern "C" int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv); #endif #elif defined(AIX) #include static JS_ALWAYS_INLINE int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { int res; JS_STATIC_ASSERT(sizeof(jsword) == sizeof(long)); res = compare_and_swaplp((atomic_l)w, &ov, nv); if (res) __asm__("isync"); return res; } #elif defined(__arm__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) JS_STATIC_ASSERT(sizeof(jsword) == sizeof(int)); static JS_ALWAYS_INLINE int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { return __sync_bool_compare_and_swap(w, ov, nv); } #elif defined(USE_ARM_KUSER) /* See https://bugzilla.mozilla.org/show_bug.cgi?id=429387 for a * description of this ABI; this is a function provided at a fixed * location by the kernel in the memory space of each process. */ typedef int (__kernel_cmpxchg_t)(int oldval, int newval, volatile int *ptr); #define __kernel_cmpxchg (*(__kernel_cmpxchg_t *)0xffff0fc0) JS_STATIC_ASSERT(sizeof(jsword) == sizeof(int)); static JS_ALWAYS_INLINE int NativeCompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { volatile int *vp = (volatile int *) w; PRInt32 failed = 1; /* Loop until a __kernel_cmpxchg succeeds. See bug 446169 */ do { failed = __kernel_cmpxchg(ov, nv, vp); } while (failed && *vp == ov); return !failed; } #elif JS_HAS_NATIVE_COMPARE_AND_SWAP #error "JS_HAS_NATIVE_COMPARE_AND_SWAP should be 0 if your platform lacks a compare-and-swap instruction." #endif /* arch-tests */ #if JS_HAS_NATIVE_COMPARE_AND_SWAP JSBool js_CompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { return !!NativeCompareAndSwap(w, ov, nv); } #elif defined(NSPR_LOCK) # ifdef __GNUC__ # warning "js_CompareAndSwap is implemented using NSPR lock" # endif JSBool js_CompareAndSwap(volatile jsword *w, jsword ov, jsword nv) { int result; static PRLock *CompareAndSwapLock = JS_NEW_LOCK(); JS_ACQUIRE_LOCK(CompareAndSwapLock); result = (*w == ov); if (result) *w = nv; JS_RELEASE_LOCK(CompareAndSwapLock); return result; } #else /* !defined(NSPR_LOCK) */ #error "NSPR_LOCK should be on when the platform lacks native compare-and-swap." #endif void js_AtomicSetMask(volatile jsword *w, jsword mask) { jsword ov, nv; do { ov = *w; nv = ov | mask; } while (!js_CompareAndSwap(w, ov, nv)); } void js_AtomicClearMask(volatile jsword *w, jsword mask) { jsword ov, nv; do { ov = *w; nv = ov & ~mask; } while (!js_CompareAndSwap(w, ov, nv)); } unsigned js_GetCPUCount() { static unsigned ncpus = 0; if (ncpus == 0) { # ifdef XP_WIN SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); ncpus = unsigned(sysinfo.dwNumberOfProcessors); # else long n = sysconf(_SC_NPROCESSORS_ONLN); ncpus = (n > 0) ? unsigned(n) : 1; # endif } return ncpus; } #ifndef NSPR_LOCK struct JSFatLock { int susp; PRLock *slock; PRCondVar *svar; JSFatLock *next; JSFatLock **prevp; }; typedef struct JSFatLockTable { JSFatLock *free_; JSFatLock *taken; } JSFatLockTable; #define GLOBAL_LOCK_INDEX(id) (((uint32_t)(jsuword)(id)>>2) & global_locks_mask) static void js_Dequeue(JSThinLock *); static PRLock **global_locks; static uint32_t global_lock_count = 1; static uint32_t global_locks_log2 = 0; static uint32_t global_locks_mask = 0; static void js_LockGlobal(void *id) { uint32_t i = GLOBAL_LOCK_INDEX(id); PR_Lock(global_locks[i]); } static void js_UnlockGlobal(void *id) { uint32_t i = GLOBAL_LOCK_INDEX(id); PR_Unlock(global_locks[i]); } #endif /* !NSPR_LOCK */ void js_InitLock(JSThinLock *tl) { #ifdef NSPR_LOCK tl->owner = 0; tl->fat = (JSFatLock*)JS_NEW_LOCK(); #else PodZero(tl); #endif } void js_FinishLock(JSThinLock *tl) { #ifdef NSPR_LOCK tl->owner = 0xdeadbeef; if (tl->fat) JS_DESTROY_LOCK(((JSLock*)tl->fat)); #else JS_ASSERT(tl->owner == 0); JS_ASSERT(tl->fat == NULL); #endif } #ifndef NSPR_LOCK static JSFatLock * NewFatlock() { JSFatLock *fl = (JSFatLock *) OffTheBooks::malloc_(sizeof(JSFatLock)); /* for now */ if (!fl) return NULL; fl->susp = 0; fl->next = NULL; fl->prevp = NULL; fl->slock = PR_NewLock(); fl->svar = PR_NewCondVar(fl->slock); return fl; } static void DestroyFatlock(JSFatLock *fl) { PR_DestroyLock(fl->slock); PR_DestroyCondVar(fl->svar); UnwantedForeground::free_(fl); } static JSFatLock * ListOfFatlocks(int listc) { JSFatLock *m; JSFatLock *m0; int i; JS_ASSERT(listc>0); m0 = m = NewFatlock(); for (i=1; inext = NewFatlock(); m = m->next; } return m0; } static void DeleteListOfFatlocks(JSFatLock *m) { JSFatLock *m0; for (; m; m=m0) { m0 = m->next; DestroyFatlock(m); } } static JSFatLockTable *fl_list_table = NULL; static uint32_t fl_list_table_len = 0; static uint32_t fl_list_chunk_len = 0; static JSFatLock * GetFatlock(void *id) { JSFatLock *m; uint32_t i = GLOBAL_LOCK_INDEX(id); if (fl_list_table[i].free_ == NULL) { #ifdef DEBUG if (fl_list_table[i].taken) printf("Ran out of fat locks!\n"); #endif fl_list_table[i].free_ = ListOfFatlocks(fl_list_chunk_len); } m = fl_list_table[i].free_; fl_list_table[i].free_ = m->next; m->susp = 0; m->next = fl_list_table[i].taken; m->prevp = &fl_list_table[i].taken; if (fl_list_table[i].taken) fl_list_table[i].taken->prevp = &m->next; fl_list_table[i].taken = m; return m; } static void PutFatlock(JSFatLock *m, void *id) { uint32_t i; if (m == NULL) return; /* Unlink m from fl_list_table[i].taken. */ *m->prevp = m->next; if (m->next) m->next->prevp = m->prevp; /* Insert m in fl_list_table[i].free. */ i = GLOBAL_LOCK_INDEX(id); m->next = fl_list_table[i].free_; fl_list_table[i].free_ = m; } #endif /* !NSPR_LOCK */ JSBool js_SetupLocks(int listc, int globc) { #ifndef NSPR_LOCK uint32_t i; if (global_locks) return JS_TRUE; #ifdef DEBUG if (listc > 10000 || listc < 0) /* listc == fat lock list chunk length */ printf("Bad number %d in js_SetupLocks()!\n", listc); if (globc > 100 || globc < 0) /* globc == number of global locks */ printf("Bad number %d in js_SetupLocks()!\n", listc); #endif global_locks_log2 = JS_CEILING_LOG2W(globc); global_locks_mask = JS_BITMASK(global_locks_log2); global_lock_count = JS_BIT(global_locks_log2); global_locks = (PRLock **) OffTheBooks::malloc_(global_lock_count * sizeof(PRLock*)); if (!global_locks) return JS_FALSE; for (i = 0; i < global_lock_count; i++) { global_locks[i] = PR_NewLock(); if (!global_locks[i]) { global_lock_count = i; js_CleanupLocks(); return JS_FALSE; } } fl_list_table = (JSFatLockTable *) OffTheBooks::malloc_(i * sizeof(JSFatLockTable)); if (!fl_list_table) { js_CleanupLocks(); return JS_FALSE; } fl_list_table_len = global_lock_count; for (i = 0; i < global_lock_count; i++) fl_list_table[i].free_ = fl_list_table[i].taken = NULL; fl_list_chunk_len = listc; #endif /* !NSPR_LOCK */ return JS_TRUE; } void js_CleanupLocks() { #ifndef NSPR_LOCK uint32_t i; if (global_locks) { for (i = 0; i < global_lock_count; i++) PR_DestroyLock(global_locks[i]); UnwantedForeground::free_(global_locks); global_locks = NULL; global_lock_count = 1; global_locks_log2 = 0; global_locks_mask = 0; } if (fl_list_table) { for (i = 0; i < fl_list_table_len; i++) { DeleteListOfFatlocks(fl_list_table[i].free_); fl_list_table[i].free_ = NULL; DeleteListOfFatlocks(fl_list_table[i].taken); fl_list_table[i].taken = NULL; } UnwantedForeground::free_(fl_list_table); fl_list_table = NULL; fl_list_table_len = 0; } #endif /* !NSPR_LOCK */ } #ifdef NSPR_LOCK static JS_ALWAYS_INLINE void ThinLock(JSThinLock *tl, jsword me) { JS_ACQUIRE_LOCK((JSLock *) tl->fat); tl->owner = me; } static JS_ALWAYS_INLINE void ThinUnlock(JSThinLock *tl, jsword /*me*/) { tl->owner = 0; JS_RELEASE_LOCK((JSLock *) tl->fat); } #else /* * Fast locking and unlocking is implemented by delaying the allocation of a * system lock (fat lock) until contention. As long as a locking thread A * runs uncontended, the lock is represented solely by storing A's identity in * the object being locked. * * If another thread B tries to lock the object currently locked by A, B is * enqueued into a fat lock structure (which might have to be allocated and * pointed to by the object), and suspended using NSPR conditional variables * (wait). A wait bit (Bacon bit) is set in the lock word of the object, * signalling to A that when releasing the lock, B must be dequeued and * notified. * * The basic operation of the locking primitives (js_Lock, js_Unlock, * js_Enqueue, and js_Dequeue) is compare-and-swap. Hence, when locking into * the word pointed at by p, compare-and-swap(p, 0, A) success implies that p * is unlocked. Similarly, when unlocking p, if compare-and-swap(p, A, 0) * succeeds this implies that p is uncontended (no one is waiting because the * wait bit is not set). * * When dequeueing, the lock is released, and one of the threads suspended on * the lock is notified. If other threads still are waiting, the wait bit is * kept (in js_Enqueue), and if not, the fat lock is deallocated. * * The functions js_Enqueue, js_Dequeue, js_SuspendThread, and js_ResumeThread * are serialized using a global lock. For scalability, a hashtable of global * locks is used, which is indexed modulo the thin lock pointer. */ /* * Invariants: * (i) global lock is held * (ii) fl->susp >= 0 */ static int js_SuspendThread(JSThinLock *tl) { JSFatLock *fl; if (tl->fat == NULL) fl = tl->fat = GetFatlock(tl); else fl = tl->fat; JS_ASSERT(fl->susp >= 0); fl->susp++; PR_Lock(fl->slock); js_UnlockGlobal(tl); DebugOnly stat = PR_WaitCondVar(fl->svar, PR_INTERVAL_NO_TIMEOUT); JS_ASSERT(stat != PR_FAILURE); PR_Unlock(fl->slock); js_LockGlobal(tl); fl->susp--; if (fl->susp == 0) { PutFatlock(fl, tl); tl->fat = NULL; } return tl->fat == NULL; } /* * (i) global lock is held * (ii) fl->susp > 0 */ static void js_ResumeThread(JSThinLock *tl) { JSFatLock *fl = tl->fat; JS_ASSERT(fl != NULL); JS_ASSERT(fl->susp > 0); PR_Lock(fl->slock); js_UnlockGlobal(tl); DebugOnly stat = PR_NotifyCondVar(fl->svar); JS_ASSERT(stat != PR_FAILURE); PR_Unlock(fl->slock); } static void js_Enqueue(JSThinLock *tl, jsword me) { jsword o, n; js_LockGlobal(tl); for (;;) { o = ReadWord(tl->owner); n = Thin_SetWait(o); if (o != 0 && NativeCompareAndSwap(&tl->owner, o, n)) { if (js_SuspendThread(tl)) me = Thin_RemoveWait(me); else me = Thin_SetWait(me); } else if (NativeCompareAndSwap(&tl->owner, 0, me)) { js_UnlockGlobal(tl); return; } } } static void js_Dequeue(JSThinLock *tl) { jsword o; js_LockGlobal(tl); o = ReadWord(tl->owner); JS_ASSERT(Thin_GetWait(o) != 0); JS_ASSERT(tl->fat != NULL); if (!NativeCompareAndSwap(&tl->owner, o, 0)) /* release it */ JS_ASSERT(0); js_ResumeThread(tl); } static JS_ALWAYS_INLINE void ThinLock(JSThinLock *tl, jsword me) { JS_ASSERT(CURRENT_THREAD_IS_ME(me)); if (NativeCompareAndSwap(&tl->owner, 0, me)) return; if (Thin_RemoveWait(ReadWord(tl->owner)) != me) js_Enqueue(tl, me); #ifdef DEBUG else JS_ASSERT(0); #endif } static JS_ALWAYS_INLINE void ThinUnlock(JSThinLock *tl, jsword me) { JS_ASSERT(CURRENT_THREAD_IS_ME(me)); /* * Since we can race with the NativeCompareAndSwap in js_Enqueue, we need * to use a C_A_S here as well -- Arjan van de Ven 30/1/08 */ if (NativeCompareAndSwap(&tl->owner, me, 0)) return; JS_ASSERT(Thin_GetWait(tl->owner)); if (Thin_RemoveWait(ReadWord(tl->owner)) == me) js_Dequeue(tl); #ifdef DEBUG else JS_ASSERT(0); /* unbalanced unlock */ #endif } #endif /* !NSPR_LOCK */ void js_Lock(JSContext *cx, JSThinLock *tl) { ThinLock(tl, CX_THINLOCK_ID(cx)); } void js_Unlock(JSContext *cx, JSThinLock *tl) { ThinUnlock(tl, CX_THINLOCK_ID(cx)); } void js_LockRuntime(JSRuntime *rt) { PR_Lock(rt->rtLock); #ifdef DEBUG rt->rtLockOwner = js_CurrentThreadId(); #endif } void js_UnlockRuntime(JSRuntime *rt) { #ifdef DEBUG rt->rtLockOwner = NULL; #endif PR_Unlock(rt->rtLock); } #ifdef DEBUG JSBool js_IsRuntimeLocked(JSRuntime *rt) { return js_CurrentThreadId() == rt->rtLockOwner; } #endif /* DEBUG */ #endif /* JS_THREADSAFE */