• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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