/* Target Code for ft32
   Copyright (C) 2015-2017 Free Software Foundation, Inc.
   Contributed by FTDI <support@ftdi.com>

   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 "backend.h"
#include "target.h"
#include "rtl.h"
#include "tree.h"
#include "df.h"
#include "memmodel.h"
#include "tm_p.h"
#include "regs.h"
#include "emit-rtl.h"
#include "diagnostic-core.h"
#include "output.h"
#include "stor-layout.h"
#include "calls.h"
#include "expr.h"
#include "builtins.h"
#include "print-tree.h"

/* This file should be included last.  */
#include "target-def.h"

#include <stdint.h>

#define LOSE_AND_RETURN(msgid, x)               \
  do                                            \
    {                                           \
      ft32_operand_lossage (msgid, x);            \
      return;                                   \
    } while (0)

/* Worker function for TARGET_RETURN_IN_MEMORY.  */

static bool
ft32_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
{
  const HOST_WIDE_INT size = int_size_in_bytes (type);
  return (size == -1 || size > 2 * UNITS_PER_WORD);
}

/* Define how to find the value returned by a function.
   VALTYPE is the data type of the value (as a tree).
   If the precise function being called is known, FUNC is its
   FUNCTION_DECL; otherwise, FUNC is 0.

   We always return values in register $r0 for ft32.  */

static rtx
ft32_function_value (const_tree valtype,
                     const_tree fntype_or_decl ATTRIBUTE_UNUSED,
                     bool outgoing ATTRIBUTE_UNUSED)
{
  return gen_rtx_REG (TYPE_MODE (valtype), FT32_R0);
}

/* Define how to find the value returned by a library function.

   We always return values in register $r0 for ft32.  */

static rtx
ft32_libcall_value (enum machine_mode mode, const_rtx fun ATTRIBUTE_UNUSED)
{
  return gen_rtx_REG (mode, FT32_R0);
}

/* Handle TARGET_FUNCTION_VALUE_REGNO_P.

   We always return values in register $r0 for ft32.  */

static bool
ft32_function_value_regno_p (const unsigned int regno)
{
  return (regno == FT32_R0);
}

/* Emit an error message when we're in an asm, and a fatal error for
   "normal" insns.  Formatted output isn't easily implemented, since we
   use output_operand_lossage to output the actual message and handle the
   categorization of the error.  */

static void
ft32_operand_lossage (const char *msgid, rtx op)
{
  debug_rtx (op);
  output_operand_lossage ("%s", msgid);
}

/* The PRINT_OPERAND_ADDRESS worker.  */

void
ft32_print_operand_address (FILE * file, rtx x)
{
  switch (GET_CODE (x))
    {
    case REG:
      fprintf (file, "%s,0", reg_names[REGNO (x)]);
      break;

    case PLUS:
      switch (GET_CODE (XEXP (x, 1)))
        {
        case CONST_INT:
          fprintf (file, "%s,%ld",
                   reg_names[REGNO (XEXP (x, 0))], INTVAL (XEXP (x, 1)));
          break;
        case SYMBOL_REF:
          output_addr_const (file, XEXP (x, 1));
          fprintf (file, "(%s)", reg_names[REGNO (XEXP (x, 0))]);
          break;
        case CONST:
          {
            rtx plus = XEXP (XEXP (x, 1), 0);
            if (GET_CODE (XEXP (plus, 0)) == SYMBOL_REF
                && CONST_INT_P (XEXP (plus, 1)))
              {
                output_addr_const (file, XEXP (plus, 0));
                fprintf (file, "+%ld(%s)", INTVAL (XEXP (plus, 1)),
                         reg_names[REGNO (XEXP (x, 0))]);
              }
            else
              abort ();
          }
          break;
        default:
          abort ();
        }
      break;

    default:
      output_addr_const (file, x);
      break;
    }
}

/* The PRINT_OPERAND worker.  */

