/*--------------------------------------------------------------------*/
/*--- Callgrind                                                    ---*/
/*---                                                      ct_fn.c ---*/
/*--------------------------------------------------------------------*/

/*
   This file is part of Callgrind, a Valgrind tool for call tracing.

   Copyright (C) 2002-2004, Josef Weidendorfer (Josef.Weidendorfer@gmx.de)

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.

   The GNU General Public License is contained in the file COPYING.
*/

#include "ct_include.h"

#define N_INITIAL_FN_ARRAY_SIZE 10071

static fn_array current_fn_active;

/*------------------------------------------------------------*/
/*--- Object/File/Function hash entry operations           ---*/
/*------------------------------------------------------------*/

/* Object hash table, fixed */
static obj_node* obj_table[N_OBJ_ENTRIES];

void SK_(init_obj_table)()
{
    Int i;
    for (i = 0; i < N_OBJ_ENTRIES; i++)
	obj_table[i] = 0;
}

#define HASH_CONSTANT   256

static UInt str_hash(const UChar *s, UInt table_size)
{
    int hash_value = 0;
    for ( ; *s; s++)
        hash_value = (HASH_CONSTANT * hash_value + *s) % table_size;
    return hash_value;
}


static UChar* anonymous_obj = "???";

static __inline__ 
obj_node* new_obj_node(SegInfo* si, obj_node* next)
{
   Int i;
   obj_node* new = VG_(malloc)(sizeof(obj_node));
   new->name  = si ? (UChar*)VG_(strdup)( VG_(seg_filename)(si) )
                     : anonymous_obj;
   for (i = 0; i < N_FILE_ENTRIES; i++) {
      new->files[i] = NULL;
   }
   SK_(stat).distinct_objs ++;
   new->number  = SK_(stat).distinct_objs;
   new->start   = si ? VG_(seg_start)(si) : 0;
   new->size    = si ? VG_(seg_size)(si) : 0;
   new->offset  = si ? VG_(seg_sym_offset)(si) : 0;
   new->next    = next;

#if CT_ENABLE_DEBUG  
   new->last_slash_pos = 0;
   i = 0;
   while(new->name[i]) {
	if (new->name[i]=='/') new->last_slash_pos = i+1;
	i++;
   }
#endif
   
   return new;
}

obj_node* SK_(get_obj_node)(SegInfo* si)
{
    obj_node*    curr_obj_node;
    UInt         objname_hash;
    const UChar* obj_name;
    
    obj_name = si ? VG_(seg_filename)(si) : anonymous_obj;

    /* lookup in obj hash */
    objname_hash = str_hash(obj_name, N_OBJ_ENTRIES);
    curr_obj_node = obj_table[objname_hash];
    while (NULL != curr_obj_node && 
	   VG_(strcmp)(obj_name, curr_obj_node->name) != 0) {
	curr_obj_node = curr_obj_node->next;
    }
    if (NULL == curr_obj_node) {
	obj_table[objname_hash] = curr_obj_node = 
	    new_obj_node(si, obj_table[objname_hash]);
    }

    return curr_obj_node;
}


static __inline__ 
file_node* new_file_node(Char filename[FILENAME_LEN],
			 obj_node* obj, file_node* next)
{
  Int i;
  file_node* new = VG_(malloc)(sizeof(file_node));
  new->name  = VG_(strdup)(filename);
  for (i = 0; i < N_FN_ENTRIES; i++) {
    new->fns[i] = NULL;
  }
  SK_(stat).distinct_files++;
  new->number  = SK_(stat).distinct_files;
  new->obj     = obj;
  new->next      = next;
  return new;
}

 
file_node* SK_(get_file_node)(obj_node* curr_obj_node,
			      Char filename[FILENAME_LEN])
{
    file_node* curr_file_node;
    UInt       filename_hash;

    /* lookup in file hash */
    filename_hash = str_hash(filename, N_FILE_ENTRIES);
    curr_file_node = curr_obj_node->files[filename_hash];
    while (NULL != curr_file_node && 
	   VG_(strcmp)(filename, curr_file_node->name) != 0) {
	curr_file_node = curr_file_node->next;
    }
    if (NULL == curr_file_node) {
	curr_obj_node->files[filename_hash] = curr_file_node = 
	    new_file_node(filename, curr_obj_node, 
			  curr_obj_node->files[filename_hash]);
    }

    return curr_file_node;
}

/* forward decl. */
static void resize_fn_array();

