/*  monitor.c -- basic synchronization routines for Toba */

#include <stdlib.h>
#include <assert.h>

#include "toba.h"
#include "../gc/gc.h"
#include "runtime.h"
#include "sthreads.h"

#include "java_lang_IllegalMonitorStateException.h"

/* Monitors need recursive locks and condition variables 
   to function correctly. We build them on top of simple 
   locks and conditions. */ 
struct rlock {
    struct sthread_mutex lock;
    struct mythread *thread;
    int refc;
};

struct rcond {
    struct rlock *rl; /* The associated lock */
    struct sthread_cond cond;
};


/* Lock for monitor creation */
static struct sthread_mutex monitor_create_lock;

struct monitor {
    struct rlock lock;
    struct rcond cond;
};

/* Forward variable for fast monitors */
static void monitor_stack_init();
static void push_monitor(void *monobj);
static void pop_monitor(void *monobj);

struct fastmonitor {
    void *monitornum;
};


static void
rlock_init(struct rlock *l) 
{
    sthread_mutex_init(&l->lock);
    l->thread = 0;
    l->refc = 0;
}

static void
rlock_lock(struct rlock *l, struct mythread *thr)
{
    /* First, try to get away without using any locks */
    if (l->thread == thr ) {
	/* I already hold the lock so this is ok */
	l->refc++;
	return;
    } else {
	sthread_mutex_lock(&l->lock);
	l->thread = thr;
	l->refc = 1;
    }
}

static void
rlock_unlock(struct rlock *l, struct mythread *thr) 
{
    if(l->thread != thr)
	throwMesg(&cl_java_lang_IllegalMonitorStateException.C,
		  "Thread calling monitorexit does not own monitor.");

    if (l->refc > 1) {
        l->refc--;
    } else {
        l->refc = 0;
        l->thread = 0;
        sthread_mutex_unlock(&l->lock);
    }
}

static void
rcond_init(struct rcond *c)
{
    sthread_cond_init(&c->cond);
    c->rl = NULL;
}

static void
rcond_wait(struct rcond *c, struct rlock *l, Long millis)
{
    int oldrefc = 0;
    l->thread = NULL;
    oldrefc = l->refc;
    l->refc = 0;
    c->rl = l;
    
    sthread_cond_wait(&c->cond, &l->lock, millis);
    
    l->thread = mythread();
    l->refc = oldrefc;
}

static void
rcond_signal(struct rcond *c)
{
   sthread_cond_signal(&c->cond);
}

static void
rcond_broadcast(struct rcond *c)
{
   sthread_cond_broadcast(&c->cond);
}

static struct monitor *
create_monitor_lock(void)
{
    struct monitor *ml;

    ml = (void *)allocate(sizeof(struct monitor));
    rlock_init(&ml->lock);
    rcond_init(&ml->cond);
    return ml;
}

void 
monitor_package_init()
{
    /* Threads should be running by this time, but this
       should run before other threads that could access monitors
       are created. Because of this, we don't need to set the 
       dontkill flag */
    monitor_stack_init();
}



static void 
monitorlock(struct monitor *ml, struct mythread *thr)
{
    /*assert(ml); */
    rlock_lock(&ml->lock, thr);
}

static void
monitorunlock(struct monitor *ml, struct mythread *thr)
{
    /*assert(ml);*/
    rlock_unlock(&ml->lock, thr);
}

/* atomically enter monitor and set *resp to val */
void 
monitorenter(Object obj, struct mythread *thr, int val, volatile int *resp)
{
    struct in_generic *o = (struct in_generic *)obj;
 
    if(!o)
        throwNullPointerException(0);

    if (singlethreaded) {
	push_monitor(obj);
	*resp = val;
	return;
    } else {
	sthread_dontkill_start(thr);
	if(!o->monitor) {
	    sthread_mutex_lock(&monitor_create_lock);
	    if (!o->monitor) {
		o->monitor = create_monitor_lock();
	    }
	    sthread_mutex_unlock(&monitor_create_lock);
	} 
	monitorlock(o->monitor, thr);
	*resp = val;
	sthread_dontkill_end(thr);
    }
}

/* atomically enter class monitor and set *resp to val. Now simply a 
 * call to monitorenter because of a bug fix. */
void 
enterclass(Class c, struct mythread *thr, int val, volatile int *resp)
{
    Object o = &c->classclass;
    monitorenter(o, thr, val, resp);
}

/* atomically release monitor and set *resp to val */
void 
monitorexit(Object obj, struct mythread *thr, int val, volatile int *resp)
{
    struct in_generic *o = (struct in_generic *)obj;

    if(!o)
        throwNullPointerException(0);

    if (singlethreaded) {
	pop_monitor(obj);
	*resp = val;
    } else {
	sthread_dontkill_start(thr);
	monitorunlock(o->monitor, thr);
	*resp = val;
	sthread_dontkill_end(thr);
    }
}

/* atomically release class monitor and set *resp to val. Now simply a 
 * call to monitorexit because of a bug fix. */
void 
exitclass(Class c, struct mythread *thr, int val, volatile int *resp)
{
    Object o = &c->classclass;
    monitorexit(o, thr, val, resp);
}


