/* Definitions of target machine for GNU compiler.  amiga 68000/68020 version.
   Copyright (C) 1992 Free Software Foundation, Inc.
   Contributed by Markus M. Wild (wild@amiga.physik.unizh.ch).

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 "tree.h"
#include "c-tree.h"
#include "m68k/m68k.c"

/* Does operand (which is a symbolic_operand) live in text space? If
   so SYMBOL_REF_FLAG, which is set by ENCODE_SECTION_INFO, will be true.

   This function is used in base relative code generation. */

int
read_only_operand (operand)
     rtx operand;
{
  if (GET_CODE (operand) == CONST)
    operand = XEXP (XEXP (operand, 0), 0);
  if (GET_CODE (operand) == SYMBOL_REF)
    return SYMBOL_REF_FLAG (operand) || CONSTANT_POOL_ADDRESS_P (operand);
  return 1;
}

/* This function is used while generating a base relative code.
   It returns 1 if a decl is not relocatable, i. e., if it can be put
   in the text section.
   Currently, it's very primitive: it just checks if the object size
   is less than 4 bytes (i. e., if it can hold a pointer).  It also
   supports arrays and floating point types.  */

int
amigaos_put_in_text (decl)
     tree decl;
{
  tree type = TREE_TYPE (decl);
  if (TREE_CODE (type) == ARRAY_TYPE)
    type = TREE_TYPE (type);
  return (TREE_INT_CST_HIGH (TYPE_SIZE (type)) == 0
	  && TREE_INT_CST_LOW (TYPE_SIZE (type)) < 32)
	 || FLOAT_TYPE_P (type);
}

/*
 * Support for Amiga-specific variable and function attributes.
 * KI 15.02.96
 */

#define AMIGA_CHIP_SECTION_NAME ".datachip"

/* Return nonzero if IDENTIFIER with arguments ARGS is a valid machine
   specific attribute for DECL.  The attributes in ATTRIBUTES have previously
   been assigned to DECL. */

int
valid_amigaos_decl_attribute(tree decl, tree attributes, tree identifier,
			     tree args)
{
  if (is_attribute_p("chip", identifier))
#ifdef ASM_OUTPUT_SECTION_NAME
    {
      if (TREE_CODE(decl)==VAR_DECL)
	{
	  if (!TREE_STATIC(decl) && !DECL_EXTERNAL(decl))
	    error("`chip' attribute cannot be specified for local variables");
	  else
	    {
	      /* The decl may have already been given a section attribute from
		 a previous declaration.  Ensure they match.  */
	      if (DECL_SECTION_NAME (decl) == NULL_TREE)
		{
		  DECL_SECTION_NAME (decl) =
		    build_string(strlen(AMIGA_CHIP_SECTION_NAME)+1,
		      AMIGA_CHIP_SECTION_NAME);
		  TREE_TYPE(DECL_SECTION_NAME (decl)) = string_type_node;
		}
	      else if (strcmp (TREE_STRING_POINTER (DECL_SECTION_NAME (decl)),
			       AMIGA_CHIP_SECTION_NAME) != 0)
		error_with_decl (decl,
		  "`chip' for `%s' conflicts with previous declaration");
	      if (args!=NULL_TREE)
		warning("superfluous arguments to `chip' attribute ignored");
	      return 1;
	    }
	}
      else
	error_with_decl (decl, "`chip' attribute not allowed for `%s'");
    }
#else
    error("`chip' attribute is not supported for this target");
#endif

  return 0;
}

/* Return nonzero if IDENTIFIER with arguments ARGS is a valid machine
   specific attribute for TYPE.  The attributes in ATTRIBUTES have previously
   been assigned to TYPE. */