static __inline__ 
fn_node* new_fn_node(Char fnname[FILENAME_LEN],
		     file_node* file, fn_node* next)
{
    fn_node* new = VG_(malloc)(sizeof(fn_node));
    new->name = VG_(strdup)(fnname);

    SK_(stat).distinct_fns++;
    new->number   = SK_(stat).distinct_fns;
    new->last_cxt = 0;
    new->pure_cxt = 0;
    new->file     = file;
    new->next     = next;

    new->dump_before  = False;
    new->dump_after   = False;
    new->zero_before  = False;
    new->toggle_collect = False;
    new->skip         = False;
    new->group        = 0;
    new->separate_callers    = SK_(clo).separate_callers;
    new->separate_recursions = SK_(clo).separate_recursions;

#if CT_ENABLE_DEBUG
    new->verbosity    = -1;
#endif

    if (SK_(stat).distinct_fns >= current_fn_active.size)
	resize_fn_array();

    return new;
}


/* Get a function node in hash2 with known file node.
 * hash nodes are created if needed
 */
static
fn_node* get_fn_node_infile(file_node* curr_file_node,
			    Char fnname[FN_NAME_LEN])
{
    fn_node* curr_fn_node;
    UInt     fnname_hash;

    CT_ASSERT(curr_file_node != 0);

    /* lookup in function hash */
    fnname_hash = str_hash(fnname, N_FN_ENTRIES);
    curr_fn_node = curr_file_node->fns[fnname_hash];
    while (NULL != curr_fn_node && 
	   VG_(strcmp)(fnname, curr_fn_node->name) != 0) {
	curr_fn_node = curr_fn_node->next;
    }
    if (NULL == curr_fn_node) {
	curr_file_node->fns[fnname_hash] = curr_fn_node = 
            new_fn_node(fnname, curr_file_node,
			curr_file_node->fns[fnname_hash]);
    }

    return curr_fn_node;
}


/* Get a function node in a Valgrind Segment.
 * Hash nodes are created if needed.
 */
static __inline__
fn_node* get_fn_node_inseg(SegInfo* si,
			   Char filename[FILENAME_LEN],
			   Char fnname[FN_NAME_LEN])
{
  obj_node  *obj  = SK_(get_obj_node)(si);
  file_node *file = SK_(get_file_node)(obj, filename);
  fn_node   *fn   = get_fn_node_infile(file, fnname);

  return fn;
}


Bool SK_(get_debug_info)(Addr instr_addr, 
			 Char filename[FILENAME_LEN],
			 Char fn_name[FN_NAME_LEN], Int* line_num,
			 SegInfo** pSegInfo)
{
  Bool found1, found2, result = True;
  int line;
  
  CT_DEBUG(6, "  + get_debug_info(0x%08x)\n", instr_addr);

  if (pSegInfo) {
      *pSegInfo = VG_(get_obj)(instr_addr);

      // for generated code in anonymous space, pSegInfo is 0
   }

   found1 = VG_(get_filename_linenum)(instr_addr, filename,
				      FILENAME_LEN, &line);
   found2 = VG_(get_fnname)(instr_addr, 
			    fn_name, FN_NAME_LEN);

   if (!found1 && !found2) {
     SK_(stat).no_debug_BBs++;
     VG_(strcpy)(filename, "???");
     VG_(strcpy)(fn_name,  "???");
     if (line_num) *line_num=0;
     result = False;

   } else if ( found1 &&  found2) {
     SK_(stat).full_debug_BBs++;
     if (line_num) *line_num=line;

   } else if ( found1 && !found2) {
     SK_(stat).file_line_debug_BBs++;
     VG_(strcpy)(fn_name,  "???");
     if (line_num) *line_num=line;

   } else  /*(!found1 &&  found2)*/ {
     SK_(stat).fn_name_debug_BBs++;
     VG_(strcpy)(filename, "???");
     if (line_num) *line_num=0;
   }

   CT_DEBUG(6, "  - get_debug_info(0x%08x): seg %s\n, fn %s\n",
	    instr_addr,
	    !pSegInfo   ? (const UChar*)"-" :
	    (*pSegInfo) ? VG_(seg_filename)(*pSegInfo) :
	    (const UChar*)"(None)",
	    fn_name);

  return result;
}

/* for _libc_freeres_wrapper => _exit renaming */
static BB* exit_bb = 0;

/*
 * Attach function struct to a BB from debug info.
 */
