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