/* Subroutines for insn-output.c for MIL-STD-1750.
   Copyright (C) 1994, 1995, 1996 Free Software Foundation, Inc.
   Contributed by O.M.Kellogg, DASA (kellogg@space.otn.dasa.de)

This file is part of GNU CC.

GNU CC 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 1, or (at your option)
any later version.

GNU CC 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 GNU CC; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#ifndef FILE
#include <stdio.h>
#endif
#include <string.h>

#define __datalbl
#include "config.h"
#include "rtl.h"
#include "tree.h"
#include "expr.h"
#define HAVE_cc0
#include "conditions.h"
#include "real.h"

struct datalabel_array datalbl[DATALBL_ARRSIZ];
int datalbl_ndx = -1;
struct jumplabel_array jmplbl[JMPLBL_ARRSIZ];
int jmplbl_ndx = -1;
int label_pending = 0, program_counter = 0;
enum section current_section = Normal;
char *sectname[4] =
{"Init", "Normal", "Konst", "Static"};

int
notice_update_cc (exp)
     rtx exp;
{
  if (GET_CODE (exp) == SET)
    {
      enum rtx_code src_code = GET_CODE (SET_SRC (exp));
      /* Jumps do not alter the cc's.  */
      if (SET_DEST (exp) == pc_rtx)
	return;
      /* Moving a register or constant into memory doesn't alter the cc's. */
      if (GET_CODE (SET_DEST (exp)) == MEM
	  && (src_code == REG || src_code == CONST_INT))
	return;
      /* Function calls clobber the cc's.  */
      if (src_code == CALL)
	{
	  CC_STATUS_INIT;
	  return;
	}
      /* Emulated longword bit-ops leave cc's incorrect */
      if (GET_MODE (SET_DEST (exp)) == HImode ?
	       src_code == AND || src_code == IOR ||
	       src_code == XOR || src_code == NOT : 0)
	{
	  CC_STATUS_INIT;
	  return;
	}
      /* Tests and compares set the cc's in predictable ways.  */
      if (SET_DEST (exp) == cc0_rtx)
	{
	  CC_STATUS_INIT;
	  cc_status.value1 = SET_SRC (exp);
	  return;
	}
      /* Anything else will set cc_status. */
      cc_status.flags = CC_NO_OVERFLOW;
      cc_status.value1 = SET_SRC (exp);
      cc_status.value2 = SET_DEST (exp);
      return;
    }
  else if (GET_CODE (exp) == PARALLEL
	   && GET_CODE (XVECEXP (exp, 0, 0)) == SET)
    {
      if (SET_DEST (XVECEXP (exp, 0, 0)) == pc_rtx)
	return;
      if (SET_DEST (XVECEXP (exp, 0, 0)) == cc0_rtx)
	{
	  CC_STATUS_INIT;
	  cc_status.value1 = SET_SRC (XVECEXP (exp, 0, 0));
	  return;
	}
      CC_STATUS_INIT;
    }
  else
    {
      CC_STATUS_INIT;
    }
}


rtx
function_arg (cum, mode, type, named)
     int cum;
     enum machine_mode mode;
     tree type;
     int named;
{
  int size;

  if (MUST_PASS_IN_STACK (mode, type))
    return (rtx) 0;
  if (mode == BLKmode)
    size = int_size_in_bytes (type);
  else
    size = GET_MODE_SIZE (mode);
  if (cum + size < 12)
    return gen_rtx (REG, mode, cum);
  else
    return (rtx) 0;
}


double
get_double (x)
     rtx x;
{
  union
    {
      double d;
      long i[2];
    }
  du;

  du.i[0] = CONST_DOUBLE_LOW (x);
  du.i[1] = CONST_DOUBLE_HIGH (x);
  return du.d;
}

char *
float_label (code, value)
     char code;
     double value;
{
  int i = 1;
  static char label[32];
  char *p;

  label[0] = code;
  p = label + 1;
  sprintf (p, "%lf", value);
  while (*p)
    {
      *p = (*p == '+') ? 'p' :
	(*p == '-') ? 'm' : *p;
      p++;
    }
  return xstrdup (label);
}


