/*  Copyright (C) 1987, 1988 Barak Pearlmutter and Kevin Lang    */

/* This file has weak pointers in it. */

#include <stdio.h>
#include "emulator.h"
#include "gc.h"


#ifdef PROTOTYPES
extern void		init_wp(void);		
extern void		enter_wp(ref r, ref wp);		
extern void		rebuild_wp_hashtable(void);		
extern ref		ref_to_wp(ref r);		
extern void		wp_hashtable_distribution(void);		
extern unsigned long	post_gc_wp(void);		
#endif


/*
 * Weak pointers are done with a simple table that goes from weak
 * pointers to objects, and a hash table that goes from objects to
 * their weak pointers.

 * In the future, this will be modified to keep separate hash tables
 * for the different areas, so that objects in spatic space need not
 * be rehashed.

 * Plus another one for immediates and fixnums, which don't live in
 * any space at all.

 */

#define WP_TABLE_SIZE 10000L

#ifdef MALLOC_WP_TABLE
ref *wp_table;
#else
ref wp_table[WP_TABLE_SIZE+1];
#endif

long wp_index = 0;


/* A hash table from references to their weak pointers.  This hash
 * table is not saved in dumped worlds, and is rebuilt from scratch
 * after each GC and upon booting a new world.

 * Structure of this hash table:

 * Keys are references themselves, smashed about and xored if deemed
 * necessary.

 * Sequential rehash, single probe.
 */

#define WP_HASHTABLE_SIZE 10007L	/* Good idea for this to be prime. */

typedef struct
{
  ref obj, wp;
} wp_hashtable_entry;

#ifdef MALLOC_WP_TABLE
wp_hashtable_entry *wp_hashtable;
#else
wp_hashtable_entry wp_hashtable[WP_HASHTABLE_SIZE];
#endif


/* The following magic number is floor( 2^32 * (sqrt(5)-1)/2 ). */
#define wp_key(r) ((unsigned long) 0x9E3779BB*(r)) /* >>10, == 2654435771L */

void init_wp()
{
#ifdef MALLOC_WP_TABLE
  wp_table = (ref *)my_malloc((long)(sizeof(ref) * WP_TABLE_SIZE));
  wp_hashtable = (wp_hashtable_entry *)
    my_malloc((long)(sizeof(wp_hashtable_entry) * WP_HASHTABLE_SIZE));
#endif
}


/* Register r as having weak pointer wp. */
void enter_wp(r, wp)
     ref r, wp;
{
  unsigned long i = wp_key(r) % WP_HASHTABLE_SIZE;

  while (TRUE)
    if (wp_hashtable[i].obj == e_nil)
      {
	wp_hashtable[i].obj = r;
	wp_hashtable[i].wp = wp;
	return;
      }
    else if (++i == WP_HASHTABLE_SIZE)
      i = 0;
}




/* Rebuild the weak pointer hash table from the information in the table
   that takes weak pointers to objects. */
void rebuild_wp_hashtable()
{
  unsigned long i;
  
  for (i=0; i<WP_HASHTABLE_SIZE; i++)
    wp_hashtable[i].obj = e_nil;

  for (i = 0; i<wp_index; i++)
    if (wp_table[1+i] != e_nil)
      enter_wp(wp_table[1+i], INT_TO_REF(i));
}





/* Return weak pointer associated with obj, making a new one if necessary. */

ref ref_to_wp(r)
     ref r;
{
  unsigned long i = wp_key(r) % WP_HASHTABLE_SIZE;

  if (r == e_nil) return INT_TO_REF(-1);

  while (TRUE)
    {
      if (wp_hashtable[i].obj == r)
	return wp_hashtable[i].wp;
      else if (wp_hashtable[i].obj == e_nil)
	{
	  /* Make a new weak pointer, installing it in both tables: */
	  wp_hashtable[i].obj = wp_table[1+wp_index] = r;
	  return wp_hashtable[i].wp = INT_TO_REF(wp_index++);
	}
      else if (++i == WP_HASHTABLE_SIZE)
	i = 0;
    }
}



void wp_hashtable_distribution()
{
  unsigned long i;

  for (i=0; i<WP_HASHTABLE_SIZE; i++)
    {
      ref r = wp_hashtable[i].obj;
      
      if (r == e_nil)
	(void)putchar('.');
      else 
	{
	  unsigned long j = wp_key(r) % WP_HASHTABLE_SIZE;
	  long dist = i-j;

	  if (dist < 0) dist += WP_HASHTABLE_SIZE;

	  if (dist < 1+'9'-'0')
	    (void)putchar((char)('0'+dist));
	  else if (dist < 1+'Z'-'A' + 1+'9'-'0')
	    (void)putchar((char)('A' + dist - (1+'9'-'0')));
	  else
	    (void)putchar('*');
	}

      fflush_stdout();
    }
}






unsigned long post_gc_wp()
{
  /* Scan the weak pointer table.  When a reference to old space is found,
     check if the location has a forwarding pointer.  If not, discard the
     reference; if so, update it. */
  long i;
  unsigned long discard_count = 0;

  for (i=0; i<wp_index; i++)
    {
      ref r = wp_table[1+i], *p;

      if ( (r&PTR_MASK) && (p = ANY_TO_PTR(r), OLD_PTR(p)) )
	{
	  ref r1 = *p;

	  if (TAG_IS(r1,LOC_TAG) && NEW_PTR(LOC_TO_PTR(r1)))
	    {
	      wp_table[1+i] = TAG_IS(r,LOC_TAG) ? r1 : r1|PTR_TAG;
	    }
	  else
	    {
	      wp_table[1+i] = e_nil;
	      discard_count += 1;
	    }
	}
    }

  rebuild_wp_hashtable();

  return discard_count;
}


