1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "hwy/aligned_allocator.h"
16 
17 #include <stddef.h>
18 
19 #include <array>
20 #include <new>
21 #include <random>
22 #include <vector>
23 
24 #include "gtest/gtest.h"
25 #include "hwy/base.h"
26 
27 namespace {
28 
29 // Sample object that keeps track on an external counter of how many times was
30 // the explicit constructor and destructor called.
31 template <size_t N>
32 class SampleObject {
33  public:
SampleObject()34   SampleObject() { data_[0] = 'a'; }
SampleObject(int * counter)35   explicit SampleObject(int* counter) : counter_(counter) {
36     if (counter) (*counter)++;
37     data_[0] = 'b';
38   }
39 
~SampleObject()40   ~SampleObject() {
41     if (counter_) (*counter_)--;
42   }
43 
44   static_assert(N > sizeof(int*), "SampleObject size too small.");
45   int* counter_ = nullptr;
46   char data_[N - sizeof(int*)];
47 };
48 
49 class FakeAllocator {
50  public:
51   // static AllocPtr and FreePtr member to be used with the alligned
52   // allocator. These functions calls the private non-static members.
StaticAlloc(void * opaque,size_t bytes)53   static void* StaticAlloc(void* opaque, size_t bytes) {
54     return reinterpret_cast<FakeAllocator*>(opaque)->Alloc(bytes);
55   }
StaticFree(void * opaque,void * memory)56   static void StaticFree(void* opaque, void* memory) {
57     return reinterpret_cast<FakeAllocator*>(opaque)->Free(memory);
58   }
59 
60   // Returns the number of pending allocations to be freed.
PendingAllocs()61   size_t PendingAllocs() { return allocs_.size(); }
62 
63  private:
Alloc(size_t bytes)64   void* Alloc(size_t bytes) {
65     void* ret = malloc(bytes);
66     allocs_.insert(ret);
67     return ret;
68   }
Free(void * memory)69   void Free(void* memory) {
70     if (!memory) return;
71     EXPECT_NE(allocs_.end(), allocs_.find(memory));
72     free(memory);
73     allocs_.erase(memory);
74   }
75 
76   std::set<void*> allocs_;
77 };
78 
79 }  // namespace
80 
81 namespace hwy {
82 
83 class AlignedAllocatorTest : public testing::Test {};
84 
TEST(AlignedAllocatorTest,FreeNullptr)85 TEST(AlignedAllocatorTest, FreeNullptr) {
86   // Calling free with a nullptr is always ok.
87   FreeAlignedBytes(/*aligned_pointer=*/nullptr, /*free_ptr=*/nullptr,
88                    /*opaque_ptr=*/nullptr);
89 }
90 
TEST(AlignedAllocatorTest,Log2)91 TEST(AlignedAllocatorTest, Log2) {
92   EXPECT_EQ(0u, detail::ShiftCount(1));
93   EXPECT_EQ(1u, detail::ShiftCount(2));
94   EXPECT_EQ(3u, detail::ShiftCount(8));
95 }
96 
97 // Allocator returns null when it detects overflow of items * sizeof(T).
TEST(AlignedAllocatorTest,Overflow)98 TEST(AlignedAllocatorTest, Overflow) {
99   constexpr size_t max = ~size_t(0);
100   constexpr size_t msb = (max >> 1) + 1;
101   using Size5 = std::array<uint8_t, 5>;
102   using Size10 = std::array<uint8_t, 10>;
103   EXPECT_EQ(nullptr,
104             detail::AllocateAlignedItems<uint32_t>(max / 2, nullptr, nullptr));
105   EXPECT_EQ(nullptr,
106             detail::AllocateAlignedItems<uint32_t>(max / 3, nullptr, nullptr));
107   EXPECT_EQ(nullptr,
108             detail::AllocateAlignedItems<Size5>(max / 4, nullptr, nullptr));
109   EXPECT_EQ(nullptr,
110             detail::AllocateAlignedItems<uint16_t>(msb, nullptr, nullptr));
111   EXPECT_EQ(nullptr,
112             detail::AllocateAlignedItems<double>(msb + 1, nullptr, nullptr));
113   EXPECT_EQ(nullptr,
114             detail::AllocateAlignedItems<Size10>(msb / 4, nullptr, nullptr));
115 }
116 
TEST(AlignedAllocatorTest,AllocDefaultPointers)117 TEST(AlignedAllocatorTest, AllocDefaultPointers) {
118   const size_t kSize = 7777;
119   void* ptr = AllocateAlignedBytes(kSize, /*alloc_ptr=*/nullptr,
120                                    /*opaque_ptr=*/nullptr);
121   ASSERT_NE(nullptr, ptr);
122   // Make sure the pointer is actually aligned.
123   EXPECT_EQ(0U, reinterpret_cast<uintptr_t>(ptr) % HWY_ALIGNMENT);
124   char* p = static_cast<char*>(ptr);
125   size_t ret = 0;
126   for (size_t i = 0; i < kSize; i++) {
127     // Performs a computation using p[] to prevent it being optimized away.
128     p[i] = static_cast<char>(i & 0x7F);
129     if (i) ret += static_cast<size_t>(p[i] * p[i - 1]);
130   }
131   EXPECT_NE(0U, ret);
132   FreeAlignedBytes(ptr, /*free_ptr=*/nullptr, /*opaque_ptr=*/nullptr);
133 }
134 
TEST(AlignedAllocatorTest,EmptyAlignedUniquePtr)135 TEST(AlignedAllocatorTest, EmptyAlignedUniquePtr) {
136   AlignedUniquePtr<SampleObject<32>> ptr(nullptr, AlignedDeleter());
137   AlignedUniquePtr<SampleObject<32>[]> arr(nullptr, AlignedDeleter());
138 }
139 
TEST(AlignedAllocatorTest,EmptyAlignedFreeUniquePtr)140 TEST(AlignedAllocatorTest, EmptyAlignedFreeUniquePtr) {
141   AlignedFreeUniquePtr<SampleObject<32>> ptr(nullptr, AlignedFreer());
142   AlignedFreeUniquePtr<SampleObject<32>[]> arr(nullptr, AlignedFreer());
143 }
144 
TEST(AlignedAllocatorTest,CustomAlloc)145 TEST(AlignedAllocatorTest, CustomAlloc) {
146   FakeAllocator fake_alloc;
147 
148   const size_t kSize = 7777;
149   void* ptr =
150       AllocateAlignedBytes(kSize, &FakeAllocator::StaticAlloc, &fake_alloc);
151   ASSERT_NE(nullptr, ptr);
152   // We should have only requested one alloc from the allocator.
153   EXPECT_EQ(1U, fake_alloc.PendingAllocs());
154   // Make sure the pointer is actually aligned.
155   EXPECT_EQ(0U, reinterpret_cast<uintptr_t>(ptr) % HWY_ALIGNMENT);
156   FreeAlignedBytes(ptr, &FakeAllocator::StaticFree, &fake_alloc);
157   EXPECT_EQ(0U, fake_alloc.PendingAllocs());
158 }
159 
TEST(AlignedAllocatorTest,MakeUniqueAlignedDefaultConstructor)160 TEST(AlignedAllocatorTest, MakeUniqueAlignedDefaultConstructor) {
161   {
162     auto ptr = MakeUniqueAligned<SampleObject<24>>();
163     // Default constructor sets the data_[0] to 'a'.
164     EXPECT_EQ('a', ptr->data_[0]);
165     EXPECT_EQ(nullptr, ptr->counter_);
166   }
167 }
168 
TEST(AlignedAllocatorTest,MakeUniqueAligned)169 TEST(AlignedAllocatorTest, MakeUniqueAligned) {
170   int counter = 0;
171   {
172     // Creates the object, initializes it with the explicit constructor and
173     // returns an unique_ptr to it.
174     auto ptr = MakeUniqueAligned<SampleObject<24>>(&counter);
175     EXPECT_EQ(1, counter);
176     // Custom constructor sets the data_[0] to 'b'.
177     EXPECT_EQ('b', ptr->data_[0]);
178   }
179   EXPECT_EQ(0, counter);
180 }
181 
TEST(AlignedAllocatorTest,MakeUniqueAlignedArray)182 TEST(AlignedAllocatorTest, MakeUniqueAlignedArray) {
183   int counter = 0;
184   {
185     // Creates the array of objects and initializes them with the explicit
186     // constructor.
187     auto arr = MakeUniqueAlignedArray<SampleObject<24>>(7, &counter);
188     EXPECT_EQ(7, counter);
189     for (size_t i = 0; i < 7; i++) {
190       // Custom constructor sets the data_[0] to 'b'.
191       EXPECT_EQ('b', arr[i].data_[0]) << "Where i = " << i;
192     }
193   }
194   EXPECT_EQ(0, counter);
195 }
196 
TEST(AlignedAllocatorTest,AllocSingleInt)197 TEST(AlignedAllocatorTest, AllocSingleInt) {
198   auto ptr = AllocateAligned<uint32_t>(1);
199   ASSERT_NE(nullptr, ptr.get());
200   EXPECT_EQ(0U, reinterpret_cast<uintptr_t>(ptr.get()) % HWY_ALIGNMENT);
201   // Force delete of the unique_ptr now to check that it doesn't crash.
202   ptr.reset(nullptr);
203   EXPECT_EQ(nullptr, ptr.get());
204 }
205 
TEST(AlignedAllocatorTest,AllocMultipleInt)206 TEST(AlignedAllocatorTest, AllocMultipleInt) {
207   const size_t kSize = 7777;
208   auto ptr = AllocateAligned<uint32_t>(kSize);
209   ASSERT_NE(nullptr, ptr.get());
210   EXPECT_EQ(0U, reinterpret_cast<uintptr_t>(ptr.get()) % HWY_ALIGNMENT);
211   // ptr[i] is actually (*ptr.get())[i] which will use the operator[] of the
212   // underlying type chosen by AllocateAligned() for the std::unique_ptr.
213   EXPECT_EQ(&(ptr[0]) + 1, &(ptr[1]));
214 
215   size_t ret = 0;
216   for (size_t i = 0; i < kSize; i++) {
217     // Performs a computation using ptr[] to prevent it being optimized away.
218     ptr[i] = static_cast<uint32_t>(i);
219     if (i) ret += ptr[i] * ptr[i - 1];
220   }
221   EXPECT_NE(0U, ret);
222 }
223 
TEST(AlignedAllocatorTest,AllocateAlignedObjectWithoutDestructor)224 TEST(AlignedAllocatorTest, AllocateAlignedObjectWithoutDestructor) {
225   int counter = 0;
226   {
227     // This doesn't call the constructor.
228     auto obj = AllocateAligned<SampleObject<24>>(1);
229     obj[0].counter_ = &counter;
230   }
231   // Destroying the unique_ptr shouldn't have called the destructor of the
232   // SampleObject<24>.
233   EXPECT_EQ(0, counter);
234 }
235 
TEST(AlignedAllocatorTest,MakeUniqueAlignedArrayWithCustomAlloc)236 TEST(AlignedAllocatorTest, MakeUniqueAlignedArrayWithCustomAlloc) {
237   FakeAllocator fake_alloc;
238   int counter = 0;
239   {
240     // Creates the array of objects and initializes them with the explicit
241     // constructor.
242     auto arr = MakeUniqueAlignedArrayWithAlloc<SampleObject<24>>(
243         7, FakeAllocator::StaticAlloc, FakeAllocator::StaticFree, &fake_alloc,
244         &counter);
245     ASSERT_NE(nullptr, arr.get());
246     // An array should still only call a single allocation.
247     EXPECT_EQ(1u, fake_alloc.PendingAllocs());
248     EXPECT_EQ(7, counter);
249     for (size_t i = 0; i < 7; i++) {
250       // Custom constructor sets the data_[0] to 'b'.
251       EXPECT_EQ('b', arr[i].data_[0]) << "Where i = " << i;
252     }
253   }
254   EXPECT_EQ(0, counter);
255   EXPECT_EQ(0u, fake_alloc.PendingAllocs());
256 }
257 
TEST(AlignedAllocatorTest,DefaultInit)258 TEST(AlignedAllocatorTest, DefaultInit) {
259   // The test is whether this compiles. Default-init is useful for output params
260   // and per-thread storage.
261   std::vector<AlignedUniquePtr<int[]>> ptrs;
262   std::vector<AlignedFreeUniquePtr<double[]>> free_ptrs;
263   ptrs.resize(128);
264   free_ptrs.resize(128);
265   // The following is to prevent elision of the pointers.
266   std::mt19937 rng(129);  // Emscripten lacks random_device.
267   std::uniform_int_distribution<size_t> dist(0, 127);
268   ptrs[dist(rng)] = MakeUniqueAlignedArray<int>(123);
269   free_ptrs[dist(rng)] = AllocateAligned<double>(456);
270   // "Use" pointer without resorting to printf. 0 == 0. Can't shift by 64.
271   const auto addr1 = reinterpret_cast<uintptr_t>(ptrs[dist(rng)].get());
272   const auto addr2 = reinterpret_cast<uintptr_t>(free_ptrs[dist(rng)].get());
273   constexpr size_t kBits = sizeof(uintptr_t) * 8;
274   EXPECT_EQ((addr1 >> (kBits - 1)) >> (kBits - 1),
275             (addr2 >> (kBits - 1)) >> (kBits - 1));
276 }
277 
278 }  // namespace hwy
279 
280 // Ought not to be necessary, but without this, no tests run on RVV.
main(int argc,char ** argv)281 int main(int argc, char** argv) {
282   ::testing::InitGoogleTest(&argc, argv);
283   return RUN_ALL_TESTS();
284 }
285