/*--------------------------------------------------------------------*/
/*--- Cache simulation.                                            ---*/
/*---                                                     ct_sim.c ---*/
/*--------------------------------------------------------------------*/

/*
   This file is part of Cachegrind, a Valgrind skin for cache
   profiling programs.

   Copyright (C) 2002 Nicholas Nethercote
      njn25@cam.ac.uk

   Modified for Calltree-Skin, Josef Weidendorfer

   This program 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 2 of the
   License, or (at your option) any later version.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.

   The GNU General Public License is contained in the file COPYING.
*/

#include "ct_include.h"


/* Notes:
  - simulates a write-allocate cache
  - (block --> set) hash function uses simple bit selection
  - handling of references straddling two cache blocks:
      - counts as only one cache access (not two)
      - both blocks hit                  --> one hit
      - one block hits, the other misses --> one miss
      - both blocks miss                 --> one miss (not two)
*/


typedef struct {
   int          size;                   /* bytes */
   int          assoc;
   int          line_size;              /* bytes */
   int          sets;
   int          sets_min_1;
   int          assoc_bits;
   int          line_size_bits;
   int          tag_shift;
   char         desc_line[128];
   int*         tags;
} cache_t2;

/* By this point, the size/assoc/line_size has been checked. */
static void cachesim_initcache(cache_t config, cache_t2* c)
{
   int i;

   c->size      = config.size;
   c->assoc     = config.assoc;
   c->line_size = config.line_size;

   c->sets           = (c->size / c->line_size) / c->assoc;
   c->sets_min_1     = c->sets - 1;
   c->assoc_bits     = VG_(log2)(c->assoc);
   c->line_size_bits = VG_(log2)(c->line_size);
   c->tag_shift      = c->line_size_bits + VG_(log2)(c->sets);

   if (c->assoc == 1) {
      VG_(sprintf)(c->desc_line, "%d B, %d B, direct-mapped", 
                                 c->size, c->line_size);
   } else {
      VG_(sprintf)(c->desc_line, "%d B, %d B, %d-way associative",
                                 c->size, c->line_size, c->assoc);
   }

   c->tags = VG_(malloc)(sizeof(UInt) * c->sets * c->assoc);

   for (i = 0; i < c->sets * c->assoc; i++)
      c->tags[i] = 0;
}

#if 0
static void print_cache(cache_t2* c)
{
   UInt set, way, i;

   /* Note initialisation and update of 'i'. */
   for (i = 0, set = 0; set < c->sets; set++) {
      for (way = 0; way < c->assoc; way++, i++) {
         VG_(printf)("%8x ", c->tags[i]);
      }
      VG_(printf)("\n");
   }
}
#endif 

/* This is done as a macro rather than by passing in the cache_t2 as an 
 * arg because it slows things down by a small amount (3-5%) due to all 
 * that extra indirection. */

#define CACHESIM(L, MISS_TREATMENT, HIT_TREATMENT)                          \
/* The cache and associated bits and pieces. */                             \
static cache_t2 L;                                                          \
                                                                            \
static void cachesim_##L##_initcache(cache_t config)                        \
{                                                                           \
    cachesim_initcache(config, &L);                                         \
}                                                                           \
                                                                            \
static Int cachesim_##L##_doref(Addr a, UChar size)			    \
{                                                                           \
   register UInt set1 = ( a         >> L.line_size_bits) & (L.sets_min_1);  \
   register UInt set2 = ((a+size-1) >> L.line_size_bits) & (L.sets_min_1);  \
   register UInt tag  = a >> L.tag_shift;                                   \
   int i, j;                                                                \
   Bool is_miss = False;                                                    \
   int* set;                                                                \
                                                                            \
   /* First case: word entirely within line. */                             \
   if (set1 == set2) {                                                      \
                                                                            \
      /* Shifting is a bit faster than multiplying */                       \
      set = &(L.tags[set1 << L.assoc_bits]);                                \
                                                                            \
      /* This loop is unrolled for just the first case, which is the most */\
      /* common.  We can't unroll any further because it would screw up   */\
      /* if we have a direct-mapped (1-way) cache.                        */\
      if (tag == set[0]) {                                                  \
         HIT_TREATMENT;                                                     \
      }                                                                     \
      /* If the tag is one other than the MRU, move it into the MRU spot  */\
      /* and shuffle the rest down.                                       */\
      for (i = 1; i < L.assoc; i++) {                                       \
         if (tag == set[i]) {                                               \
            for (j = i; j > 0; j--) {                                       \
               set[j] = set[j - 1];                                         \
            }                                                               \
            set[0] = tag;                                                   \
            HIT_TREATMENT;                                                  \
         }                                                                  \
      }                                                                     \
                                                                            \
      /* A miss;  install this tag as MRU, shuffle rest down. */            \
      for (j = L.assoc - 1; j > 0; j--) {                                   \
         set[j] = set[j - 1];                                               \
      }                                                                     \
      set[0] = tag;                                                         \
      MISS_TREATMENT;                                                       \
                                                                            \
   /* Second case: word straddles two lines. */                             \
   /* Nb: this is a fast way of doing ((set1+1) % L.sets) */                \
   } else if (((set1 + 1) & (L.sets-1)) == set2) {                          \
      set = &(L.tags[set1 << L.assoc_bits]);                                \
      if (tag == set[0]) {                                                  \
         goto block2;                                                       \
      }                                                                     \
      for (i = 1; i < L.assoc; i++) {                                       \
         if (tag == set[i]) {                                               \
            for (j = i; j > 0; j--) {                                       \
               set[j] = set[j - 1];                                         \
            }                                                               \
            set[0] = tag;                                                   \
            goto block2;                                                    \
         }                                                                  \
      }                                                                     \
      for (j = L.assoc - 1; j > 0; j--) {                                   \
         set[j] = set[j - 1];                                               \
      }                                                                     \
      set[0] = tag;                                                         \
      is_miss = True;                                                       \
block2:                                                                     \
      set = &(L.tags[set2 << L.assoc_bits]);                                \
      if (tag == set[0]) {                                                  \
         goto miss_treatment;                                               \
      }                                                                     \
      for (i = 1; i < L.assoc; i++) {                                       \
         if (tag == set[i]) {                                               \
            for (j = i; j > 0; j--) {                                       \
               set[j] = set[j - 1];                                         \
            }                                                               \
            set[0] = tag;                                                   \
            goto miss_treatment;                                            \
         }                                                                  \
      }                                                                     \
      for (j = L.assoc - 1; j > 0; j--) {                                   \
         set[j] = set[j - 1];                                               \
      }                                                                     \
      set[0] = tag;                                                         \
      is_miss = True;                                                       \
miss_treatment:                                                             \
      if (is_miss) { MISS_TREATMENT; }                                      \
                                                                            \
   } else {                                                                 \
       VG_(printf)("addr: %x  size: %u  sets: %d %d", a, size, set1, set2); \
       VG_(skin_panic)("item straddles more than two cache sets");          \
   }                                                                        \
   HIT_TREATMENT;                                                           \
}