int
valid_amigaos_type_attribute(tree type, tree attributes, tree identifier,
			     tree args)
{
  if (is_attribute_p("interrupt", identifier)
      || is_attribute_p("saveds", identifier)
      || is_attribute_p("stackext", identifier)
      || is_attribute_p("stkparm", identifier)
      || is_attribute_p("regparm", identifier))
    {
      if (TREE_CODE(type)==FUNCTION_TYPE || TREE_CODE(type)==METHOD_TYPE)
	{
	  /* "regparm" accepts one optional argument - number of registers in
	     single class that should be used to pass arguments. */
	  if (is_attribute_p("regparm", identifier))
	    {
	      if (lookup_attribute("stkparm", attributes))
		error("`regparm' and `stkparm' are mutually exclusive");
	      if (args && TREE_CODE(args)==TREE_LIST)
		{
		  tree numofregs=TREE_VALUE(args);
		  if (numofregs)
		    {
		      if (TREE_CODE(numofregs)!=INTEGER_CST
			  || TREE_INT_CST_HIGH(numofregs)
			  || TREE_INT_CST_LOW(numofregs)<1
			  || TREE_INT_CST_LOW(numofregs)>4)
			error("invalid argument to `regparm' attribute");
		      /* Will cause warning if user provided us with more
			 arguments. */
		      args=TREE_CHAIN(args);
		    }
		}
	    }
	  if (is_attribute_p("stkparm", identifier)
	      && lookup_attribute("regparm", attributes))
	    error("`regparm' and `stkparm' are mutually exclusive");
	  if ((is_attribute_p("stackext", identifier)
		&& lookup_attribute("interrupt", attributes))
	      || (is_attribute_p("interrupt", identifier)
		&& lookup_attribute("stackext", attributes)))
	    error("`stackext' and `interrupt' are mutually exclusive");
	  if (args!=NULL_TREE)
	    warning("superfluous arguments to `%s' attribute ignored",
	      IDENTIFIER_POINTER(identifier));
	  return 1;
	}
	/* Don't treat it as error if this is a pointer to function - in such
	   a case this function will be called again with inner function type.
	 */
      else if (TREE_CODE(type)!=POINTER_TYPE
	  || TREE_CODE(TREE_TYPE(type))!=FUNCTION_TYPE)
	error("`%s' attribute used in wrong context",
	  IDENTIFIER_POINTER(identifier));
    }

  return 0;
}

/* Return zero if the attributes on TYPE1 and TYPE2 are incompatible,
   one if they are compatible, and two if they are nearly compatible
   (which causes a warning to be generated). */

int
comp_amigaos_type_attributes(tree type1, tree type2)
{
  /* Functions or methods are incompatible if they specify mutually exclusive
     ways of passing arguments. */
  if (TREE_CODE(type1)==FUNCTION_TYPE || TREE_CODE(type1)==METHOD_TYPE)
    {
      tree arg1, arg2;
      arg1=TYPE_ARG_TYPES(type1);
      arg2=TYPE_ARG_TYPES(type2);
      for (; arg1 && arg2; arg1=TREE_CHAIN(arg1), arg2=TREE_CHAIN(arg2))
	if (TREE_VALUE(arg1) && TREE_VALUE(arg2))
	  {
	    tree asm1, asm2;
	    asm1=lookup_attribute("asm", TYPE_ATTRIBUTES(TREE_VALUE(arg1)));
	    asm2=lookup_attribute("asm", TYPE_ATTRIBUTES(TREE_VALUE(arg2)));
	    if (asm1 && asm2)
	      {
		if (TREE_INT_CST_LOW(TREE_VALUE(asm1))!=
		    TREE_INT_CST_LOW(TREE_VALUE(asm2)))
		return 0; /* Two different registers specified. */
	      }
	    else
	      if (asm1 || asm2)
		return 0; /* "asm" used in only one type. */
	  }
      if ((lookup_attribute("stkparm", TYPE_ATTRIBUTES(type1)) ? 1 : 0)!=
	    (lookup_attribute("stkparm", TYPE_ATTRIBUTES(type2)) ? 1 : 0)
	  || (lookup_attribute("regparm", TYPE_ATTRIBUTES(type1)) ? 1 : 0)!=
	    (lookup_attribute("regparm", TYPE_ATTRIBUTES(type2)) ? 1 : 0))
	return 0; /* "stkparm" and "regparm" are mutually exclusive. */
      if ((arg1=lookup_attribute("regparm", TYPE_ATTRIBUTES(type1)))
	  && (arg2=lookup_attribute("regparm", TYPE_ATTRIBUTES(type2))))
	{
	  int num1=-1, num2=-1;
	  if (TREE_VALUE(arg1) && TREE_CODE(TREE_VALUE(arg1))==TREE_LIST)
	    {
	      tree numofregs=TREE_VALUE(TREE_VALUE(arg1));
	      if (numofregs)
		num1=TREE_INT_CST_LOW(numofregs);
	    }
	  if (TREE_VALUE(arg2) && TREE_CODE(TREE_VALUE(arg2))==TREE_LIST)
	    {
	      tree numofregs=TREE_VALUE(TREE_VALUE(arg2));
	      if (numofregs)
		num2=TREE_INT_CST_LOW(numofregs);
	    }
	  if (num1!=num2)
	    return 0; /* Different numbers, or no number in one type. */
	}
    }
  return 1;
}

