/* $Id: sparc-recode.c,v 1.6 2010/06/05 18:42:35 fredette Exp $ */

/* ic/sparc/sparc-recode.c - recodes SPARC instructions: */

/*
 * 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.
 */

/* includes: */
#include "sparc-impl.h"

_TME_RCSID("$Id: sparc-recode.c,v 1.6 2010/06/05 18:42:35 fredette Exp $");

#if TME_HAVE_RECODE

/* macros: */

/* this gives the number of recode page bitmap bits needed to cover
   the given number of bytes.  this is always a multiple of eight: */
#define TME_SPARC_RECODE_PAGE_BITMAP_BITS(ic, size_bytes)	\
  ((((size_bytes) >> (ic)->tme_sparc_tlb_page_size_log2)	\
    + 7)							\
   & (0 - (unsigned long) 8))

/* this returns a read/write thunk index for an opcode: */
#define TME_SPARC_RECODE_RW_THUNK_INDEX(ic, address_size, endian, no_fault, opcode_0_3) \
  ((opcode_0_3)							\
   + ((TME_SPARC_VERSION(ic) >= 9)				\
      * ((16 * ((no_fault) != 0))				\
	 + (32 * ((endian) == TME_ENDIAN_LITTLE))		\
	 + (64 * (TME_SPARC_RECODE_SIZE(ic) - (address_size))))))

/* how many source address hash positions are probed: */
#define TME_SPARC_RECODE_SRC_HASH_SIZE_PROBE	(TME_SPARC_RECODE_SRC_HASH_SIZE_SET * 2)

/* the undefined source address hash key: */
#define TME_SPARC_RECODE_SRC_KEY_UNDEF		((tme_sparc_recode_src_key_t) (0 - (tme_sparc_recode_src_key_t) 1))

/* flags in a source address hash key: */
/* NB: an architecture can't have more than two flags, since those are
   the only bits available in a source address hash key (because PCs
   are 32-bit aligned): */
#define TME_SPARC64_RECODE_SRC_KEY_FLAG_AM	(1 << 0)
#define TME_SPARC64_RECODE_SRC_KEY_FLAG_CLE	(1 << 1)

/* the hit count threshold for recoding a source address: */
#define TME_SPARC_RECODE_HIT_COUNT_THRESHOLD	(512)

/* this returns the recode size for the architecture size: */
#define TME_SPARC_RECODE_SIZE(ic) (TME_RECODE_SIZE_32 + (TME_SPARC_VERSION(ic) >= 9))

/* fix certain things based on the architecture size: */
#undef TME_SPARC_VERSION
#define TME_SPARC_VERSION(ic) (8 + (_TME_SPARC_RECODE_SIZE(/**/,/**/) == 64))
#define tme_sparc_ireg_t _TME_SPARC_RECODE_SIZE(tme_uint,_t)
#define tme_sparc_ireg _TME_SPARC_RECODE_SIZE(tme_sparc_ireg_uint,/**/)
#define TME_SPARC_IREG_MSBIT (((tme_sparc_ireg_t) 1) << (sizeof(tme_sparc_ireg_t) * 8 - 1))

/* the current verify state: */
#ifdef _TME_SPARC_RECODE_VERIFY
static int _tme_sparc_recode_off;
static int _tme_sparc_recode_verify_on;
#else  /* !_TME_SPARC_RECODE_VERIFY */
#define _tme_sparc_recode_off (FALSE)
#define _tme_sparc_recode_verify_on (FALSE)
#endif /* !_TME_SPARC_RECODE_VERIFY */

/* make the sparc32 recode support: */
#define _TME_SPARC_RECODE_SIZE(x,y) _TME_CONCAT(_TME_CONCAT(x,32),y)
#include "sparc-rc-cc.c"
#include "sparc-rc-chain.c"
#include "sparc-rc-insns.c"
#include "sparc-rc-ls.c"
#undef _TME_SPARC_RECODE_SIZE

#if TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32

/* make the sparc64 recode support: */
#define _TME_SPARC_RECODE_SIZE(x,y) _TME_CONCAT(_TME_CONCAT(x,64),y)
#include "sparc-rc-cc.c"
#include "sparc-rc-chain.c"
#include "sparc-rc-insns.c"
#include "sparc-rc-ls.c"
#undef _TME_SPARC_RECODE_SIZE

#endif /* TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32 */

/* unfix certain things: */
#undef  TME_SPARC_VERSION
#define TME_SPARC_VERSION(ic) _TME_SPARC_VERSION(ic)
#undef tme_sparc_ireg_t
#undef tme_sparc_ireg
#undef TME_SPARC_IREG_MSBIT