void
ft32_print_operand (FILE * file, rtx x, int code)
{
  rtx operand = x;

  /* New code entries should just be added to the switch below.  If
     handling is finished, just return.  If handling was just a
     modification of the operand, the modified operand should be put in
     "operand", and then do a break to let default handling
     (zero-modifier) output the operand.  */

  switch (code)
    {
    case 0:
      /* No code, print as usual.  */
      break;

    case 'h':
      if (GET_CODE (operand) != REG)
        internal_error ("'h' applied to non-register operand");
      fprintf (file, "%s", reg_names[REGNO (operand) + 1]);
      return;

    case 'm':
      fprintf (file, "%ld", (long) (- INTVAL(x)));
      return;

    case 'd':                   // a DW spec, from an integer alignment (for BLKmode insns)
      {
        int i = INTVAL (x);
        char dwspec;
        switch (i)
          {
          case 1:
            dwspec = 'b';
            break;
          case 2:
            dwspec = 's';
            break;
          case 4:
            dwspec = 'l';
            break;
          default:
            if ((i % 4) != 0)
              internal_error ("bad alignment: %d", i);
            else
              dwspec = 'l';
            break;
          }
        fprintf (file, "%c", dwspec);
        return;
      }

    case 'f':
      {
        int bf = ft32_as_bitfield (INTVAL (x));
        fprintf (file, "512|(%d<<5)|%d", bf >> 5, bf & 31);
        return;
      }

    case 'g':
      {
        int bf = ft32_as_bitfield (0xffffffff ^ INTVAL (x));
        fprintf (file, "(%d<<5)|%d", bf >> 5, bf & 31);
        return;
      }

    case 'b':
      {
        ft32_print_operand (file, XEXP (x, 0), 0);
        return;
      }

    default:
      LOSE_AND_RETURN ("invalid operand modifier letter", x);
    }

  /* Print an operand as without a modifier letter.  */
  switch (GET_CODE (operand))
    {
    case REG:
      fprintf (file, "%s", reg_names[REGNO (operand)]);
      return;

    case MEM:
      output_address (GET_MODE (XEXP (operand, 0)), XEXP (operand, 0));
      return;

    default:
      /* No need to handle all strange variants, let output_addr_const
         do it for us.  */
      if (CONSTANT_P (operand))
        {
          output_addr_const (file, operand);
          return;
        }

      LOSE_AND_RETURN ("unexpected operand", x);
    }
}

const char *
ft32_load_immediate (rtx dst, int32_t i)
{
  char pattern[100];

  if ((-524288 <= i) && (i <= 524287))
    {
      sprintf (pattern, "ldk.l  %%0,%d", i);
      output_asm_insn (pattern, &dst);
    }
  else if ((-536870912 <= i) && (i <= 536870911))
    {
      ft32_load_immediate (dst, i >> 10);
      sprintf (pattern, "ldl.l  %%0,%%0,%d", i & 1023);
      output_asm_insn (pattern, &dst);
    }
  else
    {
      int rd;                   // rotate distance
      uint32_t u = i;
      for (rd = 1; rd < 32; rd++)
        {
          u = ((u >> 31) & 1) | (u << 1);
          if ((-524288 <= (int32_t) u) && ((int32_t) u <= 524287))
            {
              ft32_load_immediate (dst, (int32_t) u);
              sprintf (pattern, "ror.l  %%0,%%0,%d", rd);
              output_asm_insn (pattern, &dst);
              return "";
            }
        }
      ft32_load_immediate (dst, i >> 10);
      sprintf (pattern, "ldl.l  %%0,%%0,%d", i & 1023);
      output_asm_insn (pattern, &dst);
    }

  return "";
}

// x is a bit mask, for example:
//    00000000000000000000001111111110
// If x contains a single bit mask, return the bitfield spec.
// in the above case it returns ((9 << 5) | 1)
// Otherwise return -1.
//

#define NBITS(n)  ((1U << (n)) - 1U)

