/* Copyright (C) 1997-1999 NEC Research Institute.
 * Please see the file LICENSE for license information.
 */

#include "gc.h"

#if defined(_WIN32)
#include <windows.h>
#include <sys/timeb.h>

// On Win32, we cannot free only part of a memory area.
// This feature of munmap is used when shrinking memory
// areas (the heap and the stack), so we shouldn't shrink
// anything on Win32.
#define DONT_SHRINK_MEM 1

// I _think_ this is correct - it's some constants from Delphi...

#ifndef _SH_DENYRD
#define _O_RDONLY 0
#define _O_WRONLY 1
#define _O_RDWR 2
//OF_SHARE_COMPAT  0
//OF_SHARE_EXCLUSIVE  0x10
#define _SH_DENYWR  0x20
#define _SH_DENYRD  48
#define _SH_DENYNO  0x40
//OF_PARSE  0x100
//OF_DELETE  0x200
//OF_VERIFY  0x400
//OF_CANCEL  0x800
//#define _O_CREAT  0x1000
//OF_PROMPT  0x2000
//OF_EXIST  0x4000
//OF_REOPEN  0x8000
#endif

#else
#include <sys/mman.h>
#include <sys/resource.h>
#endif

#include <fcntl.h>
#include <string.h>
#include <values.h>

#define GC_DISPLAY_HEAP FALSE
#define GC_DEBUG FALSE
#define GC_DEBUG_ALLOC FALSE
#define STRING_HEADER GC_arrayHeader(1, 0)
#define FORWARDED 0xFFFFFFFF
#define NUM_POINTERS_MASK 0x0000FFFF
#define IS_NORMAL_MASK 0x8000
#define NUM_NON_POINTERS_MASK 0x7FFF

/* Continuation objects are layed out as follows.
 * 1 word header (CONT_HEADER)
 * 1 uint size
 * 1 uint exnStack
 * size words of stack data
 */
#define CONT_HEADER 0x7FFFFFFF

#define toBytes(n) ((n) << 2)

typedef void (*pointerFun)(GC_state *s, pointer *p);

#if defined(_WIN32)
// These seems to be defined already...
// #define min(x, y) (((uint)x < (uint)y) ? (uint)x : (uint)y)
// #define max(x, y) (((uint)x > (uint)y) ? (uint)x : (uint)y)
#else
static inline uint min(uint x, uint y) {
	return ((x < y) ? x : y);
}

static inline uint max(uint x, uint y) {
	return ((x > y) ? x : y);
}
#endif

static ushort empty_GC_offsets[] = { 0 };

static void noLocals(GC_state *s) {
	s->bytesRequested = 0;
	s->localOffsets = empty_GC_offsets;
}

/* ------------------------------------------------- */
/*               Safe mmap and munmap                */
/* ------------------------------------------------- */

#if defined(_WIN32)
long winGetPageSize()
{
  SYSTEM_INFO sInfo;
  
  GetSystemInfo(&sInfo);
  
  return sInfo.dwPageSize;
}

#define mlton_getpagesize winGetPageSize
#else
#define mlton_getpagesize getpagesize
#endif


#if defined(_WIN32)
// This is from the SML/NJ source...

void *alloc_vmem(int nb)
{
  void *p;
  
  p = (void *) VirtualAlloc(NULL,
    			    nb,
    			    MEM_COMMIT|MEM_RESERVE,
    			    PAGE_EXECUTE_READWRITE);
  if (p == NULL) {
     die("VirtualAlloc failed on request of size %lx\n", nb);
  }
  return p;
}
    			    			    			              
/* free_vmem:
 * Return  memory to OS.
*/
void free_vmem (void *p)
{
  if (!VirtualFree((LPVOID)p,
                   0,
    	           MEM_RELEASE)) {
     fprintf(stderr, "VirtualFree error code: %ld\n", (long)GetLastError());
     die("unable to VirtualFree memory at %lx\n", p);
  }
}
#endif

static void *smmap(size_t length) {
	void *result;
	
#if defined(_WIN32)
        result = alloc_vmem(length);
#else	
	result = mmap(NULL, length, PROT_READ | PROT_WRITE, 
			MAP_PRIVATE | MAP_ANON, -1, 0);
#endif
	if (result == (void*)-1) die("mmap failed");
	
	return result;
}

static void smunmap(pointer base, size_t length) {
	if (0 == length)
		return;
#if defined(_WIN32)
	free_vmem(base);
#else
	if (0 != munmap(base, length))
		die("munmap failed");
#endif
}

/* ------------------------------------------------- */
/*                fromSpace, toSpace                 */
/* ------------------------------------------------- */

static inline void setLimit(GC_state *s) {
	s->limit = s->base + s->fromSize;
}

static void fromSpace(GC_state *s) {
	s->base = smmap(s->fromSize);
	if (s->fromSize > s->maxHeapSizeSeen)
		s->maxHeapSizeSeen = s->fromSize;
	setLimit(s);
}