char *
movcnt_regno_adjust (op)
     rtx *op;
{
  static char outstr[80];
  int op0r = REGNO (op[0]), op1r = REGNO (op[1]), op2r = REGNO (op[2]);
#define dstreg op0r
#define srcreg op1r
#define cntreg op2r
#define cntreg_1750 (op0r + 1)

  if (cntreg == cntreg_1750)
    sprintf (outstr, "mov r%d,r%d", op0r, op1r);
  else if (dstreg + 1 == srcreg && cntreg > srcreg)
    sprintf (outstr, "xwr r%d,r%d\n\tmov r%d,r%d", op2r, op1r, op0r, op2r);
  else if (dstreg == cntreg + 1)
    sprintf (outstr, "xwr r%d,r%d\n\tmov r%d,r%d", op0r, op2r, op2r, op1r);
  else if (dstreg == srcreg + 1)
    sprintf (outstr, "xwr r%d,r%d\n\txwr r%d,r%d\n\tmov r%d,r%d",
	     op0r, op1r, op0r, op2r, op1r, op2r);
  else if (cntreg + 1 == srcreg)
    sprintf (outstr, "xwr r%d,r%d\n\txwr r%d,r%d\n\tmov r%d,r%d",
	     op2r, op1r, op0r, op2r, op2r, op0r);
  else if (cntreg == srcreg + 1)
    sprintf (outstr, "xwr r%d,r%d\n\tmov r%d,r%d", op0r, op1r, op1r, op0r);
  else
    sprintf (outstr, "xwr r%d,r%d\n\tmov r%d,%d\n\txwr r%d,r%d",
	     op2r, cntreg_1750, op0r, op1r, op2r, cntreg_1750);
  return outstr;
}

char *
mod_regno_adjust (instr, op)
     char *instr;
     rtx *op;
{
  static char outstr[40];
  char *r = (!strncmp (instr, "dvr", 3) ? "r" : "");
  int modregno_gcc = REGNO (op[3]), modregno_1750 = REGNO (op[0]) + 1;

  if (modregno_gcc == modregno_1750)
    sprintf (outstr, "%s r%%0,%s%%2", instr, r);
  else
    sprintf (outstr, "lr r%d,r%d\n\t%s r%%0,%s%%2\n\txwr r%d,r%d",
	modregno_gcc, modregno_1750, instr, r, modregno_1750, modregno_gcc);
  return outstr;
}


/* Auxiliary to `nonindirect_operand':
   Check if op is a valid memory operand for 1750A arith./logic (non-move)
   instructions. */
int
memop_valid (op)
     rtx op;
{
  if (GET_MODE (op) != Pmode && GET_MODE (op) != VOIDmode)
    return 0;
  switch (GET_CODE (op))
    {
    case MEM:
    case MINUS:
    case MULT:
    case DIV:
      return 0;
    case PLUS:
      if (!memop_valid (XEXP (op, 0)))
	return 0;
      return memop_valid (XEXP (op, 1));
    case REG:
      if (REGNO (op) > 0)
	return 1;
      return 0;
    case CONST:
    case CONST_INT:
    case SYMBOL_REF:
    case SUBREG:
      return 1;
    default:
      printf ("memop_valid: code=%d\n", (int) GET_CODE (op));
      return 1;
    }
}

/* extra predicate for recog: */
int
nonindirect_operand (op, mode)
     rtx op;
     enum machine_mode mode;
{
  int retval;

  switch (GET_CODE (op))
    {
    case MEM:
      retval = memop_valid (XEXP (op, 0));
      return retval;
    case REG:
      return 1;
    default:
      if (!CONSTANT_P (op))
	return 0;
    }
  return 1;
}

/* predicate for the MOV instruction: */
int
mov_memory_operand (op, mode)
     rtx op;
     enum machine_mode mode;
{
  return (GET_CODE (op) == MEM && GET_CODE (XEXP (op, 0)) == REG);
}

/* predicate for the STC instruction: */
int
small_nonneg_const (op, mode)
     rtx op;
     enum machine_mode mode;
{
  if (GET_CODE (op) == CONST_INT && INTVAL (op) >= 0 && INTVAL (op) <= 15)
    return 1;
  return 0;
}

/* predicate for constant zero: */
int
zero_operand (op, mode)
     rtx op;
     enum machine_mode mode;
{
  return op == CONST0_RTX (mode);
}


/* predicate for 1750 `B' addressing mode (Base Register with Offset)
   memory operand */
int
b_mode_operand (op)
     rtx op;
{
  if (GET_CODE (op) == MEM)
    {
      rtx inner = XEXP (op, 0);
      if (GET_CODE (inner) == REG && REG_OK_FOR_INDEX_P (inner))
	return 1;
      if (GET_CODE (inner) == PLUS)
	{
	  rtx plus_op0 = XEXP (inner, 0);
	  if (GET_CODE (plus_op0) == REG && REG_OK_FOR_INDEX_P (plus_op0))
	    {
	      rtx plus_op1 = XEXP (inner, 1);
	      if (GET_CODE (plus_op1) == CONST_INT
		  && INTVAL (plus_op1) >= 0
		  && INTVAL (plus_op1) <= 255)
		return 1;
	    }
	}
    }
  return 0;
}

