/* gc-incremental.c
 * The incremental garbage collector.
 *
 * Copyright (c) 1997 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)
#define	FTDBG(s)
#define	ADBG(s)
#define	ALDBG(s)
#define	STATS

#include "config.h"
#include "config-std.h"
#include "config-mem.h"
#include "gtypes.h"
#include "gc.h"
#include "flags.h"
#include "classMethod.h"
#include "locks.h"
#include "thread.h"
#include "errors.h"
#include "exception.h"
#include "external.h"
#include "lookup.h"
#include "md.h"

static gcList white;
static gcList grey;
static gcList black;
static gcList finalise;

static bool gcRunning = false;

#if defined(STATS)

/* Class and object space statistics */
static struct {
	int	classNr;
	int	objectNr;
	int	arrayNr;
	int	methodNr;
	int	fieldNr;
	int	staticNr;
	int	dispatchNr;
	int	bytecodeNr;
	int	exceptionNr;
	int	constantNr;
	int	utf8constNr;
	int	interfaceNr;
	int	jitNr;
	int	rootNr;
	int	indirectRootNr;
	int	lockNr;

	int	fixedNr;
	int	otherNr;

	int	classMem;
	int	objectMem;
	int	arrayMem;
	int	methodMem;
	int	fieldMem;
	int	staticMem;
	int	dispatchMem;
	int	bytecodeMem;
	int	exceptionMem;
	int	constantMem;
	int	utf8constMem;
	int	interfaceMem;
	int	jitMem;
	int	rootMem;
	int	indirectRootMem;
	int	lockMem;

	int	fixedMem;
	int	otherMem;
} objectStats;

static void objectStatsChange(void*, int);
void objectStatsPrint(void);

#define	OBJECTSTATSADD(M)	objectStatsChange(M, 1)
#define	OBJECTSTATSREMOVE(M)	objectStatsChange(M, -1)
#define	OBJECTSTATSPRINT()	objectStatsPrint()

#else

#define	OBJECTSTATSADD(M)
#define	OBJECTSTATSREMOVE(M)
#define	OBJECTSTATSPRINT()

#endif

#if defined(GC_INCREMENTAL)
int32 gcAmount;
#endif

/* We run the GC after allocating 1Mbyte of memory.  If we are
 * doing this incrementally, then we will have GC our entire heap
 * by the time we've allocated this much new space.
 */
#define	ALLOCCOUNTGC	(1024*1024)

quickLock gcman;
quickLock finalman;

int gc_mode = GC_DISABLED;	/* GC will be enabled after the first
				 * thread is setup.
				 */

extern void finalizeObject(void*);
extern void walkRefArray(void*, uint32);
extern void walkClass(void*, uint32);

/* Standard GC function sets */
gcFuncs gcNormal =	 	{ walkConservative, GC_OBJECT_NORMAL };
gcFuncs gcNoWalk =		{ walkNull,	    GC_OBJECT_NORMAL };
gcFuncs gcRoot =		{ walkConservative, GC_OBJECT_ROOT   };
gcFuncs gcIndirectRoot = 	{ walkIndirectRoot, GC_OBJECT_ROOT   };
gcFuncs gcFixed =		{ walkNull,	    GC_OBJECT_FIXED  };
gcFuncs gcNormalObject =        { walkConservative, GC_OBJECT_NORMAL };
gcFuncs gcPrimArray =		{ walkNull,	    GC_OBJECT_NORMAL };
gcFuncs gcRefArray =            { walkRefArray,     GC_OBJECT_NORMAL };
gcFuncs gcClassObject =         { walkClass,        GC_OBJECT_ROOT   };
gcFuncs gcFinalizeObject =      { walkConservative, finalizeObject   };
gcFuncs gcMethod =	 	{ walkConservative, GC_OBJECT_NORMAL };
gcFuncs gcField =	 	{ walkConservative, GC_OBJECT_NORMAL };
gcFuncs gcStaticData =	 	{ walkConservative, GC_OBJECT_NORMAL };
gcFuncs gcDispatchTable = 	{ walkConservative, GC_OBJECT_NORMAL };
gcFuncs gcBytecode =		{ walkNull,	    GC_OBJECT_NORMAL };
gcFuncs gcExceptionTable = 	{ walkConservative, GC_OBJECT_NORMAL };
gcFuncs gcConstant =	 	{ walkConservative, GC_OBJECT_NORMAL };
/* gcFuncs gcUtf8Const =	{ walkConservative, GC_OBJECT_NORMAL }; */
gcFuncs gcUtf8Const =	 	{ walkConservative, GC_OBJECT_FIXED };
gcFuncs gcInterface =	 	{ walkConservative, GC_OBJECT_NORMAL };
gcFuncs gcJitCode =	 	{ walkConservative, GC_OBJECT_NORMAL };
gcFuncs gcLock =		{ walkNull,	    GC_OBJECT_FIXED  };