static void toSpace(GC_state *s) {
	s->toBase = smmap(s->toSize);
	if (s->toSize > s->maxHeapSizeSeen)
		s->maxHeapSizeSeen = s->toSize;
}

/* ------------------------------------------------- */
/*                    currentTime                    */
/* ------------------------------------------------- */

/* return time as number of milliseconds */
static uint currentTime() {
	uint result;
	
#if defined(_WIN32)
	result = GetTickCount();
#else
	struct rusage ru;
	getrusage(RUSAGE_SELF, &ru);
	
	result = 0;
	result += 1000 * ru.ru_utime.tv_sec;
	result += 1000 * ru.ru_stime.tv_sec;
	result += ru.ru_utime.tv_usec / 1000;
	result += ru.ru_stime.tv_usec / 1000;
#endif

	return result;
}

/* ------------------------------------------------- */
/*                     roundPage                     */
/* ------------------------------------------------- */

/*
 * Round size up to a multiple of the size of a page.
 */
size_t roundPage(size_t size)
{
	static size_t	psize;

	psize = mlton_getpagesize();

	size += psize - 1;
	size -= size % psize;
	return (size);
}

/* ------------------------------------------------- */
/*                  computeHeapSize                  */
/* ------------------------------------------------- */

/* returns min(s->maxHeapSize, live * ratio).
 * It is careful not to overflow when doing the multiply.
 * It should only be called when not s->useFixedHeap, since s->maxHeapSize is
 * only set in that case.
 */
static uint computeHeapSize(GC_state *s, uint live, uint ratio) {
	ullong needed;

	assert(not s->useFixedHeap);

	needed = ((ullong)live) * ratio;
	return ((needed > s->maxHeapSize)
		? s->maxHeapSize
		: roundPage(needed));
}

/* ------------------------------------------------- */
/*               extractArrayNumBytes                */
/* ------------------------------------------------- */

/* The number of bytes in an array, not including the header. */
static inline uint extractArrayNumBytes(pointer p, 
					 uint numPointers,
					 uint numNonPointers) {
	uint numElements, bytesPerElement, result;
	
	numElements = GC_arrayNumElements(p);
	bytesPerElement = numNonPointers + toBytes(numPointers);
	result = wordAlign(numElements * bytesPerElement);
	
	return result;
}

/* WARNING:
 * foreachGlobal, foreachLocal, foreachPointerInStack, foreachPointerInHeap
 * may apply their argument function f to values that are not pointers.  These
 * values will always be nonzero mod 4, and should be first checked by f using 
 * GC_isPointer.
 */
/* ------------------------------------------------- */
/*                   foreachGlobal                   */
/* ------------------------------------------------- */

/* Apply f to each global pointer into the heap. */
static void foreachGlobal(GC_state *s, pointerFun f) {
	int i;

 	for (i = 0 ; i < s->numGlobals ; ++i)
	       	f(s, &(s->globals[i]));
}

/* ------------------------------------------------- */
/*                   foreachLocal                    */
/* ------------------------------------------------- */

/* Apply f to each local pointer into the heap. */
static void foreachLocal(GC_state *s, pointerFun f) {
	int i;

	for (i = 0 ; i < s->localOffsets[0] ; ++i)
		f(s, &(s->locals[s->localOffsets[i + 1]]));
}

/* ------------------------------------------------- */
/*               foreachPointerInStack               */
/* ------------------------------------------------- */

/* Apply f to each pointer from the stack to the heap. */
static void foreachPointerInStackGen(GC_state *s, pointer bottom, pointer top,
					pointerFun f) {
	int i;

	while (top >= bottom) {
		/* inv: top points at a frame index */
		uint frameIndex;
		GC_frameLayout *layout;
		GC_offsets frameOffsets;

		frameIndex = *(uint*) top;
#if GC_DEBUG
		fprintf(stderr, "frame index %d.\n", frameIndex);
#endif
		assert(frameIndex < s->maxFrameIndex);
		layout = &(s->frameLayouts[frameIndex]);
		frameOffsets = layout->offsets;
		top -= layout->numBytes;

		for (i = 0 ; i < frameOffsets[0] ; ++i) {
#if GC_DEBUG
			fprintf(stderr, "frame offset %d.\n", frameOffsets[i + 1]);
#endif
			f(s, (pointer*)(top + frameOffsets[i + 1]));
		}
	}

	assert(top == bottom - WORD_SIZE);
}

static void foreachPointerInStack(GC_state *s, pointerFun f) {
	foreachPointerInStackGen(s, s->stackBottom, s->stackTop, f);
}

/* ------------------------------------------------- */
/*               foreachPointerInHeap                */
/* ------------------------------------------------- */

/* Apply f to each pointer between s->front and s->back, which should be a 
 * contiguous sequence of objects, where s->front points at a header word 
 * and s->back points just past the end of the last object.
 * f may increase s->back (for example, this is done by forward).
 */