/* predicate needed for adding 1 to mem (short before output) */
int
simple_memory_operand (op, mode)
     rtx op;
     enum machine_mode mode;
{
  rtx inner;
  if (GET_CODE (op) != MEM)
    return 0;
  inner = XEXP (op, 0);
  switch (GET_CODE (inner))
    {
    case REG:
    case SYMBOL_REF:
    case LABEL_REF:
      return 1;
    case PLUS:
      if (GET_CODE (XEXP (inner, 1)) != CONST_INT)
        return 0;
      inner = (XEXP (inner, 0));
      switch (GET_CODE (inner))
	{
	case REG:
	case SYMBOL_REF:
	case LABEL_REF:
	  return 1;
	case PLUS:
	  if (GET_CODE (XEXP (inner, 1)) != CONST_INT)
            return 0;
	  switch (GET_CODE (XEXP (inner, 0)))
	    {
	    case SYMBOL_REF:
	    case LABEL_REF:
	      return 1;
	    }
	}
    }
  return 0;
}


/* destructively add one to memory address */
add_1_to_mem (opnd)	/* returns 0 for success, -1 for failure */
     rtx opnd;		/* OPND must be a MEM rtx */
{
  rtx inner = XEXP (opnd, 0);

  if (GET_CODE (opnd) != MEM)
    {
      fprintf (stderr, "add_1_to_mem: input is not MEM\n");
      return -1;  /* failure */
    }
  switch (GET_CODE (inner))
    {
    case CONST:
      inner = XEXP (inner, 0);
      if (GET_CODE (inner) != PLUS
       || GET_CODE (XEXP (inner, 1)) != CONST_INT)
	{
	  fprintf (stderr, "add_1_to_mem: CONST failure\n");
	  return -1;
	}
      INTVAL (XEXP (XEXP (XEXP (opnd, 0), 0), 1)) += 1;
      break;
    case REG:
      XEXP (opnd, 0) = gen_rtx (PLUS, Pmode, inner, const1_rtx);
      break;
    case SYMBOL_REF:
    case LABEL_REF:
      XEXP (opnd, 0) = gen_rtx (CONST, VOIDmode,
			 gen_rtx (PLUS, Pmode, inner, const1_rtx));
      break;
    case PLUS:
      inner = XEXP (inner, 1);
      switch (GET_CODE (inner))
	{
	case CONST:
	  inner = XEXP (inner, 0);
	  if (GET_CODE (inner) != PLUS
	   || GET_CODE (XEXP (inner, 1)) != CONST_INT)
	    {
	      fprintf (stderr, "add_1_to_mem: PLUS CONST failure\n");
	      return -1;
	    }
	  INTVAL (XEXP (XEXP (XEXP (XEXP (opnd, 0), 1), 0), 1)) += 1;
	  break;
	case CONST_INT:
	  INTVAL (XEXP (XEXP (opnd, 0), 1)) += 1;
	  break;
	case SYMBOL_REF:
	case LABEL_REF:
	  XEXP (XEXP (opnd, 0), 1) = gen_rtx (CONST, VOIDmode,
				     gen_rtx (PLUS, Pmode, inner, const1_rtx));
	  break;
	default:
	  fprintf (stderr, "add_1_to_mem: PLUS failure\n");
	  return -1;
	}
    }
  return 0;
}


/* Decide whether to output a conditional jump as a "Jump Conditional"
   or as a "Branch Conditional": */

int
find_jmplbl (labelnum)
     int labelnum;
{
  int i, found = 0;

  for (i = 0; i <= jmplbl_ndx; i++)
    if (labelnum == jmplbl[i].num)
      {
	found = 1;
	break;
      }
  if (found)
    return i;
  return -1;
}

char *
branch_or_jump (condition, targetlabel_number)
     char *condition;
     int targetlabel_number;
{
  static char buf[30];
  int index;

  if ((index = find_jmplbl (targetlabel_number)) >= 0)
    if (program_counter - jmplbl[index].pc < 128)
      {
	sprintf (buf, "b%s %%l0", condition);
	return buf;
      }
  sprintf (buf, "jc %s,%%l0", condition);
  return buf;
}


int
unsigned_comparison_operator (insn)
     rtx insn;
{
  switch (GET_CODE (insn))
    {
    case GEU:
    case GTU:
    case LEU:
    case LTU:
      return 1;
    default:
      return 0;
    }
}