#define	ROOTSIZE	126
typedef struct _rootTable {
	struct _rootTable*	next;
	int			nfree;
	void*			objs[ROOTSIZE];
} rootTable;
static rootTable*	rootObjects;

struct _gcStats gcStats;
extern size_t gc_heap_total;

static void startGC(void);
static void finishGC(void);

/*
 * Initalise the Garbage Collection system.
 */
static
void
initGc(void)
{
	RESETLIST(white);
	RESETLIST(grey);
	RESETLIST(black);
	RESETLIST(finalise);

DBG(	printf("white 0x%x\n", &white);
	printf("grey 0x%x\n", &grey);
	printf("black 0x%x\n", &black);
	printf("final 0x%x\n", &finalise);		)
}

/*
 * A reference from one object to another is being created.  Handle
 * the write barrier necessary for incremental collection.
 */
void
soft_addreference(void* f, void* t)
{
	gc_block* finfo;
	gc_block* tinfo;
	int fidx;
	int tidx;

	/* Quit now for null objects */
	if (t == 0) {
		return;
	}

	finfo = GCMEM2BLOCK(f);
	tinfo = GCMEM2BLOCK(t);
	fidx = GCMEM2IDX(finfo, f);
	tidx = GCMEM2IDX(tinfo, t);

	if (GC_GET_COLOUR(finfo, fidx) == GC_COLOUR_BLACK && GC_GET_COLOUR(tinfo, tidx) == GC_COLOUR_WHITE) {
		LOCK();
		REMOVELIST(tinfo);
		APPENDLIST(grey, tinfo);
		GC_SET_COLOUR(tinfo, tidx, GC_COLOUR_GREY);
		UNLOCK();
	}
}

void
markObject(void* mem)
{
	gc_block* info;
	int idx;

	/* If object isn't part of GC, return now */
	if (gc_heap_isobject(mem) == false) {
		return;
	}

	info = GCMEM2BLOCK(mem);
	idx = GCMEM2IDX(info, mem);

	/* If object's been traced before, don't do it again */
	if (GC_GET_COLOUR(info, idx) != GC_COLOUR_WHITE) {
		return;
	}

	/* If we found a new white object, mark it as grey and
	 * move it into the grey list.
	 */
	LOCK();
	GC_SET_COLOUR(info, idx, GC_COLOUR_GREY);
	REMOVELIST(info);
	APPENDLIST(grey, info);
	UNLOCK();
}

void
scanConservative(void* base, size_t size)
{
	int8* mem;

	gcStats.markedmem += size;

	for (mem = ((int8*)base) + (size & -ALIGNMENTOF_VOIDP) - sizeof(void*); (void*)mem >= base; mem -= ALIGNMENTOF_VOIDP) {
ALDBG(		
		if (gc_heap_isobject(*(void**)mem) && ((long)mem & (sizeof(void*)-1))) {
			fprintf(stderr, "<%d>", (long)mem & (sizeof(void*)-1));
		}							)
		markObject(*(void**)mem);
	}
}

void
walkConservative(void* base, uint32 size)
{
DBG(	printf("walkConservative: %x-%x\n", base, base+size);
	fflush(stdout);							)

	scanConservative (base, size);
}

/*
 * Walk an object which cannot reference anything.
 */
void
walkNull(void* base, uint32 size)
{
        gcStats.markedmem += size;
}