static void foreachPointerInHeap(GC_state *s, pointerFun f) {
	pointer front;

	front = s->front;

 	while (front < s->back) {
		/* Invariant: front always points at the next header word. */
		word header;
		uint numPointers;
		uint numNonPointers;
		bool isArray = FALSE;
		
		header = *(word*)front;

		front += WORD_SIZE;
		
		if (not (header & HIGH_BIT)) { 
			isArray = TRUE;
			if (CONT_HEADER != header) {
				/* Looking at an array, the header is here. */
				header = *(word*)front;
				front += WORD_SIZE;
			}
		} 

		/* front points at beginning of object data */
    
		/* extract header components */
		numPointers = header & NUM_POINTERS_MASK;
		numNonPointers = 
			(header >> NON_POINTERS_SHIFT) & NUM_NON_POINTERS_MASK;

		if (isArray) {
			if (CONT_HEADER == header) { /* It's a continuation. */
				uint size;
				
				size = *(uint*)front;
				front += 2 * WORD_SIZE;
				foreachPointerInStackGen
					(s, front, front + size - WORD_SIZE, f);
				front += size;
			} else { /* It's really an array */
				uint numBytes;

				numBytes = extractArrayNumBytes(front, numPointers, numNonPointers);

				if (numBytes == 0) { 
					/* An empty array -- skip the POINTER_SIZE bytes
					 * for the forwarding pointer.
					 */
					front += POINTER_SIZE;
				} else {
					pointer max;
	
					max = front + numBytes;
		
					if (numPointers == 0) {
					/* There are no pointers, just update the front
					 * of the queue.
					 */
						front = max;
					} else if (numNonPointers == 0) { 
					  	/* It's an array with only pointers. */
						for (; front < max; front += POINTER_SIZE)
							f(s, (pointer*)front);
					} else {
						uint numBytesPointers;
						
						numBytesPointers = toBytes(numPointers);

						/* for each array element */
						while (front < max) {
							pointer max2;
							front += numNonPointers;
							max2 = front + numBytesPointers;
							/* Forward all internal pointers. */
							for ( ; front < max2 ; front += POINTER_SIZE) 
								f(s, (pointer*)front);
						}
					}
					assert(front == max);
				}
			}
		} else {
			/* normal object */
			pointer max;

			front += toBytes(numNonPointers);
			max = front + toBytes(numPointers);
      
			/* Forward all internal pointers. */
			for ( ; front < max ; front += POINTER_SIZE) 
				f(s, (pointer*)front);
		}
	} /* end while (front < s->back) */

	s->front = front;
	assert(s->back == s->front);
}

/* ------------------------------------------------- */
/*                     invariant                     */
/* ------------------------------------------------- */

#ifndef NODEBUG
static bool isInFromSpace(GC_state *s, pointer p) {
	return (not(GC_isPointer(p)) 
		or (s->base <= p and p < s->base + s->fromSize));
}

static void assertIsInFromSpace(GC_state *s, pointer *p) {
	assert(isInFromSpace(s, *p));
}

static bool isInToSpace(GC_state *s, pointer p) {
	return (not(GC_isPointer(p))
		or (s->toBase <= p and p < s->toBase + s->toSize));
}

static bool invariant(GC_state *s) {
	/* would be nice to add divisiblity by pagesize of various things */

	/* Frame layouts */
	{
		int i;
		for (i = 0; i < s->maxFrameIndex; ++i) {
			GC_frameLayout *layout;

 			layout = &(s->frameLayouts[i]);

			if (layout->numBytes > 0) {
				GC_offsets offsets;
				int j;

				assert(layout->numBytes <= s->maxFrameSize);
				offsets = layout->offsets;
				for (j = 0; j < offsets[0]; ++j)
					assert(offsets[j + 1] < layout->numBytes);
			}
		}
	}

	/* Heap and stack. */
	assert(s->base <= s->frontier);
	assert(0 == s->fromSize or s->frontier < s->limit);
	assert(s->limit == s->base + s->fromSize);
	assert(s->useFixedHeap or (s->fromSize <= s->maxHeapSize
	                           and s->toSize <= s->maxHeapSize));
	assert(s->toBase == NULL or s->toSize == s->fromSize);
	assert(s->stackBottom - WORD_SIZE <= s->stackTop
		and s->stackTop < s->stackBottom + s->stackSize);
	assert(s->stackLimit == s->stackBottom + s->stackSize - s->maxFrameSize);

	/* Check that all pointers are into from space. */
	foreachLocal(s, assertIsInFromSpace);
	foreachGlobal(s, assertIsInFromSpace);
	foreachPointerInStack(s, assertIsInFromSpace);
	s->front = s->base;
	s->back = s->frontier;
	foreachPointerInHeap(s, assertIsInFromSpace);

 	/* check the exception stack */
	{
		uint offset;

		for (offset = s->exnStack; offset > 0; ) {
			assert(s->stackBottom + offset 
					<= s->stackTop + WORD_SIZE);
			offset = *(uint*)(s->stackBottom + offset + WORD_SIZE);
		}
	}

	return TRUE;
}
#endif

/* ------------------------------------------------- */
/*                   allocateStack                   */
/* ------------------------------------------------- */

