/* Copyright 1989 GROUPE BULL -- See license conditions in file COPYRIGHT
 * Copyright 1989 Massachusetts Institute of Technology
 */
/******************************************************\
* 						       *
* reference.c:					       *
* reference count management and other storage issues  *
* 						       *
\******************************************************/

#include        "EXTERN.h"
#include 	"wool.h"

/*
 * The memory management of WOOL is implemented via a differed reference
 * count. That, each time an object's reference count attains 0, it is put in
 * the zrt, which is polled at regular intervals
 */

/************************************\
* 				     *
* Zero_reference table module (zrt)  *
* 				     *
\************************************/

/*
 * The zrt (Zero Reference Table) global structure  is used to mark ALL wobs
 * that have at any moment be of REF 0, that is either being created or via
 * decrease_reference.  Then you can call zrt_gc at strategic moments, (ie in
 * no enclosing WOOL function) to free all the zero-referenced objects in the
 * zrt.
 */

#ifdef STATS

//Er... this is an old-style function definition, right? So why does it have two types?
//WOOL_OBJECT
void zrtstats(void)
{
    wool_printf("Zero-reference-table has %d", zrt_size);
    wool_printf("/%d slots\n", zrt_limit);
}
#endif /* STATS */

void zrt_init(void)
{
    zrt_size = 0;
    zrt_limit = 63;	/* pow(2,n)/4 -1 */
    zrt = (WOOL_OBJECT *) Malloc(zrt_limit * sizeof(WOOL_OBJECT));
    zrt_last = zrt;
}

/*
 * disposes really of objects stacked in zrt. Be warned that a WOOL_free might
 * trigger zrt_put during the zrt_gc !
 */

static int WlZrtInGc = 0;

void zrt_gc(int from)
{
    WOOL_OBJECT *zrt_from = zrt + from;

    WlZrtInGc = 1;
    while (zrt_last > zrt_from) {
	zrt_last--, zrt_size--;
	if (REF(*zrt_last)) {	/* ok, graduate to normal object */
	    (*zrt_last) -> reference_count |= 1;
	} else {		/* free it */
	    WOOL_send(WOOL_free, *zrt_last, (*zrt_last));
	}
    }
    WlZrtInGc = 0;
}

/*
 * Never call zrt_put if obj was already in it (sould not happen)
 */

WOOL_OBJECT zrt_put(WOOL_OBJECT obj)
{
    WOOL_OBJECT	*old_zrt;

#ifdef DEBUG
    must_not_be_in_zrt(obj);
#endif					/* DEBUG */
    if (WlZrtInGc) {
	WOOL_send(WOOL_free, obj, (obj));
    } else {
	if(zrt_size == zrt_limit) {
	    zrt_limit = (zrt_limit + 1) * 2 -1;
	    old_zrt = zrt;
	    zrt = (WOOL_OBJECT *) Realloc(zrt, zrt_limit *
					  sizeof(WOOL_OBJECT));
	    zrt_last = zrt + (zrt_last - old_zrt);
	}
	zrt_size ++;
	obj -> reference_count = 0;
	return *zrt_last++ = obj;
    }

    //I don't think we're supposed to reach here...
    return NIL;
}

/* when an object is physically replaced by another, update its entry
 * in the zrt
 */

void zrt_replace_element(WOOL_OBJECT old, WOOL_OBJECT new)
{
    WOOL_OBJECT	*zrt_ptr = zrt;

    while (zrt_ptr < zrt_last) {
	if (*zrt_ptr == old) {
	    *zrt_ptr = new;
	    return;
	}
	zrt_ptr++;
    }
#ifdef DEBUG
    wool_error("replaced object 0x%x was not in zrt!", old);
#endif /* DEBUG */
}

#ifdef DEBUG
/* checks that the element is not in fact already in the zrt...
 */

