/*--------------------------------------------------------------------*/
/*--- Callgrind                                                    ---*/
/*---                                               ct_callstack.c ---*/
/*--------------------------------------------------------------------*/

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

   Copyright (C) 2002-2012, 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 "global.h"

/*------------------------------------------------------------*/
/*--- Call stack, operations                               ---*/
/*------------------------------------------------------------*/

/* Stack of current thread. Gets initialized when switching to 1st thread.
 *
 * The artificial call stack is an array of call_entry's, representing
 * stack frames of the executing program. 
 * Array call_stack and call_stack_esp have same size and grow on demand.
 * Array call_stack_esp holds SPs of corresponding stack frames.
 *
 */

#define N_CALL_STACK_INITIAL_ENTRIES 500

call_stack CLG_(current_call_stack);

void CLG_(init_call_stack)(call_stack* s)
{
  Int i;

  CLG_ASSERT(s != 0);

  s->size = N_CALL_STACK_INITIAL_ENTRIES;   
  s->entry = (call_entry*) CLG_MALLOC("cl.callstack.ics.1",
                                      s->size * sizeof(call_entry));
  s->sp = 0;
  s->entry[0].cxt = 0; /* for assertion in push_cxt() */

  for(i=0; i<s->size; i++) s->entry[i].enter_cost = 0;
}

call_entry* CLG_(get_call_entry)(Int sp)
{
  CLG_ASSERT(sp <= CLG_(current_call_stack).sp);
  return &(CLG_(current_call_stack).entry[sp]);
}

void CLG_(copy_current_call_stack)(call_stack* dst)
{
  CLG_ASSERT(dst != 0);

  dst->size  = CLG_(current_call_stack).size;
  dst->entry = CLG_(current_call_stack).entry;
  dst->sp    = CLG_(current_call_stack).sp;
}

void CLG_(set_current_call_stack)(call_stack* s)
{
  CLG_ASSERT(s != 0);

  CLG_(current_call_stack).size  = s->size;
  CLG_(current_call_stack).entry = s->entry;
  CLG_(current_call_stack).sp    = s->sp;
}


static __inline__
void ensure_stack_size(Int i)
{
  Int oldsize;
  call_stack *cs = &CLG_(current_call_stack);

  if (i < cs->size) return;

  oldsize = cs->size;
  cs->size *= 2;
  while (i > cs->size) cs->size *= 2;

  cs->entry = (call_entry*) VG_(realloc)("cl.callstack.ess.1",
                                         cs->entry,
					 cs->size * sizeof(call_entry));

  for(i=oldsize; i<cs->size; i++)
    cs->entry[i].enter_cost = 0;

  CLG_(stat).call_stack_resizes++;
 
  CLG_DEBUGIF(2)
    VG_(printf)("        call stack enlarged to %d entries\n",
		CLG_(current_call_stack).size);
}



/* Called when function entered nonrecursive */
static void function_entered(fn_node* fn)
{
  CLG_ASSERT(fn != 0);

#if CLG_ENABLE_DEBUG
  if (fn->verbosity >=0) {
    Int old = CLG_(clo).verbose;
    CLG_(clo).verbose = fn->verbosity;
    fn->verbosity = old;
    VG_(message)(Vg_DebugMsg, 
		 "Entering %s: Verbosity set to %d\n",
		 fn->name, CLG_(clo).verbose);
  }
#endif		
	    
  if (fn->dump_before) {
    Char trigger[FN_NAME_LEN];
    VG_(sprintf)(trigger, "--dump-before=%s", fn->name);
    CLG_(dump_profile)(trigger, True);
  }
  else if (fn->zero_before) {
    CLG_(zero_all_cost)(True);
  }

  if (fn->toggle_collect) {
    CLG_(current_state).collect = !CLG_(current_state).collect;
    CLG_DEBUG(2,"   entering %s: toggled collection state to %s\n",
	     fn->name,
	     CLG_(current_state).collect ? "ON" : "OFF");
  }
}	

/* Called when function left (no recursive level active) */
static void function_left(fn_node* fn)
{
  CLG_ASSERT(fn != 0);

  if (fn->dump_after) {
    Char trigger[FN_NAME_LEN];
    VG_(sprintf)(trigger, "--dump-after=%s", fn->name);
    CLG_(dump_profile)(trigger, True);
  }
  if (fn->toggle_collect) {
    CLG_(current_state).collect = !CLG_(current_state).collect;
    CLG_DEBUG(2,"   leaving %s: toggled collection state to %s\n",
	     fn->name,
	     CLG_(current_state).collect ? "ON" : "OFF");
  }

#if CLG_ENABLE_DEBUG
  if (fn->verbosity >=0) {
    Int old = CLG_(clo).verbose;
    CLG_(clo).verbose = fn->verbosity;
    fn->verbosity = old;
    VG_(message)(Vg_DebugMsg, 
		 "Leaving %s: Verbosity set back to %d\n",
		 fn->name, CLG_(clo).verbose);
  }
#endif		
}