/* this invalidates pages in the source address hash: */
static void
_tme_sparc_recode_src_hash_invalidate(struct tme_sparc *ic,
				      tme_sparc_recode_src_key_t src_key_mask,
				      tme_sparc_recode_src_key_t src_key_match)
{
  signed long src_hash_i;
  tme_sparc_recode_src_key_t src_key;
  tme_recode_thunk_off_t insns_thunk;

  /* walk all of the positions in the source address hash: */
  src_hash_i
    = ((TME_SPARC_RECODE_SRC_HASH_MODULUS
	* TME_SPARC_RECODE_SRC_HASH_SIZE_SET)
       - 1);
  do {

    /* if the source address key in this position matches: */
    src_key
      = (ic->tme_sparc_recode_src_hash
	 [src_hash_i / TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]
	 .tme_sparc_recode_src_hash_keys
	 [src_hash_i % TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]);
    if (((src_key ^ src_key_match) & src_key_mask) == 0) {

      /* invalidate this source address key: */
      (ic->tme_sparc_recode_src_hash
       [src_hash_i / TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]
       .tme_sparc_recode_src_hash_keys
       [src_hash_i % TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT])
	= TME_SPARC_RECODE_SRC_KEY_UNDEF;

      /* if this source address key has an instructions thunk: */
      insns_thunk
	= (ic->tme_sparc_recode_src_hash
	   [src_hash_i / TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]
	   .tme_sparc_recode_src_hash_values
	   [src_hash_i % TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]);
      if ((insns_thunk & TME_BIT(0)) == 0
	  && insns_thunk != 0) {

	/* invalidate the instructions thunk: */
	tme_recode_insns_thunk_invalidate(ic->tme_sparc_recode_ic,
					  insns_thunk);
      }

      /* reset the value for this position to a hit count of zero: */
      (ic->tme_sparc_recode_src_hash
       [src_hash_i / TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]
       .tme_sparc_recode_src_hash_values
       [src_hash_i % TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT])
	= TME_BIT(0);
    }
  } while (--src_hash_i >= 0);
}

/* this invalidates our entire source hash and all instructions
   thunks: */
void
tme_sparc_recode_invalidate_all(struct tme_sparc *ic)
{

  /* invalidate our entire source hash: */
  _tme_sparc_recode_src_hash_invalidate(ic, 0, 0);

  /* invalidate all instructions thunks: */
  tme_recode_insns_thunk_invalidate_all(ic->tme_sparc_recode_ic);

  /* clear the return address stack: */
  tme_recode_chain_ras_clear(ic->tme_sparc_recode_ic,
			     &ic->tme_sparc_ic);
}

/* include the verify support: */
#ifdef _TME_SPARC_RECODE_VERIFY
#include "sparc-rc-verify.c"
#endif /* _TME_SPARC_RECODE_VERIFY */

/* this adds a new cacheable: */
static struct tme_sparc_recode_cacheable *
_tme_sparc_recode_cacheable_new(struct tme_sparc *ic,
				const struct tme_sparc_tlb *itlb,
				const tme_shared tme_uint32_t *src)
{
  unsigned int page_size_log2;
  tme_uint32_t page_size;
  tme_uint32_t page_byte_offset;
  struct tme_sparc_recode_cacheable *cacheable;
  const struct tme_bus_cacheable *bus_cacheable;
  unsigned long bitmap_bytes_old;
  unsigned long bitmap_bytes;

  /* get the page size: */
  page_size_log2 = ic->tme_sparc_tlb_page_size_log2;
  page_size = (((tme_uint32_t) 1) << page_size_log2);

  /* get the offset of the current PC into this page: */
  page_byte_offset
    = (
#if TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32
       TME_SPARC_VERSION(ic) >= 9
       ? ic->tme_sparc_ireg_uint64(TME_SPARC_IREG_PC)
       : 
#endif /* TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32 */
       ic->tme_sparc_ireg_uint32(TME_SPARC_IREG_PC));
  page_byte_offset %= page_size;

  /* if we have run out of cacheables, return failure: */
  cacheable = ic->tme_sparc_recode_cacheable_first;
  if (cacheable == &ic->tme_sparc_recode_cacheables[TME_SPARC_RECODE_CACHEABLES_MAX - 1]) {
    return (NULL);
  }
  cacheable += (ic->tme_sparc_recode_cacheables[0].tme_sparc_recode_cacheable_size != 0);

  /* if this instruction TLB entry isn't for cacheable memory, return
     failure: */
  bus_cacheable = itlb->tme_sparc_tlb_bus_tlb.tme_bus_tlb_cacheable;
  if (bus_cacheable == NULL) {
    return (NULL);
  }

  /* if the cacheable memory doesn't cover some whole number of
     aligned pages, return failure: */
  assert (bus_cacheable->tme_bus_cacheable_size > 0);
  if ((((((tme_uint8_t *) src)
	 - bus_cacheable->tme_bus_cacheable_contents)
	% page_size)
       != page_byte_offset)
      || ((bus_cacheable->tme_bus_cacheable_size
	   % page_size)
	  != 0)) {
    return (NULL);
  }

  /* initialize our cacheable: */
  /* NB: we round up the first source address key for this cacheable
     so that its page index is a multiple of eight, which will be used
     to index the least-significant bit in the first byte of the
     valids bitmap we allocate below: */
  cacheable->tme_sparc_recode_cacheable_contents = bus_cacheable->tme_bus_cacheable_contents;
  cacheable->tme_sparc_recode_cacheable_size = bus_cacheable->tme_bus_cacheable_size;
  cacheable->tme_sparc_recode_cacheable_src_key_first
    = (cacheable == &ic->tme_sparc_recode_cacheables[0]
       ? 0
       : ((cacheable - 1)->tme_sparc_recode_cacheable_src_key_first

	  + (TME_SPARC_RECODE_PAGE_BITMAP_BITS(ic, (cacheable - 1)->tme_sparc_recode_cacheable_size)
	     * page_size)));

  /* allocate the valids bitmap, and adjust the pointer to account
     for the first source key in this cacheable: */
  cacheable->tme_sparc_recode_cacheable_valids
    = ((*bus_cacheable->tme_bus_cacheable_valids_new)
       (bus_cacheable->tme_bus_cacheable_private,
	page_size_log2)
       - ((cacheable->tme_sparc_recode_cacheable_src_key_first
	   / page_size)
	  / 8));

  /* allocate or reallocate the active recode pages bitmap, and zero
     the new parts of the bitmap: */
  bitmap_bytes_old
    = (cacheable->tme_sparc_recode_cacheable_src_key_first
       / (page_size * 8));
  bitmap_bytes
    = (bitmap_bytes_old
       + (TME_SPARC_RECODE_PAGE_BITMAP_BITS(ic, cacheable->tme_sparc_recode_cacheable_size)
	  / 8));
  ic->tme_sparc_recode_cacheable_actives
    = (cacheable == &ic->tme_sparc_recode_cacheables[0]
       ? tme_new(tme_uint8_t, bitmap_bytes)
       : tme_renew(tme_uint8_t, ic->tme_sparc_recode_cacheable_actives, bitmap_bytes));
  memset((ic->tme_sparc_recode_cacheable_actives + bitmap_bytes_old),
	 0,
	 (bitmap_bytes - bitmap_bytes_old));

  /* finish allocating this cacheable: */
  ic->tme_sparc_recode_cacheable_first = cacheable;

  /* return success: */
  return (cacheable);
}