/* Common routine used to check if "a4" should be preserved/restored.  */

int
amigaos_restore_a4(void)
{
  return (flag_pic>=3 && (TARGET_RESTORE_A4 || TARGET_ALWAYS_RESTORE_A4
	    || lookup_attribute("saveds",
		 TYPE_ATTRIBUTES(TREE_TYPE(current_function_decl)))));
}

/* Don't output epilogue as RTL if preserving "a4".  */

int
amigaos_use_return_insn(void)
{
  return (use_return_insn() && !amigaos_restore_a4());
}

/*
Stack checking and auto-extend

This is my first attempt to implement stack extension with gcc.
If you think some things should be changed please write
to me immediately (fleischr@izfm.uni-stuttgart.de, or even better
to amiga-gcc-port@lists.funet.fi for discussion).

If you don't want to recompile gcc (to check it out) you can still
test the example supported (bigtest.c) or read the documentation.
Simply do a 'make' to build it.

Matthias
*/

rtx gen_stack_management_call (stack_pointer, arg, func)
     rtx stack_pointer; /* rtx to put the result into       */
     rtx arg;           /* The argument to put into d0      */
     char *func;        /* The name of the function to call */
{
  rtx fcall, assem;
  emit_move_insn (gen_rtx (REG, SImode, 0), arg); /* Move arg to d0 */
  assem = gen_rtx (ASM_OPERANDS, VOIDmode, func, "=r", 0,
                   rtvec_alloc(1), rtvec_alloc(1), "internal", 0);
  XVECEXP (assem, 3, 0) = gen_rtx (REG, SImode, 0);
  XVECEXP (assem, 4, 0) = gen_rtx (ASM_INPUT, SImode, "r");
  fcall = gen_rtx (PARALLEL, VOIDmode, rtvec_alloc(1+4));
  XVECEXP (fcall, 0, 0)
    = gen_rtx (SET, VOIDmode, stack_pointer, assem);
  XVECEXP (fcall, 0, 1)
    = gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, QImode, 9));
  XVECEXP (fcall, 0, 2)
    = gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, QImode, 8));
  XVECEXP (fcall, 0, 3)
    = gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, QImode, 1));
  XVECEXP (fcall, 0, 4)
    = gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, QImode, 0));  
  return fcall; /* call function sp=func(d0) */
}

rtx gen_stack_cleanup_call (stack_pointer,sa)
     rtx stack_pointer;
     rtx sa;
{
  return gen_stack_management_call (stack_pointer, sa, "jbsr ___move_d0_sp");
}

/*
 * Support for passing arguments in registers. KI 30.03.96
 */

char *amigaos_regparm_string;
int amigaos_regparm;

/* A function for initializing the variable CUM for the state at the
   beginning of the argument list.  The variable has type
   `CUMULATIVE_ARGS'.  The value of FNTYPE is the tree node for the data
   type of the function which will receive the args, or 0 if the args are
   to a compiler support library function.

   When processing a call to a compiler support library function,
   LIBNAME identifies which one.  It is a `symbol_ref' rtx which
   contains the name of the function, as a string.  LIBNAME is 0 when
   an ordinary C function call is being processed.  Thus, each time
   this macro is called, either LIBNAME or FNTYPE is nonzero, but
   never both of them at once. */