static void setStackLimit(GC_state *s) {
	s->stackLimit = s->stackBottom + s->stackSize - s->maxFrameSize;
}

static void allocateStack(GC_state *s) {
	s->stackBottom = smmap(s->stackSize);
	setStackLimit(s);
}	

/* ------------------------------------------------- */
/*                  translateStack                   */
/* ------------------------------------------------- */

/* Update stackTop for a stack that has moved from 
 * s->stackBottom == old to the current s->stackBottom.
 * The code is careful to use a uint for the difference and not an int so that 
 * moves over arbitrarily large differences in the address space are allowed.
 */
static void translateStack(GC_state *s, pointer old) {
	if (s->stackBottom > old)
		s->stackTop += (s->stackBottom - old);
	else
		s->stackTop -= (old - s->stackBottom);
}

/* ------------------------------------------------- */
/*                 maybeShrinkStack                  */
/* ------------------------------------------------- */

#define STACK_SHRINK_THRESHOLD 4
#define STACK_SHRINK_FACTOR 2

void maybeShrinkStack(GC_state *s) {
	uint size;
#if defined(DONT_SHRINK_MEM)
// We never shrink memory if this flag is defined
#else
	size = s->stackTop + WORD_SIZE - s->stackBottom;

	if (size * STACK_SHRINK_THRESHOLD < s->stackSize
		and s->stackSize > mlton_getpagesize()) {
		uint keep;

		keep = roundPage(max(size, s->stackSize / STACK_SHRINK_FACTOR));
		if (keep < s->stackSize) {
			if (GC_DEBUG or s->messages)
				fprintf(stderr, "Shrinking stack to size %u.\n\n\n\n\n\n\n\n\n\n\n",
					keep);
			assert(s->stackTop < s->stackBottom + keep);
			smunmap(s->stackBottom + keep, s->stackSize - keep);
			s->stackSize = keep;
			setStackLimit(s);
		}
	}
#endif //DONT_SHRINK_MEM
}

/* ------------------------------------------------- */
/*                   GC_growStack                    */
/* ------------------------------------------------- */

#define STACK_GROW_FACTOR 2
void GC_growStack(GC_state *s) {
	pointer old;
	uint oldSize, size;

	noLocals(s);
	assert(invariant(s));
	old = s->stackBottom;
	oldSize = s->stackSize;
	size = s->stackTop + WORD_SIZE - s->stackBottom;
	s->stackSize *= STACK_GROW_FACTOR;
	if (GC_DEBUG or s->messages)
		fprintf(stderr, "Growing stack to size %u.\n", s->stackSize);
	allocateStack(s);
	memcpy(s->stackBottom, old, size);
	smunmap(old, oldSize);
	translateStack(s, old);

	assert(invariant(s));
}

/* ------------------------------------------------- */
/*                   initCounters                    */
/* ------------------------------------------------- */

static void initCounters(GC_state *s) {
 	/* The next 5 are gc-summary statistics */
	s->startTime = currentTime();
	s->bytesAllocated = 0;
	s->bytesCopied = 0;
	s->numGCs = 0;
	s->gcTime = 0;
	s->maxHeapSizeSeen = 0;
	s->maxBytesLive = 0;

	/* The next 3 are for heap resizing */
	s->minLive = 20;
	s->maxLive = 3;
	/* set liveRatio (close) to the geometric mean of minLive and maxLive */
	{ 
		uint i;
		for (i = s->maxLive; i * i <= s->minLive * s->maxLive; ++i) ;
		s->liveRatio = i;
	}
}

/* ------------------------------------------------- */
/*                    getRAMsize                     */
/* ------------------------------------------------- */

/* RAM size is multiplied by this so that we don't use up all the memory */
#define RAMslop 0.95

/* Get RAM size from /proc/meminfo.  Very Linux specific. 
 *
 * /proc/meminfo looks like the following.
 *
 *        total:    used:    free:  shared: buffers:  cached:
 * Mem:  263462912 251154432 12308480 19861504 52588544 122769408
 * Swap: 131567616    53248 131514368
 * MemTotal:    257288 kB
 */
static uint getRAMsize() {
	FILE *file;
	char c;
	char buf[80];
	uint n;

#if defined(_WIN32)
        MEMORYSTATUS mStatus;
        
        GlobalMemoryStatus(&mStatus);

	n = mStatus.dwTotalVirtual;
	// We could also use mStatus.dwTotalPhys;
#else
	file = sopen("/proc/meminfo", "r");
	while ((c = fgetc(file)) != '\n') ;
	fgets(buf, sizeof("Mem:"), file);
	while ((c = fgetc(file)) == ' ') ;
	n = 0;
	do n = n * 10 + (int)(c - '0');
	while ((c = fgetc(file)) != ' ');
	fclose(file);
#endif
	return roundPage((uint)((double)n * RAMslop));
}

/* ------------------------------------------------- */
/*                   setHeapParams                   */
/* ------------------------------------------------- */

