Static Value-Flow Analysis
PersistentPointsToCache.h
Go to the documentation of this file.
1 //===- PersistentPointsToCache.h -- Persistent points-to sets ----------------//
2 
3 /*
4  * PersistentPointsToCache.h
5  *
6  * The implementation is based on
7  * Mohamad Barbar and Yulei Sui. Hash Consed Points-To Sets.
8  * 28th Static Analysis Symposium (SAS'21)
9  *
10  * Created on: Sep 28, 2020
11  * Author: Mohamad Barbar
12  */
13 
14 #ifndef PERSISTENT_POINTS_TO_H_
15 #define PERSISTENT_POINTS_TO_H_
16 
17 #include <iomanip>
18 #include <iostream>
19 #include <vector>
20 #include <functional>
21 
22 #include "SVFIR/SVFType.h"
23 
24 namespace SVF
25 {
26 
31 template <typename Data>
33 {
34 public:
36  typedef std::function<Data(const Data &, const Data &)> DataOp;
37  // TODO: an unordered pair type may be better.
39 
41  {
42  return 0;
43  };
44 
45 public:
47  {
48  idToPts.push_back(std::make_unique<Data>());
49  ptsToId[Data()] = emptyPointsToId();
50 
51  initStats();
52  }
53 
55  void clear()
56  {
57  idToPts.clear();
58  ptsToId.clear();
59 
60  unionCache.clear();
61  complementCache.clear();
62  intersectionCache.clear();
63  }
64 
66  void reset(void)
67  {
68  clear();
69 
70  // Put the empty data back in.
71  ptsToId[Data()] = emptyPointsToId();
72  idToPts.push_back(std::make_unique<Data>());
73 
74  idCounter = 1;
75  // Cache is empty...
76  initStats();
77  }
78 
80  void remapAllPts(void)
81  {
82  for (auto &d : idToPts) d->checkAndRemap();
83 
84  // Rebuild ptsToId from idToPts.
85  ptsToId.clear();
86  for (PointsToID i = 0; i < idToPts.size(); ++i) ptsToId[*idToPts[i]] = i;
87  }
88 
91  PointsToID emplacePts(const Data &pts)
92  {
93  // Is it already in the cache?
94  typename PTSToIDMap::const_iterator foundId = ptsToId.find(pts);
95  if (foundId != ptsToId.end()) return foundId->second;
96 
97  // Otherwise, insert it.
99  idToPts.push_back(std::make_unique<Data>(pts));
100  ptsToId[pts] = id;
101 
102  return id;
103  }
104 
106  const Data &getActualPts(PointsToID id) const
107  {
108  // Check if the points-to set for ID has already been stored.
109  assert(idToPts.size() > id && "PPTC::getActualPts: points-to set not stored!");
110  return *idToPts.at(id);
111  }
112 
115  {
116  static const DataOp unionOp = [](const Data &lhs, const Data &rhs)
117  {
118  return lhs | rhs;
119  };
120 
121  ++totalUnions;
122 
123  // Order operands so we don't perform x U y and y U x separately.
124  std::pair<PointsToID, PointsToID> operands = std::minmax(lhs, rhs);
125 
126  // Property cases.
127  // EMPTY_SET U x
128  if (operands.first == emptyPointsToId())
129  {
130  ++propertyUnions;
131  return operands.second;
132  }
133 
134  // x U x
135  if (operands.first == operands.second)
136  {
137  ++propertyUnions;
138  return operands.first;
139  }
140 
141  bool opPerformed = false;
142  PointsToID result = opPts(lhs, rhs, unionOp, unionCache, true, opPerformed);
143 
144  if (opPerformed)
145  {
146  ++uniqueUnions;
147 
148  // We can use lhs/rhs here rather than our ordered operands,
149  // because the operation was commutative.
150 
151  // if x U y = z, then x U z = z,
152  if (lhs != result)
153  {
154  unionCache[std::minmax(lhs, result)] = result;
156  ++totalUnions;
157  }
158 
159  // and y U z = z.
160  if (rhs != result)
161  {
162  unionCache[std::minmax(rhs, result)] = result;
164  ++totalUnions;
165  }
166  }
167  else ++lookupUnions;
168 
169  return result;
170  }
171 
174  {
175  static const DataOp complementOp = [](const Data &lhs, const Data &rhs)
176  {
177  return lhs - rhs;
178  };
179 
181 
182  // Property cases.
183  // x - x
184  if (lhs == rhs)
185  {
187  return emptyPointsToId();
188  }
189 
190  // x - EMPTY_SET
191  if (rhs == emptyPointsToId())
192  {
194  return lhs;
195  }
196 
197  // EMPTY_SET - x
198  if (lhs == emptyPointsToId())
199  {
201  return emptyPointsToId();
202  }
203 
204  bool opPerformed = false;
205  const PointsToID result = opPts(lhs, rhs, complementOp, complementCache, false, opPerformed);
206 
207  if (opPerformed)
208  {
210 
211  // We performed lhs - rhs = result, so...
212  if (result != emptyPointsToId())
213  {
214  // result AND rhs = EMPTY_SET,
215  intersectionCache[std::minmax(result, rhs)] = emptyPointsToId();
218 
219  // and result AND lhs = result,
220  intersectionCache[std::minmax(result, lhs)] = result;
223 
224  // and result - rhs = result.
225  complementCache[std::make_pair(result, rhs)] = result;
228  }
229  }
230  else ++lookupComplements;
231 
232  return result;
233  }
234 
237  {
238  static const DataOp intersectionOp = [](const Data &lhs, const Data &rhs)
239  {
240  return lhs & rhs;
241  };
242 
244 
245  // Order operands so we don't perform x U y and y U x separately.
246  std::pair<PointsToID, PointsToID> operands = std::minmax(lhs, rhs);
247 
248  // Property cases.
249  // EMPTY_SET & x
250  if (operands.first == emptyPointsToId())
251  {
253  return emptyPointsToId();
254  }
255 
256  // x & x
257  if (operands.first == operands.second)
258  {
260  return operands.first;
261  }
262 
263  bool opPerformed = false;
264  const PointsToID result = opPts(lhs, rhs, intersectionOp, intersectionCache, true, opPerformed);
265  if (opPerformed)
266  {
268 
269  // When the result is empty, we won't be adding anything of substance.
270  if (result != emptyPointsToId())
271  {
272  // We performed lhs AND rhs = result, so...
273  // result AND rhs = result,
274  if (result != rhs)
275  {
276  intersectionCache[std::minmax(result, rhs)] = result;
279  }
280 
281  // and result AND lhs = result,
282  if (result != lhs)
283  {
284  intersectionCache[std::minmax(result, lhs)] = result;
287  }
288 
289  // Also (thanks reviewer #2)
290  // result U lhs = result,
291  if (result != emptyPointsToId() && result != lhs)
292  {
293  unionCache[std::minmax(lhs, result)] = lhs;
295  ++totalUnions;
296  }
297 
298  // And result U rhs = rhs.
299  if (result != emptyPointsToId() && result != rhs)
300  {
301  unionCache[std::minmax(rhs, result)] = rhs;
303  ++totalUnions;
304  }
305  }
306  }
307  else ++lookupIntersections;
308 
309  return result;
310  }
311 
313  void printStats(const std::string subtitle) const
314  {
315  static const unsigned fieldWidth = 25;
316  SVFUtil::outs().flags(std::ios::left);
317 
318  SVFUtil::outs() << std::setw(fieldWidth) << "UniquePointsToSets" << idToPts.size() << "\n";
319 
320  SVFUtil::outs() << std::setw(fieldWidth) << "TotalUnions" << totalUnions << "\n";
321  SVFUtil::outs() << std::setw(fieldWidth) << "PropertyUnions" << propertyUnions << "\n";
322  SVFUtil::outs() << std::setw(fieldWidth) << "UniqueUnions" << uniqueUnions << "\n";
323  SVFUtil::outs() << std::setw(fieldWidth) << "LookupUnions" << lookupUnions << "\n";
324  SVFUtil::outs() << std::setw(fieldWidth) << "PreemptiveUnions" << preemptiveUnions << "\n";
325 
326  SVFUtil::outs() << std::setw(fieldWidth) << "TotalComplements" << totalComplements << "\n";
327  SVFUtil::outs() << std::setw(fieldWidth) << "PropertyComplements" << propertyComplements << "\n";
328  SVFUtil::outs() << std::setw(fieldWidth) << "UniqueComplements" << uniqueComplements << "\n";
329  SVFUtil::outs() << std::setw(fieldWidth) << "LookupComplements" << lookupComplements << "\n";
330  SVFUtil::outs() << std::setw(fieldWidth) << "PreemptiveComplements" << preemptiveComplements << "\n";
331 
332  SVFUtil::outs() << std::setw(fieldWidth) << "TotalIntersections" << totalIntersections << "\n";
333  SVFUtil::outs() << std::setw(fieldWidth) << "PropertyIntersections" << propertyIntersections << "\n";
334  SVFUtil::outs() << std::setw(fieldWidth) << "UniqueIntersections" << uniqueIntersections << "\n";
335  SVFUtil::outs() << std::setw(fieldWidth) << "LookupIntersections" << lookupIntersections << "\n";
336  SVFUtil::outs() << std::setw(fieldWidth) << "PreemptiveIntersections" << preemptiveIntersections << "\n";
337 
338  SVFUtil::outs().flush();
339  }
340 
346  {
347  Map<Data, unsigned> allPts;
348  for (const auto &d : idToPts) allPts[*d] = 1;
349  return allPts;
350  }
351 
352  // TODO: ref count API for garbage collection.
353 
354 private:
356  {
357  // Make sure we don't overflow.
358  assert(idCounter != emptyPointsToId() && "PPTC::newPointsToId: PointsToIDs exhausted! Try a larger type.");
359  return idCounter++;
360  }
361 
365  inline PointsToID opPts(PointsToID lhs, PointsToID rhs, const DataOp &dataOp, OpCache &opCache,
366  bool commutative, bool &opPerformed)
367  {
368  std::pair<PointsToID, PointsToID> operands;
369  // If we're commutative, we want to always perform the same operation: x op y.
370  // Performing x op y sometimes and y op x other times is a waste of time.
371  if (commutative) operands = std::minmax(lhs, rhs);
372  else operands = std::make_pair(lhs, rhs);
373 
374  // Check if we have performed this operation
375  OpCache::const_iterator foundResult = opCache.find(operands);
376  if (foundResult != opCache.end()) return foundResult->second;
377 
378  opPerformed = true;
379 
380  const Data &lhsPts = getActualPts(lhs);
381  const Data &rhsPts = getActualPts(rhs);
382 
383  Data result = dataOp(lhsPts, rhsPts);
384 
385  PointsToID resultId;
386  // Intern points-to set: check if result already exists.
387  typename PTSToIDMap::const_iterator foundId = ptsToId.find(result);
388  if (foundId != ptsToId.end()) resultId = foundId->second;
389  else
390  {
391  resultId = newPointsToId();
392  idToPts.push_back(std::make_unique<Data>(result));
393  ptsToId[result] = resultId;
394  }
395 
396  // Cache the result, for hash-consing.
397  opCache[operands] = resultId;
398 
399  return resultId;
400  }
401 
403  inline void initStats(void)
404  {
405 
406  totalUnions = 0;
407  uniqueUnions = 0;
408  propertyUnions = 0;
409  lookupUnions = 0;
410  preemptiveUnions = 0;
411  totalComplements = 0;
412  uniqueComplements = 0;
414  lookupComplements = 0;
416  totalIntersections = 0;
421  }
422 
423 private:
429  std::vector<std::unique_ptr<Data>> idToPts;
432 
439 
442 
443  // Statistics:
459 };
460 
461 } // End namespace SVF
462 
463 #endif /* PERSISTENT_POINTS_TO_H_ */
const char *const string
Definition: cJSON.h:172
PointsToID unionPts(PointsToID lhs, PointsToID rhs)
Unions lhs and rhs and returns their union's ID.
PointsToID intersectPts(PointsToID lhs, PointsToID rhs)
Intersects lhs and rhs (lhs AND rhs) and returns the intersection's ID.
std::vector< std::unique_ptr< Data > > idToPts
OpCache intersectionCache
Maps two IDs to their intersection. Keys must be sorted.
std::function< Data(const Data &, const Data &)> DataOp
PTSToIDMap ptsToId
Maps points-to sets to their corresponding ID.
OpCache unionCache
Maps two IDs to their union. Keys must be sorted.
void printStats(const std::string subtitle) const
Print statistics on operations and points-to set numbers.
PointsToID complementPts(PointsToID lhs, PointsToID rhs)
Relatively complements lhs and rhs (lhs \ rhs) and returns it's ID.
Map< std::pair< PointsToID, PointsToID >, PointsToID > OpCache
void remapAllPts(void)
Remaps all points-to sets stored in the cache to the current mapping.
static PointsToID emptyPointsToId(void)
PointsToID emplacePts(const Data &pts)
Map< Data, PointsToID > PTSToIDMap
void initStats(void)
Initialises statistics variables to 0.
const Data & getActualPts(PointsToID id) const
Returns the points-to set which id represents. id must be stored in the cache.
Map< Data, unsigned > getAllPts(void)
PointsToID opPts(PointsToID lhs, PointsToID rhs, const DataOp &dataOp, OpCache &opCache, bool commutative, bool &opPerformed)
PointsToID idCounter
Used to generate new PointsToIDs. Any non-zero is valid.
OpCache complementCache
Maps two IDs to their relative complement.
void reset(void)
Resets the cache removing everything except the emptyData it was initialised with.
std::ostream & outs()
Overwrite llvm::outs()
Definition: SVFUtil.h:50
for isBitcode
Definition: BasicTypes.h:68
unsigned long long u64_t
Definition: GeneralType.h:48
std::unordered_map< Key, Value, Hash, KeyEqual, Allocator > Map
Definition: GeneralType.h:101
unsigned PointsToID
Definition: GeneralType.h:63