#include "heap-utils.h"

#define unused(x) (void)x

/* Following reference paths via a reverse trace */

/* Strategy:
 * 1. Find all objects directly referenced from the roots of the GC
 * 2. Start with a set of "parent" objects we want to track.
 * 3. Look to see if any object in the set is directly referenced from a root
 * 4. Walk the heap looking for all objects which reference objects in the set.
 * 5. Record those objects as "children" of the parents - but only if they're new.
 * 6. Consider next generation, by treating the children as next set
 * 7. Repeat from step 3
 *
 */



/* Controlling variables */

/* if true then prompt for another object/wrapper after following a trail */
static int prompt_after_checking_references = 1;

/* set this from the debugger to permit roots to be found at depths
   greater than the depth of the first found roots .
*/
static int depth_limit = 0;

/* set this from the debugger to limit the number of trails which can
   be reported at any depth depth.
*/
static int trail_limit = 4;

/* set this from the debugger to limit the number of trails which can
   be reported when a trail goes cold.
*/
static int cold_trail_limit = 1;

/* set this from the debugger to limit the number of objects which get
   reported during processing for each depth.
*/
static int object_report_limit = 4;

/* set this from a debugger if you want to trace to an object */
static void *object_to_follow = 0;

/* set this from a debugger for tracing to the first instance found
   with this wrapper.
*/
static void *wrapper_to_follow = 0;



typedef struct obj_rec_st *obj_rec_p;

typedef struct obj_rec_st {
  obj_rec_p next;     /* Next recorded object with this hash value */
  obj_rec_p parent;   /* parent object that this object references  */
  mps_addr_t object;  /* The Dylan object this records */
} obj_rec_st;



#define TABLE_SIZE 0x1000
#define TABLE_MASK (TABLE_SIZE - 1)
#define TABLE_SHIFT 3

typedef obj_rec_p *obj_table;


static int hash_val(mps_addr_t obj)
{
  return ((int)obj >> TABLE_SHIFT) & TABLE_MASK;
}


static obj_rec_p root_table[TABLE_SIZE] = {NULL};      /* hash table of known roots */
static obj_rec_p processed_table[TABLE_SIZE] = {NULL}; /* hash table of processed objects */
static obj_rec_p current_table[TABLE_SIZE] = {NULL};   /* hash table of current set of objects */
static obj_rec_p child_table[TABLE_SIZE] = {NULL};     /* hash table of child objects */


static obj_rec_p alloc_obj_rec(mps_addr_t obj)
{
  obj_rec_p new = alloc_obj(sizeof(obj_rec_st));
  new->object = obj;
  new->next = NULL;
  new->parent = NULL;
  return new;
}

static void free_obj_rec(obj_rec_p or)
{
  free_obj(or, sizeof(obj_rec_st));
}

static void free_table(obj_table table)
{
  int i;
  for (i = 0; i < TABLE_SIZE; i++) {
    obj_rec_p next = table[i];
    while (next != NULL) {
      obj_rec_p this = next;
      next = next->next;
      free_obj_rec(this);
    }
    table[i] = NULL;
  }
}


static void add_to_table(obj_table table, obj_rec_p objrec)
{
  int hash = hash_val(objrec->object);
  objrec->next = table[hash];
  table[hash] = objrec;
}


static obj_rec_p find_obj_in_table(obj_table table, mps_addr_t obj)
{
  int hash = hash_val(obj);
  obj_rec_p current = table[hash];
  while (current != NULL) {
    if (current->object == obj) {
      return current;
    }
    current = current->next;
  }
  return NULL;
}

/* fold the src table into the dest - clearing src in the process */
static void merge_tables(obj_table dest, obj_table src)
{
  int i;
  for (i = 0; i < TABLE_SIZE;  i++) {
    obj_rec_p dchain = dest[i];
    obj_rec_p schain = src[i];
    if (schain != NULL) {
      dest[i] = schain;
      src[i] = NULL;
      if (dchain != NULL) {
        while (schain->next != NULL) {
          schain = schain->next;
        }
        schain->next = dchain;
      }
    }
  }
}

static int size_of_table(obj_table table)
{
  int i;
  int size = 0;
  for (i = 0; i < TABLE_SIZE; i++) {
    obj_rec_p current = table[i];
    while (current != NULL) {
      size++;
      current = current->next;
    }
  }
  return size;
}