/* set fromSize and maybe maxHeapSize, depending on whether useFixedHeap.
 * size must not be an approximation, because setHeapParams will die if it
 * can't set fromSize big enough.
 */
void setHeapParams(GC_state *s, uint size) {
	if (s->useFixedHeap) {
		if (0 == s->fromSize)
			s->fromSize = getRAMsize();
	        s->fromSize = roundPage(s->fromSize / 2);
	} else {
		if (0 == s->maxHeapSize) 
			s->maxHeapSize = getRAMsize();
		s->maxHeapSize = roundPage(s->maxHeapSize / 2);
		s->fromSize = computeHeapSize(s, size, s->liveRatio);
	}
	if (size > s->fromSize)
		die("Out of memory.");
}

/* ------------------------------------------------- */
/*                      GC_init                      */
/* ------------------------------------------------- */

void GC_init(GC_state *s) {
	initCounters(s);
	noLocals(s);
	setHeapParams(s, s->bytesLive);
	{
		int i;

		for (i = 0; i < s->numGlobals; ++i)
			s->globals[i] = (pointer)0x1;
	}
	fromSpace(s);
	s->frontier = s->base;
	s->toSize = s->fromSize;
	toSpace(s);
	/* There is initially no exception stack, so we store a terminal
         * bogus offset of 0.
	 */
	s->exnStack = 0;
	/* The initial stack size must be big enough so that doubling it will
	 * always make enough space for the largest frame that needs pushed.
 	 */
	s->stackSize = max(mlton_getpagesize(), roundPage(s->maxFrameSize));
	allocateStack(s);
	/* stackTop points at the last spot in use */
	s->stackTop = s->stackBottom - WORD_SIZE ;

	assert(invariant(s));
}

/* ------------------------------------------------- */
/*                      forward                      */
/* ------------------------------------------------- */

/* Forward the object pointed to by *pp.
 * Update *pp to point to the new object. 
 */
static inline void forward(GC_state *s, pointer *pp) {
	pointer p;

	p = *pp;
	assert(isInFromSpace(s, p));

 	if (GC_isPointer(p)) {
		word header;

		/* The header is always one word before the object. */
		header = *(word*)(p - WORD_SIZE);
		if (header == FORWARDED) 
			/* Already forwarded, just get the forwarding pointer. */
			*pp = *(pointer*)p;
		else { 
			/* forward the object */
	 		uint numPointers, numNonPointers;
			bool isNormal;
			pointer objectStart;
			uint objectBytes, headerBytes;
			
			/* extract header components */
			numPointers = header & NUM_POINTERS_MASK;
			numNonPointers = header >> NON_POINTERS_SHIFT;
			isNormal = numNonPointers & IS_NORMAL_MASK;
			numNonPointers = numNonPointers & NUM_NON_POINTERS_MASK;
	
			/* set headerBytes and objectBytes */
			if (isNormal) {
				headerBytes = GC_OBJECT_HEADER_SIZE;
				objectBytes = toBytes(numPointers + numNonPointers);
			} else if (CONT_HEADER == header) {
				headerBytes = WORD_SIZE;
				objectBytes = 2 * WORD_SIZE + *(uint*)p;
			} else { /* array */
				headerBytes = GC_ARRAY_HEADER_SIZE;
				objectBytes = extractArrayNumBytes(p, numPointers,
									 numNonPointers);
	 			/* Empty arrays have POINTER_SIZE bytes for the 
				 * forwarding pointer.
				 */
				if (objectBytes == 0) objectBytes = POINTER_SIZE;
			} 
	
			objectStart = p - headerBytes;
	
			/* copy the object */
			memcpy(s->back, objectStart, headerBytes + objectBytes);
	
	 		/* set the forwarding pointer and update s->back */
			*(word*)(p - WORD_SIZE) = FORWARDED;
			s->back += headerBytes;
			*pp = s->back;
			*(pointer*)p = s->back;
			s->back += objectBytes;
		}
	}

	assert(isInToSpace(s, *pp));
}

/* ------------------------------------------------- */
/*                  transitiveCopy                   */
/* ------------------------------------------------- */

static void transitiveCopy(GC_state *s) {
#if GC_DEBUG
	fprintf(stderr, "Transitive copy.\n");
#endif
	foreachPointerInHeap(s, forward);
}

/* ------------------------------------------------- */
/*                     startGC                       */
/* ------------------------------------------------- */

