;; Predicate definitions for TI PRU.
;; Copyright (C) 2014-2021 Free Software Foundation, Inc.
;; Contributed by Dimitar Dimitrov <dimitar@dinux.eu>
;;
;; 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/>.

(define_predicate "const_1_operand"
  (and (match_code "const_int")
       (match_test "INTVAL (op) == 1")))

; Note: Always pass a valid mode!
(define_predicate "const_ubyte_operand"
  (match_code "const_int")
{
  gcc_assert (mode != VOIDmode);
  return IN_RANGE (INTVAL (op) & GET_MODE_MASK (mode), 0, 0xff);
})

(define_predicate "const_uhword_operand"
  (match_code "const_int")
{
  gcc_assert (mode != VOIDmode);
  return IN_RANGE (INTVAL (op) & GET_MODE_MASK (mode), 0, 0xffff);
})

; TRUE for comparisons we support.
(define_predicate "pru_cmp_operator"
  (match_code "eq,ne,leu,ltu,geu,gtu"))

; TRUE for signed comparisons that need special handling for PRU.
(define_predicate "pru_signed_cmp_operator"
  (match_code "ge,gt,le,lt"))

;; FP Comparisons handled by pru_expand_pru_compare.
(define_predicate "pru_fp_comparison_operator"
  (match_code "eq,ne,lt,gt,le,ge"))

;; Return true if OP is a constant that contains only one 1 in its
;; binary representation.
(define_predicate "single_one_operand"
  (and (match_code "const_int")
       (match_test "exact_log2 (INTVAL (op) & GET_MODE_MASK (mode)) >= 0")))

;; Return true if OP is a constant that contains only one 0 in its
;; binary representation.
(define_predicate "single_zero_operand"
  (and (match_code "const_int")
       (match_test "exact_log2 (~INTVAL (op) & GET_MODE_MASK (mode)) >= 0")))

(define_predicate "pru_muldst_operand"
  (match_code "subreg,reg")
{
  if (register_operand (op, mode))
    {
      int regno;

      if (REG_P (op))
	regno = REGNO (op);
      else if (GET_CODE (op) == SUBREG && REG_P (SUBREG_REG (op)))
	regno = REGNO (SUBREG_REG (op));
      else
	return 0;

      return REGNO_REG_CLASS (regno) == MULDST_REGS
	     || regno >= FIRST_PSEUDO_REGISTER;
    }
  return 0;
})

(define_predicate "pru_mulsrc0_operand"
  (match_code "subreg,reg")
{
  if (register_operand (op, mode))
    {
      int regno;

      if (REG_P (op))
	regno = REGNO (op);
      else if (GET_CODE (op) == SUBREG && REG_P (SUBREG_REG (op)))
	regno = REGNO (SUBREG_REG (op));
      else
	return 0;

      return REGNO_REG_CLASS (regno) == MULSRC0_REGNUM
	     || regno >= FIRST_PSEUDO_REGISTER;
    }
  return 0;
})

(define_predicate "pru_mulsrc1_operand"
  (match_code "subreg,reg")
{
  if (register_operand (op, mode))
    {
      int regno;

      if (REG_P (op))
	regno = REGNO (op);
      else if (GET_CODE (op) == SUBREG && REG_P (SUBREG_REG (op)))
	regno = REGNO (SUBREG_REG (op));
      else
	return 0;

      return REGNO_REG_CLASS (regno) == MULSRC1_REGNUM
	     || regno >= FIRST_PSEUDO_REGISTER;
    }
  return 0;
})

(define_predicate "reg_or_const_int_operand"
  (ior (match_operand 0 "const_int_operand")
       (match_operand 0 "register_operand")))

(define_predicate "reg_or_ubyte_operand"
  (ior (match_operand 0 "const_ubyte_operand")
       (match_operand 0 "register_operand")))

(define_predicate "reg_or_const_1_operand"
  (ior (match_operand 0 "const_1_operand")
       (match_operand 0 "register_operand")))

(define_predicate "const_shift_operand"
  (and (match_code "const_int")
       (match_test "SHIFT_INT (INTVAL (op))")))

(define_predicate "shift_operand"
  (ior (match_operand 0 "const_shift_operand")
       (match_operand 0 "register_operand")))

(define_predicate "ctable_addr_operand"
  (and (match_code "const_int")
       (match_test "pru_get_ctable_base_index (INTVAL (op)) >= 0")))

(define_predicate "ctable_base_operand"
  (and (match_code "const_int")
       (match_test "pru_get_ctable_exact_base_index (INTVAL (op)) >= 0")))

;; Ideally we should enforce a restriction to all text labels to fit in
;; 16bits, as required by the PRU ISA.  But for the time being we'll rely on
;; binutils to catch text segment overflows.
(define_predicate "call_operand"
  (ior (match_operand 0 "immediate_operand")
       (match_operand 0 "register_operand")))