void
amigaos_init_cumulative_args(CUMULATIVE_ARGS *cum, tree fntype, tree libname)
{
  cum->regnum=-1;
  cum->regsused=0;
  cum->numofregparm=amigaos_regparm;
  if (fntype)
    {
      if (lookup_attribute("stkparm", TYPE_ATTRIBUTES(fntype)))
	cum->numofregparm=0;
      else
	{
	  tree ratree;
	  if (ratree=lookup_attribute("regparm", TYPE_ATTRIBUTES(fntype)))
	    {
	      cum->numofregparm=(amigaos_regparm ? amigaos_regparm : 2);
	      if (TREE_VALUE(ratree)
		  && TREE_CODE(TREE_VALUE(ratree))==TREE_LIST)
		{
		  tree numofregs=TREE_VALUE(TREE_VALUE(ratree));
		  cum->numofregparm=
		    (numofregs ? TREE_INT_CST_LOW(numofregs) :
		      (amigaos_regparm ? amigaos_regparm : 2));
		}
	    }
	}
      cum->formaltype=TYPE_ARG_TYPES(fntype);
    }
  else /* Call to compiler-support function. */
    {
      cum->numofregparm=0;
      cum->formaltype=0;
    }

  if (cum->numofregparm)
    {
      /* If this is a vararg call, put all arguments on stack. */
      tree param, next_param;
      for (param=TYPE_ARG_TYPES(fntype); param; param=next_param)
	{
	  next_param=TREE_CHAIN(param);
	  if (!next_param && TREE_VALUE(param)!=void_type_node)
	    cum->numofregparm=0;
	}
    }
#ifndef PCC_STATIC_STRUCT_RETURN
  /* If return value is a structure, it's address is passed as additional
     argument in a1, so we can't use this register for our purposes. */
  if (cum->numofregparm)
      if (TREE_CODE(TREE_TYPE(fntype))==RECORD_TYPE
	  || TREE_CODE(TREE_TYPE(fntype))==UNION_TYPE)
	cum->regsused |= 1 << STRUCT_VALUE_REGNUM;
#endif
}

/* A function to update the summarizer variable CUM to advance past an
   argument in the argument list. The values MODE, TYPE and NAMED describe
   that argument.  Once this is done, the variable CUM is suitable for
   analyzing the *following* argument with `FUNCTION_ARG', etc. */

void
amigaos_function_arg_advance(CUMULATIVE_ARGS *cum, enum machine_mode mode,
			     tree type, int named)
{
  if (cum->regnum!=-1)
    {
      cum->regsused |= (1 << cum->regnum);
      cum->regnum=-1;
    }
  if (cum->formaltype)
    cum->formaltype=TREE_CHAIN((tree)cum->formaltype);
}

/* A C expression that controls whether a function argument is passed
   in a register, and which register.

   The arguments are CUM, which summarizes all the previous
   arguments; MODE, the machine mode of the argument; TYPE, the data
   type of the argument as a tree node or 0 if that is not known
   (which happens for C support library functions); and NAMED, which
   is 1 for an ordinary argument and 0 for nameless arguments that
   correspond to `...' in the called function's prototype.

   The value of the expression should either be a `reg' RTX for the
   hard register in which to pass the argument, or zero to pass the
   argument on the stack. */

struct rtx_def *
amigaos_function_arg(CUMULATIVE_ARGS *cum, enum machine_mode mode,
  tree type, int named)
{
  tree asmtree;
  if (cum->formaltype && TREE_VALUE((tree)cum->formaltype)
      && (asmtree=lookup_attribute("asm",
			TYPE_ATTRIBUTES(TREE_VALUE((tree)cum->formaltype)))))
    {
#if 0
      /* See c-decl.c/push_parm_decl for an explanation why this doesn't work.
       */
      cum->regnum=TREE_INT_CST_LOW(TREE_VALUE(TREE_VALUE(asmtree)));
#else
      cum->regnum=TREE_INT_CST_LOW(TREE_VALUE(asmtree));
#endif
      if (cum->regsused & (1 << cum->regnum))
	error("two parameters allocated for one register");
      return gen_rtx(REG, mode, cum->regnum);
    }
  else if (cum->numofregparm)
    {
      if (GET_MODE_CLASS(mode)==MODE_INT && GET_MODE_SIZE(mode)<=4)
	{
	  if (POINTER_TYPE_P(type))
	    {
	      int count;
	      long mask=0x100;
	      for (count=0; count<cum->numofregparm; count++, mask<<=1)
		if (!(cum->regsused & mask))
		  {
		    cum->regnum=count+8;
		    break;
		  }
	    }
	  else
	    {
	      int count;
	      long mask=0x1;
	      for (count=0; count<cum->numofregparm; count++, mask<<=1)
		if (!(cum->regsused & mask))
		  {
		    cum->regnum=count;
		    break;
		  }
	    }
	}
      if (TARGET_68881 && GET_MODE_CLASS(mode)==MODE_FLOAT)
	{
	  int count;
	  long mask=0x10000;
	  for (count=0; count<cum->numofregparm; count++, mask<<=1)
	    if (!(cum->regsused & mask))
	      {
		cum->regnum=count+16;
		break;
	      }
	}
      if (cum->regnum!=-1)
	return gen_rtx(REG, mode, cum->regnum);
    }
  return 0;
}