static void startGC(GC_state *s) {
 	assert(invariant(s));

	if (GC_DEBUG or s->messages)
		fprintf(stderr, "Starting gc.\n");

 	s->recentStart = currentTime();

	if (not s->useFixedHeap) {
		uint needed;

		needed = computeHeapSize(s, s->bytesLive + s->bytesRequested,
						s->liveRatio);

		/* toSpace must be at least as big as fromSpace */
		if (needed < s->fromSize)
			needed = s->fromSize;

		/* Massage toSpace so that it is of needed size. */
		if (s->toBase != NULL) {
			 if (s->toSize < needed) {
				if (GC_DEBUG or s->messages)
					fprintf(stderr, "Unmapping toSpace\n");
				smunmap(s->toBase, s->toSize);
				s->toBase = NULL;
			 } else {
#if defined(DONT_SHRINK_MEM)
// We never shrink memory if this flag is defined
				needed = s->toSize;
#else
				uint delete;

				delete = s->toSize - needed;
				if (GC_DEBUG or s->messages)
					fprintf(stderr, "Shrinking toSpace by %u\n", delete);
				smunmap(s->toBase + needed, delete);
#endif //DONT_SHRINK_MEM
			 }
		}
		s->toSize = needed;
		if (NULL == s->toBase)
			toSpace(s);
	}

 	s->numGCs++;
 	s->bytesAllocated += s->frontier - s->base - s->bytesLive;
	s->front = s->toBase;
	s->back = s->toBase;

	if (GC_DEBUG or s->messages)
		fprintf(stderr, 
			"toSpace is %s bytes.\n",
			uintToCommaString(s->toSize));
}

/* ------------------------------------------------- */
/*                     finishGC                      */
/* ------------------------------------------------- */

void finishGC(GC_state *s) {
	uint gcTime;
	uint size;

	maybeShrinkStack(s);

#if GC_DEBUG
	fprintf(stderr, "Forwarding globals.\n");
#endif
	foreachGlobal(s, forward);

#if GC_DEBUG
	fprintf(stderr, "Forwarding stack\n");
#endif
	foreachPointerInStack(s, forward);

#if GC_DEBUG
	fprintf(stderr, "Forwarding %d locals.\n", s->localOffsets[0]);
#endif
	foreachLocal(s, forward);

	transitiveCopy(s);

	size = s->fromSize;

	/* Swap fromSpace and toSpace. */
	{
		pointer tmp;
		tmp = s->base;
		s->base = s->toBase;
		s->toBase = tmp;
	}
	{
		uint tmp;
		tmp = s->fromSize;
		s->fromSize = s->toSize;
		s->toSize = tmp;
	}

	s->frontier = s->back;
	s->bytesLive = s->frontier - s->base;
	if (s->bytesLive > s->maxBytesLive)
		s->maxBytesLive = s->bytesLive;

	/* Resize heap, if necessary. */
	if (not s->useFixedHeap) {
		uint needed;

		needed = s->bytesLive + s->bytesRequested;
#if defined(DONT_SHRINK_MEM)
// We never shrink memory if this flag is defined
#else
		if (computeHeapSize(s, needed, s->minLive) < s->fromSize) {
			/* shrink heap */
			uint keep;

			keep = computeHeapSize(s, needed, s->liveRatio);
			if (GC_DEBUG or s->messages)
				fprintf(stderr, "Shrinking heap to %u bytes.\n", keep);
			assert(keep <= s->fromSize);
			smunmap(s->base + keep, s->fromSize - keep);
			s->fromSize = keep;
		}
#endif //DONT_SHRINK_MEM
	
		if ((s->toSize < s->fromSize)
		    or (computeHeapSize(s, needed, s->maxLive) > s->fromSize)) {
			/* prepare to allocate new toSpace at next GC */
			smunmap(s->toBase, s->toSize);
			s->toBase = NULL;
		}
#if defined(DONT_SHRINK_MEM)
// We never shrink memory if this flag is defined
#else
		else {
		        /* shrink toSpace so that s->toSize == s->fromSize */
			smunmap(s->toBase + s->fromSize, s->toSize - s->fromSize);
	 		s->toSize = s->fromSize;
		}
#endif //DONT_SHRINK_MEM
	}

	setLimit(s);

	s->bytesCopied += s->bytesLive;
	gcTime = currentTime() - s->recentStart;
	s->gcTime += gcTime;

	if (GC_DEBUG or s->messages) {
		fprintf(stderr, "Finished gc.\n");
		fprintf(stderr, "time(ms): %s\n", intToCommaString(gcTime));
		fprintf(stderr, "live(bytes): %s (%.1f%%)\n", 
			intToCommaString(s->bytesLive),
			100.0 * ((double) s->bytesLive) / size);
	}

	assert(invariant(s));

	if (s->frontier + s->bytesRequested >= s->limit) {
		if (s->useFixedHeap or s->fromSize == s->maxHeapSize) 
			die("Out of memory.");
		else GC_gc(s);
	}

	assert(s->frontier + s->bytesRequested < s->limit);
}

/* ------------------------------------------------- */
/*                       GC_gc                       */
/* ------------------------------------------------- */

void GC_gc(GC_state *s) {
	startGC(s);
	finishGC(s);
}

/* ------------------------------------------------- */
/*                      GC_size                      */
/* ------------------------------------------------- */

uint GC_size(GC_state *s, pointer root) {
	uint size;

	noLocals(s);
	startGC(s);
	forward(s, &root);
	transitiveCopy(s);
	size = s->back - s->toBase;
	finishGC(s);
	
	return size;
}

/* ------------------------------------------------- */
/*                 GC_createStrings                  */
/* ------------------------------------------------- */

