#include "gc.h"

#include <assert.h> /* General assertations */
#include <stdio.h>  /* For printf mainly */
#include <string.h> /* For memset */
#include <stdlib.h> /* For exit() */

typedef unsigned char page;

void scan_memory(char *, int);
void tm_unlink(struct sBlock * t, int treadmillindex);

/* Platform dependant section of code */
#ifdef LINUX

#include <unistd.h> /* For sbrk definition */
#define PAGE_SIZE 4096

void *os_getmem(int bytes)
{
  return ((void *) sbrk(bytes));
}

#endif

/*
 * end and edata are the two marker symbols inserted
 * by the linker and between which all static data 
 * resides. these are used to scan global variables
 * */

extern int end;
extern int etext;

#define top_of_static_area ((int) &end)
#define bottom_of_static_area ((int) &etext)

/* Large objects should really be allocated off in page
 * offsets, that way they can easily be reusued when
 * it is required */
struct sLargeBlock * large_free_list = NULL;

void heap_decreaser();

int count_objects( struct sBlock * cwise, struct sBlock * ccwise )
{

	if (cwise == ccwise)
		return 1;
	else
		return 1 + (count_objects( cwise->counterclockwise, ccwise));

}

/* Returns the total number of objects on the treadmill specified by
   treadmill_index */
int GC_GetTreadmillTotalObjects( int ti )
{
	return count_objects( GC.treadmill_index[ti].Free, 
			   GC.treadmill_index[ti].Free->clockwise );

}

int GC_GetTreadmillFreeObjects ( int ti )
{
	return count_objects( GC.treadmill_index[ti].LastFree,
			GC.treadmill_index[ti].Free );
}

int GC_GetTreadmillUsedObjects ( int ti )
{
	/* Case where there are no free objects. */
	if (GC.treadmill_index[ti].LastFree->clockwise ==
			GC.treadmill_index[ti].Free) 
		return 0;

	return count_objects( GC.treadmill_index[ti].Free->counterclockwise,
			GC.treadmill_index[ti].LastFree->clockwise );
	
}

/* The whole of this function needs to be atomic */
void GC_init()
{
  char * newmem;
  int i;

  GC.FreeList = NULL;
  
  /* Basic sanity checks to ensure the premises on which the garbage
   * collector was build are correct */
  
  /* Ensure the defined OVERHEAD is correct */
  assert ( OVERHEAD == (sizeof(struct sBlock) - 4) );
  assert ( OVERHEAD == (sizeof(struct sLargeBlock) - 4) );

  /* Ends sanity checks */
  
  newmem = (char *) os_getmem(PAGE_SIZE);
  if ((int) newmem == -1) {
    printf("Error, not enough memory to initialize the garbage collector!\n");
    exit(1);
  }

  for (i = 0; i < NUMBER_OF_TREADMILLS; i++) {
	page * new_page;

	/* Start with 32 byte treadmills and go up */
    	GC.treadmill_index[i].size = (32 << i);    
    	new_page = alloc_first_page(GC.treadmill_index[i].size);
  	GC.treadmill_index[i].Free = (struct sBlock *) &new_page[0];
  	GC.treadmill_index[i].LastFree = 
		GC.treadmill_index[i].Free->counterclockwise;
	if (i > 6) {
		int t;
		for (t = 0; t < i - 6; t++)
			alloc_page(&GC.treadmill_index[i]);
	}
  }

  GC.large_treadmill = NULL;
  GC.large_treadmill_tail = NULL;
  
}

/* clockwise is the object on the clockwise of the chain which we are moving,
 * counterclockwise is the other end of the chain we are moving
 * if clockwise and counterclockwise are equal, we are moving one object.
 * insert_to_clockwise is the insertion point */
void link_after(struct sBlock * clockwise, 
		struct sBlock * counterclockwise, 
		struct sBlock * insert_to_clockwise)
{

	counterclockwise -> clockwise = insert_to_clockwise -> clockwise;
	insert_to_clockwise -> clockwise -> counterclockwise = counterclockwise;
	clockwise -> counterclockwise = insert_to_clockwise;
	insert_to_clockwise -> clockwise = clockwise; 
	
}

/* unlinks the specified object from the treadmill.  Merges the pointers
 * for the two surrounding pointers. */