#if !CT_HWPREFETCH

CACHESIM(L2, { return 2; }, { return 1; } );
CACHESIM(I1, { return cachesim_L2_doref(a, size); }, { return 0; } );
CACHESIM(D1, { return cachesim_L2_doref(a, size); }, { return 0; } );

#else

static Int prefetch_L2_doref(Addr a, UChar size);
CACHESIM(L2, { return 2; }, { return 1; } );
CACHESIM(I1, { return prefetch_L2_doref(a, size); }, { return 0; } );
CACHESIM(D1, { return prefetch_L2_doref(a, size); }, { return 0; } );

static ULong prefetch_up = 0;
static ULong prefetch_down = 0;

#define PF_STREAMS  8
#define PF_PAGEBITS 12

static UInt pf_lastblock[PF_STREAMS];
static Int  pf_seqblocks[PF_STREAMS];

static void prefetch_init()
{
  int i;
  for(i=0;i<PF_STREAMS;i++)
    pf_lastblock[i] = pf_seqblocks[i] = 0;
}

/*
 * HW Prefetch emulation
 * Start prefetching when detecting sequential access to 3 memory blocks.
 */
static Int prefetch_L2_doref(Addr a, UChar size)
{
  UInt stream, block;

  if (!SK_(clo).simulate_hwpref)
    return cachesim_L2_doref(a, size);

  stream = (a >> PF_PAGEBITS) % PF_STREAMS;
  block = ( a >> L2.line_size_bits);

  if (block != pf_lastblock[stream]) {
    if (pf_seqblocks[stream] == 0) {
      if (pf_lastblock[stream] +1 == block) pf_seqblocks[stream]++;
      else if (pf_lastblock[stream] -1 == block) pf_seqblocks[stream]--;
    }
    else if (pf_seqblocks[stream] >0) {
      if (pf_lastblock[stream] +1 == block) {
	pf_seqblocks[stream]++;
	if (pf_seqblocks[stream] >= 2) {
	  prefetch_up++;
	  cachesim_L2_doref(a + 5 * L2.line_size,1);
	}
      }
      else pf_seqblocks[stream] = 0;
    }
    else if (pf_seqblocks[stream] <0) {
      if (pf_lastblock[stream] -1 == block) {
	pf_seqblocks[stream]--;
	if (pf_seqblocks[stream] <= -2) {
	  prefetch_down++;
	  cachesim_L2_doref(a - 5 * L2.line_size,1);
	}
      }
      else pf_seqblocks[stream] = 0;
    }
    pf_lastblock[stream] = block;
  }

  return cachesim_L2_doref(a, size);
}  

#endif /* CT_HWPREFETCH */


/*------------------------------------------------------------*/
/*--- Automagic cache initialisation stuff                 ---*/
/*------------------------------------------------------------*/

#define UNDEFINED_CACHE     ((cache_t) { -1, -1, -1 }) 

static cache_t clo_I1_cache = UNDEFINED_CACHE;
static cache_t clo_D1_cache = UNDEFINED_CACHE;
static cache_t clo_L2_cache = UNDEFINED_CACHE;

/* All CPUID info taken from sandpile.org/a32/cpuid.htm */
/* Probably only works for Intel and AMD chips, and probably only for some of
 * them. 
 */

static __inline__ void cpuid(Int n, UInt *a, UInt *b, UInt *c, UInt *d)
{
   __asm__ __volatile__ (
    "cpuid"
    : "=a" (*a), "=b" (*b), "=c" (*c), "=d" (*d)      /* output */
    : "0" (n)         /* input */
    );
}