void GC_createStrings(GC_state *s, struct GC_stringInit inits[]) {
	pointer frontier;
	int i;

	assert(invariant(s));
	
	frontier = s->frontier;
	
	for(i = 0; inits[i].str != NULL; ++i) {
		uint numElements, numBytes;

		numElements = inits[i].size;
		numBytes = GC_ARRAY_HEADER_SIZE
			+ ((0 == numElements) 
				? POINTER_SIZE 
				: wordAlign(numElements));

		if (frontier + numBytes >= s->limit)
			die("Unable to allocate string constant \"%s\".", inits[i].str);
	
		*(word*)frontier = numElements;
		*(word*)(frontier + WORD_SIZE) = STRING_HEADER;
		s->globals[inits[i].globalIndex] = frontier + GC_ARRAY_HEADER_SIZE;
		{
			int j;

			for (j = 0; j < numElements; ++j)
				*(frontier + GC_ARRAY_HEADER_SIZE + j) 
					= inits[i].str[j];
		}
		frontier += numBytes;
	}
	s->frontier = frontier;

	assert(invariant(s));
}

/* ------------------------------------------------- */
/*                      GC_done                      */
/* ------------------------------------------------- */

static void displayUint(string name, uint n) {
	fprintf(stderr, "%s: %s\n", name, uintToCommaString(n));
}

static void displayUllong(string name, ullong n) {
	fprintf(stderr, "%s: %s\n", name, ullongToCommaString(n));
}

void GC_done(GC_state *s) {
	noLocals(s);
	assert(invariant(s));
	smunmap(s->stackBottom, s->stackSize);
	smunmap(s->base, s->fromSize);
	if (s->toBase != NULL) smunmap(s->toBase, s->toSize);

	if (s->summary) {
		double time;

		displayUint("stack size(bytes)", s->stackSize);
		displayUint("max semispace size(bytes)", s->maxHeapSizeSeen);
		time = (double)(currentTime() - s->startTime);
		fprintf(stderr, "GC time(ms): %s (%.1f%%)\n",
			intToCommaString(s->gcTime), 
			(0.0 == time) ? 0.0 
			: 100.0 * ((double) s->gcTime) / time);
		displayUint("number of GCs", s->numGCs);
		displayUllong("bytes allocated",
	 			s->bytesAllocated 
				+ (s->frontier - s->base - s->bytesLive));
		displayUllong("bytes copied", s->bytesCopied);
		displayUint("max bytes live", s->maxBytesLive);
	}	
}

/* ------------------------------------------------- */
/*                   GC_saveWorld                    */
/* ------------------------------------------------- */

/* TERMINATOR is used to separate the human readable messages at the beginning
 * of the world file from the machine readable data.
 */
#define TERMINATOR '\000'

void GC_saveWorld(GC_state *s, 
			uint magic,
			pointer fileName,
			void (*saveGlobals)(FILE *file)) {
	FILE *file;

	assert(invariant(s));

	/* The sopen must happen before the gc, because the GC will invalidate
 	 * the fileName pointer. 
	 */
#if defined(_WIN32)
	// NO! Not the windows shared open function... 
	                             // _O_CREAT
	//file = sopen((char*)fileName, _O_WRONLY, _SH_DENYWR);
	// sopen just stands for safe open here...
	file = safeopen((char*)fileName, "w");
#else
	file = sopen((char*)fileName, "w");
#endif

	/* Compact the heap into fromSpace */
	noLocals(s);
	GC_gc(s);

	fprintf(file, "Heap file created by MLton.\nbase = %x\nfrontier = %x\nstackBottom = %x\nstackTop = %x\nmaxHeapSize = %u\n", 
		(uint)s->base,
		(uint)s->frontier,
		(uint)s->stackBottom,
		(uint)s->stackTop,
		s->maxHeapSize);

 	fputc(TERMINATOR, file);

	swriteUint(magic, file);
	swriteUint((uint)s->base, file);
	swriteUint((uint)s->frontier, file);
	swriteUint((uint)s->stackBottom, file);
	swriteUint((uint)s->stackTop, file);
	swriteUint(s->exnStack, file);
	swriteUint((uint)s->useFixedHeap, file);
	swriteUint(s->fromSize, file);
	swriteUint(s->maxHeapSize, file);

	swrite(s->stackBottom, 1,
		s->stackTop - s->stackBottom + WORD_SIZE,
		file);
 	swrite(s->base, 1, s->frontier - s->base, file);
	(*saveGlobals)(file);

	fclose(file);
}

/* ------------------------------------------------- */
/*                   translateHeap                   */
/* ------------------------------------------------- */

static int translateDirection;
static uint translateDiff;

void translatePointer(GC_state *s, pointer *p) {
	if (GC_isPointer(*p)) {
		if (1 == translateDirection)
			*p += translateDiff;
		else
			*p -= translateDiff;
	}
}

/* Translate all pointers to the heap from within the stack and the heap for
 * a heap that has moved from s->base == old to s->base.
 */