int
ft32_as_bitfield (unsigned int x)
{
  int lobit, hibit;

  if (x == 0)
    return -1;

  for (lobit = 0; lobit < 32; lobit++)
    if (x & (1 << lobit))
      break;
  for (hibit = 31; hibit >= 0; hibit--)
    if (x & (1 << hibit))
      break;

  int width = 1 + hibit - lobit;
  if (width > 16)
    return -1;

  if (x != (NBITS (width) << lobit))
    return -1;                  // not a clean bitfield

  return ((width & 15) << 5) | lobit;
}

/* Per-function machine data.  */
struct GTY (()) machine_function
{
  /* Number of bytes saved on the stack for callee saved registers.  */
  int callee_saved_reg_size;

  /* Number of bytes saved on the stack for local variables.  */
  int local_vars_size;

  /* The sum of 2 sizes: locals vars and padding byte for saving the
   * registers.  Used in expand_prologue () and expand_epilogue ().  */
  int size_for_adjusting_sp;
};

/* Zero initialization is OK for all current fields.  */

static struct machine_function *
ft32_init_machine_status (void)
{
  return ggc_cleared_alloc < machine_function > ();
}


/* The TARGET_OPTION_OVERRIDE worker.
   All this curently does is set init_machine_status.  */
static void
ft32_option_override (void)
{
  /* Set the per-function-data initializer.  */
  init_machine_status = ft32_init_machine_status;
}

/* Implement targetm.select_section.  */
static section *
ft32_select_section (tree decl, int reloc, unsigned HOST_WIDE_INT align)
{
  /* Variables and constants defined in the __ea address space
     go into a special section named "._ea".  */
  if (TREE_TYPE (decl) != error_mark_node
      && TYPE_ADDR_SPACE (TREE_TYPE (decl)) == ADDR_SPACE_PM)
    {
      /* We might get called with string constants, but get_named_section
         doesn't like them as they are not DECLs.  Also, we need to set
         flags in that case.  */
      if (!DECL_P (decl))
        return get_section ("._pm", SECTION_WRITE | SECTION_DEBUG, NULL);

      return get_named_section (decl, "._pm", reloc);
    }

  return default_elf_select_section (decl, reloc, align);
}

/* Compute the size of the local area and the size to be adjusted by the
 * prologue and epilogue.  */

static void
ft32_compute_frame (void)
{
  /* For aligning the local variables.  */
  int stack_alignment = STACK_BOUNDARY / BITS_PER_UNIT;
  int padding_locals;
  int regno;

  /* Padding needed for each element of the frame.  */
  cfun->machine->local_vars_size = get_frame_size ();

  /* Align to the stack alignment.  */
  padding_locals = cfun->machine->local_vars_size % stack_alignment;
  if (padding_locals)
    padding_locals = stack_alignment - padding_locals;

  cfun->machine->local_vars_size += padding_locals;

  cfun->machine->callee_saved_reg_size = 0;

  /* Save callee-saved registers.  */
  for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
    if (df_regs_ever_live_p (regno) && (!call_used_regs[regno]))
      cfun->machine->callee_saved_reg_size += 4;

  cfun->machine->size_for_adjusting_sp =
    0 // crtl->args.pretend_args_size
    + cfun->machine->local_vars_size
    + (ACCUMULATE_OUTGOING_ARGS ? crtl->outgoing_args_size : 0);
}

// Must use LINK/UNLINK when...
// the frame is bigger than 512 bytes so cannot just "SUB" from SP
// the function actually uses $fp

static int
must_link (void)
{
  int bigframe = (cfun->machine->size_for_adjusting_sp >= 512);
  return (bigframe || frame_pointer_needed || df_regs_ever_live_p (FT32_FP)
          || df_regs_ever_live_p (FT32_FP));
}

