/* Subroutines for insn-output.c for Clipper
   Copyright (C) 1987, 88, 91, 97, 98, 1999 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, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include "config.h"
#include "system.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 "function.h"
#include "expr.h"
#include "flags.h"
#include "machmode.h"

extern char regs_ever_live[];

extern int frame_pointer_needed;

static int frame_size;

/* Compute size of a clipper stack frame where 'lsize' is the required
   space for local variables.  */

int
clipper_frame_size (lsize)
     int lsize;
{
  int i, size;				/* total size of frame */
  int save_size;
  save_size = 0;			/* compute size for reg saves */

  for (i = 16; 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;

  size = lsize + save_size;

  size = (size + 7) & ~7;		/* align to 64 Bit */
  return size;
}

/* Prologue and epilogue output
   Function is entered with pc pushed, i.e. stack is 32 bit aligned

   current_function_args_size == 0 means that the current function's args
   are passed totally in registers i.e fp is not used as ap.
   If frame_size is also 0 the current function does not push anything and
   can run with misaligned stack -> subq $4,sp / add $4,sp on entry and exit
   can be omitted.  */

void
output_function_prologue (file, lsize)
     FILE *file;
     int lsize;				/* size for locals */
{
  int i, offset;
  int size;

  frame_size = size = clipper_frame_size (lsize);

  if (frame_pointer_needed)
    {
      fputs ("\tpushw  fp,sp\n", file);
      fputs ("\tmovw   sp,fp\n", file);
    }
  else if (size != 0 || current_function_args_size != 0)
    {
      size += 4;			/* keep stack aligned */
      frame_size = size;		/* must push data or access args */
    }

  if (size)
    {
      if (size < 16)
	fprintf (file, "\tsubq   $%d,sp\n", size);
      else
	fprintf (file, "\tsubi   $%d,sp\n", size);

      /* register save slots are relative to sp, because we have small positive
	 displacements and this works whether we have a frame pointer or not */

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

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

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

  if (frame_pointer_needed)
    {
      offset = -frame_size;

      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);
    }

  else					/* no frame pointer */
    {
      offset = 0;

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

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

      if (frame_size > 0)
	{
	  if (frame_size < 16)
	    fprintf (file, "\taddq   $%d,sp\n", frame_size);
	  else
	    fprintf (file, "\taddi   $%d,sp\n", frame_size);
	}

      fputs ("\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 ();
    }
}


/* Dump the argument register to the stack; return the location
   of the block.  */

struct rtx_def *
clipper_builtin_saveregs ()
{
  rtx block, addr, r0_addr, r1_addr, f0_addr, f1_addr, mem;
  int set = get_varargs_alias_set ();

  /* Allocate the save area for r0,r1,f0,f1 */

  block = assign_stack_local (BLKmode, 6 * UNITS_PER_WORD, 2 * BITS_PER_WORD);

  RTX_UNCHANGING_P (block) = 1;
  RTX_UNCHANGING_P (XEXP (block, 0)) = 1;

  addr = XEXP (block, 0);

  r0_addr = addr;
  r1_addr = plus_constant (addr, 4);
  f0_addr = plus_constant (addr, 8);
  f1_addr = plus_constant (addr, 16);

  /* Store int regs  */

  mem = gen_rtx_MEM (SImode, r0_addr);
  MEM_ALIAS_SET (mem) = set;
  emit_move_insn (mem, gen_rtx_REG (SImode, 0));

  mem = gen_rtx_MEM (SImode, r1_addr);
  MEM_ALIAS_SET (mem) = set;
  emit_move_insn (mem, gen_rtx_REG (SImode, 1));

  /* Store float regs  */

  mem = gen_rtx_MEM (DFmode, f0_addr);
  MEM_ALIAS_SET (mem) = set;
  emit_move_insn (mem, gen_rtx_REG (DFmode, 16));

  mem = gen_rtx_MEM (DFmode, f1_addr);
  MEM_ALIAS_SET (mem) = set;
  emit_move_insn (mem, gen_rtx_REG (DFmode, 17));

  if (current_function_check_memory_usage)
    {
      emit_library_call (chkr_set_right_libfunc, 1, VOIDmode, 3,
			 f0_addr, ptr_mode,
			 GEN_INT (GET_MODE_SIZE (DFmode)),
			 TYPE_MODE (sizetype),
			 GEN_INT (MEMORY_USE_RW),
			 TYPE_MODE (integer_type_node));
      emit_library_call (chkr_set_right_libfunc, 1, VOIDmode, 3,
			 f1_addr, ptr_mode,
			 GEN_INT (GET_MODE_SIZE (DFmode)),
			 TYPE_MODE (sizetype),
			 GEN_INT (MEMORY_USE_RW), 
			 TYPE_MODE (integer_type_node));
      emit_library_call (chkr_set_right_libfunc, 1, VOIDmode, 3,
			 r0_addr, ptr_mode,
			 GEN_INT (GET_MODE_SIZE (SImode)),
			 TYPE_MODE (sizetype),
			 GEN_INT (MEMORY_USE_RW),
			 TYPE_MODE (integer_type_node));
      emit_library_call (chkr_set_right_libfunc, 1, VOIDmode, 3,
			 r1_addr, ptr_mode,
			 GEN_INT (GET_MODE_SIZE (SImode)),
			 TYPE_MODE (sizetype),
			 GEN_INT (MEMORY_USE_RW),
			 TYPE_MODE (integer_type_node));
    }

  return addr;
}

tree
clipper_build_va_list ()
{
  tree record, ap, reg, num;

  /*
    struct
    {
      int __va_ap;		// pointer to stack args
      void *__va_reg[4];	// pointer to r0,f0,r1,f1
      int __va_num;		// number of args processed
    };
  */

  record = make_node (RECORD_TYPE);

  num = build_decl (FIELD_DECL, get_identifier ("__va_num"),
		    integer_type_node);
  DECL_FIELD_CONTEXT (num) = record;

  reg = build_decl (FIELD_DECL, get_identifier ("__va_reg"),
		    build_array_type (ptr_type_node,
				      build_index_type (build_int_2 (3, 0))));
  DECL_FIELD_CONTEXT (reg) = record;
  TREE_CHAIN (reg) = num;

  ap = build_decl (FIELD_DECL, get_identifier ("__va_ap"),
		   integer_type_node);
  DECL_FIELD_CONTEXT (ap) = record;
  TREE_CHAIN (ap) = reg;

  TYPE_FIELDS (record) = ap;
  layout_type (record);

  return record;
}

void
clipper_va_start (stdarg_p, valist, nextarg)
     int stdarg_p;
     tree valist;
     rtx nextarg ATTRIBUTE_UNUSED;
{
  tree ap_field, reg_field, num_field;
  tree t, u, save_area;

  ap_field = TYPE_FIELDS (TREE_TYPE (valist));
  reg_field = TREE_CHAIN (ap_field);
  num_field = TREE_CHAIN (reg_field);

  ap_field = build (COMPONENT_REF, TREE_TYPE (ap_field), valist, ap_field);
  reg_field = build (COMPONENT_REF, TREE_TYPE (reg_field), valist, reg_field);
  num_field = build (COMPONENT_REF, TREE_TYPE (num_field), valist, num_field);

  /* Call __builtin_saveregs to save r0, r1, f0, and f1 in a block.  */

  save_area = make_tree (integer_type_node, expand_builtin_saveregs ());

  /* Set __va_ap.  */

  t = make_tree (ptr_type_node, virtual_incoming_args_rtx);
  if (stdarg_p && current_function_args_info.size != 0)
    t = build (PLUS_EXPR, ptr_type_node, t,
	       build_int_2 (current_function_args_info.size, 0));
  t = build (MODIFY_EXPR, TREE_TYPE (ap_field), ap_field, t);
  TREE_SIDE_EFFECTS (t) = 1;
  expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);

  /* Set the four entries of __va_reg.  */

  t = build1 (NOP_EXPR, ptr_type_node, save_area);
  u = build (ARRAY_REF, ptr_type_node, reg_field, build_int (0, 0));
  t = build (MODIFY_EXPR, ptr_type_node, u, t);
  TREE_SIDE_EFFECTS (t) = 1;
  expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);

  t = fold (build (PLUS_EXPR, integer_type_node, save_area,
		   build_int_2 (8, 0)));
  t = build1 (NOP_EXPR, ptr_type_node, save_area);
  u = build (ARRAY_REF, ptr_type_node, reg_field, build_int (1, 0));
  t = build (MODIFY_EXPR, ptr_type_node, u, t);
  TREE_SIDE_EFFECTS (t) = 1;
  expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);

  t = fold (build (PLUS_EXPR, integer_type_node, save_area,
		   build_int_2 (4, 0)));
  t = build1 (NOP_EXPR, ptr_type_node, save_area);
  u = build (ARRAY_REF, ptr_type_node, reg_field, build_int (2, 0));
  t = build (MODIFY_EXPR, ptr_type_node, u, t);
  TREE_SIDE_EFFECTS (t) = 1;
  expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);

  t = fold (build (PLUS_EXPR, integer_type_node, save_area,
		   build_int_2 (16, 0)));
  t = build1 (NOP_EXPR, ptr_type_node, save_area);
  u = build (ARRAY_REF, ptr_type_node, reg_field, build_int (3, 0));
  t = build (MODIFY_EXPR, ptr_type_node, u, t);
  TREE_SIDE_EFFECTS (t) = 1;
  expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);

  /* Set __va_num.  */

  t = build_int_2 (current_function_args_info.num, 0);
  t = build (MODIFY_EXPR, TREE_TYPE (num_field), num_field, t);
  TREE_SIDE_EFFECTS (t) = 1;
  expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
}

