/* AE program profiling system.
   Code to produce schemas and instrument assembly code.
   Copyright (C) 1989, 1990 by James R. Larus (larus@cs.wisc.edu)

   AE and AEC are 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 1, or (at your option) any
   later version.

   AE and AEC are 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 GNU CC; see the file COPYING.  If not, write to James R.
   Larus, Computer Sciences Department, University of Wisconsin--Madison,
   1210 West Dayton Street, Madison, WI 53706, USA or to the Free
   Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */


/* $Header: /var/home/larus/AE/AE/RCS/ae-output.c,v 2.2 90/02/14 09:20:01 larus Exp Locker: larus $ */


#include <stdio.h>
#include <string.h>
#include "config.h"
#include "rtl.h"
#include "basic-block.h"
#include "flags.h"
#include "regs.h"
#include "ae.h"
#include "ae-machine.h"
#include "schema.h"


void ae_after_block_end ();
void ae_after_call_insn ();
void ae_after_easy_insn ();
void ae_after_impossible_insn ();
void ae_after_load_insn ();
void ae_after_store_insn ();
void ae_before_block_end ();
void ae_before_call_insn ();
void ae_before_easy_insn ();
void ae_before_impossible_insn ();
void ae_before_load_insn ();
void ae_before_store_insn ();
void ae_block_start ();
void ae_function_end ();
void ae_function_start ();
int asm_insn_size ();
void block_end_cjump_schema ();
void block_end_jump_schema ();
void block_end_schema ();
void block_start_schema ();
void call_indirect_schema ();
void call_schema ();
void call_schema_str ();
int cjump_target_p ();
void compute_def_schema ();
int condjump_p ();
list defns_reaching_insn ();
void edge_schema ();
rtx find_block_head ();
void find_mem_refs ();
void flush_uneventful_insn ();
void free_list ();
void function_end_schema ();
void function_start_schema ();
rtx gen_rtx ();
int get_frame_size ();
int head_to_block_number ();
int implicit_last_block ();
void issue_address_event ();
void issue_event ();
void issue_event_space_check ();
void issue_short_event ();
void load_schema_int_offset ();
void load_schema_reg_offset ();
void load_symbol_schema ();
void load_unknown_schema ();
int multiway_jump_p ();
rtx next_insn_or_label ();
void note_stores ();
void output_asm_insn ();
void peek_ahead ();
void process_block_start ();
void process_insn_after ();
void process_insn_before ();
int record_fp ();
void record_insn_effects ();
int record_sp ();
void rtx_arg_out ();
char *rtx_op_out ();
void save_store_dest ();
void simple_def_schema ();
int simplejump_p ();
void store_schema_int_offset ();
void store_schema_reg_offset ();
void store_symbol_schema ();
void store_unknown_schema ();
int substring ();
void symbol_def_schema ();
void uneventful_schema ();
void unknown_def_schema ();


rtx *basic_block_end;
rtx *basic_block_head;

extern aux_insn_info *insn_info; /* Information on instructions */

extern char record_reg_on_entry []; /* Non-zero => save register upon entry. */

extern FILE *asm_out_file;	/* Assembly output file. */

extern FILE *schema_out_file;	/* Schema output file. */

extern char *fun_name;		/* Name of function being processed. */


/* Maintain a circular queue of the next MAX_PEEP instructions and
   their patterns, so we can look ahead (LA) to account for the effects
   of instructions deleted by peephole optimization. */

static rtx la_insn [MAX_PEEP];

static rtx la_pattern [MAX_PEEP];


/* H points one position before first item in queue.  T points to the
   last element in queue. */

static int la_h = 0, la_t = 0;


/* Non-zero if the assembly code for an instruction is not yet output. */

static int before_insn_flg;


/* Number of block being processed. */

static int block_no;


/* Used to retrieve destination of store instruction via NOTE_STORE. */

static rtx store_dest;


/* Used in THUNK_FOR_LOAD/STORE_INSN. */

static rtx check_insn;


/* Number of uneventful instructions since the last eventful one. */

static int num_uneventful_insn = 0;


/* Non-zero when outputing instructions to record event. */

static int issuing_event = 0;


/* Non-zero when processing rtx CALL insn. */

static int in_rtx_call_insn = 0;


/* rtx code for register containing pointer to AE buffer */

static rtx ae_buffer_pointer = NULL;


/* rtx code for register containing pointer to end of AE buffer */

#if (defined (AE_BUFFER_BOUND_REG) || defined (AE_BUFFER_BOUND_VAR))
static rtx ae_buffer_end_pointer = NULL;
#endif


/* Non-zero means last branch instruction was annuled and so did not
   execute its delay slots. */

#if (defined (JUMP_DELAY_SLOTS) || defined (CJUMP_DELAY_SLOTS))
static int annuled_branch_seen = 0;
#endif


/* Amount of space left in the AE buffer from the amount available
upon block entry. */

static int space_left_in_chunk = 0;



/* Invoke just before assembly code for an instruction or label is
   output.  Notice interesting events and issue a schema command and/or
   produce code to record the event. */

