/*
 * locks.c
 * Manage locking system
 *
 * Copyright (c) 1996,97 T. J. Wilkinson & Associates, London, UK.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * Written by Tim Wilkinson <tim@tjwassoc.co.uk>
 */

#define	DBG(s)
#define	FDBG(s)

#include "config.h"
#include "config-std.h"
#include "object.h"
#include "baseClasses.h"
#include "thread.h"
#include "locks.h"
#include "errors.h"
#include "exception.h"
#include "md.h"

#define	MAXLOCK		32
#define	HASHLOCK(a)	((((uintp)(a)) / sizeof(void*)) % MAXLOCK)

static struct lockList {
	quickLock	lock;
	iLock*		head;
} lockTable[MAXLOCK];

/*
 * Retrieve a machine specific (possibly) locking structure associated with
 * this address.  If one isn't found, allocate it.
 */
iLock*
newLock(void* address)
{
	struct lockList* lockHead;
	iLock* lock;
	iLock* freelock;

	freelock = 0;
	lockHead = &lockTable[HASHLOCK(address)];

	SPINON(lockHead->lock);

	for (lock = lockHead->head; lock != NULL; lock = lock->next) {
		if (lock->address == address) {
			lock->ref++;
			SPINOFF(lockHead->lock);
			return (lock);
		}
		if (lock->ref == 0 && freelock == 0) {
			freelock = lock;
		}
	}

	/* Allocate a new lock structure - use a free one if we found it */
	if (freelock != 0) {
		lock = freelock;
	}
	else {
		lock = gc_malloc(sizeof(iLock), &gcLock);
		lock->next = lockHead->head;
		lockHead->head = lock;
	}

	/* Fill in the details */
	lock->address = address;
	lock->ref = 1;
	lock->holder = NULL;
	lock->count = 0;
	MUTEX_INITIALISE(lock);
	CONDVAR_INITIALISE(lock);
	SPINOFF(lockHead->lock);
	return (lock);
}

/*
 * Retrieve a machine specific (possibly) locking structure associated with
 * this address.
 */
iLock*
getLock(void* address)
{
	struct lockList* lockHead;
	iLock* lock;

	lockHead = &lockTable[HASHLOCK(address)];

	for (lock = lockHead->head; lock != NULL; lock = lock->next) {
		if (lock->address == address) {
			break;
		}
	}
	return (lock);
}

/*
 * Free a lock if no longer in use.
 */
void
freeLock(iLock* lk)
{
	struct lockList* lockHead;
	lockHead = &lockTable[HASHLOCK(lk->address)];

	SPINON(lockHead->lock);

	/* If lock no longer in use, release it for reallocation */
	lk->ref--;
	if (lk->ref == 0) {
		assert(lk->count == 0);
		assert(lk->holder == 0);
FDBG(		printf("Freeing lock for 0x%x\n", lk->address);		)
	}

	SPINOFF(lockHead->lock);
}

/*
 * Lock a mutex.
 */
void
_lockMutex(void* addr)
{
	iLock* lk;

DBG(	printf("Lock 0x%x on 0x%x\n", currentThread, addr);		)

	lk = newLock(addr);

	if (lk->holder == CURRENTTHREAD()) {
		lk->count++;
	}
	else {
		MUTEX_LOCK(lk);
		lk->holder = CURRENTTHREAD();
		lk->count = 1;
	}
}

/*
 * Release a mutex.
 */
void
_unlockMutex(void* addr)
{
	iLock* lk;

DBG(	printf("Unlock 0x%x on 0x%x\n", currentThread, addr);		)

	lk = getLock(addr);

	assert(lk->holder == CURRENTTHREAD());
	lk->count--;
	if (lk->count == 0) {
		lk->holder = NULL;
		MUTEX_UNLOCK(lk);
	}
	freeLock(lk);
}

/*
 * Wait on a conditional variable.
 */
int
_waitCond(void* addr, jlong timeout)
{
	iLock* lk;
	int count;

DBG(	printf("Wait 0x%x on 0x%x\n", currentThread, addr);	)

	lk = getLock(addr);

	if (lk->holder != currentThread) {
		throwException(IllegalMonitorStateException);
	}

	count = lk->count;
	lk->count = 0;
	lk->holder = NULL;
	CONDVAR_WAIT(lk, timeout);
	lk->holder = CURRENTTHREAD();
	lk->count = count;
	return (0);
}

/*
 * Wake one thread on a conditional variable.
 */
void
_signalCond(void* addr)
{
	iLock* lk;

DBG(	printf("Signal 0x%x on 0x%x\n", currentThread, addr);)

	lk = getLock(addr);
	if (lk->holder != currentThread) {
		throwException(IllegalMonitorStateException);
	}

	CONDVAR_SIGNAL(lk);
}

/*
 * Wake all threads on a conditional variable.
 */
void
_broadcastCond(void* addr)
{
	iLock* lk;

DBG(	printf("Broadcast 0x%x on 0x%x 0x%x\n", currentThread, addr);	)

	lk = getLock(addr);
	if (lk->holder != currentThread) {
		throwException(IllegalMonitorStateException);
	}

	CONDVAR_BROADCAST(lk);
}