static void micro_ops_warn(Int actual_size, Int used_size, Int line_size)
{
    VG_(message)(Vg_DebugMsg, 
       "warning: Pentium with %d K micro-op instruction trace cache", 
       actual_size);
    VG_(message)(Vg_DebugMsg, 
       "         Simulating a %d KB cache with %d B lines", 
       used_size, line_size);
}

/* Intel method is truly wretched.  We have to do an insane indexing into an
 * array of pre-defined configurations for various parts of the memory
 * hierarchy. 
 */
static
Int Intel_cache_info(Int level, cache_t* I1c, cache_t* D1c, cache_t* L2c)
{
   UChar info[16];
   Int   i, trials;
   Bool  L2_found = False;

   if (level < 2) {
      VG_(message)(Vg_DebugMsg, 
         "warning: CPUID level < 2 for Intel processor (%d)", 
         level);
      return -1;
   }

   cpuid(2, (Int*)&info[0], (Int*)&info[4], 
            (Int*)&info[8], (Int*)&info[12]);
   trials  = info[0] - 1;   /* AL register - bits 0..7 of %eax */
   info[0] = 0x0;           /* reset AL */

   if (0 != trials) {
      VG_(message)(Vg_DebugMsg, 
         "warning: non-zero CPUID trials for Intel processor (%d)",
         trials);
      return -1;
   }

   for (i = 0; i < 16; i++) {

      switch (info[i]) {

      case 0x0:       /* ignore zeros */
          break;
          
      /* TLB info, ignore */
      case 0x01: case 0x02: case 0x03: case 0x04:
      case 0x50: case 0x51: case 0x52: case 0x5b: case 0x5c: case 0x5d:
      case 0xb0: case 0xb3:
          break;      

      case 0x06: *I1c = (cache_t) {  8, 4, 32 }; break;
      case 0x08: *I1c = (cache_t) { 16, 4, 32 }; break;
      case 0x30: *I1c = (cache_t) { 32, 8, 64 }; break;

      case 0x0a: *D1c = (cache_t) {  8, 2, 32 }; break;
      case 0x0c: *D1c = (cache_t) { 16, 4, 32 }; break;
      case 0x2c: *D1c = (cache_t) {  32, 8, 64 }; break;

      /* IA-64 info -- panic! */
      case 0x10: case 0x15: case 0x1a: 
      case 0x88: case 0x89: case 0x8a: case 0x8d:
      case 0x90: case 0x96: case 0x9b:
         VG_(message)(Vg_DebugMsg,
            "error: IA-64 cache stats!  Cachegrind doesn't run on IA-64...");
         VG_(skin_panic)("IA-64 detected");

      case 0x22: case 0x23: case 0x25: case 0x29: 
          VG_(message)(Vg_DebugMsg, 
             "warning: L3 cache detected but ignored\n");
          break;

      /* These are sectored, whatever that means */
      case 0x39: *L2c = (cache_t) {  128, 4, 64 }; L2_found = True; break;
      case 0x3c: *L2c = (cache_t) {  256, 4, 64 }; L2_found = True; break;

      /* If a P6 core, this means "no L2 cache".  
         If a P4 core, this means "no L3 cache".
         We don't know what core it is, so don't issue a warning.  To detect
         a missing L2 cache, we use 'L2_found'. */
      case 0x40:
          break;

      case 0x41: *L2c = (cache_t) {  128, 4, 32 }; L2_found = True; break;
      case 0x42: *L2c = (cache_t) {  256, 4, 32 }; L2_found = True; break;
      case 0x43: *L2c = (cache_t) {  512, 4, 32 }; L2_found = True; break;
      case 0x44: *L2c = (cache_t) { 1024, 4, 32 }; L2_found = True; break;
      case 0x45: *L2c = (cache_t) { 2048, 4, 32 }; L2_found = True; break;

      /* These are sectored, whatever that means */
      case 0x66: *D1c = (cache_t) {  8, 4, 64 };  break;      /* sectored */
      case 0x67: *D1c = (cache_t) { 16, 4, 64 };  break;      /* sectored */
      case 0x68: *D1c = (cache_t) { 32, 4, 64 };  break;      /* sectored */

      /* HACK ALERT: Instruction trace cache -- capacity is micro-ops based.
       * conversion to byte size is a total guess;  treat the 12K and 16K
       * cases the same since the cache byte size must be a power of two for
       * everything to work!.  Also guessing 32 bytes for the line size... 
       */
      case 0x70:    /* 12K micro-ops, 8-way */
         *I1c = (cache_t) { 16, 8, 32 };  
         micro_ops_warn(12, 16, 32);
         break;  
      case 0x71:    /* 16K micro-ops, 8-way */
         *I1c = (cache_t) { 16, 8, 32 };  
         micro_ops_warn(16, 16, 32); 
         break;  
      case 0x72:    /* 32K micro-ops, 8-way */
         *I1c = (cache_t) { 32, 8, 32 };  
         micro_ops_warn(32, 32, 32); 
         break;  

      /* These are sectored, whatever that means */
      case 0x79: *L2c = (cache_t) {  128, 8,  64 }; L2_found = True;  break;
      case 0x7a: *L2c = (cache_t) {  256, 8,  64 }; L2_found = True;  break;
      case 0x7b: *L2c = (cache_t) {  512, 8,  64 }; L2_found = True;  break;
      case 0x7c: *L2c = (cache_t) { 1024, 8,  64 }; L2_found = True;  break;
      case 0x7e: *L2c = (cache_t) {  256, 8, 128 }; L2_found = True;  break;

      case 0x81: *L2c = (cache_t) {  128, 8, 32 };  L2_found = True;  break;
      case 0x82: *L2c = (cache_t) {  256, 8, 32 };  L2_found = True;  break;
      case 0x83: *L2c = (cache_t) {  512, 8, 32 };  L2_found = True;  break;
      case 0x84: *L2c = (cache_t) { 1024, 8, 32 };  L2_found = True;  break;
      case 0x85: *L2c = (cache_t) { 2048, 8, 32 };  L2_found = True;  break;
      case 0x86: *L2c = (cache_t) {  512, 4, 64 };  L2_found = True;  break;
      case 0x87: *L2c = (cache_t) { 1024, 8, 64 };  L2_found = True;  break;

      default:
          VG_(message)(Vg_DebugMsg, 
             "warning: Unknown Intel cache config value "
             "(0x%x), ignoring", info[i]);
          break;
      }
   }

   if (!L2_found)
      VG_(message)(Vg_DebugMsg, 
         "warning: L2 cache not installed, ignore L2 results.");

   return 0;
}