void tm_unlink(struct sBlock * t, int treadmillindex)
{
	int total_objs_before;

	total_objs_before = GC_GetTreadmillTotalObjects( treadmillindex );
	
	t -> clockwise -> counterclockwise = t -> counterclockwise;
	t -> counterclockwise -> clockwise = t -> clockwise;
	
	/* Now the object is unlinked, lets test to see if its one of the 
	 * important pointers on the treadmill */
	if (t == GC.treadmill_index[treadmillindex].LastFree) {
		/* If we are unlinking the lastfree we need to
		 * update the lastfree pointer to a valid object.
		 * This should be counterclockwise of where lastfree
		 * used to point to. */
	//	printf("Updating LastFree with tm_unlink\n");
		GC.treadmill_index[treadmillindex].LastFree =
			t -> counterclockwise;
		
	}

	if (t == GC.treadmill_index[treadmillindex].Free) {
	//	printf("Updating Free with tm_unlink\n");
		GC.treadmill_index[treadmillindex].Free = 
			t -> clockwise;
	}
	
	/* Ensure that we havent made a mistake */
	assert ( GC.treadmill_index[treadmillindex].Free != 
			GC.treadmill_index[treadmillindex].LastFree );

	assert ( total_objs_before - 1 == GC_GetTreadmillTotalObjects (treadmillindex) ) ;	
	
}

void * getmem( int bytes_size )
{
	struct sLargeBlock *temp;

	if (large_free_list == NULL) {

		return os_getmem( bytes_size );
		
	} 
	
	if (large_free_list->size > PAGE_SIZE) {
	
		large_free_list -> size -= PAGE_SIZE;
	
		return (void *) ( (int) large_free_list +
				( large_free_list -> size ) -
				PAGE_SIZE );
		
	}
	
	assert ( large_free_list -> size == PAGE_SIZE );
	temp = large_free_list;

	large_free_list = large_free_list -> next;
		
	return (void *) temp;

}

page * partition_page( int size)
{
	page * new_page;
  	struct sBlock * new_block;
  	int i;
  	struct sBlock * cwise, * ccwise; /* For use when calling link... */

	new_page = getmem(PAGE_SIZE);
  	if ((int) new_page == -1) {
    		return (page *) -1;
  	}

	/*   Fastest way to set the flags to zero is the set the page to 0 */
  	memset (new_page, 0, PAGE_SIZE);

	for (i = 0; i < PAGE_SIZE-size; i += size) {  
   		new_block = (struct sBlock *) &new_page[i];
    		new_block->clockwise = 
      		(struct sBlock *) &new_page[i + size];
	}

	for (i = size; i < PAGE_SIZE; i += size) {
		new_block = (struct sBlock *) &new_page[i];
		new_block->counterclockwise =  
			(struct sBlock *) &new_page[i - size];
	}

	return new_page;  
}

int alloc_page(struct sTreadmill *treadmill)
{
	page * new_page;
  	struct sBlock * new_block;
  	struct sBlock * cwise, * ccwise; /* For use when calling link... */

	new_page = partition_page(treadmill->size);

  	ccwise = (struct sBlock *) &new_page[PAGE_SIZE - treadmill->size];
  	cwise = (struct sBlock *) &new_page[0];
  
  	link_after ( cwise, ccwise, treadmill->LastFree );
  
  	treadmill->LastFree = ccwise;
  
  	return 0;
  
}

/* 
 * alloc_first_page
 *
 * */
page * alloc_first_page(int size)
{

  	page * new_page;
  	struct sBlock * new_block;

	new_page = partition_page(size);
  
	new_block = (struct sBlock *) &new_page[0];
  	new_block->counterclockwise = 
    		(struct sBlock *) &new_page[PAGE_SIZE - size];

  	new_block =
    		(struct sBlock *) &new_page[PAGE_SIZE - size];
  	new_block->clockwise = (struct sBlock *) &new_page[0];

	return (page *) &new_block[0];

}

inline void * alloc_small_item( int size )
{
  int index = 0;
  int bitcount;
  struct sTreadmill *treadmill;
  struct sBlock *new_mem;

  /* Ensure that this function does not start running at the same
     time as alloc_page is running */
  
  size += OVERHEAD;
  
  /* Start at 32 bytes and increase by 1 treadmill for each shift right
   * until we have a zero */
  bitcount = size >> 5;

  /* This loop operates a maximum of 9 times
   *  Note: Perhaps this would be a good candidate for the 
   *  bit stream manipulation instructions of the intel chip.
   *  Too bad gcc has a really poor inline asm mechanisim, or
   *  i would have use them. */
  while (bitcount != 0) { 
    index++;
    bitcount = bitcount >> 1;
  }

  treadmill = & GC.treadmill_index[index];

  new_mem = treadmill->Free;
  
  treadmill->Free =
 	treadmill->Free->clockwise;  

  if (treadmill->Free == treadmill->LastFree) {
    /* Fork off a thread to allocate another page */
    if (alloc_page(treadmill)) {
	printf("Failed to allocate because of no more pages available\n");
	return (NOT_ENOUGH_MEM);    
    }
  }

	printf("Alloc small(%u)\n", &new_mem->data);
  
  return ((void *) &new_mem->data);
  
}