static obj_rec_p add_new_to_table(obj_table table, mps_addr_t obj)
{
  obj_rec_p new = alloc_obj_rec(obj);
  add_to_table(table, new);
  return new;
}

static obj_rec_p new_obj_rec(mps_addr_t obj)
{
  return add_new_to_table(child_table, obj);
}

static obj_rec_p new_root(mps_addr_t obj, mps_addr_t root)
{
  obj_rec_p new = add_new_to_table(root_table, obj);
  obj_rec_p parent = alloc_obj_rec(root);
  new->parent = parent;
  return new;
}


static void clear_object_reference_paths (void)
{
  free_table(root_table);
  free_table(processed_table);
  free_table(current_table);
  free_table(child_table);
}



static void display_object_reference(void* object, mps_lib_FILE *stream)
{
  void *wrapper = ((void**)object)[0];
  char *class_name = class_name_from_wrapper(wrapper);
  mps_lib_fputs("    ", stream);
  mps_lib_fputs(class_name, stream);
  display_padding_for_string(class_name, ' ', class_name_size, stream);
  display_hex_address(object, stream);
  mps_lib_fputc('\n', stream);
}

static void display_reference_paths (obj_rec_p or, mps_lib_FILE *stream)
{
  if (or != 0) {
    display_reference_paths(or->parent, stream);
    display_object_reference(or->object, stream);
  }
}

static void display_cold_trail_intro (int gensize)
{
  mps_lib_FILE *stream = mps_lib_get_stdout();
  mps_lib_fputs("\nFailed to find any objects referenced from a root.\n", stream);
  mps_lib_fputs("Hence all detected objects are 'floating garbage'.\n", stream);
  mps_lib_fputs("There were ", stream);
  display_integer(gensize, stream);
  mps_lib_fputs(" objects encountered while processing the last depth.\n", stream);
  mps_lib_fputs("Here are some cold trails:\n", stream);
}

static void display_cold_trail_details (obj_rec_p obj)
{
  mps_lib_FILE *stream = mps_lib_get_stdout();
  mps_lib_fputs("\nStart of a cold trail of object references\n", stream);
  display_reference_paths(obj, stream);
  mps_lib_fputs("End of cold trail\n", stream);
}

static void display_trail_details (obj_rec_p obj, obj_rec_p root)
{
  mps_lib_FILE *stream = mps_lib_get_stdout();
  mps_lib_fputs("\nStart of a trail of object references\n", stream);
  display_reference_paths(obj, stream);
  mps_lib_fputs("Referenced from a root at address                     ", stream);
  display_hex_address(root->parent->object, stream);
  mps_lib_fputc('\n', stream);

  mps_lib_fputs("End of trail\n", stream);
}

static void display_generation_details (int gennum, int gensize, obj_table table)
{
  int i;
  int count = 0;
  mps_lib_FILE *stream = mps_lib_get_stdout();
  mps_lib_fputs("Processing depth ", stream);
  display_integer(gennum, stream);
  mps_lib_fputs(" containing ", stream);
  display_integer(gensize, stream);
  mps_lib_fputs(" objects:\n", stream);
  for (i = 0; i < TABLE_SIZE; i++) {
    obj_rec_p current = table[i];
    while (current != NULL) {
      count++;
      if (count > object_report_limit) {
        mps_lib_fputs("    To display more objects, ", stream);
        mps_lib_fputs("use the debugger to set the variable at address: ", stream);
        display_hex_address(&object_report_limit, stream);
        mps_lib_fputc('\n', stream);
        return;
      } else {
        display_object_reference(current->object, stream);
      }
      current = current->next;
    }
  }
}

static void display_reference_variables_addresses (void)
{
  mps_lib_FILE *stream = mps_lib_get_stdout();
  mps_lib_fputs("\nTo find a trail of references to an object, \n", stream);
  mps_lib_fputs("use the debugger to set one of the following variables:\n", stream);
  mps_lib_fputs("      object_to_follow  - set address ", stream);
  display_hex_address(&object_to_follow, stream);
  mps_lib_fputc('\n', stream);
  mps_lib_fputs("      wrapper_to_follow - set address ", stream);
  display_hex_address(&wrapper_to_follow, stream);
  mps_lib_fputc('\n', stream);
}

