[bmalloc] IsoHeap should have lower tier using shared IsoPage
[WebKit-https.git] / Source / bmalloc / test / testbmalloc.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include <bmalloc/bmalloc.h>
27 #include <bmalloc/Environment.h>
28 #include <bmalloc/IsoHeapInlines.h>
29 #include <cmath>
30 #include <cstdlib>
31 #include <set>
32 #include <vector>
33
34 using namespace bmalloc;
35 using namespace bmalloc::api;
36
37 // We don't have a NO_RETURN_DUE_TO_EXIT, nor should we. That's ridiculous.
38 static bool hiddenTruthBecauseNoReturnIsStupid() { return true; }
39
40 static void usage()
41 {
42     puts("Usage: testb3 [<filter>]");
43     if (hiddenTruthBecauseNoReturnIsStupid())
44         exit(1);
45 }
46
47 #define RUN(test) do {                          \
48         if (!shouldRun(#test))                  \
49             break;                              \
50         puts(#test "...");                      \
51         test;                                   \
52         puts(#test ": OK!");                    \
53     } while (false)
54
55 // Nothing fancy for now; we just use the existing WTF assertion machinery.
56 #define CHECK(x) do {                                                   \
57         if (!!(x))                                                      \
58             break;                                                      \
59         fprintf(stderr, "%s:%d: in %s: assertion %s failed.\n",         \
60             __FILE__, __LINE__, __PRETTY_FUNCTION__, #x);               \
61         abort();                                                        \
62     } while (false)
63
64 static std::set<void*> toptrset(const std::vector<void*>& ptrs)
65 {
66     std::set<void*> result;
67     for (void* ptr : ptrs) {
68         if (ptr)
69             result.insert(ptr);
70     }
71     return result;
72 }
73
74 static void assertEmptyPointerSet(const std::set<void*>& pointers)
75 {
76     if (Environment::get()->isDebugHeapEnabled()) {
77         printf("    skipping checks because DebugHeap.\n");
78         return;
79     }
80     if (pointers.empty())
81         return;
82     printf("Pointer set not empty!\n");
83     printf("Pointers:");
84     for (void* ptr : pointers)
85         printf(" %p", ptr);
86     printf("\n");
87     CHECK(pointers.empty());
88 }
89
90 template<typename heapType>
91 static void assertHasObjects(IsoHeap<heapType>& heap, std::set<void*> pointers)
92 {
93     if (Environment::get()->isDebugHeapEnabled()) {
94         printf("    skipping checks because DebugHeap.\n");
95         return;
96     }
97     auto& impl = heap.impl();
98     std::lock_guard<Mutex> locker(impl.lock);
99     impl.forEachLiveObject(
100         [&] (void* object) {
101             pointers.erase(object);
102         });
103     assertEmptyPointerSet(pointers);
104 }
105
106 template<typename heapType>
107 static void assertHasOnlyObjects(IsoHeap<heapType>& heap, std::set<void*> pointers)
108 {
109     if (Environment::get()->isDebugHeapEnabled()) {
110         printf("    skipping checks because DebugHeap.\n");
111         return;
112     }
113     auto& impl = heap.impl();
114     std::lock_guard<Mutex> locker(impl.lock);
115     impl.forEachLiveObject(
116         [&] (void* object) {
117             CHECK(pointers.erase(object) == 1);
118         });
119     assertEmptyPointerSet(pointers);
120 }
121
122 template<typename heapType>
123 static void assertClean(IsoHeap<heapType>& heap)
124 {
125     scavengeThisThread();
126     if (!Environment::get()->isDebugHeapEnabled()) {
127         auto& impl = heap.impl();
128         {
129             std::lock_guard<Mutex> locker(impl.lock);
130             CHECK(!impl.numLiveObjects());
131         }
132     }
133     heap.scavenge();
134     if (!Environment::get()->isDebugHeapEnabled()) {
135         auto& impl = heap.impl();
136         std::lock_guard<Mutex> locker(impl.lock);
137         CHECK(!impl.numCommittedPages());
138     }
139 }
140
141 static void testIsoSimple()
142 {
143     static IsoHeap<double> heap;
144     void* ptr1 = heap.allocate();
145     CHECK(ptr1);
146     void* ptr2 = heap.allocate();
147     CHECK(ptr2);
148     CHECK(ptr1 != ptr2);
149     CHECK(std::abs(static_cast<char*>(ptr1) - static_cast<char*>(ptr2)) >= 8);
150     assertHasObjects(heap, {ptr1, ptr2});
151     heap.deallocate(ptr1);
152     heap.deallocate(ptr2);
153     assertClean(heap);
154 }
155
156 static void testIsoSimpleScavengeBeforeDealloc()
157 {
158     static IsoHeap<double> heap;
159     void* ptr1 = heap.allocate();
160     CHECK(ptr1);
161     void* ptr2 = heap.allocate();
162     CHECK(ptr2);
163     CHECK(ptr1 != ptr2);
164     CHECK(std::abs(static_cast<char*>(ptr1) - static_cast<char*>(ptr2)) >= 8);
165     scavengeThisThread();
166     assertHasOnlyObjects(heap, {ptr1, ptr2});
167     heap.deallocate(ptr1);
168     heap.deallocate(ptr2);
169     assertClean(heap);
170 }
171
172 static void testIsoFlipFlopFragmentedPages()
173 {
174     static IsoHeap<double> heap;
175     std::vector<void*> ptrs;
176     for (unsigned i = 100000; i--;) {
177         void* ptr = heap.allocate();
178         CHECK(ptr);
179         ptrs.push_back(ptr);
180     }
181     for (unsigned i = 0; i < ptrs.size(); i += 2) {
182         heap.deallocate(ptrs[i]);
183         ptrs[i] = nullptr;
184     }
185     for (unsigned i = ptrs.size() / 2; i--;)
186         ptrs.push_back(heap.allocate());
187     for (void* ptr : ptrs)
188         heap.deallocate(ptr);
189     assertClean(heap);
190 }
191
192 static void testIsoFlipFlopFragmentedPagesScavengeInMiddle()
193 {
194     static IsoHeap<double> heap;
195     std::vector<void*> ptrs;
196     for (unsigned i = 100000; i--;) {
197         void* ptr = heap.allocate();
198         CHECK(ptr);
199         ptrs.push_back(ptr);
200     }
201     CHECK(toptrset(ptrs).size() == ptrs.size());
202     for (unsigned i = 0; i < ptrs.size(); i += 2) {
203         heap.deallocate(ptrs[i]);
204         ptrs[i] = nullptr;
205     }
206     heap.scavenge();
207     unsigned numCommittedPagesBefore;
208     auto& impl = heap.impl();
209     {
210         std::lock_guard<Mutex> locker(impl.lock);
211         numCommittedPagesBefore = impl.numCommittedPages();
212     }
213     assertHasOnlyObjects(heap, toptrset(ptrs));
214     for (unsigned i = ptrs.size() / 2; i--;)
215         ptrs.push_back(heap.allocate());
216     {
217         std::lock_guard<Mutex> locker(impl.lock);
218         CHECK(numCommittedPagesBefore == impl.numCommittedPages());
219     }
220     for (void* ptr : ptrs)
221         heap.deallocate(ptr);
222     assertClean(heap);
223 }
224
225 static void testIsoFlipFlopFragmentedPagesScavengeInMiddle288()
226 {
227     static IsoHeap<char[288]> heap;
228     std::vector<void*> ptrs;
229     for (unsigned i = 100000; i--;) {
230         void* ptr = heap.allocate();
231         CHECK(ptr);
232         ptrs.push_back(ptr);
233     }
234     CHECK(toptrset(ptrs).size() == ptrs.size());
235     for (unsigned i = 0; i < ptrs.size(); i += 2) {
236         heap.deallocate(ptrs[i]);
237         ptrs[i] = nullptr;
238     }
239     heap.scavenge();
240     unsigned numCommittedPagesBefore;
241     auto& impl = heap.impl();
242     {
243         std::lock_guard<Mutex> locker(impl.lock);
244         numCommittedPagesBefore = impl.numCommittedPages();
245     }
246     assertHasOnlyObjects(heap, toptrset(ptrs));
247     for (unsigned i = ptrs.size() / 2; i--;)
248         ptrs.push_back(heap.allocate());
249     {
250         std::lock_guard<Mutex> locker(impl.lock);
251         CHECK(numCommittedPagesBefore == impl.numCommittedPages());
252     }
253     for (void* ptr : ptrs)
254         heap.deallocate(ptr);
255     assertClean(heap);
256 }
257
258 static void testIsoMallocAndFreeFast()
259 {
260     static IsoHeap<char[256]> heap;
261     void* ptr = nullptr;
262     for (int i = 0; i < 1e6; ++i) {
263         ptr = heap.allocate();
264         heap.deallocate(ptr);
265     }
266     CHECK(!IsoPageBase::pageFor(ptr)->isShared());
267 }
268
269 class BisoMalloced {
270     MAKE_BISO_MALLOCED(BisoMalloced, BNOEXPORT);
271 public:
272     BisoMalloced(int x, float y)
273         : x(x)
274         , y(y)
275     {
276     }
277     
278     int x;
279     float y;
280 };
281
282 MAKE_BISO_MALLOCED_IMPL(BisoMalloced);
283
284 static void testBisoMalloced()
285 {
286     BisoMalloced* ptr = new BisoMalloced(4, 5);
287     assertHasObjects(BisoMalloced::bisoHeap(), { ptr });
288     delete ptr;
289     assertClean(BisoMalloced::bisoHeap());
290 }
291
292 class BisoMallocedInline {
293     MAKE_BISO_MALLOCED_INLINE(BisoMalloced);
294 public:
295     BisoMallocedInline(int x, float y)
296         : x(x)
297         , y(y)
298     {
299     }
300     
301     int x;
302     float y;
303 };
304
305 static void testBisoMallocedInline()
306 {
307     BisoMallocedInline* ptr = new BisoMallocedInline(4, 5);
308     assertHasObjects(BisoMallocedInline::bisoHeap(), { ptr });
309     delete ptr;
310     assertClean(BisoMallocedInline::bisoHeap());
311 }
312
313 static void run(const char* filter)
314 {
315     auto shouldRun = [&] (const char* testName) -> bool {
316         return !filter || !!strcasestr(testName, filter);
317     };
318     
319     RUN(testIsoSimple());
320     RUN(testIsoSimpleScavengeBeforeDealloc());
321     RUN(testIsoFlipFlopFragmentedPages());
322     RUN(testIsoFlipFlopFragmentedPagesScavengeInMiddle());
323     RUN(testIsoFlipFlopFragmentedPagesScavengeInMiddle288());
324     RUN(testIsoMallocAndFreeFast());
325     RUN(testBisoMalloced());
326     RUN(testBisoMallocedInline());
327     
328     puts("Success!");
329 }
330
331 int main(int argc, char** argv)
332 {
333     const char* filter = nullptr;
334     switch (argc) {
335     case 1:
336         break;
337     case 2:
338         filter = argv[1];
339         break;
340     default:
341         usage();
342         break;
343     }
344     
345     run(filter);
346     return 0;
347 }
348