/* AMD method is straightforward, just extract appropriate bits from the
 * result registers.
 *
 * Bits, for D1 and I1:
 *  31..24  data L1 cache size in KBs    
 *  23..16  data L1 cache associativity (FFh=full)    
 *  15.. 8  data L1 cache lines per tag    
 *   7.. 0  data L1 cache line size in bytes
 *
 * Bits, for L2:
 *  31..16  unified L2 cache size in KBs
 *  15..12  unified L2 cache associativity (0=off, FFh=full)
 *  11.. 8  unified L2 cache lines per tag    
 *   7.. 0  unified L2 cache line size in bytes
 *
 * #3  The AMD K7 processor's L2 cache must be configured prior to relying 
 *     upon this information. (Whatever that means -- njn)
 *
 * Also, according to Cyrille Chepelov, Duron stepping A0 processors (model
 * 0x630) have a bug and misreport their L2 size as 1KB (it's really 64KB),
 * so we detect that.
 * 
 * Returns 0 on success, non-zero on failure.
 */
static
Int AMD_cache_info(cache_t* I1c, cache_t* D1c, cache_t* L2c)
{
   UInt ext_level;
   Int dummy, model;
   Int I1i, D1i, L2i;
   
   cpuid(0x80000000, &ext_level, &dummy, &dummy, &dummy);

   if (0 == (ext_level & 0x80000000) || ext_level < 0x80000006) {
      VG_(message)(Vg_UserMsg, 
         "warning: ext_level < 0x80000006 for AMD processor (0x%x)", 
         ext_level);
      return -1;
   }

   cpuid(0x80000005, &dummy, &dummy, &D1i, &I1i);
   cpuid(0x80000006, &dummy, &dummy, &L2i, &dummy);

   cpuid(0x1, &model, &dummy, &dummy, &dummy);
   /*VG_(message)(Vg_UserMsg,"CPU model %04x",model);*/

   /* Check for Duron bug */
   if (model == 0x630) {
      VG_(message)(Vg_UserMsg,
         "Buggy Duron stepping A0. Assuming L2 size=65536 bytes");
      L2i = (64 << 16) | (L2i & 0xffff);
   }

   D1c->size      = (D1i >> 24) & 0xff;
   D1c->assoc     = (D1i >> 16) & 0xff;
   D1c->line_size = (D1i >>  0) & 0xff;

   I1c->size      = (I1i >> 24) & 0xff;
   I1c->assoc     = (I1i >> 16) & 0xff;
   I1c->line_size = (I1i >>  0) & 0xff;

   L2c->size      = (L2i >> 16) & 0xffff; /* Nb: different bits used for L2 */
   L2c->assoc     = (L2i >> 12) & 0xf;
   L2c->line_size = (L2i >>  0) & 0xff;

   return 0;
}

static jmp_buf cpuid_jmpbuf;

static
void cpuid_SIGILL_handler(int signum)
{
   __builtin_longjmp(cpuid_jmpbuf, 1);
}