fn_node* SK_(get_fn_node)(BB* bb)
{
    Char       filename[FILENAME_LEN], fnname[FN_NAME_LEN];
    SegInfo*   si;
    Int        line_num;
    fn_node*   fn;

    /* fn from debug info is idempotent for a BB */
    if (bb->fn) return bb->fn;

    CT_DEBUG(3,"+ get_fn_node(BB 0x%x)\n", bb_addr(bb));

    SK_(get_debug_info)(bb_addr(bb),
			filename, fnname, &line_num, &si);

    if (0 == VG_(strcmp)(fnname, "???")) {
	/* Use address as found in library */
	VG_(sprintf)(fnname, "0x%08x%s", 
		     bb->offset,
		     (bb->sect_kind == Vg_SectData) ? " [Data]" :
		     (bb->sect_kind == Vg_SectBSS)  ? " [BSS]"  :
		     (bb->sect_kind == Vg_SectGOT)  ? " [GOT]"  :
		     (bb->sect_kind == Vg_SectPLT)  ? " [PLT]"  : "");
    }
    else {
      if (VG_(get_fnname_if_entry)(bb_addr(bb), fnname, FN_NAME_LEN))
	bb->is_entry = 1;
    }

    /* HACK for correct _exit: 
     * _exit is redirected to VG_(__libc_freeres_wrapper) by valgrind,
     * so we rename it back again :-)
     */
    if (0 == VG_(strcmp)(fnname, "vgPlain___libc_freeres_wrapper")
	&& exit_bb) {
      SK_(get_debug_info)(bb_addr(exit_bb),
			  filename, fnname, &line_num, &si);
	
	CT_DEBUG(1, "__libc_freeres_wrapper renamed to _exit\n");
    }
    if (0 == VG_(strcmp)(fnname, "_exit") && !exit_bb)
	exit_bb = bb;
    

    fn = get_fn_node_inseg( si, filename, fnname);

    if (bb->sect_kind == Vg_SectPLT)	
	fn->skip = SK_(clo).skip_plt;
    
    SK_(update_fn_config)(fn);

    /* Every function gets a "pure" context, i.e. a context with stack
     * depth 1 only with this function. This is for compression of mangled
     * names
     */
    if (!fn->pure_cxt) {
	fn_node* pure[2];
	pure[0] = 0;
	pure[1] = fn;
	fn->pure_cxt = SK_(get_cxt)(pure+1);
    }

    bb->fn   = fn;
    bb->line = line_num;

    CT_DEBUG(3,"- get_fn_node(BB 0x%x): %s (in %s:%d)\n",
	     bb_addr(bb), fnname, filename, line_num);

    return fn;
}


/*------------------------------------------------------------*/
/*--- Active function array operations                     ---*/
/*------------------------------------------------------------*/

/* The active function array is a thread-specific array
 * of UInts, mapping function numbers to the active count of
 * functions.
 * The active count is the number of times a function appears
 * in the current call stack, and is used when costs for recursion
 * levels should be separated.
 */

UInt* SK_(get_fn_entry)(Int n)
{
  CT_ASSERT(n < current_fn_active.size);
  return current_fn_active.array + n;
}

void SK_(init_fn_array)(fn_array* a)
{
  Int i;

  CT_ASSERT(a != 0);

  a->size = N_INITIAL_FN_ARRAY_SIZE;
  if (a->size <= SK_(stat).distinct_fns)
    a->size = SK_(stat).distinct_fns+1;
  
  a->array = (UInt*) VG_(malloc)(a->size * sizeof(UInt));
  for(i=0;i<a->size;i++)
    a->array[i] = 0;
}

void SK_(copy_current_fn_array)(fn_array* dst)
{
  CT_ASSERT(dst != 0);

  dst->size  = current_fn_active.size;
  dst->array = current_fn_active.array;
}

fn_array* SK_(get_current_fn_array)()
{
  return &current_fn_active;
}

void SK_(set_current_fn_array)(fn_array* a)
{
  CT_ASSERT(a != 0);

  current_fn_active.size  = a->size;
  current_fn_active.array = a->array;
  if (current_fn_active.size <= SK_(stat).distinct_fns)
    resize_fn_array();
}

/* ensure that active_array is big enough:
 *  <distinct_fns> is the highest index, so <fn_active_array_size>
 *  has to be bigger than that.
 */
static void resize_fn_array()
{
    UInt* new;
    Int i, newsize;

    newsize = current_fn_active.size;
    while (newsize <= SK_(stat).distinct_fns) newsize *=2;

    CT_DEBUG(0, "Resize fn_active_array: %d => %d\n",
	     current_fn_active.size, newsize);

    new = VG_(malloc)(newsize * sizeof(UInt));
    for(i=0;i<current_fn_active.size;i++)
      new[i] = current_fn_active.array[i];
    while(i<newsize)
	new[i++] = 0;

    VG_(free)(current_fn_active.array);
    current_fn_active.size = newsize;
    current_fn_active.array = new;
    SK_(stat).fn_array_resizes++;
}




syntax highlighted by Code2HTML, v. 0.9.1