/* $Id: sparc-rc-cc.c,v 1.2 2009/08/28 01:41:45 fredette Exp $ */

/* ic/sparc/sparc-rc-cc.c - SPARC recode condition code support: */

/*
 * Copyright (c) 2008 Matt Fredette
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Matt Fredette.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#if TME_SPARC_RECODE_SIZE(ic) == TME_RECODE_SIZE_32

_TME_RCSID("$Id: sparc-rc-cc.c,v 1.2 2009/08/28 01:41:45 fredette Exp $");

/* macros: */

/* this converts sparc64 icc flags into the current architecture's icc
   flags: */
#define TME_SPARC_ICC(ccr_icc)					\
  ((ccr_icc)							\
   * (((TME_SPARC_VERSION(ic) < 9)				\
       * (TME_SPARC32_PSR_ICC					\
	  / TME_SPARC64_CCR_ICC))				\
      + (TME_SPARC_VERSION(ic) >= 9)))

#define TME_SPARC_RECODE_CCR_NEED(insn_class, insn_size, size)	\
  ((((insn_class == TME_RECODE_INSN_CLASS_ADDITIVE)		\
     * ((TME_SPARC64_CCR_ICC_C					\
	 * (TME_RECODE_FLAG_NEED(insn_class, insn_size,		\
				 TME_RECODE_COND_C, size)	\
	    != 0))						\
	+ (TME_SPARC64_CCR_ICC_V				\
	   * (TME_RECODE_FLAG_NEED(insn_class, insn_size,	\
				   TME_RECODE_COND_V, size)	\
	      != 0))))						\
    + (TME_SPARC64_CCR_ICC_Z					\
       * (TME_RECODE_FLAG_NEED(insn_class, insn_size,		\
			       TME_RECODE_COND_Z, size)		\
	  != 0))						\
    + (TME_SPARC64_CCR_ICC_N					\
       * (TME_RECODE_FLAG_NEED(insn_class, insn_size,		\
			       TME_RECODE_COND_N, size)		\
	  != 0)))						\
   * ((TME_SPARC64_CCR_XCC					\
       / TME_SPARC64_CCR_ICC)					\
      * (size > TME_RECODE_SIZE_32)))

/* this macro gives the %icc and %xcc N and Z flags: */
#define TME_SPARC_RECODE_CCR_N_Z(dst, ccr_need)			\
  (0								\
   + ((((tme_uint32_t) (dst)) == 0)				\
      * ((ccr_need) & TME_SPARC64_CCR_ICC_Z))			\
   + ((((tme_int32_t) (dst)) < 0)				\
      * ((ccr_need) & TME_SPARC64_CCR_ICC_N))			\
   + ((((tme_sparc_ireg_t) (dst)) == 0)				\
      * ((ccr_need) & TME_SPARC64_CCR_XCC_Z))			\
   + ((((dst) & TME_SPARC_IREG_MSBIT) != 0)			\
      * ((ccr_need) & TME_SPARC64_CCR_XCC_N)))

/* this macro updates %icc and %xcc: */
#define TME_SPARC_RECODE_CCR_UPDATE(ic, ccr, ccr_need)		\
  do {								\
    if (TME_SPARC_VERSION((struct tme_sparc *) (ic)) >= 9) {	\
      ((struct tme_sparc *) (ic))->tme_sparc64_ireg_ccr		\
	= ((((struct tme_sparc *) (ic))->tme_sparc64_ireg_ccr	\
	    & ~ccr_need)					\
	   | ccr);						\
    }								\
    else {							\
      ((struct tme_sparc *) (ic))->tme_sparc32_ireg_psr		\
	= ((((struct tme_sparc *) (ic))->tme_sparc32_ireg_psr	\
	    & ~TME_SPARC_ICC(ccr_need))				\
	   | TME_SPARC_ICC(ccr));				\
    }								\
  } while (/* CONSTCOND */ 0)

/* rename various things by the architecture size: */
#define _tme_sparc_flags_additive _TME_SPARC_RECODE_SIZE(_tme_sparc,_flags_additive)
#define _tme_sparc_flags_logical _TME_SPARC_RECODE_SIZE(_tme_sparc,_flags_logical)
#define _tme_sparc_flags_func_logical _TME_SPARC_RECODE_SIZE(_tme_sparc,_flags_func_logical)
#define _tme_sparc_flags_func_add _TME_SPARC_RECODE_SIZE(_tme_sparc,_flags_func_add)
#define _tme_sparc_flags_func_sub _TME_SPARC_RECODE_SIZE(_tme_sparc,_flags_func_sub)
#define _tme_sparc_recode_conds_func_icc _TME_SPARC_RECODE_SIZE(_tme_sparc,_recode_conds_func_icc)
#define _tme_sparc_recode_cc_init _TME_SPARC_RECODE_SIZE(_tme_sparc,_recode_cc_init)