/* Push call on call stack.
 *
 * Increment the usage count for the function called.
 * A jump from <from> to <to>, with <sp>.
 * If <skip> is true, this is a call to a function to be skipped;
 * for this, we set jcc = 0.
 */
void CLG_(push_call_stack)(BBCC* from, UInt jmp, BBCC* to, Addr sp, Bool skip)
{
    jCC* jcc;
    UInt* pdepth;
    call_entry* current_entry;
    Addr ret_addr;

    /* Ensure a call stack of size <current_sp>+1.
     * The +1 is needed as push_cxt will store the
     * context at [current_sp]
     */
    ensure_stack_size(CLG_(current_call_stack).sp +1);
    current_entry = &(CLG_(current_call_stack).entry[CLG_(current_call_stack).sp]);

    if (skip) {
	jcc = 0;
    }
    else {
	fn_node* to_fn = to->cxt->fn[0];

	if (CLG_(current_state).nonskipped) {
	    /* this is a jmp from skipped to nonskipped */
	    CLG_ASSERT(CLG_(current_state).nonskipped == from);
	}

	/* As push_cxt() has to be called before push_call_stack if not
	 * skipping, the old context should already be saved on the stack */
	CLG_ASSERT(current_entry->cxt != 0);
	CLG_(copy_cost_lz)( CLG_(sets).full, &(current_entry->enter_cost),
			   CLG_(current_state).cost );

	jcc = CLG_(get_jcc)(from, jmp, to);
	CLG_ASSERT(jcc != 0);

	pdepth = CLG_(get_fn_entry)(to_fn->number);
	if (CLG_(clo).skip_direct_recursion) {
	    /* only increment depth if another function is called */
	  if (jcc->from->cxt->fn[0] != to_fn) (*pdepth)++;
	}
	else (*pdepth)++;

	if (*pdepth>1)
	  CLG_(stat).rec_call_counter++;
	
	jcc->call_counter++;
	CLG_(stat).call_counter++;

	if (*pdepth == 1) function_entered(to_fn);
    }

    /* return address is only is useful with a real call;
     * used to detect RET w/o CALL */
    if (from->bb->jmp[jmp].jmpkind == jk_Call) {
      UInt instr = from->bb->jmp[jmp].instr;
      ret_addr = bb_addr(from->bb) +
	from->bb->instr[instr].instr_offset +
	from->bb->instr[instr].instr_size;
    }
    else
      ret_addr = 0;

    /* put jcc on call stack */
    current_entry->jcc = jcc;
    current_entry->sp = sp;
    current_entry->ret_addr = ret_addr;
    current_entry->nonskipped = CLG_(current_state).nonskipped;

    CLG_(current_call_stack).sp++;

    /* To allow for above assertion we set context of next frame to 0 */
    CLG_ASSERT(CLG_(current_call_stack).sp < CLG_(current_call_stack).size);
    current_entry++;
    current_entry->cxt = 0;

    if (!skip)
	CLG_(current_state).nonskipped = 0;
    else if (!CLG_(current_state).nonskipped) {
	/* a call from nonskipped to skipped */
	CLG_(current_state).nonskipped = from;
	if (!CLG_(current_state).nonskipped->skipped) {
	  CLG_(init_cost_lz)( CLG_(sets).full,
			     &CLG_(current_state).nonskipped->skipped);
	  CLG_(stat).distinct_skips++;
	}
    }

#if CLG_ENABLE_DEBUG
    CLG_DEBUGIF(0) {
	if (CLG_(clo).verbose<2) {
	  if (jcc && jcc->to && jcc->to->bb) {
	    char spaces[][41] = { "   .   .   .   .   .   .   .   .   .   .",
				  "  .   .   .   .   .   .   .   .   .   . ",
				  " .   .   .   .   .   .   .   .   .   .  ",
				  ".   .   .   .   .   .   .   .   .   .   " };

	    int s = CLG_(current_call_stack).sp;
	    Int* pars = (Int*) sp;

	    BB* bb = jcc->to->bb;
	    if (s>40) s=40;
	    VG_(printf)("%s> %s(0x%x, 0x%x, ...) [%s / %#lx]\n", spaces[s%4]+40-s, bb->fn->name,
                        pars ? pars[1]:0,
			pars ? pars[2]:0,
			bb->obj->name + bb->obj->last_slash_pos,
			bb->offset);
	  }
	}
	else if (CLG_(clo).verbose<4) {
	    VG_(printf)("+ %2d ", CLG_(current_call_stack).sp);
	    CLG_(print_short_jcc)(jcc);
	    VG_(printf)(", SP %#lx, RA %#lx\n", sp, ret_addr);
	}
	else {
	    VG_(printf)("  Pushed ");
	    CLG_(print_stackentry)(3, CLG_(current_call_stack).sp-1);
	}
    }
#endif

}


