/* brig-cvt-inst-handler.cc -- brig cvt (convert) instruction handling
   Copyright (C) 2016-2017 Free Software Foundation, Inc.
   Contributed by Pekka Jaaskelainen <pekka.jaaskelainen@parmance.com>
   for General Processor Tech.

   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 <sstream>

#include "brig-code-entry-handler.h"

#include "gimple-expr.h"
#include "errors.h"
#include "convert.h"
#include "tree-pretty-print.h"
#include "print-tree.h"
#include "diagnostic-core.h"
#include "brig-util.h"

const BrigAluModifier8_t *
brig_cvt_inst_handler::modifier (const BrigBase *base) const
{
  const BrigInstCvt *inst = (const BrigInstCvt *) base;
  return &inst->modifier;
}

const BrigRound8_t *
brig_cvt_inst_handler::round (const BrigBase *base) const
{
  const BrigInstCvt *inst = (const BrigInstCvt *) base;
  return &inst->round;
}

size_t
brig_cvt_inst_handler::generate (const BrigBase *base)
{
  /* In cvt instructions there can be at least four data types involved:

     - the input register type
     - the output register type
     - the conversion source type
     - the conversion destination type
  */

  const BrigInstBase *brig_inst
    = (const BrigInstBase *) &((const BrigInstBasic *) base)->base;
  const BrigInstCvt *cvt_inst = (const BrigInstCvt *) base;

  const BrigAluModifier8_t *inst_modifier = modifier (base);
  const bool FTZ = inst_modifier != NULL && (*inst_modifier) & BRIG_ALU_FTZ;

  /* The conversion source type.  */
  tree src_type = get_tree_expr_type_for_hsa_type (cvt_inst->sourceType);

  bool src_is_fp16 = cvt_inst->sourceType == BRIG_TYPE_F16;

  /* The conversion destination type.  */
  tree dest_type = gccbrig_tree_type_for_hsa_type (brig_inst->type);

  bool dest_is_fp16 = brig_inst->type == BRIG_TYPE_F16;

  if (!dest_type || !src_type)
    {
      gcc_unreachable ();
      return base->byteCount;
    }

  tree_stl_vec operands = build_operands (*brig_inst);
  tree &input = operands.at (1);
  tree &output = operands.at (0);

  size_t conv_src_size = int_size_in_bytes (src_type);
  size_t conv_dst_size = int_size_in_bytes (dest_type);
  size_t src_reg_size = int_size_in_bytes (TREE_TYPE (input));

  /* The input register can be of different type&size than the
     conversion input size.  First cast the input to the conversion
     input type.  These casts are always bitcasts which can be
     expressed as casts between different unsigned integers.  */
  if (src_reg_size != conv_src_size)
    {
      tree unsigned_int_type = NULL_TREE;
      if (INTEGRAL_TYPE_P (src_type))
	unsigned_int_type = unsigned_type_for (src_type);
      else /* Find a matching size int type for the REAL type.  */
	{
	  if (conv_src_size == 2)
	    unsigned_int_type = gccbrig_tree_type_for_hsa_type (BRIG_TYPE_U16);
	  else if (conv_src_size == 4)
	    unsigned_int_type = gccbrig_tree_type_for_hsa_type (BRIG_TYPE_U32);
	  else if (conv_src_size == 8)
	    unsigned_int_type = gccbrig_tree_type_for_hsa_type (BRIG_TYPE_U64);
	  else
	    gcc_unreachable ();
	}
      input = convert_to_integer (unsigned_int_type, input);
    }

  if (src_is_fp16)
    input = build_h2f_conversion (input);

  /* Flush the float operand to zero if indicated with 'ftz'.  */
  if (FTZ && SCALAR_FLOAT_TYPE_P (src_type))
    {
      tree casted_input = build_reinterpret_cast (src_type, input);
      input = flush_to_zero (src_is_fp16) (*this, casted_input);
    }

  tree conversion_result = NULL_TREE;
  if (brig_inst->type == BRIG_TYPE_B1)
    {
      /* When the destination is b1, cvt does a 'ztest' operation which is
	 defined as a != 0 for integers and similarly (!= 0.0f) for floats.  */
      if (INTEGRAL_TYPE_P (src_type))
	{
	  /* Generate an integer not equal operation.  */
	  conversion_result = build2 (NE_EXPR, TREE_TYPE (input), input,
				      build_int_cst (TREE_TYPE (input), 0));
	}
      else
	{
	  /* For REAL source types, ztest returns 1 if the value is not +- 0.0f.
	     We can perform this check with an integer comparison after
	     masking away the sign bit from a correct position.  This is safer
	     than using absf because of exceptions in case of a NaN
	     input (NaN exceptions are not generated with cvt).  */
	  tree unsigned_int_type = NULL_TREE;
	  /* Bit battern with all but the upper bit 1.  */
	  tree and_mask = NULL_TREE;
	  if (conv_src_size == 2)
	    {
	      unsigned_int_type = gccbrig_tree_type_for_hsa_type (BRIG_TYPE_U16);
	      and_mask = build_int_cst (unsigned_int_type, 0x7FFF);
	    }
	  else if (conv_src_size == 4)
	    {
	      unsigned_int_type = gccbrig_tree_type_for_hsa_type (BRIG_TYPE_U32);
	      and_mask = build_int_cst (unsigned_int_type, 0x7FFFFFFF);
	    }
	  else if (conv_src_size == 8)
	    {
	      unsigned_int_type = gccbrig_tree_type_for_hsa_type (BRIG_TYPE_U64);
	      and_mask = build_int_cst (unsigned_int_type, 0x7FFFFFFFFFFFFFFF);
	    }
	  else
	    gcc_unreachable ();
	  tree casted_input = build_reinterpret_cast (unsigned_int_type, input);
	  tree masked_input
	    = build2 (BIT_AND_EXPR, unsigned_int_type, casted_input, and_mask);
	  conversion_result
	    = build2 (NE_EXPR, TREE_TYPE (masked_input), masked_input,
		      build_int_cst (unsigned_int_type, 0));
	}
      /* The result from the comparison is a boolean, convert it to such.  */
      conversion_result
	= convert_to_integer (gccbrig_tree_type_for_hsa_type (BRIG_TYPE_B1),
			      conversion_result);
    }
  else if (dest_is_fp16)
    {
      tree casted_input = build_reinterpret_cast (src_type, input);
      conversion_result
	= convert_to_real (brig_to_generic::s_fp32_type, casted_input);
      if (FTZ)
	conversion_result = flush_to_zero (true) (*this, conversion_result);
      conversion_result = build_f2h_conversion (conversion_result);
    }
  else if (SCALAR_FLOAT_TYPE_P (dest_type))
    {
      tree casted_input = build_reinterpret_cast (src_type, input);
      conversion_result = convert_to_real (dest_type, casted_input);
    }
  else if (INTEGRAL_TYPE_P (dest_type) && INTEGRAL_TYPE_P (src_type))
    {
      conversion_result = extend_int (input, dest_type, src_type);
    }
  else if (INTEGRAL_TYPE_P (dest_type) && SCALAR_FLOAT_TYPE_P (src_type))
    {

      if (cvt_inst->round == BRIG_ROUND_INTEGER_ZERO_SAT)
	{

	  /* Use builtins for the saturating conversions.  */
#undef DEF_HSAIL_SAT_BUILTIN
#undef DEF_HSAIL_BUILTIN
#undef DEF_HSAIL_ATOMIC_BUILTIN
#undef DEF_HSAIL_INTR_BUILTIN
#undef DEF_HSAIL_CVT_ZEROI_SAT_BUILTIN

	  tree builtin = NULL_TREE;
	  BrigType16_t src_arith_type
	    = src_is_fp16
	    ? (BrigType16_t) BRIG_TYPE_F32 : cvt_inst->sourceType;
#define DEF_HSAIL_CVT_ZEROI_SAT_BUILTIN(ENUM, HSAIL_DST_TYPE, HSAIL_SRC_TYPE, \
					NAME, TYPE, ATTRS)		\
	  if (brig_inst->type == HSAIL_DST_TYPE				\
	      && src_arith_type == HSAIL_SRC_TYPE)		\
	    builtin = builtin_decl_explicit (ENUM);			\
	  else
#include "brig-builtins.def"
	    gcc_unreachable ();

	  tree casted_input = build_reinterpret_cast (src_type, input);
	  conversion_result
	    = call_builtin (builtin, 1, dest_type, src_type, casted_input);
	}
      else
	{
	  tree casted_input = build_reinterpret_cast (src_type, input);

	  /* Perform the int to float conversion.  */
	  conversion_result = convert_to_integer (dest_type, casted_input);
	}
      /* The converted result is finally extended to the target register
	 width, using the same sign as the destination.  */
      conversion_result
	= convert_to_integer (TREE_TYPE (output), conversion_result);
    }
  else
    {
      /* Just use CONVERT_EXPR and hope for the best.  */
      tree casted_input = build_reinterpret_cast (dest_type, input);
      conversion_result = build1 (CONVERT_EXPR, dest_type, casted_input);
    }

  size_t dst_reg_size = int_size_in_bytes (TREE_TYPE (output));

  tree assign = NULL_TREE;
  /* The output register can be of different type&size than the
     conversion output size.  Cast it to the register variable type.  */
  if (dst_reg_size > conv_dst_size)
    {
      tree casted_output
	= build1 (CONVERT_EXPR, TREE_TYPE (output), conversion_result);
      assign = build2 (MODIFY_EXPR, TREE_TYPE (output), output, casted_output);
    }
  else
    {
      tree casted_output
	= build_reinterpret_cast (TREE_TYPE (output), conversion_result);
      assign = build2 (MODIFY_EXPR, TREE_TYPE (output), output, casted_output);
    }
  m_parent.m_cf->append_statement (assign);

  return base->byteCount;
}