rtx
clipper_va_arg (valist, type)
     tree valist, type;
{
  tree ap_field, reg_field, num_field;
  tree addr, t;
  HOST_WIDE_INT align;
  rtx addr_rtx, over_label = NULL_RTX, tr;

  /*
    Integers:

    if (VA.__va_num < 2)
      addr = VA.__va_reg[2 * VA.__va_num];
    else
      addr = round(VA.__va_ap), VA.__va_ap = round(VA.__va_ap) + sizeof(TYPE);
    VA.__va_num++;

    Floats:

    if (VA.__va_num < 2)
      addr = VA.__va_reg[2 * VA.__va_num + 1];
    else
      addr = round(VA.__va_ap), VA.__va_ap = round(VA.__va_ap) + sizeof(TYPE);
    VA.__va_num++;

    Aggregates:

    addr = round(VA.__va_ap), VA.__va_ap = round(VA.__va_ap) + sizeof(TYPE);
    VA.__va_num++;
  */

  ap_field = TYPE_FIELDS (TREE_TYPE (valist));
  reg_field = TREE_CHAIN (ap_field);
  num_field = TREE_CHAIN (reg_field);

  ap_field = build (COMPONENT_REF, TREE_TYPE (ap_field), valist, ap_field);
  reg_field = build (COMPONENT_REF, TREE_TYPE (reg_field), valist, reg_field);
  num_field = build (COMPONENT_REF, TREE_TYPE (num_field), valist, num_field);

  addr_rtx = gen_reg_rtx (Pmode);

  if (! AGGREGATE_TYPE_P (type))
    {
      tree inreg;
      rtx false_label;

      over_label = gen_label_rtx ();
      false_label = gen_label_rtx ();

      emit_cmp_and_jump_insns (expand_expr (num_field, NULL_RTX, 0,
					    OPTAB_LIB_WIDEN),
			       GEN_INT (2), GE, const0_rtx,
			       TYPE_MODE (TREE_TYPE (num_field)),
			       TREE_UNSIGNED (num_field), 0, false_label);

      inreg = fold (build (MULT_EXPR, integer_type_node, num_field,
			   build_int_2 (2, 0)));
      if (FLOAT_TYPE_P (type))
	inreg = fold (build (PLUS_EXPR, integer_type_node, inreg,
			     build_int_2 (1, 0)));
      inreg = fold (build (ARRAY_REF, ptr_type_node, reg_field, inreg));

      tr = expand_expr (inreg, addr_rtx, VOIDmode, EXPAND_NORMAL);
      if (tr != addr_rtx)
	emit_move_insn (addr_rtx, tr);

      emit_jump_insn (gen_jump (over_label));
      emit_barrier ();
      emit_label (false_label);
    }

  /* Round to alignment of `type', or at least integer alignment.  */

  align = TYPE_ALIGN (type);
  if (align < TYPE_ALIGN (integer_type_node))
    align = TYPE_ALIGN (integer_type_node);
  align /= BITS_PER_UNIT;

  addr = fold (build (PLUS_EXPR, ptr_type_node, ap_field,
		      build_int_2 (align-1, 0)));
  addr = fold (build (BIT_AND_EXPR, ptr_type_node, addr,
		      build_int_2 (-align, -1)));
  addr = save_expr (addr);

  tr = expand_expr (addr, addr_rtx, Pmode, EXPAND_NORMAL);
  if (tr != addr_rtx)
    emit_move_insn (addr_rtx, tr);
  
  t = build (MODIFY_EXPR, TREE_TYPE (ap_field), ap_field,
	     build (PLUS_EXPR, TREE_TYPE (ap_field), 
		    addr, build_int_2 (int_size_in_bytes (type), 0)));
  TREE_SIDE_EFFECTS (t) = 1;
  expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);

  if (over_label)
    emit_label (over_label);

  t = build (MODIFY_EXPR, TREE_TYPE (num_field), num_field,
	     build (PLUS_EXPR, TREE_TYPE (num_field), 
		    num_field, build_int_2 (1, 0)));
  TREE_SIDE_EFFECTS (t) = 1;
  expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);

  return addr_rtx;
}

/* 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));
}