void
watch_insn_output (insn)
     register rtx insn;
{
  rtx par_1;
  register int i;

  if (GET_CODE (insn) == INSN
      && GET_CODE (PATTERN (insn)) == PARALLEL
      && (par_1 = XVECEXP (PATTERN (insn), 0, 0))
      && ! (GET_CODE (par_1) == SET
	    || GET_CODE (par_1) == USE
	    || GET_CODE (par_1) == CLOBBER
	    || GET_CODE (par_1) == CALL
	    || GET_CODE (par_1) == RETURN))
    /* Instruction munged by peephole optimization.  We don't have a
       real pattern that we can interpret and the following instruction
       is deleted.  Luckily, we record the patterns of instructions
       ahead of the current instruction. */
    {
      rtx next_insn = next_insn_or_label (insn, 1);
      register int i, num_examined = 0;

      while (la_t != la_h && la_insn [la_h] != next_insn)
	{
	  /* Examine effects of instructions up to next (undeleted)
	     instruction. */
	  if (la_insn [la_h])
	    record_insn_effects (la_insn [la_h], la_pattern [la_h]);
	  la_h = (la_h + 1) % MAX_PEEP;
	  num_examined += 1;
	}
      /* Record instructions MAX_PEEP ahead for this instruction
	 (minus 1 for deleted next instruction) and deleted instructions. */
      for (i = 0; i < num_examined; i ++)
	peek_ahead (next_insn_or_label (insn, MAX_PEEP - 1 + i));
    }
  else
    {
      peek_ahead (next_insn_or_label (insn, MAX_PEEP));
      record_insn_effects (insn, PATTERN (insn));
    }
}


/* Initialize the look ahead buffer by filling it with the first instructions
   in a function. */

void
initialize_look_ahead_buffer ()
{
  register int i;

  la_h = la_t = 0;
  if (n_basic_blocks > 0)
    for (i = 0; i < MAX_PEEP; i++)
      peek_ahead (insn_or_label (basic_block_head [0], i + 1));
}


/* Remember the instruction MAX_PEEP instructions further along, so
   that we can find its pattern even if it is destroyed by peephole
   optimization. */

static void
peek_ahead (nth_insn)
     rtx nth_insn;
{
  la_t = (la_t + 1) % MAX_PEEP;
  if (la_t == la_h) la_h = (la_h + 1) % MAX_PEEP;
  la_insn [la_t] = nth_insn;
  la_pattern [la_t] = (nth_insn == (rtx) NULL
		       || GET_CODE (nth_insn) == CODE_LABEL
		       ? NULL : PATTERN (nth_insn));
}


/* Capture the an instruction's events by either producing a schema or
   adding code to the assembly file to record values or both.  We
   maintain an one instruction look-ahead buffer so that routines for
   each type of instruction can be invoked before and after the assembly
   code for the instruction is produced. */

void
record_insn_effects (insn, pattern)
     register rtx insn, pattern;
{
  static rtx prev_insn = NULL;
  static rtx prev_pattern = NULL;

  if (prev_insn)
    process_insn_after (prev_insn, prev_pattern);
  if (insn)
    process_insn_before (insn, pattern);
  prev_insn = insn;
  prev_pattern = pattern;
}


/* Invoke the appropriate routine for each type of instruction BEFORE its
   assembly code is written to the file. */

static void
process_insn_before (insn, pattern)
     rtx insn, pattern;
{
  register aux_insn_info *info = &insn_info [INSN_UID (insn)];
  register int flags = info->flags;
  void thunk_for_load_insn ();

  store_dest = NULL;
  /* Function start is processed before any instructions.  Block start
     is process before instructions, but after labels. */
  if (flags & FUNCTION_START_FLAG)
    ae_function_start ();

  if ((flags & BLOCK_START_FLAG) && GET_CODE (insn) != CODE_LABEL)
    process_block_start (insn);

  if (GET_CODE (insn) == CODE_LABEL)
    ;
  else if (flags & (EASY_INSN_FLAG | HARD_INSN_FLAG))
    ae_before_easy_insn (insn, pattern);
  else if (GET_CODE (insn) == CALL_INSN)
    {
      ae_before_call_insn (insn, flags & IMPOSSIBLE_INSN_FLAG);
      if (flags & IMPOSSIBLE_INSN_FLAG)
	note_stores (pattern, save_store_dest);
      goto may_be_impossible;
    }
  else				/* Load and stores */
    {
      note_stores (pattern, save_store_dest);

      if (store_dest
	  && (GET_CODE (store_dest) == MEM
	      || GET_CODE (store_dest) == CONST))
	ae_before_store_insn (insn, store_dest);
      else
	{
	  check_insn = insn;
	  before_insn_flg = 1;
	  if (GET_CODE (pattern) == SET)
	    /* Only interested in loads at this point. */
	    find_mem_refs (SET_SRC (pattern), thunk_for_load_insn);
	  else
	    APPLY_TO_EXP_IN_INSN (pattern,
				  e,
				  find_mem_refs (e, thunk_for_load_insn));

	may_be_impossible:
	  if (flags & IMPOSSIBLE_INSN_FLAG)
	    ae_before_impossible_insn (insn, store_dest);
	}
    }

  if (flags & BLOCK_END_FLAG)
    ae_before_block_end (insn, block_no);
}


void
process_block_start (insn)
rtx insn;
{
  block_no = head_to_block_number (find_block_head (insn));
  ae_block_start (insn, block_no,
		  cjump_target_p (basic_block_head [block_no]));
}


/* Invoke the appropriate routine for each type of instruction AFTER its
   assembly code is written to the file. */