/*
 * This is a root object which refers to anther object.  A root object
 * has a simple format [void* mem, size_t size].  The 'mem'
 * refers to the object, size it's length
 */
void
walkIndirectRoot(void* base, uint32 size)
{
	rootIndirect* mem;

	mem = (rootIndirect*)base;
	(*mem->funcs->walk)(mem->mem, mem->len);
}

/*
 * Walk a GC'able object.  Mark the object as black then for each pointer
 * in the object which refers to another GC object, grey the object
 * and insert into the grey list.
 */
void
walkBlock(gc_block* info)
{
	int idx;

	/* Move object to black list.  We do this before we start walking
	 * the block incase we reference another object in the same block
	 * and so move this object back onto the grey list.
	 */
	REMOVELIST(info);
	APPENDLIST(black, info);

	for (idx = 0; idx < info->nr; idx++) {
		if (GC_GET_COLOUR(info, idx) == GC_COLOUR_GREY) {
			GC_SET_COLOUR(info, idx, GC_COLOUR_BLACK);
			gcStats.markedobj += 1;
			(*GC_GET_FUNCS(info, idx)->walk)(GCBLOCK2MEM(info, idx), GCBLOCKSIZE(info));
		}
	}
}

/*
 * Walk a bit of memory.
 */
void
walkMemory(void* mem)
{
	gc_block* info;
	int idx;

	info = GCMEM2BLOCK(mem);
	idx = GCMEM2IDX(info, mem);

	REMOVELIST(info);
	APPENDLIST(black, info);
	GC_SET_COLOUR(info, idx, GC_COLOUR_BLACK);

	gcStats.markedobj += 1;
	(*GC_GET_FUNCS(info, idx)->walk)(mem, GCBLOCKSIZE(info));
}

/*
 * The Garbage Collector sits in a loop starting a collection, waiting
 * until it's finished incrementally, then tidying up before starting
 * another one.
 */
void
gcMan(void)
{
	gc_block* info;
	gc_block* ninfo;
	int idx;

	/* All threads start with interrupts disabled */
	intsRestore();

	lockMutex(&gcman);

	for(;;) {
#if defined(GC_INCREMENTAL)
		startGC();

		gcRunning = false;
		waitCond(&gcman, 0);
		assert(gcRunning == true);

		LOCK();

		/* Walk the threads.  Although we do this initially it is
		 * possible that there's an object on the thread stack which
		 * isn't referenced anywhere else but is live.
		 */
		walkLiveThreads();

		/* Walk any remaining grey objects - shouldn't be necessary
		 * unless GC has been activated explicitly.
		 */
		for (info = grey.cnext; info != &grey; info = grey.cnext) {
			walkBlock(info);
		}

		/* Now walk any white objects which will be finalized.  They
		 * may get reattached, so anything they reference must also
		 * be live just in case.
		 */
		for (info = white.cnext; info != &white; info = ninfo) {
			ninfo = info->cnext;
			for (idx = 0; idx < info->nr; idx++) {
				if (GC_GET_STATE(info, idx) == GC_STATE_NEEDFINALIZE) {
					gcStats.markedobj += 1;
					markObject(GCBLOCK2MEM(info, idx));
				}
			}
		}

		/* We may now have more grey objects, so walk them */
		for (obj = grey.cnext; obj != &grey; obj = grey.cnext) {
			walkBlock(obj);
		}

		finishGC();

		UNLOCK();
#else
		gcRunning = false;
		waitCond(&gcman, 0);
		assert(gcRunning == true);

		LOCK();

		startGC();

		for (info = grey.cnext; info != &grey; info = grey.cnext) {
			walkBlock(info);
		}

		/* Now walk any white objects which will be finalied.  They
		 * may get reattached, so anything they reference must also
		 * be live just in case.
		 */
		for (info = white.cnext; info != &white; info = ninfo) {
			ninfo = info->cnext;
			for (idx = 0; idx < info->nr; idx++) {
				if (GC_GET_STATE(info, idx) == GC_STATE_NEEDFINALIZE) {
					gcStats.markedobj += 1;
					markObject(GCBLOCK2MEM(info, idx));
				}
			}
		}
		for (info = black.cnext; info != &black; info = ninfo) {
			ninfo = info->cnext;
			for (idx = 0; idx < info->nr; idx++) {
				if (GC_GET_COLOUR(info, idx) == GC_COLOUR_WHITE && GC_GET_STATE(info, idx) == GC_STATE_NEEDFINALIZE) {
					gcStats.markedobj += 1;
					markObject(GCBLOCK2MEM(info, idx));
				}
			}
		}

		/* We may now have more grey objects, so walk them */
		for (info = grey.cnext; info != &grey; info = grey.cnext) {
			walkBlock(info);
		}

		finishGC();

		UNLOCK();

#endif

		if (flag_gc > 0) {
			fprintf(stderr, "<GC: heap %dK, total %dK, alloc %dK, marked %dK, freeing %dK>\n", gc_heap_total/1024, gcStats.totalmem/1024, gcStats.allocmem/1024, gcStats.markedmem/1024, (gcStats.totalmem > gcStats.markedmem ? (gcStats.totalmem - gcStats.markedmem)/1024 : 0));
		}
		if (flag_gc > 1) {
			OBJECTSTATSPRINT();
		}

		gcStats.totalmem -= gcStats.freedmem;
		gcStats.totalobj -= gcStats.freedobj;
		gcStats.allocobj = 0;
		gcStats.allocmem = 0;

		/* Wake up anyone waiting for the GC to finish */
		broadcastCond(&gcman);
	}
}