;; Return true if OP is a text segment reference.
;; This is needed for program memory address expressions.  Borrowed from AVR.
(define_predicate "text_segment_operand"
  (match_code "code_label,label_ref,symbol_ref,plus,minus")
{
  poly_int64 offset;
  rtx base = strip_offset (op, &offset);

  switch (GET_CODE (base))
    {
    case CODE_LABEL:
      /* Why AVR lists this as a valid option?  Let's catch it.  */
      gcc_unreachable ();
      return false;
    case LABEL_REF:
      return true;
    case SYMBOL_REF:
      return SYMBOL_REF_FUNCTION_P (base);
    case PLUS:
    case MINUS:
      /* Handle constructs like (&&label1 - &&label2).  See pr70460.c.  */
      return text_segment_operand (XEXP (op, 0), VOIDmode);
    default:
      return false;
    }
})

;; Return true if OP is a load multiple operation.  It is known to be a
;; PARALLEL and the first section will be tested.

(define_special_predicate "load_multiple_operation"
  (match_code "parallel")
{
  machine_mode elt_mode;
  int count = XVECLEN (op, 0);
  unsigned int dest_regno;
  rtx src_addr, base_reg;
  poly_int64 base_offs;
  int i;

  /* Perform a quick check so we don't blow up below.  */
  if (GET_CODE (XVECEXP (op, 0, 0)) != SET
      || GET_CODE (SET_DEST (XVECEXP (op, 0, 0))) != REG
      || GET_CODE (SET_SRC (XVECEXP (op, 0, 0))) != MEM)
    return false;

  dest_regno = REGNO (SET_DEST (XVECEXP (op, 0, 0)));
  src_addr = XEXP (SET_SRC (XVECEXP (op, 0, 0)), 0);
  elt_mode = GET_MODE (SET_DEST (XVECEXP (op, 0, 0)));

  base_reg = strip_offset (src_addr, &base_offs);
  if (GET_CODE (base_reg) != REG)
    return false;

  for (i = 1; i < count; i++)
    {
      rtx elt_reg;
      poly_int64 elt_offs;
      rtx elt = XVECEXP (op, 0, i);

      if (GET_CODE (elt) != SET
	  || GET_CODE (SET_DEST (elt)) != REG
	  || GET_MODE (SET_DEST (elt)) != elt_mode
	  || REGNO (SET_DEST (elt)) != dest_regno + i * GET_MODE_SIZE (elt_mode)
	  || GET_CODE (SET_SRC (elt)) != MEM
	  || GET_MODE (SET_SRC (elt)) != elt_mode)
	return false;

      elt_reg = strip_offset (XEXP (SET_SRC (elt), 0), &elt_offs);

      if (GET_CODE (elt_reg) != REG
	  || ! rtx_equal_p (elt_reg, base_reg)
	  || elt_offs != base_offs + i * GET_MODE_SIZE (elt_mode))
	return false;
    }

  return true;
})

;; Return true if OP is a store multiple operation.  It is known to be a
;; PARALLEL and the first section will be tested.

(define_special_predicate "store_multiple_operation"
  (match_code "parallel")
{
  machine_mode elt_mode;
  int count = XVECLEN (op, 0);
  unsigned int src_regno;
  rtx dest_addr, base_reg;
  poly_int64 base_offs;
  int i;

  /* Perform a quick check so we don't blow up below.  */
  if (GET_CODE (XVECEXP (op, 0, 0)) != SET
      || GET_CODE (SET_DEST (XVECEXP (op, 0, 0))) != MEM
      || GET_CODE (SET_SRC (XVECEXP (op, 0, 0))) != REG)
    return false;

  src_regno = REGNO (SET_SRC (XVECEXP (op, 0, 0)));
  dest_addr = XEXP (SET_DEST (XVECEXP (op, 0, 0)), 0);
  elt_mode = GET_MODE (SET_SRC (XVECEXP (op, 0, 0)));

  base_reg = strip_offset (dest_addr, &base_offs);
  if (GET_CODE (base_reg) != REG)
    return false;

  for (i = 1; i < count; i++)
    {
      rtx elt_reg;
      poly_int64 elt_offs;
      rtx elt = XVECEXP (op, 0, i);

      if (GET_CODE (elt) != SET
	  || GET_CODE (SET_SRC (elt)) != REG
	  || GET_MODE (SET_SRC (elt)) != elt_mode
	  || REGNO (SET_SRC (elt)) != src_regno + i * GET_MODE_SIZE (elt_mode)
	  || GET_CODE (SET_DEST (elt)) != MEM
	  || GET_MODE (SET_DEST (elt)) != elt_mode)
	return false;

      elt_reg = strip_offset (XEXP (SET_DEST (elt), 0), &elt_offs);

      if (GET_CODE (elt_reg) != REG
	  || ! rtx_equal_p (elt_reg, base_reg)
	  || elt_offs != base_offs + i * GET_MODE_SIZE (elt_mode))
	return false;
    }
  return true;
})