static void
process_insn_after (insn, pattern)
     rtx insn, pattern;
{
  register aux_insn_info *info = &insn_info [INSN_UID (insn)];
  register int flags = info->flags;
  void thunk_for_load_insn ();

  if ((flags & BLOCK_START_FLAG) && GET_CODE (insn) == CODE_LABEL)
    process_block_start (insn);

  if (GET_CODE (insn) == CODE_LABEL)
    ;
  else if (flags & (EASY_INSN_FLAG | HARD_INSN_FLAG))
    ae_after_easy_insn (insn, pattern);
  else if (GET_CODE (insn) == CALL_INSN)
    {
      ae_after_call_insn (insn, flags & IMPOSSIBLE_INSN_FLAG);
      if (flags & IMPOSSIBLE_INSN_FLAG)
	note_stores (pattern, save_store_dest);
      goto may_be_impossible;
    }
  else				/* Load and stores */
    {
      store_dest = NULL;
      note_stores (pattern, save_store_dest);

      if (store_dest
	  && (GET_CODE (store_dest) == MEM || GET_CODE (store_dest) == CONST))
	ae_after_store_insn (insn, store_dest);
      else
	{
	  check_insn = insn;
	  before_insn_flg = 0;
	  if (GET_CODE (pattern) == SET)
	    /* Only interested in loads at this point. */
	    find_mem_refs (SET_SRC (pattern), thunk_for_load_insn);
	  else
	    APPLY_TO_EXP_IN_INSN (pattern,
				  e,
				  find_mem_refs (e, thunk_for_load_insn));

	may_be_impossible:
	  if (flags & IMPOSSIBLE_INSN_FLAG)
	    ae_after_impossible_insn (insn, store_dest);
	}
    }

  if (flags & BLOCK_END_FLAG)
    ae_after_block_end (insn, block_no);
}


/* Set STORE_DEST to the destination of a store instruction. */

static void
save_store_dest (d, x) rtx d, x; {store_dest = d;}


static void
thunk_for_load_insn (exp, base, offset)
     rtx exp, base, offset;
{
   if (before_insn_flg)
     ae_before_load_insn (check_insn, exp);
   else
     ae_after_load_insn (check_insn, exp);
}


/* Return 1 if function contains an implicit final block and 0
   otherwise. */

int
implicit_last_block ()
{
  int l = n_basic_blocks - 1;

  if (l > 0)
    {
      rtx last_end = basic_block_end [l];
      if (GET_CODE (last_end) == JUMP_INSN
	  && !simplejump_p (last_end)
	  && condjump_p (last_end))
	return 1;
      else
	return 0;
    }
  return 0;
}



/* Table of instructions that take up more than one word.  These are
   usually pseudo-instructions allowed by the assembler.  Even elements
   are instructions' names.  Odd elements are the size (in number of
   words).  All other instructions are assumed to be STD_ASM_INSN_LENGTH
   bytes.  Table must be sorted by instruction name. */

static char *asm_insn_size_tbl [] = {
#ifdef ASM_INSN_SIZE_EXCEPTIONS
  ASM_INSN_SIZE_EXCEPTIONS
#else
  0
#endif
};


/* Invoked by OUTPUT_ASM_INSN before each instruction or label is
   written to assembly file. */

void
watch_asm_insn_output (template, operands)
     register char *template;
     rtx *operands;
{
  register char *e;

#if (defined (JUMP_DELAY_SLOTS) || defined (CJUMP_DELAY_SLOTS))
  annuled_branch_seen = 0;
#endif

  if (!issuing_event)
    while (*template != '\0')
      {
	/* Skip white space */
	while (*template == ' ' || *template == '\t') template ++;
	if ((*template == ASM_COMMENT_CHAR))
	  ;
	else if ((*template == ASM_DIRECTIVE_CHAR))
	  ;
	else if (!in_rtx_call_insn && (e = ASM_INSN_IS_CALL (template)))
	  {
	    /* Instruction contains a call to a subroutine (whose name
	       probably begins with a ".") that is not apparent in rtx code. */

	    register char buf [256], *p;

	    flush_uneventful_insn (0);
	    /* Find end of subroutine name: */
	    for (p = e; *p != ',' && *p != ' ' && *p != '\n' && *p != '\0'; )
	      p ++;
	    buf [0] = '*';	/* Do not prepend an '_' */
	    strncpy (buf + 1, e, p - e);
	    buf [p - e + 1] = '\0';
	    call_schema_str (buf);
	  }
	else
	  {
#if (defined (JUMP_DELAY_SLOTS) || defined (CJUMP_DELAY_SLOTS))
	    if (BRANCH_IS_ANNULED (template))
	      annuled_branch_seen = 1;
#endif
	    num_uneventful_insn += asm_insn_size (template);
	  }

	/* Check for multiple instructions in one string. */
	while (*template != '\0' && *template != '\n') template ++;
	if (*template == '\n') template ++;
      }
}


static int
asm_insn_size (template)
  register char *template;
{
#ifdef ASM_INSN_SIZE_EXCEPTIONS
  register int low = 0;
  register int hi = ((sizeof (asm_insn_size_tbl) / sizeof (char *)) / 2) - 1;

  while (low <= hi)
    {
      register int mid = (low + hi) / 2;
      register char *t = template, *p = asm_insn_size_tbl [2 * mid];

      while (*t == *p) {t++; p++;}

      if (*p == '\0')		/* End of pattern */
	return (int) asm_insn_size_tbl [2 * mid + 1];
      else if (*t > *p)
	low = mid + 1;
      else
	hi = mid - 1;
    }
#endif

  return 1;			/* Most instructions are 1 word long */
}


