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 |
11457783 |
CDList(const CDList& l) : |
105 |
|
ContextObj(l), |
106 |
|
d_list(NULL), |
107 |
11457783 |
d_size(l.d_size), |
108 |
|
d_callDestructor(false), |
109 |
|
d_sizeAlloc(0), |
110 |
|
d_cleanUp(l.d_cleanUp), |
111 |
22915566 |
d_allocator(l.d_allocator) { |
112 |
34373349 |
Debug("cdlist") << "copy ctor: " << this |
113 |
22915566 |
<< " from " << &l |
114 |
11457783 |
<< " size " << d_size << std::endl; |
115 |
11457783 |
} |
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 |
77148692 |
void grow() { |
125 |
154297384 |
Debug("cdlist") << "grow " << this << " " << getContext()->getLevel() |
126 |
77148692 |
<< ": grow? " << d_size << " " << d_sizeAlloc << std::endl; |
127 |
77148692 |
if (d_size != d_sizeAlloc) |
128 |
|
{ |
129 |
|
// If there is still unused allocated space |
130 |
76635243 |
return; |
131 |
|
} |
132 |
1026898 |
Debug("cdlist") << "grow " << this << " " << getContext()->getLevel() |
133 |
513449 |
<< ": grow!" << std::endl; |
134 |
|
|
135 |
513449 |
if(d_list == NULL) { |
136 |
|
// Allocate an initial list if one does not yet exist |
137 |
330202 |
d_sizeAlloc = INITIAL_SIZE; |
138 |
990606 |
Debug("cdlist") << "initial grow of cdlist " << this |
139 |
660404 |
<< " level " << getContext()->getLevel() |
140 |
330202 |
<< " to " << d_sizeAlloc << std::endl; |
141 |
330202 |
if(d_sizeAlloc > d_allocator.max_size()) { |
142 |
|
d_sizeAlloc = d_allocator.max_size(); |
143 |
|
} |
144 |
330202 |
d_list = d_allocator.allocate(d_sizeAlloc); |
145 |
330202 |
if(d_list == NULL) { |
146 |
|
throw std::bad_alloc(); |
147 |
|
} |
148 |
|
} else { |
149 |
|
// Allocate a new array with double the size |
150 |
183247 |
size_t newSize = GROWTH_FACTOR * d_sizeAlloc; |
151 |
183247 |
if(newSize > d_allocator.max_size()) { |
152 |
|
newSize = d_allocator.max_size(); |
153 |
|
Assert(newSize > d_sizeAlloc) |
154 |
|
<< "cannot request larger list due to allocator limits"; |
155 |
|
} |
156 |
183247 |
T* newList = d_allocator.allocate(newSize); |
157 |
549735 |
Debug("cdlist") << "2x grow of cdlist " << this |
158 |
366490 |
<< " level " << getContext()->getLevel() |
159 |
183245 |
<< " to " << newSize |
160 |
183245 |
<< " (from " << d_list |
161 |
183245 |
<< " to " << newList << ")" << std::endl; |
162 |
183245 |
if(newList == NULL) { |
163 |
|
throw std::bad_alloc(); |
164 |
|
} |
165 |
183245 |
std::memcpy(newList, d_list, sizeof(T) * d_sizeAlloc); |
166 |
183245 |
d_allocator.deallocate(d_list, d_sizeAlloc); |
167 |
183245 |
d_list = newList; |
168 |
183245 |
d_sizeAlloc = newSize; |
169 |
|
} |
170 |
|
} |
171 |
|
|
172 |
|
/** |
173 |
|
* Implementation of mandatory ContextObj method save: simply copies |
174 |
|
* the current size to a copy using the copy constructor (the |
175 |
|
* pointer and the allocated size are *not* copied as they are not |
176 |
|
* restored on a pop). The saved information is allocated using the |
177 |
|
* ContextMemoryManager. |
178 |
|
*/ |
179 |
8687844 |
ContextObj* save(ContextMemoryManager* pCMM) override |
180 |
|
{ |
181 |
8687844 |
ContextObj* data = new(pCMM) CDList<T, CleanUp, Allocator>(*this); |
182 |
26063532 |
Debug("cdlist") << "save " << this |
183 |
17375688 |
<< " at level " << this->getContext()->getLevel() |
184 |
8687844 |
<< " size at " << this->d_size |
185 |
8687844 |
<< " sizeAlloc at " << this->d_sizeAlloc |
186 |
8687844 |
<< " d_list is " << this->d_list |
187 |
8687844 |
<< " data:" << data << std::endl; |
188 |
8687844 |
return data; |
189 |
|
} |
190 |
|
|
191 |
|
protected: |
192 |
|
/** |
193 |
|
* Implementation of mandatory ContextObj method restore: simply |
194 |
|
* restores the previous size. Note that the list pointer and the |
195 |
|
* allocated size are not changed. |
196 |
|
*/ |
197 |
11457783 |
void restore(ContextObj* data) override |
198 |
|
{ |
199 |
34373349 |
Debug("cdlist") << "restore " << this << " level " |
200 |
22915566 |
<< this->getContext()->getLevel() << " data == " << data |
201 |
11457783 |
<< " call dtor == " << this->d_callDestructor |
202 |
11457783 |
<< " d_list == " << this->d_list << std::endl; |
203 |
11457783 |
truncateList(((CDList<T, CleanUp, Allocator>*)data)->d_size); |
204 |
34373349 |
Debug("cdlist") << "restore " << this << " level " |
205 |
22915566 |
<< this->getContext()->getLevel() << " size back to " |
206 |
11457783 |
<< this->d_size << " sizeAlloc at " << this->d_sizeAlloc |
207 |
|
<< std::endl; |
208 |
11457783 |
} |
209 |
|
|
210 |
|
/** |
211 |
|
* Given a size parameter smaller than d_size, truncateList() |
212 |
|
* removes the elements from the end of the list until d_size equals size. |
213 |
|
* |
214 |
|
* WARNING! You should only use this function when you know what you are doing. |
215 |
|
* This is a primitive operation with strange context dependent behavior! |
216 |
|
* It is up to the user of the function to ensure that the saved d_size values |
217 |
|
* at lower context levels are less than or equal to size. |
218 |
|
*/ |
219 |
15378612 |
void truncateList(const size_t size){ |
220 |
15378612 |
Assert(size <= d_size); |
221 |
15378612 |
if(d_callDestructor) { |
222 |
153109806 |
while(d_size != size) { |
223 |
69166438 |
--d_size; |
224 |
69166438 |
d_cleanUp(&d_list[d_size]); |
225 |
69166438 |
d_allocator.destroy(&d_list[d_size]); |
226 |
|
} |
227 |
|
} else { |
228 |
601682 |
d_size = size; |
229 |
|
} |
230 |
15378612 |
} |
231 |
|
|
232 |
|
public: |
233 |
|
/** |
234 |
|
* Main constructor: d_list starts as NULL, size is 0 |
235 |
|
*/ |
236 |
1135816 |
CDList(Context* context, |
237 |
|
bool callDestructor = true, |
238 |
|
const CleanUp& cleanup = CleanUp(), |
239 |
|
const Allocator& alloc = Allocator()) : |
240 |
|
ContextObj(context), |
241 |
|
d_list(NULL), |
242 |
|
d_size(0), |
243 |
|
d_callDestructor(callDestructor), |
244 |
|
d_sizeAlloc(0), |
245 |
|
d_cleanUp(cleanup), |
246 |
1135816 |
d_allocator(alloc) { |
247 |
1135816 |
} |
248 |
|
|
249 |
|
/** |
250 |
|
* Destructor: delete the list |
251 |
|
*/ |
252 |
1135816 |
~CDList() { |
253 |
1135816 |
this->destroy(); |
254 |
|
|
255 |
1135816 |
if(this->d_callDestructor) { |
256 |
1116884 |
truncateList(0); |
257 |
|
} |
258 |
|
|
259 |
1135816 |
this->d_allocator.deallocate(this->d_list, this->d_sizeAlloc); |
260 |
2271632 |
} |
261 |
|
|
262 |
|
/** |
263 |
|
* Return the current size of (i.e. valid number of objects in) the |
264 |
|
* list. |
265 |
|
*/ |
266 |
102153429 |
size_t size() const { |
267 |
102153429 |
return d_size; |
268 |
|
} |
269 |
|
|
270 |
|
/** |
271 |
|
* Return true iff there are no valid objects in the list. |
272 |
|
*/ |
273 |
14152843 |
bool empty() const { |
274 |
14152843 |
return d_size == 0; |
275 |
|
} |
276 |
|
|
277 |
|
/** |
278 |
|
* Add an item to the end of the list. This method uses the copy constructor |
279 |
|
* of T, so the type has to support it. As a result, this method cannot be |
280 |
|
* used with types that do not have a copy constructor such as |
281 |
|
* std::unique_ptr. Use CDList::emplace_back() instead of CDList::push_back() |
282 |
|
* to avoid this issue. |
283 |
|
*/ |
284 |
77148652 |
void push_back(const T& data) { |
285 |
231445956 |
Debug("cdlist") << "push_back " << this |
286 |
154297304 |
<< " " << getContext()->getLevel() |
287 |
77148652 |
<< ": make-current, " |
288 |
77148652 |
<< "d_list == " << d_list << std::endl; |
289 |
77148652 |
makeCurrent(); |
290 |
77148652 |
grow(); |
291 |
77148650 |
Assert(d_size < d_sizeAlloc); |
292 |
|
|
293 |
231445950 |
Debug("cdlist") << "push_back " << this |
294 |
154297300 |
<< " " << getContext()->getLevel() |
295 |
77148650 |
<< ": construct! at " << d_list |
296 |
154297300 |
<< "[" << d_size << "] == " << &d_list[d_size] |
297 |
|
<< std::endl; |
298 |
77148650 |
d_allocator.construct(&d_list[d_size], data); |
299 |
231445950 |
Debug("cdlist") << "push_back " << this |
300 |
154297300 |
<< " " << getContext()->getLevel() |
301 |
77148650 |
<< ": done..." << std::endl; |
302 |
77148650 |
++d_size; |
303 |
231445950 |
Debug("cdlist") << "push_back " << this |
304 |
154297300 |
<< " " << getContext()->getLevel() |
305 |
77148650 |
<< ": size now " << d_size << std::endl; |
306 |
77148650 |
} |
307 |
|
|
308 |
|
/** |
309 |
|
* Construct an item to the end of the list. This method constructs the item |
310 |
|
* in-place (similar to std::vector::emplace_back()), so it can be used for |
311 |
|
* types that do not have a copy constructor such as std::unique_ptr. |
312 |
|
*/ |
313 |
|
template <typename... Args> |
314 |
40 |
void emplace_back(Args&&... args) |
315 |
|
{ |
316 |
120 |
Debug("cdlist") << "emplace_back " << this << " " |
317 |
80 |
<< getContext()->getLevel() << ": make-current, " |
318 |
40 |
<< "d_list == " << d_list << std::endl; |
319 |
40 |
makeCurrent(); |
320 |
40 |
grow(); |
321 |
40 |
Assert(d_size < d_sizeAlloc); |
322 |
|
|
323 |
120 |
Debug("cdlist") << "emplace_back " << this << " " |
324 |
80 |
<< getContext()->getLevel() << ": construct! at " << d_list |
325 |
80 |
<< "[" << d_size << "] == " << &d_list[d_size] << std::endl; |
326 |
80 |
std::allocator_traits<AllocatorT>::construct( |
327 |
40 |
d_allocator, &d_list[d_size], std::forward<Args>(args)...); |
328 |
120 |
Debug("cdlist") << "emplace_back " << this << " " |
329 |
80 |
<< getContext()->getLevel() << ": done..." << std::endl; |
330 |
40 |
++d_size; |
331 |
120 |
Debug("cdlist") << "emplace_back " << this << " " |
332 |
80 |
<< getContext()->getLevel() << ": size now " << d_size |
333 |
|
<< std::endl; |
334 |
40 |
} |
335 |
|
|
336 |
|
/** |
337 |
|
* Access to the ith item in the list. |
338 |
|
*/ |
339 |
93112693 |
const T& operator[](size_t i) const { |
340 |
93112693 |
Assert(i < d_size) << "index out of bounds in CDList::operator[]"; |
341 |
93112693 |
return d_list[i]; |
342 |
|
} |
343 |
|
|
344 |
|
/** |
345 |
|
* Returns the most recent item added to the list. |
346 |
|
*/ |
347 |
4642 |
const T& back() const { |
348 |
4642 |
Assert(d_size > 0) << "CDList::back() called on empty list"; |
349 |
4642 |
return d_list[d_size - 1]; |
350 |
|
} |
351 |
|
|
352 |
|
/** |
353 |
|
* Iterator for CDList class. It has to be const because we don't |
354 |
|
* allow items in the list to be changed. It's a straightforward |
355 |
|
* wrapper around a pointer. Note that for efficiency, we implement |
356 |
|
* only prefix increment and decrement. Also note that it's OK to |
357 |
|
* create an iterator from an empty, uninitialized list, as begin() |
358 |
|
* and end() will have the same value (NULL). |
359 |
|
*/ |
360 |
|
class const_iterator { |
361 |
|
T const* d_it; |
362 |
|
|
363 |
2506783 |
const_iterator(T const* it) : d_it(it) {} |
364 |
|
|
365 |
|
friend class CDList<T, CleanUp, Allocator>; |
366 |
|
|
367 |
|
public: |
368 |
|
|
369 |
|
// FIXME we support operator--, but do we satisfy all the |
370 |
|
// requirements of a bidirectional iterator ? |
371 |
|
typedef std::input_iterator_tag iterator_category; |
372 |
|
typedef T value_type; |
373 |
|
typedef std::ptrdiff_t difference_type; |
374 |
|
typedef const T* pointer; |
375 |
|
typedef const T& reference; |
376 |
|
|
377 |
41528 |
const_iterator() : d_it(NULL) {} |
378 |
|
|
379 |
2 |
inline bool operator==(const const_iterator& i) const { |
380 |
2 |
return d_it == i.d_it; |
381 |
|
} |
382 |
|
|
383 |
17094060 |
inline bool operator!=(const const_iterator& i) const { |
384 |
17094060 |
return d_it != i.d_it; |
385 |
|
} |
386 |
|
|
387 |
16511553 |
inline const T& operator*() const { |
388 |
16511553 |
return *d_it; |
389 |
|
} |
390 |
|
|
391 |
|
inline const T* operator->() const { return d_it; } |
392 |
|
|
393 |
|
/** Prefix increment */ |
394 |
16390703 |
const_iterator& operator++() { |
395 |
16390703 |
++d_it; |
396 |
16390703 |
return *this; |
397 |
|
} |
398 |
|
|
399 |
|
/** Prefix decrement */ |
400 |
|
const_iterator& operator--() { --d_it; return *this; } |
401 |
|
|
402 |
|
/** operator+ */ |
403 |
65888 |
const_iterator operator+(long signed int off) const { |
404 |
65888 |
return const_iterator(d_it + off); |
405 |
|
} |
406 |
|
|
407 |
|
// Postfix operations on iterators: requires a Proxy object to |
408 |
|
// hold the intermediate value for dereferencing |
409 |
|
class Proxy { |
410 |
|
const T* d_t; |
411 |
|
|
412 |
|
public: |
413 |
|
|
414 |
|
Proxy(const T& p): d_t(&p) {} |
415 |
|
|
416 |
|
T& operator*() { |
417 |
|
return *d_t; |
418 |
|
} |
419 |
|
};/* class CDList<>::const_iterator::Proxy */ |
420 |
|
|
421 |
|
/** Postfix increment: returns Proxy with the old value. */ |
422 |
|
Proxy operator++(int) { |
423 |
|
Proxy e(*(*this)); |
424 |
|
++(*this); |
425 |
|
return e; |
426 |
|
} |
427 |
|
|
428 |
|
/** Postfix decrement: returns Proxy with the old value. */ |
429 |
|
Proxy operator--(int) { |
430 |
|
Proxy e(*(*this)); |
431 |
|
--(*this); |
432 |
|
return e; |
433 |
|
} |
434 |
|
|
435 |
|
};/* class CDList<>::const_iterator */ |
436 |
|
typedef const_iterator iterator; |
437 |
|
|
438 |
|
/** |
439 |
|
* Returns an iterator pointing to the first item in the list. |
440 |
|
*/ |
441 |
678116 |
const_iterator begin() const { |
442 |
678116 |
return const_iterator(static_cast<T const*>(d_list)); |
443 |
|
} |
444 |
|
|
445 |
|
/** |
446 |
|
* Returns an iterator pointing one past the last item in the list. |
447 |
|
*/ |
448 |
1762779 |
const_iterator end() const { |
449 |
1762779 |
return const_iterator(static_cast<T const*>(d_list) + d_size); |
450 |
|
} |
451 |
|
};/* class CDList<> */ |
452 |
|
|
453 |
|
template <class T, class CleanUp> |
454 |
|
class CDList<T, CleanUp, ContextMemoryAllocator<T> > : public ContextObj { |
455 |
|
/* CDList is incompatible for use with a ContextMemoryAllocator. |
456 |
|
* |
457 |
|
* Explanation: |
458 |
|
* If ContextMemoryAllocator is used and d_list grows at a deeper context |
459 |
|
* level the reallocated will be reallocated in a context memory region that |
460 |
|
* can be destroyed on pop. To support this, a full copy of d_list would have |
461 |
|
* to be made. As this is unacceptable for performance in other situations, we |
462 |
|
* do not do this. |
463 |
|
*/ |
464 |
|
|
465 |
|
static_assert(sizeof(T) == 0, |
466 |
|
"Cannot create a CDList with a ContextMemoryAllocator."); |
467 |
|
}; |
468 |
|
|
469 |
|
} // namespace context |
470 |
|
} // namespace cvc5 |
471 |
|
|
472 |
|
#endif /* CVC5__CONTEXT__CDLIST_H */ |