static 
Int get_caches_from_CPUID(cache_t* I1c, cache_t* D1c, cache_t* L2c)
{
   Int  level, res, ret;
   Char vendor_id[13];
   vki_ksigaction sigill_new, sigill_saved;

   /* Install own SIGILL handler */
   sigill_new.ksa_handler  = cpuid_SIGILL_handler;
   sigill_new.ksa_flags    = 0;
   //sigill_new.ksa_restorer = NULL;
   res = VG_(ksigemptyset)( &sigill_new.ksa_mask );
   CT_ASSERT(res == 0);

   res = VG_(ksigaction)( VKI_SIGILL, &sigill_new, &sigill_saved );
   CT_ASSERT(res == 0);

   /* Trap for illegal instruction, in case it's a really old processor that
    * doesn't support CPUID. */
   if (__builtin_setjmp(cpuid_jmpbuf) == 0) {
      cpuid(0, &level, (int*)&vendor_id[0], 
                       (int*)&vendor_id[8], (int*)&vendor_id[4]);    
      vendor_id[12] = '\0';

      /* Restore old SIGILL handler */
      res = VG_(ksigaction)( VKI_SIGILL, &sigill_saved, NULL );
      CT_ASSERT(res == 0);

   } else  {
      VG_(message)(Vg_DebugMsg, "CPUID instruction not supported");

      /* Restore old SIGILL handler */
      res = VG_(ksigaction)( VKI_SIGILL, &sigill_saved, NULL );
      CT_ASSERT(res == 0);
      return -1;
   }

   if (0 == level) {
      VG_(message)(Vg_DebugMsg, "CPUID level is 0, early Pentium?\n");
      return -1;
   }

   /* Only handling Intel and AMD chips... no Cyrix, Transmeta, etc */
   if (0 == VG_(strcmp)(vendor_id, "GenuineIntel")) {
      ret = Intel_cache_info(level, I1c, D1c, L2c);

   } else if (0 == VG_(strcmp)(vendor_id, "AuthenticAMD")) {
      ret = AMD_cache_info(I1c, D1c, L2c);

   } else if (0 == VG_(strcmp)(vendor_id, "CentaurHauls")) {
      /* Total kludge.  Pretend to be a VIA Nehemiah. */
      D1c->size      = 64;
      D1c->assoc     = 16;
      D1c->line_size = 16;
      I1c->size      = 64;
      I1c->assoc     = 4;
      I1c->line_size = 16;
      L2c->size      = 64;
      L2c->assoc     = 16;
      L2c->line_size = 16;
      ret = 0;

   } else {
      VG_(message)(Vg_DebugMsg, "CPU vendor ID not recognised (%s)",
                   vendor_id);
      return -1;
   }

   /* Successful!  Convert sizes from KB to bytes */
   I1c->size *= 1024;
   D1c->size *= 1024;
   L2c->size *= 1024;
      
   return ret;
}

/* Checks cache config is ok;  makes it so if not. */
static 
void check_cache(cache_t* cache, cache_t* dflt, Char *name)
{
   /* First check they're all powers of two */
   if (-1 == VG_(log2)(cache->size)) {
      VG_(message)(Vg_UserMsg,
         "warning: %s size of %dB not a power of two; "
         "defaulting to %dB", name, cache->size, dflt->size);
      cache->size = dflt->size;
   }

   if (-1 == VG_(log2)(cache->assoc)) {
      VG_(message)(Vg_UserMsg,
         "warning: %s associativity of %d not a power of two; "
         "defaulting to %d-way", name, cache->assoc, dflt->assoc);
      cache->assoc = dflt->assoc;
   }

   if (-1 == VG_(log2)(cache->line_size)) {
      VG_(message)(Vg_UserMsg,
         "warning: %s line size of %dB not a power of two; "
         "defaulting to %dB", 
         name, cache->line_size, dflt->line_size);
      cache->line_size = dflt->line_size;
   }

   /* Then check line size >= 16 -- any smaller and a single instruction could
    * straddle three cache lines, which breaks a simulation assertion and is
    * stupid anyway. */
   if (cache->line_size < MIN_LINE_SIZE) {
      VG_(message)(Vg_UserMsg,
         "warning: %s line size of %dB too small; "
         "increasing to %dB", name, cache->line_size, MIN_LINE_SIZE);
      cache->line_size = MIN_LINE_SIZE;
   }

   /* Then check cache size > line size (causes seg faults if not). */
   if (cache->size <= cache->line_size) {
      VG_(message)(Vg_UserMsg,
         "warning: %s cache size of %dB <= line size of %dB; "
         "increasing to %dB", name, cache->size, cache->line_size,
                              cache->line_size * 2);
      cache->size = cache->line_size * 2;
   }

   /* Then check assoc <= (size / line size) (seg faults otherwise). */
   if (cache->assoc > (cache->size / cache->line_size)) {
      VG_(message)(Vg_UserMsg,
         "warning: %s associativity > (size / line size); "
         "increasing size to %dB", 
            name, cache->assoc * cache->line_size);
      cache->size = cache->assoc * cache->line_size;
   }
}

/* On entry, args are undefined.  Fill them with any info from the
 * command-line, then fill in any remaining with CPUID instruction if possible,
 * otherwise use defaults.  Then check them and fix if not ok. */
