1/*
2 * Copyright © 2018 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24/* it is a test after all */
25#undef NDEBUG
26
27#include <algorithm>
28#include <cassert>
29#include <climits>
30#include <cstdint>
31#include <cstdio>
32#include <cstdlib>
33#include <random>
34#include <set>
35#include <vector>
36
37#ifndef _WIN32
38#include <err.h>
39#else
40#define errx(code, msg, ...)             \
41   do {                                  \
42      fprintf(stderr, msg, __VA_ARGS__); \
43      exit(code);                        \
44   } while (0);
45#endif
46
47#include "vma.h"
48
49namespace {
50
51static const uint64_t MEM_PAGE_SIZE = 4096;
52
53struct allocation {
54   uint64_t start_page;
55   uint64_t num_pages;
56};
57
58struct allocation_less {
59   bool operator()(const allocation& lhs, const allocation& rhs) const
60   {
61      assert(lhs.start_page + lhs.num_pages > lhs.start_page);
62      return lhs.start_page + lhs.num_pages <= rhs.start_page;
63   }
64};
65
66constexpr uint64_t allocation_end_page(const allocation& a) {
67   return a.start_page + a.num_pages;
68}
69
70struct random_test {
71   static const uint64_t MEM_START_PAGE = 1;
72   static const uint64_t MEM_SIZE = 0xfffffffffffff000;
73   static const uint64_t MEM_PAGES = MEM_SIZE / MEM_PAGE_SIZE;
74
75   random_test(uint_fast32_t seed)
76      : heap_holes{allocation{MEM_START_PAGE, MEM_PAGES}}, rand{seed}
77   {
78      util_vma_heap_init(&heap, MEM_START_PAGE * MEM_PAGE_SIZE, MEM_SIZE);
79   }
80
81   ~random_test()
82   {
83      util_vma_heap_finish(&heap);
84   }
85
86   void test(unsigned long count)
87   {
88      std::uniform_int_distribution<> one_to_thousand(1, 1000);
89      while (count-- > 0) {
90         int action = one_to_thousand(rand);
91         if (action == 1)          fill();
92         else if (action == 2)     empty();
93         else if (action < 374)    dealloc();
94         else                      alloc();
95      }
96   }
97
98   bool alloc(uint64_t size_order=52, uint64_t align_order=52)
99   {
100      std::geometric_distribution<> dist;
101
102      if (align_order > 51)
103         align_order = std::min(dist(rand), 51);
104      uint64_t align_pages = 1ULL << align_order;
105      uint64_t align = align_pages * MEM_PAGE_SIZE;
106
107      if (size_order > 51)
108         size_order = std::min(dist(rand), 51);
109      uint64_t size_pages = 1ULL << size_order;
110      uint64_t size = size_pages * MEM_PAGE_SIZE;
111
112      uint64_t addr = util_vma_heap_alloc(&heap, size, align);
113
114      if (addr == 0) {
115         /* assert no gaps are present in the tracker that could satisfy this
116          * allocation.
117          */
118         for (const auto& hole : heap_holes) {
119            uint64_t hole_alignment_pages =
120               (align_pages - (hole.start_page % align_pages)) % align_pages;
121            assert(hole.num_pages < size_pages + hole_alignment_pages);
122         }
123         return false;
124      } else {
125         assert(addr % align == 0);
126         uint64_t addr_page = addr / MEM_PAGE_SIZE;
127         allocation a{addr_page, size_pages};
128         auto i = heap_holes.find(a);
129         assert(i != end(heap_holes));
130         allocation hole = *i;
131
132         assert(hole.start_page <= addr_page);
133         assert(hole.num_pages >= size_pages + addr_page - hole.start_page);
134
135         heap_holes.erase(i);
136         if (hole.start_page < a.start_page) {
137            heap_holes.emplace(allocation{hole.start_page,
138                     a.start_page - hole.start_page});
139         }
140         if (allocation_end_page(hole) > allocation_end_page(a)) {
141            heap_holes.emplace(allocation{allocation_end_page(a),
142                     allocation_end_page(hole) - allocation_end_page(a)});
143         }
144
145         allocations.push_back(a);
146         return true;
147      }
148   }
149
150   void dealloc()
151   {
152      if (allocations.size() == 0)
153         return;
154
155      std::uniform_int_distribution<> dist(0, allocations.size() - 1);
156      int to_dealloc = dist(rand);
157
158      std::swap(allocations.at(to_dealloc), allocations.back());
159      allocation a = allocations.back();
160      allocations.pop_back();
161
162      util_vma_heap_free(&heap, a.start_page * MEM_PAGE_SIZE,
163                         a.num_pages * MEM_PAGE_SIZE);
164
165      assert(heap_holes.find(a) == end(heap_holes));
166      auto next = heap_holes.upper_bound(a);
167      if (next != end(heap_holes)) {
168         if (next->start_page == allocation_end_page(a)) {
169            allocation x {a.start_page, a.num_pages + next->num_pages};
170            next = heap_holes.erase(next);
171            next = heap_holes.insert(next, x);
172
173            if (next != begin(heap_holes)) {
174               auto prev = next;
175               prev--;
176               if (allocation_end_page(*prev) == next->start_page) {
177                  allocation x {prev->start_page,
178                        prev->num_pages + next->num_pages};
179
180                  heap_holes.erase(prev);
181                  next = heap_holes.erase(next);
182                  heap_holes.insert(next, x);
183               }
184            }
185
186            return;
187         }
188      }
189
190      if (next != begin(heap_holes)) {
191         auto prev = next;
192         prev--;
193         if (allocation_end_page(*prev) == a.start_page) {
194            allocation x {prev->start_page, prev->num_pages + a.num_pages};
195            next = heap_holes.erase(prev);
196            heap_holes.insert(next, x);
197
198            return;
199         }
200      }
201
202      heap_holes.emplace(a);
203   }
204
205   void fill()
206   {
207      for (int sz = 51; sz >= 0; sz--) {
208         while (alloc(sz, 0))
209            ;
210      }
211      assert(heap_holes.empty());
212   }
213
214   void empty()
215   {
216      while (allocations.size() != 0)
217         dealloc();
218      assert(heap_holes.size() == 1);
219      auto& hole = *begin(heap_holes);
220      assert(hole.start_page == MEM_START_PAGE && hole.num_pages == MEM_PAGES);
221   }
222
223   struct util_vma_heap heap;
224   std::set<allocation, allocation_less> heap_holes;
225   std::default_random_engine rand;
226   std::vector<allocation> allocations;
227};
228
229}
230
231int main(int argc, char **argv)
232{
233   unsigned long seed, count;
234   if (argc == 3) {
235      char *arg_end = NULL;
236      seed = strtoul(argv[1], &arg_end, 0);
237      if (!arg_end || *arg_end || seed == ULONG_MAX)
238         errx(1, "invalid seed \"%s\"", argv[1]);
239
240      arg_end = NULL;
241      count = strtoul(argv[2], &arg_end, 0);
242      if (!arg_end || *arg_end || count == ULONG_MAX)
243         errx(1, "invalid count \"%s\"", argv[2]);
244   } else if (argc == 1) {
245      /* importantly chosen prime numbers. */
246      seed = 8675309;
247      count = 2459;
248   } else {
249      errx(1, "USAGE: %s seed iter_count\n", argv[0]);
250   }
251
252   random_test r{(uint_fast32_t)seed};
253   r.test(count);
254
255   printf("ok\n");
256   return 0;
257}
258