/* the sparc64 recode xcc conditions function: */
static int
_tme_sparc64_recode_conds_func_xcc(tme_recode_uguest_t flags, tme_uint32_t cond)
{
  return (_tme_sparc_conds_icc
	  [TME_FIELD_MASK_EXTRACTU(flags, TME_SPARC64_CCR_XCC)]
	  & (1 << cond));
}

#endif /* TME_SPARC_RECODE_SIZE(ic) == TME_RECODE_SIZE_32 */

/* the additive instruction flags: */
static const struct tme_recode_flag _tme_sparc_flags_additive[] = {
#if TME_SPARC_VERSION(ic) >= 9
  {
    TME_RECODE_COND_N,
    TME_RECODE_SIZE_64,
    TME_SPARC64_CCR_XCC_N
  },
  {
    TME_RECODE_COND_Z,
    TME_RECODE_SIZE_64,
    TME_SPARC64_CCR_XCC_Z
  },
  {
    TME_RECODE_COND_V,
    TME_RECODE_SIZE_64,
    TME_SPARC64_CCR_XCC_V
  },
  {
    TME_RECODE_COND_C,
    TME_RECODE_SIZE_64,
    TME_SPARC64_CCR_XCC_C
  },
#endif /* TME_SPARC_VERSION(ic) >= 9 */
  { 
    TME_RECODE_COND_N,
    TME_RECODE_SIZE_32,
    TME_SPARC_ICC(TME_SPARC64_CCR_ICC_N)
  },
  { 
    TME_RECODE_COND_Z,
    TME_RECODE_SIZE_32,
    TME_SPARC_ICC(TME_SPARC64_CCR_ICC_Z)
  },
  { 
    TME_RECODE_COND_V,
    TME_RECODE_SIZE_32,
    TME_SPARC_ICC(TME_SPARC64_CCR_ICC_V)
  },
  { 
    TME_RECODE_COND_C,
    TME_RECODE_SIZE_32,
    TME_SPARC_ICC(TME_SPARC64_CCR_ICC_C)
  },
  {
    0,
    0,
    0
  }
};

/* the logical instruction flags: */
static const struct tme_recode_flag _tme_sparc_flags_logical[] = {
#if TME_SPARC_VERSION(ic) >= 9
  {
    TME_RECODE_COND_N,
    TME_RECODE_SIZE_64,
    TME_SPARC64_CCR_XCC_N
  },
  {
    TME_RECODE_COND_Z,
    TME_RECODE_SIZE_64,
    TME_SPARC64_CCR_XCC_Z
  },
  {
    TME_RECODE_COND_FALSE,
    0,
    TME_SPARC64_CCR_XCC_V
  },
  {
    TME_RECODE_COND_FALSE,
    0,
    TME_SPARC64_CCR_XCC_C
  },
#endif /* TME_SPARC_VERSION(ic) >= 9 */
  { 
    TME_RECODE_COND_N,
    TME_RECODE_SIZE_32,
    TME_SPARC_ICC(TME_SPARC64_CCR_ICC_N)
  },
  { 
    TME_RECODE_COND_Z,
    TME_RECODE_SIZE_32,
    TME_SPARC_ICC(TME_SPARC64_CCR_ICC_Z)
  },
  { 
    TME_RECODE_COND_FALSE,
    0,
    TME_SPARC_ICC(TME_SPARC64_CCR_ICC_V)
  },
  { 
    TME_RECODE_COND_FALSE,
    0,
    TME_SPARC_ICC(TME_SPARC64_CCR_ICC_C)
  },
  {
    0,
    0,
    0
  }
};

/* the logical instruction flags function: */
static void
_tme_sparc_flags_func_logical(struct tme_ic *ic,
			      tme_recode_uguest_t src1,
			      tme_recode_uguest_t src2,
			      tme_recode_uguest_t dst)
{
  unsigned int ccr_need;

  ccr_need
    = (TME_SPARC_RECODE_CCR_NEED(TME_RECODE_INSN_CLASS_LOGICAL,
				 TME_SPARC_RECODE_SIZE(ic),
				 TME_RECODE_SIZE_32)
       | TME_SPARC_RECODE_CCR_NEED(TME_RECODE_INSN_CLASS_LOGICAL,
				   TME_SPARC_RECODE_SIZE(ic),
				   TME_SPARC_RECODE_SIZE(ic)));

  TME_SPARC_RECODE_CCR_UPDATE(ic,
			      TME_SPARC_RECODE_CCR_N_Z(dst, ccr_need),
			      ccr_need);
}