/*
 * Start the GC process by scanning the root and thread stack objects.
 */
static
void
startGC(void)
{
	void* obj;
	gc_block* info;
	int idx;
	int i;
	rootTable* tptr;

	gcStats.freedmem = 0;
	gcStats.freedobj = 0;
	gcStats.markedobj = 0;
	gcStats.markedmem = 0;

	/* Walk the root objects */
	for (tptr = rootObjects; tptr != 0; tptr = tptr->next) {
		for (i = 0; i < tptr->nfree; i++) {
			obj = tptr->objs[i];
			info = GCMEM2BLOCK(obj);
			idx = GCMEM2IDX(info, obj);
			GC_SET_COLOUR(info, idx, GC_COLOUR_WHITE);
			markObject(obj);
		}
	}

	/* Walk the thread objects */
	walkLiveThreads();
}

/*
 * Finish off the GC process.  Any unreached (white) objects are moved
 * for finalising and the finaliser woken.
 * The reached (black) objects are moved onto the now empty white list
 * and recoloured white.
 */
static
void
finishGC(void)
{
	gc_block* info;
	int idx;
	bool needfinalise;

	/* There shouldn't be any grey objects at this point */
	assert(grey.cnext == &grey);

	/* Any white objects should now be finalised */
	while (white.cnext != &white) {
		info = white.cnext;
		REMOVELIST(info);
		needfinalise = false;

		/* Walk block looking to see if any objects need
		 * finalizing.  If so, attach the whole block to the
		 * finalizer queue.
		 */
		for (idx = 0; idx < info->nr; idx++) {
			if (GC_GET_COLOUR(info, idx) == GC_COLOUR_WHITE) {
				if (GC_GET_STATE(info, idx) == GC_STATE_NEEDFINALIZE) {
					needfinalise = true;
				}
				else {
FDBG(					printf("freeObject %p size %d\n",
					  info, info->size);
					fflush(stdout);		)
					gcStats.freedmem += GCBLOCKSIZE(info);
					gcStats.freedobj += 1;
					OBJECTSTATSREMOVE(GCBLOCK2MEM(info, idx));
					gc_heap_free(GCBLOCK2MEM(info, idx));
				}
			}
		}
		if (needfinalise == true) {
			APPENDLIST(finalise, info);
		}
	}

	/* Now move the black objects back to the white queue for next time.
	 */
	while (black.cnext != &black) {
		info = black.cnext;
		REMOVELIST(info);
		needfinalise = false;

		for (idx = 0; idx < info->nr; idx++) {
			if (GC_GET_COLOUR(info, idx) == GC_COLOUR_BLACK) {
				GC_SET_COLOUR(info, idx, GC_COLOUR_WHITE);
			}
			else if (GC_GET_COLOUR(info, idx) == GC_COLOUR_WHITE) {
				if (GC_GET_STATE(info, idx) == GC_STATE_NEEDFINALIZE) {
					needfinalise = true;
				}
				else {
FDBG(					printf("freeObject %p size %d\n",
					  info, info->size);
					fflush(stdout);		)
					gcStats.freedmem += GCBLOCKSIZE(info);
					gcStats.freedobj += 1;
					OBJECTSTATSREMOVE(GCBLOCK2MEM(info, idx));
					gc_heap_free(GCBLOCK2MEM(info, idx));
				}
			}
		}

		if (needfinalise == true) {
			APPENDLIST(finalise, info);
		}
		else {
			APPENDLIST(white, info);
		}
	}

FTDBG(	printf("Freed %d objects of %dK\n", gcStats.freedobj,
		gcStats.freedmem/1024);					)

	/* If there's stuff to be finalised then we'd better do it */
	if (finalise.cnext != &finalise) {
		lockMutex(&finalman);
		signalCond(&finalman);
		unlockMutex(&finalman);
	}

}