void
ft32_expand_prologue (void)
{
  int regno;
  rtx insn;

  ft32_compute_frame ();

  int args_to_push = crtl->args.pretend_args_size;
  if (args_to_push)
    {
      int i;

      insn = emit_insn (gen_movsi_pop ((gen_rtx_REG (Pmode, FT32_R29))));

      for (i = 0; i < (args_to_push / 4); i++)
	{
	  insn =
	    emit_insn (gen_movsi_push ((gen_rtx_REG (Pmode, FT32_R5 - i))));
	  RTX_FRAME_RELATED_P (insn) = 1;
	}

      insn = emit_insn (gen_movsi_push ((gen_rtx_REG (Pmode, FT32_R29))));
    }

  if (flag_stack_usage_info)
    current_function_static_stack_size = cfun->machine->size_for_adjusting_sp;

  if (!must_link () && (cfun->machine->callee_saved_reg_size == 4))
    {
      insn =
	emit_insn (gen_link
		   (gen_rtx_REG (Pmode, FT32_R13),
		    GEN_INT (-cfun->machine->size_for_adjusting_sp)));
      RTX_FRAME_RELATED_P (insn) = 1;
      return;
    }
  /* Save callee-saved registers.  */
  if (optimize_size)
    {
      for (regno = FIRST_PSEUDO_REGISTER; regno-- > 0;)
	{
	  if (!fixed_regs[regno] && !call_used_regs[regno]
	      && df_regs_ever_live_p (regno))
	    {
	      rtx preg = gen_rtx_REG (Pmode, regno);
	      emit_insn (gen_call_prolog (preg));
	      break;
	    }
	}
    }
  else
    {
      for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
	{
	  if (!fixed_regs[regno] && df_regs_ever_live_p (regno)
	      && !call_used_regs[regno])
	    {
	      insn = emit_insn (gen_movsi_push (gen_rtx_REG (Pmode, regno)));
	      RTX_FRAME_RELATED_P (insn) = 1;
	    }
	}
    }

  if (65536 <= cfun->machine->size_for_adjusting_sp)
    {
      error ("stack frame must be smaller than 64K");
      return;
    }
  if (must_link ())
    {
      insn =
	emit_insn (gen_link
		   (gen_rtx_REG (Pmode, FT32_FP),
		    GEN_INT (-cfun->machine->size_for_adjusting_sp)));
      RTX_FRAME_RELATED_P (insn) = 1;
    }
  else if (cfun->machine->size_for_adjusting_sp > 0)
    {
      int adj = cfun->machine->size_for_adjusting_sp;
      insn = emit_insn (gen_addsi3 (gen_rtx_REG (SImode, FT32_SP),
				    gen_rtx_REG (SImode, FT32_SP),
				    GEN_INT (-adj)));
      RTX_FRAME_RELATED_P (insn) = 1;
    }
}

void
ft32_expand_epilogue (void)
{
  int regno;
  int pretend = crtl->args.pretend_args_size;

  if (!must_link ()
      && (cfun->machine->size_for_adjusting_sp == 24)
      && (cfun->machine->callee_saved_reg_size == 0))
    {
      emit_jump_insn (gen_returner24 ());
      return;
    }

  // Set when the epilog code will also add 24 to $sp
  int epilog24 = (!must_link ()
                  && (cfun->machine->size_for_adjusting_sp == 24)
                  && optimize_size);

  if (must_link ())
    {
      emit_insn (gen_unlink ());
    }
  else if (!epilog24 && (cfun->machine->size_for_adjusting_sp > 0))
    {
      emit_insn (gen_addsi3 (gen_rtx_REG (SImode, FT32_SP),
                             gen_rtx_REG (SImode, FT32_SP),
                             GEN_INT (cfun->machine->size_for_adjusting_sp)));
    }

  if (cfun->machine->callee_saved_reg_size != 0)
    {
      for (regno = FIRST_PSEUDO_REGISTER; regno-- > 0;)
        {
          if (!fixed_regs[regno] && !call_used_regs[regno]
              && df_regs_ever_live_p (regno))
            {
              rtx preg = gen_rtx_REG (Pmode, regno);
              if (optimize_size && (pretend == 0))
                {
                  if (epilog24)
                    emit_insn (gen_jump_epilog24 (preg));
                  else
                    emit_insn (gen_jump_epilog (preg));
                  return;
                }
              emit_insn (gen_movsi_pop (preg));
            }
        }
    }

  if (pretend != 0)
    emit_jump_insn (gen_pretend_returner (GEN_INT (pretend)));
  else
    emit_jump_insn (gen_returner ());
}