/* the add instruction flags function: */
static void
_tme_sparc_flags_func_add(struct tme_ic *ic,
			  tme_recode_uguest_t src1,
			  tme_recode_uguest_t src2,
			  tme_recode_uguest_t dst)
{
  unsigned int ccr_need;
  unsigned int ccr;

  ccr_need
    = (TME_SPARC_RECODE_CCR_NEED(TME_RECODE_INSN_CLASS_ADDITIVE,
				 TME_SPARC_RECODE_SIZE(ic),
				 TME_RECODE_SIZE_32)
       | TME_SPARC_RECODE_CCR_NEED(TME_RECODE_INSN_CLASS_ADDITIVE,
				   TME_SPARC_RECODE_SIZE(ic),
				   TME_SPARC_RECODE_SIZE(ic)));

  /* make the %icc and %xcc N and Z flags: */
  ccr = TME_SPARC_RECODE_CCR_N_Z(dst, ccr_need);

  /* if src1 and src2 both have bit 31 set, or if dst does not have
     bit 31 set and either src1 or src2 does, set %icc.C: */
  if (((tme_int32_t) ((src1 & src2) | (~dst & (src1 | src2)))) < 0) {
    ccr += (ccr_need & TME_SPARC64_CCR_ICC_C);
  }

  /* if src1 and src2 have the same bit 31, and dst has a different
     bit 31, set %icc.V: */
  if (((tme_int32_t) ((src2 ^ dst) & (src1 ^ ~src2))) < 0) {
    ccr += (ccr_need & TME_SPARC64_CCR_ICC_V);
  }

  /* if src1 and src2 both have the most-significant bit set, or if
     dst does not have the most-significant bit set either src1 or
     src2 does, set %xcc.C: */
  if (((src1 & src2) | (~dst & (src1 | src2))) & TME_SPARC_IREG_MSBIT) {
    ccr += (ccr_need & TME_SPARC64_CCR_XCC_C);
  }

  /* if src1 and src2 have the same most-significant bit, and dst has
     a different most-significant bit, set %xcc.V: */
  if (((src2 ^ dst) & (src1 ^ ~src2)) & TME_SPARC_IREG_MSBIT) {
    ccr += (ccr_need & TME_SPARC64_CCR_XCC_V);
  }

  TME_SPARC_RECODE_CCR_UPDATE(ic, ccr, ccr_need);
}

/* the sub instruction flags function: */
static void
_tme_sparc_flags_func_sub(struct tme_ic *ic,
			  tme_recode_uguest_t src1,
			  tme_recode_uguest_t src2,
			  tme_recode_uguest_t dst)
{
  unsigned int ccr_need;
  unsigned int ccr;

  ccr_need
    = (TME_SPARC_RECODE_CCR_NEED(TME_RECODE_INSN_CLASS_ADDITIVE,
				 TME_SPARC_RECODE_SIZE(ic),
				 TME_RECODE_SIZE_32)
       | TME_SPARC_RECODE_CCR_NEED(TME_RECODE_INSN_CLASS_ADDITIVE,
				   TME_SPARC_RECODE_SIZE(ic),
				   TME_SPARC_RECODE_SIZE(ic)));

  /* make the %icc and %xcc N and Z flags: */
  ccr = TME_SPARC_RECODE_CCR_N_Z(dst, ccr_need);

  /* if src2 is greater than src1 in 32 bits, set %icc.C: */
  if (((tme_uint32_t) src2) > ((tme_uint32_t) src1)) {
    ccr += (ccr_need & TME_SPARC64_CCR_ICC_C);
  }

  /* if src1 and src2 have different bit 31s, and dst has a different
     bit 31 than src1, set %icc.V: */
  if (((tme_int32_t) ((src1 ^ src2) & (src1 ^ dst))) < 0) {
    ccr += (ccr_need & TME_SPARC64_CCR_ICC_V);
  }

  /* if src2 is greater than src1, set %xcc.C: */
  if (((tme_sparc_ireg_t) src2) > ((tme_sparc_ireg_t) src1)) {
    ccr += (ccr_need & TME_SPARC64_CCR_XCC_C);
  }

  /* if src1 and src2 have different most-significant bits, and dst
     has a different most-significant bit than src1, set %xcc.V: */
  if (((src1 ^ src2) & (src1 ^ dst)) & TME_SPARC_IREG_MSBIT) {
    ccr += (ccr_need & TME_SPARC64_CCR_XCC_V);
  }

  TME_SPARC_RECODE_CCR_UPDATE(ic, ccr, ccr_need);
}

/* the sparc icc conditions function: */
static int
_tme_sparc_recode_conds_func_icc(tme_recode_uguest_t flags, tme_uint32_t cond)
{
  return (_tme_sparc_conds_icc
	  [TME_FIELD_MASK_EXTRACTU(flags, TME_SPARC_ICC(TME_SPARC64_CCR_ICC))]
	  & (1 << cond));
}

