/* Subroutines for insn-output.c for Clipper
   Copyright (C) 1987, 1988, 1991 Free Software Foundation, Inc.

   Contributed by Holger Teutsch (holger@hotbso.rhein-main.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 2, 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, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <stdio.h>
#include "config.h"
#include "rtl.h"
#include "regs.h"
#include "hard-reg-set.h"
#include "real.h"
#include "insn-config.h"
#include "conditions.h"
#include "insn-flags.h"
#include "output.h"
#include "insn-attr.h"
#include "tree.h"
#include "c-tree.h"
#include "expr.h"
#include "flags.h"
#include "machmode.h"

extern char regs_ever_live[];

#ifdef ACCUMULATE_OUTGOING_ARGS
extern int current_function_outgoing_args_size;
#endif

static int save_reg_offset;

/*
 * prologue and epilogue output
 * function is entered with pc pushed, i.e. stack is 32 bit alligned
 *
 */
void
output_function_prologue (file, lsize)
     FILE *file;
     int lsize;				/* size for locals */
{
  int i, offset;
  int save_size;
  int size;				/* total size of frame */

  save_size = 0;			/* compute size for reg saves */
  for (i = 17; i < 32; i++)
    if (regs_ever_live[i] && !call_used_regs[i])
      save_size += 8;

  for (i = 0; i < 16; i++)
    if (regs_ever_live[i] && !call_used_regs[i])
      save_size += 4;

  save_reg_offset = lsize + save_size;

  save_reg_offset = (save_reg_offset + 7) & ~7;	/* align to 64 Bit */

#ifdef ACCUMULATE_OUTGOING_ARGS
  size = save_reg_offset + current_function_outgoing_args_size;
#else
  size = save_reg_offset;
#endif
  
  size = (size + 7) & ~7;		/* align to 64 Bit */

  if (size == 0)
    {
      fputs ("\tpushw  fp,sp\n", file);
      fputs ("\tmovw   sp,fp\n", file);
    }
  else
    {
      if (size < 16)
	fprintf (file, "\tsubq   $%d,sp\n", size + 4); /* room for fp */
      else
	fprintf (file, "\tsubi   $%d,sp\n", size + 4);

      fprintf (file, "\tstorw  fp,%d(sp)\n", size);
      fprintf (file, "\tloada  %d(sp),fp\n", size);
    }

  offset = -save_reg_offset;

  for (i = 16; i < 32; i++)
    if (regs_ever_live[i] && !call_used_regs[i])
      {
	fprintf (file, "\tstord  f%d,%d(fp)\n", i-16, offset);
	offset += 8;
      }

  for (i = 0; i < 16; i++)
    if (regs_ever_live[i] && !call_used_regs[i])
      {
	fprintf (file, "\tstorw  r%d,%d(fp)\n", i, offset);
	offset += 4;
      }
}

void
output_function_epilogue (file, size)
     FILE *file;
     int size;				/* ignored */
{
  int i, offset;

  offset = -save_reg_offset;

  for (i = 16; i < 32; i++)
    if (regs_ever_live[i] && !call_used_regs[i])
      {
	fprintf (file, "\tloadd  %d(fp),f%d\n", offset, i-16);
	offset += 8;
      }

  for (i = 0; i < 16; i++)
    if (regs_ever_live[i] && !call_used_regs[i])
      {
	fprintf (file, "\tloadw  %d(fp),r%d\n", offset, i);
	offset += 4;
      }

  fputs ("\tmovw   fp,sp\n\tpopw   sp,fp\n\tret    sp\n",
	 file);
}

/*
 * blockmove
 *
 * clipper_movstr ()
 */
void
clipper_movstr (operands)
     rtx *operands;
{
  rtx dst,src,cnt,tmp,top,bottom,xops[3];
  int align;
  int fixed;

  extern FILE *asm_out_file;

  dst = operands[0];
  src = operands[1];
  /* don't change this operands[2]; gcc 2.3.3 doesn't honor clobber note */
  align = INTVAL (operands[3]);
  tmp = operands[4];
  cnt = operands[5];

  if (GET_CODE (operands[2]) == CONST_INT) /* fixed size move */
    {
      if ((fixed = INTVAL (operands[2])) <= 0)
	abort ();

      if (fixed <16)
	output_asm_insn ("loadq  %2,%5", operands);
      else
	output_asm_insn ("loadi  %2,%5", operands);
    }
  else
    {
      fixed = 0;
      bottom = (rtx)gen_label_rtx ();	/* need a bottom label */
      xops[0] = cnt; xops[1] = bottom;
      output_asm_insn ("movw   %2,%5", operands); /* count is scratch reg 5 */
      output_asm_insn ("brle   %l1", xops);
    }


  top = (rtx)gen_label_rtx ();		/* top of loop label */
  ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (top));


  xops[0] = src; xops[1] = tmp; xops[2] = dst;

  if (fixed && (align & 0x3) == 0)	/* word aligned move with known size */
    {
      if (fixed >= 4)
	{
	  rtx xops1[2];
	  output_asm_insn(
	    "loadw  %a0,%1\n\taddq   $4,%0\n\tstorw  %1,%a2\n\taddq   $4,%2",
			  xops);

	  xops1[0] = cnt; xops1[1] = top;
	  output_asm_insn ("subq   $4,%0\n\tbrgt   %l1", xops1);
	}

      if (fixed & 0x2)
	{
	  output_asm_insn ("loadh  %a0,%1\n\tstorh  %1,%a2", xops);
	  if (fixed & 0x1)
	    output_asm_insn ("loadb  2%a0,%1\n\tstorb  %1,2%a2", xops);
	}
      else
	if (fixed & 0x1)
	  output_asm_insn ("loadb  %a0,%1\n\tstorb  %1,%a2", xops);
    }
  else
    {
      output_asm_insn(
	  "loadb  %a0,%1\n\taddq   $1,%0\n\tstorb  %1,%a2\n\taddq   $1,%2",
		      xops);

      xops[0] = cnt; xops[1] = top;
      output_asm_insn ("subq   $1,%0\n\tbrgt   %l1", xops);
    }

  if (fixed == 0)
    ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (bottom));
}