inline void * alloc_large_item( int size )
{
  	struct sLargeBlock *new_block;
  	int real_size;
	
  	/* Allocate a large block and put it on treadmill */

  	/* Work out the number of pages which are required to be allocated
   	 * for this object. Large objects are always allocated with a 
   	 * granularity of a page */

  	/* This remains correct for as long as the overhead for small and
   	 * large objects remains the same */
	
  	real_size = ( (size + OVERHEAD) / PAGE_SIZE ) * PAGE_SIZE;
	real_size += (size + OVERHEAD) % PAGE_SIZE ? PAGE_SIZE : 0;	

  	new_block = (struct sLargeBlock *) os_getmem (real_size);

  if ((int) new_block == -1) {
	printf("Not enough free memory to allocate the new item\n");
	return (NOT_ENOUGH_MEM);
  }

  memset (new_block, 0, real_size);
  
  new_block->size = real_size;
  
  if (GC.large_treadmill == NULL) {
	  GC.large_treadmill_tail = new_block;
	  GC.large_treadmill = new_block;
  } else {
	  GC.large_treadmill_tail->next = new_block;
	  GC.large_treadmill_tail = new_block;
  }
 
	printf("Alloc large at(%u), size (%u)\n",
			&new_block->data,
			new_block->size);
	
  	return ((void *) &new_block->data);
}

void * alloc ( int size )
{
	
  if ( size+OVERHEAD >= PAGE_SIZE )
    return alloc_large_item(size);
  else 
    return alloc_small_item(size);
  	 
}

void scan_all_threads()
{
	/* This ensures that the memory area which belongs to the
	 * garbage collector is never sweeped. */

	printf("Scanning memory from %u to %u\n",
		top_of_static_area,
		(int) &GC.begin);

	scan_memory( (unsigned char *) top_of_static_area,
		(int) ((int) top_of_static_area) - ((int) &GC.begin) - 4 );

	printf("Scanning memory from %u to %u\n",
		(int) &GC.begin,
		bottom_of_static_area);

	scan_memory( (unsigned char *) &GC.end,
		(int) ((int) &GC.end) - ((int) bottom_of_static_area) - 4 );

}

void scan_treadmill(int tm)
{
	struct sTreadmill *treadmill;
	treadmill = & GC.treadmill_index[tm];
	
	treadmill->Scan = treadmill->Free->counterclockwise;
	treadmill->From = treadmill->Free;
	
	while (treadmill->Scan != treadmill->LastFree) {
		
		/* There are no references to this object,
		 * and thus its ok to collect it. */
		if (treadmill->Scan->flags == 0) {
	
			treadmill->Scan = treadmill->Scan->counterclockwise;	
				
		} else {
			struct sBlock * move;

			/* reset the flags for the next round of collection */
			treadmill->Scan->flags = 0;
			
			/* Flags the 'move' object, and unlinks it from
			 * the treadmill */		
			move = treadmill->Scan;
			
			treadmill->Scan = treadmill->Scan->counterclockwise;
			tm_unlink( move, tm );
			
			link_after( move, move, 
					treadmill->From->counterclockwise);

			treadmill->From = move;
			
		} /* end 'else' */
		
	} /* end 'while' */

	treadmill->LastFree = treadmill->From->counterclockwise;	
	
}

/* is_object.
 * When passed 'query' which is an address, it will return whether or not
 * that address is part of an object.
 * This will flag the object as black (traversable) also.
 * */