/* Return non-zero if S2 is a substring of S1. */

static int
substring (s1, s2)
     register char *s1, *s2;
{
  for ( ; *s1 != '\0'; s1 ++)
    {
      register char *ss1 = s1, *ss2 = s2;

      for ( ; *ss1 && *ss2 && *ss1 == *ss2; ss1 ++, ss2 ++ );
      if (*ss2 == '\0') return 1;
    }
  return 0;
}



void
ae_function_start ()
{
  function_start_schema (fun_name, n_basic_blocks + implicit_last_block ());
  ae_buffer_pointer = MAKE_AE_BUFFER_POINTER ();
#if (defined (AE_BUFFER_BOUND_REG) || defined (AE_BUFFER_BOUND_VAR))
  ae_buffer_end_pointer = MAKE_AE_BOUND_POINTER ();
#endif
  SCHEMA_PROLOGUE (record_reg_on_entry);
}


static int
record_sp ()
{
  unknown_def_schema (STACK_POINTER_REGNUM);
  issue_event (stack_pointer_rtx);
  return 1;
}


static int
record_fp ()
{
  unknown_def_schema (FRAME_POINTER_REGNUM);
  issue_event (frame_pointer_rtx);
  return 1;
}


void
ae_function_end ()
{
  SCHEMA_EPILOGUE ();
  function_end_schema (fun_name);
}



/* If a conditional jumps to a block, then issue an event upon entry. */

void
ae_block_start (insn, block_no, cjump_target_p)
rtx insn;
int block_no, cjump_target_p;
{
  space_left_in_chunk = 0;
  flush_uneventful_insn (0);	/* Previous block's delayed instruction */
  if (cjump_target_p)
    {
      block_start_schema (block_no, 1);
      /* Plus 1 since function m */
      issue_short_event (block_no, n_basic_blocks + implicit_last_block ());
    }
  else
    block_start_schema (block_no, 0);
}


void
ae_before_block_end (insn, block_no)
rtx insn;
int block_no;
{
}


void
ae_after_block_end (insn, block_no)
rtx insn;
int block_no;
{
  if (insn == NULL)
    {
      flush_uneventful_insn (0);
      block_end_schema (insn, block_no, 0);
    }
  else if (GET_CODE (insn) == JUMP_INSN)
    {
      if (simplejump_p (insn))
	{
#ifdef JUMP_DELAY_SLOTS
	  if (annuled_branch_seen)
	    flush_uneventful_insn (1);
	  else
	    {
	      flush_uneventful_insn (1 + JUMP_DELAY_SLOTS);
	      num_uneventful_insn += JUMP_DELAY_SLOTS;
	    }
#else
	  flush_uneventful_insn (1);
#endif
	  block_end_jump_schema (insn,
				 block_no,
				 head_to_block_number (JUMP_LABEL (insn)),
				 cjump_target_p (JUMP_LABEL (insn)));
	}
      else if (condjump_p (insn) || multiway_jump_p (insn))
	{
#ifdef CJUMP_DELAY_SLOTS
	  flush_uneventful_insn (1 + CJUMP_DELAY_SLOTS);
	  num_uneventful_insn += 1;
#else
	  flush_uneventful_insn (1);
#endif
	  block_end_cjump_schema (insn, block_no);
	}
      else
	{
	  /* A multiway branch appears as:
	     (jump_insn (parallel [(set (pc) (reg #)) (use (label_ref ##))]))
	     where ## is the label of the instruction containing the address
	     vector (which is not passed to us by final).  Process the address
	     vector, no this blind jump. */
	  ae_after_block_end (NEXT_INSN (NEXT_INSN
					 (CONTAINING_INSN
					  (XEXP (XVECEXP (PATTERN (insn),
							  0, 1),
						 0)))),
			      block_no);
	}
    }
  else
    {
      flush_uneventful_insn (0);
      /* Block ends with non-jump instruction. */
      block_end_schema (insn,
			block_no,
			cjump_target_p (next_insn_or_label (insn, 1)));
    }
}


static void
ae_before_call_insn (insn, impossible_flag)
     rtx insn;
     int impossible_flag;
{
  in_rtx_call_insn = 1;
}


static void
ae_after_call_insn (insn, impossible_flag)
     rtx insn;
     int impossible_flag;
{
  rtx p = PATTERN (insn);
  rtx x = (GET_CODE (p) == PARALLEL ? XVECEXP (p, 0, 0) : p);
  rtx c = (GET_CODE (x) == SET ? SET_SRC (x) : x);
  rtx callee = XEXP (c, 0);

#ifdef CALL_DELAY_SLOTS
  flush_uneventful_insn (1 + CALL_DELAY_SLOTS);
  if (impossible_flag)
    num_uneventful_insn += 2;	/* To account for NOP before call is output */
  else
    num_uneventful_insn += 1;	/* To account for NOP */
#else
  flush_uneventful_insn (1);
#endif
  in_rtx_call_insn = 0;
  if (GET_CODE (callee) == MEM && GET_CODE (XEXP (callee, 0)) == SYMBOL_REF)
    call_schema (XEXP (callee, 0));
  else if (GET_CODE (callee) == MEM && GET_CODE (XEXP (callee, 0)) == REG)
    {
      list defs = defns_reaching_insn (insn, XEXP (callee, 0));

      if (defs != NULL && CDR (defs) == NULL) /* 1 element list */
	{
	  rtx def = PATTERN ((rtx) CAR (defs));

	  if (GET_CODE (def) == SET && GET_CODE (SET_SRC (def)) == SYMBOL_REF)
	    call_schema (SET_SRC (def));
	  else
	    goto indirect_call;
	}
      else
	/* Otherwise, do an indirect call to an address that is saved (since
	   the instruction appears to be impossible). */
      indirect_call:
	call_indirect_schema (XEXP (callee, 0));
      free_list (defs);
    }
  else
    error ("Unknown quantity to call_indirect");
}