/* this initializes for condition codes: */
static void
_tme_sparc_recode_cc_init(struct tme_sparc *ic)
{
  struct tme_recode_ic *recode_ic;
  struct tme_recode_flags_group flags_group;
  struct tme_recode_conds_group conds_group;

  /* recover the recode ic: */
  recode_ic = ic->tme_sparc_recode_ic;

  /* make the common parts of a flags group: */
  memset((char *) &flags_group, 0, sizeof(flags_group));
  flags_group.tme_recode_flags_group_insn_size = TME_SPARC_RECODE_SIZE(ic);
  if (TME_SPARC_VERSION(ic) <= 8) {
    flags_group.tme_recode_flags_group_flags_reg_size = TME_RECODE_SIZE_32;
    flags_group.tme_recode_flags_group_flags_reg = TME_SPARC32_IREG_PSR;
  }
  else {
    flags_group.tme_recode_flags_group_flags_reg_size = TME_RECODE_SIZE_8;
    flags_group.tme_recode_flags_group_flags_reg = TME_SPARC64_IREG_CCR << 3;
  }

  /* make the flags thunk for addcc and addxcc: */
  flags_group.tme_recode_flags_group_insn_class = TME_RECODE_INSN_CLASS_ADDITIVE;
  flags_group.tme_recode_flags_group_flags = _tme_sparc_flags_additive;
  flags_group.tme_recode_flags_group_guest_func = _tme_sparc_flags_func_add;
  ic->tme_sparc_recode_flags_thunk_add
    = tme_recode_flags_thunk(recode_ic,
			     &flags_group);

  /* make the flags thunk for subcc and subxcc: */
  flags_group.tme_recode_flags_group_insn_class = TME_RECODE_INSN_CLASS_ADDITIVE;
  flags_group.tme_recode_flags_group_flags = _tme_sparc_flags_additive;
  flags_group.tme_recode_flags_group_guest_func = _tme_sparc_flags_func_sub;
  ic->tme_sparc_recode_flags_thunk_sub
    = tme_recode_flags_thunk(recode_ic,
			     &flags_group);

  /* make the flags thunk for the logical instructions: */
  flags_group.tme_recode_flags_group_insn_class = TME_RECODE_INSN_CLASS_LOGICAL;
  flags_group.tme_recode_flags_group_flags = _tme_sparc_flags_logical;
  flags_group.tme_recode_flags_group_guest_func = _tme_sparc_flags_func_logical;
  ic->tme_sparc_recode_flags_thunk_logical
    = tme_recode_flags_thunk(recode_ic,
			     &flags_group);

  /* make the common parts of a conditions group: */
  memset((char *) &conds_group, 0, sizeof(conds_group));
  conds_group.tme_recode_conds_group_flags_reg_size = flags_group.tme_recode_flags_group_flags_reg_size;
  conds_group.tme_recode_conds_group_flags_reg = flags_group.tme_recode_flags_group_flags_reg;
  conds_group.tme_recode_conds_group_cond_count = TME_SPARC_COND_NOT;

  /* make the conditions thunk for %icc: */
  conds_group.tme_recode_conds_group_flags = TME_SPARC_ICC(TME_SPARC64_CCR_ICC);
  conds_group.tme_recode_conds_group_guest_func = _tme_sparc_recode_conds_func_icc;
  ic->tme_sparc_recode_conds_thunk_icc
    = tme_recode_conds_thunk(recode_ic,
			     &conds_group);

  /* if this is a sparc64: */
  if (TME_SPARC_VERSION(ic) >= 9) {

    /* make the flags thunk for the sparc64 internal "rcc" flags.
       this is identical to the flags thunk for the logical
       instructions, except it uses the internal "rcc" register: */
    flags_group.tme_recode_flags_group_flags_reg = TME_SPARC64_IREG_RCC << 3;
    ic->tme_sparc_recode_flags_thunk_rcc
      = tme_recode_flags_thunk(recode_ic,
			       &flags_group);

    /* make the conditions thunk for %xcc: */
    conds_group.tme_recode_conds_group_flags = TME_SPARC64_CCR_XCC;
    conds_group.tme_recode_conds_group_guest_func = _tme_sparc64_recode_conds_func_xcc;
    ic->tme_sparc_recode_conds_thunk_xcc
      = tme_recode_conds_thunk(recode_ic,
			       &conds_group);

    /* make the conditions thunk for the internal "rcc" flags.  this
       is identical to the conditions thunk for %xcc, except it uses
       the internal "rcc" register: */
    conds_group.tme_recode_conds_group_flags_reg = flags_group.tme_recode_flags_group_flags_reg;
    ic->tme_sparc_recode_conds_thunk_rcc
      = tme_recode_conds_thunk(recode_ic,
			       &conds_group);
  }
}