int is_object(unsigned int query, int *vsize)
{

	register int i;
	register struct sBlock *scan;
	register struct sLargeBlock *lscan;
	
	if (query == 0)
		return 0;
	
	for (i = 0; i < NUMBER_OF_TREADMILLS; i++) {
	
		scan = GC.treadmill_index[i].Free->counterclockwise;
	
		while (scan != GC.treadmill_index[i].Free->clockwise) {

			if (query == (unsigned int) &scan->data)
			{
				/* This will always be a multiple of 4 */
				*vsize = GC.treadmill_index[i].size
					       - OVERHEAD;
				
				/* Check and set colour flag here */
				if (scan->flags == 0) {
					scan->flags = 1;
					printf("Is object found a small object\n");
					return (int) &scan->data;	
				}

			}

			scan = scan->counterclockwise;	
		}
	}

	/* Now scan the large objects queue to see if this is a 
	 * large object */

	lscan = GC.large_treadmill;

	while (lscan != NULL) {
		
		if (query >= (unsigned int) &lscan->data && 
			query <= ((unsigned int) &lscan->data) + 
			lscan->size) {

				*vsize = (lscan -> size - OVERHEAD);
				if (lscan->flags == 0) {
					lscan->flags = 1;
					printf("is object found a large object at(%u), size(%u)\n",
							(int) &lscan->data, *vsize);
					return (int) &lscan->data;
				}
			}
		
		lscan = lscan -> next;
	}
	
	return 0;	
}

/* Length is the length of the peice of memory in bytes. */
void scan_memory(char * start, int length)
{
	register int i;

	printf("scan_memory: start(%u), length(%u)\n",
			start, length);
	
	for (i = 0; i < length; i++) {
		int size, obj;

		obj = is_object ( * ((int *) &start[i]), &size );
		if (obj != 0)
			scan_memory((char *) obj, size);
	}
	
}


void garbage_collect()
{
	int i;
	struct sLargeBlock *scan, *prev, *temp;
	struct sBlock *smallscan;
	
	gc_begin:

	/* Mark all used objects... */	
	scan_all_threads();

	/* Now reclaim all unmarked objects */
	for (i = 0; i < NUMBER_OF_TREADMILLS; i++)
		scan_treadmill(i);

	scan = GC.large_treadmill;
	prev = NULL;
	
	while (scan != NULL) {
		
		/* DEBUG: ensuring the list traversal is good */
		assert (prev == NULL || prev->next == scan);
		
		if (scan->flags == 0) {	

			temp = scan;
			
			if (prev == NULL)
				GC.large_treadmill = scan->next;
			else
				prev->next = scan->next;

			scan = scan->next;
			
			/* Now scan is unlinked from the large object list,
			 * we can put it into the free large object list */
			
			temp -> next = large_free_list;
			large_free_list = temp;
			
		} else {
			prev = scan;	
			scan = scan -> next;
		} /* if */
	} /* while */
} /* garbage_collect */

#define MINFREE 8

/* An auxiliary function for the heap returner */
int heap_return_aux(struct sBlock * scan, int i)
{
	/* Tests to see if we want to remove the object specified
	 * in the parameter scan on treadmill i */
	if ( ((int) scan) + GC.treadmill_index[i].size >=
		((int) sbrk(0) - 8 ) 
		&&
		GC_GetTreadmillFreeObjects ( i ) > MINFREE
	)
	{
		tm_unlink( scan, i );	
			
		sbrk( -GC.treadmill_index[i].size );
		return 1;
	}

	return 0;
		
}

void heap_return_large()
{
	struct sLargeBlock * prev = NULL;
	struct sLargeBlock * scan = large_free_list;

	while (scan != NULL) {
		
		if ( (int) scan + (scan -> size) >
				(int) sbrk(0) - 8) {
			
			struct sLargeBlock * temp = scan;
			
			if (prev == NULL) {
				large_free_list = large_free_list -> next;
				scan = large_free_list;
			} else {
				scan = scan -> next;
				prev->next = scan;
			}

			sbrk( - temp -> size );
			
		} else {
			prev = scan;
			scan = scan -> next;
		}
	}
	
	
}

void heap_return()
{
	int i; int j;
	
	/* Sort the treadmills */	
	for (i = 0; i < NUMBER_OF_TREADMILLS; i++) {
		struct sBlock * scan;
				
		while (heap_return_aux(GC.treadmill_index[i].LastFree, i) == 1)
		{
		}
		
		scan = GC.treadmill_index[i].LastFree -> counterclockwise;

		while ( scan != GC.treadmill_index[i].Free->counterclockwise) {
		
			if (heap_return_aux(scan, i) == 1) {
				i = 0;
				scan = GC.treadmill_index[i].LastFree->counterclockwise;
			} else {
				scan = scan -> counterclockwise;
			}
		}
	}

	/* Now process the free list for large objects */

	heap_return_large();

}


