/* Definitions of Toshiba Media Processor
   Copyright (C) 2001-2015 Free Software Foundation, Inc.
   Contributed by Red Hat, Inc.

This file is part of GCC.

GCC 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 3, or (at your option) any later
version.

GCC 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 GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "tree.h"
#include "diagnostic-core.h"
#include "c-family/c-pragma.h"
#include "cpplib.h"
#include "hard-reg-set.h"
#include "output.h" /* for decode_reg_name */
#include "mep-protos.h"
#include "hashtab.h"
#include "hash-set.h"
#include "vec.h"
#include "machmode.h"
#include "input.h"
#include "function.h"
#define MAX_RECOG_OPERANDS 10
#include "reload.h"
#include "target.h"

enum cw_which { CW_AVAILABLE, CW_CALL_SAVED };

/* This is normally provided by rtl.h but we can't include that file
   here.  It's safe to copy the definition here because we're only
   using it internally; the value isn't passed to functions outside
   this file.  */
#ifndef INVALID_REGNUM
#define INVALID_REGNUM                    (~(unsigned int) 0)
#endif

static enum cpp_ttype
mep_pragma_lex (tree *valp)
{
  enum cpp_ttype t = pragma_lex (valp);
  if (t == CPP_EOF)
    t = CPP_PRAGMA_EOL;
  return t;
}

static void
mep_pragma_io_volatile (cpp_reader *reader ATTRIBUTE_UNUSED)
{
  /* On off.  */
  tree val;
  enum cpp_ttype type;
  const char * str;

  type = mep_pragma_lex (&val);
  if (type == CPP_NAME)
    {
      str = IDENTIFIER_POINTER (val);

      type = mep_pragma_lex (&val);
      if (type != CPP_PRAGMA_EOL)
	warning (0, "junk at end of #pragma io_volatile");

      if (strcmp (str, "on") == 0)
	{
	  target_flags |= MASK_IO_VOLATILE;
	  return;
	}
      if (strcmp (str, "off") == 0)
	{
	  target_flags &= ~ MASK_IO_VOLATILE;
	  return;
	}
    }

  error ("#pragma io_volatile takes only on or off");
}

static unsigned int
parse_cr_reg (const char * str)
{
  unsigned int regno;

  regno = decode_reg_name (str);
  if (regno >= FIRST_PSEUDO_REGISTER)
    return INVALID_REGNUM;

  /* Verify that the regno is in CR_REGS.  */
  if (! TEST_HARD_REG_BIT (reg_class_contents[CR_REGS], regno))
    return INVALID_REGNUM;
  return regno;
}

static bool
parse_cr_set (HARD_REG_SET * set)
{
  tree val;
  enum cpp_ttype type;
  unsigned int last_regno = INVALID_REGNUM;
  bool do_range = false;

  CLEAR_HARD_REG_SET (*set);

  while ((type = mep_pragma_lex (&val)) != CPP_PRAGMA_EOL)
    {
      if (type == CPP_COMMA)
	{
	  last_regno = INVALID_REGNUM;
	  do_range = false;
	}
      else if (type == CPP_ELLIPSIS)
	{
	  if (last_regno == INVALID_REGNUM)
	    {
	      error ("invalid coprocessor register range");
	      return false;
	    }
	  do_range = true;
	}
      else if (type == CPP_NAME || type == CPP_STRING)
	{
	  const char *str;
	  unsigned int regno, i;

	  if (TREE_CODE (val) == IDENTIFIER_NODE)
	    str = IDENTIFIER_POINTER (val);
  	  else if (TREE_CODE (val) == STRING_CST)
	    str = TREE_STRING_POINTER (val);
	  else
	    gcc_unreachable ();

	  regno = parse_cr_reg (str);
	  if (regno == INVALID_REGNUM)
	    {
	      error ("invalid coprocessor register %qE", val);
	      return false;
	    }

	  if (do_range)
	    {
	      if (last_regno > regno)
		i = regno, regno = last_regno;
	      else
		i = last_regno;
	      do_range = false;
	    }
	  else
	    last_regno = i = regno;

	  while (i <= regno)
	    {
	      SET_HARD_REG_BIT (*set, i);
	      i++;
	    }
	}
      else
	{
	  error ("malformed coprocessor register");
	  return false;
	}
    }
  return true;
}

static void
mep_pragma_coprocessor_which (enum cw_which cw_which)
{
  HARD_REG_SET set;

  /* Process the balance of the pragma and turn it into a hard reg set.  */
  if (! parse_cr_set (&set))
    return;

  /* Process the collected hard reg set.  */
  switch (cw_which)
    {
    case CW_AVAILABLE:
      {
	int i;
	for (i = 0; i < FIRST_PSEUDO_REGISTER; ++i)
	  if (TEST_HARD_REG_BIT (set, i))
	    fixed_regs[i] = 0;
      }
      break;

    case CW_CALL_SAVED:
      {
	int i;
	for (i = 0; i < FIRST_PSEUDO_REGISTER; ++i)
	  if (TEST_HARD_REG_BIT (set, i))
	    fixed_regs[i] = call_used_regs[i] = 0;
      }
      break;

    default:
      gcc_unreachable ();
    }

  /* Fix up register class hierarchy.  */
  mep_save_register_info ();
  mep_reinit_regs ();

  if (cfun == 0)
    {
      init_dummy_function_start ();
      init_caller_save ();
      expand_dummy_function_end ();
    }
  else
    {
      init_caller_save ();
    }
}