/*
 * The finaliser sits in a loop waiting to finalise objects.  When a
 * new finalised list is available, it is woken by the GC and finalises
 * the objects in turn.  An object is only finalised once after which
 * it is deleted.
 */
void
finaliserMan(void)
{
	gc_block* info;
	int idx;

	/* All threads start with interrupts disabled */
	intsRestore();

	lockMutex(&finalman);

	for (;;) {
		waitCond(&finalman, 0);
		while (finalise.cnext != &finalise) {
			LOCK();
			info = finalise.cnext;
			REMOVELIST(info);
			APPENDLIST(white, info);
			UNLOCK();

			for (idx = 0; idx < info->nr; idx++) {
				if (GC_GET_STATE(info, idx) == GC_STATE_NEEDFINALIZE) {
					/* Objects are only finalised once */
					GC_SET_STATE(info, idx, GC_STATE_FINALIZED);
					GC_SET_COLOUR(info, idx, GC_COLOUR_WHITE);
					/* Call finaliser */
					(*GC_GET_FUNCS(info, idx)->final)(GCBLOCK2MEM(info, idx));
				}
			}
		}
	}
}

/*
 * Explicity invoke the garbage collecto and wait for it to complete.
 */
void
invokeGC(void)
{
	lockMutex(&gcman);
	if (gcRunning == false) {
		gcRunning = true;
		signalCond(&gcman);
	}
	waitCond(&gcman, 0);
	unlockMutex(&gcman);
}

/*
 * Allocate a new object.  The object is attached to the white queue.
 * After allocation, if incremental collection is active we peform
 * a little garbage collection.  If we finish it, we wakeup the garbage
 * collector.
 */

void throwOutOfMemory(void) __NORETURN__;