void translateHeap(GC_state *s, pointer old) {
	if (s->base == old)
		return;
	else if (s->base > old) {
		translateDiff = s->base - old;
		translateDirection = 1;
	} else {
		translateDiff = old - s->base;
		translateDirection = -1;
	}

	/* Translate globals. */
	{
		int i;
		for (i = 0; i < s->numGlobals; ++i)
			translatePointer(s, &(s->globals[i]));
	}

	/* Translate stack. */
	foreachPointerInStack(s, translatePointer);

	/* Translate heap. */
	s->front = s->base;
	s->back = s->frontier;
	foreachPointerInHeap(s, translatePointer);
}

/* ------------------------------------------------- */
/*                   GC_loadWorld                    */
/* ------------------------------------------------- */

void GC_loadWorld(GC_state *s, 
			uint magic,
			char *fileName,
			bool heapSizeCommandLine,
			void (*loadGlobals)(FILE *file)) {
	FILE *file;
	uint heapSize, fromSize, maxHeapSize, magic2;
	pointer base, frontier, stackBottom;
 	bool useFixedHeap;
	char c;
	
	initCounters(s);
	
#if defined(_WIN32)
	//file = sopen((char*)fileName, _O_RDONLY, _SH_DENYNO);
	file = safeopen((char*)fileName, "r");
#else
	file = sopen((char*)fileName, "r");
#endif
	
	until ((c = fgetc(file)) == TERMINATOR or EOF == c);
	
	if (EOF == c) die("Invalid world.");
	
	magic2 = sreadUint(file);
	unless (magic == magic2)
		die("Invalid world: wrong magic number.");
	base = (pointer)sreadUint(file);
	frontier = (pointer)sreadUint(file);
	heapSize = frontier - base;
	s->bytesLive = heapSize;
	stackBottom = (pointer)sreadUint(file);
	s->stackTop = (pointer)sreadUint(file);
	s->exnStack = sreadUint(file);
	useFixedHeap = (bool)sreadUint(file);
	fromSize = sreadUint(file);
 	maxHeapSize = sreadUint(file);

	/* set s->useFixedHeap, s->maxHeapSize, s->fromSize */
	if (heapSizeCommandLine)
	       	setHeapParams(s, heapSize);
	else {
		s->useFixedHeap = useFixedHeap;
		if (useFixedHeap)
			s->fromSize = fromSize;
		else {
			s->maxHeapSize = maxHeapSize;
			s->fromSize = computeHeapSize(s, heapSize, s->liveRatio);
		}
	}

	noLocals(s);

	/* Allocate stack. */
	s->stackSize = roundPage(s->stackTop - stackBottom);
	allocateStack(s);
	sread(s->stackBottom, 1,
		(s->stackTop + WORD_SIZE) - stackBottom,
 		file);
	translateStack(s, stackBottom);

	/* Allocate fromSpace. */
	unless (heapSize <= s->fromSize) 
		die("Not enough space to load world.");
	fromSpace(s);
	sread(s->base, 1, heapSize, file);
	s->frontier = s->base + heapSize;

	(*loadGlobals)(file);
	
	unless (EOF == fgetc(file))
	die("Invalid world: junk at end of file.");
	
	fclose(file);

	/* This must occur after loading the heap, stack, and globals, since it
	 * changes pointers in all of them.
	 */
	translateHeap(s, base);

	/* Allocate toSpace.  We at lease must do this if s->useFixedHeap. */
	s->toSize = s->fromSize;
	toSpace(s);

	assert(invariant(s));
}

/* ------------------------------------------------- */
/*                     saveStack                     */
/* ------------------------------------------------- */

/* Returns a pointer to the cont object. */
pointer GC_saveStack(GC_state *s) {
	pointer front, result;
	uint size;

	size = s->stackTop + WORD_SIZE - s->stackBottom;

	/* Ensure enough space in heap to save the stack. */
	s->bytesRequested = 3 * WORD_SIZE + size;
	if (s->frontier + s->bytesRequested >= s->limit) {
		s->localOffsets = empty_GC_offsets;
		GC_gc(s);
	}

	front = s->frontier;
	*(uint*)front = CONT_HEADER;
	front += WORD_SIZE;
	result = front;
	*(uint*)front = size;
	front += WORD_SIZE;
	*(uint*)front = s->exnStack;
	front += WORD_SIZE;
	memcpy(front, s->stackBottom, size);
	s->frontier = front + size;

	return result;
}

/* ------------------------------------------------- */
/*                    restoreStack                   */
/* ------------------------------------------------- */

void GC_restoreStack(GC_state *s, pointer p) {
	uint size;

	size = *(uint*)p;
	s->exnStack = *(uint*)(p + WORD_SIZE);

	smunmap(s->stackBottom, s->stackSize);
	s->stackSize = roundPage(size);
	allocateStack(s);
	s->stackTop = s->stackBottom + size - WORD_SIZE;
	memcpy(s->stackBottom, p + 2 * WORD_SIZE, size);
}
