/* Tracing support for CGEN-based simulators.
   Copyright (C) 1996, 1997 Free Software Foundation, Inc.
   Contributed by Cygnus Support.

This file is part of GDB, the GNU debugger.

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, 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.  */

#include "sim-main.h"
#include "bfd.h"
#include "cpu-opc.h"

#ifndef SIZE_INSTRUCTION
#define SIZE_INSTRUCTION 16
#endif

#ifndef SIZE_LOCATION
#define SIZE_LOCATION 20
#endif

#ifndef SIZE_PC
#define SIZE_PC 6
#endif

#ifndef SIZE_LINE_NUMBER
#define SIZE_LINE_NUMBER 4
#endif

#ifndef SIZE_CYCLE_COUNT
#define SIZE_CYCLE_COUNT 2
#endif

#ifndef SIZE_TOTAL_CYCLE_COUNT
#define SIZE_TOTAL_CYCLE_COUNT 9
#endif

/* Text is queued in TRACE_BUF because we want to output the insn's cycle
   count first but that isn't known until after the insn has executed.  */
static char trace_buf[1024];
/* If NULL, output to stdout directly.  */
static char *bufptr;

/* For computing an instruction's cycle count.
   FIXME: Need to move into cpu struct for smp case.  */
static unsigned long last_cycle_count;

void
trace_insn_init (SIM_CPU *cpu)
{
  bufptr = trace_buf;
  *bufptr = 0;
}

void
trace_insn_fini (SIM_CPU *cpu)
{
  if (CPU_PROFILE_FLAGS (cpu) [PROFILE_MODEL_IDX])
    {
      unsigned long total = PROFILE_TOTAL_CYCLE_COUNT (CPU_PROFILE_DATA (cpu));
      trace_printf (cpu, "%-*ld %-*ld ",
		    SIZE_CYCLE_COUNT, total - last_cycle_count,
		    SIZE_TOTAL_CYCLE_COUNT, total);
      last_cycle_count = total;
    }

  trace_printf (cpu, "%s\n", trace_buf);
}

/* For communication between trace_insn and trace_result.  */
static int printed_result_p;

void
trace_insn (SIM_CPU *cpu, const struct cgen_insn *opcode,
	    const struct argbuf *abuf, PCADDR pc)
{
  const char *filename;
  const char *functionname;
  unsigned int linenumber;
  char *p, buf[256], disasm_buf[50];

  if (! TRACE_P (cpu, TRACE_LINENUM_IDX))
    {
      cgen_trace_printf (cpu, "0x%.*x %-*s ",
			 SIZE_PC, (unsigned) pc,
			 SIZE_INSTRUCTION,
			 CGEN_INSN_SYNTAX (opcode)->mnemonic);
      return;
    }

  buf[0] = 0;
  
  if (STATE_TEXT_SECTION (CPU_STATE (cpu))
      && pc >= STATE_TEXT_START (CPU_STATE (cpu))
      && pc < STATE_TEXT_END (CPU_STATE (cpu)))
    {
      filename = (const char *) 0;
      functionname = (const char *) 0;
      linenumber = 0;
      if (bfd_find_nearest_line (STATE_PROG_BFD (CPU_STATE (cpu)),
				 STATE_TEXT_SECTION (CPU_STATE (cpu)),
				 (struct symbol_cache_entry **) 0,
				 pc - STATE_TEXT_START (CPU_STATE (cpu)),
				 &filename, &functionname, &linenumber))
	{
	  p = buf;
	  if (linenumber)
	    {
	      sprintf (p, "#%-*d ", SIZE_LINE_NUMBER, linenumber);
	      p += strlen (p);
	    }
	  else
	    {
	      sprintf (p, "%-*s ", SIZE_LINE_NUMBER+1, "---");
	      p += SIZE_LINE_NUMBER+2;
	    }

	  if (functionname)
	    {
	      sprintf (p, "%s ", functionname);
	      p += strlen (p);
	    }
	  else if (filename)
	    {
	      char *q = (char *) strrchr (filename, '/');
	      sprintf (p, "%s ", (q) ? q+1 : filename);
	      p += strlen (p);
	    }

	  if (*p == ' ')
	    *p = '\0';
	}
    }

  sim_disassemble_insn (opcode, abuf, pc, disasm_buf);

  cgen_trace_printf (cpu, "0x%.*x %-*.*s %-*s ",
		     SIZE_PC, (unsigned) pc,
		     SIZE_LOCATION, SIZE_LOCATION, buf,
		     SIZE_INSTRUCTION,
#if 0
		     CGEN_INSN_SYNTAX (opcode)->mnemonic
#else
		     disasm_buf
#endif
		);

  printed_result_p = 0;
}

void
trace_extract (SIM_CPU *cpu, PCADDR pc, char *name, ...)
{
  va_list args;
  int printed_one_p = 0;
  char *fmt;

  va_start (args, name);

  trace_printf (cpu, "Extract: 0x%.*x: %s ", SIZE_PC, pc, name);

  do {
    int type,ival;

    fmt = va_arg (args, char *);

    if (fmt)
      {
	if (printed_one_p)
	  trace_printf (cpu, ", ");
	printed_one_p = 1;
	type = va_arg (args, int);
	switch (type)
	  {
	  case 'x' :
	    ival = va_arg (args, int);
	    trace_printf (cpu, fmt, ival);
	    break;
	  default :
	    abort ();
	  }
      }
  } while (fmt);

  va_end (args);
  trace_printf (cpu, "\n");
}

void
trace_result (SIM_CPU *cpu, char *name, int type, ...)
{
  va_list args;

  va_start (args, type);
  if (printed_result_p)
    cgen_trace_printf (cpu, ", ");
  switch (type)
    {
    case 'x' :
    default :
      cgen_trace_printf (cpu, "%s <- 0x%x", name, va_arg (args, int));
      break;
    case 'D' :
      {
	DI di;
	/* this is separated from previous line for sunos cc */
	di = va_arg (args, DI);
	cgen_trace_printf (cpu, "%s <- 0x%x%08x", name,
			   GETHIDI(di), GETLODI (di));
	break;
      }
    }
  printed_result_p = 1;
  va_end (args);
}

/* Print trace output to BUFPTR if active, otherwise print normally.
   This is only for tracing semantic code.  */

void
cgen_trace_printf (SIM_CPU *cpu, char *fmt, ...)
{
  va_list args;

  va_start (args, fmt);

  if (bufptr == NULL)
    {
      if (TRACE_FILE (CPU_TRACE_DATA (cpu)) == NULL)
	(* STATE_CALLBACK (CPU_STATE (cpu))->evprintf_filtered)
	  (STATE_CALLBACK (CPU_STATE (cpu)), fmt, args);
      else
	vfprintf (TRACE_FILE (CPU_TRACE_DATA (cpu)), fmt, args);
    }
  else
    {
      vsprintf (bufptr, fmt, args);
      bufptr += strlen (bufptr);
    }

  va_end (args);
}