static 
void get_caches(cache_t* I1c, cache_t* D1c, cache_t* L2c)
{
   /* Defaults are for a model 3 or 4 Athlon */
   cache_t I1_dflt = (cache_t) {  65536, 2, 64 };
   cache_t D1_dflt = (cache_t) {  65536, 2, 64 };
   cache_t L2_dflt = (cache_t) { 262144, 8, 64 };

#define CMD_LINE_DEFINED(L)            \
   (-1 != clo_##L##_cache.size  ||     \
    -1 != clo_##L##_cache.assoc ||     \
    -1 != clo_##L##_cache.line_size)

   *I1c = clo_I1_cache;
   *D1c = clo_D1_cache;
   *L2c = clo_L2_cache;

   /* If any undefined on command-line, try CPUID */
   if (! CMD_LINE_DEFINED(I1) ||
       ! CMD_LINE_DEFINED(D1) ||
       ! CMD_LINE_DEFINED(L2)) { 

      /* Overwrite CPUID result for any cache defined on command-line */
      if (0 == get_caches_from_CPUID(I1c, D1c, L2c)) {
   
         if (CMD_LINE_DEFINED(I1)) *I1c = clo_I1_cache;
         if (CMD_LINE_DEFINED(D1)) *D1c = clo_D1_cache;
         if (CMD_LINE_DEFINED(L2)) *L2c = clo_L2_cache;

      /* CPUID failed, use defaults for each undefined by command-line */
      } else {
         VG_(message)(Vg_DebugMsg, 
                      "Couldn't detect cache configuration, using one "
                      "or more defaults ");

         *I1c = (CMD_LINE_DEFINED(I1) ? clo_I1_cache : I1_dflt);
         *D1c = (CMD_LINE_DEFINED(D1) ? clo_D1_cache : D1_dflt);
         *L2c = (CMD_LINE_DEFINED(L2) ? clo_L2_cache : L2_dflt);
      }
   }
#undef CMD_LINE_DEFINED

   check_cache(I1c, &I1_dflt, "I1");
   check_cache(D1c, &D1_dflt, "D1");
   check_cache(L2c, &L2_dflt, "L2");

   if (VG_(clo_verbosity) > 1) {
      VG_(message)(Vg_UserMsg, "Cache configuration used:");
      VG_(message)(Vg_UserMsg, "  I1: %dB, %d-way, %dB lines",
                               I1c->size, I1c->assoc, I1c->line_size);
      VG_(message)(Vg_UserMsg, "  D1: %dB, %d-way, %dB lines",
                               D1c->size, D1c->assoc, D1c->line_size);
      VG_(message)(Vg_UserMsg, "  L2: %dB, %d-way, %dB lines",
                               L2c->size, L2c->assoc, L2c->line_size);
   }
}




void SK_(cachesim_init)(void)
{
  cache_t I1c, D1c, L2c; 

  get_caches(&I1c, &D1c, &L2c);

  cachesim_I1_initcache(I1c);
  cachesim_D1_initcache(D1c);
  cachesim_L2_initcache(L2c);
#if CT_HWPREFETCH
  prefetch_init();
#endif
}

void SK_(cachesim_getdesc)(Char* buf)
{
  Int p;
  p = VG_(sprintf)(buf, "\ndesc: I1 cache: %s\n", I1.desc_line);
  p += VG_(sprintf)(buf+p, "desc: D1 cache: %s\n", D1.desc_line);
  VG_(sprintf)(buf+p, "desc: L2 cache: %s\n", L2.desc_line);
}

/*------------------------------------------------------------*/
/*--- Setup for Event set.                                 ---*/
/*------------------------------------------------------------*/

struct event_sets SK_(sets);

/* Offset to events in event set, used in log_* functions */
static Int off_D0_Ir;
static Int off_D1r_Ir;
static Int off_D1r_Dr;
static Int off_D1w_Ir;
static Int off_D1w_Dw;
static Int off_D2_Ir;
static Int off_D2_Dr;
static Int off_D2_Dw;


void SK_(init_eventsets)(Bool do_cache, Int max_user)
{
  EventType * e1, *e2, *e3;
  EventSet *Ir, *Dr, *Dw;
  EventSet *D0, *D1r, *D1w, *D2;
  EventSet *sim, *full;

  if (do_cache) {
    Ir = SK_(get_eventset)("Ir", 3);
    e1 = SK_(register_eventtype)("Ir");
    e2 = SK_(register_eventtype)("I1mr");
    e3 = SK_(register_eventtype)("I2mr");
    SK_(add_dep_event3)(Ir, e1,e2,e3);
    
    Dr = SK_(get_eventset)("Dr", 3);
    e1 = SK_(register_eventtype)("Dr");
    e2 = SK_(register_eventtype)("D1mr");
    e3 = SK_(register_eventtype)("D2mr");
    SK_(add_dep_event3)(Dr, e1,e2,e3);
    
    Dw = SK_(get_eventset)("Dw", 3);
    e1 = SK_(register_eventtype)("Dw");
    e2 = SK_(register_eventtype)("D1mw");
    e3 = SK_(register_eventtype)("D2mw");
    SK_(add_dep_event3)(Dw, e1,e2,e3);
  }
  else {
    Ir = SK_(get_eventset)("Ir", 1);
    e1 = SK_(register_eventtype)("Ir");
    SK_(add_eventtype)(Ir, e1);
    Dr = 0;
    Dw = 0;
  }

  D0 = Ir;
  off_D0_Ir = 0;

  D1r        = SK_(get_eventset)("D1r", 6);
  off_D1r_Ir = SK_(add_eventset)(D1r, Ir);
  off_D1r_Dr = SK_(add_eventset)(D1r, Dr);

  D1w = SK_(get_eventset)("D1w", 6);
  off_D1w_Ir   = SK_(add_eventset)(D1w, Ir);
  off_D1w_Dw   = SK_(add_eventset)(D1w, Dw);

  D2  = SK_(get_eventset)("D2", 9);
  off_D2_Ir    = SK_(add_eventset)(D2, Ir);
  off_D2_Dr    = SK_(add_eventset)(D2, Dr);
  off_D2_Dw    = SK_(add_eventset)(D2, Dw);

  sim                     = SK_(get_eventset)("sim", 9);
  SK_(sets).off_sim_Ir   = SK_(add_eventset)(sim, Ir);
  SK_(sets).off_sim_Dr   = SK_(add_eventset)(sim, Dr);
  SK_(sets).off_sim_Dw   = SK_(add_eventset)(sim, Dw);

  if (SK_(clo).collect_alloc) max_user += 2;
  if (SK_(clo).collect_systime) max_user += 2;

  full = SK_(get_eventset)("full", sim->size + max_user);
  SK_(add_eventset)(full, sim);
  SK_(sets).off_full_Ir   = SK_(sets).off_sim_Ir;
  SK_(sets).off_full_Dr   = SK_(sets).off_sim_Dr;
  SK_(sets).off_full_Dw   = SK_(sets).off_sim_Dw;

  SK_(sets).Ir  = Ir;
  SK_(sets).Dr  = Dr;
  SK_(sets).Dw  = Dw;

  SK_(sets).D0  = D0;
  SK_(sets).D1r = D1r;
  SK_(sets).D1w = D1w;
  SK_(sets).D2  = D2;

  SK_(sets).sim  = sim;
  SK_(sets).full = full;

  /* Not-existing events are silently ignored */
  SK_(dumpmap) = SK_(get_eventmapping)(full);
  SK_(append_event)(SK_(dumpmap), "Ir");
  SK_(append_event)(SK_(dumpmap), "Dr");
  SK_(append_event)(SK_(dumpmap), "Dw");
  SK_(append_event)(SK_(dumpmap), "I1mr");
  SK_(append_event)(SK_(dumpmap), "D1mr");
  SK_(append_event)(SK_(dumpmap), "D1mw");
  SK_(append_event)(SK_(dumpmap), "I2mr");
  SK_(append_event)(SK_(dumpmap), "D2mr");
  SK_(append_event)(SK_(dumpmap), "D2mw");  

  if (SK_(clo).collect_alloc) {
    e1 = SK_(register_eventtype)("allocCount");
    e2 = SK_(register_eventtype)("allocSize");
    SK_(sets).off_full_user =  SK_(add_dep_event2)(full, e1,e2);

    SK_(append_event)(SK_(dumpmap), "allocCount");
    SK_(append_event)(SK_(dumpmap), "allocSize");
  }

  if (SK_(clo).collect_systime) {
    e1 = SK_(register_eventtype)("sysCount");
    e2 = SK_(register_eventtype)("sysTime");
    SK_(sets).off_full_systime =  SK_(add_dep_event2)(full, e1,e2);
    
    SK_(append_event)(SK_(dumpmap), "sysCount");
    SK_(append_event)(SK_(dumpmap), "sysTime");
  }
}


void SK_(add_and_zero_Dx)(EventSet* es, SimCost dst, ULong* cost)
{
  /* FIXME: This is hardcoded... */
  if (es == SK_(sets).D0) {
    SK_(add_and_zero_cost)( SK_(sets).Ir, dst + SK_(sets).off_sim_Ir,
			    cost + off_D0_Ir);
  }
  else if (es == SK_(sets).D1r) {
    SK_(add_and_zero_cost)( SK_(sets).Ir, dst + SK_(sets).off_sim_Ir,
			    cost + off_D1r_Ir);
    SK_(add_and_zero_cost)( SK_(sets).Ir, dst + SK_(sets).off_sim_Dr,
			    cost + off_D1r_Dr);
  }
  else if (es == SK_(sets).D1w) {
    SK_(add_and_zero_cost)( SK_(sets).Ir, dst + SK_(sets).off_sim_Ir,
			    cost + off_D1w_Ir);
    SK_(add_and_zero_cost)( SK_(sets).Ir, dst + SK_(sets).off_sim_Dw,
			    cost + off_D1w_Dw);
  }
  else {
    CT_ASSERT(es == SK_(sets).D2);
    SK_(add_and_zero_cost)( SK_(sets).Ir, dst + SK_(sets).off_sim_Ir,
			    cost + off_D2_Ir);
    SK_(add_and_zero_cost)( SK_(sets).Ir, dst + SK_(sets).off_sim_Dr,
			    cost + off_D2_Dr);
    SK_(add_and_zero_cost)( SK_(sets).Ir, dst + SK_(sets).off_sim_Dw,
			    cost + off_D2_Dw);
  }
}




/*------------------------------------------------------------*/
/*--- Helper functions called by instrumented code         ---*/
/*------------------------------------------------------------*/


static __inline__
void inc_costs(int miss, ULong* c1, ULong* c2)
{
   c1[0]++;
   c2[0]++;
   if (miss == 0) return;

   c1[1]++;
   c2[1]++;
   if (miss==1) return;

   c1[2]++;
   c2[2]++;

   CT_ASSERT(miss==2);
}

/* Following global vars are setup before by setup_bbcc():
 * - Addr   bb_base     (instruction start address of original BB)
 * - ULong* cost_base   (start of cost array for BB)
 * - BBCC*  nonskipped  (only != 0 when in a function not skipped)
 */

__attribute__ ((regparm (1)))
void SK_(log_0D)(InstrInfo* ii)
{
   Int missIr;

   VGP_PUSHCC(VgpCacheSimulate);
   
   CT_DEBUG(6,"log_0D:  iaddr=%p, isize=%u\n",
               SK_(bb_base) + ii->instr_offset, ii->instr_size);
   missIr = cachesim_I1_doref(SK_(bb_base) + ii->instr_offset,
			      ii->instr_size);
   if (SK_(current_state).collect) {
     ULong* cost_Ir;

     if (SK_(current_state).nonskipped)
       cost_Ir = SK_(current_state).nonskipped->skipped +
	 SK_(sets).off_full_Ir;
     else
       cost_Ir = SK_(cost_base) + ii->cost_offset + off_D0_Ir;
       
     inc_costs(missIr, cost_Ir, 
	       SK_(current_state).cost + SK_(sets).off_full_Ir );
   }
   VGP_POPCC(VgpCacheSimulate);
}

__attribute__ ((regparm (2)))
void SK_(log_1Dr)(InstrInfo* ii, Addr data)
{
  Int missIr, missDr;

  VGP_PUSHCC(VgpCacheSimulate);

  CT_DEBUG(6,"log_1Dr: iaddr=%p, isize=%u, daddr=%p, dsize=%u\n",
	   SK_(bb_base) + ii->instr_offset, ii->instr_size,
	   data, ii->data_size);

  missIr = cachesim_I1_doref(SK_(bb_base) + ii->instr_offset,
			     ii->instr_size);
  missDr = cachesim_D1_doref(data, ii->data_size);
  if (SK_(current_state).collect) {
    ULong *cost_Ir, *cost_Dr;

    if (SK_(current_state).nonskipped) {
      cost_Ir = SK_(current_state).nonskipped->skipped + SK_(sets).off_full_Ir;
      cost_Dr = SK_(current_state).nonskipped->skipped + SK_(sets).off_full_Dr;
    }
    else {
      cost_Ir = SK_(cost_base) + ii->cost_offset + off_D1r_Ir;
      cost_Dr = SK_(cost_base) + ii->cost_offset + off_D1r_Dr;
    }
       
    inc_costs(missIr, cost_Ir, 
	      SK_(current_state).cost + SK_(sets).off_full_Ir );
    inc_costs(missDr, cost_Dr,
	      SK_(current_state).cost + SK_(sets).off_full_Dr );
  }

  VGP_POPCC(VgpCacheSimulate);
}

__attribute__ ((regparm (2)))
void SK_(log_1Dw)(InstrInfo* ii, Addr data)
{
  Int missIr, missDw;

  VGP_PUSHCC(VgpCacheSimulate);

  CT_DEBUG(6,"log_1Dw: iaddr=%p, isize=%u, daddr=%p, dsize=%u\n",
	   SK_(bb_base) + ii->instr_offset, ii->instr_size,
	   data, ii->data_size);

  missIr = cachesim_I1_doref(SK_(bb_base) + ii->instr_offset,
			     ii->instr_size);
  missDw = cachesim_D1_doref(data, ii->data_size);
  if (SK_(current_state).collect) {
    ULong *cost_Ir, *cost_Dw;

    if (SK_(current_state).nonskipped) {
      cost_Ir = SK_(current_state).nonskipped->skipped + SK_(sets).off_sim_Ir;
      cost_Dw = SK_(current_state).nonskipped->skipped + SK_(sets).off_sim_Dw;
    }
    else {
      cost_Ir = SK_(cost_base) + ii->cost_offset + off_D1w_Ir;
      cost_Dw = SK_(cost_base) + ii->cost_offset + off_D1w_Dw;
    }
       
    inc_costs(missIr, cost_Ir,
	      SK_(current_state).cost + SK_(sets).off_full_Ir );
    inc_costs(missDw, cost_Dw,
	      SK_(current_state).cost + SK_(sets).off_full_Dw );
  }

  VGP_POPCC(VgpCacheSimulate);
}

__attribute__ ((regparm (3)))
void SK_(log_2D)(InstrInfo* ii, Addr data1, Addr data2)
{
  Int missIr, missDr, missDw;
  
  VGP_PUSHCC(VgpCacheSimulate);

  CT_DEBUG(6,"log_D2:  iaddr=%p, isize=%u, daddr1=%p, daddr2=%p, dsize=%u\n",
	   SK_(bb_base) + ii->instr_offset, ii->instr_size,
	   data1, data2, ii->data_size);

  missIr = cachesim_I1_doref(SK_(bb_base) + ii->instr_offset,
			     ii->instr_size);
  missDr = cachesim_D1_doref(data1, ii->data_size);
  missDw = cachesim_D1_doref(data2, ii->data_size);

  if (SK_(current_state).collect) {
    ULong *cost_Ir, *cost_Dr, *cost_Dw;

    if (SK_(current_state).nonskipped) {
      cost_Ir = SK_(current_state).nonskipped->skipped + SK_(sets).off_sim_Ir;
      cost_Dr = SK_(current_state).nonskipped->skipped + SK_(sets).off_sim_Dr;
      cost_Dw = SK_(current_state).nonskipped->skipped + SK_(sets).off_sim_Dw;
    }
    else {
      cost_Ir = SK_(cost_base) + ii->cost_offset + off_D2_Ir;
      cost_Dr = SK_(cost_base) + ii->cost_offset + off_D2_Dr;
      cost_Dw = SK_(cost_base) + ii->cost_offset + off_D2_Dw;
    }
       
    inc_costs(missIr, cost_Ir, SK_(current_state).cost + SK_(sets).off_full_Ir );
    inc_costs(missDr, cost_Dr, SK_(current_state).cost + SK_(sets).off_full_Dr );
    inc_costs(missDw, cost_Dw, SK_(current_state).cost + SK_(sets).off_full_Dw );
  }
  VGP_POPCC(VgpCacheSimulate);
}





/*--------------------------------------------------------------------*/
/*--- end                                                 ct_sim.c ---*/
/*--------------------------------------------------------------------*/



syntax highlighted by Code2HTML, v. 0.9.1