tme_recode_thunk_off_t
tme_sparc_recode(struct tme_sparc *ic,
		 const struct tme_sparc_tlb *itlb,
		 const tme_shared tme_uint32_t *src)
{
  
  const struct tme_sparc_recode_cacheable *cacheable;
  tme_sparc_recode_src_key_t src_key;
  tme_uint32_t pstate;
  unsigned long page_index;
  tme_uint8_t page_invalid;
  const tme_shared tme_uint8_t *cacheable_valid_byte;
  unsigned int cacheable_valid_mask;
  signed long src_hash_i;
  signed int src_hash_probe;
  tme_sparc_recode_src_key_t src_key_other;
  tme_recode_thunk_off_t insns_thunk;
  tme_recode_thunk_off_t hit_count;

  TME_SPARC_STAT(ic, tme_sparc_stats_recode_calls);

  /* if recoding is off: */
  if (_tme_sparc_recode_off) {

    /* we can't recode this source address: */
    return (0);
  }

  /* if this instruction TLB entry doesn't extend to the end of the
     page: */
  /* XXX FIXME - this isn't right, since it will reject all itlb
     entries that don't end right at a recode page boundary, instead
     of only those that end before the end of the page containing the
     current PC.  maybe this check should move to sparc-rc-insns.c or
     sparc-execute.c, where we have the current PC? */
  if (__tme_predict_false((~itlb->tme_sparc_tlb_addr_last)
			  & ((1 << ic->tme_sparc_tlb_page_size_log2)
			     - sizeof(tme_uint32_t)))) {

    /* we can't recode this source address: */
    return (0);
  }

  /* assume that we will recode at this source address, and save it: */
  ic->tme_sparc_recode_insns_group.tme_recode_insns_group_src
    = (const tme_shared tme_uint8_t *) src;

  /* search for a cacheable that contains this source address: */
  cacheable = ic->tme_sparc_recode_cacheable_first;
  for (;;) {

    /* assume that this cacheable contains this source address, and get the
       source address key relative to the start of the cacheable: */
    src_key = ((tme_uint8_t *) src) - cacheable->tme_sparc_recode_cacheable_contents;

    /* if this cacheable contains this source address: */
    if (__tme_predict_true(src_key
			   < cacheable->tme_sparc_recode_cacheable_size)) {

      /* stop now: */
      break;
    }

    /* if we have not run out of cacheables: */
    if (__tme_predict_true(--cacheable != (&ic->tme_sparc_recode_cacheables[0] - 1))) {
      continue;
    }

    /* if this instruction TLB entry isn't for a cacheable, or if we
       can't add this cacheable: */
    cacheable = _tme_sparc_recode_cacheable_new(ic, itlb, src);
    if (cacheable == NULL) {

      /* we can't recode this source address: */
      return (0);
    }
  }

  /* make the absolute source address key by adding in the first
     source address key in this cacheable: */
  src_key += cacheable->tme_sparc_recode_cacheable_src_key_first;
  assert ((src_key % sizeof(tme_uint32_t)) == 0);

  /* if this is a v9 CPU: */
  if (TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32
      && TME_SPARC_VERSION(ic) >= 9) {

    /* add PSTATE.AM and PSTATE.CLE to the source address key: */
    pstate = ic->tme_sparc64_ireg_pstate;
    if (pstate & TME_SPARC64_PSTATE_AM) {
      src_key += TME_SPARC64_RECODE_SRC_KEY_FLAG_AM;
    }
    if (pstate & TME_SPARC64_PSTATE_CLE) {
      src_key += TME_SPARC64_RECODE_SRC_KEY_FLAG_CLE;
    }
  }

  /* the source address key must be defined: */
  assert (src_key != TME_SPARC_RECODE_SRC_KEY_UNDEF);

  /* make the index for the page containing this source address: */
  page_index = src_key >> ic->tme_sparc_tlb_page_size_log2;

  /* assume that this recode page is still valid: */
  page_invalid = FALSE;

  /* get the cacheable valid byte and mask for this page: */
  cacheable_valid_byte = &cacheable->tme_sparc_recode_cacheable_valids[page_index / 8];
  cacheable_valid_mask = TME_BIT(page_index % 8);

  /* assume that we will recode at this source address, and save the
     cacheable valid byte and mask for this page: */
  ic->tme_sparc_recode_insns_group.tme_recode_insns_group_valid_byte = cacheable_valid_byte;
  ic->tme_sparc_recode_insns_group.tme_recode_insns_group_valid_mask = cacheable_valid_mask;

  /* if the valid bit for this page is no longer set: */
  if (__tme_predict_false(((*cacheable_valid_byte) & cacheable_valid_mask) == 0)) {

    /* this page is invalid: */
    page_invalid = TRUE;

    /* if this page was active: */
    if (__tme_predict_false(ic->tme_sparc_recode_cacheable_actives[page_index / 8]
			    & TME_BIT(page_index % 8))) {

      TME_SPARC_STAT(ic, tme_sparc_stats_recode_page_invalids);

      /* mark this page as inactive: */
      ic->tme_sparc_recode_cacheable_actives[page_index / 8] &= ~TME_BIT(page_index % 8);

      /* re-set the valid bit for the page containing this
	 source address: */
      /* NB: we cheat and pass our
	 page-index-base-greater-than-zero-relative valids bitmap
	 pointer instead of the original pointer returned by the
	 allocator.  this only works if the re-set function will work
	 given arbitrary valids bitmap pointers and page indices: */
      (*itlb->tme_sparc_tlb_bus_tlb.tme_bus_tlb_cacheable->tme_bus_cacheable_valids_set)
	(itlb->tme_sparc_tlb_bus_tlb.tme_bus_tlb_cacheable->tme_bus_cacheable_private,
	 cacheable->tme_sparc_recode_cacheable_valids,
	 page_index);

      /* invalidate this page in the source address hash: */
      _tme_sparc_recode_src_hash_invalidate(ic, 
					    (0
					     - ((tme_sparc_recode_src_key_t)
						(1 << ic->tme_sparc_tlb_page_size_log2))),
					    src_key);
    }
  }

  /* hash the source address key: */
  src_hash_i
    = ((((src_key
	  / sizeof(tme_uint32_t))
	 % TME_SPARC_RECODE_SRC_HASH_MODULUS)
	* TME_SPARC_RECODE_SRC_HASH_SIZE_SET)
       + (TME_SPARC_RECODE_SRC_HASH_SIZE_SET
	  - 1));

  /* search for the source address key in the hash: */
  src_hash_probe = TME_SPARC_RECODE_SRC_HASH_SIZE_PROBE;
  for (;;) {

    TME_SPARC_STAT(ic, tme_sparc_stats_recode_source_hash_probes);

    /* get the source address key at this position: */
    src_key_other
      = (ic->tme_sparc_recode_src_hash
	 [src_hash_i / TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]
	 .tme_sparc_recode_src_hash_keys
	 [src_hash_i % TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]);

    /* if we found the source address key, stop now: */
    if (__tme_predict_true(src_key_other == src_key)) {
      break;
    }

    /* if this position in the hash is free: */
    if (src_key_other == TME_SPARC_RECODE_SRC_KEY_UNDEF) {

      /* allocate this position in the hash: */
      (ic->tme_sparc_recode_src_hash
       [src_hash_i / TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]
       .tme_sparc_recode_src_hash_keys
       [src_hash_i % TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT])
	= src_key;

      /* stop now: */
      break;
    }

    /* if we have searched enough: */
    if (__tme_predict_false(--src_hash_probe < 0)) {

      TME_SPARC_STAT(ic, tme_sparc_stats_recode_source_hash_misses);

      /* we won't recode this source address: */
      return (0);
    }

    /* move to the next position to search: */
    if (__tme_predict_false(--src_hash_i < 0)) {
      src_hash_i
	= ((TME_SPARC_RECODE_SRC_HASH_MODULUS
	    * TME_SPARC_RECODE_SRC_HASH_SIZE_SET)
	   - 1);
    }
  }

  /* assume that this source address has been recoded, and get the
     instructions thunk: */
  insns_thunk
    = (ic->tme_sparc_recode_src_hash
       [src_hash_i / TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]
       .tme_sparc_recode_src_hash_values
       [src_hash_i % TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]);

  /* if the source address has not been recoded yet: */
  if (__tme_predict_false(insns_thunk & TME_BIT(0))) {

    /* update the hit count for this source address: */
    hit_count = insns_thunk + 2;

    /* if the hit count for this source address is still less than the
       recode threshold: */
    if (__tme_predict_true(hit_count
			   < ((TME_SPARC_RECODE_HIT_COUNT_THRESHOLD * 2)
			      + 1))) {

      /* update the hit count for this source address: */
      (ic->tme_sparc_recode_src_hash
       [src_hash_i / TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]
       .tme_sparc_recode_src_hash_values
       [src_hash_i % TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT])
	= hit_count;

      /* we won't recode this source address now: */
      return (0);
    }

    /* mark this page as active: */
    ic->tme_sparc_recode_cacheable_actives[page_index / 8] |= TME_BIT(page_index % 8);

    /* if this page is invalid: */
    if (page_invalid) {

      /* return now and interpret instructions normally - the next
	 time we encounter this page again, we will mark this
	 page as inactive, re-set the page's valid bit and
	 invalidate the page in the source address hash.

	 this somewhat limits the work we do for pages that are
	 both executed from and written to frequently.  we will only
	 recode an instructions thunk when its page was valid for the
	 entire time it took for its hit count to go from zero to the
	 threshold, and we will only invalidate an invalid page in the
	 source address hash when any hit count in the page reaches
	 the threshold: */
      return (0);
    }

    /* recode starting from this source address: */
    insns_thunk
      = (
#if TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32
	 TME_SPARC_VERSION(ic) >= 9
	 ? _tme_sparc64_recode_recode(ic, itlb)
	 :
#endif /* TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32 */
	 _tme_sparc32_recode_recode(ic, itlb));

    /* if we couldn't recode at this source address: */
    if (__tme_predict_false(insns_thunk < 0)) {

      /* the only time we can't recode is when the host runs out of
	 memory for instructions thunks.  when this happens, the host
	 invalidates all previous instructions thunks, so we have to
	 invalidate our entire source hash: */
      tme_sparc_recode_invalidate_all(ic);
      return (0);
    }

    /* set the instructions thunk for this source address: */
    assert ((insns_thunk & TME_BIT(0)) == 0);
    (ic->tme_sparc_recode_src_hash
     [src_hash_i / TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT]
     .tme_sparc_recode_src_hash_values
     [src_hash_i % TME_SPARC_RECODE_SRC_HASH_SIZE_ELEMENT])
      = insns_thunk;

    /* we can't run the new instructions thunk without re-checking the
       valid bit for the page containing this source address
       first - if it wasn't set for the entire time we were recoding,
       the instructions thunk might not be consistent with the source
       memory.

       to keep things simple, we just return now and interpret
       instructions normally - the next time we get to this source
       address, we'll check the valid bit and either run the
       instructions thunk for the first time, or we'll do the
       invalidate: */
    return (0);
  }

  /* return the instructions thunk offset: */
  return (insns_thunk);
}