void*
gc_malloc(size_t size, gcFuncs* funcs)
{
	static int gc_init = 0;
	gc_block* info;
	rootTable* t;
	void* mem;
	int i;

	LOCK();

	/* Initialise GC */
	if (gc_init == 0) {
		gc_init = 1;
		initGc();
	}

	mem = gc_heap_malloc(size);
	if (mem == 0) {
		throwOutOfMemory();
	}
ADBG(	printf("gc_malloc: 0x%x (%d)\n", mem, size);			)

	info = GCMEM2BLOCK(mem);
	i = GCMEM2IDX(info, mem);

	gcStats.totalmem += GCBLOCKSIZE(info);
	gcStats.totalobj += 1;
	gcStats.allocmem += GCBLOCKSIZE(info);
	gcStats.allocobj += 1;

	GC_SET_FUNCS(info, i, funcs);

	OBJECTSTATSADD(mem);

	/* Determine whether we need to finalise or not */
	if ((void*)funcs->final <= GC_OBJECT_SPECIAL) {
		GC_SET_STATE(info, i, GC_STATE_NORMAL);
	}
	else {
		GC_SET_STATE(info, i, GC_STATE_NEEDFINALIZE);
	}

	/* If object is a root object, attach it to root list */
	if (funcs->final == GC_OBJECT_ROOT) {
		t = rootObjects;
		if (t == 0 || t->nfree == ROOTSIZE) {
			rootObjects = gc_calloc_fixed(sizeof(rootTable), 1);
			rootObjects->next = t;
			t = rootObjects;
		}
		t->objs[t->nfree] = mem;
		t->nfree++;
	}

	/* If object is fixed, we give it the fixed colour and do not
	 * attach it to any lists.  This object is not part of the GC
	 * regieme and must be freed explicitly.
	 */
	if (funcs->final == GC_OBJECT_FIXED) {
		GC_SET_COLOUR(info, i, GC_COLOUR_FIXED);
	}
	else {
		if (ONLIST(info)) {
			REMOVELIST(info);
		}
#if defined(GC_INCREMENTAL)
		GC_SET_COLOUR(info, i, GC_COLOUR_GREY);
		APPENDLIST(grey, info);
#else
		GC_SET_COLOUR(info, i, GC_COLOUR_WHITE);
		APPENDLIST(white, info);
#endif
	}

#if defined(GC_INCREMENTAL)
	/* Perform a little GC as we go */
	if (grey.cnext != &grey) {
		gcAmount += (gcStats.totalmem * GC_OBJECT_SIZE(obj)) / ALLOCCOUNTGC;
		while (gcAmount > 0) {
			gcAmount -= GC_OBJECT_SIZE(grey.cnext);
			walkBlock(grey.cnext);
			if (grey.cnext == &grey) {
				UNLOCK();
				invokeGC();
				goto out;
			}
		}
		UNLOCK();
		out:;
	}
	else {
		UNLOCK();
	}
#else
	UNLOCK();
#endif
	return (mem);
}

/*
 * Allocate an object which is fixed (not part of the GC)
 */
void*
gc_malloc_fixed(size_t size)
{
	return (gc_malloc(size, &gcFixed));
}

/*
 * Reallocate an object.
 */
void*
gc_realloc_fixed(void* mem, size_t size)
{
	gc_block* info;
	int idx;
	void* newmem;

	/* If nothing to realloc from, just allocate */
	if (mem == NULL) {
		return (gc_malloc_fixed(size));
	}

	info = GCMEM2BLOCK(mem);
	idx = GCMEM2IDX(info, mem);

	/* Can only handled fixed objects at the moment */
	assert(GC_GET_COLOUR(info, idx) == GC_COLOUR_FIXED);

	/* If we'll fit into the current space, just send it back */
	if (info->size >= size) {
		return (mem);
	}

	/* Allocate new memory, copy data, and free the old */
	newmem = gc_malloc_fixed(size);
	memcpy(newmem, mem, info->size);
	gc_free(mem);

	return (newmem);
}

/*
 * Explicitly free an object.
 */
void
gc_free(void* mem)
{
	gc_block* info;
	int idx;

	if (mem != 0) {
		LOCK();

		info = GCMEM2BLOCK(mem);
		idx = GCMEM2IDX(info, mem);

		/* Can only handled fixed objects at the moment */
		assert(GC_GET_COLOUR(info, idx) == GC_COLOUR_FIXED);
		assert(GC_GET_FUNCS(info, idx) == &gcFixed);

		OBJECTSTATSREMOVE(mem);

		/* Keep the stats correct */
		gcStats.totalmem -= GCBLOCKSIZE(info);
		gcStats.totalobj -= 1;

		gc_heap_free(mem);

		UNLOCK();
	}
}

/*
 * Attach a random piece of memory to the GC system.  The object is made
 * a root, which is generally what is wanted.
 */
void*
gc_attach(void* mem, size_t len, gcFuncs* funcs)
{
	rootIndirect* root;

	root = gc_malloc(sizeof(rootIndirect), &gcIndirectRoot);
	root->mem = mem;
	root->len = len;
	root->funcs = funcs;
	return (root);
}

/*
 * Reattach a different piece of memory to the GC system, replacing a
 * previous one.
 */
void*
gc_reattach(rootIndirect* root, void* mem, size_t len)
{
	root->mem = mem;
	root->len = len;
	return (root);
}