#undef TARGET_FRAME_POINTER_REQUIRED
#define TARGET_FRAME_POINTER_REQUIRED ft32_frame_pointer_required
static bool
ft32_frame_pointer_required (void)
{
  return cfun->calls_alloca;
}

#undef  TARGET_CAN_ELIMINATE
#define TARGET_CAN_ELIMINATE ft32_can_eliminate

/* Return true if register FROM can be eliminated via register TO.  */

static bool
ft32_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to)
{
  return 1;
  return (to == FRAME_POINTER_REGNUM) || !ft32_frame_pointer_required ();
}

/* Implements the macro INITIAL_ELIMINATION_OFFSET, return the OFFSET.  */

int
ft32_initial_elimination_offset (int from, int to)
{
  ft32_compute_frame ();

  if (from == ARG_POINTER_REGNUM && to == FRAME_POINTER_REGNUM)
    {
      return cfun->machine->callee_saved_reg_size + 2 * UNITS_PER_WORD;
    }

  if (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
    {
      int arg_offset;
      arg_offset = must_link ()? 2 : 1;
      return ((cfun->machine->callee_saved_reg_size
               + arg_offset * UNITS_PER_WORD)
              + cfun->machine->size_for_adjusting_sp);
    }

  if ((from == FRAME_POINTER_REGNUM) && (to == STACK_POINTER_REGNUM))
    {
      return cfun->machine->size_for_adjusting_sp;
    }

  gcc_unreachable ();
}

/* Worker function for TARGET_SETUP_INCOMING_VARARGS.  */

static void
ft32_setup_incoming_varargs (cumulative_args_t cum_v,
			     enum machine_mode mode,
			     tree type ATTRIBUTE_UNUSED,
			     int *pretend_size, int no_rtl ATTRIBUTE_UNUSED)
{
  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
  int named_size =
    GET_MODE_SIZE (SImode) * (*cum - FT32_R0) + GET_MODE_SIZE (mode);

  if (named_size < 24)
    *pretend_size = 24 - named_size;
  else
    *pretend_size = 0;
}

/* Return the fixed registers used for condition codes.  */

static bool
ft32_fixed_condition_code_regs (unsigned int *p1, unsigned int *p2)
{
  *p1 = CC_REG;
  *p2 = INVALID_REGNUM;
  return true;
}

/* Return the next register to be used to hold a function argument or
   NULL_RTX if there's no more space.  */

static rtx
ft32_function_arg (cumulative_args_t cum_v, enum machine_mode mode,
                   const_tree type ATTRIBUTE_UNUSED,
                   bool named ATTRIBUTE_UNUSED)
{
  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);

  if (*cum < 8)
    return gen_rtx_REG (mode, *cum);
  else
    return NULL_RTX;
}

#define FT32_FUNCTION_ARG_SIZE(MODE, TYPE)      \
  ((MODE) != BLKmode ? GET_MODE_SIZE (MODE)     \
   : (unsigned) int_size_in_bytes (TYPE))

static void
ft32_function_arg_advance (cumulative_args_t cum_v, enum machine_mode mode,
                           const_tree type, bool named ATTRIBUTE_UNUSED)
{
  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);

  *cum = (*cum < FT32_R6
          ? *cum + ((3 + FT32_FUNCTION_ARG_SIZE (mode, type)) / 4) : *cum);
}

/* Return non-zero if the function argument described by TYPE is to be
   passed by reference.  */

static bool
ft32_pass_by_reference (cumulative_args_t cum ATTRIBUTE_UNUSED,
                        enum machine_mode mode, const_tree type,
                        bool named ATTRIBUTE_UNUSED)
{
  unsigned HOST_WIDE_INT size;

  if (type)
    {
      if (AGGREGATE_TYPE_P (type))
        return true;
      size = int_size_in_bytes (type);
    }
  else
    size = GET_MODE_SIZE (mode);

  return size > 4 * 6;
}

