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 |
14275585 |
CDList(const CDList& l) |
105 |
|
: ContextObj(l), |
106 |
|
d_list(nullptr), |
107 |
14275585 |
d_size(l.d_size), |
108 |
|
d_callDestructor(false), |
109 |
|
d_sizeAlloc(0), |
110 |
|
d_cleanUp(l.d_cleanUp), |
111 |
28551170 |
d_allocator(l.d_allocator) |
112 |
|
{ |
113 |
28551170 |
Debug("cdlist") << "copy ctor: " << this << " from " << &l << " size " |
114 |
14275585 |
<< d_size << std::endl; |
115 |
14275585 |
} |
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 |
85647321 |
void grow() { |
125 |
171294642 |
Debug("cdlist") << "grow " << this << " " << getContext()->getLevel() |
126 |
85647321 |
<< ": grow? " << d_size << " " << d_sizeAlloc << std::endl; |
127 |
85647321 |
if (d_size != d_sizeAlloc) |
128 |
|
{ |
129 |
|
// If there is still unused allocated space |
130 |
85111999 |
return; |
131 |
|
} |
132 |
1070644 |
Debug("cdlist") << "grow " << this << " " << getContext()->getLevel() |
133 |
535322 |
<< ": grow!" << std::endl; |
134 |
|
|
135 |
535322 |
const size_t maxSize = |
136 |
535322 |
std::allocator_traits<AllocatorT>::max_size(d_allocator); |
137 |
535322 |
if (d_list == nullptr) |
138 |
|
{ |
139 |
|
// Allocate an initial list if one does not yet exist |
140 |
338308 |
d_sizeAlloc = INITIAL_SIZE; |
141 |
1014924 |
Debug("cdlist") << "initial grow of cdlist " << this |
142 |
676616 |
<< " level " << getContext()->getLevel() |
143 |
338308 |
<< " to " << d_sizeAlloc << std::endl; |
144 |
338308 |
if (d_sizeAlloc > maxSize) |
145 |
|
{ |
146 |
|
d_sizeAlloc = maxSize; |
147 |
|
} |
148 |
338308 |
d_list = |
149 |
338308 |
std::allocator_traits<AllocatorT>::allocate(d_allocator, d_sizeAlloc); |
150 |
338308 |
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 |
197014 |
size_t newSize = GROWTH_FACTOR * d_sizeAlloc; |
159 |
197014 |
if (newSize > maxSize) |
160 |
|
{ |
161 |
|
newSize = maxSize; |
162 |
|
Assert(newSize > d_sizeAlloc) |
163 |
|
<< "cannot request larger list due to allocator limits"; |
164 |
|
} |
165 |
197012 |
T* newList = |
166 |
197014 |
std::allocator_traits<AllocatorT>::allocate(d_allocator, newSize); |
167 |
591036 |
Debug("cdlist") << "2x grow of cdlist " << this |
168 |
394024 |
<< " level " << getContext()->getLevel() |
169 |
197012 |
<< " to " << newSize |
170 |
197012 |
<< " (from " << d_list |
171 |
197012 |
<< " to " << newList << ")" << std::endl; |
172 |
197012 |
if (newList == nullptr) |
173 |
|
{ |
174 |
|
throw std::bad_alloc(); |
175 |
|
} |
176 |
197012 |
std::memcpy(newList, d_list, sizeof(T) * d_sizeAlloc); |
177 |
197012 |
std::allocator_traits<AllocatorT>::deallocate( |
178 |
|
d_allocator, d_list, d_sizeAlloc); |
179 |
197012 |
d_list = newList; |
180 |
197012 |
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 |
10452866 |
ContextObj* save(ContextMemoryManager* pCMM) override |
192 |
|
{ |
193 |
10452866 |
ContextObj* data = new(pCMM) CDList<T, CleanUp, Allocator>(*this); |
194 |
31358598 |
Debug("cdlist") << "save " << this |
195 |
20905732 |
<< " at level " << this->getContext()->getLevel() |
196 |
10452866 |
<< " size at " << this->d_size |
197 |
10452866 |
<< " sizeAlloc at " << this->d_sizeAlloc |
198 |
10452866 |
<< " d_list is " << this->d_list |
199 |
10452866 |
<< " data:" << data << std::endl; |
200 |
10452866 |
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 |
14275585 |
void restore(ContextObj* data) override |
210 |
|
{ |
211 |
42826755 |
Debug("cdlist") << "restore " << this << " level " |
212 |
28551170 |
<< this->getContext()->getLevel() << " data == " << data |
213 |
14275585 |
<< " call dtor == " << this->d_callDestructor |
214 |
14275585 |
<< " d_list == " << this->d_list << std::endl; |
215 |
14275585 |
truncateList(((CDList<T, CleanUp, Allocator>*)data)->d_size); |
216 |
42826755 |
Debug("cdlist") << "restore " << this << " level " |
217 |
28551170 |
<< this->getContext()->getLevel() << " size back to " |
218 |
14275585 |
<< this->d_size << " sizeAlloc at " << this->d_sizeAlloc |
219 |
|
<< std::endl; |
220 |
14275585 |
} |
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 |
19141831 |
void truncateList(const size_t size){ |
232 |
19141831 |
Assert(size <= d_size); |
233 |
19141831 |
if(d_callDestructor) { |
234 |
172348651 |
while(d_size != size) { |
235 |
76950381 |
--d_size; |
236 |
76950381 |
d_cleanUp(&d_list[d_size]); |
237 |
76950381 |
std::allocator_traits<AllocatorT>::destroy(d_allocator, |
238 |
76950381 |
&d_list[d_size]); |
239 |
|
} |
240 |
|
} else { |
241 |
693942 |
d_size = size; |
242 |
|
} |
243 |
19141831 |
} |
244 |
|
|
245 |
|
public: |
246 |
|
/** |
247 |
|
* Main constructor: d_list starts as nullptr, size is 0 |
248 |
|
*/ |
249 |
1129914 |
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 |
1129914 |
d_allocator(alloc) |
260 |
|
{ |
261 |
1129914 |
} |
262 |
|
|
263 |
|
/** |
264 |
|
* Destructor: delete the list |
265 |
|
*/ |
266 |
1129914 |
~CDList() { |
267 |
1129914 |
this->destroy(); |
268 |
|
|
269 |
1129914 |
if(this->d_callDestructor) { |
270 |
1110224 |
truncateList(0); |
271 |
|
} |
272 |
|
|
273 |
1129914 |
std::allocator_traits<AllocatorT>::deallocate( |
274 |
|
d_allocator, this->d_list, this->d_sizeAlloc); |
275 |
2259828 |
} |
276 |
|
|
277 |
|
/** |
278 |
|
* Return the current size of (i.e. valid number of objects in) the |
279 |
|
* list. |
280 |
|
*/ |
281 |
180550967 |
size_t size() const { |
282 |
180550967 |
return d_size; |
283 |
|
} |
284 |
|
|
285 |
|
/** |
286 |
|
* Return true iff there are no valid objects in the list. |
287 |
|
*/ |
288 |
16205723 |
bool empty() const { |
289 |
16205723 |
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 |
85647241 |
void push_back(const T& data) { |
300 |
256941723 |
Debug("cdlist") << "push_back " << this |
301 |
171294482 |
<< " " << getContext()->getLevel() |
302 |
85647241 |
<< ": make-current, " |
303 |
85647241 |
<< "d_list == " << d_list << std::endl; |
304 |
85647241 |
makeCurrent(); |
305 |
85647241 |
grow(); |
306 |
85647239 |
Assert(d_size < d_sizeAlloc); |
307 |
|
|
308 |
256941717 |
Debug("cdlist") << "push_back " << this |
309 |
171294478 |
<< " " << getContext()->getLevel() |
310 |
85647239 |
<< ": construct! at " << d_list |
311 |
171294478 |
<< "[" << d_size << "] == " << &d_list[d_size] |
312 |
|
<< std::endl; |
313 |
85647239 |
std::allocator_traits<AllocatorT>::construct( |
314 |
85647239 |
d_allocator, &d_list[d_size], data); |
315 |
256941717 |
Debug("cdlist") << "push_back " << this |
316 |
171294478 |
<< " " << getContext()->getLevel() |
317 |
85647239 |
<< ": done..." << std::endl; |
318 |
85647239 |
++d_size; |
319 |
256941717 |
Debug("cdlist") << "push_back " << this |
320 |
171294478 |
<< " " << getContext()->getLevel() |
321 |
85647239 |
<< ": size now " << d_size << std::endl; |
322 |
85647239 |
} |
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 |
128838230 |
const T& operator[](size_t i) const { |
356 |
128838230 |
Assert(i < d_size) << "index out of bounds in CDList::operator[]"; |
357 |
128838230 |
return d_list[i]; |
358 |
|
} |
359 |
|
|
360 |
|
/** |
361 |
|
* Returns the most recent item added to the list. |
362 |
|
*/ |
363 |
4531 |
const T& back() const { |
364 |
4531 |
Assert(d_size > 0) << "CDList::back() called on empty list"; |
365 |
4531 |
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 |
2589639 |
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 |
48637 |
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 |
19700932 |
inline bool operator!=(const const_iterator& i) const { |
400 |
19700932 |
return d_it != i.d_it; |
401 |
|
} |
402 |
|
|
403 |
19059706 |
inline const T& operator*() const { |
404 |
19059706 |
return *d_it; |
405 |
|
} |
406 |
|
|
407 |
|
inline const T* operator->() const { return d_it; } |
408 |
|
|
409 |
|
/** Prefix increment */ |
410 |
18935569 |
const_iterator& operator++() { |
411 |
18935569 |
++d_it; |
412 |
18935569 |
return *this; |
413 |
|
} |
414 |
|
|
415 |
|
/** Prefix decrement */ |
416 |
|
const_iterator& operator--() { --d_it; return *this; } |
417 |
|
|
418 |
|
/** operator+ */ |
419 |
65235 |
const_iterator operator+(long signed int off) const { |
420 |
65235 |
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 |
739806 |
const_iterator begin() const { |
458 |
739806 |
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 |
1784598 |
const_iterator end() const { |
465 |
1784598 |
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 */ |