#ifdef TME_RECODE_DEBUG
#include <stdio.h>

void tme_sparc_recode_dump_ic _TME_P((const struct tme_sparc *));
void
tme_sparc_recode_dump_ic(const struct tme_sparc *ic)
{
  const char *conds_integer[] = {
    "n", "e", "le", "l", "leu", "cs", "neg", "vs",
    NULL
  };

  printf("sparc%u add flags thunk %p:\n",
	 (1 << TME_SPARC_RECODE_SIZE(ic)),
	 ic->tme_sparc_recode_flags_thunk_add);
  tme_recode_flags_thunk_dump(ic->tme_sparc_recode_ic,
			      ic->tme_sparc_recode_flags_thunk_add);
  printf("sparc%u sub flags thunk %p:\n",
	 (1 << TME_SPARC_RECODE_SIZE(ic)),
	 ic->tme_sparc_recode_flags_thunk_sub);
  tme_recode_flags_thunk_dump(ic->tme_sparc_recode_ic,
			      ic->tme_sparc_recode_flags_thunk_sub);
  printf("sparc%u logical flags thunk %p:\n",
	 (1 << TME_SPARC_RECODE_SIZE(ic)),
	 ic->tme_sparc_recode_flags_thunk_logical);
  tme_recode_flags_thunk_dump(ic->tme_sparc_recode_ic,
			      ic->tme_sparc_recode_flags_thunk_logical);

  if (TME_SPARC_VERSION(ic) >= 9) {
    printf("sparc%u rcc flags thunk %p:\n",
	   (1 << TME_SPARC_RECODE_SIZE(ic)),
	   ic->tme_sparc_recode_flags_thunk_rcc);
    tme_recode_flags_thunk_dump(ic->tme_sparc_recode_ic,
				ic->tme_sparc_recode_flags_thunk_rcc);
  }
  printf("sparc%u %%icc conds thunk %p:\n",
	 (1 << TME_SPARC_RECODE_SIZE(ic)),
	 ic->tme_sparc_recode_conds_thunk_icc);
  tme_recode_conds_thunk_dump(ic->tme_sparc_recode_ic, 
			      ic->tme_sparc_recode_conds_thunk_icc,
			      conds_integer);
  if (TME_SPARC_VERSION(ic) >= 9) {
    printf("sparc%u %%xcc conds thunk %p:\n",
	   (1 << TME_SPARC_RECODE_SIZE(ic)),
	   ic->tme_sparc_recode_conds_thunk_xcc);
    tme_recode_conds_thunk_dump(ic->tme_sparc_recode_ic, 
				ic->tme_sparc_recode_conds_thunk_xcc,
				conds_integer);
    printf("sparc%u %%rcc conds thunk %p:\n",
	   (1 << TME_SPARC_RECODE_SIZE(ic)),
	   ic->tme_sparc_recode_conds_thunk_rcc);
    tme_recode_conds_thunk_dump(ic->tme_sparc_recode_ic, 
				ic->tme_sparc_recode_conds_thunk_rcc,
				conds_integer);
  }
}

