GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/context/cdlist.h Lines: 143 149 96.0 %
Date: 2021-09-17 Branches: 926 2496 37.1 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Morgan Deters, Tim King, Clark Barrett
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
 * Context-dependent list class (only supports append).
13
 *
14
 * This list only supports appending to the list; on backtrack, the list is
15
 * simply shortened.
16
 */
17
18
#include "cvc5_private.h"
19
20
#ifndef CVC5__CONTEXT__CDLIST_H
21
#define CVC5__CONTEXT__CDLIST_H
22
23
#include <cstring>
24
#include <iterator>
25
#include <memory>
26
#include <string>
27
28
#include "base/check.h"
29
#include "context/cdlist_forward.h"
30
#include "context/context.h"
31
#include "context/context_mm.h"
32
33
namespace cvc5 {
34
namespace context {
35
36
/**
37
 * Generic context-dependent dynamic array.  Note that for efficiency,
38
 * this implementation makes the following assumptions:
39
 *
40
 * 1. Over time, objects are only added to the list.  Objects are only
41
 *    removed when a pop restores the list to a previous state.
42
 *
43
 * 2. T objects can safely be copied using their copy constructor,
44
 *    operator=, and memcpy.
45
 */
46
template <class T, class CleanUpT, class AllocatorT>
47
class CDList : public ContextObj {
48
public:
49
50
  /** The value type with which this CDList<> was instantiated. */
51
  typedef T value_type;
52
53
  /** The cleanup type with which this CDList<> was instantiated. */
54
  typedef CleanUpT CleanUp;
55
56
  /** The allocator type with which this CDList<> was instantiated. */
57
  typedef AllocatorT Allocator;
58
59
protected:
60
61
  /**
62
   * d_list is a dynamic array of objects of type T.
63
   */
64
  T* d_list;
65
66
  /**
67
   * Number of objects in d_list
68
   */
69
  size_t d_size;
70
71
private:
72
73
  static const size_t INITIAL_SIZE = 10;
74
  static const size_t GROWTH_FACTOR = 2;
75
76
  /**
77
   * Whether to call the destructor when items are popped from the
78
   * list.  True by default, but can be set to false by setting the
79
   * second argument in the constructor to false.
80
   */
81
  bool d_callDestructor;
82
83
  /**
84
   * Allocated size of d_list.
85
   */
86
  size_t d_sizeAlloc;
87
88
  /**
89
   * The CleanUp functor.
90
   */
91
  CleanUp d_cleanUp;
92
93
  /**
94
   * Our allocator.
95
   */
96
  Allocator d_allocator;
97
98
 protected:
99
  /**
100
   * Private copy constructor used only by save().  d_list and
101
   * d_sizeAlloc are not copied: only the base class information and
102
   * d_size are needed in restore.
103
   */
104
16525414
  CDList(const CDList& l)
105
      : ContextObj(l),
106
        d_list(nullptr),
107
16525414
        d_size(l.d_size),
108
        d_callDestructor(false),
109
        d_sizeAlloc(0),
110
        d_cleanUp(l.d_cleanUp),
111
33050828
        d_allocator(l.d_allocator)
112
  {
113
33050828
    Debug("cdlist") << "copy ctor: " << this << " from " << &l << " size "
114
16525414
                    << d_size << std::endl;
115
16525414
  }
116
  CDList& operator=(const CDList& l) = delete;
117
118
 private:
119
  /**
120
   * Reallocate the array with more space. Throws bad_alloc if memory
121
   * allocation fails. Does not perform any action if there is still unused
122
   * allocated space.
123
   */
124
93478190
  void grow() {
125
186956380
    Debug("cdlist") << "grow " << this << " " << getContext()->getLevel()
126
93478190
                    << ": grow? " << d_size << " " << d_sizeAlloc << std::endl;
127
93478190
    if (d_size != d_sizeAlloc)
128
    {
129
      // If there is still unused allocated space
130
92920275
      return;
131
    }
132
1115830
    Debug("cdlist") << "grow " << this << " " << getContext()->getLevel()
133
557915
                    << ": grow!" << std::endl;
134
135
557915
    const size_t maxSize =
136
557915
        std::allocator_traits<AllocatorT>::max_size(d_allocator);
137
557915
    if (d_list == nullptr)
138
    {
139
      // Allocate an initial list if one does not yet exist
140
348370
      d_sizeAlloc = INITIAL_SIZE;
141
1045110
      Debug("cdlist") << "initial grow of cdlist " << this
142
696740
                      << " level " << getContext()->getLevel()
143
348370
                      << " to " << d_sizeAlloc << std::endl;
144
348370
      if (d_sizeAlloc > maxSize)
145
      {
146
        d_sizeAlloc = maxSize;
147
      }
148
348370
      d_list =
149
348370
          std::allocator_traits<AllocatorT>::allocate(d_allocator, d_sizeAlloc);
150
348370
      if (d_list == nullptr)
151
      {
152
        throw std::bad_alloc();
153
      }
154
    }
155
    else
156
    {
157
      // Allocate a new array with double the size
158
209545
      size_t newSize = GROWTH_FACTOR * d_sizeAlloc;
159
209545
      if (newSize > maxSize)
160
      {
161
        newSize = maxSize;
162
        Assert(newSize > d_sizeAlloc)
163
            << "cannot request larger list due to allocator limits";
164
      }
165
209543
      T* newList =
166
209545
          std::allocator_traits<AllocatorT>::allocate(d_allocator, newSize);
167
628629
      Debug("cdlist") << "2x grow of cdlist " << this
168
419086
                      << " level " << getContext()->getLevel()
169
209543
                      << " to " << newSize
170
209543
                      << " (from " << d_list
171
209543
                      << " to " << newList << ")" << std::endl;
172
209543
      if (newList == nullptr)
173
      {
174
        throw std::bad_alloc();
175
      }
176
209543
      std::memcpy(newList, d_list, sizeof(T) * d_sizeAlloc);
177
209543
      std::allocator_traits<AllocatorT>::deallocate(
178
          d_allocator, d_list, d_sizeAlloc);
179
209543
      d_list = newList;
180
209543
      d_sizeAlloc = newSize;
181
    }
182
  }
183
184
  /**
185
   * Implementation of mandatory ContextObj method save: simply copies
186
   * the current size to a copy using the copy constructor (the
187
   * pointer and the allocated size are *not* copied as they are not
188
   * restored on a pop).  The saved information is allocated using the
189
   * ContextMemoryManager.
190
   */
191
12253427
  ContextObj* save(ContextMemoryManager* pCMM) override
192
  {
193
12253427
    ContextObj* data = new(pCMM) CDList<T, CleanUp, Allocator>(*this);
194
36760281
    Debug("cdlist") << "save " << this
195
24506854
                    << " at level " << this->getContext()->getLevel()
196
12253427
                    << " size at " << this->d_size
197
12253427
                    << " sizeAlloc at " << this->d_sizeAlloc
198
12253427
                    << " d_list is " << this->d_list
199
12253427
                    << " data:" << data << std::endl;
200
12253427
    return data;
201
  }
202
203
protected:
204
  /**
205
   * Implementation of mandatory ContextObj method restore: simply
206
   * restores the previous size.  Note that the list pointer and the
207
   * allocated size are not changed.
208
   */
209
16525413
 void restore(ContextObj* data) override
210
 {
211
49576239
   Debug("cdlist") << "restore " << this << " level "
212
33050826
                   << this->getContext()->getLevel() << " data == " << data
213
16525413
                   << " call dtor == " << this->d_callDestructor
214
16525413
                   << " d_list == " << this->d_list << std::endl;
215
16525413
   truncateList(((CDList<T, CleanUp, Allocator>*)data)->d_size);
216
49576239
   Debug("cdlist") << "restore " << this << " level "
217
33050826
                   << this->getContext()->getLevel() << " size back to "
218
16525413
                   << this->d_size << " sizeAlloc at " << this->d_sizeAlloc
219
                   << std::endl;
220
16525413
  }
221
222
  /**
223
   * Given a size parameter smaller than d_size, truncateList()
224
   * removes the elements from the end of the list until d_size equals size.
225
   *
226
   * WARNING! You should only use this function when you know what you are doing.
227
   * This is a primitive operation with strange context dependent behavior!
228
   * It is up to the user of the function to ensure that the saved d_size values
229
   * at lower context levels are less than or equal to size.
230
   */
231
21922386
  void truncateList(const size_t size){
232
21922386
    Assert(size <= d_size);
233
21922386
    if(d_callDestructor) {
234
187528875
      while(d_size != size) {
235
83247557
        --d_size;
236
83247557
        d_cleanUp(&d_list[d_size]);
237
83247557
        std::allocator_traits<AllocatorT>::destroy(d_allocator,
238
83247557
                                                   &d_list[d_size]);
239
      }
240
    } else {
241
888625
      d_size = size;
242
    }
243
21922386
  }
244
245
 public:
246
  /**
247
   * Main constructor: d_list starts as nullptr, size is 0
248
   */
249
1162336
  CDList(Context* context,
250
         bool callDestructor = true,
251
         const CleanUp& cleanup = CleanUp(),
252
         const Allocator& alloc = Allocator())
253
      : ContextObj(context),
254
        d_list(nullptr),
255
        d_size(0),
256
        d_callDestructor(callDestructor),
257
        d_sizeAlloc(0),
258
        d_cleanUp(cleanup),
259
1162336
        d_allocator(alloc)
260
  {
261
1162336
  }
262
263
  /**
264
   * Destructor: delete the list
265
   */
266
1156793
  ~CDList() {
267
1156793
    this->destroy();
268
269
1156793
    if(this->d_callDestructor) {
270
1136901
      truncateList(0);
271
    }
272
273
1156793
    std::allocator_traits<AllocatorT>::deallocate(
274
        d_allocator, this->d_list, this->d_sizeAlloc);
275
2313586
  }
276
277
  /**
278
   * Return the current size of (i.e. valid number of objects in) the
279
   * list.
280
   */
281
197763002
  size_t size() const {
282
197763002
    return d_size;
283
  }
284
285
  /**
286
   * Return true iff there are no valid objects in the list.
287
   */
288
20690067
  bool empty() const {
289
20690067
    return d_size == 0;
290
  }
291
292
  /**
293
   * Add an item to the end of the list. This method uses the copy constructor
294
   * of T, so the type has to support it. As a result, this method cannot be
295
   * used with types that do not have a copy constructor such as
296
   * std::unique_ptr. Use CDList::emplace_back() instead of CDList::push_back()
297
   * to avoid this issue.
298
   */
299
93478110
  void push_back(const T& data) {
300
280434330
    Debug("cdlist") << "push_back " << this
301
186956220
                    << " " << getContext()->getLevel()
302
93478110
                    << ": make-current, "
303
93478110
                    << "d_list == " << d_list << std::endl;
304
93478110
    makeCurrent();
305
93478110
    grow();
306
93478108
    Assert(d_size < d_sizeAlloc);
307
308
280434324
    Debug("cdlist") << "push_back " << this
309
186956216
                    << " " << getContext()->getLevel()
310
93478108
                    << ": construct! at " << d_list
311
186956216
                    << "[" << d_size << "] == " << &d_list[d_size]
312
                    << std::endl;
313
93478108
    std::allocator_traits<AllocatorT>::construct(
314
93478108
        d_allocator, &d_list[d_size], data);
315
280434324
    Debug("cdlist") << "push_back " << this
316
186956216
                    << " " << getContext()->getLevel()
317
93478108
                    << ": done..." << std::endl;
318
93478108
    ++d_size;
319
280434324
    Debug("cdlist") << "push_back " << this
320
186956216
                    << " " << getContext()->getLevel()
321
93478108
                    << ": size now " << d_size << std::endl;
322
93478108
  }
323
324
  /**
325
   * Construct an item to the end of the list. This method constructs the item
326
   * in-place (similar to std::vector::emplace_back()), so it can be used for
327
   * types that do not have a copy constructor such as std::unique_ptr.
328
   */
329
  template <typename... Args>
330
80
  void emplace_back(Args&&... args)
331
  {
332
240
    Debug("cdlist") << "emplace_back " << this << " "
333
160
                    << getContext()->getLevel() << ": make-current, "
334
80
                    << "d_list == " << d_list << std::endl;
335
80
    makeCurrent();
336
80
    grow();
337
80
    Assert(d_size < d_sizeAlloc);
338
339
240
    Debug("cdlist") << "emplace_back " << this << " "
340
160
                    << getContext()->getLevel() << ": construct! at " << d_list
341
160
                    << "[" << d_size << "] == " << &d_list[d_size] << std::endl;
342
160
    std::allocator_traits<AllocatorT>::construct(
343
80
        d_allocator, &d_list[d_size], std::forward<Args>(args)...);
344
240
    Debug("cdlist") << "emplace_back " << this << " "
345
160
                    << getContext()->getLevel() << ": done..." << std::endl;
346
80
    ++d_size;
347
240
    Debug("cdlist") << "emplace_back " << this << " "
348
160
                    << getContext()->getLevel() << ": size now " << d_size
349
                    << std::endl;
350
80
  }
351
352
  /**
353
   * Access to the ith item in the list.
354
   */
355
140986962
  const T& operator[](size_t i) const {
356
140986962
    Assert(i < d_size) << "index out of bounds in CDList::operator[]";
357
140986962
    return d_list[i];
358
  }
359
360
  /**
361
   * Returns the most recent item added to the list.
362
   */
363
4784
  const T& back() const {
364
4784
    Assert(d_size > 0) << "CDList::back() called on empty list";
365
4784
    return d_list[d_size - 1];
366
  }
367
368
  /**
369
   * Iterator for CDList class.  It has to be const because we don't
370
   * allow items in the list to be changed.  It's a straightforward
371
   * wrapper around a pointer.  Note that for efficiency, we implement
372
   * only prefix increment and decrement.  Also note that it's OK to
373
   * create an iterator from an empty, uninitialized list, as begin()
374
   * and end() will have the same value (nullptr).
375
   */
376
  class const_iterator {
377
    T const* d_it;
378
379
3292300
    const_iterator(T const* it) : d_it(it) {}
380
381
    friend class CDList<T, CleanUp, Allocator>;
382
383
  public:
384
385
    // FIXME we support operator--, but do we satisfy all the
386
    // requirements of a bidirectional iterator ?
387
    typedef std::input_iterator_tag iterator_category;
388
    typedef T value_type;
389
    typedef std::ptrdiff_t difference_type;
390
    typedef const T* pointer;
391
    typedef const T& reference;
392
393
48790
    const_iterator() : d_it(nullptr) {}
394
395
2
    inline bool operator==(const const_iterator& i) const {
396
2
      return d_it == i.d_it;
397
    }
398
399
26012857
    inline bool operator!=(const const_iterator& i) const {
400
26012857
      return d_it != i.d_it;
401
    }
402
403
25288126
    inline const T& operator*() const {
404
25288126
      return *d_it;
405
    }
406
407
    inline const T* operator->() const { return d_it; }
408
409
    /** Prefix increment */
410
25161098
    const_iterator& operator++() {
411
25161098
      ++d_it;
412
25161098
      return *this;
413
    }
414
415
    /** Prefix decrement */
416
    const_iterator& operator--() { --d_it; return *this; }
417
418
    /** operator+ */
419
86090
    const_iterator operator+(long signed int off) const {
420
86090
      return const_iterator(d_it + off);
421
    }
422
423
    // Postfix operations on iterators: requires a Proxy object to
424
    // hold the intermediate value for dereferencing
425
    class Proxy {
426
      const T* d_t;
427
428
    public:
429
430
      Proxy(const T& p): d_t(&p) {}
431
432
      T& operator*() {
433
        return *d_t;
434
      }
435
    };/* class CDList<>::const_iterator::Proxy */
436
437
    /** Postfix increment: returns Proxy with the old value. */
438
    Proxy operator++(int) {
439
      Proxy e(*(*this));
440
      ++(*this);
441
      return e;
442
    }
443
444
    /** Postfix decrement: returns Proxy with the old value. */
445
    Proxy operator--(int) {
446
      Proxy e(*(*this));
447
      --(*this);
448
      return e;
449
    }
450
451
  };/* class CDList<>::const_iterator */
452
  typedef const_iterator iterator;
453
454
  /**
455
   * Returns an iterator pointing to the first item in the list.
456
   */
457
824376
  const_iterator begin() const {
458
824376
    return const_iterator(static_cast<T const*>(d_list));
459
  }
460
461
  /**
462
   * Returns an iterator pointing one past the last item in the list.
463
   */
464
2381834
  const_iterator end() const {
465
2381834
    return const_iterator(static_cast<T const*>(d_list) + d_size);
466
  }
467
};/* class CDList<> */
468
469
template <class T, class CleanUp>
470
class CDList<T, CleanUp, ContextMemoryAllocator<T> > : public ContextObj {
471
  /* CDList is incompatible for use with a ContextMemoryAllocator.
472
   *
473
   * Explanation:
474
   * If ContextMemoryAllocator is used and d_list grows at a deeper context
475
   * level the reallocated will be reallocated in a context memory region that
476
   * can be destroyed on pop. To support this, a full copy of d_list would have
477
   * to be made. As this is unacceptable for performance in other situations, we
478
   * do not do this.
479
   */
480
481
  static_assert(sizeof(T) == 0,
482
                "Cannot create a CDList with a ContextMemoryAllocator.");
483
};
484
485
}  // namespace context
486
}  // namespace cvc5
487
488
#endif /* CVC5__CONTEXT__CDLIST_H */