int
next_cc_user_is_unsigned (insn)
     rtx insn;
{
  if ( !(insn = next_cc0_user (insn)))
    abort ();
  else if (GET_CODE (insn) == JUMP_INSN
	   && GET_CODE (PATTERN (insn)) == SET
	   && GET_CODE (SET_SRC (PATTERN (insn))) == IF_THEN_ELSE)
    return unsigned_comparison_operator (XEXP (SET_SRC (PATTERN (insn)), 0));
  else if (GET_CODE (insn) == INSN
	   && GET_CODE (PATTERN (insn)) == SET)
    return unsigned_comparison_operator (SET_SRC (PATTERN (insn)));
  else
    abort ();
}



/* The PRINT_OPERAND and PRINT_OPERAND_ADDRESS macros have been
   made functions: */

print_operand (file, x, kode)
     FILE *file;
     rtx x;
     enum rtx_code kode;
{
  switch (GET_CODE (x))
    {
    case REG:
      fprintf (file, "%d", REGNO (x));
      break;
    case SYMBOL_REF:
      fprintf (file, "%s", XSTR (x, 0));
      break;
    case LABEL_REF:
    case CONST:
    case MEM:
      if (kode == 'Q')
	{
	  rtx inner = XEXP (x, 0);
	  switch (GET_CODE (inner))
	    {
	    case REG:
	      fprintf (file, "r%d,0", REGNO (inner));
	      break;
	    case PLUS:
	      fprintf (file, "r%d,%d", REGNO (XEXP (inner, 0)),
		       INTVAL (XEXP (inner, 1)));
	      break;
	    default:
	      fprintf (file, "[ill Q code=%d]", GET_CODE (inner));
	    }
	}
      else
        output_address (XEXP (x, 0));
      break;
    case CONST_DOUBLE:
/*    {
	double value = get_double (x);
	char fltstr[32];
	sprintf (fltstr, "%lf", value);

	if (kode == 'D' || kode == 'E')
	  {
	    int i, found = 0;
	    for (i = 0; i <= datalbl_ndx; i++)
	      if (strcmp (fltstr, datalbl[i].value) == 0)
		{
		  found = 1;
		  break;
		}
	    if (!found)
	      {
		strcpy (datalbl[i = ++datalbl_ndx].value, fltstr);
		datalbl[i].name = float_label (kode, value);
		datalbl[i].size = (kode == 'E') ? 3 : 2;
		check_section (Konst);
		fprintf (file, "K%s \tdata%s %s ;p_o\n", datalbl[i].name,
			(kode == 'E' ? "ef" : "f"), fltstr);
		check_section (Normal);
	      }
	  }
	else if (kode == 'F' || kode == 'G')
	  {
	    int i, found = 0;
	    for (i = 0; i <= datalbl_ndx; i++)
	      if (strcmp (fltstr, datalbl[i].value) == 0)
		{
		  found = 1;
		  break;
		}
	    if (!found)
	      {
		fprintf (stderr,
		   "float value %lfnot found upon label reference\n", value);
		strcpy (datalbl[i = ++datalbl_ndx].value, fltstr);
		datalbl[i].name = float_label (kode, value);
		datalbl[i].size = (kode == 'G') ? 3 : 2;
		check_section (Konst);
		fprintf (file, "K%s \tdata%s %s ;p_o\n", datalbl[i].name,
			(kode == 'G' ? "ef" : "f"), fltstr);
		check_section (Normal);
	      }
	    fprintf (file, "%s ;P_O 'F'", datalbl[i].name);
	  }
	else
	  fprintf (file, " %s  ;P_O cst_dbl ", fltstr);
      }
 */
      fprintf (file, "%lf", get_double (x));
      break;
    case CONST_INT:
      if (kode == 'J')
	fprintf (file, "%d", -INTVAL (x));
      else if (INTVAL (x) > 0x7FFF)
	fprintf (file, "%d  ; range correction (val>0x7FFF) applied",
		 INTVAL (x) - 0x10000);
      else
	fprintf (file, "%d", INTVAL (x));
      break;
    case CODE_LABEL:
      fprintf (file, "L%d", XINT (x, 3));
      break;
    case CALL:
      fprintf (file, "CALL nargs=%d, func is either '%s' or '%s'",
       XEXP (x, 1), XSTR (XEXP (XEXP (x, 0), 1), 0), XSTR (XEXP (x, 0), 1));
      break;
    case PLUS:
      {
	rtx op0 = XEXP (x, 0), op1 = XEXP (x, 1);
	int op0code = GET_CODE (op0), op1code = GET_CODE (op1);
	if (op1code == CONST_INT)
	  switch (op0code)
	    {
	    case REG:
	      fprintf (file, "%d,r%d  ; p_o_PLUS for REG and CONST_INT",
		       INTVAL (op1), REGNO (op0));
	      break;
	    case SYMBOL_REF:
	      fprintf (file, "%d+%s", INTVAL (op1), XSTR (op0, 0));
	      break;
	    case MEM:
	      fprintf (file, "%d,[mem:", INTVAL (op1));
	      output_address (XEXP (op0, 0));
	      fprintf (file, "] ;P_O plus");
	      break;
	    default:
	      fprintf (file, "p_o_PLUS UFO, code=%d, with CONST=%d",
		       (int) op0code, INTVAL (op1));
	    }
	else if (op1code == SYMBOL_REF && op0code == REG)
	  fprintf (file, "%s,r%d  ; P_O: (plus reg sym)",
		   XSTR (op1, 0), REGNO (op0));
	else
	  fprintf (file, "p_o_+: op0code=%d, op1code=%d", op0code, op1code);
      }
      break;
    default:
      fprintf (file, "p_o_UFO code=%d", GET_CODE (x));
    }
}