void tme_sparc_recode_dump_insns _TME_P((const struct tme_sparc *));
void
tme_sparc_recode_dump_insns(const struct tme_sparc *ic)
{
  const struct tme_recode_insn *insns;
  const struct tme_recode_insn *insns_end;
  unsigned int insn_i;
  const char *s;
  unsigned int insn_size;
  unsigned int operands_mask;
  unsigned int thunk_i;
  unsigned int operand_i;
  int delim;
  const char *regcs = "goli";
  const char *conds_integer[] = {
    "n", "e", "le", "l", "leu", "cs", "neg", "vs",
    "a", "ne", "g", "ge", "gu", "cc", "pos", "vc",
  };
  tme_uint32_t chain_info;

  printf("%%pc = ");
  tme_recode_uguest_dump(
#if TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32
			 TME_SPARC_VERSION(ic) >= 9
			 ? ic->tme_sparc_ireg_uint64(TME_SPARC_IREG_PC)
			 :
#endif /* TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32 */
			 ic->tme_sparc_ireg_uint32(TME_SPARC_IREG_PC));
  printf("\n");

  insns = ic->tme_sparc_recode_insns_group.tme_recode_insns_group_insns;
  insns_end = ic->tme_sparc_recode_insns_group.tme_recode_insns_group_insns_end;
  for (insn_i = 0; insns < insns_end; insns++, insn_i++) {

    printf("%2u: ", insn_i);

    s = tme_recode_opcode_dump(insns->tme_recode_insn_opcode);
    insn_size = insns->tme_recode_insn_size;
    switch (insns->tme_recode_insn_opcode) {
    case TME_RECODE_OPCODE_EXTZ:
    case TME_RECODE_OPCODE_EXTS:
      printf("ext%u%c",
	     (1 << insns->tme_recode_insn_operand_src[1]),
	     (insns->tme_recode_insn_opcode == TME_RECODE_OPCODE_EXTZ ? 'z' : 's'));
      s = "";
      operands_mask = (1 << 0) | (1 << 2);
      break;
    case TME_RECODE_OPCODE_DEFC:
      printf("defc\t %%%ccc:%s, %%c%u\n",
	     (insns->tme_recode_insn_conds_thunk == ic->tme_sparc_recode_conds_thunk_icc
	      ? 'i'
	      : insns->tme_recode_insn_conds_thunk == ic->tme_sparc_recode_conds_thunk_xcc
	      ? 'x'
	      : 'r'),
	     conds_integer
	     [insns->tme_recode_insn_operand_src[0]
	      + (TME_SPARC_COND_NOT
		 * (insns->tme_recode_insn_operand_src[1] != 0))],
	     insns->tme_recode_insn_operand_dst);
      continue;
    case TME_RECODE_OPCODE_IF:
      printf("%s\t %%c%u\n", s, insns->tme_recode_insn_operand_src[0]);
      continue;
    case TME_RECODE_OPCODE_ELSE:
    case TME_RECODE_OPCODE_ENDIF:
      printf("%s\n", s);
      continue;
    case TME_RECODE_OPCODE_RW:
      for (thunk_i = 0;; thunk_i++) {
	assert (thunk_i < TME_ARRAY_ELS(ic->tme_sparc_recode_rw_thunks));
	if (insns->tme_recode_insn_rw_thunk == ic->tme_sparc_recode_rw_thunks[thunk_i]) {
	  break;
	}
      }
      s = "?rw?";
      switch (thunk_i % 0x10) {
      case 0x0: s = (TME_SPARC_VERSION(ic) >= 9 ? "lduw" : "ld"); break;
      case 0x1: s = "ldub"; break;
      case 0x2: s = "lduh"; break;
      case 0x4: s = (TME_SPARC_VERSION(ic) >= 9 ? "stw" : "st"); break;
      case 0x5: s = "stb"; break;
      case 0x6: s = "sth"; break;
      case 0x8: if (TME_SPARC_VERSION(ic) >= 9) { s = "ldsw"; } break;
      case 0x9: s = "ldsb"; break;
      case 0xa: s = "ldsh"; break;
      case 0xb: if (TME_SPARC_VERSION(ic) >= 9) { s = "ldx"; } break;
      case 0xe: if (TME_SPARC_VERSION(ic) >= 9) { s = "stx"; } break;
      }
      if (thunk_i & 16) {
	printf("%s", s);
	s = ".nf";
      }
      if (thunk_i & 32) {
	printf("%s", s);
	s = ".el";
      }
      if (thunk_i & 64) {
	printf("%s", s);
	s = ".am";
      }
      insn_size = 0;
      operands_mask = (1 << 0);
      if (insns->tme_recode_insn_rw_thunk->tme_recode_rw_thunk_write) {
	operands_mask |= (1 << 1);
      }
      else {
	operands_mask |= (1 << 2);
      }
      break;
    default:
      operands_mask = (1 << 0) | (1 << 1) | (1 << 2);
      break;
    }

    printf("%s", s);
    if (insn_size > 0) {
      printf("%u", (1 << insns->tme_recode_insn_size));
    }
    delim = '\t';
    for (operand_i = 0; operand_i <= 2; operand_i++) {
      if (operands_mask & (1 << operand_i)) {
	putchar(delim);
	putchar(' ');
	delim = ',';
	switch (insns->tme_recode_insn_operands[operand_i]) {
	case TME_RECODE_OPERAND_UNDEF:
	  s = "undef";
	  break;
	case TME_RECODE_OPERAND_IMM:
	  tme_recode_uguest_dump(insns->tme_recode_insn_imm_uguest);
	  continue;
#if TME_RECODE_OPERAND_NULL != TME_RECODE_OPERAND_ZERO
#error "TME_RECODE_OPERAND_ values changed"
#endif
	case TME_RECODE_OPERAND_ZERO: 
	  s = "g0";
	  if (operand_i == 2
	      && insns->tme_recode_insn_opcode == TME_RECODE_OPCODE_GUEST) {
	    s = "null";
	  }
	  break;
	case TME_SPARC_IREG_PC: s = "pc"; break;
	case TME_SPARC_IREG_PC_NEXT: s = "pc_next"; break;
	case TME_SPARC_IREG_PC_NEXT_NEXT: s = "pc_next_next"; break;
	case TME_SPARC_IREG_INSN: s = "insn"; break;
	case TME_SPARC_IREG_TMP(0): s = "tmp0"; break;
	case TME_SPARC_IREG_TMP(1): s = "tmp1"; break;
	case TME_SPARC_IREG_TMP(2): s = "tmp2"; break;
	default:
	  if (insns->tme_recode_insn_operands[operand_i] < 32) {
	    printf("%%%c%u",
		   regcs[insns->tme_recode_insn_operands[operand_i] / 8],
		   (insns->tme_recode_insn_operands[operand_i] % 8));
	    continue;
	  }
	  s = "??";
	  break;
	}
	printf("%%%s", s);
      }
    }

    if (insns->tme_recode_insn_opcode < TME_RECODE_OPCODES_INTEGER
	&& insns->tme_recode_insn_flags_thunk != NULL) {
      printf(", %%%s",
	     (TME_SPARC_VERSION(ic) < 9
	      ? "icc"
	      : insns->tme_recode_insn_flags_thunk == ic->tme_sparc_recode_flags_thunk_rcc
	      ? "rcc"
	      : "ccr"));
    }	     

    putchar('\n');
  }
  
  chain_info = ic->tme_sparc_recode_insns_group.tme_recode_insns_group_chain_info;
  printf("chain %s %s ",
	 ((chain_info & TME_RECODE_CHAIN_INFO_JUMP)
	  ? "jump"
	  : (chain_info & TME_RECODE_CHAIN_INFO_RETURN)
	  ? "return"
	  : "call"),
	 (((chain_info
	    & (TME_RECODE_CHAIN_INFO_NEAR
	       | TME_RECODE_CHAIN_INFO_FAR))
	   == TME_RECODE_CHAIN_INFO_NEAR)
	  ? "near"
	  : "far"));
  if ((chain_info
       & (TME_RECODE_CHAIN_INFO_UNCONDITIONAL
	  | TME_RECODE_CHAIN_INFO_CONDITIONAL))
      == TME_RECODE_CHAIN_INFO_UNCONDITIONAL) {
    printf("unconditional\n");
  }
  else {
    printf("conditional alternate %s\n",
	   (((chain_info
	      & (TME_RECODE_CHAIN_INFO_ALTERNATE_NEAR
		 | TME_RECODE_CHAIN_INFO_ALTERNATE_FAR))
	     == TME_RECODE_CHAIN_INFO_ALTERNATE_NEAR)
	    ? "near"
	    : "far"));
  }
}

