GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/context/cdhashmap.h Lines: 120 123 97.6 %
Date: 2021-09-10 Branches: 510 1285 39.7 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Morgan Deters, Tim King, Dejan Jovanovic
4
 *
5
 * This file is part of the cvc5 project.
6
 *
7
 * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
8
 * in the top-level source directory and their institutional affiliations.
9
 * All rights reserved.  See the file COPYING in the top-level source
10
 * directory for licensing information.
11
 * ****************************************************************************
12
 *
13
 * Context-dependent map class.
14
 *
15
 * Generic templated class for a map which must be saved and restored as
16
 * contexts are pushed and popped.  Requires that operator= be defined for the
17
 * data class, and operator== for the key class.  For key types that don't have
18
 * a std::hash<>, you should provide an explicit HashFcn.
19
 *
20
 * See also:
21
 *  CDInsertHashMap : An "insert-once" CD hash map.
22
 *  CDTrailHashMap : A lightweight CD hash map with poor iteration
23
 *    characteristics and some quirks in usage.
24
 *
25
 * Internal documentation:
26
 *
27
 * CDHashMap<> is something of a work in progress at present (26 May
28
 * 2010), due to some recent discoveries of problems with its
29
 * internal state.  Here are some notes on the internal use of
30
 * CDOhash_maps that may be useful in figuring out this mess:
31
 *
32
 *     So you have a CDHashMap<>.
33
 *
34
 *     You insert some (key,value) pairs.  Each allocates a CDOhash_map<>
35
 *     and goes on a doubly-linked list headed by map.d_first and
36
 *     threaded via CDOhash_map.{d_prev,d_next}.  CDOhash_maps are constructed
37
 *     with a NULL d_map pointer, but then immediately call
38
 *     makeCurrent() and set the d_map pointer back to the map.  At
39
 *     context level 0, this doesn't lead to anything special.  In
40
 *     higher context levels, this stores away a CDOhash_map with a NULL
41
 *     map pointer at level 0, and a non-NULL map pointer in the
42
 *     current context level.  (Remember that for later.)
43
 *
44
 *     When a key is associated to a new value in a CDHashMap, its
45
 *     associated CDOhash_map calls makeCurrent(), then sets the new
46
 *     value.  The save object is also a CDOhash_map (allocated in context
47
 *     memory).
48
 *
49
 *     Now, CDOhash_maps disappear in a variety of ways.
50
 *
51
 *     First, you might pop beyond a "modification of the value"
52
 *     scope level, requiring a re-association of the key to an old
53
 *     value.  This is easy.  CDOhash_map::restore() does the work, and
54
 *     the context memory of the save object is reclaimed as usual.
55
 *
56
 *     Second, you might pop beyond a "insert the key" scope level,
57
 *     requiring that the key be completely removed from the map and
58
 *     its CDOhash_map object memory freed.  Here, the CDOhash_map is restored
59
 *     to a "NULL-map" state (see above), signaling it to remove
60
 *     itself from the map completely and put itself on a "trash
61
 *     list" for its scope.
62
 *
63
 *     Third, you might delete the cdhashmap(calling CDHashMap::~CDHashMap()).
64
 *     This first calls destroy(), as per ContextObj contract, but
65
 *     cdhashmapdoesn't save/restore itself, so that does nothing at the
66
 *     CDHashMap-level. Then, for each element in the map, it marks it as being
67
 *     "part of a complete map destruction", which essentially short-circuits
68
 *     CDOhash_map::restore() (see CDOhash_map::restore()), then deallocates
69
 *     it.
70
 *
71
 *     Fourth, you might clear() the CDHashMap.  This does exactly the
72
 *     same as CDHashMap::~CDHashMap(), except that it doesn't call destroy()
73
 *     on itself.
74
 *
75
 *     ContextObj::deleteSelf() calls the CDOhash_map destructor, then
76
 *     frees the memory associated to the CDOhash_map.
77
 *     CDOhash_map::~CDOhash_map() calls destroy(), which restores as much as
78
 *     possible.
79
 */