print_operand_address (file, addr)
     FILE *file;
     register rtx addr;
{
  rtx op0,op1;

 retry:
  switch (GET_CODE (addr))
    {
    case REG:
      fprintf (file, "(%s)", reg_names[REGNO (addr)]);
      break;

    case PLUS:
      /* can be 'symbol + reg' or 'reg + reg' */

      op0 = XEXP (addr, 0);
      op1 = XEXP (addr, 1);

      if (GET_CODE (op0) == REG && GET_CODE (op1) == REG)
	{
	  fprintf (file, "[%s](%s)",
		   reg_names[REGNO (op0)], reg_names[REGNO (op1)]);
	  break;
	}

      if (GET_CODE (op0) == REG && CONSTANT_ADDRESS_P (op1))
	{
	  output_addr_const (file, op1);
	  fprintf (file, "(%s)", reg_names[REGNO (op0)]);
	  break;
	}

      if (GET_CODE (op1) == REG && CONSTANT_ADDRESS_P (op0))
	{
	  output_addr_const (file, op0);
	  fprintf (file, "(%s)", reg_names[REGNO (op1)]);
	  break;
	}
      abort ();				/* Oh no */

    default:
      output_addr_const (file, addr);
    }
}


char *
rev_cond_name (op)
     rtx op;
{
  switch (GET_CODE (op))
    {
    case EQ:
      return "ne";
    case NE:
      return "eq";
    case LT:
      return "ge";
    case LE:
      return "gt";
    case GT:
      return "le";
    case GE:
      return "lt";
    case LTU:
      return "geu";
    case LEU:
      return "gtu";
    case GTU:
      return "leu";
    case GEU:
      return "ltu";

    default:
      abort ();
    }
}


/* Do what is necessary for `va_start'.  The argument is ignored;
   We look at the current function to determine if stdargs or varargs
   is used and fill in an initial va_list.  A pointer to this constructor
   is returned.  */

struct rtx_def *
clipper_builtin_saveregs (arglist)
     tree arglist;
{
  extern int current_function_varargs;
  rtx block, addr, argsize;
  /* Allocate the va_list constructor */
  block = assign_stack_local (BLKmode, 8 * UNITS_PER_WORD, 2 * BITS_PER_WORD);
  RTX_UNCHANGING_P (block) = 1;
  RTX_UNCHANGING_P (XEXP (block, 0)) = 1;

  addr = copy_to_reg (XEXP (block, 0));

  /* Store float regs  */

  emit_move_insn (gen_rtx (MEM, DFmode, addr),
		  gen_rtx (REG, DFmode, 16));

  emit_move_insn (gen_rtx (MEM, DFmode,
			   gen_rtx (PLUS, Pmode, addr,
				    gen_rtx (CONST_INT, Pmode, 8))),
		  gen_rtx (REG, DFmode, 17));

  /* Store int regs  */

  emit_move_insn (gen_rtx (MEM, SImode,
			   gen_rtx (PLUS, Pmode, addr,
				    gen_rtx (CONST_INT, Pmode, 16))),
		  gen_rtx (REG, SImode, 0));

  emit_move_insn (gen_rtx (MEM, SImode,
			   gen_rtx (PLUS, Pmode, addr,
				    gen_rtx (CONST_INT, Pmode, 20))),
		  gen_rtx (REG, SImode, 1));


  /* Store the arg pointer in the __va_stk member.  */

  emit_move_insn (gen_rtx (MEM, SImode,
			   gen_rtx (PLUS, Pmode, addr,
				    gen_rtx (CONST_INT, Pmode, 24))),
		  copy_to_reg (virtual_incoming_args_rtx));


  /* Return the address of the va_list constructor, but don't put it in a
     register.  This fails when not optimizing and produces worse code when
     optimizing.  */
  return XEXP (block, 0);
}


/* Return truth value of whether OP can be used as an word register
   operand. Reject (SUBREG:SI (REG:SF )) */

int
int_reg_operand (op, mode)
     rtx op;
     enum machine_mode mode;
{
  return (register_operand (op, mode) &&
	  (GET_CODE (op) != SUBREG ||
	   GET_MODE_CLASS (GET_MODE (SUBREG_REG (op))) == MODE_INT));
}

/* Return truth value of whether OP can be used as a float register
   operand. Reject (SUBREG:SF (REG:SI )) )) */

int
fp_reg_operand (op, mode)
     rtx op;
     enum machine_mode mode;
{
  return (register_operand (op, mode) &&
	  (GET_CODE (op) != SUBREG ||
	   GET_MODE_CLASS (GET_MODE (SUBREG_REG (op))) == MODE_FLOAT));
}

