1 /*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #ifndef SkTArray_DEFINED
9 #define SkTArray_DEFINED
10
11 #include "include/private/base/SkAlignedStorage.h"
12 #include "include/private/base/SkAssert.h"
13 #include "include/private/base/SkAttributes.h"
14 #include "include/private/base/SkContainers.h"
15 #include "include/private/base/SkMalloc.h"
16 #include "include/private/base/SkMath.h"
17 #include "include/private/base/SkSpan_impl.h"
18 #include "include/private/base/SkTo.h"
19 #include "include/private/base/SkTypeTraits.h" // IWYU pragma: keep
20
21 #include <algorithm>
22 #include <climits>
23 #include <cstddef>
24 #include <cstdint>
25 #include <cstring>
26 #include <initializer_list>
27 #include <new>
28 #include <utility>
29
30 /** SkTArray<T> implements a typical, mostly std::vector-like array.
31 Each T will be default-initialized on allocation, and ~T will be called on destruction.
32
33 MEM_MOVE controls the behavior when a T needs to be moved (e.g. when the array is resized)
34 - true: T will be bit-copied via memcpy.
35 - false: T will be moved via move-constructors.
36
37 Modern implementations of std::vector<T> will generally provide similar performance
38 characteristics when used with appropriate care. Consider using std::vector<T> in new code.
39 */
40 template <typename T, bool MEM_MOVE = sk_is_trivially_relocatable_v<T>> class SkTArray {
41 public:
42 using value_type = T;
43
44 /**
45 * Creates an empty array with no initial storage
46 */
SkTArray()47 SkTArray() : fOwnMemory(true), fCapacity{0} {}
48
49 /**
50 * Creates an empty array that will preallocate space for reserveCount
51 * elements.
52 */
SkTArray(int reserveCount)53 explicit SkTArray(int reserveCount) : SkTArray() { this->reserve_back(reserveCount); }
54
55 /**
56 * Copies one array to another. The new array will be heap allocated.
57 */
SkTArray(const SkTArray & that)58 SkTArray(const SkTArray& that) : SkTArray(that.fData, that.fSize) {}
59
SkTArray(SkTArray && that)60 SkTArray(SkTArray&& that) {
61 if (that.fOwnMemory) {
62 this->setData(that);
63 that.setData({});
64 } else {
65 this->initData(that.fSize);
66 that.move(fData);
67 }
68 fSize = std::exchange(that.fSize, 0);
69 }
70
71 /**
72 * Creates a SkTArray by copying contents of a standard C array. The new
73 * array will be heap allocated. Be careful not to use this constructor
74 * when you really want the (void*, int) version.
75 */
SkTArray(const T * array,int count)76 SkTArray(const T* array, int count) {
77 this->initData(count);
78 this->copy(array);
79 }
80
81 /**
82 * Creates a SkTArray by copying contents of an initializer list.
83 */
SkTArray(std::initializer_list<T> data)84 SkTArray(std::initializer_list<T> data) : SkTArray(data.begin(), data.size()) {}
85
86 SkTArray& operator=(const SkTArray& that) {
87 if (this == &that) {
88 return *this;
89 }
90 this->clear();
91 this->checkRealloc(that.size(), kExactFit);
92 fSize = that.fSize;
93 this->copy(that.fData);
94 return *this;
95 }
96 SkTArray& operator=(SkTArray&& that) {
97 if (this != &that) {
98 this->clear();
99 if (that.fOwnMemory) {
100 // The storage is on the heap, so move the data pointer.
101 if (fOwnMemory) {
102 sk_free(fData);
103 }
104
105 fData = std::exchange(that.fData, nullptr);
106
107 // Can't use exchange with bitfields.
108 fCapacity = that.fCapacity;
109 that.fCapacity = 0;
110
111 fOwnMemory = true;
112 } else {
113 // The data is stored inline in that, so move it element-by-element.
114 this->checkRealloc(that.size(), kExactFit);
115 that.move(fData);
116 }
117 fSize = std::exchange(that.fSize, 0);
118 }
119 return *this;
120 }
121
~SkTArray()122 ~SkTArray() {
123 this->destroyAll();
124 if (fOwnMemory) {
125 sk_free(fData);
126 }
127 }
128
129 /**
130 * Resets to size() = n newly constructed T objects and resets any reserve count.
131 */
reset(int n)132 void reset(int n) {
133 SkASSERT(n >= 0);
134 this->clear();
135 this->checkRealloc(n, kExactFit);
136 fSize = n;
137 for (int i = 0; i < this->size(); ++i) {
138 new (fData + i) T;
139 }
140 }
141
142 /**
143 * Resets to a copy of a C array and resets any reserve count.
144 */
reset(const T * array,int count)145 void reset(const T* array, int count) {
146 SkASSERT(count >= 0);
147 this->clear();
148 this->checkRealloc(count, kExactFit);
149 fSize = count;
150 this->copy(array);
151 }
152
153 /**
154 * Ensures there is enough reserved space for n elements.
155 */
reserve(int n)156 void reserve(int n) {
157 SkASSERT(n >= 0);
158 if (n > this->size()) {
159 this->checkRealloc(n - this->size(), kGrowing);
160 }
161 }
162
163 /**
164 * Ensures there is enough reserved space for n additional elements. The is guaranteed at least
165 * until the array size grows above n and subsequently shrinks below n, any version of reset()
166 * is called, or reserve_back() is called again.
167 */
reserve_back(int n)168 void reserve_back(int n) {
169 SkASSERT(n >= 0);
170 if (n > 0) {
171 this->checkRealloc(n, kExactFit);
172 }
173 }
174
removeShuffle(int n)175 void removeShuffle(int n) {
176 SkASSERT(n < this->size());
177 int newCount = fSize - 1;
178 fSize = newCount;
179 fData[n].~T();
180 if (n != newCount) {
181 this->move(n, newCount);
182 }
183 }
184
185 // Is the array empty.
empty()186 bool empty() const { return fSize == 0; }
187
188 /**
189 * Adds 1 new default-initialized T value and returns it by reference. Note
190 * the reference only remains valid until the next call that adds or removes
191 * elements.
192 */
push_back()193 T& push_back() {
194 void* newT = this->push_back_raw(1);
195 return *new (newT) T;
196 }
197
198 /**
199 * Version of above that uses a copy constructor to initialize the new item
200 */
push_back(const T & t)201 T& push_back(const T& t) {
202 void* newT = this->push_back_raw(1);
203 return *new (newT) T(t);
204 }
205
206 /**
207 * Version of above that uses a move constructor to initialize the new item
208 */
push_back(T && t)209 T& push_back(T&& t) {
210 void* newT = this->push_back_raw(1);
211 return *new (newT) T(std::move(t));
212 }
213
214 /**
215 * Construct a new T at the back of this array.
216 */
emplace_back(Args &&...args)217 template<class... Args> T& emplace_back(Args&&... args) {
218 void* newT = this->push_back_raw(1);
219 return *new (newT) T(std::forward<Args>(args)...);
220 }
221
222 /**
223 * Allocates n more default-initialized T values, and returns the address of
224 * the start of that new range. Note: this address is only valid until the
225 * next API call made on the array that might add or remove elements.
226 */
push_back_n(int n)227 T* push_back_n(int n) {
228 SkASSERT(n >= 0);
229 T* newTs = TCast(this->push_back_raw(n));
230 for (int i = 0; i < n; ++i) {
231 new (&newTs[i]) T;
232 }
233 return newTs;
234 }
235
236 /**
237 * Version of above that uses a copy constructor to initialize all n items
238 * to the same T.
239 */
push_back_n(int n,const T & t)240 T* push_back_n(int n, const T& t) {
241 SkASSERT(n >= 0);
242 T* newTs = TCast(this->push_back_raw(n));
243 for (int i = 0; i < n; ++i) {
244 new (&newTs[i]) T(t);
245 }
246 return static_cast<T*>(newTs);
247 }
248
249 /**
250 * Version of above that uses a copy constructor to initialize the n items
251 * to separate T values.
252 */
push_back_n(int n,const T t[])253 T* push_back_n(int n, const T t[]) {
254 SkASSERT(n >= 0);
255 this->checkRealloc(n, kGrowing);
256 T* end = this->end();
257 for (int i = 0; i < n; ++i) {
258 new (end + i) T(t[i]);
259 }
260 fSize += n;
261 return end;
262 }
263
264 /**
265 * Version of above that uses the move constructor to set n items.
266 */
move_back_n(int n,T * t)267 T* move_back_n(int n, T* t) {
268 SkASSERT(n >= 0);
269 this->checkRealloc(n, kGrowing);
270 T* end = this->end();
271 for (int i = 0; i < n; ++i) {
272 new (end + i) T(std::move(t[i]));
273 }
274 fSize += n;
275 return end;
276 }
277
278 /**
279 * Removes the last element. Not safe to call when size() == 0.
280 */
pop_back()281 void pop_back() {
282 SkASSERT(fSize > 0);
283 --fSize;
284 fData[fSize].~T();
285 }
286
287 /**
288 * Removes the last n elements. Not safe to call when size() < n.
289 */
pop_back_n(int n)290 void pop_back_n(int n) {
291 SkASSERT(n >= 0);
292 SkASSERT(this->size() >= n);
293 int i = fSize;
294 while (i-- > fSize - n) {
295 (*this)[i].~T();
296 }
297 fSize -= n;
298 }
299
300 /**
301 * Pushes or pops from the back to resize. Pushes will be default
302 * initialized.
303 */
resize_back(int newCount)304 void resize_back(int newCount) {
305 SkASSERT(newCount >= 0);
306
307 if (newCount > this->size()) {
308 this->push_back_n(newCount - fSize);
309 } else if (newCount < this->size()) {
310 this->pop_back_n(fSize - newCount);
311 }
312 }
313
314 /** Swaps the contents of this array with that array. Does a pointer swap if possible,
315 otherwise copies the T values. */
swap(SkTArray & that)316 void swap(SkTArray& that) {
317 using std::swap;
318 if (this == &that) {
319 return;
320 }
321 if (fOwnMemory && that.fOwnMemory) {
322 swap(fData, that.fData);
323 swap(fSize, that.fSize);
324
325 // Can't use swap because fCapacity is a bit field.
326 auto allocCount = fCapacity;
327 fCapacity = that.fCapacity;
328 that.fCapacity = allocCount;
329 } else {
330 // This could be more optimal...
331 SkTArray copy(std::move(that));
332 that = std::move(*this);
333 *this = std::move(copy);
334 }
335 }
336
begin()337 T* begin() {
338 return fData;
339 }
begin()340 const T* begin() const {
341 return fData;
342 }
343
344 // It's safe to use fItemArray + fSize because if fItemArray is nullptr then adding 0 is
345 // valid and returns nullptr. See [expr.add] in the C++ standard.
end()346 T* end() {
347 if (fData == nullptr) {
348 SkASSERT(fSize == 0);
349 }
350 return fData + fSize;
351 }
end()352 const T* end() const {
353 if (fData == nullptr) {
354 SkASSERT(fSize == 0);
355 }
356 return fData + fSize;
357 }
data()358 T* data() { return fData; }
data()359 const T* data() const { return fData; }
size()360 int size() const { return fSize; }
size_bytes()361 size_t size_bytes() const { return this->bytes(fSize); }
resize(size_t count)362 void resize(size_t count) { this->resize_back((int)count); }
363
clear()364 void clear() {
365 this->destroyAll();
366 fSize = 0;
367 }
368
shrink_to_fit()369 void shrink_to_fit() {
370 if (!fOwnMemory || fSize == fCapacity) {
371 return;
372 }
373 if (fSize == 0) {
374 sk_free(fData);
375 fData = nullptr;
376 fCapacity = 0;
377 } else {
378 SkSpan<std::byte> allocation = Allocate(fSize);
379 this->move(TCast(allocation.data()));
380 if (fOwnMemory) {
381 sk_free(fData);
382 }
383 this->setDataFromBytes(allocation);
384 }
385 }
386
387 /**
388 * Get the i^th element.
389 */
390 T& operator[] (int i) {
391 SkASSERT(i < this->size());
392 SkASSERT(i >= 0);
393 return fData[i];
394 }
395
396 const T& operator[] (int i) const {
397 SkASSERT(i < this->size());
398 SkASSERT(i >= 0);
399 return fData[i];
400 }
401
at(int i)402 T& at(int i) { return (*this)[i]; }
at(int i)403 const T& at(int i) const { return (*this)[i]; }
404
405 /**
406 * equivalent to operator[](0)
407 */
front()408 T& front() { SkASSERT(fSize > 0); return fData[0];}
409
front()410 const T& front() const { SkASSERT(fSize > 0); return fData[0];}
411
412 /**
413 * equivalent to operator[](size() - 1)
414 */
back()415 T& back() { SkASSERT(fSize); return fData[fSize - 1];}
416
back()417 const T& back() const { SkASSERT(fSize > 0); return fData[fSize - 1];}
418
419 /**
420 * equivalent to operator[](size()-1-i)
421 */
fromBack(int i)422 T& fromBack(int i) {
423 SkASSERT(i >= 0);
424 SkASSERT(i < this->size());
425 return fData[fSize - i - 1];
426 }
427
fromBack(int i)428 const T& fromBack(int i) const {
429 SkASSERT(i >= 0);
430 SkASSERT(i < this->size());
431 return fData[fSize - i - 1];
432 }
433
434 bool operator==(const SkTArray<T, MEM_MOVE>& right) const {
435 int leftCount = this->size();
436 if (leftCount != right.size()) {
437 return false;
438 }
439 for (int index = 0; index < leftCount; ++index) {
440 if (fData[index] != right.fData[index]) {
441 return false;
442 }
443 }
444 return true;
445 }
446
447 bool operator!=(const SkTArray<T, MEM_MOVE>& right) const {
448 return !(*this == right);
449 }
450
capacity()451 int capacity() const {
452 return fCapacity;
453 }
454
455 protected:
456 // Creates an empty array that will use the passed storage block until it is insufficiently
457 // large to hold the entire array.
458 template <int InitialCapacity>
459 SkTArray(SkAlignedSTStorage<InitialCapacity, T>* storage, int size = 0) {
460 static_assert(InitialCapacity >= 0);
461 SkASSERT(size >= 0);
462 SkASSERT(storage->get() != nullptr);
463 if (size > InitialCapacity) {
464 this->initData(size);
465 } else {
466 this->setDataFromBytes(*storage);
467 fSize = size;
468
469 // setDataFromBytes always sets fOwnMemory to true, but we are actually using static
470 // storage here, which shouldn't ever be freed.
471 fOwnMemory = false;
472 }
473 }
474
475 // Copy a C array, using pre-allocated storage if preAllocCount >= count. Otherwise, storage
476 // will only be used when array shrinks to fit.
477 template <int InitialCapacity>
SkTArray(const T * array,int size,SkAlignedSTStorage<InitialCapacity,T> * storage)478 SkTArray(const T* array, int size, SkAlignedSTStorage<InitialCapacity, T>* storage)
479 : SkTArray{storage, size}
480 {
481 this->copy(array);
482 }
483
484 private:
485 // Growth factors for checkRealloc.
486 static constexpr double kExactFit = 1.0;
487 static constexpr double kGrowing = 1.5;
488
489 static constexpr int kMinHeapAllocCount = 8;
490 static_assert(SkIsPow2(kMinHeapAllocCount), "min alloc count not power of two.");
491
492 // Note for 32-bit machines kMaxCapacity will be <= SIZE_MAX. For 64-bit machines it will
493 // just be INT_MAX if the sizeof(T) < 2^32.
494 static constexpr int kMaxCapacity = SkToInt(std::min(SIZE_MAX / sizeof(T), (size_t)INT_MAX));
495
setDataFromBytes(SkSpan<std::byte> allocation)496 void setDataFromBytes(SkSpan<std::byte> allocation) {
497 T* data = TCast(allocation.data());
498 // We have gotten extra bytes back from the allocation limit, pin to kMaxCapacity. It
499 // would seem like the SkContainerAllocator should handle the divide, but it would have
500 // to a full divide instruction. If done here the size is known at compile, and usually
501 // can be implemented by a right shift. The full divide takes ~50X longer than the shift.
502 size_t size = std::min(allocation.size() / sizeof(T), SkToSizeT(kMaxCapacity));
503 setData(SkSpan<T>(data, size));
504 }
505
setData(SkSpan<T> array)506 void setData(SkSpan<T> array) {
507 fData = array.data();
508 fCapacity = SkToU32(array.size());
509 fOwnMemory = true;
510 }
511
512 // We disable Control-Flow Integrity sanitization (go/cfi) when casting item-array buffers.
513 // CFI flags this code as dangerous because we are casting `buffer` to a T* while the buffer's
514 // contents might still be uninitialized memory. When T has a vtable, this is especially risky
515 // because we could hypothetically access a virtual method on fItemArray and jump to an
516 // unpredictable location in memory. Of course, SkTArray won't actually use fItemArray in this
517 // way, and we don't want to construct a T before the user requests one. There's no real risk
518 // here, so disable CFI when doing these casts.
519 SK_NO_SANITIZE("cfi")
TCast(void * buffer)520 static T* TCast(void* buffer) {
521 return (T*)buffer;
522 }
523
bytes(int n)524 size_t bytes(int n) const {
525 SkASSERT(n <= kMaxCapacity);
526 return SkToSizeT(n) * sizeof(T);
527 }
528
529 static SkSpan<std::byte> Allocate(int capacity, double growthFactor = 1.0) {
530 return SkContainerAllocator{sizeof(T), kMaxCapacity}.allocate(capacity, growthFactor);
531 }
532
initData(int count)533 void initData(int count) {
534 this->setDataFromBytes(Allocate(count));
535 fSize = count;
536 }
537
destroyAll()538 void destroyAll() {
539 if (!this->empty()) {
540 T* cursor = this->begin();
541 T* const end = this->end();
542 do {
543 cursor->~T();
544 cursor++;
545 } while (cursor < end);
546 }
547 }
548
549 /** In the following move and copy methods, 'dst' is assumed to be uninitialized raw storage.
550 * In the following move methods, 'src' is destroyed leaving behind uninitialized raw storage.
551 */
copy(const T * src)552 void copy(const T* src) {
553 if constexpr (std::is_trivially_copyable_v<T>) {
554 if (!this->empty() && src != nullptr) {
555 sk_careful_memcpy(fData, src, this->size_bytes());
556 }
557 } else {
558 for (int i = 0; i < this->size(); ++i) {
559 new (fData + i) T(src[i]);
560 }
561 }
562 }
563
move(int dst,int src)564 void move(int dst, int src) {
565 if constexpr (MEM_MOVE) {
566 memcpy(static_cast<void*>(&fData[dst]),
567 static_cast<const void*>(&fData[src]),
568 sizeof(T));
569 } else {
570 new (&fData[dst]) T(std::move(fData[src]));
571 fData[src].~T();
572 }
573 }
574
move(void * dst)575 void move(void* dst) {
576 if constexpr (MEM_MOVE) {
577 sk_careful_memcpy(dst, fData, this->bytes(fSize));
578 } else {
579 for (int i = 0; i < this->size(); ++i) {
580 new (static_cast<char*>(dst) + this->bytes(i)) T(std::move(fData[i]));
581 fData[i].~T();
582 }
583 }
584 }
585
586 // Helper function that makes space for n objects, adjusts the count, but does not initialize
587 // the new objects.
push_back_raw(int n)588 void* push_back_raw(int n) {
589 this->checkRealloc(n, kGrowing);
590 void* ptr = fData + fSize;
591 fSize += n;
592 return ptr;
593 }
594
checkRealloc(int delta,double growthFactor)595 void checkRealloc(int delta, double growthFactor) {
596 // This constant needs to be declared in the function where it is used to work around
597 // MSVC's persnickety nature about template definitions.
598 SkASSERT(delta >= 0);
599 SkASSERT(fSize >= 0);
600 SkASSERT(fCapacity >= 0);
601
602 // Return if there are enough remaining allocated elements to satisfy the request.
603 if (this->capacity() - fSize >= delta) {
604 return;
605 }
606
607 // Don't overflow fSize or size_t later in the memory allocation. Overflowing memory
608 // allocation really only applies to fSizes on 32-bit machines; on 64-bit machines this
609 // will probably never produce a check. Since kMaxCapacity is bounded above by INT_MAX,
610 // this also checks the bounds of fSize.
611 if (delta > kMaxCapacity - fSize) {
612 sk_report_container_overflow_and_die();
613 }
614 const int newCount = fSize + delta;
615
616 SkSpan<std::byte> allocation = Allocate(newCount, growthFactor);
617
618 this->move(TCast(allocation.data()));
619 if (fOwnMemory) {
620 sk_free(fData);
621 }
622 this->setDataFromBytes(allocation);
623 SkASSERT(this->capacity() >= newCount);
624 SkASSERT(fData != nullptr);
625 }
626
627 T* fData{nullptr};
628 int fSize{0};
629 uint32_t fOwnMemory : 1;
630 uint32_t fCapacity : 31;
631 };
632
swap(SkTArray<T,M> & a,SkTArray<T,M> & b)633 template <typename T, bool M> static inline void swap(SkTArray<T, M>& a, SkTArray<T, M>& b) {
634 a.swap(b);
635 }
636
637 /**
638 * Subclass of SkTArray that contains a preallocated memory block for the array.
639 */
640 template <int N, typename T, bool MEM_MOVE = sk_is_trivially_relocatable_v<T>>
641 class SkSTArray : private SkAlignedSTStorage<N,T>, public SkTArray<T, MEM_MOVE> {
642 private:
643 static_assert(N > 0);
644 using STORAGE = SkAlignedSTStorage<N,T>;
645 using INHERITED = SkTArray<T, MEM_MOVE>;
646
647 public:
SkSTArray()648 SkSTArray()
649 : STORAGE{}, INHERITED(static_cast<STORAGE*>(this)) {}
650
SkSTArray(const T * array,int count)651 SkSTArray(const T* array, int count)
652 : STORAGE{}, INHERITED(array, count, static_cast<STORAGE*>(this)) {}
653
SkSTArray(std::initializer_list<T> data)654 SkSTArray(std::initializer_list<T> data) : SkSTArray(data.begin(), SkToInt(data.size())) {}
655
SkSTArray(int reserveCount)656 explicit SkSTArray(int reserveCount) : SkSTArray() {
657 this->reserve_back(reserveCount);
658 }
659
SkSTArray(const SkSTArray & that)660 SkSTArray (const SkSTArray& that) : SkSTArray() { *this = that; }
SkSTArray(const INHERITED & that)661 explicit SkSTArray(const INHERITED& that) : SkSTArray() { *this = that; }
SkSTArray(SkSTArray && that)662 SkSTArray ( SkSTArray&& that) : SkSTArray() { *this = std::move(that); }
SkSTArray(INHERITED && that)663 explicit SkSTArray( INHERITED&& that) : SkSTArray() { *this = std::move(that); }
664
665 SkSTArray& operator=(const SkSTArray& that) {
666 INHERITED::operator=(that);
667 return *this;
668 }
669 SkSTArray& operator=(const INHERITED& that) {
670 INHERITED::operator=(that);
671 return *this;
672 }
673
674 SkSTArray& operator=(SkSTArray&& that) {
675 INHERITED::operator=(std::move(that));
676 return *this;
677 }
678 SkSTArray& operator=(INHERITED&& that) {
679 INHERITED::operator=(std::move(that));
680 return *this;
681 }
682
683 // Force the use of SkTArray for data() and size().
684 using INHERITED::data;
685 using INHERITED::size;
686 };
687
688 #endif
689