/* Some function arguments will only partially fit in the registers
   that hold arguments.  Given a new arg, return the number of bytes
   that fit in argument passing registers.  */

static int
ft32_arg_partial_bytes (cumulative_args_t cum_v,
                        enum machine_mode mode, tree type, bool named)
{
  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
  int bytes_left, size;

  if (*cum >= 8)
    return 0;

  if (ft32_pass_by_reference (cum_v, mode, type, named))
    size = 4;
  else if (type)
    {
      if (AGGREGATE_TYPE_P (type))
        return 0;
      size = int_size_in_bytes (type);
    }
  else
    size = GET_MODE_SIZE (mode);

  bytes_left = (4 * 6) - ((*cum - 2) * 4);

  if (size > bytes_left)
    return bytes_left;
  else
    return 0;
}

/* Used by constraints.md to distinguish between GENERIC and PM
   memory addresses.  */

int
ft32_is_mem_pm (rtx o)
{
  return (MEM_P (o)
          && !ADDR_SPACE_GENERIC_P (MEM_ADDR_SPACE (o)));
}

/* The Global `targetm' Variable.  */

/* Initialize the GCC target structure.  */

#undef  TARGET_PROMOTE_PROTOTYPES
#define TARGET_PROMOTE_PROTOTYPES       hook_bool_const_tree_true

#undef  TARGET_RETURN_IN_MEMORY
#define TARGET_RETURN_IN_MEMORY         ft32_return_in_memory
#undef  TARGET_MUST_PASS_IN_STACK
#define TARGET_MUST_PASS_IN_STACK       must_pass_in_stack_var_size
#undef  TARGET_PASS_BY_REFERENCE
#define TARGET_PASS_BY_REFERENCE        ft32_pass_by_reference
#undef  TARGET_ARG_PARTIAL_BYTES
#define TARGET_ARG_PARTIAL_BYTES        ft32_arg_partial_bytes
#undef  TARGET_FUNCTION_ARG
#define TARGET_FUNCTION_ARG             ft32_function_arg
#undef  TARGET_FUNCTION_ARG_ADVANCE
#define TARGET_FUNCTION_ARG_ADVANCE     ft32_function_arg_advance


#undef  TARGET_SETUP_INCOMING_VARARGS
#define TARGET_SETUP_INCOMING_VARARGS   ft32_setup_incoming_varargs

#undef  TARGET_FIXED_CONDITION_CODE_REGS
#define TARGET_FIXED_CONDITION_CODE_REGS ft32_fixed_condition_code_regs

/* Define this to return an RTX representing the place where a
   function returns or receives a value of data type RET_TYPE, a tree
   node representing a data type.  */
#undef TARGET_FUNCTION_VALUE
#define TARGET_FUNCTION_VALUE ft32_function_value
#undef TARGET_LIBCALL_VALUE
#define TARGET_LIBCALL_VALUE ft32_libcall_value
#undef TARGET_FUNCTION_VALUE_REGNO_P
#define TARGET_FUNCTION_VALUE_REGNO_P ft32_function_value_regno_p

#undef TARGET_OPTION_OVERRIDE
#define TARGET_OPTION_OVERRIDE ft32_option_override

#undef TARGET_ASM_SELECT_SECTION
#define TARGET_ASM_SELECT_SECTION  ft32_select_section

#undef TARGET_VALID_POINTER_MODE
#define TARGET_VALID_POINTER_MODE ft32_valid_pointer_mode
static bool
ft32_valid_pointer_mode (enum machine_mode mode)
{
  if (mode == SImode)
    return 1;
  return 0;
}

#undef TARGET_ADDR_SPACE_POINTER_MODE
#define TARGET_ADDR_SPACE_POINTER_MODE ft32_addr_space_pointer_mode
static enum machine_mode
ft32_addr_space_pointer_mode (addr_space_t addrspace ATTRIBUTE_UNUSED)
{
  return Pmode;
}

#undef TARGET_ADDR_SPACE_ADDRESS_MODE
#define TARGET_ADDR_SPACE_ADDRESS_MODE ft32_addr_space_address_mode
static enum machine_mode
ft32_addr_space_address_mode (addr_space_t addrspace ATTRIBUTE_UNUSED)
{
  return Pmode;
}

