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 |
15557624 |
CDList(const CDList& l) |
105 |
|
: ContextObj(l), |
106 |
|
d_list(nullptr), |
107 |
15557624 |
d_size(l.d_size), |
108 |
|
d_callDestructor(false), |
109 |
|
d_sizeAlloc(0), |
110 |
|
d_cleanUp(l.d_cleanUp), |
111 |
31115248 |
d_allocator(l.d_allocator) |
112 |
|
{ |
113 |
31115248 |
Debug("cdlist") << "copy ctor: " << this << " from " << &l << " size " |
114 |
15557624 |
<< d_size << std::endl; |
115 |
15557624 |
} |
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 |
89104898 |
void grow() { |
125 |
178209796 |
Debug("cdlist") << "grow " << this << " " << getContext()->getLevel() |
126 |
89104898 |
<< ": grow? " << d_size << " " << d_sizeAlloc << std::endl; |
127 |
89104898 |
if (d_size != d_sizeAlloc) |
128 |
|
{ |
129 |
|
// If there is still unused allocated space |
130 |
88548481 |
return; |
131 |
|
} |
132 |
1112834 |
Debug("cdlist") << "grow " << this << " " << getContext()->getLevel() |
133 |
556417 |
<< ": grow!" << std::endl; |
134 |
|
|
135 |
556417 |
const size_t maxSize = |
136 |
556417 |
std::allocator_traits<AllocatorT>::max_size(d_allocator); |
137 |
556417 |
if (d_list == nullptr) |
138 |
|
{ |
139 |
|
// Allocate an initial list if one does not yet exist |
140 |
347210 |
d_sizeAlloc = INITIAL_SIZE; |
141 |
1041630 |
Debug("cdlist") << "initial grow of cdlist " << this |
142 |
694420 |
<< " level " << getContext()->getLevel() |
143 |
347210 |
<< " to " << d_sizeAlloc << std::endl; |
144 |
347210 |
if (d_sizeAlloc > maxSize) |
145 |
|
{ |
146 |
|
d_sizeAlloc = maxSize; |
147 |
|
} |
148 |
347210 |
d_list = |
149 |
347210 |
std::allocator_traits<AllocatorT>::allocate(d_allocator, d_sizeAlloc); |
150 |
347210 |
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 |
209207 |
size_t newSize = GROWTH_FACTOR * d_sizeAlloc; |
159 |
209207 |
if (newSize > maxSize) |
160 |
|
{ |
161 |
|
newSize = maxSize; |
162 |
|
Assert(newSize > d_sizeAlloc) |
163 |
|
<< "cannot request larger list due to allocator limits"; |
164 |
|
} |
165 |
209205 |
T* newList = |
166 |
209207 |
std::allocator_traits<AllocatorT>::allocate(d_allocator, newSize); |
167 |
627615 |
Debug("cdlist") << "2x grow of cdlist " << this |
168 |
418410 |
<< " level " << getContext()->getLevel() |
169 |
209205 |
<< " to " << newSize |
170 |
209205 |
<< " (from " << d_list |
171 |
209205 |
<< " to " << newList << ")" << std::endl; |
172 |
209205 |
if (newList == nullptr) |
173 |
|
{ |
174 |
|
throw std::bad_alloc(); |
175 |
|
} |
176 |
209205 |
std::memcpy(newList, d_list, sizeof(T) * d_sizeAlloc); |
177 |
209205 |
std::allocator_traits<AllocatorT>::deallocate( |
178 |
|
d_allocator, d_list, d_sizeAlloc); |
179 |
209205 |
d_list = newList; |
180 |
209205 |
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 |
11489611 |
ContextObj* save(ContextMemoryManager* pCMM) override |
192 |
|
{ |
193 |
11489611 |
ContextObj* data = new(pCMM) CDList<T, CleanUp, Allocator>(*this); |
194 |
34468833 |
Debug("cdlist") << "save " << this |
195 |
22979222 |
<< " at level " << this->getContext()->getLevel() |
196 |
11489611 |
<< " size at " << this->d_size |
197 |
11489611 |
<< " sizeAlloc at " << this->d_sizeAlloc |
198 |
11489611 |
<< " d_list is " << this->d_list |
199 |
11489611 |
<< " data:" << data << std::endl; |
200 |
11489611 |
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 |
15557623 |
void restore(ContextObj* data) override |
210 |
|
{ |
211 |
46672869 |
Debug("cdlist") << "restore " << this << " level " |
212 |
31115246 |
<< this->getContext()->getLevel() << " data == " << data |
213 |
15557623 |
<< " call dtor == " << this->d_callDestructor |
214 |
15557623 |
<< " d_list == " << this->d_list << std::endl; |
215 |
15557623 |
truncateList(((CDList<T, CleanUp, Allocator>*)data)->d_size); |
216 |
46672869 |
Debug("cdlist") << "restore " << this << " level " |
217 |
31115246 |
<< this->getContext()->getLevel() << " size back to " |
218 |
15557623 |
<< this->d_size << " sizeAlloc at " << this->d_sizeAlloc |
219 |
|
<< std::endl; |
220 |
15557623 |
} |
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 |
20743305 |
void truncateList(const size_t size){ |
232 |
20743305 |
Assert(size <= d_size); |
233 |
20743305 |
if(d_callDestructor) { |
234 |
179279405 |
while(d_size != size) { |
235 |
79685924 |
--d_size; |
236 |
79685924 |
d_cleanUp(&d_list[d_size]); |
237 |
79685924 |
std::allocator_traits<AllocatorT>::destroy(d_allocator, |
238 |
79685924 |
&d_list[d_size]); |
239 |
|
} |
240 |
|
} else { |
241 |
835748 |
d_size = size; |
242 |
|
} |
243 |
20743305 |
} |
244 |
|
|
245 |
|
public: |
246 |
|
/** |
247 |
|
* Main constructor: d_list starts as nullptr, size is 0 |
248 |
|
*/ |
249 |
1158071 |
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 |
1158071 |
d_allocator(alloc) |
260 |
|
{ |
261 |
1158071 |
} |
262 |
|
|
263 |
|
/** |
264 |
|
* Destructor: delete the list |
265 |
|
*/ |
266 |
1152538 |
~CDList() { |
267 |
1152538 |
this->destroy(); |
268 |
|
|
269 |
1152538 |
if(this->d_callDestructor) { |
270 |
1132700 |
truncateList(0); |
271 |
|
} |
272 |
|
|
273 |
1152538 |
std::allocator_traits<AllocatorT>::deallocate( |
274 |
|
d_allocator, this->d_list, this->d_sizeAlloc); |
275 |
2305076 |
} |
276 |
|
|
277 |
|
/** |
278 |
|
* Return the current size of (i.e. valid number of objects in) the |
279 |
|
* list. |
280 |
|
*/ |
281 |
192336060 |
size_t size() const { |
282 |
192336060 |
return d_size; |
283 |
|
} |
284 |
|
|
285 |
|
/** |
286 |
|
* Return true iff there are no valid objects in the list. |
287 |
|
*/ |
288 |
19626166 |
bool empty() const { |
289 |
19626166 |
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 |
89104818 |
void push_back(const T& data) { |
300 |
267314454 |
Debug("cdlist") << "push_back " << this |
301 |
178209636 |
<< " " << getContext()->getLevel() |
302 |
89104818 |
<< ": make-current, " |
303 |
89104818 |
<< "d_list == " << d_list << std::endl; |
304 |
89104818 |
makeCurrent(); |
305 |
89104818 |
grow(); |
306 |
89104816 |
Assert(d_size < d_sizeAlloc); |
307 |
|
|
308 |
267314448 |
Debug("cdlist") << "push_back " << this |
309 |
178209632 |
<< " " << getContext()->getLevel() |
310 |
89104816 |
<< ": construct! at " << d_list |
311 |
178209632 |
<< "[" << d_size << "] == " << &d_list[d_size] |
312 |
|
<< std::endl; |
313 |
89104816 |
std::allocator_traits<AllocatorT>::construct( |
314 |
89104816 |
d_allocator, &d_list[d_size], data); |
315 |
267314448 |
Debug("cdlist") << "push_back " << this |
316 |
178209632 |
<< " " << getContext()->getLevel() |
317 |
89104816 |
<< ": done..." << std::endl; |
318 |
89104816 |
++d_size; |
319 |
267314448 |
Debug("cdlist") << "push_back " << this |
320 |
178209632 |
<< " " << getContext()->getLevel() |
321 |
89104816 |
<< ": size now " << d_size << std::endl; |
322 |
89104816 |
} |
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 |
136724680 |
const T& operator[](size_t i) const { |
356 |
136724680 |
Assert(i < d_size) << "index out of bounds in CDList::operator[]"; |
357 |
136724680 |
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 |
3268014 |
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 |
48437 |
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 |
25896145 |
inline bool operator!=(const const_iterator& i) const { |
400 |
25896145 |
return d_it != i.d_it; |
401 |
|
} |
402 |
|
|
403 |
25178342 |
inline const T& operator*() const { |
404 |
25178342 |
return *d_it; |
405 |
|
} |
406 |
|
|
407 |
|
inline const T* operator->() const { return d_it; } |
408 |
|
|
409 |
|
/** Prefix increment */ |
410 |
25051790 |
const_iterator& operator++() { |
411 |
25051790 |
++d_it; |
412 |
25051790 |
return *this; |
413 |
|
} |
414 |
|
|
415 |
|
/** Prefix decrement */ |
416 |
|
const_iterator& operator--() { --d_it; return *this; } |
417 |
|
|
418 |
|
/** operator+ */ |
419 |
85376 |
const_iterator operator+(long signed int off) const { |
420 |
85376 |
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 |
816972 |
const_iterator begin() const { |
458 |
816972 |
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 |
2365666 |
const_iterator end() const { |
465 |
2365666 |
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 */ |