static void
mep_pragma_coprocessor_width (void)
{
  tree val;
  enum cpp_ttype type;
  HOST_WIDE_INT i;

  type = mep_pragma_lex (&val);
  switch (type)
    {
    case CPP_NUMBER:
      if (! tree_fits_uhwi_p (val))
	break;
      i = tree_to_uhwi (val);
      /* This pragma no longer has any effect.  */
#if 0
      if (i == 32)
	target_flags &= ~MASK_64BIT_CR_REGS;
      else if (i == 64)
	target_flags |= MASK_64BIT_CR_REGS;
      else
	break;
      targetm.init_builtins ();
#else
      if (i != 32 && i != 64)
	break;
#endif

      type = mep_pragma_lex (&val);
      if (type != CPP_PRAGMA_EOL)
	warning (0, "junk at end of #pragma GCC coprocessor width");
      return;

    default:
      break;
    }

  error ("#pragma GCC coprocessor width takes only 32 or 64");
}

static void
mep_pragma_coprocessor_subclass (void)
{
  tree val;
  enum cpp_ttype type;
  HARD_REG_SET set;
  int class_letter;
  enum reg_class rclass;

  type = mep_pragma_lex (&val);
  if (type != CPP_CHAR)
    goto syntax_error;
  class_letter = tree_to_uhwi (val);
  switch (class_letter)
    {
    case 'A':
      rclass = USER0_REGS;
      break;
    case 'B':
      rclass = USER1_REGS;
      break;
    case 'C':
      rclass = USER2_REGS;
      break;
    case 'D':
      rclass = USER3_REGS;
      break;
    default:
      error ("#pragma GCC coprocessor subclass letter must be in [ABCD]");
      return;
    }
  if (reg_class_size[rclass] > 0)
    {
      error ("#pragma GCC coprocessor subclass '%c' already defined",
	     class_letter);
      return;
    }

  type = mep_pragma_lex (&val);
  if (type != CPP_EQ)
    goto syntax_error;

  if (! parse_cr_set (&set))
    return;

  /* Fix up register class hierarchy.  */
  COPY_HARD_REG_SET (reg_class_contents[rclass], set);
  mep_init_regs ();
  return;

 syntax_error:
  error ("malformed #pragma GCC coprocessor subclass");
}

static void
mep_pragma_disinterrupt (cpp_reader *reader ATTRIBUTE_UNUSED)
{
  tree val;
  enum cpp_ttype type;
  int saw_one = 0;

  for (;;)
    {
      type = mep_pragma_lex (&val);
      if (type == CPP_COMMA)
	continue;
      if (type != CPP_NAME)
	break;
      mep_note_pragma_disinterrupt (IDENTIFIER_POINTER (val));
      saw_one = 1;
    }
  if (!saw_one || type != CPP_PRAGMA_EOL)
    {
      error ("malformed #pragma disinterrupt");
      return;
    }
}

static void
mep_pragma_coprocessor (cpp_reader *reader ATTRIBUTE_UNUSED)
{
  tree val;
  enum cpp_ttype type;

  type = mep_pragma_lex (&val);
  if (type != CPP_NAME)
    {
      error ("malformed #pragma GCC coprocessor");
      return;
    }

  if (!TARGET_COP)
    error ("coprocessor not enabled");

  if (strcmp (IDENTIFIER_POINTER (val), "available") == 0)
    mep_pragma_coprocessor_which (CW_AVAILABLE);
  else if (strcmp (IDENTIFIER_POINTER (val), "call_saved") == 0)
    mep_pragma_coprocessor_which (CW_CALL_SAVED);
  else if (strcmp (IDENTIFIER_POINTER (val), "width") == 0)
    mep_pragma_coprocessor_width ();
  else if (strcmp (IDENTIFIER_POINTER (val), "subclass") == 0)
    mep_pragma_coprocessor_subclass ();
  else
    error ("unknown #pragma GCC coprocessor %E", val);
}

static void
mep_pragma_call (cpp_reader *reader ATTRIBUTE_UNUSED)
{
  tree val;
  enum cpp_ttype type;
  int saw_one = 0;

  for (;;)
    {
      type = mep_pragma_lex (&val);
      if (type == CPP_COMMA)
	continue;
      if (type != CPP_NAME)
	break;
      mep_note_pragma_call (IDENTIFIER_POINTER (val));
      saw_one = 1;
    }
  if (!saw_one || type != CPP_PRAGMA_EOL)
    {
      error ("malformed #pragma call");
      return;
    }
}

void
mep_register_pragmas (void)
{
  c_register_pragma ("custom", "io_volatile", mep_pragma_io_volatile);
  c_register_pragma ("GCC", "coprocessor", mep_pragma_coprocessor);
  c_register_pragma (0, "disinterrupt", mep_pragma_disinterrupt);
  c_register_pragma (0, "call", mep_pragma_call);
}