print_operand_address (file, addr)
     FILE *file;
     rtx addr;
{
  switch (GET_CODE (addr))
    {
    case REG:
      fprintf (file, "0,r%d ; P_O_A", REGNO (addr));
      break;
    case PLUS:
      {
	register rtx x = XEXP (addr, 0), y = XEXP (addr, 1);
	switch (GET_CODE (x))
	  {
	  case REG:
	    switch (GET_CODE (y))
	      {
	      case CONST:
		output_address (XEXP (y, 0));
		fprintf (file, ",r%d ;P_O_A reg + const expr", REGNO (x));
		break;
	      case CONST_INT:
		fprintf (file, "%d,r%d", INTVAL (y), REGNO (x));
		break;
	      case SYMBOL_REF:
		fprintf (file, "%s,r%d  ; P_O_A reg + sym",
			 XSTR (y, 0), REGNO (x));
		break;
	      case LABEL_REF:
		output_address (XEXP (y, 0));
		fprintf (file, ",r%d  ; P_O_A reg + label", REGNO (x));
		break;
	      default:
		fprintf (file, "[P_O_A reg%d+UFO code=%d]",
			 REGNO (x), GET_CODE (y));
	      }
	    break;
	  case LABEL_REF:
	    output_address (XEXP (x, 0));
	    break;
	  case SYMBOL_REF:
	    switch (GET_CODE (y))
	      {
	      case CONST_INT:
		fprintf (file, "%d+%s", INTVAL (y), XSTR (x, 0));
		break;
	      case REG:
		fprintf (file, "%s,r%d ;P_O_A sym + reg",
			 XSTR (x, 0), REGNO (y));
		break;
	      default:
		fprintf (file, "P_O_A sym/lab+UFO[sym=%s,code(y)=%d]",
			 XSTR (x, 0), GET_CODE (y));
	      }
	    break;
	  case CONST:
	    output_address (XEXP (x, 0));
	    if (GET_CODE (y) == REG)
	      fprintf (file, ",r%d ;P_O_A const + reg", REGNO (x));
	    else
	      fprintf (file, "P_O_A const+UFO code(y)=%d]", GET_CODE (y));
	    break;
	  case MEM:
	    output_address (y);
	    fprintf (file, ",[mem:");
	    output_address (XEXP (x, 0));
	    fprintf (file, "] ;P_O_A plus");
	    break;
	  default:
	    fprintf (file, "P_O_A plus op1_UFO[code1=%d,code2=%d]",
		     GET_CODE (x), GET_CODE (y));
	  }
      }
      break;
    case CONST_INT:
      if (INTVAL (addr) < 0x10000 && INTVAL (addr) >= -0x10000)
	fprintf (file, "%d ; p_o_a const addr?!", INTVAL (addr));
      else
	{
	  fprintf (file, "[p_o_a=ILLEGAL_CONST]");
	  output_addr_const (file, addr);
	}
      break;
    case LABEL_REF:
    case SYMBOL_REF:
      fprintf (file, "%s", XSTR (addr, 0));
      break;
    case MEM:
      fprintf (file, "[memUFO:");
      output_address (XEXP (addr, 0));
      fprintf (file, "]");
      break;
    case CONST:
      output_address (XEXP (addr, 0));
      fprintf (file, " ;P_O_A const");
      break;
    case CODE_LABEL:
      fprintf (file, "L%d", XINT (addr, 3));
      break;
    default:
      fprintf (file, " p_o_a UFO, code=%d val=0x%x",
	       (int) GET_CODE (addr), INTVAL (addr));
      break;
    }
}

