| |
| /*--------------------------------------------------------------------*/ |
| /*--- Cache simulation. ---*/ |
| /*--- sim.c ---*/ |
| /*--------------------------------------------------------------------*/ |
| |
| /* |
| This file is part of Callgrind, a Valgrind tool for call graph |
| profiling programs. |
| |
| Copyright (C) 2003-2005, Josef Weidendorfer (Josef.Weidendorfer@gmx.de) |
| |
| This tool is derived from and contains code from Cachegrind |
| Copyright (C) 2002-2009 Nicholas Nethercote (njn@valgrind.org) |
| |
| 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 "global.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) |
| */ |
| |
| /* Cache configuration */ |
| #include "cg_arch.h" |
| |
| /* additional structures for cache use info, separated |
| * according usage frequency: |
| * - line_loaded : pointer to cost center of instruction |
| * which loaded the line into cache. |
| * Needed to increment counters when line is evicted. |
| * - line_use : updated on every access |
| */ |
| typedef struct { |
| UInt count; |
| UInt mask; /* e.g. for 64Byte line size 1bit/2Byte */ |
| } line_use; |
| |
| typedef struct { |
| Addr memline, iaddr; |
| line_use* dep_use; /* point to higher-level cacheblock for this memline */ |
| ULong* use_base; |
| } line_loaded; |
| |
| /* Cache state */ |
| typedef struct { |
| char* name; |
| int size; /* bytes */ |
| int assoc; |
| int line_size; /* bytes */ |
| Bool sectored; /* prefetch nearside cacheline on read */ |
| int sets; |
| int sets_min_1; |
| int line_size_bits; |
| int tag_shift; |
| UWord tag_mask; |
| char desc_line[128]; |
| UWord* tags; |
| |
| /* for cache use */ |
| int line_size_mask; |
| int* line_start_mask; |
| int* line_end_mask; |
| line_loaded* loaded; |
| line_use* use; |
| } cache_t2; |
| |
| /* |
| * States of flat caches in our model. |
| * We use a 2-level hierarchy, |
| */ |
| static cache_t2 I1, D1, L2; |
| |
| /* Lower bits of cache tags are used as flags for a cache line */ |
| #define CACHELINE_FLAGMASK (MIN_LINE_SIZE-1) |
| #define CACHELINE_DIRTY 1 |
| |
| |
| /* Cache simulator Options */ |
| static Bool clo_simulate_writeback = False; |
| static Bool clo_simulate_hwpref = False; |
| static Bool clo_simulate_sectors = False; |
| static Bool clo_collect_cacheuse = False; |
| |
| /* Following global vars are setup before by |
| * setup_bbcc()/cachesim_after_bbsetup(): |
| * |
| * - 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) |
| */ |
| |
| /* Offset to events in event set, used in log_* functions |
| * <off_EventSet_BasicEventSet>: offset where basic set is found |
| */ |
| static Int off_UIr_Ir; |
| static Int off_UIrDr_Ir, off_UIrDr_Dr; |
| static Int off_UIrDrDw_Ir, off_UIrDrDw_Dr, off_UIrDrDw_Dw; |
| static Int off_UIrDw_Ir, off_UIrDw_Dw; |
| static Int off_UIrDwDr_Ir, off_UIrDwDr_Dr, off_UIrDwDr_Dw; |
| |
| static Addr bb_base; |
| static ULong* cost_base; |
| static InstrInfo* current_ii; |
| |
| /* Cache use offsets */ |
| /* The offsets are only correct because all per-instruction event sets get |
| * the "Use" set added first ! |
| */ |
| static Int off_I1_AcCost = 0; |
| static Int off_I1_SpLoss = 1; |
| static Int off_D1_AcCost = 0; |
| static Int off_D1_SpLoss = 1; |
| static Int off_L2_AcCost = 2; |
| static Int off_L2_SpLoss = 3; |
| |
| /* Cache access types */ |
| typedef enum { Read = 0, Write = CACHELINE_DIRTY } RefType; |
| |
| /* Result of a reference into a flat cache */ |
| typedef enum { Hit = 0, Miss, MissDirty } CacheResult; |
| |
| /* Result of a reference into a hierarchical cache model */ |
| typedef enum { |
| L1_Hit, |
| L2_Hit, |
| MemAccess, |
| WriteBackMemAccess } CacheModelResult; |
| |
| typedef CacheModelResult (*simcall_type)(Addr, UChar); |
| |
| static struct { |
| simcall_type I1_Read; |
| simcall_type D1_Read; |
| simcall_type D1_Write; |
| } simulator; |
| |
| /*------------------------------------------------------------*/ |
| /*--- Cache Simulator Initialization ---*/ |
| /*------------------------------------------------------------*/ |
| |
| static void cachesim_clearcache(cache_t2* c) |
| { |
| Int i; |
| |
| for (i = 0; i < c->sets * c->assoc; i++) |
| c->tags[i] = 0; |
| if (c->use) { |
| for (i = 0; i < c->sets * c->assoc; i++) { |
| c->loaded[i].memline = 0; |
| c->loaded[i].use_base = 0; |
| c->loaded[i].dep_use = 0; |
| c->loaded[i].iaddr = 0; |
| c->use[i].mask = 0; |
| c->use[i].count = 0; |
| c->tags[i] = i % c->assoc; /* init lower bits as pointer */ |
| } |
| } |
| } |
| |
| static void cacheuse_initcache(cache_t2* c); |
| |
| /* By this point, the size/assoc/line_size has been checked. */ |
| static void cachesim_initcache(cache_t config, cache_t2* c) |
| { |
| c->size = config.size; |
| c->assoc = config.assoc; |
| c->line_size = config.line_size; |
| c->sectored = False; // FIXME |
| |
| c->sets = (c->size / c->line_size) / c->assoc; |
| c->sets_min_1 = c->sets - 1; |
| c->line_size_bits = VG_(log2)(c->line_size); |
| c->tag_shift = c->line_size_bits + VG_(log2)(c->sets); |
| c->tag_mask = ~((1<<c->tag_shift)-1); |
| |
| /* Can bits in tag entries be used for flags? |
| * Should be always true as MIN_LINE_SIZE >= 16 */ |
| CLG_ASSERT( (c->tag_mask & CACHELINE_FLAGMASK) == 0); |
| |
| if (c->assoc == 1) { |
| VG_(sprintf)(c->desc_line, "%d B, %d B, direct-mapped%s", |
| c->size, c->line_size, |
| c->sectored ? ", sectored":""); |
| } else { |
| VG_(sprintf)(c->desc_line, "%d B, %d B, %d-way associative%s", |
| c->size, c->line_size, c->assoc, |
| c->sectored ? ", sectored":""); |
| } |
| |
| c->tags = (UWord*) CLG_MALLOC("cl.sim.cs_ic.1", |
| sizeof(UWord) * c->sets * c->assoc); |
| if (clo_collect_cacheuse) |
| cacheuse_initcache(c); |
| else |
| c->use = 0; |
| cachesim_clearcache(c); |
| } |
| |
| |
| #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 |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- Write Through Cache Simulation ---*/ |
| /*------------------------------------------------------------*/ |
| |
| /* |
| * Simple model: L1 & L2 Write Through |
| * Does not distinguish among read and write references |
| * |
| * Simulator functions: |
| * CacheModelResult cachesim_I1_ref(Addr a, UChar size) |
| * CacheModelResult cachesim_D1_ref(Addr a, UChar size) |
| */ |
| |
| static __inline__ |
| CacheResult cachesim_setref(cache_t2* c, UInt set_no, UWord tag) |
| { |
| int i, j; |
| UWord *set; |
| |
| set = &(c->tags[set_no * c->assoc]); |
| |
| /* 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]) |
| return Hit; |
| |
| /* If the tag is one other than the MRU, move it into the MRU spot */ |
| /* and shuffle the rest down. */ |
| for (i = 1; i < c->assoc; i++) { |
| if (tag == set[i]) { |
| for (j = i; j > 0; j--) { |
| set[j] = set[j - 1]; |
| } |
| set[0] = tag; |
| return Hit; |
| } |
| } |
| |
| /* A miss; install this tag as MRU, shuffle rest down. */ |
| for (j = c->assoc - 1; j > 0; j--) { |
| set[j] = set[j - 1]; |
| } |
| set[0] = tag; |
| |
| return Miss; |
| } |
| |
| static CacheResult cachesim_ref(cache_t2* c, Addr a, UChar size) |
| { |
| UInt set1 = ( a >> c->line_size_bits) & (c->sets_min_1); |
| UInt set2 = ((a+size-1) >> c->line_size_bits) & (c->sets_min_1); |
| UWord tag = a >> c->tag_shift; |
| |
| /* Access entirely within line. */ |
| if (set1 == set2) |
| return cachesim_setref(c, set1, tag); |
| |
| /* Access straddles two lines. */ |
| /* Nb: this is a fast way of doing ((set1+1) % c->sets) */ |
| else if (((set1 + 1) & (c->sets-1)) == set2) { |
| UWord tag2 = (a+size-1) >> c->tag_shift; |
| |
| /* the call updates cache structures as side effect */ |
| CacheResult res1 = cachesim_setref(c, set1, tag); |
| CacheResult res2 = cachesim_setref(c, set2, tag2); |
| return ((res1 == Miss) || (res2 == Miss)) ? Miss : Hit; |
| |
| } else { |
| VG_(printf)("addr: %lx size: %u sets: %d %d", a, size, set1, set2); |
| VG_(tool_panic)("item straddles more than two cache sets"); |
| } |
| return Hit; |
| } |
| |
| static |
| CacheModelResult cachesim_I1_ref(Addr a, UChar size) |
| { |
| if ( cachesim_ref( &I1, a, size) == Hit ) return L1_Hit; |
| if ( cachesim_ref( &L2, a, size) == Hit ) return L2_Hit; |
| return MemAccess; |
| } |
| |
| static |
| CacheModelResult cachesim_D1_ref(Addr a, UChar size) |
| { |
| if ( cachesim_ref( &D1, a, size) == Hit ) return L1_Hit; |
| if ( cachesim_ref( &L2, a, size) == Hit ) return L2_Hit; |
| return MemAccess; |
| } |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- Write Back Cache Simulation ---*/ |
| /*------------------------------------------------------------*/ |
| |
| /* |
| * More complex model: L1 Write-through, L2 Write-back |
| * This needs to distinguish among read and write references. |
| * |
| * Simulator functions: |
| * CacheModelResult cachesim_I1_Read(Addr a, UChar size) |
| * CacheModelResult cachesim_D1_Read(Addr a, UChar size) |
| * CacheModelResult cachesim_D1_Write(Addr a, UChar size) |
| */ |
| |
| /* |
| * With write-back, result can be a miss evicting a dirty line |
| * The dirty state of a cache line is stored in Bit0 of the tag for |
| * this cache line (CACHELINE_DIRTY = 1). By OR'ing the reference |
| * type (Read/Write), the line gets dirty on a write. |
| */ |
| static __inline__ |
| CacheResult cachesim_setref_wb(cache_t2* c, RefType ref, UInt set_no, UWord tag) |
| { |
| int i, j; |
| UWord *set, tmp_tag; |
| |
| set = &(c->tags[set_no * c->assoc]); |
| |
| /* 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] & ~CACHELINE_DIRTY)) { |
| set[0] |= ref; |
| return Hit; |
| } |
| /* If the tag is one other than the MRU, move it into the MRU spot */ |
| /* and shuffle the rest down. */ |
| for (i = 1; i < c->assoc; i++) { |
| if (tag == (set[i] & ~CACHELINE_DIRTY)) { |
| tmp_tag = set[i] | ref; // update dirty flag |
| for (j = i; j > 0; j--) { |
| set[j] = set[j - 1]; |
| } |
| set[0] = tmp_tag; |
| return Hit; |
| } |
| } |
| |
| /* A miss; install this tag as MRU, shuffle rest down. */ |
| tmp_tag = set[c->assoc - 1]; |
| for (j = c->assoc - 1; j > 0; j--) { |
| set[j] = set[j - 1]; |
| } |
| set[0] = tag | ref; |
| |
| return (tmp_tag & CACHELINE_DIRTY) ? MissDirty : Miss; |
| } |
| |
| |
| static __inline__ |
| CacheResult cachesim_ref_wb(cache_t2* c, RefType ref, Addr a, UChar size) |
| { |
| UInt set1 = ( a >> c->line_size_bits) & (c->sets_min_1); |
| UInt set2 = ((a+size-1) >> c->line_size_bits) & (c->sets_min_1); |
| UWord tag = a & c->tag_mask; |
| |
| /* Access entirely within line. */ |
| if (set1 == set2) |
| return cachesim_setref_wb(c, ref, set1, tag); |
| |
| /* Access straddles two lines. */ |
| /* Nb: this is a fast way of doing ((set1+1) % c->sets) */ |
| else if (((set1 + 1) & (c->sets-1)) == set2) { |
| UWord tag2 = (a+size-1) & c->tag_mask; |
| |
| /* the call updates cache structures as side effect */ |
| CacheResult res1 = cachesim_setref_wb(c, ref, set1, tag); |
| CacheResult res2 = cachesim_setref_wb(c, ref, set2, tag2); |
| |
| if ((res1 == MissDirty) || (res2 == MissDirty)) return MissDirty; |
| return ((res1 == Miss) || (res2 == Miss)) ? Miss : Hit; |
| |
| } else { |
| VG_(printf)("addr: %lx size: %u sets: %d %d", a, size, set1, set2); |
| VG_(tool_panic)("item straddles more than two cache sets"); |
| } |
| return Hit; |
| } |
| |
| |
| static |
| CacheModelResult cachesim_I1_Read(Addr a, UChar size) |
| { |
| if ( cachesim_ref( &I1, a, size) == Hit ) return L1_Hit; |
| switch( cachesim_ref_wb( &L2, Read, a, size) ) { |
| case Hit: return L2_Hit; |
| case Miss: return MemAccess; |
| default: break; |
| } |
| return WriteBackMemAccess; |
| } |
| |
| static |
| CacheModelResult cachesim_D1_Read(Addr a, UChar size) |
| { |
| if ( cachesim_ref( &D1, a, size) == Hit ) return L1_Hit; |
| switch( cachesim_ref_wb( &L2, Read, a, size) ) { |
| case Hit: return L2_Hit; |
| case Miss: return MemAccess; |
| default: break; |
| } |
| return WriteBackMemAccess; |
| } |
| |
| static |
| CacheModelResult cachesim_D1_Write(Addr a, UChar size) |
| { |
| if ( cachesim_ref( &D1, a, size) == Hit ) { |
| /* Even for a L1 hit, the write-trough L1 passes |
| * the write to the L2 to make the L2 line dirty. |
| * But this causes no latency, so return the hit. |
| */ |
| cachesim_ref_wb( &L2, Write, a, size); |
| return L1_Hit; |
| } |
| switch( cachesim_ref_wb( &L2, Write, a, size) ) { |
| case Hit: return L2_Hit; |
| case Miss: return MemAccess; |
| default: break; |
| } |
| return WriteBackMemAccess; |
| } |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- Hardware Prefetch Simulation ---*/ |
| /*------------------------------------------------------------*/ |
| |
| 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_clear(void) |
| { |
| 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. |
| * One stream can be detected per 4k page. |
| */ |
| static __inline__ |
| void prefetch_L2_doref(Addr a) |
| { |
| UInt stream = (a >> PF_PAGEBITS) % PF_STREAMS; |
| UInt 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_ref(&L2, 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_ref(&L2, a - 5 * L2.line_size,1); |
| } |
| } |
| else pf_seqblocks[stream] = 0; |
| } |
| pf_lastblock[stream] = block; |
| } |
| } |
| |
| /* simple model with hardware prefetch */ |
| |
| static |
| CacheModelResult prefetch_I1_ref(Addr a, UChar size) |
| { |
| if ( cachesim_ref( &I1, a, size) == Hit ) return L1_Hit; |
| prefetch_L2_doref(a); |
| if ( cachesim_ref( &L2, a, size) == Hit ) return L2_Hit; |
| return MemAccess; |
| } |
| |
| static |
| CacheModelResult prefetch_D1_ref(Addr a, UChar size) |
| { |
| if ( cachesim_ref( &D1, a, size) == Hit ) return L1_Hit; |
| prefetch_L2_doref(a); |
| if ( cachesim_ref( &L2, a, size) == Hit ) return L2_Hit; |
| return MemAccess; |
| } |
| |
| |
| /* complex model with hardware prefetch */ |
| |
| static |
| CacheModelResult prefetch_I1_Read(Addr a, UChar size) |
| { |
| if ( cachesim_ref( &I1, a, size) == Hit ) return L1_Hit; |
| prefetch_L2_doref(a); |
| switch( cachesim_ref_wb( &L2, Read, a, size) ) { |
| case Hit: return L2_Hit; |
| case Miss: return MemAccess; |
| default: break; |
| } |
| return WriteBackMemAccess; |
| } |
| |
| static |
| CacheModelResult prefetch_D1_Read(Addr a, UChar size) |
| { |
| if ( cachesim_ref( &D1, a, size) == Hit ) return L1_Hit; |
| prefetch_L2_doref(a); |
| switch( cachesim_ref_wb( &L2, Read, a, size) ) { |
| case Hit: return L2_Hit; |
| case Miss: return MemAccess; |
| default: break; |
| } |
| return WriteBackMemAccess; |
| } |
| |
| static |
| CacheModelResult prefetch_D1_Write(Addr a, UChar size) |
| { |
| prefetch_L2_doref(a); |
| if ( cachesim_ref( &D1, a, size) == Hit ) { |
| /* Even for a L1 hit, the write-trough L1 passes |
| * the write to the L2 to make the L2 line dirty. |
| * But this causes no latency, so return the hit. |
| */ |
| cachesim_ref_wb( &L2, Write, a, size); |
| return L1_Hit; |
| } |
| switch( cachesim_ref_wb( &L2, Write, a, size) ) { |
| case Hit: return L2_Hit; |
| case Miss: return MemAccess; |
| default: break; |
| } |
| return WriteBackMemAccess; |
| } |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- Cache Simulation with use metric collection ---*/ |
| /*------------------------------------------------------------*/ |
| |
| /* can not be combined with write-back or prefetch */ |
| |
| static |
| void cacheuse_initcache(cache_t2* c) |
| { |
| int i; |
| unsigned int start_mask, start_val; |
| unsigned int end_mask, end_val; |
| |
| c->use = CLG_MALLOC("cl.sim.cu_ic.1", |
| sizeof(line_use) * c->sets * c->assoc); |
| c->loaded = CLG_MALLOC("cl.sim.cu_ic.2", |
| sizeof(line_loaded) * c->sets * c->assoc); |
| c->line_start_mask = CLG_MALLOC("cl.sim.cu_ic.3", |
| sizeof(int) * c->line_size); |
| c->line_end_mask = CLG_MALLOC("cl.sim.cu_ic.4", |
| sizeof(int) * c->line_size); |
| |
| c->line_size_mask = c->line_size-1; |
| |
| /* Meaning of line_start_mask/line_end_mask |
| * Example: for a given cache line, you get an access starting at |
| * byte offset 5, length 4, byte 5 - 8 was touched. For a cache |
| * line size of 32, you have 1 bit per byte in the mask: |
| * |
| * bit31 bit8 bit5 bit 0 |
| * | | | | |
| * 11..111111100000 line_start_mask[5] |
| * 00..000111111111 line_end_mask[(5+4)-1] |
| * |
| * use_mask |= line_start_mask[5] && line_end_mask[8] |
| * |
| */ |
| start_val = end_val = ~0; |
| if (c->line_size < 32) { |
| int bits_per_byte = 32/c->line_size; |
| start_mask = (1<<bits_per_byte)-1; |
| end_mask = start_mask << (32-bits_per_byte); |
| for(i=0;i<c->line_size;i++) { |
| c->line_start_mask[i] = start_val; |
| start_val = start_val & ~start_mask; |
| start_mask = start_mask << bits_per_byte; |
| |
| c->line_end_mask[c->line_size-i-1] = end_val; |
| end_val = end_val & ~end_mask; |
| end_mask = end_mask >> bits_per_byte; |
| } |
| } |
| else { |
| int bytes_per_bit = c->line_size/32; |
| start_mask = 1; |
| end_mask = 1 << 31; |
| for(i=0;i<c->line_size;i++) { |
| c->line_start_mask[i] = start_val; |
| c->line_end_mask[c->line_size-i-1] = end_val; |
| if ( ((i+1)%bytes_per_bit) == 0) { |
| start_val &= ~start_mask; |
| end_val &= ~end_mask; |
| start_mask <<= 1; |
| end_mask >>= 1; |
| } |
| } |
| } |
| |
| CLG_DEBUG(6, "Config %s:\n", c->desc_line); |
| for(i=0;i<c->line_size;i++) { |
| CLG_DEBUG(6, " [%2d]: start mask %8x, end mask %8x\n", |
| i, c->line_start_mask[i], c->line_end_mask[i]); |
| } |
| |
| /* We use lower tag bits as offset pointers to cache use info. |
| * I.e. some cache parameters don't work. |
| */ |
| if ( (1<<c->tag_shift) < c->assoc) { |
| VG_(message)(Vg_DebugMsg, |
| "error: Use associativity < %d for cache use statistics!", |
| (1<<c->tag_shift) ); |
| VG_(tool_panic)("Unsupported cache configuration"); |
| } |
| } |
| |
| |
| /* for I1/D1 caches */ |
| #define CACHEUSE(L) \ |
| \ |
| static CacheModelResult cacheuse##_##L##_doRead(Addr a, UChar size) \ |
| { \ |
| UInt set1 = ( a >> L.line_size_bits) & (L.sets_min_1); \ |
| UInt set2 = ((a+size-1) >> L.line_size_bits) & (L.sets_min_1); \ |
| UWord tag = a & L.tag_mask; \ |
| UWord tag2; \ |
| int i, j, idx; \ |
| UWord *set, tmp_tag; \ |
| UInt use_mask; \ |
| \ |
| CLG_DEBUG(6,"%s.Acc(Addr %#lx, size %d): Sets [%d/%d]\n", \ |
| L.name, a, size, set1, set2); \ |
| \ |
| /* First case: word entirely within line. */ \ |
| if (set1 == set2) { \ |
| \ |
| set = &(L.tags[set1 * L.assoc]); \ |
| use_mask = L.line_start_mask[a & L.line_size_mask] & \ |
| L.line_end_mask[(a+size-1) & L.line_size_mask]; \ |
| \ |
| /* 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] & L.tag_mask)) { \ |
| idx = (set1 * L.assoc) + (set[0] & ~L.tag_mask); \ |
| L.use[idx].count ++; \ |
| L.use[idx].mask |= use_mask; \ |
| CLG_DEBUG(6," Hit0 [idx %d] (line %#lx from %#lx): %x => %08x, count %d\n",\ |
| idx, L.loaded[idx].memline, L.loaded[idx].iaddr, \ |
| use_mask, L.use[idx].mask, L.use[idx].count); \ |
| return L1_Hit; \ |
| } \ |
| /* 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] & L.tag_mask)) { \ |
| tmp_tag = set[i]; \ |
| for (j = i; j > 0; j--) { \ |
| set[j] = set[j - 1]; \ |
| } \ |
| set[0] = tmp_tag; \ |
| idx = (set1 * L.assoc) + (tmp_tag & ~L.tag_mask); \ |
| L.use[idx].count ++; \ |
| L.use[idx].mask |= use_mask; \ |
| CLG_DEBUG(6," Hit%d [idx %d] (line %#lx from %#lx): %x => %08x, count %d\n",\ |
| i, idx, L.loaded[idx].memline, L.loaded[idx].iaddr, \ |
| use_mask, L.use[idx].mask, L.use[idx].count); \ |
| return L1_Hit; \ |
| } \ |
| } \ |
| \ |
| /* A miss; install this tag as MRU, shuffle rest down. */ \ |
| tmp_tag = set[L.assoc - 1] & ~L.tag_mask; \ |
| for (j = L.assoc - 1; j > 0; j--) { \ |
| set[j] = set[j - 1]; \ |
| } \ |
| set[0] = tag | tmp_tag; \ |
| idx = (set1 * L.assoc) + tmp_tag; \ |
| return update_##L##_use(&L, idx, \ |
| use_mask, a &~ L.line_size_mask); \ |
| \ |
| /* 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) { \ |
| Int miss1=0, miss2=0; /* 0: L1 hit, 1:L1 miss, 2:L2 miss */ \ |
| set = &(L.tags[set1 * L.assoc]); \ |
| use_mask = L.line_start_mask[a & L.line_size_mask]; \ |
| if (tag == (set[0] & L.tag_mask)) { \ |
| idx = (set1 * L.assoc) + (set[0] & ~L.tag_mask); \ |
| L.use[idx].count ++; \ |
| L.use[idx].mask |= use_mask; \ |
| CLG_DEBUG(6," Hit0 [idx %d] (line %#lx from %#lx): %x => %08x, count %d\n",\ |
| idx, L.loaded[idx].memline, L.loaded[idx].iaddr, \ |
| use_mask, L.use[idx].mask, L.use[idx].count); \ |
| goto block2; \ |
| } \ |
| for (i = 1; i < L.assoc; i++) { \ |
| if (tag == (set[i] & L.tag_mask)) { \ |
| tmp_tag = set[i]; \ |
| for (j = i; j > 0; j--) { \ |
| set[j] = set[j - 1]; \ |
| } \ |
| set[0] = tmp_tag; \ |
| idx = (set1 * L.assoc) + (tmp_tag & ~L.tag_mask); \ |
| L.use[idx].count ++; \ |
| L.use[idx].mask |= use_mask; \ |
| CLG_DEBUG(6," Hit%d [idx %d] (line %#lx from %#lx): %x => %08x, count %d\n",\ |
| i, idx, L.loaded[idx].memline, L.loaded[idx].iaddr, \ |
| use_mask, L.use[idx].mask, L.use[idx].count); \ |
| goto block2; \ |
| } \ |
| } \ |
| tmp_tag = set[L.assoc - 1] & ~L.tag_mask; \ |
| for (j = L.assoc - 1; j > 0; j--) { \ |
| set[j] = set[j - 1]; \ |
| } \ |
| set[0] = tag | tmp_tag; \ |
| idx = (set1 * L.assoc) + tmp_tag; \ |
| miss1 = update_##L##_use(&L, idx, \ |
| use_mask, a &~ L.line_size_mask); \ |
| block2: \ |
| set = &(L.tags[set2 * L.assoc]); \ |
| use_mask = L.line_end_mask[(a+size-1) & L.line_size_mask]; \ |
| tag2 = (a+size-1) & L.tag_mask; \ |
| if (tag2 == (set[0] & L.tag_mask)) { \ |
| idx = (set2 * L.assoc) + (set[0] & ~L.tag_mask); \ |
| L.use[idx].count ++; \ |
| L.use[idx].mask |= use_mask; \ |
| CLG_DEBUG(6," Hit0 [idx %d] (line %#lx from %#lx): %x => %08x, count %d\n",\ |
| idx, L.loaded[idx].memline, L.loaded[idx].iaddr, \ |
| use_mask, L.use[idx].mask, L.use[idx].count); \ |
| return miss1; \ |
| } \ |
| for (i = 1; i < L.assoc; i++) { \ |
| if (tag2 == (set[i] & L.tag_mask)) { \ |
| tmp_tag = set[i]; \ |
| for (j = i; j > 0; j--) { \ |
| set[j] = set[j - 1]; \ |
| } \ |
| set[0] = tmp_tag; \ |
| idx = (set2 * L.assoc) + (tmp_tag & ~L.tag_mask); \ |
| L.use[idx].count ++; \ |
| L.use[idx].mask |= use_mask; \ |
| CLG_DEBUG(6," Hit%d [idx %d] (line %#lx from %#lx): %x => %08x, count %d\n",\ |
| i, idx, L.loaded[idx].memline, L.loaded[idx].iaddr, \ |
| use_mask, L.use[idx].mask, L.use[idx].count); \ |
| return miss1; \ |
| } \ |
| } \ |
| tmp_tag = set[L.assoc - 1] & ~L.tag_mask; \ |
| for (j = L.assoc - 1; j > 0; j--) { \ |
| set[j] = set[j - 1]; \ |
| } \ |
| set[0] = tag2 | tmp_tag; \ |
| idx = (set2 * L.assoc) + tmp_tag; \ |
| miss2 = update_##L##_use(&L, idx, \ |
| use_mask, (a+size-1) &~ L.line_size_mask); \ |
| return (miss1==MemAccess || miss2==MemAccess) ? MemAccess:L2_Hit; \ |
| \ |
| } else { \ |
| VG_(printf)("addr: %#lx size: %u sets: %d %d", a, size, set1, set2); \ |
| VG_(tool_panic)("item straddles more than two cache sets"); \ |
| } \ |
| return 0; \ |
| } |
| |
| |
| /* logarithmic bitcounting algorithm, see |
| * http://graphics.stanford.edu/~seander/bithacks.html |
| */ |
| static __inline__ unsigned int countBits(unsigned int bits) |
| { |
| unsigned int c; // store the total here |
| const int S[] = {1, 2, 4, 8, 16}; // Magic Binary Numbers |
| const int B[] = {0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF}; |
| |
| c = bits; |
| c = ((c >> S[0]) & B[0]) + (c & B[0]); |
| c = ((c >> S[1]) & B[1]) + (c & B[1]); |
| c = ((c >> S[2]) & B[2]) + (c & B[2]); |
| c = ((c >> S[3]) & B[3]) + (c & B[3]); |
| c = ((c >> S[4]) & B[4]) + (c & B[4]); |
| return c; |
| } |
| |
| static void update_L2_use(int idx, Addr memline) |
| { |
| line_loaded* loaded = &(L2.loaded[idx]); |
| line_use* use = &(L2.use[idx]); |
| int i = ((32 - countBits(use->mask)) * L2.line_size)>>5; |
| |
| CLG_DEBUG(2, " L2.miss [%d]: at %#lx accessing memline %#lx\n", |
| idx, bb_base + current_ii->instr_offset, memline); |
| if (use->count>0) { |
| CLG_DEBUG(2, " old: used %d, loss bits %d (%08x) [line %#lx from %#lx]\n", |
| use->count, i, use->mask, loaded->memline, loaded->iaddr); |
| CLG_DEBUG(2, " collect: %d, use_base %p\n", |
| CLG_(current_state).collect, loaded->use_base); |
| |
| if (CLG_(current_state).collect && loaded->use_base) { |
| (loaded->use_base)[off_L2_AcCost] += 1000 / use->count; |
| (loaded->use_base)[off_L2_SpLoss] += i; |
| } |
| } |
| |
| use->count = 0; |
| use->mask = 0; |
| |
| loaded->memline = memline; |
| loaded->iaddr = bb_base + current_ii->instr_offset; |
| loaded->use_base = (CLG_(current_state).nonskipped) ? |
| CLG_(current_state).nonskipped->skipped : |
| cost_base + current_ii->cost_offset; |
| } |
| |
| static |
| CacheModelResult cacheuse_L2_access(Addr memline, line_loaded* l1_loaded) |
| { |
| UInt setNo = (memline >> L2.line_size_bits) & (L2.sets_min_1); |
| UWord* set = &(L2.tags[setNo * L2.assoc]); |
| UWord tag = memline & L2.tag_mask; |
| |
| int i, j, idx; |
| UWord tmp_tag; |
| |
| CLG_DEBUG(6,"L2.Acc(Memline %#lx): Set %d\n", memline, setNo); |
| |
| if (tag == (set[0] & L2.tag_mask)) { |
| idx = (setNo * L2.assoc) + (set[0] & ~L2.tag_mask); |
| l1_loaded->dep_use = &(L2.use[idx]); |
| |
| CLG_DEBUG(6," Hit0 [idx %d] (line %#lx from %#lx): => %08x, count %d\n", |
| idx, L2.loaded[idx].memline, L2.loaded[idx].iaddr, |
| L2.use[idx].mask, L2.use[idx].count); |
| return L2_Hit; |
| } |
| for (i = 1; i < L2.assoc; i++) { |
| if (tag == (set[i] & L2.tag_mask)) { |
| tmp_tag = set[i]; |
| for (j = i; j > 0; j--) { |
| set[j] = set[j - 1]; |
| } |
| set[0] = tmp_tag; |
| idx = (setNo * L2.assoc) + (tmp_tag & ~L2.tag_mask); |
| l1_loaded->dep_use = &(L2.use[idx]); |
| |
| CLG_DEBUG(6," Hit%d [idx %d] (line %#lx from %#lx): => %08x, count %d\n", |
| i, idx, L2.loaded[idx].memline, L2.loaded[idx].iaddr, |
| L2.use[idx].mask, L2.use[idx].count); |
| return L2_Hit; |
| } |
| } |
| |
| /* A miss; install this tag as MRU, shuffle rest down. */ |
| tmp_tag = set[L2.assoc - 1] & ~L2.tag_mask; |
| for (j = L2.assoc - 1; j > 0; j--) { |
| set[j] = set[j - 1]; |
| } |
| set[0] = tag | tmp_tag; |
| idx = (setNo * L2.assoc) + tmp_tag; |
| l1_loaded->dep_use = &(L2.use[idx]); |
| |
| update_L2_use(idx, memline); |
| |
| return MemAccess; |
| } |
| |
| |
| |
| |
| #define UPDATE_USE(L) \ |
| \ |
| static CacheModelResult update##_##L##_use(cache_t2* cache, int idx, \ |
| UInt mask, Addr memline) \ |
| { \ |
| line_loaded* loaded = &(cache->loaded[idx]); \ |
| line_use* use = &(cache->use[idx]); \ |
| int c = ((32 - countBits(use->mask)) * cache->line_size)>>5; \ |
| \ |
| CLG_DEBUG(2, " %s.miss [%d]: at %#lx accessing memline %#lx (mask %08x)\n", \ |
| cache->name, idx, bb_base + current_ii->instr_offset, memline, mask); \ |
| if (use->count>0) { \ |
| CLG_DEBUG(2, " old: used %d, loss bits %d (%08x) [line %#lx from %#lx]\n",\ |
| use->count, c, use->mask, loaded->memline, loaded->iaddr); \ |
| CLG_DEBUG(2, " collect: %d, use_base %p\n", \ |
| CLG_(current_state).collect, loaded->use_base); \ |
| \ |
| if (CLG_(current_state).collect && loaded->use_base) { \ |
| (loaded->use_base)[off_##L##_AcCost] += 1000 / use->count; \ |
| (loaded->use_base)[off_##L##_SpLoss] += c; \ |
| \ |
| /* FIXME (?): L1/L2 line sizes must be equal ! */ \ |
| loaded->dep_use->mask |= use->mask; \ |
| loaded->dep_use->count += use->count; \ |
| } \ |
| } \ |
| \ |
| use->count = 1; \ |
| use->mask = mask; \ |
| loaded->memline = memline; \ |
| loaded->iaddr = bb_base + current_ii->instr_offset; \ |
| loaded->use_base = (CLG_(current_state).nonskipped) ? \ |
| CLG_(current_state).nonskipped->skipped : \ |
| cost_base + current_ii->cost_offset; \ |
| \ |
| if (memline == 0) return L2_Hit; \ |
| return cacheuse_L2_access(memline, loaded); \ |
| } |
| |
| UPDATE_USE(I1); |
| UPDATE_USE(D1); |
| |
| CACHEUSE(I1); |
| CACHEUSE(D1); |
| |
| |
| static |
| void cacheuse_finish(void) |
| { |
| int i; |
| InstrInfo ii = { 0,0,0,0 }; |
| |
| if (!CLG_(current_state).collect) return; |
| |
| bb_base = 0; |
| current_ii = ⅈ |
| cost_base = 0; |
| |
| /* update usage counters */ |
| if (I1.use) |
| for (i = 0; i < I1.sets * I1.assoc; i++) |
| if (I1.loaded[i].use_base) |
| update_I1_use( &I1, i, 0,0); |
| |
| if (D1.use) |
| for (i = 0; i < D1.sets * D1.assoc; i++) |
| if (D1.loaded[i].use_base) |
| update_D1_use( &D1, i, 0,0); |
| |
| if (L2.use) |
| for (i = 0; i < L2.sets * L2.assoc; i++) |
| if (L2.loaded[i].use_base) |
| update_L2_use(i, 0); |
| } |
| |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- Helper functions called by instrumented code ---*/ |
| /*------------------------------------------------------------*/ |
| |
| |
| static __inline__ |
| void inc_costs(CacheModelResult r, ULong* c1, ULong* c2) |
| { |
| switch(r) { |
| case WriteBackMemAccess: |
| if (clo_simulate_writeback) { |
| c1[3]++; |
| c2[3]++; |
| } |
| // fall through |
| |
| case MemAccess: |
| c1[2]++; |
| c2[2]++; |
| // fall through |
| |
| case L2_Hit: |
| c1[1]++; |
| c2[1]++; |
| // fall through |
| |
| default: |
| c1[0]++; |
| c2[0]++; |
| } |
| } |
| |
| static |
| Char* cacheRes(CacheModelResult r) |
| { |
| switch(r) { |
| case L1_Hit: return "L1 Hit "; |
| case L2_Hit: return "L2 Hit "; |
| case MemAccess: return "L2 Miss"; |
| case WriteBackMemAccess: return "L2 Miss (dirty)"; |
| default: |
| tl_assert(0); |
| } |
| return "??"; |
| } |
| |
| VG_REGPARM(1) |
| static void log_1I0D(InstrInfo* ii) |
| { |
| CacheModelResult IrRes; |
| |
| current_ii = ii; |
| IrRes = (*simulator.I1_Read)(bb_base + ii->instr_offset, ii->instr_size); |
| |
| CLG_DEBUG(6, "log_1I0D: Ir %#lx/%u => %s\n", |
| bb_base + ii->instr_offset, ii->instr_size, cacheRes(IrRes)); |
| |
| if (CLG_(current_state).collect) { |
| ULong* cost_Ir; |
| |
| if (CLG_(current_state).nonskipped) |
| cost_Ir = CLG_(current_state).nonskipped->skipped + CLG_(sets).off_full_Ir; |
| else |
| cost_Ir = cost_base + ii->cost_offset + off_UIr_Ir; |
| |
| inc_costs(IrRes, cost_Ir, |
| CLG_(current_state).cost + CLG_(sets).off_full_Ir ); |
| } |
| } |
| |
| VG_REGPARM(2) |
| static void log_2I0D(InstrInfo* ii1, InstrInfo* ii2) |
| { |
| CacheModelResult Ir1Res, Ir2Res; |
| ULong *global_cost_Ir; |
| |
| current_ii = ii1; |
| Ir1Res = (*simulator.I1_Read)(bb_base + ii1->instr_offset, ii1->instr_size); |
| current_ii = ii2; |
| Ir2Res = (*simulator.I1_Read)(bb_base + ii2->instr_offset, ii2->instr_size); |
| |
| CLG_DEBUG(6, "log_2I0D: Ir1 %#lx/%u => %s, Ir2 %#lx/%u => %s\n", |
| bb_base + ii1->instr_offset, ii1->instr_size, cacheRes(Ir1Res), |
| bb_base + ii2->instr_offset, ii2->instr_size, cacheRes(Ir2Res) ); |
| |
| if (!CLG_(current_state).collect) return; |
| |
| global_cost_Ir = CLG_(current_state).cost + CLG_(sets).off_full_Ir; |
| if (CLG_(current_state).nonskipped) { |
| ULong* skipped_cost_Ir = CLG_(current_state).nonskipped->skipped + |
| CLG_(sets).off_full_Ir; |
| inc_costs(Ir1Res, global_cost_Ir, skipped_cost_Ir); |
| inc_costs(Ir2Res, global_cost_Ir, skipped_cost_Ir); |
| return; |
| } |
| |
| inc_costs(Ir1Res, global_cost_Ir, cost_base + ii1->cost_offset + off_UIr_Ir); |
| inc_costs(Ir2Res, global_cost_Ir, cost_base + ii2->cost_offset + off_UIr_Ir); |
| } |
| |
| VG_REGPARM(3) |
| static void log_3I0D(InstrInfo* ii1, InstrInfo* ii2, InstrInfo* ii3) |
| { |
| CacheModelResult Ir1Res, Ir2Res, Ir3Res; |
| ULong *global_cost_Ir; |
| |
| current_ii = ii1; |
| Ir1Res = (*simulator.I1_Read)(bb_base + ii1->instr_offset, ii1->instr_size); |
| current_ii = ii2; |
| Ir2Res = (*simulator.I1_Read)(bb_base + ii2->instr_offset, ii2->instr_size); |
| current_ii = ii3; |
| Ir3Res = (*simulator.I1_Read)(bb_base + ii3->instr_offset, ii3->instr_size); |
| |
| CLG_DEBUG(6, "log_3I0D: Ir1 %#lx/%u => %s, Ir2 %#lx/%u => %s, Ir3 %#lx/%u => %s\n", |
| bb_base + ii1->instr_offset, ii1->instr_size, cacheRes(Ir1Res), |
| bb_base + ii2->instr_offset, ii2->instr_size, cacheRes(Ir2Res), |
| bb_base + ii3->instr_offset, ii3->instr_size, cacheRes(Ir3Res) ); |
| |
| if (!CLG_(current_state).collect) return; |
| |
| global_cost_Ir = CLG_(current_state).cost + CLG_(sets).off_full_Ir; |
| if (CLG_(current_state).nonskipped) { |
| ULong* skipped_cost_Ir = CLG_(current_state).nonskipped->skipped + |
| CLG_(sets).off_full_Ir; |
| inc_costs(Ir1Res, global_cost_Ir, skipped_cost_Ir); |
| inc_costs(Ir2Res, global_cost_Ir, skipped_cost_Ir); |
| inc_costs(Ir3Res, global_cost_Ir, skipped_cost_Ir); |
| return; |
| } |
| |
| inc_costs(Ir1Res, global_cost_Ir, cost_base + ii1->cost_offset + off_UIr_Ir); |
| inc_costs(Ir2Res, global_cost_Ir, cost_base + ii2->cost_offset + off_UIr_Ir); |
| inc_costs(Ir3Res, global_cost_Ir, cost_base + ii3->cost_offset + off_UIr_Ir); |
| } |
| |
| /* Instruction doing a read access */ |
| |
| VG_REGPARM(3) |
| static void log_1I1Dr(InstrInfo* ii, Addr data_addr, Word data_size) |
| { |
| CacheModelResult IrRes, DrRes; |
| |
| current_ii = ii; |
| IrRes = (*simulator.I1_Read)(bb_base + ii->instr_offset, ii->instr_size); |
| DrRes = (*simulator.D1_Read)(data_addr, data_size); |
| |
| CLG_DEBUG(6, "log_1I1Dr: Ir %#lx/%u => %s, Dr %#lx/%lu => %s\n", |
| bb_base + ii->instr_offset, ii->instr_size, cacheRes(IrRes), |
| data_addr, data_size, cacheRes(DrRes)); |
| |
| if (CLG_(current_state).collect) { |
| ULong *cost_Ir, *cost_Dr; |
| |
| if (CLG_(current_state).nonskipped) { |
| cost_Ir = CLG_(current_state).nonskipped->skipped + CLG_(sets).off_full_Ir; |
| cost_Dr = CLG_(current_state).nonskipped->skipped + CLG_(sets).off_full_Dr; |
| } |
| else { |
| // event set must be UIrDr or extension |
| CLG_ASSERT((ii->eventset == CLG_(sets).UIrDr) || |
| (ii->eventset == CLG_(sets).UIrDrDw)); |
| cost_Ir = cost_base + ii->cost_offset + off_UIrDr_Ir; |
| cost_Dr = cost_base + ii->cost_offset + off_UIrDr_Dr; |
| } |
| |
| inc_costs(IrRes, cost_Ir, |
| CLG_(current_state).cost + CLG_(sets).off_full_Ir ); |
| inc_costs(DrRes, cost_Dr, |
| CLG_(current_state).cost + CLG_(sets).off_full_Dr ); |
| } |
| } |
| |
| |
| VG_REGPARM(3) |
| static void log_0I1Dr(InstrInfo* ii, Addr data_addr, Word data_size) |
| { |
| CacheModelResult DrRes; |
| |
| current_ii = ii; |
| DrRes = (*simulator.D1_Read)(data_addr, data_size); |
| |
| CLG_DEBUG(6, "log_0I1Dr: Dr %#lx/%lu => %s\n", |
| data_addr, data_size, cacheRes(DrRes)); |
| |
| if (CLG_(current_state).collect) { |
| ULong *cost_Dr; |
| |
| if (CLG_(current_state).nonskipped) { |
| cost_Dr = CLG_(current_state).nonskipped->skipped + CLG_(sets).off_full_Dr; |
| } |
| else { |
| Int off_Dr; |
| if (ii->eventset == CLG_(sets).UIrDr) off_Dr = off_UIrDr_Dr; |
| else if (ii->eventset == CLG_(sets).UIrDrDw) off_Dr = off_UIrDrDw_Dr; |
| else if (ii->eventset == CLG_(sets).UIrDwDr) off_Dr = off_UIrDwDr_Dr; |
| else CLG_ASSERT(0); |
| |
| cost_Dr = cost_base + ii->cost_offset + off_Dr; |
| } |
| |
| inc_costs(DrRes, cost_Dr, |
| CLG_(current_state).cost + CLG_(sets).off_full_Dr ); |
| } |
| } |
| |
| |
| /* Instruction doing a write access */ |
| |
| VG_REGPARM(3) |
| static void log_1I1Dw(InstrInfo* ii, Addr data_addr, Word data_size) |
| { |
| CacheModelResult IrRes, DwRes; |
| |
| current_ii = ii; |
| IrRes = (*simulator.I1_Read)(bb_base + ii->instr_offset, ii->instr_size); |
| DwRes = (*simulator.D1_Write)(data_addr, data_size); |
| |
| CLG_DEBUG(6, "log_1I1Dw: Ir %#lx/%u => %s, Dw %#lx/%lu => %s\n", |
| bb_base + ii->instr_offset, ii->instr_size, cacheRes(IrRes), |
| data_addr, data_size, cacheRes(DwRes)); |
| |
| if (CLG_(current_state).collect) { |
| ULong *cost_Ir, *cost_Dw; |
| |
| if (CLG_(current_state).nonskipped) { |
| cost_Ir = CLG_(current_state).nonskipped->skipped + CLG_(sets).off_full_Ir; |
| cost_Dw = CLG_(current_state).nonskipped->skipped + CLG_(sets).off_full_Dw; |
| } |
| else { |
| // This helper is called when a Dr event follows Ir; |
| // Event set must be UIrDw or extension |
| CLG_ASSERT((ii->eventset == CLG_(sets).UIrDw) || |
| (ii->eventset == CLG_(sets).UIrDwDr)); |
| cost_Ir = cost_base + ii->cost_offset + off_UIrDw_Ir; |
| cost_Dw = cost_base + ii->cost_offset + off_UIrDw_Dw; |
| } |
| |
| inc_costs(IrRes, cost_Ir, |
| CLG_(current_state).cost + CLG_(sets).off_full_Ir ); |
| inc_costs(DwRes, cost_Dw, |
| CLG_(current_state).cost + CLG_(sets).off_full_Dw ); |
| } |
| } |
| |
| VG_REGPARM(3) |
| static void log_0I1Dw(InstrInfo* ii, Addr data_addr, Word data_size) |
| { |
| CacheModelResult DwRes; |
| |
| current_ii = ii; |
| DwRes = (*simulator.D1_Write)(data_addr, data_size); |
| |
| CLG_DEBUG(6, "log_0I1Dw: Dw %#lx/%lu => %s\n", |
| data_addr, data_size, cacheRes(DwRes)); |
| |
| if (CLG_(current_state).collect) { |
| ULong *cost_Dw; |
| |
| if (CLG_(current_state).nonskipped) { |
| cost_Dw = CLG_(current_state).nonskipped->skipped + CLG_(sets).off_full_Dw; |
| } |
| else { |
| Int off_Dw; |
| if (ii->eventset == CLG_(sets).UIrDw) off_Dw = off_UIrDw_Dw; |
| else if (ii->eventset == CLG_(sets).UIrDwDr) off_Dw = off_UIrDwDr_Dw; |
| else if (ii->eventset == CLG_(sets).UIrDrDw) off_Dw = off_UIrDrDw_Dw; |
| else CLG_ASSERT(0); |
| |
| cost_Dw = cost_base + ii->cost_offset + off_Dw; |
| } |
| |
| inc_costs(DwRes, cost_Dw, |
| CLG_(current_state).cost + CLG_(sets).off_full_Dw ); |
| } |
| } |
| |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- Cache configuration ---*/ |
| /*------------------------------------------------------------*/ |
| |
| #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; |
| |
| |
| /* Checks cache config is ok; makes it so if not. */ |
| static |
| void check_cache(cache_t* cache, Char *name) |
| { |
| /* Simulator requires line size and set count to be powers of two */ |
| if (( cache->size % (cache->line_size * cache->assoc) != 0) || |
| (-1 == VG_(log2)(cache->size/cache->line_size/cache->assoc))) { |
| VG_(message)(Vg_UserMsg, |
| "error: %s set count not a power of two; aborting.", |
| name); |
| } |
| |
| if (-1 == VG_(log2)(cache->line_size)) { |
| VG_(message)(Vg_UserMsg, |
| "error: %s line size of %dB not a power of two; aborting.", |
| name, cache->line_size); |
| VG_(exit)(1); |
| } |
| |
| // 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, |
| "error: %s line size of %dB too small; aborting.", |
| name, cache->line_size); |
| VG_(exit)(1); |
| } |
| |
| /* Then check cache size > line size (causes seg faults if not). */ |
| if (cache->size <= cache->line_size) { |
| VG_(message)(Vg_UserMsg, |
| "error: %s cache size of %dB <= line size of %dB; aborting.", |
| name, cache->size, cache->line_size); |
| VG_(exit)(1); |
| } |
| |
| /* 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); aborting.", name); |
| VG_(exit)(1); |
| } |
| } |
| |
| static |
| void configure_caches(cache_t* I1c, cache_t* D1c, cache_t* L2c) |
| { |
| #define DEFINED(L) (-1 != L.size || -1 != L.assoc || -1 != L.line_size) |
| |
| Int n_clos = 0; |
| |
| // Count how many were defined on the command line. |
| if (DEFINED(clo_I1_cache)) { n_clos++; } |
| if (DEFINED(clo_D1_cache)) { n_clos++; } |
| if (DEFINED(clo_L2_cache)) { n_clos++; } |
| |
| // Set the cache config (using auto-detection, if supported by the |
| // architecture) |
| VG_(configure_caches)( I1c, D1c, L2c, (3 == n_clos) ); |
| |
| // Then replace with any defined on the command line. |
| if (DEFINED(clo_I1_cache)) { *I1c = clo_I1_cache; } |
| if (DEFINED(clo_D1_cache)) { *D1c = clo_D1_cache; } |
| if (DEFINED(clo_L2_cache)) { *L2c = clo_L2_cache; } |
| |
| // Then check values and fix if not acceptable. |
| check_cache(I1c, "I1"); |
| check_cache(D1c, "D1"); |
| check_cache(L2c, "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); |
| } |
| #undef CMD_LINE_DEFINED |
| } |
| |
| |
| /* Initialize and clear simulator state */ |
| static void cachesim_post_clo_init(void) |
| { |
| /* Cache configurations. */ |
| cache_t I1c, D1c, L2c; |
| |
| /* Initialize access handlers */ |
| if (!CLG_(clo).simulate_cache) { |
| CLG_(cachesim).log_1I0D = 0; |
| CLG_(cachesim).log_1I0D_name = "(no function)"; |
| CLG_(cachesim).log_2I0D = 0; |
| CLG_(cachesim).log_2I0D_name = "(no function)"; |
| CLG_(cachesim).log_3I0D = 0; |
| CLG_(cachesim).log_3I0D_name = "(no function)"; |
| |
| CLG_(cachesim).log_1I1Dr = 0; |
| CLG_(cachesim).log_1I1Dr_name = "(no function)"; |
| CLG_(cachesim).log_1I1Dw = 0; |
| CLG_(cachesim).log_1I1Dw_name = "(no function)"; |
| |
| CLG_(cachesim).log_0I1Dr = 0; |
| CLG_(cachesim).log_0I1Dr_name = "(no function)"; |
| CLG_(cachesim).log_0I1Dw = 0; |
| CLG_(cachesim).log_0I1Dw_name = "(no function)"; |
| return; |
| } |
| |
| /* Configuration of caches only needed with real cache simulation */ |
| configure_caches(&I1c, &D1c, &L2c); |
| |
| I1.name = "I1"; |
| D1.name = "D1"; |
| L2.name = "L2"; |
| |
| cachesim_initcache(I1c, &I1); |
| cachesim_initcache(D1c, &D1); |
| cachesim_initcache(L2c, &L2); |
| |
| /* the other cache simulators use the standard helpers |
| * with dispatching via simulator struct */ |
| |
| CLG_(cachesim).log_1I0D = log_1I0D; |
| CLG_(cachesim).log_1I0D_name = "log_1I0D"; |
| CLG_(cachesim).log_2I0D = log_2I0D; |
| CLG_(cachesim).log_2I0D_name = "log_2I0D"; |
| CLG_(cachesim).log_3I0D = log_3I0D; |
| CLG_(cachesim).log_3I0D_name = "log_3I0D"; |
| |
| CLG_(cachesim).log_1I1Dr = log_1I1Dr; |
| CLG_(cachesim).log_1I1Dw = log_1I1Dw; |
| CLG_(cachesim).log_1I1Dr_name = "log_1I1Dr"; |
| CLG_(cachesim).log_1I1Dw_name = "log_1I1Dw"; |
| |
| CLG_(cachesim).log_0I1Dr = log_0I1Dr; |
| CLG_(cachesim).log_0I1Dw = log_0I1Dw; |
| CLG_(cachesim).log_0I1Dr_name = "log_0I1Dr"; |
| CLG_(cachesim).log_0I1Dw_name = "log_0I1Dw"; |
| |
| if (clo_collect_cacheuse) { |
| |
| /* Output warning for not supported option combinations */ |
| if (clo_simulate_hwpref) { |
| VG_(message)(Vg_DebugMsg, |
| "warning: prefetch simulation can not be used with cache usage"); |
| clo_simulate_hwpref = False; |
| } |
| |
| if (clo_simulate_writeback) { |
| VG_(message)(Vg_DebugMsg, |
| "warning: write-back simulation can not be used with cache usage"); |
| clo_simulate_writeback = False; |
| } |
| |
| simulator.I1_Read = cacheuse_I1_doRead; |
| simulator.D1_Read = cacheuse_D1_doRead; |
| simulator.D1_Write = cacheuse_D1_doRead; |
| return; |
| } |
| |
| if (clo_simulate_hwpref) { |
| prefetch_clear(); |
| |
| if (clo_simulate_writeback) { |
| simulator.I1_Read = prefetch_I1_Read; |
| simulator.D1_Read = prefetch_D1_Read; |
| simulator.D1_Write = prefetch_D1_Write; |
| } |
| else { |
| simulator.I1_Read = prefetch_I1_ref; |
| simulator.D1_Read = prefetch_D1_ref; |
| simulator.D1_Write = prefetch_D1_ref; |
| } |
| |
| return; |
| } |
| |
| if (clo_simulate_writeback) { |
| simulator.I1_Read = cachesim_I1_Read; |
| simulator.D1_Read = cachesim_D1_Read; |
| simulator.D1_Write = cachesim_D1_Write; |
| } |
| else { |
| simulator.I1_Read = cachesim_I1_ref; |
| simulator.D1_Read = cachesim_D1_ref; |
| simulator.D1_Write = cachesim_D1_ref; |
| } |
| } |
| |
| |
| /* Clear simulator state. Has to be initialized before */ |
| static |
| void cachesim_clear(void) |
| { |
| cachesim_clearcache(&I1); |
| cachesim_clearcache(&D1); |
| cachesim_clearcache(&L2); |
| |
| prefetch_clear(); |
| } |
| |
| |
| static void 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); |
| } |
| |
| static |
| void cachesim_print_opts(void) |
| { |
| VG_(printf)( |
| "\n cache simulator options:\n" |
| " --simulate-cache=no|yes Do cache simulation [no]\n" |
| " --simulate-wb=no|yes Count write-back events [no]\n" |
| " --simulate-hwpref=no|yes Simulate hardware prefetch [no]\n" |
| #if CLG_EXPERIMENTAL |
| " --simulate-sectors=no|yes Simulate sectored behaviour [no]\n" |
| #endif |
| " --cacheuse=no|yes Collect cache block use [no]\n" |
| " --I1=<size>,<assoc>,<line_size> set I1 cache manually\n" |
| " --D1=<size>,<assoc>,<line_size> set D1 cache manually\n" |
| " --L2=<size>,<assoc>,<line_size> set L2 cache manually\n" |
| ); |
| } |
| |
| static void parse_opt ( cache_t* cache, char* opt ) |
| { |
| Long i1, i2, i3; |
| Char* endptr; |
| |
| // Option argument looks like "65536,2,64". Extract them. |
| i1 = VG_(strtoll10)(opt, &endptr); if (*endptr != ',') goto bad; |
| i2 = VG_(strtoll10)(endptr+1, &endptr); if (*endptr != ',') goto bad; |
| i3 = VG_(strtoll10)(endptr+1, &endptr); if (*endptr != '\0') goto bad; |
| |
| // Check for overflow. |
| cache->size = (Int)i1; |
| cache->assoc = (Int)i2; |
| cache->line_size = (Int)i3; |
| if (cache->size != i1) goto overflow; |
| if (cache->assoc != i2) goto overflow; |
| if (cache->line_size != i3) goto overflow; |
| |
| return; |
| |
| overflow: |
| VG_(message)(Vg_UserMsg, |
| "one of the cache parameters was too large and overflowed\n"); |
| bad: |
| // XXX: this omits the "--I1/D1/L2=" part from the message, but that's |
| // not a big deal. |
| VG_(err_bad_option)(opt); |
| } |
| |
| /* Check for command line option for cache configuration. |
| * Return False if unknown and not handled. |
| * |
| * Called from CLG_(process_cmd_line_option)() in clo.c |
| */ |
| static Bool cachesim_parse_opt(Char* arg) |
| { |
| Char* tmp_str; |
| |
| if VG_BOOL_CLO(arg, "--simulate-wb", clo_simulate_writeback) {} |
| else if VG_BOOL_CLO(arg, "--simulate-hwpref", clo_simulate_hwpref) {} |
| else if VG_BOOL_CLO(arg, "--simulate-sectors", clo_simulate_sectors) {} |
| |
| else if VG_BOOL_CLO(arg, "--cacheuse", clo_collect_cacheuse) { |
| if (clo_collect_cacheuse) { |
| /* Use counters only make sense with fine dumping */ |
| CLG_(clo).dump_instr = True; |
| } |
| } |
| |
| else if VG_STR_CLO(arg, "--I1", tmp_str) |
| parse_opt(&clo_I1_cache, tmp_str); |
| else if VG_STR_CLO(arg, "--D1", tmp_str) |
| parse_opt(&clo_D1_cache, tmp_str); |
| else if VG_STR_CLO(arg, "--L2", tmp_str) |
| parse_opt(&clo_L2_cache, tmp_str); |
| else |
| return False; |
| |
| return True; |
| } |
| |
| /* Adds commas to ULong, right justifying in a field field_width wide, returns |
| * the string in buf. */ |
| static |
| Int commify(ULong n, int field_width, char* buf) |
| { |
| int len, n_commas, i, j, new_len, space; |
| |
| VG_(sprintf)(buf, "%llu", n); |
| len = VG_(strlen)(buf); |
| n_commas = (len - 1) / 3; |
| new_len = len + n_commas; |
| space = field_width - new_len; |
| |
| /* Allow for printing a number in a field_width smaller than it's size */ |
| if (space < 0) space = 0; |
| |
| /* Make j = -1 because we copy the '\0' before doing the numbers in groups |
| * of three. */ |
| for (j = -1, i = len ; i >= 0; i--) { |
| buf[i + n_commas + space] = buf[i]; |
| |
| if ((i>0) && (3 == ++j)) { |
| j = 0; |
| n_commas--; |
| buf[i + n_commas + space] = ','; |
| } |
| } |
| /* Right justify in field. */ |
| for (i = 0; i < space; i++) buf[i] = ' '; |
| return new_len; |
| } |
| |
| static |
| void percentify(Int n, Int ex, Int field_width, char buf[]) |
| { |
| int i, len, space; |
| |
| VG_(sprintf)(buf, "%d.%d%%", n / ex, n % ex); |
| len = VG_(strlen)(buf); |
| space = field_width - len; |
| if (space < 0) space = 0; /* Allow for v. small field_width */ |
| i = len; |
| |
| /* Right justify in field */ |
| for ( ; i >= 0; i--) buf[i + space] = buf[i]; |
| for (i = 0; i < space; i++) buf[i] = ' '; |
| } |
| |
| static |
| void cachesim_printstat(void) |
| { |
| FullCost total = CLG_(total_cost), D_total = 0; |
| ULong L2_total_m, L2_total_mr, L2_total_mw, |
| L2_total, L2_total_r, L2_total_w; |
| char buf1[RESULTS_BUF_LEN], |
| buf2[RESULTS_BUF_LEN], |
| buf3[RESULTS_BUF_LEN]; |
| Int l1, l2, l3; |
| Int p; |
| |
| if ((VG_(clo_verbosity) >1) && clo_simulate_hwpref) { |
| VG_(message)(Vg_DebugMsg, "Prefetch Up: %llu", |
| prefetch_up); |
| VG_(message)(Vg_DebugMsg, "Prefetch Down: %llu", |
| prefetch_down); |
| VG_(message)(Vg_DebugMsg, ""); |
| } |
| |
| /* I cache results. Use the I_refs value to determine the first column |
| * width. */ |
| l1 = commify(total[CLG_(sets).off_full_Ir], 0, buf1); |
| VG_(message)(Vg_UserMsg, "I refs: %s", buf1); |
| |
| if (!CLG_(clo).simulate_cache) return; |
| |
| commify(total[CLG_(sets).off_full_Ir +1], l1, buf1); |
| VG_(message)(Vg_UserMsg, "I1 misses: %s", buf1); |
| |
| commify(total[CLG_(sets).off_full_Ir +2], l1, buf1); |
| VG_(message)(Vg_UserMsg, "L2i misses: %s", buf1); |
| |
| p = 100; |
| |
| if (0 == total[CLG_(sets).off_full_Ir]) |
| total[CLG_(sets).off_full_Ir] = 1; |
| |
| percentify(total[CLG_(sets).off_full_Ir+1] * 100 * p / |
| total[CLG_(sets).off_full_Ir], p, l1+1, buf1); |
| VG_(message)(Vg_UserMsg, "I1 miss rate: %s", buf1); |
| |
| percentify(total[CLG_(sets).off_full_Ir+2] * 100 * p / |
| total[CLG_(sets).off_full_Ir], p, l1+1, buf1); |
| VG_(message)(Vg_UserMsg, "L2i miss rate: %s", buf1); |
| VG_(message)(Vg_UserMsg, ""); |
| |
| /* D cache results. |
| Use the D_refs.rd and D_refs.wr values to determine the |
| * width of columns 2 & 3. */ |
| |
| D_total = CLG_(get_eventset_cost)( CLG_(sets).full ); |
| CLG_(init_cost)( CLG_(sets).full, D_total); |
| CLG_(copy_cost)( CLG_(sets).Dr, D_total, total + CLG_(sets).off_full_Dr ); |
| CLG_(add_cost) ( CLG_(sets).Dw, D_total, total + CLG_(sets).off_full_Dw ); |
| |
| commify( D_total[0], l1, buf1); |
| l2 = commify(total[CLG_(sets).off_full_Dr], 0, buf2); |
| l3 = commify(total[CLG_(sets).off_full_Dw], 0, buf3); |
| VG_(message)(Vg_UserMsg, "D refs: %s (%s rd + %s wr)", |
| buf1, buf2, buf3); |
| |
| commify( D_total[1], l1, buf1); |
| commify(total[CLG_(sets).off_full_Dr+1], l2, buf2); |
| commify(total[CLG_(sets).off_full_Dw+1], l3, buf3); |
| VG_(message)(Vg_UserMsg, "D1 misses: %s (%s rd + %s wr)", |
| buf1, buf2, buf3); |
| |
| commify( D_total[2], l1, buf1); |
| commify(total[CLG_(sets).off_full_Dr+2], l2, buf2); |
| commify(total[CLG_(sets).off_full_Dw+2], l3, buf3); |
| VG_(message)(Vg_UserMsg, "L2d misses: %s (%s rd + %s wr)", |
| buf1, buf2, buf3); |
| |
| p = 10; |
| |
| if (0 == D_total[0]) D_total[0] = 1; |
| if (0 == total[CLG_(sets).off_full_Dr]) total[CLG_(sets).off_full_Dr] = 1; |
| if (0 == total[CLG_(sets).off_full_Dw]) total[CLG_(sets).off_full_Dw] = 1; |
| |
| percentify( D_total[1] * 100 * p / D_total[0], p, l1+1, buf1); |
| percentify(total[CLG_(sets).off_full_Dr+1] * 100 * p / |
| total[CLG_(sets).off_full_Dr], p, l2+1, buf2); |
| percentify(total[CLG_(sets).off_full_Dw+1] * 100 * p / |
| total[CLG_(sets).off_full_Dw], p, l3+1, buf3); |
| VG_(message)(Vg_UserMsg, "D1 miss rate: %s (%s + %s )", buf1, buf2,buf3); |
| |
| percentify( D_total[2] * 100 * p / D_total[0], p, l1+1, buf1); |
| percentify(total[CLG_(sets).off_full_Dr+2] * 100 * p / |
| total[CLG_(sets).off_full_Dr], p, l2+1, buf2); |
| percentify(total[CLG_(sets).off_full_Dw+2] * 100 * p / |
| total[CLG_(sets).off_full_Dw], p, l3+1, buf3); |
| VG_(message)(Vg_UserMsg, "L2d miss rate: %s (%s + %s )", buf1, buf2,buf3); |
| VG_(message)(Vg_UserMsg, ""); |
| |
| |
| |
| /* L2 overall results */ |
| |
| L2_total = |
| total[CLG_(sets).off_full_Dr +1] + |
| total[CLG_(sets).off_full_Dw +1] + |
| total[CLG_(sets).off_full_Ir +1]; |
| L2_total_r = |
| total[CLG_(sets).off_full_Dr +1] + |
| total[CLG_(sets).off_full_Ir +1]; |
| L2_total_w = total[CLG_(sets).off_full_Dw +1]; |
| commify(L2_total, l1, buf1); |
| commify(L2_total_r, l2, buf2); |
| commify(L2_total_w, l3, buf3); |
| VG_(message)(Vg_UserMsg, "L2 refs: %s (%s rd + %s wr)", |
| buf1, buf2, buf3); |
| |
| L2_total_m = |
| total[CLG_(sets).off_full_Dr +2] + |
| total[CLG_(sets).off_full_Dw +2] + |
| total[CLG_(sets).off_full_Ir +2]; |
| L2_total_mr = |
| total[CLG_(sets).off_full_Dr +2] + |
| total[CLG_(sets).off_full_Ir +2]; |
| L2_total_mw = total[CLG_(sets).off_full_Dw +2]; |
| commify(L2_total_m, l1, buf1); |
| commify(L2_total_mr, l2, buf2); |
| commify(L2_total_mw, l3, buf3); |
| VG_(message)(Vg_UserMsg, "L2 misses: %s (%s rd + %s wr)", |
| buf1, buf2, buf3); |
| |
| percentify(L2_total_m * 100 * p / |
| (total[CLG_(sets).off_full_Ir] + D_total[0]), p, l1+1, buf1); |
| percentify(L2_total_mr * 100 * p / |
| (total[CLG_(sets).off_full_Ir] + total[CLG_(sets).off_full_Dr]), |
| p, l2+1, buf2); |
| percentify(L2_total_mw * 100 * p / |
| total[CLG_(sets).off_full_Dw], p, l3+1, buf3); |
| VG_(message)(Vg_UserMsg, "L2 miss rate: %s (%s + %s )", |
| buf1, buf2,buf3); |
| } |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- Setup for Event set. ---*/ |
| /*------------------------------------------------------------*/ |
| |
| struct event_sets CLG_(sets); |
| |
| void CLG_(init_eventsets)(Int max_user) |
| { |
| EventType * e1, *e2, *e3, *e4; |
| // Basic event sets from which others are composed |
| EventSet *Use, *Ir, *Dr, *Dw; |
| // Compositions of basic sets used for per-instruction counters |
| EventSet *UIr, *UIrDr, *UIrDrDw, *UIrDw, *UIrDwDr; |
| // Composition used for global counters and aggregation |
| EventSet *full; |
| int sizeOfUseIr; |
| |
| // the "Use" events types only are used with "cacheuse" simulation |
| Use = CLG_(get_eventset)("Use", 4); |
| if (clo_collect_cacheuse) { |
| /* if TUse is 0, there was never a load, and no loss, too */ |
| e1 = CLG_(register_eventtype)("AcCost1"); |
| CLG_(add_eventtype)(Use, e1); |
| e1 = CLG_(register_eventtype)("SpLoss1"); |
| CLG_(add_eventtype)(Use, e1); |
| e1 = CLG_(register_eventtype)("AcCost2"); |
| CLG_(add_eventtype)(Use, e1); |
| e1 = CLG_(register_eventtype)("SpLoss2"); |
| CLG_(add_eventtype)(Use, e1); |
| } |
| |
| Ir = CLG_(get_eventset)("Ir", 4); |
| Dr = CLG_(get_eventset)("Dr", 4); |
| Dw = CLG_(get_eventset)("Dw", 4); |
| if (CLG_(clo).simulate_cache) { |
| e1 = CLG_(register_eventtype)("Ir"); |
| e2 = CLG_(register_eventtype)("I1mr"); |
| e3 = CLG_(register_eventtype)("I2mr"); |
| if (clo_simulate_writeback) { |
| e4 = CLG_(register_eventtype)("I2dmr"); |
| CLG_(add_dep_event4)(Ir, e1,e2,e3,e4); |
| } |
| else |
| CLG_(add_dep_event3)(Ir, e1,e2,e3); |
| |
| e1 = CLG_(register_eventtype)("Dr"); |
| e2 = CLG_(register_eventtype)("D1mr"); |
| e3 = CLG_(register_eventtype)("D2mr"); |
| if (clo_simulate_writeback) { |
| e4 = CLG_(register_eventtype)("D2dmr"); |
| CLG_(add_dep_event4)(Dr, e1,e2,e3,e4); |
| } |
| else |
| CLG_(add_dep_event3)(Dr, e1,e2,e3); |
| |
| e1 = CLG_(register_eventtype)("Dw"); |
| e2 = CLG_(register_eventtype)("D1mw"); |
| e3 = CLG_(register_eventtype)("D2mw"); |
| if (clo_simulate_writeback) { |
| e4 = CLG_(register_eventtype)("D2dmw"); |
| CLG_(add_dep_event4)(Dw, e1,e2,e3,e4); |
| } |
| else |
| CLG_(add_dep_event3)(Dw, e1,e2,e3); |
| |
| } |
| else { |
| e1 = CLG_(register_eventtype)("Ir"); |
| CLG_(add_eventtype)(Ir, e1); |
| } |
| |
| // Self cost event sets per guest instruction (U used only for cacheUse). |
| // Each basic event set only appears once, as eg. multiple different Dr's |
| // in one guest instruction are counted in the same counter. |
| |
| sizeOfUseIr = Use->size + Ir->size; |
| UIr = CLG_(get_eventset)("UIr", sizeOfUseIr); |
| CLG_(add_eventset)(UIr, Use); |
| off_UIr_Ir = CLG_(add_eventset)(UIr, Ir); |
| |
| UIrDr = CLG_(get_eventset)("UIrDr", sizeOfUseIr + Dr->size); |
| CLG_(add_eventset)(UIrDr, Use); |
| off_UIrDr_Ir = CLG_(add_eventset)(UIrDr, Ir); |
| off_UIrDr_Dr = CLG_(add_eventset)(UIrDr, Dr); |
| |
| UIrDrDw = CLG_(get_eventset)("IrDrDw", sizeOfUseIr + Dr->size + Dw->size); |
| CLG_(add_eventset)(UIrDrDw, Use); |
| off_UIrDrDw_Ir = CLG_(add_eventset)(UIrDrDw, Ir); |
| off_UIrDrDw_Dr = CLG_(add_eventset)(UIrDrDw, Dr); |
| off_UIrDrDw_Dw = CLG_(add_eventset)(UIrDrDw, Dw); |
| |
| UIrDw = CLG_(get_eventset)("UIrDw", sizeOfUseIr + Dw->size); |
| CLG_(add_eventset)(UIrDw, Use); |
| off_UIrDw_Ir = CLG_(add_eventset)(UIrDw, Ir); |
| off_UIrDw_Dw = CLG_(add_eventset)(UIrDw, Dw); |
| |
| UIrDwDr = CLG_(get_eventset)("IrDwDr", sizeOfUseIr + Dw->size + Dr->size); |
| CLG_(add_eventset)(UIrDwDr, Use); |
| off_UIrDwDr_Ir = CLG_(add_eventset)(UIrDrDw, Ir); |
| off_UIrDwDr_Dw = CLG_(add_eventset)(UIrDrDw, Dw); |
| off_UIrDwDr_Dr = CLG_(add_eventset)(UIrDrDw, Dr); |
| |
| |
| // the "full" event set is used as global counter and for aggregation |
| if (CLG_(clo).collect_alloc) max_user += 2; |
| if (CLG_(clo).collect_systime) max_user += 2; |
| full = CLG_(get_eventset)("full", |
| sizeOfUseIr + Dr->size + Dw->size + max_user); |
| CLG_(add_eventset)(full, Use); |
| CLG_(sets).off_full_Ir = CLG_(add_eventset)(full, Ir); |
| CLG_(sets).off_full_Dr = CLG_(add_eventset)(full, Dr); |
| CLG_(sets).off_full_Dw = CLG_(add_eventset)(full, Dw); |
| if (CLG_(clo).collect_alloc) { |
| e1 = CLG_(register_eventtype)("allocCount"); |
| e2 = CLG_(register_eventtype)("allocSize"); |
| CLG_(sets).off_full_alloc = CLG_(add_dep_event2)(full, e1,e2); |
| } |
| if (CLG_(clo).collect_systime) { |
| e1 = CLG_(register_eventtype)("sysCount"); |
| e2 = CLG_(register_eventtype)("sysTime"); |
| CLG_(sets).off_full_systime = CLG_(add_dep_event2)(full, e1,e2); |
| } |
| |
| CLG_(sets).Use = Use; |
| CLG_(sets).Ir = Ir; |
| CLG_(sets).Dr = Dr; |
| CLG_(sets).Dw = Dw; |
| CLG_(sets).UIr = UIr; |
| CLG_(sets).UIrDr = UIrDr; |
| CLG_(sets).UIrDrDw = UIrDrDw; |
| CLG_(sets).UIrDw = UIrDw; |
| CLG_(sets).UIrDwDr = UIrDwDr; |
| CLG_(sets).full = full; |
| |
| |
| CLG_DEBUGIF(1) { |
| CLG_DEBUG(1, "EventSets:\n"); |
| CLG_(print_eventset)(-2, Use); |
| CLG_(print_eventset)(-2, Ir); |
| CLG_(print_eventset)(-2, Dr); |
| CLG_(print_eventset)(-2, Dw); |
| CLG_(print_eventset)(-2, full); |
| } |
| |
| /* Not-existing events are silently ignored */ |
| CLG_(dumpmap) = CLG_(get_eventmapping)(full); |
| CLG_(append_event)(CLG_(dumpmap), "Ir"); |
| CLG_(append_event)(CLG_(dumpmap), "Dr"); |
| CLG_(append_event)(CLG_(dumpmap), "Dw"); |
| CLG_(append_event)(CLG_(dumpmap), "I1mr"); |
| CLG_(append_event)(CLG_(dumpmap), "D1mr"); |
| CLG_(append_event)(CLG_(dumpmap), "D1mw"); |
| CLG_(append_event)(CLG_(dumpmap), "I2mr"); |
| CLG_(append_event)(CLG_(dumpmap), "D2mr"); |
| CLG_(append_event)(CLG_(dumpmap), "D2mw"); |
| CLG_(append_event)(CLG_(dumpmap), "I2dmr"); |
| CLG_(append_event)(CLG_(dumpmap), "D2dmr"); |
| CLG_(append_event)(CLG_(dumpmap), "D2dmw"); |
| CLG_(append_event)(CLG_(dumpmap), "AcCost1"); |
| CLG_(append_event)(CLG_(dumpmap), "SpLoss1"); |
| CLG_(append_event)(CLG_(dumpmap), "AcCost2"); |
| CLG_(append_event)(CLG_(dumpmap), "SpLoss2"); |
| CLG_(append_event)(CLG_(dumpmap), "allocCount"); |
| CLG_(append_event)(CLG_(dumpmap), "allocSize"); |
| CLG_(append_event)(CLG_(dumpmap), "sysCount"); |
| CLG_(append_event)(CLG_(dumpmap), "sysTime"); |
| |
| } |
| |
| |
| |
| static |
| void add_and_zero_Dx(EventSet* es, SimCost dst, ULong* cost) |
| { |
| /* if eventset use is defined, it is always first (hardcoded!) */ |
| CLG_(add_and_zero_cost)( CLG_(sets).Use, dst, cost); |
| |
| if (es == CLG_(sets).UIr) { |
| CLG_(add_and_zero_cost)( CLG_(sets).Ir, dst + CLG_(sets).off_full_Ir, |
| cost + off_UIr_Ir); |
| } |
| else if (es == CLG_(sets).UIrDr) { |
| CLG_(add_and_zero_cost)( CLG_(sets).Ir, dst + CLG_(sets).off_full_Ir, |
| cost + off_UIrDr_Ir); |
| CLG_(add_and_zero_cost)( CLG_(sets).Dr, dst + CLG_(sets).off_full_Dr, |
| cost + off_UIrDr_Dr); |
| } |
| else if (es == CLG_(sets).UIrDrDw) { |
| CLG_(add_and_zero_cost)( CLG_(sets).Ir, dst + CLG_(sets).off_full_Ir, |
| cost + off_UIrDrDw_Ir); |
| CLG_(add_and_zero_cost)( CLG_(sets).Dr, dst + CLG_(sets).off_full_Dr, |
| cost + off_UIrDrDw_Dr); |
| CLG_(add_and_zero_cost)( CLG_(sets).Dw, dst + CLG_(sets).off_full_Dw, |
| cost + off_UIrDrDw_Dw); |
| } |
| else if (es == CLG_(sets).UIrDw) { |
| CLG_(add_and_zero_cost)( CLG_(sets).Ir, dst + CLG_(sets).off_full_Ir, |
| cost + off_UIrDw_Ir); |
| CLG_(add_and_zero_cost)( CLG_(sets).Dw, dst + CLG_(sets).off_full_Dw, |
| cost + off_UIrDw_Dw); |
| } |
| else if (es == CLG_(sets).UIrDwDr) { |
| CLG_(add_and_zero_cost)( CLG_(sets).Ir, dst + CLG_(sets).off_full_Ir, |
| cost + off_UIrDwDr_Ir); |
| CLG_(add_and_zero_cost)( CLG_(sets).Dw, dst + CLG_(sets).off_full_Dw, |
| cost + off_UIrDwDr_Dw); |
| CLG_(add_and_zero_cost)( CLG_(sets).Dr, dst + CLG_(sets).off_full_Dr, |
| cost + off_UIrDwDr_Dr); |
| } |
| else CLG_ASSERT(0); |
| } |
| |
| /* this is called at dump time for every instruction executed */ |
| static void cachesim_add_icost(SimCost cost, BBCC* bbcc, |
| InstrInfo* ii, ULong exe_count) |
| { |
| if (!CLG_(clo).simulate_cache) |
| cost[CLG_(sets).off_full_Ir] += exe_count; |
| else { |
| |
| #if 0 |
| /* There is always a trivial case where exe_count and Ir can be |
| * slightly different because ecounter is updated when executing |
| * the next BB. E.g. for last BB executed, or when toggling collection |
| */ |
| /* FIXME: Hardcoded that each eventset has Ir as first */ |
| if ((bbcc->cost + ii->cost_offset)[0] != exe_count) { |
| VG_(printf)("==> Ir %llu, exe %llu\n", |
| (bbcc->cost + ii->cost_offset)[0], exe_count); |
| CLG_(print_bbcc_cost)(-2, bbcc); |
| //CLG_ASSERT((bbcc->cost + ii->cost_offset)[0] == exe_count); |
| } |
| #endif |
| |
| add_and_zero_Dx(ii->eventset, cost, |
| bbcc->cost + ii->cost_offset); |
| } |
| } |
| |
| static |
| void cachesim_after_bbsetup(void) |
| { |
| BBCC* bbcc = CLG_(current_state).bbcc; |
| |
| if (CLG_(clo).simulate_cache) { |
| BB* bb = bbcc->bb; |
| |
| /* only needed if log_* functions are called */ |
| bb_base = bb->obj->offset + bb->offset; |
| cost_base = bbcc->cost; |
| } |
| } |
| |
| static |
| void cachesim_finish(void) |
| { |
| if (clo_collect_cacheuse) |
| cacheuse_finish(); |
| } |
| |
| /*------------------------------------------------------------*/ |
| /*--- The simulator defined in this file ---*/ |
| /*------------------------------------------------------------*/ |
| |
| struct cachesim_if CLG_(cachesim) = { |
| .print_opts = cachesim_print_opts, |
| .parse_opt = cachesim_parse_opt, |
| .post_clo_init = cachesim_post_clo_init, |
| .clear = cachesim_clear, |
| .getdesc = cachesim_getdesc, |
| .printstat = cachesim_printstat, |
| .add_icost = cachesim_add_icost, |
| .after_bbsetup = cachesim_after_bbsetup, |
| .finish = cachesim_finish, |
| |
| /* these will be set by cachesim_post_clo_init */ |
| .log_1I0D = 0, |
| .log_2I0D = 0, |
| .log_3I0D = 0, |
| |
| .log_1I1Dr = 0, |
| .log_1I1Dw = 0, |
| |
| .log_0I1Dr = 0, |
| .log_0I1Dw = 0, |
| |
| .log_1I0D_name = "(no function)", |
| .log_2I0D_name = "(no function)", |
| .log_3I0D_name = "(no function)", |
| |
| .log_1I1Dr_name = "(no function)", |
| .log_1I1Dw_name = "(no function)", |
| |
| .log_0I1Dr_name = "(no function)", |
| .log_0I1Dw_name = "(no function)", |
| }; |
| |
| |
| /*--------------------------------------------------------------------*/ |
| /*--- end ct_sim.c ---*/ |
| /*--------------------------------------------------------------------*/ |
| |