static void display_gen_limit_addresses (int gennum)
{
  mps_lib_FILE *stream = mps_lib_get_stdout();
  mps_lib_fputs("\nTo find deeper trails of references to an object, \n", stream);
  mps_lib_fputs("use the debugger to set the following variable\n", stream);
  mps_lib_fputs("to a depth limit greater than ", stream);
  display_integer(gennum, stream);
  mps_lib_fputs(":\n", stream);
  mps_lib_fputs("      depth_limit  - set address ", stream);
  display_hex_address(&depth_limit, stream);
  mps_lib_fputc('\n', stream);
}

static void display_trail_limit_addresses (int rootnum)
{
  mps_lib_FILE *stream = mps_lib_get_stdout();
  mps_lib_fputs("\nTo display more trails of references at this depth, \n", stream);
  mps_lib_fputs("use the debugger to set the following variable\n", stream);
  mps_lib_fputs("to a trail limit greater than ", stream);
  display_integer(rootnum, stream);
  mps_lib_fputs(":\n", stream);
  mps_lib_fputs("      trail_limit  - set address ", stream);
  display_hex_address(&trail_limit, stream);
  mps_lib_fputc('\n', stream);
}

static void display_cold_trail_limit_addresses (int reported)
{
  mps_lib_FILE *stream = mps_lib_get_stdout();
  mps_lib_fputs("\nTo display more cold trails, \n", stream);
  mps_lib_fputs("use the debugger to set the following variable\n", stream);
  mps_lib_fputs("to a trail limit greater than ", stream);
  display_integer(reported, stream);
  mps_lib_fputs(":\n", stream);
  mps_lib_fputs("      cold_trail_limit  - set address ", stream);
  display_hex_address(&cold_trail_limit, stream);
  mps_lib_fputc('\n', stream);
}

static void add_target_object (mps_addr_t obj)
{
  new_obj_rec(obj);
}

static void add_target_object_of_wrapper (mps_addr_t object,
                                          mps_fmt_t format, mps_pool_t pool,
                                          void *wrapper, size_t p2)
{
  void **found = (void**)object;
  void *found_wrapper = *found;
  unused(format);
  unused(pool);
  unused(p2);
  if (found_wrapper == wrapper) {
    add_target_object(object);
  }
}

extern mps_arena_t arena;

static void add_target_objects_from_wrapper (void *wrapper)
{
  mps_arena_formatted_objects_walk(arena, &add_target_object_of_wrapper, wrapper, 0);
}



static obj_rec_p object_in_roots(mps_addr_t object)
{
  return find_obj_in_table(root_table, object);
}

static obj_rec_p object_in_processed_set(mps_addr_t object)
{
  return find_obj_in_table(processed_table, object);
}

static obj_rec_p object_in_current_set(mps_addr_t object)
{
  return find_obj_in_table(current_table, object);
}


static int size_of_current_set()
{
  return size_of_table(current_table);
}

static void advance_generation (void)
{
  /* merge the current into the processed set */
  /* then merge the children to make them thecurrent set */
  merge_tables(processed_table, current_table);
  merge_tables(current_table, child_table);
}



static BOOL trace_reference(mps_addr_t object, mps_addr_t parent,
                            int parent_size, void * env)
{
  obj_rec_p refrec = object_in_current_set(object);
  unused(parent_size);
  unused(env);
  if (refrec != NULL) {
    /* Have found a ref to the current gen */
    if (object_in_processed_set(parent)
        || object_in_current_set(parent)) {
      /* Found a reference from a known parent or sibling. */
      /* Since the trail to the parent has already been followed, */
      /* we can be sure that this cannot have any effect */
      /* on whether a child object is a root */
      return FALSE;
    } else {
      /* Found an "interesting" reference, so find or make an */
      /* obj_rec for the reference source, and record the references */
      obj_rec_p this_or = new_obj_rec(parent);
      this_or->parent = refrec;
      return FALSE;
    }
  }
  return TRUE;
}


static void look_for_reference(mps_addr_t object, mps_fmt_t format,
                               mps_pool_t pool,
                               void *p1, size_t p2)
{
  unused(format);
  unused(pool);
  unused(p1);
  unused(p2);
  trace_object(object, trace_reference, NULL);
}


/* traverse the heap looking for any objects which reference this generation */
static void process_generation (void)
{
  mps_arena_formatted_objects_walk(arena, &look_for_reference, NULL, 0);
}



