• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 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 #include "src/core/SkTBlockList.h"
9 #include "tests/Test.h"
10 
11 namespace {
12 struct C {
C__anon25be94e40111::C13     C() : fID(-1) { ++gInstCnt; }
C__anon25be94e40111::C14     C(int id) : fID(id) { ++gInstCnt; }
C__anon25be94e40111::C15     C(C&& c) : C(c.fID) {}
C__anon25be94e40111::C16     C(const C& c) : C(c.fID) {}
17 
18     C& operator=(C&&) = default;
19     C& operator=(const C&) = default;
20 
~C__anon25be94e40111::C21     ~C() { --gInstCnt; }
22 
23     int fID;
24 
25     // Under the hood, SkTBlockList and SkBlockAllocator round up to max_align_t. If 'C' was
26     // just 4 bytes, that often means the internal blocks can squeeze a few extra instances in. This
27     // is fine, but makes predicting a little trickier, so make sure C is a bit bigger.
28     int fPadding[4];
29 
30     static int gInstCnt;
31 };
32 int C::gInstCnt = 0;
33 
34 struct D {
35     int fID;
36 };
37 
38 }  // namespace
39 
40 class TBlockListTestAccess {
41 public:
42     template<int N>
ScratchBlockSize(SkTBlockList<C,N> & list)43     static size_t ScratchBlockSize(SkTBlockList<C, N>& list) {
44         return (size_t) list.fAllocator->scratchBlockSize();
45     }
46 
47     template<int N>
TotalSize(SkTBlockList<C,N> & list)48     static size_t TotalSize(SkTBlockList<C, N>& list) {
49         return list.fAllocator->totalSize();
50     }
51 };
52 
53 // Checks that the allocator has the correct count, etc and that the element IDs are correct.
54 // Then pops popCnt items and checks again.
55 template<int N>
check_allocator_helper(SkTBlockList<C,N> * allocator,int cnt,int popCnt,skiatest::Reporter * reporter)56 static void check_allocator_helper(SkTBlockList<C, N>* allocator, int cnt, int popCnt,
57                                    skiatest::Reporter* reporter) {
58     REPORTER_ASSERT(reporter, (0 == cnt) == allocator->empty());
59     REPORTER_ASSERT(reporter, cnt == allocator->count());
60     REPORTER_ASSERT(reporter, cnt == C::gInstCnt);
61 
62     int i = 0;
63     for (const C& c : allocator->items()) {
64         REPORTER_ASSERT(reporter, i == c.fID);
65         REPORTER_ASSERT(reporter, allocator->item(i).fID == i);
66         ++i;
67     }
68     REPORTER_ASSERT(reporter, i == cnt);
69 
70     if (cnt > 0) {
71         REPORTER_ASSERT(reporter, cnt-1 == allocator->back().fID);
72     }
73 
74     if (popCnt > 0) {
75         for (i = 0; i < popCnt; ++i) {
76             allocator->pop_back();
77         }
78         check_allocator_helper(allocator, cnt - popCnt, 0, reporter);
79     }
80 }
81 
82 template<int N>
check_iterator_helper(SkTBlockList<C,N> * allocator,const std::vector<C * > & expected,skiatest::Reporter * reporter)83 static void check_iterator_helper(SkTBlockList<C, N>* allocator,
84                                   const std::vector<C*>& expected,
85                                   skiatest::Reporter* reporter) {
86     const SkTBlockList<C, N>* cAlloc = allocator;
87     REPORTER_ASSERT(reporter, (size_t) allocator->count() == expected.size());
88     // Forward+const
89     int i = 0;
90     for (const C& c : cAlloc->items()) {
91         REPORTER_ASSERT(reporter, (uintptr_t) &c == (uintptr_t) expected[i]);
92         ++i;
93     }
94     REPORTER_ASSERT(reporter, (size_t) i == expected.size());
95 
96     // Forward+non-const
97     i = 0;
98     for (C& c : allocator->items()) {
99         REPORTER_ASSERT(reporter, (uintptr_t) &c == (uintptr_t) expected[i]);
100         ++i;
101     }
102     REPORTER_ASSERT(reporter, (size_t) i == expected.size());
103 
104     // Reverse+const
105     i = (int) expected.size() - 1;
106     for (const C& c : cAlloc->ritems()) {
107         REPORTER_ASSERT(reporter, (uintptr_t) &c == (uintptr_t) expected[i]);
108         --i;
109     }
110     REPORTER_ASSERT(reporter, i == -1);
111 
112     // Reverse+non-const
113     i = (int) expected.size() - 1;
114     for (C& c : allocator->ritems()) {
115         REPORTER_ASSERT(reporter, (uintptr_t) &c == (uintptr_t) expected[i]);
116         --i;
117     }
118     REPORTER_ASSERT(reporter, i == -1);
119 
120     // Also test random access
121     for (i = 0; i < allocator->count(); ++i) {
122         REPORTER_ASSERT(reporter, (uintptr_t) &allocator->item(i) == (uintptr_t) expected[i]);
123         REPORTER_ASSERT(reporter, (uintptr_t) &cAlloc->item(i) == (uintptr_t) expected[i]);
124     }
125 }
126 
127 // Adds cnt items to the allocator, tests the cnts and iterators, pops popCnt items and checks
128 // again. Finally it resets the allocator and checks again.
129 template<int N>
check_allocator(SkTBlockList<C,N> * allocator,int cnt,int popCnt,skiatest::Reporter * reporter)130 static void check_allocator(SkTBlockList<C, N>* allocator, int cnt, int popCnt,
131                             skiatest::Reporter* reporter) {
132     enum ItemInitializer : int {
133         kCopyCtor,
134         kMoveCtor,
135         kCopyAssign,
136         kMoveAssign,
137         kEmplace,
138     };
139     static constexpr int kInitCount = (int) kEmplace + 1;
140 
141     SkASSERT(allocator);
142     SkASSERT(allocator->empty());
143     std::vector<C*> items;
144     for (int i = 0; i < cnt; ++i) {
145         switch((ItemInitializer) (i % kInitCount)) {
146             case kCopyCtor:
147                 allocator->push_back(C(i));
148                 break;
149             case kMoveCtor:
150                 allocator->push_back(std::move(C(i)));
151                 break;
152             case kCopyAssign:
153                 allocator->push_back() = C(i);
154                 break;
155             case kMoveAssign:
156                 allocator->push_back() = std::move(C(i));
157                 break;
158             case kEmplace:
159                 allocator->emplace_back(i);
160                 break;
161         }
162         items.push_back(&allocator->back());
163     }
164     check_iterator_helper(allocator, items, reporter);
165     check_allocator_helper(allocator, cnt, popCnt, reporter);
166     allocator->reset();
167     check_iterator_helper(allocator, {}, reporter);
168     check_allocator_helper(allocator, 0, 0, reporter);
169 }
170 
171 template<int N>
run_allocator_test(SkTBlockList<C,N> * allocator,skiatest::Reporter * reporter)172 static void run_allocator_test(SkTBlockList<C, N>* allocator, skiatest::Reporter* reporter) {
173     check_allocator(allocator, 0, 0, reporter);
174     check_allocator(allocator, 1, 1, reporter);
175     check_allocator(allocator, 2, 2, reporter);
176     check_allocator(allocator, 10, 1, reporter);
177     check_allocator(allocator, 10, 5, reporter);
178     check_allocator(allocator, 10, 10, reporter);
179     check_allocator(allocator, 100, 10, reporter);
180 }
181 
182 template<int N1, int N2>
run_concat_test(skiatest::Reporter * reporter,int aCount,int bCount)183 static void run_concat_test(skiatest::Reporter* reporter, int aCount, int bCount) {
184 
185     SkTBlockList<C, N1> listA;
186     SkTBlockList<C, N2> listB;
187 
188     for (int i = 0; i < aCount; ++i) {
189         listA.emplace_back(i);
190     }
191     for (int i = 0; i < bCount; ++i) {
192         listB.emplace_back(aCount + i);
193     }
194 
195     REPORTER_ASSERT(reporter, listA.count() == aCount && listB.count() == bCount);
196     REPORTER_ASSERT(reporter, C::gInstCnt == aCount + bCount);
197 
198     // Concatenate B into A and verify.
199     listA.concat(std::move(listB));
200     REPORTER_ASSERT(reporter, listA.count() == aCount + bCount);
201     // SkTBlockList guarantees the moved list is empty, but clang-tidy doesn't know about it;
202     // in practice we won't really be using moved lists so this won't pollute our main code base
203     // with lots of warning disables.
204     REPORTER_ASSERT(reporter, listB.count() == 0); // NOLINT(bugprone-use-after-move)
205     REPORTER_ASSERT(reporter, C::gInstCnt == aCount + bCount);
206 
207     int i = 0;
208     for (const C& item : listA.items()) {
209         // By construction of A and B originally, the concatenated id sequence is continuous
210         REPORTER_ASSERT(reporter, i == item.fID);
211         i++;
212     }
213     REPORTER_ASSERT(reporter, i == (aCount + bCount));
214 }
215 
216 template<int N1, int N2>
run_concat_trivial_test(skiatest::Reporter * reporter,int aCount,int bCount)217 static void run_concat_trivial_test(skiatest::Reporter* reporter, int aCount, int bCount) {
218     static_assert(std::is_trivially_copyable<D>::value);
219 
220     // This is similar to run_concat_test(), except since D is trivial we can't verify the instant
221     // counts that are tracked via ctor/dtor.
222     SkTBlockList<D, N1> listA;
223     SkTBlockList<D, N2> listB;
224 
225     for (int i = 0; i < aCount; ++i) {
226         listA.push_back({i});
227     }
228     for (int i = 0; i < bCount; ++i) {
229         listB.push_back({aCount + i});
230     }
231 
232     REPORTER_ASSERT(reporter, listA.count() == aCount && listB.count() == bCount);
233     // Concatenate B into A and verify.
234     listA.concat(std::move(listB));
235     REPORTER_ASSERT(reporter, listA.count() == aCount + bCount);
236     REPORTER_ASSERT(reporter, listB.count() == 0); // NOLINT(bugprone-use-after-move): see above
237 
238     int i = 0;
239     for (const D& item : listA.items()) {
240         // By construction of A and B originally, the concatenated id sequence is continuous
241         REPORTER_ASSERT(reporter, i == item.fID);
242         i++;
243     }
244     REPORTER_ASSERT(reporter, i == (aCount + bCount));
245 }
246 
247 template<int N>
run_reserve_test(skiatest::Reporter * reporter)248 static void run_reserve_test(skiatest::Reporter* reporter) {
249     constexpr int kItemsPerBlock = N + 4; // Make this a number > 1, even if N starting items == 1
250 
251     SkTBlockList<C, N> list(kItemsPerBlock);
252     size_t initialSize = TBlockListTestAccess::TotalSize(list);
253     // Should be able to add N instances of T w/o changing size from initialSize
254     for (int i = 0; i < N; ++i) {
255         list.push_back(C(i));
256     }
257     REPORTER_ASSERT(reporter, initialSize == TBlockListTestAccess::TotalSize(list));
258 
259     // Reserve room for 2*kItemsPerBlock items
260     list.reserve(2 * kItemsPerBlock);
261     REPORTER_ASSERT(reporter, list.count() == N); // count shouldn't change though
262 
263     size_t reservedSize = TBlockListTestAccess::TotalSize(list);
264     REPORTER_ASSERT(reporter, reservedSize >= initialSize + 2 * kItemsPerBlock * sizeof(C));
265     for (int i = 0; i < 2 * kItemsPerBlock; ++i) {
266         list.push_back(C(i));
267     }
268     REPORTER_ASSERT(reporter, reservedSize == TBlockListTestAccess::TotalSize(list));
269 
270     // Make the next block partially fully (N > 0 but < kItemsPerBlock)
271     for (int i = 0; i < N; ++i) {
272         list.push_back(C(i));
273     }
274 
275     // Reserve room again for 2*kItemsPerBlock, but reserve should automatically take account of the
276     // (kItemsPerBlock-N) that are still available in the active block
277     list.reserve(2 * kItemsPerBlock);
278     int extraReservedCount = kItemsPerBlock + N;
279     // Because SkTBlockList normally allocates blocks in fixed sizes, and extraReservedCount >
280     // items-per-block, it will always use that size and not that of the growth policy.
281     REPORTER_ASSERT(reporter, TBlockListTestAccess::ScratchBlockSize(list) >=
282                                        extraReservedCount * sizeof(C));
283 
284     reservedSize = TBlockListTestAccess::TotalSize(list);
285     for (int i = 0; i < 2 * kItemsPerBlock; ++i) {
286         list.push_back(C(i));
287     }
288     REPORTER_ASSERT(reporter, reservedSize == TBlockListTestAccess::TotalSize(list));
289 
290     // If we reserve a count < items-per-block, it will use the fixed size from the growth policy.
291     list.reserve(2);
292     REPORTER_ASSERT(reporter, TBlockListTestAccess::ScratchBlockSize(list) >=
293                                        kItemsPerBlock * sizeof(C));
294 
295     // Ensure the reservations didn't initialize any more D's than anticipated
296     int expectedInstanceCount = 2 * (N + 2 * kItemsPerBlock);
297     REPORTER_ASSERT(reporter, expectedInstanceCount == C::gInstCnt);
298 
299     list.reset();
300     REPORTER_ASSERT(reporter, 0 == C::gInstCnt);
301 }
302 
DEF_TEST(SkTBlockList,reporter)303 DEF_TEST(SkTBlockList, reporter) {
304     // Test combinations of allocators with and without stack storage and with different block sizes
305     SkTBlockList<C> a1(1);
306     run_allocator_test(&a1, reporter);
307 
308     SkTBlockList<C> a2(2);
309     run_allocator_test(&a2, reporter);
310 
311     SkTBlockList<C> a5(5);
312     run_allocator_test(&a5, reporter);
313 
314     SkTBlockList<C, 1> sa1;
315     run_allocator_test(&sa1, reporter);
316 
317     SkTBlockList<C, 3> sa3;
318     run_allocator_test(&sa3, reporter);
319 
320     SkTBlockList<C, 4> sa4;
321     run_allocator_test(&sa4, reporter);
322 
323     run_reserve_test<1>(reporter);
324     run_reserve_test<2>(reporter);
325     run_reserve_test<3>(reporter);
326     run_reserve_test<4>(reporter);
327     run_reserve_test<5>(reporter);
328 
329     run_concat_test<1, 1>(reporter, 10, 10);
330     run_concat_test<5, 1>(reporter, 50, 10);
331     run_concat_test<1, 5>(reporter, 10, 50);
332     run_concat_test<5, 5>(reporter, 100, 100);
333 
334     run_concat_trivial_test<1, 1>(reporter, 10, 10);
335     run_concat_trivial_test<5, 1>(reporter, 50, 10);
336     run_concat_trivial_test<1, 5>(reporter, 10, 50);
337     run_concat_trivial_test<5, 5>(reporter, 100, 100);
338 }
339