static void
ae_before_easy_insn (insn, pattern)
     rtx insn, pattern;
{
}


static void
ae_after_easy_insn (insn, pattern)
     rtx insn, pattern;
{
  flush_uneventful_insn (1);
  if (GET_CODE (pattern) == SET)
    {
      register rtx d = SET_DEST (pattern);
      register rtx e = SET_SRC (pattern);

      while (1)
	switch (GET_CODE (e))
	  {
	  case PLUS:
	  case MINUS:
	  case MULT:
	  case DIV:
	  case MOD:
	  case AND:
	  case IOR:
	  case LSHIFT:
	  case ASHIFT:
	  case LSHIFTRT:
	    compute_def_schema (REGNO (d), GET_CODE (e), XEXP (e, 0),
				XEXP (e, 1));
	    return;

	  case NEG:
	  case NOT:
	    compute_def_schema (REGNO (d), GET_CODE (e), XEXP (e, 0), NULL);
	    return;

	  case SYMBOL_REF:
	    if (*XSTR (e, 0) == '*')
	      /* Probably a local symbol, so record value */
	      {
		unknown_def_schema (REGNO (d));
		issue_event (d);
	      }
	    else
	      symbol_def_schema (REGNO (d), XSTR (e, 0), 0);
	    return;

	  case SIGN_EXTEND:
	    e = XEXP (e, 0);
	    break;

	  case CONST:
	    {
	      /* Instruction is: rx <- @sym or rx <- @sym + c. */
	      rtx t = XEXP (e, 0);
	      rtx base, offset;

	      if (GET_CODE (t) == PLUS)
		{
		  base = XEXP (t, 0), offset = XEXP (t, 1);
		  if (GET_CODE (base) == SYMBOL_REF
		      && GET_CODE (offset) == CONST_INT)
		    {
		      char *symbol_name = XSTR (base, 0);

		      if (*symbol_name == '*')
			{
			  unknown_def_schema (REGNO (d));
			  issue_event (d);
			}
		      else
			symbol_def_schema (REGNO (d), symbol_name,
					   XINT (offset, 0));
		    }
		  else
		    goto unknown;
		}
	      else if (GET_CODE (t) == SYMBOL_REF)
		symbol_def_schema (REGNO (d), XEXP (t, 0), 0);
	      else
		goto unknown;
	      return;
	    }

	  case SUBREG:
	    d = SUBREG_REG (d);
	    /* Fall through */

	  case CONST_INT:
	  case REG:
	    compute_def_schema (REGNO (d), NIL, e, NULL);
	    return;

	  case LABEL_REF:
	    unknown_def_schema (REGNO (d));
	    issue_event (d);
	    return;

	  default:
	    goto unknown;
	  }
    }
  else if (GET_CODE (pattern) == PARALLEL)
    {
      register int i;

      for (i = 0; i < XVECLEN (pattern, 0); i ++)
	ae_before_easy_insn (insn, XVECEXP (pattern, 0, i));
    }
  else
    {
    unknown:
      print_rtl(stderr, insn);
      error ("Easy insn wasn't so easy."); /*NOTREACHED*/
    }
}



void
ae_before_impossible_insn (insn, store_dest)
     rtx insn, store_dest;
{
}


void
ae_after_impossible_insn (insn, store_dest)
     rtx insn, store_dest;
{
  flush_uneventful_insn (1);
  unknown_def_schema (REGNO (store_dest));
  issue_event (store_dest);
}


void
ae_before_load_insn (insn, exp)
     rtx insn, exp;
{
  void thunk_for_load_before ();

  find_mem_refs (exp, thunk_for_load_before);
}


void
ae_after_load_insn (insn, exp)
     rtx insn, exp;
{
  void thunk_for_load_after ();

  flush_uneventful_insn (1);
  find_mem_refs (exp, thunk_for_load_after);
}


static void
thunk_for_load_before (exp, base, offset)
     rtx exp, base, offset;
{
  if (GET_CODE (base) == SYMBOL_REF)
    {
      if (*XSTR (base, 0) == '*')
	goto unknown;		/* Probably a local symbol, so record value */
      else if (offset == NULL)
	;
      else if (GET_CODE (offset) == CONST_INT)
	;
      else
	goto unknown;
    }
  else if (REG_P (base))
    {
      if (offset == NULL)
	;
      else if (GET_CODE (offset) == CONST_INT)
	;
      else if (REG_P (offset))
	;
      else
	goto unknown;
    }
  else if (GET_CODE (base) == CONST
	   && GET_CODE (XEXP (base, 0)) == PLUS
	   && GET_CODE (XEXP (XEXP (base, 0), 0)) == SYMBOL_REF)
    {
      if (*XSTR (base, 0) == '*')
	goto unknown;		/* Probably a local symbol, so record value */
    }
  else
    {
    unknown:
      issue_address_event (exp, base, offset);
    }
}