void must_not_be_in_zrt(WOOL_OBJECT obj)
{
    WOOL_OBJECT	*zrt_ptr = zrt;

    while (zrt_ptr < zrt_last) {
	if (*zrt_ptr == obj) {
	    wool_printf("at zrt[%d]", zrt_last - zrt);
	    wool_printf(" and zrt[%d], type: ", zrt_ptr - zrt);
	    wool_print(wool_type(obj));
	    wool_puts(", obj: ");
	    wool_print(obj);
	    wool_newline();
	    wool_error("object 0x%x was already in zrt!", obj);
	}
	zrt_ptr++;
    }
}
#endif /* DEBUG	     */

/***********************\
* 		        *
* reference management  *
* 		        *
\***********************/

/* increase_reference is a macro (REF(x)++) */

#ifdef DEBUG			/* macro otherwise */
void increase_reference(WOOL_OBJECT obj) /* obj may be UNDEFINED */
{
    REF(obj) += 2;
}

void decrease_reference(WOOL_OBJECT obj) /* obj may be UNDEFINED */
{
    if (obj) {
	if (((obj -> reference_count) -= 2) == 1)
	    zrt_put(obj);
	else if (obj -> reference_count < 0) {
	    wool_print(obj);
	    wool_error(": reference_count became %d", obj ->
		       reference_count);
	}
    }
}

void decrease_reference_non_null(WOOL_OBJECT obj)  /* obj must be non-nil */
{
	if (((obj -> reference_count) -= 2) == 1)
	    zrt_put(obj);
	else if (obj -> reference_count < 0) {
	    wool_print(obj);
	    wool_error(": reference_count became %d", obj ->
		       reference_count);
	}
}
#endif /* DEBUG */

/*
 * decrease_reference_in_list:
 * decrease reference count of all the elements of the list.
 * but doesn't free the list.
 */

void decrease_reference_in_list(int count, WOOL_OBJECT *list)
{
    WOOL_OBJECT *last = list + count;

    while (list < last){
	decrease_reference(*list);
	list++;
    }
}

/*
 * duplicate an array of objects, increasing the reference count,
 * and mallocing
 */

void duplicate_n_objects(WOOL_OBJECT *source, WOOL_OBJECT **dest, int n) /* source is the array, dest is a POINTER to the array, n is how many to copy */
{
    WOOL_OBJECT *p = source, *q, *last = source + n;

    q = *dest = (WOOL_OBJECT *) Malloc(sizeof(WOOL_OBJECT) * n);
    while (p < last)
        increase_reference(*q++ = *p++);
}

/*
 * duplicate an array of objects, increasing the reference count,
 * without mallocing (dest already points to an malloced aera)
 */

void copy_n_objects(WOOL_OBJECT *source, WOOL_OBJECT *dest, int n) /* source is the array */ /* dest is  the array */  /* how many to copy */
{
    WOOL_OBJECT *p = source, *q = dest, *last = source + n;

    while (p < last)
	increase_reference(*q++ = *p++);
}


/************************************\
* 				     *
* Delayed Free table module (dft)    *
* 				     *
\************************************/

/* this table holds all memory chunks that should be freed only when at the
 * top-level
 */

#define INITIAL_DFT_SIZE 31	/* pow(2,n)/4 - 1 */

/* This table holds memory chunks that are to be freed at the top-level
 */

void dft_init(void)
{
    dft = (char **) Malloc(INITIAL_DFT_SIZE * sizeof(char *));
    dft_last = dft;
    dft_last_allocated = dft + INITIAL_DFT_SIZE;
}

/* dft_gc disposes really of memory stacked in dft. Defined as a macro
 * while (dft_last > dft) Free(*(--dft_last)) in wool.h
 */

/* put in dft for later freeing
 */

void DelayedFree(char *ptr)
{
    ASSERT(ptr != NULL);
    if(dft_last == dft_last_allocated) {
	char	**old_dft;
	int size = dft_last - dft, old_size = size;
	size = (size + 1) * 2 -1;
	old_dft = dft;
	dft = (char **) Realloc(dft, size * sizeof(char *));
	dft_last = dft + old_size;
	dft_last_allocated = dft + size;
    }
    *dft_last++ = ptr;
}