/* Pop call stack and update inclusive sums.
 * Returns modified fcc.
 *
 * If the JCC becomes inactive, call entries are freed if possible
 */
void CLG_(pop_call_stack)()
{
    jCC* jcc;
    Int depth = 0;
    call_entry* lower_entry;

    if (CLG_(current_state).sig >0) {
	/* Check if we leave a signal handler; this can happen when
	 * calling longjmp() in the handler */
	CLG_(run_post_signal_on_call_stack_bottom)();
    }

    lower_entry =
	&(CLG_(current_call_stack).entry[CLG_(current_call_stack).sp-1]);

    CLG_DEBUG(4,"+ pop_call_stack: frame %d, jcc %p\n", 
		CLG_(current_call_stack).sp, lower_entry->jcc);

    /* jCC item not any more on real stack: pop */
    jcc = lower_entry->jcc;
    CLG_(current_state).nonskipped = lower_entry->nonskipped;

    if (jcc) {
	fn_node* to_fn  = jcc->to->cxt->fn[0];
	UInt* pdepth =  CLG_(get_fn_entry)(to_fn->number);
	if (CLG_(clo).skip_direct_recursion) {
	    /* only decrement depth if another function was called */
	  if (jcc->from->cxt->fn[0] != to_fn) (*pdepth)--;
	}
	else (*pdepth)--;
	depth = *pdepth;

	/* add cost difference to sum */
	if ( CLG_(add_diff_cost_lz)( CLG_(sets).full, &(jcc->cost),
				    lower_entry->enter_cost,
				    CLG_(current_state).cost) ) {
	    
	  /* only count this call if it attributed some cost.
	   * the ret_counter is used to check if a BBCC dump is needed.
	   */
	  jcc->from->ret_counter++;
	}
	CLG_(stat).ret_counter++;

	/* restore context */
	CLG_(current_state).cxt  = lower_entry->cxt;
	CLG_(current_fn_stack).top =
	  CLG_(current_fn_stack).bottom + lower_entry->fn_sp;
	CLG_ASSERT(CLG_(current_state).cxt != 0);

	if (depth == 0) function_left(to_fn);
    }

    /* To allow for an assertion in push_call_stack() */
    lower_entry->cxt = 0;

    CLG_(current_call_stack).sp--;

#if CLG_ENABLE_DEBUG
    CLG_DEBUGIF(1) {
	if (CLG_(clo).verbose<4) {
	    if (jcc) {
		/* popped JCC target first */
		VG_(printf)("- %2d %#lx => ",
			    CLG_(current_call_stack).sp,
			    bb_addr(jcc->to->bb));
		CLG_(print_addr)(bb_jmpaddr(jcc->from->bb));
		VG_(printf)(", SP %#lx\n",
			    CLG_(current_call_stack).entry[CLG_(current_call_stack).sp].sp);
		CLG_(print_cost)(10, CLG_(sets).full, jcc->cost);
	    }
	    else
		VG_(printf)("- %2d [Skipped JCC], SP %#lx\n",
			    CLG_(current_call_stack).sp,
			    CLG_(current_call_stack).entry[CLG_(current_call_stack).sp].sp);
	}
	else {
	    VG_(printf)("  Popped ");
	    CLG_(print_stackentry)(7, CLG_(current_call_stack).sp);
	    if (jcc) {
		VG_(printf)("       returned to ");
		CLG_(print_addr_ln)(bb_jmpaddr(jcc->from->bb));
	    }
	}
    }
#endif

}


/* Unwind enough CallStack items to sync with current stack pointer.
 * Returns the number of stack frames unwinded.
 */
Int CLG_(unwind_call_stack)(Addr sp, Int minpops)
{
    Int csp;
    Int unwind_count = 0;
    CLG_DEBUG(4,"+ unwind_call_stack(sp %#lx, minpops %d): frame %d\n",
	      sp, minpops, CLG_(current_call_stack).sp);

    /* We pop old stack frames.
     * For a call, be p the stack address with return address.
     *  - call_stack_esp[] has SP after the CALL: p-4
     *  - current sp is after a RET: >= p
     */
    
    while( (csp=CLG_(current_call_stack).sp) >0) {
	call_entry* top_ce = &(CLG_(current_call_stack).entry[csp-1]);

	if ((top_ce->sp < sp) ||
	    ((top_ce->sp == sp) && minpops>0)) {

	    minpops--;
	    unwind_count++;
	    CLG_(pop_call_stack)();
	    csp=CLG_(current_call_stack).sp;
	    continue;
	}
	break;
    }

    CLG_DEBUG(4,"- unwind_call_stack\n");
    return unwind_count;
}