static void
thunk_for_load_after (exp, base, offset)
     rtx exp, base, offset;
{
  if (GET_CODE (base) == SYMBOL_REF)
    {
      char *symbol_name = XSTR (base, 0);

      if (*symbol_name == '*')
	goto unknown;		/* Probably a local symbol, so record value */
      else if (offset == NULL)
	load_symbol_schema (symbol_name, 0);
      else if (GET_CODE (offset) == CONST_INT)
	load_symbol_schema (symbol_name, XINT (offset, 0));
      else
	goto unknown;
    }
  else if (REG_P (base))
    {
      if (offset == NULL)
	load_schema_int_offset (REGNO (base), 0, 0);
      else if (GET_CODE (offset) == CONST_INT)
	load_schema_int_offset (REGNO (base), XINT (offset, 0), 0);
      else if (REG_P (offset))
	load_schema_reg_offset (REGNO (base), offset, 0);
      else
	goto unknown;
    }
  else if (GET_CODE (base) == CONST
	   && GET_CODE (XEXP (base, 0)) == PLUS
	   && GET_CODE (XEXP (XEXP (base, 0), 0)) == SYMBOL_REF)
    {
      char *symbol_name = XSTR (XEXP (XEXP (base, 0), 0), 0);
      if (*symbol_name == '*')
	goto unknown;		/* Probably a local symbol, so record value */
      else
	load_symbol_schema (symbol_name, XINT (XEXP (XEXP (base, 0), 1), 0));
    }
  else
    {
    unknown:
      load_unknown_schema ();
    }
}


void
ae_before_store_insn (insn, store_dest)
     rtx insn, store_dest;
{
  void thunk_for_store_before ();

  find_mem_refs (store_dest, thunk_for_store_before);
}


void
ae_after_store_insn (insn, store_dest)
     rtx insn, store_dest;
{
  void thunk_for_store_after ();

  flush_uneventful_insn (1);
  find_mem_refs (store_dest, thunk_for_store_after);
}


static void
thunk_for_store_before (exp, base, offset)
     rtx exp, base, offset;
{
  if (GET_CODE (base) == SYMBOL_REF)
    {
      if (*XSTR (base, 0) == '*')
	goto unknown;		/* Probably a local symbol, so record value */
      else if (offset == NULL)
	;
      else if (GET_CODE (offset) == CONST_INT)
	;
      else
	goto unknown;
    }
  else if (REG_P (base))
    {
      if (offset == NULL)
	;
      else if (GET_CODE (offset) == CONST_INT)
	;
      else if (REG_P (offset))
	;
      else
	goto unknown;
    }
  else if (GET_CODE (base) == CONST
	   && GET_CODE (XEXP (base, 0)) == PLUS
	   && GET_CODE (XEXP (XEXP (base, 0), 0)) == SYMBOL_REF)
    {
      if (*XSTR (base, 0) == '*')
	goto unknown;		/* Probably a local symbol, so record value */
    }
  else
    {
    unknown:
      issue_address_event (exp, base, offset);
    }
}


static void
thunk_for_store_after (exp, base, offset)
     rtx exp, base, offset;
{
  if (GET_CODE (base) == SYMBOL_REF)
    {
      char *symbol_name = XSTR (base, 0);

      if (*symbol_name == '*')
	goto unknown;		/* Probably a local symbol, so record value */
      else if (offset == NULL)
	store_symbol_schema (symbol_name, 0);
      else if (GET_CODE (offset) == CONST_INT)
	store_symbol_schema (symbol_name, XINT (offset, 0));
      else
	goto unknown;
    }
  else if (REG_P (base))
    {
      if (offset == NULL)
	store_schema_int_offset (REGNO (base), 0, 0);
      else if (GET_CODE (offset) == CONST_INT)
	store_schema_int_offset (REGNO (base), XINT (offset, 0), 0);
      else if (REG_P (offset))
	store_schema_reg_offset (REGNO (base), offset, 0);
      else
	goto unknown;
    }
  else if (GET_CODE (base) == CONST
	   && GET_CODE (XEXP (base, 0)) == PLUS
	   && GET_CODE (XEXP (XEXP (base, 0), 0)) == SYMBOL_REF)
    {
      char *symbol_name = XSTR (XEXP (XEXP (base, 0), 0), 0);

      if (*symbol_name == '*')
	goto unknown;
      else
	store_symbol_schema (symbol_name, XINT (XEXP (XEXP (base, 0), 1), 0));
    }
  else
    {
    unknown:
      store_unknown_schema ();
    }
}


static void
flush_uneventful_insn (num_current)
     int num_current;
{
  if (num_uneventful_insn - num_current > 0)
    uneventful_schema (num_uneventful_insn - num_current, STD_ASM_INSN_LENGTH);
  num_uneventful_insn = 0;
}



/* Issue a schema by writing to the schema file. */


static void
function_start_schema (fun_name, n_blocks)
     char *fun_name;
     int n_blocks;
{
  fprintf (schema_out_file, "%s ", FUNCTION_START);
  assemble_name (schema_out_file, fun_name);
  fprintf (schema_out_file, " %d\n", n_blocks);
}