#undef TARGET_ADDR_SPACE_SUBSET_P
#define TARGET_ADDR_SPACE_SUBSET_P ft32_addr_space_subset_p
static bool
ft32_addr_space_subset_p (addr_space_t subset ATTRIBUTE_UNUSED,
                          addr_space_t superset ATTRIBUTE_UNUSED)
{
  return false;
}

#undef TARGET_CASE_VALUES_THRESHOLD
#define TARGET_CASE_VALUES_THRESHOLD ft32_target_case_values_threshold

static unsigned int
ft32_target_case_values_threshold (void)
{
  return 4;
}

#undef TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P
#define TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P \
  ft32_addr_space_legitimate_address_p


// Enabling LRA gives the infamous
//    internal compiler error: Max. number of generated reload insns per insn is achieved (90)
// errors e.g. when compiling sieve.c

static bool
ft32_lra_p (void)
{
  return ft32_lra_flag;
}

#undef TARGET_LRA_P
#define TARGET_LRA_P ft32_lra_p

static bool
reg_ok_for_base_p (rtx r, bool strict)
{
  int NUM = REGNO (r);
  if (strict)
    return (HARD_REGNO_OK_FOR_BASE_P (NUM)
            || HARD_REGNO_OK_FOR_BASE_P (reg_renumber[(NUM)]));
  else
    return ((NUM) >= FIRST_PSEUDO_REGISTER || HARD_REGNO_OK_FOR_BASE_P (NUM));
}

static bool
ft32_addr_space_legitimate_address_p (enum machine_mode mode, rtx x,
                                      bool strict,
                                      addr_space_t as ATTRIBUTE_UNUSED)
{
  if (mode != BLKmode)
    {
      if (GET_CODE (x) == PLUS)
        {
          rtx op1, op2;
          op1 = XEXP (x, 0);
          op2 = XEXP (x, 1);
          if (GET_CODE (op1) == REG
              && CONST_INT_P (op2)
              && INTVAL (op2) >= -128
              && INTVAL (op2) < 128 && reg_ok_for_base_p (op1, strict))
            goto yes;
          if (GET_CODE (op1) == SYMBOL_REF && CONST_INT_P (op2))
            goto yes;
        }
      if (REG_P (x) && reg_ok_for_base_p (x, strict))
        goto yes;
      if (GET_CODE (x) == SYMBOL_REF
          || GET_CODE (x) == LABEL_REF || CONST_INT_P (x))
        goto yes;
    }
  else
    {
      if (REG_P (x) && reg_ok_for_base_p (x, strict))
        goto yes;
    }

  return 0;
yes:
  return 1;
}

#undef TARGET_ENCODE_SECTION_INFO
#define TARGET_ENCODE_SECTION_INFO  ft32_elf_encode_section_info

void
ft32_elf_encode_section_info (tree decl, rtx rtl, int first)
{
  enum tree_code code;
  rtx symbol;

  /* Careful not to prod global register variables.  */
  if (!MEM_P (rtl))
    return;
  symbol = XEXP (rtl, 0);
  if (GET_CODE (symbol) != SYMBOL_REF)
    return;

  default_encode_section_info (decl, rtl, first);

  code = TREE_CODE (decl);
  switch (TREE_CODE_CLASS (code))
    {
    case tcc_declaration:
      {
	tree type = TREE_TYPE (decl);
	int is_flash = (type && TYPE_P (type)
			&& !ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (type)));
	if ((code == VAR_DECL) && !is_flash)
	  SYMBOL_REF_FLAGS (symbol) |= 0x1000;
      }
      break;

    case tcc_constant:
    case tcc_exceptional:
      if (code == STRING_CST)
	SYMBOL_REF_FLAGS (symbol) |= 0x1000;
      break;

    default:
      break;
    }
}

struct gcc_target targetm = TARGET_INITIALIZER;

#include "gt-ft32.h"