#endif /* TME_RECODE_DEBUG */

#endif /* TME_HAVE_RECODE */

/* this initializes sparc recoding: */
void
tme_sparc_recode_init(struct tme_sparc *ic)
{
#if TME_HAVE_RECODE
  unsigned long reg_guest_count;
  unsigned long reg_guest;
  struct tme_recode_ic *recode_ic;

  /* get the number of guest registers: */
  reg_guest_count = TME_SPARC_IREG_TMP(3);

  /* allocate the recode ic: */
  recode_ic
    = tme_malloc0(sizeof(struct tme_recode_ic)
		  + (sizeof(union tme_recode_reginfo)
		     * reg_guest_count));
  ic->tme_sparc_recode_ic = recode_ic;

  /* set the register size: */
  recode_ic->tme_recode_ic_reg_size = TME_SPARC_RECODE_SIZE(ic);

  /* set the register count: */
  recode_ic->tme_recode_ic_reg_count = reg_guest_count;

  /* allocate some number of read-uses records: */
  /* XXX FIXME - this can be anything, and doesn't affect correctness,
     just performance.  should it be tunable? */
  recode_ic->tme_recode_ic_reg_guest_ruses_record_count = 512;
  recode_ic->tme_recode_ic_reg_guest_ruses_records
    = tme_new(tme_uint16_t, 
	      recode_ic->tme_recode_ic_reg_guest_ruses_record_count + 1);
  recode_ic->tme_recode_ic_reg_guest_ruses_records
    [recode_ic->tme_recode_ic_reg_guest_ruses_record_count]
    = TME_RECODE_REG_RUSES_RECORD_UNDEF;

  /* assume that all registers use flat addressing: */
  for (reg_guest = 0;
       reg_guest < reg_guest_count;
       reg_guest++) {
    (recode_ic->tme_recode_ic_reginfo
     + reg_guest)->tme_recode_reginfo_all = TME_RECODE_REGINFO_TYPE_FLAT;
  }

  /* %g0 is fixed: */
  (recode_ic->tme_recode_ic_reginfo
   + TME_RECODE_REG_GUEST(TME_SPARC_IREG_G0))->tme_recode_reginfo_all
    |= TME_RECODE_REGINFO_TYPE_FIXED;

  /* %r8 through %r23 are addressed through window zero, and %r24
     through %r31 are addressed through window one: */
  reg_guest = TME_RECODE_REG_GUEST(8);
  do {
    (recode_ic->tme_recode_ic_reginfo
     + reg_guest)->tme_recode_reginfo_all = TME_RECODE_REGINFO_TYPE_WINDOW(0);
  } while (++reg_guest <= TME_RECODE_REG_GUEST(23));
  do {
    (recode_ic->tme_recode_ic_reginfo
     + reg_guest)->tme_recode_reginfo_all = TME_RECODE_REGINFO_TYPE_WINDOW(1);
  } while (++reg_guest <= TME_RECODE_REG_GUEST(31));

  /* on a v9 CPU, %r1 through %r7 are addressed through window two: */
  if (TME_SPARC_VERSION(ic) >= 9) {
    reg_guest = TME_RECODE_REG_GUEST(1);
    do {
      (recode_ic->tme_recode_ic_reginfo
       + reg_guest)->tme_recode_reginfo_all = TME_RECODE_REGINFO_TYPE_WINDOW(2);
    } while (++reg_guest <= TME_RECODE_REG_GUEST(7));
  }

  /* the temporary registers are temporary: */
  reg_guest = TME_RECODE_REG_GUEST(TME_SPARC_IREG_TMP(0));
  do {
    (recode_ic->tme_recode_ic_reginfo
     + reg_guest)->tme_recode_reginfo_all
      |= TME_RECODE_REGINFO_TYPE_TEMPORARY;
  } while (++reg_guest <= TME_RECODE_REG_GUEST(TME_SPARC_IREG_TMP(2)));

  /* set the window base offsets: */
  recode_ic->tme_recode_ic_window_base_offsets[0]
    = (((char *) &ic->tme_sparc_recode_window_base_offsets[0]) - ((char *) ic));
  recode_ic->tme_recode_ic_window_base_offsets[1]
    = (((char *) &ic->tme_sparc_recode_window_base_offsets[1]) - ((char *) ic));
  recode_ic->tme_recode_ic_window_base_offsets[2]
    = (((char *) &ic->tme_sparc_recode_window_base_offsets[2]) - ((char *) ic));

  /* initialize the cacheables: */
  ic->tme_sparc_recode_cacheables[0].tme_sparc_recode_cacheable_size = 0;
  ic->tme_sparc_recode_cacheable_first = &ic->tme_sparc_recode_cacheables[0];

  /* invalidate the entire source hash: */
  _tme_sparc_recode_src_hash_invalidate(ic, 0, 0);

  /* set the TLB page size: */
  recode_ic->tme_recode_ic_tlb_page_size
    = (((tme_uint32_t) 1) << ic->tme_sparc_tlb_page_size_log2);

  /* set the pointer to the first instruction in a group: */
  ic->tme_sparc_recode_insns_group.tme_recode_insns_group_insns
    = &ic->tme_sparc_recode_insns[0];

  /* initialize the recode ic: */
  /* XXX FIXME - this run size should be passed in as an argument.  it
     needs to be big enough for the maximum number of non-variable
     thunks (flags, conds, chain, etc.) that we will make, probably
     TME_RECODE_HOST_THUNK_SIZE_MAX times some n? */
  tme_recode_ic_new(recode_ic, 4 * 1024 * 1024);

  /* call the architecture-specific init functions: */
  if (TME_SPARC_VERSION(ic) >= 9) {
#if TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32
    _tme_sparc64_recode_cc_init(ic);
    _tme_sparc64_recode_chain_init(ic);
    _tme_sparc64_recode_ls_init(ic);
#endif /* TME_RECODE_SIZE_GUEST_MAX > TME_RECODE_SIZE_32 */
  }
  else {
    _tme_sparc32_recode_cc_init(ic);
    _tme_sparc32_recode_chain_init(ic);
    _tme_sparc32_recode_ls_init(ic);
  }

#ifdef _TME_SPARC_RECODE_VERIFY

  /* initialize the verifier: */
  _tme_sparc_recode_verify_init(ic);

#endif /* _TME_SPARC_RECODE_VERIFY */

#ifdef TME_RECODE_DEBUG

  /* dumps the ic flags/conds: */
  tme_sparc_recode_dump_ic(ic);

  /* dumps the instructions: */
  tme_sparc_recode_dump_insns(ic);
#endif

#else  /* !TME_HAVE_RECODE */
  /* unused: */
  ic = 0;
#endif /* !TME_HAVE_RECODE */

}
