diff options
Diffstat (limited to 'src/third_party/js-1.7/jslock.c')
-rw-r--r-- | src/third_party/js-1.7/jslock.c | 1303 |
1 files changed, 0 insertions, 1303 deletions
diff --git a/src/third_party/js-1.7/jslock.c b/src/third_party/js-1.7/jslock.c deleted file mode 100644 index 48550099797..00000000000 --- a/src/third_party/js-1.7/jslock.c +++ /dev/null @@ -1,1303 +0,0 @@ -/* -*- 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 "jsstddef.h" -#include <stdlib.h> -#include "jspubtd.h" -#include "jsutil.h" /* Added by JSIFY */ -#include "jstypes.h" -#include "jsbit.h" -#include "jscntxt.h" -#include "jsdtoa.h" -#include "jsgc.h" -#include "jslock.h" -#include "jsscope.h" -#include "jsstr.h" - -#define ReadWord(W) (W) - -#ifndef NSPR_LOCK - -#include <memory.h> - -static PRLock **global_locks; -static uint32 global_lock_count = 1; -static uint32 global_locks_log2 = 0; -static uint32 global_locks_mask = 0; - -#define GLOBAL_LOCK_INDEX(id) (((uint32)(id) >> 2) & global_locks_mask) - -static void -js_LockGlobal(void *id) -{ - uint32 i = GLOBAL_LOCK_INDEX(id); - PR_Lock(global_locks[i]); -} - -static void -js_UnlockGlobal(void *id) -{ - uint32 i = GLOBAL_LOCK_INDEX(id); - PR_Unlock(global_locks[i]); -} - -/* Exclude Alpha NT. */ -#if defined(_WIN32) && defined(_M_IX86) -#pragma warning( disable : 4035 ) - -static JS_INLINE int -js_CompareAndSwap(jsword *w, jsword ov, jsword nv) -{ - __asm { - mov eax, ov - mov ecx, nv - mov ebx, w - lock cmpxchg [ebx], ecx - sete al - and eax, 1h - } -} - -#elif defined(__GNUC__) && defined(__i386__) - -/* Note: This fails on 386 cpus, cmpxchgl is a >= 486 instruction */ -static JS_INLINE int -js_CompareAndSwap(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) - : "r" (w), "r" (nv), "a" (ov) - : "cc", "memory"); - return (int)res; -} - -#elif (defined(__USLC__) || defined(_SCO_DS)) && defined(i386) - -/* Note: This fails on 386 cpus, cmpxchgl is a >= 486 instruction */ - -asm int -js_CompareAndSwap(jsword *w, jsword ov, jsword nv) -{ -%ureg w, nv; - movl ov,%eax - lock - cmpxchgl nv,(w) - sete %al - andl $1,%eax -%ureg w; mem ov, nv; - movl ov,%eax - movl nv,%ecx - lock - cmpxchgl %ecx,(w) - sete %al - andl $1,%eax -%ureg nv; - movl ov,%eax - movl w,%edx - lock - cmpxchgl nv,(%edx) - sete %al - andl $1,%eax -%mem w, ov, nv; - movl ov,%eax - movl nv,%ecx - movl w,%edx - lock - cmpxchgl %ecx,(%edx) - sete %al - andl $1,%eax -} -#pragma asm full_optimization js_CompareAndSwap - -#elif defined(SOLARIS) && defined(sparc) && defined(ULTRA_SPARC) - -static JS_INLINE int -js_CompareAndSwap(jsword *w, jsword ov, jsword nv) -{ -#if defined(__GNUC__) - unsigned int res; - JS_ASSERT(ov != nv); - asm volatile ("\ -stbar\n\ -cas [%1],%2,%3\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; -#else /* !__GNUC__ */ - extern int compare_and_swap(jsword*, jsword, jsword); - JS_ASSERT(ov != nv); - return compare_and_swap(w, ov, nv); -#endif -} - -#elif defined(AIX) - -#include <sys/atomic_op.h> - -static JS_INLINE int -js_CompareAndSwap(jsword *w, jsword ov, jsword nv) -{ - return !_check_lock((atomic_p)w, ov, nv); -} - -#else - -#error "Define NSPR_LOCK if your platform lacks a compare-and-swap instruction." - -#endif /* arch-tests */ - -#endif /* !NSPR_LOCK */ - -void -js_InitLock(JSThinLock *tl) -{ -#ifdef NSPR_LOCK - tl->owner = 0; - tl->fat = (JSFatLock*)JS_NEW_LOCK(); -#else - memset(tl, 0, sizeof(JSThinLock)); -#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 -} - -static void js_Dequeue(JSThinLock *); - -#ifdef DEBUG_SCOPE_COUNT - -#include <stdio.h> -#include "jsdhash.h" - -static FILE *logfp; -static JSDHashTable logtbl; - -typedef struct logentry { - JSDHashEntryStub stub; - char op; - const char *file; - int line; -} logentry; - -static void -logit(JSScope *scope, char op, const char *file, int line) -{ - logentry *entry; - - if (!logfp) { - logfp = fopen("/tmp/scope.log", "w"); - if (!logfp) - return; - setvbuf(logfp, NULL, _IONBF, 0); - } - fprintf(logfp, "%p %c %s %d\n", scope, op, file, line); - - if (!logtbl.entryStore && - !JS_DHashTableInit(&logtbl, JS_DHashGetStubOps(), NULL, - sizeof(logentry), 100)) { - return; - } - entry = (logentry *) JS_DHashTableOperate(&logtbl, scope, JS_DHASH_ADD); - if (!entry) - return; - entry->stub.key = scope; - entry->op = op; - entry->file = file; - entry->line = line; -} - -void -js_unlog_scope(JSScope *scope) -{ - if (!logtbl.entryStore) - return; - (void) JS_DHashTableOperate(&logtbl, scope, JS_DHASH_REMOVE); -} - -# define LOGIT(scope,op) logit(scope, op, __FILE__, __LINE__) - -#else - -# define LOGIT(scope,op) /* nothing */ - -#endif /* DEBUG_SCOPE_COUNT */ - -/* - * Return true if scope's ownercx, or the ownercx of a single-threaded scope - * for which ownercx is waiting to become multi-threaded and shared, is cx. - * That condition implies deadlock in ClaimScope if cx's thread were to wait - * to share scope. - * - * (i) rt->gcLock held - */ -static JSBool -WillDeadlock(JSScope *scope, JSContext *cx) -{ - JSContext *ownercx; - - do { - ownercx = scope->ownercx; - if (ownercx == cx) { - JS_RUNTIME_METER(cx->runtime, deadlocksAvoided); - return JS_TRUE; - } - } while (ownercx && (scope = ownercx->scopeToShare) != NULL); - return JS_FALSE; -} - -/* - * Make scope multi-threaded, i.e. share its ownership among contexts in rt - * using a "thin" or (if necessary due to contention) "fat" lock. Called only - * from ClaimScope, immediately below, when we detect deadlock were we to wait - * for scope's lock, because its ownercx is waiting on a scope owned by the - * calling cx. - * - * (i) rt->gcLock held - */ -static void -ShareScope(JSRuntime *rt, JSScope *scope) -{ - JSScope **todop; - - if (scope->u.link) { - for (todop = &rt->scopeSharingTodo; *todop != scope; - todop = &(*todop)->u.link) { - JS_ASSERT(*todop != NO_SCOPE_SHARING_TODO); - } - *todop = scope->u.link; - scope->u.link = NULL; /* null u.link for sanity ASAP */ - JS_NOTIFY_ALL_CONDVAR(rt->scopeSharingDone); - } - js_InitLock(&scope->lock); - if (scope == rt->setSlotScope) { - /* - * Nesting locks on another thread that's using scope->ownercx: give - * the held lock a reentrancy count of 1 and set its lock.owner field - * directly (no compare-and-swap needed while scope->ownercx is still - * non-null). See below in ClaimScope, before the ShareScope call, - * for more on why this is necessary. - * - * If NSPR_LOCK is defined, we cannot deadlock holding rt->gcLock and - * acquiring scope->lock.fat here, against another thread holding that - * fat lock and trying to grab rt->gcLock. This is because no other - * thread can attempt to acquire scope->lock.fat until scope->ownercx - * is null *and* our thread has released rt->gcLock, which interlocks - * scope->ownercx's transition to null against tests of that member - * in ClaimScope. - */ - scope->lock.owner = CX_THINLOCK_ID(scope->ownercx); -#ifdef NSPR_LOCK - JS_ACQUIRE_LOCK((JSLock*)scope->lock.fat); -#endif - scope->u.count = 1; - } else { - scope->u.count = 0; - } - js_FinishSharingScope(rt, scope); -} - -/* - * js_FinishSharingScope is the tail part of ShareScope, split out to become a - * subroutine of JS_EndRequest too. The bulk of the work here involves making - * mutable strings in the scope's object's slots be immutable. We have to do - * this because such strings will soon be available to multiple threads, so - * their buffers can't be realloc'd any longer in js_ConcatStrings, and their - * members can't be modified by js_ConcatStrings, js_MinimizeDependentStrings, - * or js_UndependString. - * - * The last bit of work done by js_FinishSharingScope nulls scope->ownercx and - * updates rt->sharedScopes. - */ -#define MAKE_STRING_IMMUTABLE(rt, v, vp) \ - JS_BEGIN_MACRO \ - JSString *str_ = JSVAL_TO_STRING(v); \ - uint8 *flagp_ = js_GetGCThingFlags(str_); \ - if (*flagp_ & GCF_MUTABLE) { \ - if (JSSTRING_IS_DEPENDENT(str_) && \ - !js_UndependString(NULL, str_)) { \ - JS_RUNTIME_METER(rt, badUndependStrings); \ - *vp = JSVAL_VOID; \ - } else { \ - *flagp_ &= ~GCF_MUTABLE; \ - } \ - } \ - JS_END_MACRO - -void -js_FinishSharingScope(JSRuntime *rt, JSScope *scope) -{ - JSObject *obj; - uint32 nslots; - jsval v, *vp, *end; - - obj = scope->object; - nslots = JS_MIN(obj->map->freeslot, obj->map->nslots); - for (vp = obj->slots, end = vp + nslots; vp < end; vp++) { - v = *vp; - if (JSVAL_IS_STRING(v)) - MAKE_STRING_IMMUTABLE(rt, v, vp); - } - - scope->ownercx = NULL; /* NB: set last, after lock init */ - JS_RUNTIME_METER(rt, sharedScopes); -} - -/* - * Given a scope with apparently non-null ownercx different from cx, try to - * set ownercx to cx, claiming exclusive (single-threaded) ownership of scope. - * If we claim ownership, return true. Otherwise, we wait for ownercx to be - * set to null (indicating that scope is multi-threaded); or if waiting would - * deadlock, we set ownercx to null ourselves via ShareScope. In any case, - * once ownercx is null we return false. - */ -static JSBool -ClaimScope(JSScope *scope, JSContext *cx) -{ - JSRuntime *rt; - JSContext *ownercx; - jsrefcount saveDepth; - PRStatus stat; - - rt = cx->runtime; - JS_RUNTIME_METER(rt, claimAttempts); - JS_LOCK_GC(rt); - - /* Reload in case ownercx went away while we blocked on the lock. */ - while ((ownercx = scope->ownercx) != NULL) { - /* - * Avoid selflock if ownercx is dead, or is not running a request, or - * has the same thread as cx. Set scope->ownercx to cx so that the - * matching JS_UNLOCK_SCOPE or JS_UNLOCK_OBJ macro call will take the - * fast path around the corresponding js_UnlockScope or js_UnlockObj - * function call. - * - * If scope->u.link is non-null, scope has already been inserted on - * the rt->scopeSharingTodo list, because another thread's context - * already wanted to lock scope while ownercx was running a request. - * We can't claim any scope whose u.link is non-null at this point, - * even if ownercx->requestDepth is 0 (see below where we suspend our - * request before waiting on rt->scopeSharingDone). - */ - if (!scope->u.link && - (!js_ValidContextPointer(rt, ownercx) || - !ownercx->requestDepth || - ownercx->thread == cx->thread)) { - JS_ASSERT(scope->u.count == 0); - scope->ownercx = cx; - JS_UNLOCK_GC(rt); - JS_RUNTIME_METER(rt, claimedScopes); - return JS_TRUE; - } - - /* - * Avoid deadlock if scope's owner context is waiting on a scope that - * we own, by revoking scope's ownership. This approach to deadlock - * avoidance works because the engine never nests scope locks, except - * for the notable case of js_SetProtoOrParent (see jsobj.c). - * - * If cx could hold locks on ownercx->scopeToShare, or if ownercx - * could hold locks on scope, we would need to keep reentrancy counts - * for all such "flyweight" (ownercx != NULL) locks, so that control - * would unwind properly once these locks became "thin" or "fat". - * Apart from the js_SetProtoOrParent exception, the engine promotes - * a scope from exclusive to shared access only when locking, never - * when holding or unlocking. - * - * If ownercx's thread is calling js_SetProtoOrParent, trying to lock - * the inner scope (the scope of the object being set as the prototype - * of the outer object), ShareScope will find the outer object's scope - * at rt->setSlotScope. If it's the same as scope, we give it a lock - * held by ownercx's thread with reentrancy count of 1, then we return - * here and break. After that we unwind to js_[GS]etSlotThreadSafe or - * js_LockScope (our caller), where we wait on the newly-fattened lock - * until ownercx's thread unwinds from js_SetProtoOrParent. - * - * Avoid deadlock before any of this scope/context cycle detection if - * cx is on the active GC's thread, because in that case, no requests - * will run until the GC completes. Any scope wanted by the GC (from - * a finalizer) that can't be claimed must be slated for sharing. - */ - if (rt->gcThread == cx->thread || - (ownercx->scopeToShare && - WillDeadlock(ownercx->scopeToShare, cx))) { - ShareScope(rt, scope); - break; - } - - /* - * Thanks to the non-zero NO_SCOPE_SHARING_TODO link terminator, we - * can decide whether scope is on rt->scopeSharingTodo with a single - * non-null test, and avoid double-insertion bugs. - */ - if (!scope->u.link) { - scope->u.link = rt->scopeSharingTodo; - rt->scopeSharingTodo = scope; - js_HoldObjectMap(cx, &scope->map); - } - - /* - * Inline JS_SuspendRequest before we wait on rt->scopeSharingDone, - * saving and clearing cx->requestDepth so we don't deadlock if the - * GC needs to run on ownercx. - * - * Unlike JS_SuspendRequest and JS_EndRequest, we must take care not - * to decrement rt->requestCount if cx is active on the GC's thread, - * because the GC has already reduced rt->requestCount to exclude all - * such such contexts. - */ - saveDepth = cx->requestDepth; - if (saveDepth) { - cx->requestDepth = 0; - if (rt->gcThread != cx->thread) { - JS_ASSERT(rt->requestCount > 0); - rt->requestCount--; - if (rt->requestCount == 0) - JS_NOTIFY_REQUEST_DONE(rt); - } - } - - /* - * We know that some other thread's context owns scope, which is now - * linked onto rt->scopeSharingTodo, awaiting the end of that other - * thread's request. So it is safe to wait on rt->scopeSharingDone. - */ - cx->scopeToShare = scope; - stat = PR_WaitCondVar(rt->scopeSharingDone, PR_INTERVAL_NO_TIMEOUT); - JS_ASSERT(stat != PR_FAILURE); - - /* - * Inline JS_ResumeRequest after waiting on rt->scopeSharingDone, - * restoring cx->requestDepth. Same note as above for the inlined, - * specialized JS_SuspendRequest code: beware rt->gcThread. - */ - if (saveDepth) { - if (rt->gcThread != cx->thread) { - while (rt->gcLevel > 0) - JS_AWAIT_GC_DONE(rt); - rt->requestCount++; - } - cx->requestDepth = saveDepth; - } - - /* - * Don't clear cx->scopeToShare until after we're through waiting on - * all condition variables protected by rt->gcLock -- that includes - * rt->scopeSharingDone *and* rt->gcDone (hidden in JS_AWAIT_GC_DONE, - * in the inlined JS_ResumeRequest code immediately above). - * - * Otherwise, the GC could easily deadlock with another thread that - * owns a scope wanted by a finalizer. By keeping cx->scopeToShare - * set till here, we ensure that such deadlocks are detected, which - * results in the finalized object's scope being shared (it must, of - * course, have other, live objects sharing it). - */ - cx->scopeToShare = NULL; - } - - JS_UNLOCK_GC(rt); - return JS_FALSE; -} - -/* Exported to js.c, which calls it via OBJ_GET_* and JSVAL_IS_* macros. */ -JS_FRIEND_API(jsval) -js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot) -{ - jsval v; - JSScope *scope; -#ifndef NSPR_LOCK - JSThinLock *tl; - jsword me; -#endif - - /* - * We handle non-native objects via JSObjectOps.getRequiredSlot, treating - * all slots starting from 0 as required slots. A property definition or - * some prior arrangement must have allocated slot. - * - * Note once again (see jspubtd.h, before JSGetRequiredSlotOp's typedef) - * the crucial distinction between a |required slot number| that's passed - * to the get/setRequiredSlot JSObjectOps, and a |reserved slot index| - * passed to the JS_Get/SetReservedSlot APIs. - */ - if (!OBJ_IS_NATIVE(obj)) - return OBJ_GET_REQUIRED_SLOT(cx, obj, slot); - - /* - * Native object locking is inlined here to optimize the single-threaded - * and contention-free multi-threaded cases. - */ - scope = OBJ_SCOPE(obj); - JS_ASSERT(scope->ownercx != cx); - JS_ASSERT(obj->slots && slot < obj->map->freeslot); - - /* - * Avoid locking if called from the GC (see GC_AWARE_GET_SLOT in jsobj.h). - * Also avoid locking an object owning a sealed scope. If neither of those - * special cases applies, try to claim scope's flyweight lock from whatever - * context may have had it in an earlier request. - */ - if (CX_THREAD_IS_RUNNING_GC(cx) || - (SCOPE_IS_SEALED(scope) && scope->object == obj) || - (scope->ownercx && ClaimScope(scope, cx))) { - return obj->slots[slot]; - } - -#ifndef NSPR_LOCK - tl = &scope->lock; - me = CX_THINLOCK_ID(cx); - JS_ASSERT(CURRENT_THREAD_IS_ME(me)); - if (js_CompareAndSwap(&tl->owner, 0, me)) { - /* - * Got the lock with one compare-and-swap. Even so, someone else may - * have mutated obj so it now has its own scope and lock, which would - * require either a restart from the top of this routine, or a thin - * lock release followed by fat lock acquisition. - */ - if (scope == OBJ_SCOPE(obj)) { - v = obj->slots[slot]; - if (!js_CompareAndSwap(&tl->owner, me, 0)) { - /* Assert that scope locks never revert to flyweight. */ - JS_ASSERT(scope->ownercx != cx); - LOGIT(scope, '1'); - scope->u.count = 1; - js_UnlockObj(cx, obj); - } - return v; - } - if (!js_CompareAndSwap(&tl->owner, me, 0)) - js_Dequeue(tl); - } - else if (Thin_RemoveWait(ReadWord(tl->owner)) == me) { - return obj->slots[slot]; - } -#endif - - js_LockObj(cx, obj); - v = obj->slots[slot]; - - /* - * Test whether cx took ownership of obj's scope during js_LockObj. - * - * This does not mean that a given scope reverted to flyweight from "thin" - * or "fat" -- it does mean that obj's map pointer changed due to another - * thread setting a property, requiring obj to cease sharing a prototype - * object's scope (whose lock was not flyweight, else we wouldn't be here - * in the first place!). - */ - scope = OBJ_SCOPE(obj); - if (scope->ownercx != cx) - js_UnlockScope(cx, scope); - return v; -} - -void -js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v) -{ - JSScope *scope; -#ifndef NSPR_LOCK - JSThinLock *tl; - jsword me; -#endif - - /* Any string stored in a thread-safe object must be immutable. */ - if (JSVAL_IS_STRING(v)) - MAKE_STRING_IMMUTABLE(cx->runtime, v, &v); - - /* - * We handle non-native objects via JSObjectOps.setRequiredSlot, as above - * for the Get case. - */ - if (!OBJ_IS_NATIVE(obj)) { - OBJ_SET_REQUIRED_SLOT(cx, obj, slot, v); - return; - } - - /* - * Native object locking is inlined here to optimize the single-threaded - * and contention-free multi-threaded cases. - */ - scope = OBJ_SCOPE(obj); - JS_ASSERT(scope->ownercx != cx); - JS_ASSERT(obj->slots && slot < obj->map->freeslot); - - /* - * Avoid locking if called from the GC (see GC_AWARE_GET_SLOT in jsobj.h). - * Also avoid locking an object owning a sealed scope. If neither of those - * special cases applies, try to claim scope's flyweight lock from whatever - * context may have had it in an earlier request. - */ - if (CX_THREAD_IS_RUNNING_GC(cx) || - (SCOPE_IS_SEALED(scope) && scope->object == obj) || - (scope->ownercx && ClaimScope(scope, cx))) { - obj->slots[slot] = v; - return; - } - -#ifndef NSPR_LOCK - tl = &scope->lock; - me = CX_THINLOCK_ID(cx); - JS_ASSERT(CURRENT_THREAD_IS_ME(me)); - if (js_CompareAndSwap(&tl->owner, 0, me)) { - if (scope == OBJ_SCOPE(obj)) { - obj->slots[slot] = v; - if (!js_CompareAndSwap(&tl->owner, me, 0)) { - /* Assert that scope locks never revert to flyweight. */ - JS_ASSERT(scope->ownercx != cx); - LOGIT(scope, '1'); - scope->u.count = 1; - js_UnlockObj(cx, obj); - } - return; - } - if (!js_CompareAndSwap(&tl->owner, me, 0)) - js_Dequeue(tl); - } - else if (Thin_RemoveWait(ReadWord(tl->owner)) == me) { - obj->slots[slot] = v; - return; - } -#endif - - js_LockObj(cx, obj); - obj->slots[slot] = v; - - /* - * Same drill as above, in js_GetSlotThreadSafe. Note that we cannot - * assume obj has its own mutable scope (where scope->object == obj) yet, - * because OBJ_SET_SLOT is called for the "universal", common slots such - * as JSSLOT_PROTO and JSSLOT_PARENT, without a prior js_GetMutableScope. - * See also the JSPROP_SHARED attribute and its usage. - */ - scope = OBJ_SCOPE(obj); - if (scope->ownercx != cx) - js_UnlockScope(cx, scope); -} - -#ifndef NSPR_LOCK - -static JSFatLock * -NewFatlock() -{ - JSFatLock *fl = (JSFatLock *)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); - free(fl); -} - -static JSFatLock * -ListOfFatlocks(int listc) -{ - JSFatLock *m; - JSFatLock *m0; - int i; - - JS_ASSERT(listc>0); - m0 = m = NewFatlock(); - for (i=1; i<listc; i++) { - m->next = 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 fl_list_table_len = 0; -static uint32 fl_list_chunk_len = 0; - -static JSFatLock * -GetFatlock(void *id) -{ - JSFatLock *m; - - uint32 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 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 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_CeilingLog2(globc); - global_locks_mask = JS_BITMASK(global_locks_log2); - global_lock_count = JS_BIT(global_locks_log2); - global_locks = (PRLock **) 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 *) 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 i; - - if (global_locks) { - for (i = 0; i < global_lock_count; i++) - PR_DestroyLock(global_locks[i]); - 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; - } - free(fl_list_table); - fl_list_table = NULL; - fl_list_table_len = 0; - } -#endif /* !NSPR_LOCK */ -} - -#ifndef NSPR_LOCK - -/* - * 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; - PRStatus stat; - - 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); - 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; - PRStatus stat; - - JS_ASSERT(fl != NULL); - JS_ASSERT(fl->susp > 0); - PR_Lock(fl->slock); - js_UnlockGlobal(tl); - 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 && js_CompareAndSwap(&tl->owner, o, n)) { - if (js_SuspendThread(tl)) - me = Thin_RemoveWait(me); - else - me = Thin_SetWait(me); - } - else if (js_CompareAndSwap(&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 (!js_CompareAndSwap(&tl->owner, o, 0)) /* release it */ - JS_ASSERT(0); - js_ResumeThread(tl); -} - -JS_INLINE void -js_Lock(JSThinLock *tl, jsword me) -{ - JS_ASSERT(CURRENT_THREAD_IS_ME(me)); - if (js_CompareAndSwap(&tl->owner, 0, me)) - return; - if (Thin_RemoveWait(ReadWord(tl->owner)) != me) - js_Enqueue(tl, me); -#ifdef DEBUG - else - JS_ASSERT(0); -#endif -} - -JS_INLINE void -js_Unlock(JSThinLock *tl, jsword me) -{ - JS_ASSERT(CURRENT_THREAD_IS_ME(me)); - - /* - * Only me can hold the lock, no need to use compare and swap atomic - * operation for this common case. - */ - if (tl->owner == me) { - tl->owner = 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_LockRuntime(JSRuntime *rt) -{ - PR_Lock(rt->rtLock); -#ifdef DEBUG - rt->rtLockOwner = js_CurrentThreadId(); -#endif -} - -void -js_UnlockRuntime(JSRuntime *rt) -{ -#ifdef DEBUG - rt->rtLockOwner = 0; -#endif - PR_Unlock(rt->rtLock); -} - -void -js_LockScope(JSContext *cx, JSScope *scope) -{ - jsword me = CX_THINLOCK_ID(cx); - - JS_ASSERT(CURRENT_THREAD_IS_ME(me)); - JS_ASSERT(scope->ownercx != cx); - if (CX_THREAD_IS_RUNNING_GC(cx)) - return; - if (scope->ownercx && ClaimScope(scope, cx)) - return; - - if (Thin_RemoveWait(ReadWord(scope->lock.owner)) == me) { - JS_ASSERT(scope->u.count > 0); - LOGIT(scope, '+'); - scope->u.count++; - } else { - JSThinLock *tl = &scope->lock; - JS_LOCK0(tl, me); - JS_ASSERT(scope->u.count == 0); - LOGIT(scope, '1'); - scope->u.count = 1; - } -} - -void -js_UnlockScope(JSContext *cx, JSScope *scope) -{ - jsword me = CX_THINLOCK_ID(cx); - - /* We hope compilers use me instead of reloading cx->thread in the macro. */ - if (CX_THREAD_IS_RUNNING_GC(cx)) - return; - if (cx->lockedSealedScope == scope) { - cx->lockedSealedScope = NULL; - return; - } - - /* - * If scope->ownercx is not null, it's likely that two contexts not using - * requests nested locks for scope. The first context, cx here, claimed - * scope; the second, scope->ownercx here, re-claimed it because the first - * was not in a request, or was on the same thread. We don't want to keep - * track of such nesting, because it penalizes the common non-nested case. - * Instead of asserting here and silently coping, we simply re-claim scope - * for cx and return. - * - * See http://bugzilla.mozilla.org/show_bug.cgi?id=229200 for a real world - * case where an asymmetric thread model (Mozilla's main thread is known - * to be the only thread that runs the GC) combined with multiple contexts - * per thread has led to such request-less nesting. - */ - if (scope->ownercx) { - JS_ASSERT(scope->u.count == 0); - JS_ASSERT(scope->lock.owner == 0); - scope->ownercx = cx; - return; - } - - JS_ASSERT(scope->u.count > 0); - if (Thin_RemoveWait(ReadWord(scope->lock.owner)) != me) { - JS_ASSERT(0); /* unbalanced unlock */ - return; - } - LOGIT(scope, '-'); - if (--scope->u.count == 0) { - JSThinLock *tl = &scope->lock; - JS_UNLOCK0(tl, me); - } -} - -/* - * NB: oldscope may be null if our caller is js_GetMutableScope and it just - * dropped the last reference to oldscope. - */ -void -js_TransferScopeLock(JSContext *cx, JSScope *oldscope, JSScope *newscope) -{ - jsword me; - JSThinLock *tl; - - JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, newscope)); - - /* - * If the last reference to oldscope went away, newscope needs no lock - * state update. - */ - if (!oldscope) - return; - JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, oldscope)); - - /* - * Special case in js_LockScope and js_UnlockScope for the GC calling - * code that locks, unlocks, or mutates. Nothing to do in these cases, - * because scope and newscope were "locked" by the GC thread, so neither - * was actually locked. - */ - if (CX_THREAD_IS_RUNNING_GC(cx)) - return; - - /* - * Special case in js_LockObj and js_UnlockScope for locking the sealed - * scope of an object that owns that scope (the prototype or mutated obj - * for which OBJ_SCOPE(obj)->object == obj), and unlocking it. - */ - JS_ASSERT(cx->lockedSealedScope != newscope); - if (cx->lockedSealedScope == oldscope) { - JS_ASSERT(newscope->ownercx == cx || - (!newscope->ownercx && newscope->u.count == 1)); - cx->lockedSealedScope = NULL; - return; - } - - /* - * If oldscope is single-threaded, there's nothing to do. - */ - if (oldscope->ownercx) { - JS_ASSERT(oldscope->ownercx == cx); - JS_ASSERT(newscope->ownercx == cx || - (!newscope->ownercx && newscope->u.count == 1)); - return; - } - - /* - * We transfer oldscope->u.count only if newscope is not single-threaded. - * Flow unwinds from here through some number of JS_UNLOCK_SCOPE and/or - * JS_UNLOCK_OBJ macro calls, which will decrement newscope->u.count only - * if they find newscope->ownercx != cx. - */ - if (newscope->ownercx != cx) { - JS_ASSERT(!newscope->ownercx); - newscope->u.count = oldscope->u.count; - } - - /* - * Reset oldscope's lock state so that it is completely unlocked. - */ - LOGIT(oldscope, '0'); - oldscope->u.count = 0; - tl = &oldscope->lock; - me = CX_THINLOCK_ID(cx); - JS_UNLOCK0(tl, me); -} - -void -js_LockObj(JSContext *cx, JSObject *obj) -{ - JSScope *scope; - - JS_ASSERT(OBJ_IS_NATIVE(obj)); - - /* - * We must test whether the GC is calling and return without mutating any - * state, especially cx->lockedSealedScope. Note asymmetry with respect to - * js_UnlockObj, which is a thin-layer on top of js_UnlockScope. - */ - if (CX_THREAD_IS_RUNNING_GC(cx)) - return; - - for (;;) { - scope = OBJ_SCOPE(obj); - if (SCOPE_IS_SEALED(scope) && scope->object == obj && - !cx->lockedSealedScope) { - cx->lockedSealedScope = scope; - return; - } - - js_LockScope(cx, scope); - - /* If obj still has this scope, we're done. */ - if (scope == OBJ_SCOPE(obj)) - return; - - /* Lost a race with a mutator; retry with obj's new scope. */ - js_UnlockScope(cx, scope); - } -} - -void -js_UnlockObj(JSContext *cx, JSObject *obj) -{ - JS_ASSERT(OBJ_IS_NATIVE(obj)); - js_UnlockScope(cx, OBJ_SCOPE(obj)); -} - -#ifdef DEBUG - -JSBool -js_IsRuntimeLocked(JSRuntime *rt) -{ - return js_CurrentThreadId() == rt->rtLockOwner; -} - -JSBool -js_IsObjLocked(JSContext *cx, JSObject *obj) -{ - JSScope *scope = OBJ_SCOPE(obj); - - return MAP_IS_NATIVE(&scope->map) && js_IsScopeLocked(cx, scope); -} - -JSBool -js_IsScopeLocked(JSContext *cx, JSScope *scope) -{ - /* Special case: the GC locking any object's scope, see js_LockScope. */ - if (CX_THREAD_IS_RUNNING_GC(cx)) - return JS_TRUE; - - /* Special case: locked object owning a sealed scope, see js_LockObj. */ - if (cx->lockedSealedScope == scope) - return JS_TRUE; - - /* - * General case: the scope is either exclusively owned (by cx), or it has - * a thin or fat lock to cope with shared (concurrent) ownership. - */ - if (scope->ownercx) { - JS_ASSERT(scope->ownercx == cx || scope->ownercx->thread == cx->thread); - return JS_TRUE; - } - return js_CurrentThreadId() == - ((JSThread *)Thin_RemoveWait(ReadWord(scope->lock.owner)))->id; -} - -#endif /* DEBUG */ -#endif /* JS_THREADSAFE */ |