80
81
#include "cvc5_private.h"
82
83
#ifndef CVC5__CONTEXT__CDHASHMAP_H
84
#define CVC5__CONTEXT__CDHASHMAP_H
85
86
#include <functional>
87
#include <iterator>
88
#include <unordered_map>
89
90
#include "base/check.h"
91
#include "context/cdhashmap_forward.h"
92
#include "context/context.h"
93
94
namespace cvc5 {
95
namespace context {
96
97
// Auxiliary class: almost the same as CDO (see cdo.h)
98
99
template <class Key, class Data, class HashFcn = std::hash<Key> >
100
class CDOhash_map : public ContextObj
101
{
102
  friend class CDHashMap<Key, Data, HashFcn>;
103
104
 public:
105
  // The type of the <Key, Data> pair mapped by this class.
106
  //
107
  // Implementation:
108
  // The data and key visible to users of CDHashMap are only visible through
109
  // const references. Thus the type of dereferencing a
110
  // CDHashMap<Key, Data>::iterator.second is intended to always be a
111
  // `const Data&`. (Otherwise, to get a Data& safely, access operations
112
  // would need to makeCurrent() to get the Data&, which is an unacceptable
113
  // performance hit.) To allow for the desired updating in other scenarios, we
114
  // store a std::pair<const Key, const Data> and break the const encapsulation
115
  // when necessary.
116
  using value_type = std::pair<const Key, const Data>;
117
118
 private:
119
  value_type d_value;
120
121
  // See documentation of value_type for why this is needed.
122
58548599
  Key& mutable_key() { return const_cast<Key&>(d_value.first); }
123
  // See documentation of value_type for why this is needed.
124
194550887
  Data& mutable_data() { return const_cast<Data&>(d_value.second); }
125
126
  CDHashMap<Key, Data, HashFcn>* d_map;
127
128
  // Doubly-linked list for keeping track of elements in order of insertion
129
  CDOhash_map* d_prev;
130
  CDOhash_map* d_next;
131
132
58548602
  ContextObj* save(ContextMemoryManager* pCMM) override
133
  {
134
58548602
    return new (pCMM) CDOhash_map(*this);
135
  }
136
137
58548599
  void restore(ContextObj* data) override
138
  {
139
58548599
    CDOhash_map* p = static_cast<CDOhash_map*>(data);
140
58548599
    if (d_map != NULL)
141
    {
142
58495517
      if (p->d_map == NULL)
143
      {
144
57297971
        Assert(d_map->d_map.find(getKey()) != d_map->d_map.end()
145
               && (*d_map->d_map.find(getKey())).second == this);
146
        // no longer in map (popped beyond first level in which it was)
147
57297971
        d_map->d_map.erase(getKey());
148
        // If we call deleteSelf() here, it re-enters restore().  So,
149
        // put it on a "trash heap" instead, for later deletion.
150
        //
151
        // FIXME multithreading
152
57297971
        if (d_map->d_first == this)
153
        {
154
876438
          Debug("gc") << "remove first-elem " << this << " from map " << d_map
155
438219
                      << " with next-elem " << d_next << std::endl;
156
438219
          if (d_next == this)
157
          {
158
438201
            Assert(d_prev == this);
159
438201
            d_map->d_first = NULL;
160
          }
161
          else
162
          {
163
18
            d_map->d_first = d_next;
164
          }
165
        }
166
        else
167
        {
168
113719504
          Debug("gc") << "remove nonfirst-elem " << this << " from map "
169
56859752
                      << d_map << std::endl;
170
        }
171
57297971
        d_next->d_prev = d_prev;
172
57297971
        d_prev->d_next = d_next;
173
174
57297971
        Debug("gc") << "CDHashMap<> trash push_back " << this << std::endl;
175
        // this->deleteSelf();
176
57297971
        enqueueToGarbageCollect();
177
      }
178
      else
179
      {
180
1197546
        mutable_data() = p->get();
181
      }
182
    }
183
    // Explicitly call destructors for the key and the data as they will not
184
    // otherwise get called.
185
58548599
    p->mutable_key().~Key();
186
58548599
    p->mutable_data().~Data();
187
58548599
  }
188
189
  /** ensure copy ctor is only called by us */
190
58548602
  CDOhash_map(const CDOhash_map& other)
191
      : ContextObj(other),
192
        // don't need to save the key---and if we do we can get
193
        // refcounts for Node keys messed up and leak memory
194
        d_value(Key(), other.d_value.second),
195
58548602
        d_map(other.d_map),
196
        d_prev(NULL),
197
117097204
        d_next(NULL)
198
  {
199
58548602
  }
200
  CDOhash_map& operator=(const CDOhash_map&) = delete;
201
202
 public:
203
69394453
  CDOhash_map(Context* context,
204
              CDHashMap<Key, Data, HashFcn>* map,
205
              const Key& key,
206
              const Data& data,
207
              bool atLevelZero = false)
208
69394453
      : ContextObj(false, context), d_value(key, data), d_map(NULL)
209
  {
210
69394453
    if (atLevelZero)
211
    {
212
      // "Initializing" map insertion: this entry will never be
213
      // removed from the map, it's inserted at level 0 as an
214
      // "initializing" element.  See
215
      // CDHashMap<>::insertAtContextLevelZero().
216
35904
      mutable_data() = data;
217
    }
218
    else
219
    {
220
      // Normal map insertion: first makeCurrent(), then set the data
221
      // and then, later, the map.  Order is important; we can't
222
      // initialize d_map in the constructor init list above, because
223
      // we want the restore of d_map to NULL to signal us to remove
224
      // the element from the map.
225
226
69358549
      set(data);
227
    }
228
69394453
    d_map = map;
229
230
69394453
    CDOhash_map*& first = d_map->d_first;
231
69394453
    if (first == NULL)
232
    {
233
1646230
      first = d_next = d_prev = this;
234
1646230
      Debug("gc") << "add first-elem " << this << " to map " << d_map
235
                  << std::endl;
236
    }
237
    else
238
    {
239
135496446
      Debug("gc") << "add nonfirst-elem " << this << " to map " << d_map
240
67748223
                  << " with first-elem " << first << "[" << first->d_prev << " "
241
67748223
                  << first->d_next << std::endl;
242
67748223
      d_prev = first->d_prev;
243
67748223
      d_next = first;
244
67748223
      d_prev->d_next = this;
245
67748223
      first->d_prev = this;
246
    }
247
69394453
  }
248
249
69394446
  ~CDOhash_map() { destroy(); }
250
251
134768838
  void set(const Data& data)
252
  {
253
134768838
    makeCurrent();
254
134768838
    mutable_data() = data;
255
134768838
  }
256
257
171893913
  const Key& getKey() const { return d_value.first; }
258
259
17684538
  const Data& get() const { return d_value.second; }
260
261
123165466
  const value_type& getValue() const { return d_value; }
262
263
10621850
  operator Data() { return get(); }
264
265
65285221
  const Data& operator=(const Data& data)
266
  {
267
65285221
    set(data);
268
65285221
    return data;
269
  }
270
271
2042789
  CDOhash_map* next() const
272
  {
273
2042789
    if (d_next == d_map->d_first)
274
    {
275
147283
      return NULL;
276
    }
277
    else
278
    {
279
1895506
      return d_next;
280
    }
281
  }
282
}; /* class CDOhash_map<> */
283
284
/**
285
 * Generic templated class for a map which must be saved and restored
286
 * as contexts are pushed and popped.  Requires that operator= be
287
 * defined for the data class, and operator== for the key class.
288
 */
289
template <class Key, class Data, class HashFcn>
290
class CDHashMap : public ContextObj
291
{
292
  typedef CDOhash_map<Key, Data, HashFcn> Element;
293
  typedef std::unordered_map<Key, Element*, HashFcn> table_type;
294
295
  friend class CDOhash_map<Key, Data, HashFcn>;
296
297
  table_type d_map;
298
299
  Element* d_first;
300
  Context* d_context;
301
302
  // Nothing to save; the elements take care of themselves
303
  ContextObj* save(ContextMemoryManager* pCMM) override
304
  {
305
    Unreachable();
306
    SuppressWrongNoReturnWarning;
307
  }
308
309
  // Similarly, nothing to restore
310
  void restore(ContextObj* data) override { Unreachable(); }
311
312
  // no copy or assignment
313
  CDHashMap(const CDHashMap&) = delete;
314
  CDHashMap& operator=(const CDHashMap&) = delete;
315
316
 public:
317
3037381
  CDHashMap(Context* context)
318
3037381
      : ContextObj(context), d_map(), d_first(NULL), d_context(context)
319
  {
320
3037381
  }
321
322
3026552
  ~CDHashMap()
323
  {
324
3026552
    Debug("gc") << "cdhashmap" << this << " disappearing, destroying..."
325
                << std::endl;
326
3026552
    destroy();
327
3026552
    Debug("gc") << "cdhashmap" << this << " disappearing, done destroying"
328
                << std::endl;
329
3026552
    clear();
330
6053104
  }
331
332
3026552
  void clear()
333
  {
334
3026552
    Debug("gc") << "clearing cdhashmap" << this << ", emptying trash"
335
                << std::endl;
336
3026552
    Debug("gc") << "done emptying trash for " << this << std::endl;
337
338
15123027
    for (auto& key_element_pair : d_map)
339
    {
340
      // mark it as being a destruction (short-circuit restore())
341
12096475
      Element* element = key_element_pair.second;
342
12096475
      element->d_map = nullptr;
343
12096475
      element->deleteSelf();
344
    }
345
3026552
    d_map.clear();
346
3026552
    d_first = nullptr;
347
3026552
  }
348
349
  // The usual operators of map
350
351
155685
  size_t size() const { return d_map.size(); }
352
353
3596669
  bool empty() const { return d_map.empty(); }
354
355
  size_t count(const Key& k) const { return d_map.count(k); }
356
357
  // If a key is not present, a new object is created and inserted
358
81772213
  Element& operator[](const Key& k)
359
  {
360
81772213
    const auto res = d_map.insert({k, nullptr});
361
81772213
    if (res.second)
362
    {  // create new object
363
56210201
      res.first->second = new (true) Element(d_context, this, k, Data());
364
    }
365
81772213
    return *(res.first->second);
366
  }
367
368
13273416
  bool insert(const Key& k, const Data& d)
369
  {
370
13273416
    const auto res = d_map.insert({k, nullptr});
371
13273416
    if (res.second)
372
    {  // create new object
373
13148348
      res.first->second = new (true) Element(d_context, this, k, d);
374
    }
375
    else
376
    {
377
125068
      res.first->second->set(d);
378
    }
379
13273416
    return res.second;
380
  }
381
382
  /**
383
   * Version of insert() for CDHashMap<> that inserts data value d at
384
   * context level zero.  This is a special escape hatch for inserting
385
   * "initializing" data into the map.  Imagine something happens at a
386
   * deep context level L that causes insertion into a map, such that
387
   * the object should have an "initializing" value v1 below context
388
   * level L, and a "current" value v2 at context level L.  Then you
389
   * can (assuming key k):
390
   *
391
   *   map.insertAtContextLevelZero(k, v1);
392
   *   map.insert(k, v2);
393
   *
394
   * The justification for this "escape hatch" has to do with
395
   * variables and assignments in theories (e.g., in arithmetic).
396
   * Let's say you introduce a new variable x at some deep decision
397
   * level (thanks to lazy registration, or a splitting lemma, or
398
   * whatever).  x might be mapped to something, but for theory
399
   * implementation simplicity shouldn't disappear from the map on
400
   * backjump; rather, it can take another (legal) value, or a special
401
   * value to indicate it needs to be recomputed.
402
   *
403
   * It is an error (checked via AlwaysAssert()) to
404
   * insertAtContextLevelZero() a key that already is in the map.
405
   */
406
35904
  void insertAtContextLevelZero(const Key& k, const Data& d)
407
  {
408
35904
    AlwaysAssert(d_map.find(k) == d_map.end());
409
410
35904
    Element* obj =
411
35904
        new (true) Element(d_context, this, k, d, true /* atLevelZero */);
412
35908
    d_map.insert(std::make_pair(k, obj));
413
35904
  }
414
415
  // FIXME: no erase(), too much hassle to implement efficiently...
416
417
  using value_type = typename CDOhash_map<Key, Data, HashFcn>::value_type;
418
419
  class iterator
420
  {
421
    const Element* d_it;
422
423
   public:
424
    using iterator_category = std::input_iterator_tag;
425
    using value_type = typename CDOhash_map<Key, Data, HashFcn>::value_type;
426
    using difference_type = ptrdiff_t;
427
    using pointer = typename CDOhash_map<Key, Data, HashFcn>::value_type*;
428
    using reference = typename CDOhash_map<Key, Data, HashFcn>::value_type&;
429
430
561548706
    iterator(const Element* p) : d_it(p) {}
431
104
    iterator(const iterator& i) : d_it(i.d_it) {}
432
433
    // Default constructor
434
120444
    iterator() : d_it(nullptr) {}
435
436
    // (Dis)equality
437
76173127
    bool operator==(const iterator& i) const { return d_it == i.d_it; }
438
203872675
    bool operator!=(const iterator& i) const { return d_it != i.d_it; }
439
440
    // Dereference operators.
441
115278922
    const value_type& operator*() const { return d_it->getValue(); }
442
7886544
    const value_type* operator->() const { return &d_it->getValue(); }
443
444
    // Prefix increment
445
2042789
    iterator& operator++()
446
    {
447
2042789
      d_it = d_it->next();
448
2042789
      return *this;
449
    }
450
451
    // Postfix increment is not yet supported.
452
  }; /* class CDHashMap<>::iterator */
453
454
  typedef iterator const_iterator;
455
456
379529
  iterator begin() const { return iterator(d_first); }
457
458
401205904
  iterator end() const { return iterator(NULL); }
459
460
281304221
  iterator find(const Key& k) const
461
  {
462
281304221
    typename table_type::const_iterator i = d_map.find(k);
463
464
281304221
    if (i == d_map.end())
465
    {
466
121340948
      return end();
467
    }
468
    else
469
    {
470
159963273
      return iterator((*i).second);
471
    }
472
  }
473
474
}; /* class CDHashMap<> */
475
476
}  // namespace context
477
}  // namespace cvc5
478
479
#endif /* CVC5__CONTEXT__CDHASHMAP_H */