static int display_roots_in_current_set (void)
{
  int rootnum = 0;
  int i;
  for (i = 0; i < TABLE_SIZE; i++) {
    obj_rec_p current = current_table[i];
    while (current != NULL) {
      obj_rec_p refrec = object_in_roots(current->object);
      if (refrec != NULL) {
        rootnum++;
        if (trail_limit == rootnum) {
          display_trail_limit_addresses(rootnum); /* prompt to increase limit */
          report_break("Set the trail limit to show more if required\n");
        }
        if (trail_limit > rootnum) {
          display_trail_details(current, refrec);
        }
      }
      current = current->next;
    }
  }
  return rootnum;
}

static void display_cold_trail()
{
  int size = size_of_current_set();
  int reported = 0;
  int i;
  if (size > 0) {
    /* don't display for null entry sets */
    display_cold_trail_intro(size);
    for (i = 0; i < TABLE_SIZE; i++) {
      obj_rec_p current = current_table[i];
      while (current != NULL) {
        if (cold_trail_limit == reported) {
          /* prompt to increase limit */
          display_cold_trail_limit_addresses(reported);
          report_break("Set the cold trail limit to show more if required\n");
        }
        if (cold_trail_limit > reported) {
          display_cold_trail_details(current);
        }
        reported++;
        current = current->next;
      }
    }
  }
}

static void advance_through_generations (void)
{
  int gennum = 0;
  while (TRUE) {
    int roots;
    int gensize;
    gensize = size_of_table(child_table);
    if (gensize == 0) {
      /* failed to find a trail from a root */
      /* We must have etected a clique kept alive by GC conservatism */
      display_cold_trail();
      return;
    }
    display_generation_details(gennum, gensize, child_table);
    advance_generation();
    roots = display_roots_in_current_set();
    if (roots > 0) {
      /* check to see if we really want to continue */
      if (depth_limit <= gennum) {
        display_gen_limit_addresses(gennum); /* prompt to increase limit */
        report_break("Set the depth limit to trace further if required\n");
        if (depth_limit <= gennum) {
          return;
        }
      }
    }
    process_generation(); /* find children for this generation */
    gennum++;
  }
}

static void record_a_root (mps_addr_t *objectref, mps_root_t root,
                           void *p1, size_t p2)
{
  void **object = (void **)(*objectref);
  void *wrapper = *object;
  unused(root);
  unused(p1);
  unused(p2);
  if (wrapper && ((int)wrapper & 3) == 0) {
    obj_rec_p rootrec = object_in_roots(object);
    if (rootrec == NULL) {
      /* Have found a new root - so add to the set */
      new_root(object, objectref);
    }
  }
}


/* Avoid inlining function calls from now on.
 * It might confuse the roots walk, by putting extra stuff on the stack.
 */
#pragma inline_depth(0)

/* The following function "cleans" below the stack pointer,
 * avoiding any palimpsest problem, whereby spurious "old"
 * values turn up
 */

static int recurse_to_clean_the_stack(int count, int dummy1, int dummy2, int dummy3)
{
  if (count > 0) {
    int res = recurse_to_clean_the_stack(count-1, dummy1, dummy2, dummy3);
    return res + dummy1;
  } else {
    return count;
  }
}

static void find_all_roots()
{
  recurse_to_clean_the_stack(200, 0, 0, 0);
  mps_arena_roots_walk(arena, &record_a_root, 0, 0);
}

static void find_trails()
{
  if (wrapper_to_follow) {
    report_message("\nTracing paths to all objects with selected wrapper ...\n");
    add_target_objects_from_wrapper(wrapper_to_follow);
  } else if (object_to_follow) {
    report_message("\nTracing paths to selected object ...\n");
    add_target_object(object_to_follow);
  }
  advance_through_generations();
}

static int follow_reference_paths (void)
{
  while (object_to_follow || wrapper_to_follow) {
    find_all_roots();
    find_trails();

    object_to_follow = 0;
    wrapper_to_follow = 0;
    if (prompt_after_checking_references) {
      display_reference_variables_addresses();
      clear_object_reference_paths();
      report_break("Set a new object or wrapper to trace if required\n");
    } else {
      clear_object_reference_paths();
      return 0;
    }
  }
  return 0;
}


void display_reference_trails (void)
{
  if (object_to_follow || wrapper_to_follow) {
    follow_reference_paths();
  } else {
    display_reference_variables_addresses();
  }
}