/*
 * Set a specific finaliser on some memory.
 */
void
gc_set_finalizer(void* mem, gcFuncs* funcs)
{
	gc_block* info;
	int idx;

	info = GCMEM2BLOCK(mem);
	idx = GCMEM2IDX(info, mem);

	GC_SET_STATE(info, idx, GC_STATE_NEEDFINALIZE);
	GC_SET_FUNCS(info, idx, funcs);
}

#if defined(STATS)

/* --------------------------------------------------------------------- */
/* The following functions are strictly for statistics gathering	 */

static
void
objectStatsChange(void* mem, int diff)
{
	gc_block* info;
	int idx;
	gcFuncs* funcs;
	int objdiff;
	int memdiff;

	info = GCMEM2BLOCK(mem);
	idx = GCMEM2IDX(info, mem);
	funcs = GC_GET_FUNCS(info, idx);
	objdiff = diff * 1;
	memdiff = diff * GCBLOCKSIZE(info);

#define	CHECK_GC_TYPE(GCFUNCS,STAT)				\
	if (funcs == &(GCFUNCS)) {				\
		objectStats.STAT##Nr += objdiff;		\
		objectStats.STAT##Mem += memdiff;		\
	} else

	CHECK_GC_TYPE(gcNormalObject, object)
	CHECK_GC_TYPE(gcFinalizeObject, object)
	CHECK_GC_TYPE(gcRefArray, array)
	CHECK_GC_TYPE(gcPrimArray, array)
	CHECK_GC_TYPE(gcClassObject, class)
	CHECK_GC_TYPE(gcMethod, method)
	CHECK_GC_TYPE(gcField, field)
	CHECK_GC_TYPE(gcStaticData, static)
	CHECK_GC_TYPE(gcDispatchTable, dispatch)
	CHECK_GC_TYPE(gcBytecode, bytecode)
	CHECK_GC_TYPE(gcExceptionTable, exception)
	CHECK_GC_TYPE(gcConstant, constant)
	CHECK_GC_TYPE(gcUtf8Const, utf8const)
	CHECK_GC_TYPE(gcInterface, interface)
	CHECK_GC_TYPE(gcRoot, root)
	CHECK_GC_TYPE(gcIndirectRoot, indirectRoot)
	CHECK_GC_TYPE(gcJitCode, jit)
	CHECK_GC_TYPE(gcLock, lock)
	CHECK_GC_TYPE(gcFixed, fixed)

	/* Else .... */
	{
		objectStats.otherNr += objdiff;
		objectStats.otherMem += memdiff;
	}

#undef	CHECK_GC_TYPE

}

void
objectStatsPrint(void)
{
	int cnt = 0;

#define	PRINT_GC_STAT(STAT)						    \
	fprintf(stderr, "%14.14s: Nr %6d  Mem %6dK",			    \
		#STAT, objectStats.STAT##Nr, objectStats.STAT##Mem / 1024); \
	cnt++;								    \
	if (cnt % 2 != 0) {						    \
		fprintf(stderr, "   ");					    \
	}								    \
	else {								    \
		fprintf(stderr, "\n");					    \
	}

	fprintf(stderr, "Memory statistics:\n");
	fprintf(stderr, "------------------\n");

	PRINT_GC_STAT(object);
	PRINT_GC_STAT(array);
	PRINT_GC_STAT(class);
	PRINT_GC_STAT(method);
	PRINT_GC_STAT(field);
	PRINT_GC_STAT(static);
	PRINT_GC_STAT(dispatch);
	PRINT_GC_STAT(bytecode);
	PRINT_GC_STAT(exception);
	PRINT_GC_STAT(constant);
	PRINT_GC_STAT(utf8const);
	PRINT_GC_STAT(interface);
	PRINT_GC_STAT(root);
	PRINT_GC_STAT(indirectRoot);
	PRINT_GC_STAT(jit);
	PRINT_GC_STAT(fixed);
	PRINT_GC_STAT(lock);
	PRINT_GC_STAT(other);

	if (cnt % 2 != 0) {
		fprintf(stderr, "\n");
	}
		
#undef	PRINT_GC_STAT
}

#endif