void
monitorwait(Object obj, Long millis)
{
    struct monitor *ml;
    struct in_generic *o = (struct in_generic *)obj;

    if (singlethreaded) {
	if (millis)
	    sthread_sleep(millis);
	else
	    throwMesg(&cl_java_lang_IllegalMonitorStateException.C,
	   "Single-threaded application deadlocked on an unconditional wait .");
    } else {

	ml = o->monitor;
	if (!ml || ml->lock.thread != mythread()) {
	    ml = NULL;
	    throwMesg(&cl_java_lang_IllegalMonitorStateException.C,
		      "Thread calling wait does not own monitor.");
	}
	
	rcond_wait(&ml->cond, &ml->lock, millis);
	ml = NULL;
    }
}

void
monitornotify(Object obj)
{
    struct monitor *ml;
    struct in_generic *o = (struct in_generic *)obj;

    if (singlethreaded)
	return;

    ml = o->monitor;
    if (!ml || ml->lock.thread != mythread()) {
        ml = NULL;
	throwMesg(&cl_java_lang_IllegalMonitorStateException.C, 
		  "Thread calling notify does not own monitor.");
    }

    rcond_signal(&ml->cond);
    ml = NULL;
}

void
monitornotifyall(Object obj)
{
    struct monitor *ml;
    struct in_generic *o = (struct in_generic *)obj;

    if (singlethreaded)
	return;

    ml = o->monitor;
    if (!ml || ml->lock.thread != mythread()) {
        ml = NULL;
	throwMesg(&cl_java_lang_IllegalMonitorStateException.C, 
		  "Thread calling notifyall does not own monitor.");
    }
    rcond_broadcast(&ml->cond);
    ml = NULL;
}


/* Stack for fast single-threaded monitors - pop_monitor has to go to
 * herculean effort to deal with the silly (but legal by the VM spec)
 * case of out-of-order monitor manipulations */

static struct fastmonitor *stackbase; /* The base of the stack*/
static struct fastmonitor *stacktop; /* The first free element on the stack */
static struct fastmonitor *stackmax; /* Just beyond the limit of the stack */

static void 
monitor_stack_init()
{
    stackbase = (struct fastmonitor *)GC_malloc(4096);
    stacktop = stackbase;
    stackmax = (struct fastmonitor *)(((char *)stackbase) + 4096);
    /* Push a bubble on the bottom of the stack to catch underflow */
    push_monitor(0);
}


static void 
grow_monitor_stack() 
{
    int stacksize = stackmax - stackbase;
    int stacktopoffset = stacktop - stackbase;
    stackbase = (struct fastmonitor *)
	GC_realloc(stackbase, stacksize * 2 * sizeof(struct fastmonitor));
    stacktop = stackbase + stacktopoffset;
    stackmax = stackbase + (stacksize * 2);
}

static void 
push_monitor(void *monobj)
{
    struct fastmonitor nm;

    assert(singlethreaded);

    nm.monitornum = monobj;

    *(stacktop++) = nm;
    if (stacktop >= stackmax)
	grow_monitor_stack();
}

static void 
pop_monitor(void *monobj) 
{
    struct fastmonitor *fm;

    assert(singlethreaded); 

    fm = stacktop - 1;
    if (monobj == fm->monitornum) {
	/* The common case! */
	stacktop--;
    } else {
	/* If the monitor we're looking for isn't on the top, they are or
	 * have been doing non-nesting monitor manipulation. This will be
	 * slow, and you deserve it for violating the intuitive nesting
	 * properties of monitors! */

	/* Keep popping while we're looking at a bubble. */
	while ((fm->monitornum == 0) && (fm >= stackbase))
	    fm = ((--stacktop) - 1);
	
	/* If the top of the stack is still not right, we need
	 * to go down in the stack and find the right monitor, 
	 * and make it a bubble, but we don't change the top-of-stack
	 * pointer */
	while ((fm->monitornum != monobj) &&
	       (fm >= stackbase)) {
	    fm--;
	}
	/* If we didn't find the monitor, then throw the appropriate error */
	if (fm >= stackbase) {
	    fm->monitornum = 0;
	} else {
	    throwMesg(&cl_java_lang_IllegalMonitorStateException.C, 
		      "Thread calling monitorexit does not own monitor.");
	}
    }
}

/* We're about to go multi-threaded - actually 
   create and lock the existing monitor locks -
   called from sthread_create */
void 
fixup_monitors()
{
    struct fastmonitor *fm, *currbase;
    struct mythread *mt = mythread();
    
    int tmp;

    assert(singlethreaded == 0);

    /* lock we should have grabbed during initialization */
    sthread_mutex_init(&monitor_create_lock);

    currbase = stackbase;
    /* The actual order we enter all of these monitors is unimportant, but
     * we actually do lock them in the order they were created. */
    while (currbase < stacktop) {
	fm = currbase++;
	if (fm->monitornum != 0) {
	    Object o = fm->monitornum;
	    monitorenter(o, mt, 0, &tmp);
	} else {
	    /* A bubble in the monitor stack because of out-of-order 
	     * monitor locking - do nothing */

            /*EMPTY*/
	}
    }
}