static void
function_end_schema (fun_name)
     char *fun_name;
{
  fprintf (schema_out_file, "%s ", FUNCTION_END);
  assemble_name (schema_out_file, fun_name);
  fprintf (schema_out_file, "\n\n");
}


static void
block_start_schema (block_no, target_p)
     int block_no, target_p;
{
  fprintf (schema_out_file, "\n%s %d\n",
	   (target_p ? BLOCK_START_TARGET : BLOCK_START), block_no);
}


static void
block_end_schema (insn, block_no, next_target_p)
     rtx insn;
     int block_no, next_target_p;
{
  fprintf (schema_out_file, "%s %d",
	   (next_target_p ? BLOCK_END_NEXT_TARGET : BLOCK_END), block_no);
  edge_schema (insn);
}


static void
block_end_jump_schema (insn, block_no, target_block_no, next_target_p)
     rtx insn;
     int block_no, target_block_no, next_target_p;
{
  fprintf (schema_out_file, "%s %d %d",
	   (next_target_p ? BLOCK_END_JUMP_NEXT_TARGET : BLOCK_END_JUMP),
	   block_no, target_block_no);
  edge_schema (insn);
}


static void
edge_schema (insn)
     rtx insn;
{
  if (insn != NULL)
    {
      /* List exit before backedges */
      DOLIST (e, list, insn_info [INSN_UID (insn)].exit_edge,
	      {fprintf (schema_out_file, " %s(%d %d)",
			LOOP_EXIT, CAR (e), CDR(e));
	       free (e);});
      DOLIST (e, list, insn_info [INSN_UID (insn)].back_edge,
	      {fprintf (schema_out_file, " %s(%d %d)",
			LOOP_BACK, CAR (e), CDR (e));
	       free (e);});
      DOLIST (e, list, insn_info [INSN_UID (insn)].entry_edge,
	      {fprintf (schema_out_file, " %s(%d %d)",
			LOOP_ENTRY, CAR (e), CDR (e));
	       free (e);});
    }
  fprintf (schema_out_file, "\n");
}


static void
block_end_cjump_schema (insn, block_no)
     rtx insn;
     int block_no;
{
  fprintf (schema_out_file, "%s %d", BLOCK_END_CJUMP, block_no);
  if (condjump_p (insn))
    fprintf (schema_out_file, " %d %d", block_no + 1,
	     head_to_block_number (JUMP_LABEL (insn)));
  else
    {
      rtx body =  PATTERN (insn);
      register int vlen, i;

      vlen = XVECLEN (body, 0);
      for (i = 0; i < vlen; i++)
	fprintf (schema_out_file, " %d",
		 head_to_block_number (XEXP (XVECEXP (body, 0, i), 0)));
    }
  edge_schema (insn);
}


static void
simple_def_schema (rd, r1, value)
     int rd, r1, value;
{
  fprintf (schema_out_file, "%s2 R%d #R%d + #I%d\n",
	   COMPUTE_DEFN_, rd, r1, value);
}


static void
compute_def_schema (rd, code, a1, a2)
     int rd;
     RTX_CODE code;
     rtx a1, a2;
{
  if (code == NIL)		/* Nullary */
    {
      fprintf (schema_out_file, "%s0 R%d ", COMPUTE_DEFN_, rd);
      rtx_arg_out (schema_out_file, a1);
    }
  else if (a2 == NULL)		/* Unary */
    {
      fprintf (schema_out_file, "%s1 R%d %s ", COMPUTE_DEFN_, rd,
	       rtx_op_out (code));
      rtx_arg_out (schema_out_file, a1);
    }
  else				/* Binary */
    {
      fprintf (schema_out_file, "%s2 R%d ", COMPUTE_DEFN_, rd);
      rtx_arg_out (schema_out_file, a1);
      fprintf (schema_out_file, " %s ", rtx_op_out (code));
      rtx_arg_out (schema_out_file, a2);
    }
  fprintf (schema_out_file, "\n");
}


static void
rtx_arg_out (file, a)
     FILE *file;
     rtx a;
{
  switch (GET_CODE (a))
    {
    case REG:
      fprintf (file, "#R%d", REGNO (a));
      break;
    case SUBREG:
      rtx_arg_out (file, SUBREG_REG (a));
      break;
    case CONST_INT:
      fprintf (file, "#I%d", INTVAL (a));
      break;
    case SYMBOL_REF:
      fprintf (file, "#S");
      assemble_name (file, XEXP (a, 0));
      break;
    default: error ("Unknown object to output"); /*NOTREACHED*/
    }
}


char *
rtx_op_out (code)
     RTX_CODE code;
{
  switch (code)
    {
    case PLUS: return "+";
    case MINUS: return "-";
    case MULT: return "*";
    case DIV: return "/";
    case MOD: return "%";
    case AND: return "&";
    case IOR: return "|";
    case LSHIFT: return "<<";
    case ASHIFT: return "<<";
    case LSHIFTRT: return ">>";
    case NEG: return "-";
    case NOT: return "~";
    default: error ("Unknown operator to output"); /*NOTREACHED*/
    }
}


static void
symbol_def_schema (rd, symbol, offset)
     int rd;
     char *symbol;
     int offset;
{
  fprintf (schema_out_file, "%s2 R%d #I%d + #S", COMPUTE_DEFN_, rd, offset);
  assemble_name (schema_out_file, symbol);
  fprintf (schema_out_file, "\n");
}


