/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape 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/NPL/ * * 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.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "Monitor.h" #include "Exceptions.h" #include "SysCallsRuntime.h" #include "prprf.h" /* Author: Bjorn Carlson (bjorn@netscape.com) This is the new implementation: Each object has one state word in which we can place information regarding the status of the lock. This might later be a word shared with other parts of the system such as the gc. At this point, we are assuming that we can use three bits. Hence; Obj: ------------ | type | ------------ | hash/gc bb| ------------ where the lock state is captured in the two bits 'bb'. The lock state is as follows: case 01: Obj is unlocked and the remaining 30 bits are used as a hash code (shifted by 2). case 11: Obj is unlocked and the remaining 30 bits index an entry in the hash code vector. If the code is 0, the object has not yet received its hash code (strings are assigned a code lazily). case 00: Obj is locked and the remaining 30 bits point to a stack frame entry containing the old contents of the object. As a special case we use the word 0 for signalling that the object is currently busy, which we use when updating its hash code. case 10: Obj is locked, some thread is waiting, and the remaining 30 bits point to the stack as above. For the specific algorithms for reading and writing hash codes, and locking and unlocking monitors, see below. When a thread is created (from inside java.lang.Thread, or externally), the initial function should be Thread::run, so that appropriate initialization takes place. When a thread dies (is interrupted), I try to catch this in the calls to NSPR which fails in such a case. At failure, I try to take the appropriate action. A thread may die, however, waiting for I/O or hanging off some NSPR lock outside our monitors. */ Monitors* monitors; inline bool Monitor::cmpAndSwap(Int32* w, Int32 ov, Int32 nv) { bool r=false; #if defined(_WIN32) && !defined(__GNUC__) __asm { mov eax,ov mov ecx, nv mov ebx, w lock cmpxchg [ebx], ecx sete r } #elif defined(__i386__) /* This actually only works 486 and better... */ { Int32 readval; __asm__ __volatile__ ("lock; cmpxchgl %3, %1; sete %0" : "=q" (r), "=m" (*w), "=a" (readval) : "r" (nv), "m" (*w), "0" (r), "2" (ov)); } #else ov; PR_fprintf(PR_STDOUT,"compare and swap NYI!!\n"); *w = nv; r = true; #endif return r; } inline Int32 Monitor::pushStack() { // for now return monitors->pushStack(); // should be something like: // __asm { // mov ecx,v // mov [esp]Context.lock,ecx // } } inline Int32 Monitor::popStack() { // for now return monitors->popStack(); // should be something like: // __asm { // mov eax,[esp]Context.lock // } } inline bool Monitor::inStack(Int32 v) { return monitors->inStack(v); // Use #ifdef HAVE_STACK_GROWING_UP // and something like: // p = beginning of current stack; // __asm { // cmp esp,v // setb r1 // mov ecx,p // cmp ecx,v // seta r2 // mov eax,r1 // and eax,r2 // } } void Monitor::staticInit() { monitors = new Monitors(10); threadInit(); } void Monitor::destroy() { delete monitors; } inline void Monitor::threadInit() { monitors->threadInit(); } // locks the monitor part of object o. /* the algorithm is as follows: get a pointer to the state slot of the object, w, and the stack entry, sp, in which to save the old value of *w. Now; 1. if *w is 0, then the monitor is busy and we spin. 2. if *w is unlocked (an odd number), then we optimistically save *w to sp, and try to swap sp into w. If this succeeds, we now own the monitor. If it fails, we loop back to 1. 3. if *w is locked, we check if we own the lock, by checking if the pointer stored in *w (v) points into our stack. If it does, we save 0 onto the stack, and return. If it doesn't, we enqueue ourselves onto the suspension list of the monitor. For more on this, see enqueue() (there is a special case when the lock is released while we are trying to suspend, which is dealt with in enqueue()). The variable 'wl' is used for keeping the waiting bit set when returning from enqueue, if we see that more threads are waiting when we wake up. Hence, or:ing 'wl' with 'sp' will keep the bit set if needed. When we return from enqueue() we loop back to 1. */ void Monitor::lock(JavaObject &o) { Int32* w; Int32 v,sp,wl=0; #ifdef DEBUG_ PR_fprintf(PR_STDOUT,"\nT%x trying to lock %x\n",PR_GetCurrentThread(),&o); #endif w = &o.state; sp = pushStack(); while (true) { v = *w; if (v == 0) continue; if ((v&Unlocked) != 0) { // unlocked // write to stack entry *(Int32*)sp = v; sp |= wl; if (cmpAndSwap(w,v,sp)) return; continue; // try again } v &= ~LockMask; if (inStack(v)) { // I own it *(Int32*)sp = 0; return; } wl = monitors->enqueue(o); } } // unlocking a monitor which we must own. // The algorithm is: /* 1. Pop the stack entry corresponding to this lock. If 0, then return. 2. Otherwise, check the state *w of the object to see if it is busy, and if so, spin. Otherwise, we check to see if we own the lock. If not, an exception is thrown. 3. finally, we try to swap back top into w, comparing with the pointer v. If this fails, it is either because the monitor is busy, or the waiting bit is set (the waiting bit resides outside the bitpattern for the pointer, and hence masking the pointer with ~LockMask, will remove the waiting bit from 'v'). Then, dequeue() is called, which will resume a thread if one exists, and release the monitor. */ void Monitor::unlock(JavaObject &o) { Int32* w; Int32 v,top; #ifdef DEBUG_ PR_fprintf(PR_STDOUT,"\nT%x unlocking %x\n",PR_GetCurrentThread(),&o); #endif w = &o.state; top = popStack(); if (top == 0) return; while (true) { v = *w; if (v == 0) continue; v &= ~LockMask; if (!inStack(v)) { sysThrowNamedException("java/lang/IllegalMonitorStateException"); // doesn't return return; } if (!cmpAndSwap(w,v,top)) monitors->dequeue(o,top); return; } } // locks the monitor part of object o for wait():s sake. // This is a replica of lock() but where we don't save to the stack (done // previously since we used to own this lock), and we don't check if we own // the monitor because we don't (having just been wakened inside wait()). // For more, see wait(). void Monitor::wlock(JavaObject &o, Int32 sp) { Int32* w; Int32 v,wl=0; #ifdef DEBUG_ PR_fprintf(PR_STDOUT,"\nT%x trying to w-lock %x\n",PR_GetCurrentThread(),&o); #endif w = &o.state; while (true) { v = *w; if (v == 0) continue; if ((v&Unlocked) != 0) { // unlocked sp |= wl; if (cmpAndSwap(w,v,sp)) return; continue; // try again } wl = monitors->enqueue(o); } } // waits for someone to notify o within 'tim'. If we don't own the monitor, // an exception is thrown. void Monitor::wait(JavaObject &o, Int64 tim) { Int32* w; Int32 v, t; if (tim < 0) { sysThrowNamedException("java/lang/IllegalArgumentException"); return; } t = (Int32)tim; w = &o.state; while (true) { v = *w; if (v == 0) continue; v &= ~LockMask; if (!inStack(v)) { sysThrowNamedException("java/lang/IllegalMonitorStateException"); return; } #ifdef DEBUG PR_fprintf(PR_STDOUT,"About to wait for %d\n",t); #endif monitors->wait(o,v,t); #ifdef DEBUG PR_fprintf(PR_STDOUT,"finished waiting\n"); #endif return; } } // notifies someone waiting on o void Monitor::notify(JavaObject &o) { Int32* w; Int32 v; w = &o.state; while (true) { v = *w; if (v == 0) continue; v &= ~LockMask; if (!inStack(v)) { sysThrowNamedException("java/lang/IllegalMonitorStateException"); return; } monitors->notify(o); return; } } // notifies everyone waiting on o void Monitor::notifyAll(JavaObject &o) { Int32* w; Int32 v; w = &o.state; while (true) { v = *w; if (v == 0) continue; v &= ~LockMask; if (!inStack(v)) { sysThrowNamedException("java/lang/IllegalMonitorStateException"); return; } monitors->notifyAll(o); return; } } // reading a hash code is done as follows: /* 1. get a pointer to the object state slot, w. 2. spin, if the monitor is busy. 3. if the monitor is locked by someone then; 3.1 if someone else owns it, make the monitor busy, and read from the other thread's stack entry or the object itself (if it was released while we are trying to read it). Then restore w back to non-busy. 3.2 if we own it, read from our stack. 4. if the hash value of the object is extended, get the value from the table. Otherwise, shift it appropriately, and return that value. */ Int32 Monitor::hashRead(JavaObject& obj) { Int32* w; Int32 v, ov; w = &obj.state; while (true) { v = *w; if (v == 0) continue; if ((v&Unlocked) == 0) { // locked by someone v &= ~LockMask; if (!inStack(v)) { // some other thread while ((ov = exchange(w,0)) == 0) // make it busy ; if ((ov&Unlocked) == 0) v = *(Int32*)(ov & ~LockMask); else v = ov; *w = ov; } else // current thread v = *(Int32*)v; } if ((v&XHash) == 0) return v>>KeyShift; else return monitors->hashGet(v); } } // writing a hash code is done as: /* 1. decide if we need to allocate an extended code, and if so, allocate an entry optimistically. 2. make the monitor busy. 3. if the monitor is locked: 3.1. if the value is still unset, update the stack entry with the new code. 3.2 If set, deallocate the new code since we don't need it then. 4. if unlocked, then update the old value. 5. finally, write back the old value. */ void Monitor::hashWrite(JavaObject& obj, Int32 k) { Int32* w; Int32 ov, v; w = &obj.state; if (k >= (1<hashAdd(k) | XHash; else k = (k<hashRemove(k); } else // unlocked ov = k; *w = ov; } Int32 Monitor::hashCode(JavaObject& obj) { Int32 v = hashRead(obj); if (v == 0) { v = monitors->nextHash(); hashWrite(obj,v); } return v; } // Monitors:: template Monitors::Monitors(int l) : pool(), cacheNext(0), taken(NULL), stateNext(1), hashNext(1), hashVecTop(0), hashVecSize(64) { int i; PRStatus stat; if (l > 10000) sysThrowNamedException("java/lang/IllegalArgumentException"); free = newList(l); _lock = PR_NewLock(); table = new PointerHashTable(pool); for (i=0; i Monitors::~Monitors() { delete table; PR_DestroyLock(_lock); } template M* Monitors::allocate(JavaObject& o) { M* m; if (free == NULL) free = newList(10); m = free; m->init(o); free = free->next; m->next = taken; m->prev = NULL; if (taken != NULL) taken->prev = m; taken = m; return m; } template void Monitors::deallocate(M* m) { if (m->prev != NULL) m->prev->next = m->next; if (m->next != NULL) m->next->prev = m->prev; if (m == taken) taken = m->next; m->next = free; free = m; } template M* Monitors::newList(int l) { M* m; M* m0; assert(l>0); m0 = m = new(pool) M; for (int i=1; inext = new(pool) M; m = m->next; } return m0; } template int Monitors::enqueue(JavaObject& o) { M* m; // enqueue does ALL the deallocation of fat monitors, so that if a monitor // is released in unlock()/dequeue() we deallocate it when waking up. // we must deal with two cases: // (i) we must suspend, and thus we might need to allocate a monitor object // (ii) the object was just released, and we thus go back to lock() and try again Int32* w = &o.state; Int32 v = *w; if (v!=0 && // not busy (v&Monitor::Unlocked) == 0 && // still locked Monitor::cmpAndSwap(w,v,v|Monitor::Waiting)) { // swap in waiting bit lock(); // now allocate a fat monitor if ((m = getObject(o)) == NULL) { m = allocate(o); addObject(o,m); } unlock(); #ifdef DEBUG PR_fprintf(PR_STDOUT,"\n T%x going to sleep on %x (%d)\n",PR_GetCurrentThread(),w,*w&Monitor::LockMask); #endif // puts current thread to sleep. Tries locking again at wakeup. // suspend and resume are sticky, so that if the lock was released while we // were trying to suspend, we return immediately (see MonitorObject::suspend()). if (!m->suspend()) { // if failure, it was interrupted if (m->empty()) { // no more state. Deallocate monitor deallocate(m); removeObject(o); } sysThrowNamedException("java/lang/InterruptedException"); return 0; // never reached } #ifdef DEBUG PR_fprintf(PR_STDOUT,"\n T%x waking up at %x (%d)\n",PR_GetCurrentThread(),w,*w&Monitor::LockMask); #endif v = 0; if (m->empty()) { // no more state. Deallocate monitor deallocate(m); removeObject(o); } else if (m->isSuspended()) v = Monitor::Waiting; // return waiting bit to lock() to preserve it return v; } return 0; } // dequeue looks for a fat monitor for o, and if one is found, it resumes it. // This sticks, so that a concurrent suspend will return immediately if the resume // beats it. Finally, the monitor is released by putting back the old value in, // which was picked up from the stack ('top' in unlock()). template void Monitors::dequeue(JavaObject& o,Int32 v) { M* m; Int32 ov; Int32* w = &o.state; do ov = *w; while (ov == 0 || !Monitor::cmpAndSwap(w,ov,v)); // release it lock(); if ((m = getObject(o)) != NULL) { m->resume(); // wake a suspended thread } else if ((ov & Monitor::Waiting) != 0) { // this is if enqueue() didn't make the lock() and suspend() before we got called m = allocate(o); addObject(o,m); m->resume(); } unlock(); } // wait() allocates a fat monitor if necessary. Then reads back the old // value from the stack, releases the monitor, resumes a suspended thread, // puts itself to sleep (which might return immediately if there is a pending // resume). At wakeup, it tries to reacquire the lock, using the still valid stack pointer. template void Monitors::wait(JavaObject& o, Int32 sp, Int32 tim) { M* m; Int32 ov, v; Int32* w; w = &o.state; v = *(Int32*)sp; do ov = *w; while (ov == 0 || !Monitor::cmpAndSwap(w,ov,v)); // release it lock(); if ((m = getObject(o)) == NULL) { m = allocate(o); addObject(o,m); } unlock(); m->resume(); // resume a suspended thread if (!m->wait(tim)) { // if failure, it was interrupted if (m->empty()) { // no more state. Deallocate monitor deallocate(m); removeObject(o); } sysThrowNamedException("java/lang/InterruptedException"); return; // never reached } Monitor::wlock(o,sp); } template void Monitors::notify(JavaObject& o) { M* m; lock(); if ((m = getObject(o)) == NULL) { m = allocate(o); addObject(o,m); } unlock(); m->notify(); return; } template void Monitors::notifyAll(JavaObject& o) { M* m; lock(); if ((m = getObject(o)) == NULL) { m = allocate(o); addObject(o,m); } unlock(); m->notifyAll(); return; } template void Monitors::addObject(JavaObject& o, M* m) { Int32* w = &o.state; keyCache[cacheNext] = &o.state; valCache[cacheNext] = m; cacheNext = (cacheNext+1)%cacheSize; table->add(reinterpret_cast(w), m); } template M* Monitors::getObject(JavaObject& o) { M* m; int i; Int32* w = &o.state; for (i=0; iget(reinterpret_cast(w),&m)) return m; else return NULL; } template void Monitors::removeObject(JavaObject& o) { int i; Int32* w = &o.state; for (i=0; iremove(reinterpret_cast(w)); } template Int32 Monitors::hashGet(Int32 i) { lock(); Int32 k = hashVec[i>>Monitor::KeyShift]; unlock(); return k; } template Int32 Monitors::hashAdd(Int32 k) { assert(k >= (1< 0; ) hashVec[i] = oldVec[i]; i = hashVecSize; hashVecSize *= 2; } unlock(); hashVec[i] = k; return i< void Monitors::hashRemove(Int32 i) { lock(); if ((i>>Monitor::KeyShift) == hashVecTop-1) hashVecTop--; unlock(); } template Int32 Monitors::nextHash() { lock(); int hN = hashNext++; unlock(); return hN; } // These are only in here until we have the stack entries for monitors // in place. template Int32 Monitors::pushStack() { Int32* a = (Int32*)PR_GetThreadPrivate(thr_stack); int i = (int)PR_GetThreadPrivate(thr_top); if (i==256) return 0; PR_SetThreadPrivate(thr_top,(void*)(i+1)); return (Int32)&a[i]; } template Int32 Monitors::popStack() { Int32* a = (Int32*)PR_GetThreadPrivate(thr_stack); int i = (int)PR_GetThreadPrivate(thr_top); PR_SetThreadPrivate(thr_top,(void*)(i-1)); return a[i-1]; } template inline bool Monitors::inStack(Int32 sp) { // for now Int32* a = (Int32*)PR_GetThreadPrivate(thr_stack); return (Int32)&a[0] <= sp && sp < (Int32)&a[256]; }