CCTLib
Calling-context and data-object attribution library for Intel Pin
ins_reuse_client.cpp
Go to the documentation of this file.
1 // @COPYRIGHT@
2 // Licensed under MIT license.
3 // See LICENSE.TXT file in the project root for more information.
4 // ==============================================================
5 
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <stdint.h>
9 #include <atomic>
10 #include <malloc.h>
11 #include <iostream>
12 #include <fstream>
13 #include <unistd.h>
14 #include <assert.h>
15 #include <string.h>
16 #include <sys/mman.h>
17 #include <sstream>
18 #include <functional>
19 #include <unordered_set>
20 #include <vector>
21 #include <unordered_map>
22 #include <algorithm>
23 #include <list>
24 // bits/stdc++.h is a libstdc++ extension and is not available under Pin 4.x's
25 // libc++/musl CRT. The individual STL headers we actually need are already
26 // included above.
27 // Legacy boost::property_tree usage removed; JSON parsing now uses
28 // nlohmann/json (included below). Pin RT does not support Boost.
29 #include <nlohmann/json.hpp>
30 #include "pin.H"
31 #include "cctlib.H"
32 #include "shadow_memory.H"
33 #include "rbtree.h"
34 #include <xmmintrin.h>
35 #include <immintrin.h>
36 #include <sys/time.h>
37 #include <sys/resource.h>
38 
39 extern "C" {
40 #include "xed-interface.h"
41 #include "xed-common-hdrs.h"
42 }
43 
44 #include <google/sparse_hash_map>
45 #include <google/dense_hash_map>
47 
48 using namespace std;
49 using namespace PinCCTLib;
50 
51 #define MAX_REUSE_DISTANCE_BINS (32) // 4 GB
52 #define MAX_REUSE_DISTANCE (1ULL << (MAX_REUSE_DISTANCE_BINS)) // 4 GB
53 
54 #define CACHELINE_BIT (6)
55 #define CACHELINE_SIZE (1L << CACHELINE_BIT)
56 
57 #define NUM_BLOCKS (3)
58 #define NUM_CACHE_LEVELS (4) // L1i, L2, L3, iTLB
59 
60 #define FIRST_USE (ULLONG_MAX)
61 
62 #define MULTI_THREADED
63 
64 // CCT/LRU knobs
65 static KNOB<bool> KnobCCT(KNOB_MODE_WRITEONCE, "pintool", "cct", "0", "Enable CCT-level LRU eviction pair tracking");
66 static KNOB<UINT64> KnobL1iCap(KNOB_MODE_WRITEONCE, "pintool", "l1i_cap", "8192", "L1i capacity in unique instructions");
67 static KNOB<UINT64> KnobL2Cap(KNOB_MODE_WRITEONCE, "pintool", "l2_cap", "65536", "L2 capacity in unique instructions");
68 static KNOB<UINT64> KnobL3Cap(KNOB_MODE_WRITEONCE, "pintool", "l3_cap", "2097152", "L3 capacity in unique instructions");
69 static KNOB<UINT64> KnobITLBCap(KNOB_MODE_WRITEONCE, "pintool", "itlb_cap", "64", "iTLB capacity in 4K pages");
70 static KNOB<UINT32> KnobTopN(KNOB_MODE_WRITEONCE, "pintool", "topn", "100", "Top-N eviction pairs to report per level");
71 
72 static bool gCCTEnabled = false;
74 static const char* gCacheLevelNames[NUM_CACHE_LEVELS] = {"L1i", "L2", "L3", "iTLB"};
75 
76 // JIT-time mapping: instruction address → CCTLib slot
77 static unordered_map<ADDRINT, uint32_t> gAddrToSlot;
78 
79 
80 struct {
81  char dummy1[128];
82  xed_state_t xedState;
83  char dummy2[128];
85 
87 
88 constexpr struct {
89  size_t blkSize;
91 } BLK_INFO[NUM_BLOCKS] = {
92  {CACHELINE_SIZE, (char*)"64B CacheLineReuse"},
93  {4096, (char*)"4K OS PageSizeReuse"},
94  {1L << 21, (char*)"2M Huge PageSizeReuse"},
95 };
96 
98  char padding1[128];
99  uint64_t bytesLoad;
100  long long numIns;
102  uint64_t numInsExecuted;
103  uint64_t footprint;
106  struct {
109  uint64_t footprint;
111  void* prevBlock;
114 
115  // iTLB page-level LRU model (BBL granularity)
117  uint64_t itlbTick;
118  uint64_t itlbFootprint;
120 
121  // CCT eviction pair tracking
122  unordered_map<uint64_t, uint64_t> missPairs[NUM_CACHE_LEVELS];
123 
125  char padding2[128];
126 };
127 
128 static struct {
129  PIN_LOCK lock;
130  uint64_t numInsExecuted;
131  uint64_t footprint;
133  struct {
135  uint64_t footprint;
138  unordered_map<uint64_t, uint64_t> missPairs[NUM_CACHE_LEVELS];
140 
141 
142 // key for accessing TLS storage in the threads. initialized once in main()
143 static TLS_KEY client_tls_key;
145 
146 // function to access thread-specific data
147 inline InsReuseThreadData* ClientGetTLS(const THREADID threadId) {
148 #ifdef MULTI_THREADED
149  InsReuseThreadData* tdata =
150  static_cast<InsReuseThreadData*>(PIN_GetThreadData(client_tls_key, threadId));
151  return tdata;
152 #else
153  return gSingleThreadedTData;
154 #endif
155 }
156 
157 static inline size_t GetBlock(size_t addr, size_t blockMask) {
158  return addr & (blockMask);
159 }
160 
161 
162 static INT32 Usage() {
163  PIN_ERROR("Pin tool to profile instruction reuse distance.\n" + KNOB_BASE::StringKnobSummary() + "\n");
164  return -1;
165 }
166 
167 // Main for RedSpy, initialize the tool, register instrumentation functions and call the target program.
168 static FILE* gTraceFile;
169 static ofstream gJSONFile;
170 
171 // Initialized the needed data structures before launching the target program
172 static void ClientInit(int argc, char* argv[]) {
173  // Create output file
174  char name[MAX_FILE_PATH] = "insReuse.out.";
175  char* envPath = getenv("INS_REUSE_CLIENT_OUTPUT_FILE");
176 
177  if (envPath) {
178  // assumes max of MAX_FILE_PATH
179  snprintf(name, sizeof(name), "%s", envPath);
180  }
181 
182 
183  gethostname(name + strlen(name), MAX_FILE_PATH - strlen(name));
184  pid_t pid = getpid();
185  sprintf(name + strlen(name), "%d", pid);
186  cerr << "\n Creating log file at:" << name << "\n";
187  gTraceFile = fopen(name, "w");
188  // print the arguments passed
189  fprintf(gTraceFile, "\n");
190 
191  for (int i = 0; i < argc; i++) {
192  fprintf(gTraceFile, "%s ", argv[i]);
193  }
194 
195  fprintf(gTraceFile, "\n");
196 
197  int i = 0;
198  for (i = 0; i < argc; i++) {
199  if (0 == strcmp(argv[i], "--")) {
200  i++;
201  break;
202  }
203  }
204  json j;
205  if (i < argc) {
206  j["exe"] = argv[i];
207  i++;
208  }
209  stringstream ss;
210  for (; i < argc; i++) {
211  ss << argv[i] << " ";
212  }
213  j["args"] = ss.str();
214  gJSONFile.open(string(name) + ".json");
215  gJSONFile << j.dump(4) << "\n";
216 
217  // Init Xed
218  // Init XED for decoding instructions
219  xed_state_init(&InsReuseGlobals.xedState, XED_MACHINE_MODE_LONG_64, (xed_address_width_enum_t)0, XED_ADDRESS_WIDTH_64b);
220 
221  // init some globals
222  PIN_InitLock(&GLOBAL_STATS.lock);
223  GLOBAL_STATS.numInsExecuted = 0;
224  GLOBAL_STATS.footprint = 0;
225  for (int i = 0; i < MAX_REUSE_DISTANCE_BINS; i++) {
226  GLOBAL_STATS.insReuseHisto[i] = 0;
227  }
228  for (int j = 0; j < NUM_BLOCKS; j++) {
229  GLOBAL_STATS.blockData[j].numBlocksCounter = 0;
230  GLOBAL_STATS.blockData[j].footprint = 0;
231  for (int i = 0; i < MAX_REUSE_DISTANCE_BINS; i++) {
232  GLOBAL_STATS.blockData[j].reuseHisto[i] = 0;
233  }
234  }
235  fflush(gTraceFile);
236  gJSONFile.flush();
237 }
238 
239 static inline void UpdateInsReuseStats(uint64_t distance, uint64_t count, InsReuseThreadData* tData) {
240  uint64_t bin;
241 
242  // Bin 0: [0, 1)
243  // Bin 1 [1, 2)
244  // Bin 2 [2, 4)
245  // Bin 3 [4, 8)
246 
247  if (distance >= MAX_REUSE_DISTANCE) {
248  bin = MAX_REUSE_DISTANCE_BINS - 1;
249  } else if (distance == 0) {
250  bin = 0;
251  } else {
252  bin = (sizeof(uint64_t) * 8 - 1 - __builtin_clzl(distance)) + 1 /* 1 for dedicated bin 0*/;
253  }
254  tData->insReuseHisto[bin] += count;
255 }
256 
257 
258 static inline void UpdateBlockReuseStats(uint64_t distance, uint64_t count, uint64_t* reuseHisto) {
259  // Bin 0: [0, 1)
260  // Bin 1 [1, 2)
261  // Bin 2 [2, 4)
262  // Bin 3 [4, 8)
263 
264  uint64_t bin;
265  if (distance >= MAX_REUSE_DISTANCE) {
266  bin = MAX_REUSE_DISTANCE_BINS - 1;
267  } else if (distance == 0) {
268  bin = 0;
269  } else {
270  bin = (sizeof(uint64_t) * 8 - 1 - __builtin_clzl(distance)) + 1 /* 1 for dedicated bin 0*/;
271  }
272  // One instruction is at this distance
273  reuseHisto[bin]++;
274  // The rest of the instructions on the same cacheline have a zero distance
275  if (count > 1) {
276  reuseHisto[0] += count - 1;
277  }
278 }
279 
280 static inline uint64_t ComputeInsReuseDistance(uint64_t prevTick, uint64_t newTick, uint32_t v, InsReuseThreadData* tData, RBTree_t* rbt, uint32_t payload = 0) {
281  uint64_t reuseDist;
282  auto* node = rbt->FindSumGreaterThan(prevTick, &reuseDist);
283  if (node) {
284  auto* retNode = rbt->Delete(node);
285  retNode->key = newTick;
286  retNode->value = v;
287  retNode->payload = payload;
288  rbt->Insert(retNode);
289  // reuseDist = instructions executed by *other* BBLs since last access.
290  // Add (v-1) for the within-BBL instructions that also execute
291  // between two accesses to the same instruction in this BBL.
292  return reuseDist + v - 1;
293  } else {
294  return FIRST_USE;
295  }
296 }
297 
298 // CCTLib JIT-time callback: record each instruction's address → slot mapping
299 static VOID InstrumentInsCallback(INS ins, VOID* v, const uint32_t slot) {
300  gAddrToSlot[INS_Address(ins)] = slot;
301 }
302 
304  unordered_map<uint64_t, uint64_t>& pairMap) {
305  uint64_t key = ((uint64_t)victimCtxt << 32) | (uint64_t)curCtxt;
306  pairMap[key]++;
307 }
308 
309 static inline void CheckEvictions(RBTree_t& tree,
311  uint64_t reuseDistance, bool isFirstUse,
312  const uint64_t* capacities, int numLevels,
313  unordered_map<uint64_t, uint64_t>* missPairs) {
314  for (int lvl = 0; lvl < numLevels; lvl++) {
315  bool isMiss = isFirstUse
316  ? (tree.GetTotalSum() > capacities[lvl])
317  : (reuseDistance > capacities[lvl]);
318  if (isMiss) {
319  auto* victim = tree.FindNodeAtRankFromRight(capacities[lvl]);
320  if (victim) {
321  RecordEvictionPair(victim->payload, curCtxt, missPairs[lvl]);
322  }
323  }
324  }
325 }
326 
327 static inline uint64_t ComputeBlockReuseDistance(uint64_t prevTick, uint64_t newTick, uint32_t v, InsReuseThreadData* tData, RBTree_t* rbt) {
328  // Find how many RB-Tree nodes are to the right of this node.
329  uint64_t reuseDist;
330  auto* node = rbt->FindSumGreaterThan(prevTick, &reuseDist);
331  if (node) {
332  // Delete the node from RB-tree
333  auto* retNode = rbt->Delete(node);
334  // reinsert the node with new tick
335  retNode->key = newTick;
336  retNode->value = v;
337  rbt->Insert(retNode);
338  return reuseDist;
339  } else {
340  return FIRST_USE;
341  }
342 }
343 
344 
345 static inline void AnalyzeInsLevelReuse(void* insAddr, uint32_t numInsInBBL, uint32_t slot, THREADID threadId) {
346  assert(0 != numInsInBBL);
347  InsReuseThreadData* tData = ClientGetTLS(threadId);
348  tuple<uint64_t[SHADOW_PAGE_SIZE]>& t = tData->smIns.GetOrCreateShadowBaseAddress((uint64_t)insAddr);
349  uint64_t* shadowMemAddr = &(get<0>(t)[PAGE_OFFSET((uint64_t)insAddr)]);
350 
351  uint64_t prevTick = *shadowMemAddr;
352  uint64_t newTick = tData->numInsExecuted;
353  uint64_t reuseDistance = FIRST_USE;
354  bool isFirstUse = (prevTick == 0);
355 
357  if (gCCTEnabled) {
358  curCtxt = GetContextHandle(threadId, slot);
359  }
360 
361  if (isFirstUse) {
362  tData->numInsExecuted += numInsInBBL;
363  tData->footprint += numInsInBBL;
364  auto* newNode = new TreeNode<uint64_t, uint32_t, uint64_t>(tData->numInsExecuted, numInsInBBL);
365  newNode->payload = curCtxt;
366  tData->insRBTree.Insert(newNode);
367  *shadowMemAddr = tData->numInsExecuted;
368  } else {
369  if (prevTick != newTick) {
370  tData->numInsExecuted += numInsInBBL;
371  }
372  reuseDistance = ComputeInsReuseDistance(prevTick, tData->numInsExecuted, numInsInBBL, tData, &tData->insRBTree, curCtxt);
373  assert(FIRST_USE != reuseDistance);
374  UpdateInsReuseStats(reuseDistance, numInsInBBL, tData);
375  *shadowMemAddr = tData->numInsExecuted;
376  }
377 
378  if (gCCTEnabled) {
379  // Check L1i, L2, L3 evictions
381  reuseDistance, isFirstUse,
382  gCacheCapacities, 3, tData->missPairs);
383 
384  // iTLB page-level check
385  uint64_t page = (uint64_t)insAddr >> 12;
386  tuple<uint64_t[SHADOW_PAGE_SIZE]>& pt = tData->smITLBPage.GetOrCreateShadowBaseAddress(page);
387  uint64_t* pageShadow = &(get<0>(pt)[PAGE_OFFSET(page)]);
388  uint64_t pagePrevTick = *pageShadow;
389  bool pageFirstUse = (pagePrevTick == 0);
390 
391  tData->itlbTick++;
392  uint64_t pageNewTick = tData->itlbTick;
393 
394  if (pageFirstUse) {
395  tData->itlbFootprint++;
396  auto* newNode = new TreeNode<uint64_t, uint32_t, uint64_t>(pageNewTick, 1);
397  newNode->payload = curCtxt;
398  tData->itlbRBTree.Insert(newNode);
399  } else {
400  uint64_t pageReuseDist;
401  auto* node = tData->itlbRBTree.FindSumGreaterThan(pagePrevTick, &pageReuseDist);
402  if (node) {
403  auto* retNode = tData->itlbRBTree.Delete(node);
404  retNode->key = pageNewTick;
405  retNode->value = 1;
406  retNode->payload = curCtxt;
407  tData->itlbRBTree.Insert(retNode);
408  }
409 
410  // Check iTLB eviction
411  uint64_t itlbCap = gCacheCapacities[3];
412  bool itlbMiss = (pageReuseDist > itlbCap);
413  if (itlbMiss) {
414  auto* victim = tData->itlbRBTree.FindNodeAtRankFromRight(itlbCap);
415  if (victim) {
416  RecordEvictionPair(victim->payload, curCtxt, tData->missPairs[3]);
417  }
418  }
419  }
420 
421  *pageShadow = pageNewTick;
422  }
423 }
424 
425 template <int blockSize, int blkIdx>
426 static inline void AnalyzeBlockLevelReuse(void* block, uint32_t numInsInBlock, THREADID threadId) {
427  assert(0 != numInsInBlock);
428  // blockSize must be a power of 2.
429  assert(1 == __builtin_popcountll(blockSize));
430 
431  InsReuseThreadData* tData = ClientGetTLS(threadId);
432  auto* blockData = &(tData->blockData[blkIdx]);
433 
434  // Fast path: if block == prevBlock, simply increment the histo with 0 reuse distance and return
435  if (blockData->prevBlock == block) {
436  UpdateBlockReuseStats(0 /*reuseDistance*/, numInsInBlock, blockData->reuseHisto);
437  return;
438  }
439  // update blockData->prevBlock to block.
440  blockData->prevBlock = block;
441 
442  tuple<uint64_t[SHADOW_PAGE_SIZE]>& t = blockData->sm.GetOrCreateShadowBaseAddress((size_t)block);
443  uint64_t* shadowMemAddr = &(get<0>(t)[PAGE_OFFSET((size_t)block)]);
444  uint64_t prevTick = *shadowMemAddr;
445  uint64_t newTick = ++blockData->numBlocksCounter;
446 
447  if (prevTick == 0 /* first use */) {
448  // needs a new insertion
449  auto* newNode = new TreeNode<uint64_t, uint32_t, uint64_t>(newTick, 1);
450  blockData->rbTree.Insert(newNode);
451  blockData->footprint++;
452 
453  if (numInsInBlock > 1) {
454  // The rest of the instructions on the same cacheline have a zero distance
455  blockData->reuseHisto[0] += numInsInBlock - 1;
456  }
457  } else {
458  // TODO: Optimization: if the last block access is same as this one, we can bypass this RB-Tree deletion, insertion
459  // and instead directly increment the key. Need to be careful not to violate any tree properties.
460  // May have to check to make sure that the node value is also unchanged.
461  //uint64_t reuseDistance = ComputeBlockReuseDistance(prevTick, blockData->numBlocksCounter, numInsInBlock, tData, & blockData->rbTree);
462  uint64_t reuseDistance = ComputeBlockReuseDistance(prevTick, newTick, 1, tData, &blockData->rbTree);
463  assert(FIRST_USE != reuseDistance);
464  UpdateBlockReuseStats(reuseDistance, numInsInBlock, blockData->reuseHisto);
465  }
466  *shadowMemAddr = newTick;
467 }
468 
469 
470 static void InstrumentInsLevelReuse(TRACE trace, void* f) {
471  // Instruction-level reuse distance
472  for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) {
473  uint32_t totInsInBbl = BBL_NumIns(bbl);
474  ADDRINT insAddr = BBL_Address(bbl);
475  uint32_t slot = 0;
476  if (gCCTEnabled) {
477  auto it = gAddrToSlot.find(insAddr);
478  if (it != gAddrToSlot.end()) {
479  slot = it->second;
480  }
481  }
482  BBL_InsertCall(bbl,
483  IPOINT_BEFORE, (AFUNPTR)AnalyzeInsLevelReuse,
484  IARG_ADDRINT, insAddr,
485  IARG_UINT32, totInsInBbl,
486  IARG_UINT32, slot,
487  IARG_THREAD_ID, IARG_END);
488  }
489 }
490 
491 template <int blockSize, int blkIdx>
492 static void InstrumentMemBlockLevelReuse(TRACE trace, void* f) {
493  // blockSize must be a power of 2.
494  assert(1 == __builtin_popcountll(blockSize));
495  const size_t blockBits = __builtin_ctzll(blockSize);
496  const size_t blockMask = ~(blockSize - 1);
497 
498  // Cacheline-level reuse distance
499  for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) {
500  uint32_t numInsInMemBlock = 0;
501  size_t prevMemBlock = GetBlock((uint64_t)INS_Address(BBL_InsHead(bbl)), blockMask);
502  for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins)) {
503  // Does instruction straddle two mem blocks?
504  size_t insStartMemBlock = GetBlock((size_t)INS_Address(ins), blockMask);
505  size_t insEndMemBlock = GetBlock((size_t)(INS_Address(ins) + INS_Size(ins) - 1), blockMask);
506 
507  // +1 for the ongoing mem block
508  if (insStartMemBlock == prevMemBlock) {
509  numInsInMemBlock++;
510  }
511 
512  // Assumption: ins can never span more than one memory block.
513  // Continue adding more to this mem block
514  if (insEndMemBlock == prevMemBlock) {
515  continue;
516  }
517  // either the ins ends in a new block or begins on a new block.
518 
519  INS_InsertCall(ins,
520  IPOINT_BEFORE, (AFUNPTR)AnalyzeBlockLevelReuse<blockSize, blkIdx>,
521  IARG_ADDRINT, prevMemBlock,
522  IARG_UINT32, numInsInMemBlock,
523  IARG_THREAD_ID, IARG_END);
524  numInsInMemBlock = 1;
525  prevMemBlock = insEndMemBlock;
526  }
527 
528  INS_InsertCall(BBL_InsTail(bbl),
529  IPOINT_BEFORE, (AFUNPTR)AnalyzeBlockLevelReuse<blockSize, blkIdx>,
530  IARG_ADDRINT, prevMemBlock,
531  IARG_UINT32, numInsInMemBlock,
532  IARG_THREAD_ID, IARG_END);
533  }
534 }
535 
536 //instrument the trace, count the number of ins in the trace and instrument each BBL
537 static void InstrumentTrace(TRACE trace, void* f) {
538  InstrumentInsLevelReuse(trace, f);
539 
540  // Not using a loop yet, although it can be done. I want to write it such that porting to non c++11 is not too hard.
541  InstrumentMemBlockLevelReuse<BLK_INFO[0].blkSize, 0>(trace, f);
542  InstrumentMemBlockLevelReuse<BLK_INFO[1].blkSize, 1>(trace, f);
543  InstrumentMemBlockLevelReuse<BLK_INFO[2].blkSize, 2>(trace, f);
544 }
545 
546 // On each Unload of a loaded image, the accummulated redundancy information is dumped
547 static VOID ImageUnload(IMG img, VOID* v) {
548  fprintf(gTraceFile, "\n TODO .. Multi-threading is not well supported.");
549  fprintf(gTraceFile, "\nUnloading %s", IMG_Name(img).c_str());
550  PIN_LockClient();
551 
552  if (gCCTEnabled && IMG_IsMainExecutable(img)) {
553  for (int lvl = 0; lvl < NUM_CACHE_LEVELS; lvl++) {
554  auto& pairMap = GLOBAL_STATS.missPairs[lvl];
555  if (pairMap.empty())
556  continue;
557 
558  vector<pair<uint64_t, uint64_t>> sorted(pairMap.begin(), pairMap.end());
559  sort(sorted.begin(), sorted.end(),
560  [](const pair<uint64_t, uint64_t>& a, const pair<uint64_t, uint64_t>& b) {
561  return a.second > b.second;
562  });
563 
564  uint64_t totalMisses = 0;
565  for (auto& kv : sorted)
566  totalMisses += kv.second;
567 
568  fprintf(gTraceFile, "\n\n===== %s Eviction Pairs (capacity=%lu, total misses=%lu) =====\n",
569  gCacheLevelNames[lvl], gCacheCapacities[lvl], totalMisses);
570 
571  json jPairs;
572  jPairs["Metric"] = string("EvictionPairs_") + gCacheLevelNames[lvl];
573  jPairs["Capacity"] = gCacheCapacities[lvl];
574  jPairs["TotalMisses"] = totalMisses;
575 
576  uint32_t topN = min((uint32_t)sorted.size(), KnobTopN.Value());
577  for (uint32_t i = 0; i < topN; i++) {
578  uint64_t key = sorted[i].first;
579  uint64_t count = sorted[i].second;
580  ContextHandle_t evictedCtxt = (ContextHandle_t)(key >> 32);
581  ContextHandle_t incomingCtxt = (ContextHandle_t)(key & 0xFFFFFFFF);
582  double pct = (double)count / totalMisses * 100.0;
583 
584  fprintf(gTraceFile, "\n--- Rank %u: count=%lu (%.2f%%) ---\n", i + 1, count, pct);
585  fprintf(gTraceFile, " Evicted context:\n");
586  PrintFullCallingContext(evictedCtxt);
587  fprintf(gTraceFile, " Incoming context:\n");
588  PrintFullCallingContext(incomingCtxt);
589 
590  json jEntry;
591  jEntry["rank"] = i + 1;
592  jEntry["count"] = count;
593  jEntry["percentage"] = pct;
594 
595  vector<Context> evictedPath, incomingPath;
596  GetFullCallingContext(evictedCtxt, evictedPath);
597  GetFullCallingContext(incomingCtxt, incomingPath);
598 
599  for (auto& c : evictedPath)
600  jEntry["evicted"].push_back(c.functionName);
601  for (auto& c : incomingPath)
602  jEntry["incoming"].push_back(c.functionName);
603 
604  jPairs["pairs"].push_back(jEntry);
605  }
606  gJSONFile << jPairs.dump(4) << "\n";
607  }
608  }
609 
610  PIN_UnlockClient();
611 }
612 
613 static void DumpHisto(uint64_t* histo, uint64_t footprint, const string& key1, const string& key2) {
614  double total = 0;
615  for (int i = 0; i < MAX_REUSE_DISTANCE_BINS; i++) {
616  total += histo[i];
617  }
618  for (int i = 0; i < MAX_REUSE_DISTANCE_BINS; i++) {
619  fprintf(gTraceFile, "\n %2d %e (%.2lf%%)", i, (double)histo[i], histo[i] / total * 100);
620  }
621  fflush(gTraceFile);
622 
623  json j;
624  j["Source"] = key1;
625  j["Metric"] = key2;
626  for (int i = 0; i < MAX_REUSE_DISTANCE_BINS; i++) {
627  j["raw"].push_back(histo[i]);
628  j["relative"].push_back(histo[i] * 1.0 / total);
629  }
630  j["Footprint"] = footprint;
631  gJSONFile << j.dump(4) << "\n";
632 }
633 
634 static VOID ThreadFiniFunc(THREADID threadid, const CONTEXT* ctxt, INT32 code, VOID* v) {
635  InsReuseThreadData* tData = ClientGetTLS(threadid);
636 
637  // Take the good old lock (don't expect much contention unless threads are created and destroyed as if there is no tomorrow)
638  PIN_GetLock(&GLOBAL_STATS.lock, threadid);
639 
640  GLOBAL_STATS.numInsExecuted += tData->numInsExecuted;
641  // can't add the footprint because it is not additive (non unique)
642  for (int i = 0; i < MAX_REUSE_DISTANCE_BINS; i++) {
643  GLOBAL_STATS.insReuseHisto[i] += tData->insReuseHisto[i];
644  }
645  fprintf(gTraceFile, "\nTID %d instruction-reuse histo (ins footprint = %e)", threadid, (double)tData->footprint);
646 
647  DumpHisto(tData->insReuseHisto, tData->footprint, "TID " + to_string(threadid), "InsReuse");
648 
649  for (int j = 0; j < NUM_BLOCKS; j++) {
650  GLOBAL_STATS.blockData[j].numBlocksCounter += tData->blockData[j].numBlocksCounter;
651  // can't add the footprint because it is not additive (non unique)
652  for (int i = 0; i < MAX_REUSE_DISTANCE_BINS; i++) {
653  GLOBAL_STATS.blockData[j].reuseHisto[i] += tData->blockData[j].reuseHisto[i];
654  }
655  fprintf(gTraceFile, "\nTID %d %s histo (%zu byte blks footprint = %e)", threadid, BLK_INFO[j].blkDescription, BLK_INFO[j].blkSize, (double)tData->blockData[j].footprint);
656 
657  DumpHisto(tData->blockData[j].reuseHisto, tData->blockData[j].footprint, "TID " + to_string(threadid), BLK_INFO[j].blkDescription);
658  }
659 
660  if (gCCTEnabled) {
661  for (int lvl = 0; lvl < NUM_CACHE_LEVELS; lvl++) {
662  for (auto& kv : tData->missPairs[lvl]) {
663  GLOBAL_STATS.missPairs[lvl][kv.first] += kv.second;
664  }
665  }
666  }
667 
668  // release the lock
669  PIN_ReleaseLock(&GLOBAL_STATS.lock);
670 }
671 
672 /*
673  static void PrintStats() {
674  struct rusage rusage;
675  getrusage(RUSAGE_SELF, &rusage);
676  size_t peakRSS = (size_t)(rusage.ru_maxrss);
677  }
678 
679  */
680 
681 static VOID FiniFunc(INT32 code, VOID* v) {
682  // Peak memory
683  struct rusage rusage;
684  getrusage(RUSAGE_SELF, &rusage);
685  size_t peakRSS = (size_t)(rusage.ru_maxrss);
686  struct timeval ut = rusage.ru_utime;
687  struct timeval st = rusage.ru_stime;
688 
689  fprintf(gTraceFile, "\nWhole program instruction-reuse histo ((ins footprint = %e)", (double)GLOBAL_STATS.footprint);
690  DumpHisto(GLOBAL_STATS.insReuseHisto, GLOBAL_STATS.footprint /* basically 0 */, "Whole program", "InsReuse");
691  for (int i = 0; i < NUM_BLOCKS; i++) {
692  fprintf(gTraceFile, "\nWhole program %s histo (%zu byte blks footprint = %e)", BLK_INFO[i].blkDescription, BLK_INFO[i].blkSize, (double)GLOBAL_STATS.blockData[i].footprint);
693  DumpHisto(GLOBAL_STATS.blockData[i].reuseHisto, GLOBAL_STATS.blockData[i].footprint /* basically 0*/, "Whole program", BLK_INFO[i].blkDescription);
694  }
695 
696  fprintf(gTraceFile, "\n ------- \n");
697  fprintf(gTraceFile, "\nutime: %lu", ut.tv_sec * 1000000 + ut.tv_usec);
698  fprintf(gTraceFile, "\nstime: %lu", st.tv_sec * 1000000 + st.tv_usec);
699  fprintf(gTraceFile, "\nRSS: %zu", peakRSS);
700  fprintf(gTraceFile, "\n EOF");
701 
702  json j;
703  j["utime"] = ut.tv_sec * 1000000 + ut.tv_usec;
704  j["stime"] = st.tv_sec * 1000000 + st.tv_usec;
705  j["RSS"] = peakRSS;
706  gJSONFile << j.dump(4) << "\n";
707  gJSONFile.close();
708  fclose(gTraceFile);
709 }
710 
711 static void InitThreadData(InsReuseThreadData* tdata) {
712  tdata->bytesLoad = 0;
713  tdata->sampleFlag = true;
714  tdata->numIns = 0;
715 
716  for (int i = 0; i < NUM_BLOCKS; i++) {
717  tdata->blockData[i].numBlocksCounter = 0x42; // the start of clock
718  tdata->blockData[i].footprint = 0; // the start of clock
719  tdata->blockData[i].prevBlock = NULL; // last accessed block
720  memset(tdata->blockData[i].reuseHisto, 0, sizeof(uint64_t) * MAX_REUSE_DISTANCE_BINS);
721  }
722 
723  tdata->numInsExecuted = 0x42; // the start of clock
724  tdata->footprint = 0x0; // the start of clock
725  memset(tdata->insReuseHisto, 0, sizeof(uint64_t) * MAX_REUSE_DISTANCE_BINS);
726 
727  tdata->itlbTick = 0x42;
728  tdata->itlbFootprint = 0;
729 }
730 
731 static VOID ThreadStart(THREADID threadid, CONTEXT* ctxt, INT32 flags, VOID* v) {
732  InsReuseThreadData* tdata = new InsReuseThreadData();
733  InitThreadData(tdata);
734  // __sync_fetch_and_add(&gClientNumThreads, 1);
735 #ifdef MULTI_THREADED
736  PIN_SetThreadData(client_tls_key, tdata, threadid);
737 #else
738  gSingleThreadedTData = tdata;
739 #endif
740 }
741 
742 // NOLINTBEGIN(bugprone-exception-escape) -- Pin setup calls do not throw
743 // in practice, and Pin's harness terminates the process on unhandled exceptions.
744 int main(int argc, char* argv[]) {
745  // Initialize PIN
746  if (PIN_Init(argc, argv))
747  return Usage();
748 
749  // Initialize Symbols, we need them to report functions and lines
750  PIN_InitSymbols();
751 
752  // Init Client
753  ClientInit(argc, argv);
754 
755  // Initialize CCTLib if -cct is enabled
756  gCCTEnabled = KnobCCT.Value();
757  if (gCCTEnabled) {
759  gCacheCapacities[0] = KnobL1iCap.Value();
760  gCacheCapacities[1] = KnobL2Cap.Value();
761  gCacheCapacities[2] = KnobL3Cap.Value();
762  gCacheCapacities[3] = KnobITLBCap.Value();
763  }
764 
765  // Obtain a key for TLS storage.
766  client_tls_key = PIN_CreateThreadDataKey(nullptr /*TODO have a destructir*/);
767  // Register ThreadStart to be called when a thread starts.
768  PIN_AddThreadStartFunction(ThreadStart, nullptr);
769  // fini function for post-mortem analysis
770  PIN_AddThreadFiniFunction(ThreadFiniFunc, nullptr);
771  PIN_AddFiniFunction(FiniFunc, nullptr);
772 
773  TRACE_AddInstrumentFunction(InstrumentTrace, nullptr);
774 
775  // Register ImageUnload to be called when an image is unloaded
776  IMG_AddUnloadFunction(ImageUnload, nullptr);
777 
778  // Launch program now
779  PIN_StartProgram();
780  return 0;
781 }
782 // NOLINTEND(bugprone-exception-escape)
#define MAX_FILE_PATH
Definition: cctlib.H:18
#define INTERESTING_INS_ALL
Definition: cctlib.H:85
TNKV * FindSumGreaterThan(K key, S *sum)
Definition: rbtree.h:101
void Insert(TNKV *newNode)
Definition: rbtree.h:426
S GetTotalSum()
Definition: rbtree.h:777
TNKV * Delete(TNKV *node)
Definition: rbtree.h:625
TNKV * FindNodeAtRankFromRight(S capacity)
Definition: rbtree.h:786
ShadowTuple< Ts... > & GetOrCreateShadowBaseAddress(const size_t address)
static void UpdateInsReuseStats(uint64_t distance, uint64_t count, InsReuseThreadData *tData)
static void RecordEvictionPair(ContextHandle_t victimCtxt, ContextHandle_t curCtxt, unordered_map< uint64_t, uint64_t > &pairMap)
#define MAX_REUSE_DISTANCE
static void ClientInit(int argc, char *argv[])
int main(int argc, char *argv[])
static FILE * gTraceFile
static unordered_map< ADDRINT, uint32_t > gAddrToSlot
xed_state_t xedState
static VOID ImageUnload(IMG img, VOID *v)
uint64_t numBlocksCounter
static void CheckEvictions(RBTree_t &tree, ContextHandle_t curCtxt, uint64_t reuseDistance, bool isFirstUse, const uint64_t *capacities, int numLevels, unordered_map< uint64_t, uint64_t > *missPairs)
char * blkDescription
static KNOB< UINT64 > KnobITLBCap(KNOB_MODE_WRITEONCE, "pintool", "itlb_cap", "64", "iTLB capacity in 4K pages")
unordered_map< uint64_t, uint64_t > missPairs[NUM_CACHE_LEVELS]
char dummy2[128]
static void DumpHisto(uint64_t *histo, uint64_t footprint, const string &key1, const string &key2)
uint64_t insReuseHisto[MAX_REUSE_DISTANCE_BINS]
static void InstrumentMemBlockLevelReuse(TRACE trace, void *f)
static void InitThreadData(InsReuseThreadData *tdata)
static INT32 Usage()
static ofstream gJSONFile
size_t blkSize
static struct @9 GLOBAL_STATS
static const char * gCacheLevelNames[NUM_CACHE_LEVELS]
static KNOB< UINT64 > KnobL3Cap(KNOB_MODE_WRITEONCE, "pintool", "l3_cap", "2097152", "L3 capacity in unique instructions")
static uint64_t ComputeBlockReuseDistance(uint64_t prevTick, uint64_t newTick, uint32_t v, InsReuseThreadData *tData, RBTree_t *rbt)
#define NUM_BLOCKS
constexpr struct @8 BLK_INFO[NUM_BLOCKS]
static InsReuseThreadData * gSingleThreadedTData
PIN_LOCK lock
uint64_t reuseHisto[MAX_REUSE_DISTANCE_BINS]
static uint64_t gCacheCapacities[NUM_CACHE_LEVELS]
static void InstrumentTrace(TRACE trace, void *f)
static void AnalyzeBlockLevelReuse(void *block, uint32_t numInsInBlock, THREADID threadId)
static KNOB< UINT64 > KnobL2Cap(KNOB_MODE_WRITEONCE, "pintool", "l2_cap", "65536", "L2 capacity in unique instructions")
static KNOB< UINT32 > KnobTopN(KNOB_MODE_WRITEONCE, "pintool", "topn", "100", "Top-N eviction pairs to report per level")
uint64_t footprint
static void AnalyzeInsLevelReuse(void *insAddr, uint32_t numInsInBBL, uint32_t slot, THREADID threadId)
static VOID FiniFunc(INT32 code, VOID *v)
struct @9::@11 blockData[NUM_BLOCKS]
InsReuseThreadData * ClientGetTLS(const THREADID threadId)
static size_t GetBlock(size_t addr, size_t blockMask)
static void UpdateBlockReuseStats(uint64_t distance, uint64_t count, uint64_t *reuseHisto)
static VOID ThreadFiniFunc(THREADID threadid, const CONTEXT *ctxt, INT32 code, VOID *v)
nlohmann::json json
char dummy1[128]
static VOID InstrumentInsCallback(INS ins, VOID *v, const uint32_t slot)
struct @7 InsReuseGlobals
static KNOB< bool > KnobCCT(KNOB_MODE_WRITEONCE, "pintool", "cct", "0", "Enable CCT-level LRU eviction pair tracking")
static void InstrumentInsLevelReuse(TRACE trace, void *f)
static bool gCCTEnabled
#define MAX_REUSE_DISTANCE_BINS
static TLS_KEY client_tls_key
uint64_t numInsExecuted
#define CACHELINE_SIZE
#define FIRST_USE
#define NUM_CACHE_LEVELS
static uint64_t ComputeInsReuseDistance(uint64_t prevTick, uint64_t newTick, uint32_t v, InsReuseThreadData *tData, RBTree_t *rbt, uint32_t payload=0)
static VOID ThreadStart(THREADID threadid, CONTEXT *ctxt, INT32 flags, VOID *v)
static KNOB< UINT64 > KnobL1iCap(KNOB_MODE_WRITEONCE, "pintool", "l1i_cap", "8192", "L1i capacity in unique instructions")
int PinCCTLibInit(IsInterestingInsFptr isInterestingIns, FILE *logFile, CCTLibInstrumentInsCallback userCallback, VOID *userCallbackArg, BOOL doDataCentric=false)
Definition: cctlib.cpp:3132
hpcrun_metricFlags_t flags
Definition: cctlib.cpp:3425
VOID PrintFullCallingContext(const ContextHandle_t ctxtHandle)
Definition: cctlib.cpp:2346
uint16_t size_t
Definition: cctlib.cpp:3359
VOID GetFullCallingContext(const ContextHandle_t curCtxtHndle, std::vector< Context > &contextVec)
Definition: cctlib.cpp:2339
ContextHandle_t GetContextHandle(const THREADID id, const uint32_t slot)
Definition: cctlib.cpp:1356
uint32_t ContextHandle_t
Definition: cctlib.H:22
#define PAGE_OFFSET(addr)
Definition: shadow_memory.H:15
uint64_t insReuseHisto[MAX_REUSE_DISTANCE_BINS]
ShadowMemory< uint64_t > smITLBPage
ShadowMemory< uint64_t > smIns
unordered_map< uint64_t, uint64_t > missPairs[NUM_CACHE_LEVELS]
struct InsReuseThreadData::@10 blockData[NUM_BLOCKS]
uint64_t reuseHisto[MAX_REUSE_DISTANCE_BINS]
ShadowMemory< uint64_t > sm
K key
Definition: rbtree.h:22
void f()
Definition: test1.c:23
int a[1000]
Definition: testArray.c:5