static void
unknown_def_schema (rd)
     int rd;
{fprintf (schema_out_file, "%s R%d\n", UNKNOWN_DEFN, rd);}


static void
store_schema_int_offset (reg_no, offset, double_p)
     int reg_no, offset, double_p;
{
  fprintf (schema_out_file, "%s #R%d + #I%d\n",
	   (double_p ? STORE_D_INST : STORE_INST), reg_no, offset);
}


static void
store_schema_reg_offset (reg_no, offset_reg, double_p)
     int reg_no;
     rtx offset_reg;
     int double_p;
{
  fprintf (schema_out_file, "%s #R%d + #R%d\n",
	   (double_p ? STORE_D_INST : STORE_INST), reg_no, REGNO (offset_reg));
}


static void
store_symbol_schema (symbol, offset)
     char *symbol;
     int offset;
{
  fprintf (schema_out_file, "%s #I%d + #S", STORE_INST, offset);
  assemble_name (schema_out_file, symbol);
  fprintf (schema_out_file, "\n");
}


static void
store_unknown_schema ()
{fprintf (schema_out_file, "%s\n", STORE_UNKNOWN_INST);}


static void
load_schema_int_offset (reg_no, offset, double_p)
     int reg_no, offset, double_p;
{
  fprintf (schema_out_file, "%s #R%d + #I%d\n",
	   (double_p ? LOAD_D_INST : LOAD_INST), reg_no, offset);
}

static void
load_schema_reg_offset (reg_no, offset_reg, double_p)
     int reg_no;
     rtx offset_reg;
     int double_p;
{
  fprintf (schema_out_file, "%s #R%d + #R%d\n",
	   (double_p ? LOAD_D_INST : LOAD_INST), reg_no, REGNO (offset_reg));
}


static void
load_symbol_schema (symbol, offset)
     char *symbol;
     int offset;
{
  fprintf (schema_out_file, "%s #I%d + #S", LOAD_INST, offset);
  assemble_name (schema_out_file, symbol);
  fprintf (schema_out_file, "\n");
}


static void
load_unknown_schema ()
{fprintf (schema_out_file, "%s\n", LOAD_UNKNOWN_INST);}


static void
call_schema (callee)
     rtx callee;
{call_schema_str (XSTR (callee, 0));}


static void
call_schema_str (callee)
     char *callee;
{
  fprintf (schema_out_file, "%s ", CALL_INST);
  assemble_name (schema_out_file, callee);
  fprintf (schema_out_file, "\n");
}


static void
call_indirect_schema (callee_reg)
     rtx callee_reg;
{
  fprintf (schema_out_file, "%s R%d\n", CALL_INDIRECT_INST,
	   REGNO (callee_reg));
}


static void
uneventful_schema (num_insn, size_insn)
     int num_insn, size_insn;
{
  fprintf (schema_out_file, "%s %d %d\n", UNEVENTFUL_INST,
	   num_insn, size_insn);
}



/* The instruction sequence to store a full-word event in the AE
   buffer looks like:

     CMP RB, RL
     BLE  OK				! Check if buffer is full
     CALL AE_FLUSH_BUFFER
  OK:

     ST VALUE, 0 + RB			! Store word.
     ADD RB, 4, RB			! Bump pointer

RB is the register or variable containing a pointer to next free byte
in AE buffer.  RL can be a register or variable containing a pointer
to end of this buffer - CHUNK_SIZE.  Or, RL can be a constant value
(e.g., first negative number minus a small offset) pointing to the end
of this buffer.

We cheat a bit and only perform the space check on the first event in
a block.  At this test, we check for enough memory for all events in
the block.  However, when we see the first event, we don't know how
many more events will be in the block, so we check for a lot
(CHUNK_SIZE) of space.  If we overestimate the amount, it does not
matter since RB rarely is near the end of the buffer.

Also, on most machines, we cannot do a simple store of the value since
the buffer contains: unsigned chars, unsigned shorts, and longs and
hence is byte-aligned. */


/* Produce the space check at the beginning of a block. */

static void
issue_event_space_check (comment, size)
     char *comment;
     int size;
{
  char buffer[256];

  if (space_left_in_chunk > size)
    {
      sprintf (buffer, "\t\t\t%c %s Event", ASM_COMMENT_CHAR, comment);
      output_asm_insn (buffer, NULL);
      space_left_in_chunk -= size;
      return;
    }
  else
    {
      space_left_in_chunk = CHUNK_SIZE;
      GENERATE_SPACE_CHECK (comment, size);
    }
}


static void
issue_event (value)
     rtx value;
{
  issuing_event = 1;
  issue_event_space_check ("", 4);
  GENERATE_EVENT (value);
  issuing_event = 0;
}


static void
issue_short_event (value, max_value)
     int value, max_value;
{
  rtx xops [2];
  int bytes = (max_value < 256 ? 1 : 2);

  issuing_event = 1;
  issue_event_space_check ("Short", bytes);
  GENERATE_SHORT_EVENT (value, bytes);
  issuing_event = 0;
}


static void
issue_address_event (address, base, offset)
     rtx address, base, offset;
{
  issuing_event = 1;
  issue_event_space_check ("Address", 4);
  GENERATE_ADDRESS_EVENT(address, base, offset);
  issuing_event = 0;
}
