/*
 * Copyright (c) 1983, 1993, 1998
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "gprof.h"
#include "search_list.h"
#include "source.h"
#include "symtab.h"
#include "cg_arcs.h"
#include "corefile.h"
#include "hist.h"

/*
 * Opcodes of the call instructions:
 */
#define OP_Jxx	0x1aU
#define	OP_BSR	0x34U

#define Jxx_FUNC_JMP		0U
#define Jxx_FUNC_JSR		1U
#define Jxx_FUNC_RET		2U
#define Jxx_FUNC_JSR_COROUTINE	3U

/* *INDENT-OFF* */
/* Here to document only.  We can't use this when cross compiling as
   the bitfield layout might not be the same as native.

   typedef union
     {
       struct
         {
	   unsigned other:26;
	   unsigned op_code:6;
         }
       a;				-- any format
       struct
         {
	   int disp:21;
	   unsigned ra:5;
	   unsigned op_code:6;
         }
       b;				-- branch format
       struct
         {
	   int hint:14;
	   unsigned func:2;
	   unsigned rb:5;
	   unsigned ra:5;
	   unsigned op_code:6;
         }
       j;				-- jump format
     }
    alpha_Instruction;
*/
/* *INDENT-ON* */

static Sym indirect_child;

void alpha_find_call (Sym *, bfd_vma, bfd_vma);

/*
 * On the Alpha we can only detect PC relative calls, which are
 * usually generated for calls to functions within the same
 * object file only.  This is still better than nothing, however.
 * (In particular it should be possible to find functions that
 *  potentially call integer division routines, for example.)
 */
void
alpha_find_call (Sym *parent, bfd_vma p_lowpc, bfd_vma p_highpc)
{
  bfd_vma pc, dest_pc;
  unsigned int insn;
  Sym *child;
  Sym_Table *symtab = get_symtab ();

  if (indirect_child.name == NULL)
    {
      sym_init (&indirect_child);
      indirect_child.name = _("<indirect child>");
      indirect_child.cg.prop.fract = 1.0;
      indirect_child.cg.cyc.head = &indirect_child;
    }

  DBG (CALLDEBUG, printf (_("[find_call] %s: 0x%lx to 0x%lx\n"),
			  parent->name, (unsigned long) p_lowpc,
			  (unsigned long) p_highpc));
  p_lowpc = (p_lowpc + 3) & ~3;
  p_highpc &= ~3;
  for (pc = p_lowpc; pc < p_highpc; pc += 4)
    {
      insn = bfd_get_32 (core_bfd, ((unsigned char *) core_text_space
				    + pc - core_text_sect->vma));
      switch (insn & (0x3fU << 26))
	{
	case OP_Jxx << 26:
	  /*
	   * There is no simple and reliable way to determine the
	   * target of a jsr (the hint bits help, but there aren't
	   * enough bits to get a satisfactory hit rate).  Instead,
	   * for any indirect jump we simply add an arc from PARENT
	   * to INDIRECT_CHILD---that way the user it at least able
	   * to see that there are other calls as well.
	   */
	  if ((insn & (3 << 14)) == Jxx_FUNC_JSR << 14
	      || (insn & (3 << 14)) == Jxx_FUNC_JSR_COROUTINE << 14)
	    {
	      DBG (CALLDEBUG,
		   printf (_("[find_call] 0x%lx: jsr%s <indirect_child>\n"),
			   (unsigned long) pc,
			   ((insn & (3 << 14)) == Jxx_FUNC_JSR << 14
			    ? "" : "_coroutine")));
	      arc_add (parent, &indirect_child, (unsigned long) 0);
	    }
	  break;

	case OP_BSR << 26:
	  DBG (CALLDEBUG,
	       printf (_("[find_call] 0x%lx: bsr"), (unsigned long) pc));
	  /*
	   * Regular PC relative addressing.  Check that this is the
	   * address of a function.  The linker sometimes redirects
	   * the entry point by 8 bytes to skip loading the global
	   * pointer, so we allow for either address:
	   */
	  dest_pc = pc + 4 + (((bfd_signed_vma) (insn & 0x1fffff)
			       ^ 0x100000) - 0x100000);
	  if (hist_check_address (dest_pc))
	    {
	      child = sym_lookup (symtab, dest_pc);
              if (child)
                {
	          DBG (CALLDEBUG,
		       printf (" 0x%lx\t; name=%s, addr=0x%lx",
			       (unsigned long) dest_pc, child->name,
			       (unsigned long) child->addr));
	          if (child->addr == dest_pc || child->addr == dest_pc - 8)
		    {
		      DBG (CALLDEBUG, printf ("\n"));
		      /* a hit:  */
		      arc_add (parent, child, (unsigned long) 0);
		      continue;
		    }
                }
	    }
	  /*
	   * Something funny going on.
	   */
	  DBG (CALLDEBUG, printf ("\tbut it's a botch\n"));
	  break;

	default:
	  break;
	}
    }
}
