1 // Copyright 2020 Google LLC
2 // SPDX-License-Identifier: Apache-2.0
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
8 // http://www.apache.org/licenses/LICENSE-2.0
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
16 #include "hwy/aligned_allocator.h"
20 #include <stdlib.h> // malloc
27 #include "gtest/gtest.h"
31 // Sample object that keeps track on an external counter of how many times was
32 // the explicit constructor and destructor called.
36 SampleObject() { data_
[0] = 'a'; }
37 explicit SampleObject(int* counter
) : counter_(counter
) {
38 if (counter
) (*counter
)++;
43 if (counter_
) (*counter_
)--;
46 static_assert(N
> sizeof(int*), "SampleObject size too small.");
47 int* counter_
= nullptr;
48 char data_
[N
- sizeof(int*)];
53 // static AllocPtr and FreePtr member to be used with the aligned
54 // allocator. These functions calls the private non-static members.
55 static void* StaticAlloc(void* opaque
, size_t bytes
) {
56 return reinterpret_cast<FakeAllocator
*>(opaque
)->Alloc(bytes
);
58 static void StaticFree(void* opaque
, void* memory
) {
59 return reinterpret_cast<FakeAllocator
*>(opaque
)->Free(memory
);
62 // Returns the number of pending allocations to be freed.
63 size_t PendingAllocs() { return allocs_
.size(); }
66 void* Alloc(size_t bytes
) {
67 void* ret
= malloc(bytes
);
71 void Free(void* memory
) {
73 EXPECT_NE(allocs_
.end(), allocs_
.find(memory
));
74 allocs_
.erase(memory
);
78 std::set
<void*> allocs_
;
85 class AlignedAllocatorTest
: public testing::Test
{};
87 TEST(AlignedAllocatorTest
, FreeNullptr
) {
88 // Calling free with a nullptr is always ok.
89 FreeAlignedBytes(/*aligned_pointer=*/nullptr, /*free_ptr=*/nullptr,
90 /*opaque_ptr=*/nullptr);
93 TEST(AlignedAllocatorTest
, Log2
) {
94 EXPECT_EQ(0u, detail::ShiftCount(1));
95 EXPECT_EQ(1u, detail::ShiftCount(2));
96 EXPECT_EQ(3u, detail::ShiftCount(8));
99 // Allocator returns null when it detects overflow of items * sizeof(T).
100 TEST(AlignedAllocatorTest
, Overflow
) {
101 constexpr size_t max
= ~size_t(0);
102 constexpr size_t msb
= (max
>> 1) + 1;
103 using Size5
= std::array
<uint8_t, 5>;
104 using Size10
= std::array
<uint8_t, 10>;
106 detail::AllocateAlignedItems
<uint32_t>(max
/ 2, nullptr, nullptr));
108 detail::AllocateAlignedItems
<uint32_t>(max
/ 3, nullptr, nullptr));
110 detail::AllocateAlignedItems
<Size5
>(max
/ 4, nullptr, nullptr));
112 detail::AllocateAlignedItems
<uint16_t>(msb
, nullptr, nullptr));
114 detail::AllocateAlignedItems
<double>(msb
+ 1, nullptr, nullptr));
116 detail::AllocateAlignedItems
<Size10
>(msb
/ 4, nullptr, nullptr));
119 TEST(AlignedAllocatorTest
, AllocDefaultPointers
) {
120 const size_t kSize
= 7777;
121 void* ptr
= AllocateAlignedBytes(kSize
, /*alloc_ptr=*/nullptr,
122 /*opaque_ptr=*/nullptr);
123 ASSERT_NE(nullptr, ptr
);
124 // Make sure the pointer is actually aligned.
125 EXPECT_EQ(0U, reinterpret_cast<uintptr_t>(ptr
) % HWY_ALIGNMENT
);
126 char* p
= static_cast<char*>(ptr
);
128 for (size_t i
= 0; i
< kSize
; i
++) {
129 // Performs a computation using p[] to prevent it being optimized away.
130 p
[i
] = static_cast<char>(i
& 0x7F);
131 if (i
) ret
+= static_cast<size_t>(p
[i
] * p
[i
- 1]);
134 FreeAlignedBytes(ptr
, /*free_ptr=*/nullptr, /*opaque_ptr=*/nullptr);
137 TEST(AlignedAllocatorTest
, EmptyAlignedUniquePtr
) {
138 AlignedUniquePtr
<SampleObject
<32>> ptr(nullptr, AlignedDeleter());
139 AlignedUniquePtr
<SampleObject
<32>[]> arr(nullptr, AlignedDeleter());
142 TEST(AlignedAllocatorTest
, EmptyAlignedFreeUniquePtr
) {
143 AlignedFreeUniquePtr
<SampleObject
<32>> ptr(nullptr, AlignedFreer());
144 AlignedFreeUniquePtr
<SampleObject
<32>[]> arr(nullptr, AlignedFreer());
147 TEST(AlignedAllocatorTest
, CustomAlloc
) {
148 FakeAllocator fake_alloc
;
150 const size_t kSize
= 7777;
152 AllocateAlignedBytes(kSize
, &FakeAllocator::StaticAlloc
, &fake_alloc
);
153 ASSERT_NE(nullptr, ptr
);
154 // We should have only requested one alloc from the allocator.
155 EXPECT_EQ(1U, fake_alloc
.PendingAllocs());
156 // Make sure the pointer is actually aligned.
157 EXPECT_EQ(0U, reinterpret_cast<uintptr_t>(ptr
) % HWY_ALIGNMENT
);
158 FreeAlignedBytes(ptr
, &FakeAllocator::StaticFree
, &fake_alloc
);
159 EXPECT_EQ(0U, fake_alloc
.PendingAllocs());
162 TEST(AlignedAllocatorTest
, MakeUniqueAlignedDefaultConstructor
) {
164 auto ptr
= MakeUniqueAligned
<SampleObject
<24>>();
165 // Default constructor sets the data_[0] to 'a'.
166 EXPECT_EQ('a', ptr
->data_
[0]);
167 EXPECT_EQ(nullptr, ptr
->counter_
);
171 TEST(AlignedAllocatorTest
, MakeUniqueAligned
) {
174 // Creates the object, initializes it with the explicit constructor and
175 // returns an unique_ptr to it.
176 auto ptr
= MakeUniqueAligned
<SampleObject
<24>>(&counter
);
177 EXPECT_EQ(1, counter
);
178 // Custom constructor sets the data_[0] to 'b'.
179 EXPECT_EQ('b', ptr
->data_
[0]);
181 EXPECT_EQ(0, counter
);
184 TEST(AlignedAllocatorTest
, MakeUniqueAlignedArray
) {
187 // Creates the array of objects and initializes them with the explicit
189 auto arr
= MakeUniqueAlignedArray
<SampleObject
<24>>(7, &counter
);
190 EXPECT_EQ(7, counter
);
191 for (size_t i
= 0; i
< 7; i
++) {
192 // Custom constructor sets the data_[0] to 'b'.
193 EXPECT_EQ('b', arr
[i
].data_
[0]) << "Where i = " << i
;
196 EXPECT_EQ(0, counter
);
199 TEST(AlignedAllocatorTest
, AllocSingleInt
) {
200 auto ptr
= AllocateAligned
<uint32_t>(1);
201 ASSERT_NE(nullptr, ptr
.get());
202 EXPECT_EQ(0U, reinterpret_cast<uintptr_t>(ptr
.get()) % HWY_ALIGNMENT
);
203 // Force delete of the unique_ptr now to check that it doesn't crash.
205 EXPECT_EQ(nullptr, ptr
.get());
208 TEST(AlignedAllocatorTest
, AllocMultipleInt
) {
209 const size_t kSize
= 7777;
210 auto ptr
= AllocateAligned
<uint32_t>(kSize
);
211 ASSERT_NE(nullptr, ptr
.get());
212 EXPECT_EQ(0U, reinterpret_cast<uintptr_t>(ptr
.get()) % HWY_ALIGNMENT
);
213 // ptr[i] is actually (*ptr.get())[i] which will use the operator[] of the
214 // underlying type chosen by AllocateAligned() for the std::unique_ptr.
215 EXPECT_EQ(&(ptr
[0]) + 1, &(ptr
[1]));
218 for (size_t i
= 0; i
< kSize
; i
++) {
219 // Performs a computation using ptr[] to prevent it being optimized away.
220 ptr
[i
] = static_cast<uint32_t>(i
);
221 if (i
) ret
+= ptr
[i
] * ptr
[i
- 1];
226 TEST(AlignedAllocatorTest
, AllocateAlignedObjectWithoutDestructor
) {
229 // This doesn't call the constructor.
230 auto obj
= AllocateAligned
<SampleObject
<24>>(1);
231 obj
[0].counter_
= &counter
;
233 // Destroying the unique_ptr shouldn't have called the destructor of the
235 EXPECT_EQ(0, counter
);
238 TEST(AlignedAllocatorTest
, MakeUniqueAlignedArrayWithCustomAlloc
) {
239 FakeAllocator fake_alloc
;
242 // Creates the array of objects and initializes them with the explicit
244 auto arr
= MakeUniqueAlignedArrayWithAlloc
<SampleObject
<24>>(
245 7, FakeAllocator::StaticAlloc
, FakeAllocator::StaticFree
, &fake_alloc
,
247 ASSERT_NE(nullptr, arr
.get());
248 // An array should still only call a single allocation.
249 EXPECT_EQ(1u, fake_alloc
.PendingAllocs());
250 EXPECT_EQ(7, counter
);
251 for (size_t i
= 0; i
< 7; i
++) {
252 // Custom constructor sets the data_[0] to 'b'.
253 EXPECT_EQ('b', arr
[i
].data_
[0]) << "Where i = " << i
;
256 EXPECT_EQ(0, counter
);
257 EXPECT_EQ(0u, fake_alloc
.PendingAllocs());
260 TEST(AlignedAllocatorTest
, DefaultInit
) {
261 // The test is whether this compiles. Default-init is useful for output params
262 // and per-thread storage.
263 std::vector
<AlignedUniquePtr
<int[]>> ptrs
;
264 std::vector
<AlignedFreeUniquePtr
<double[]>> free_ptrs
;
266 free_ptrs
.resize(128);
267 // The following is to prevent elision of the pointers.
268 std::mt19937
rng(129); // Emscripten lacks random_device.
269 std::uniform_int_distribution
<size_t> dist(0, 127);
270 ptrs
[dist(rng
)] = MakeUniqueAlignedArray
<int>(123);
271 free_ptrs
[dist(rng
)] = AllocateAligned
<double>(456);
272 // "Use" pointer without resorting to printf. 0 == 0. Can't shift by 64.
273 const auto addr1
= reinterpret_cast<uintptr_t>(ptrs
[dist(rng
)].get());
274 const auto addr2
= reinterpret_cast<uintptr_t>(free_ptrs
[dist(rng
)].get());
275 constexpr size_t kBits
= sizeof(uintptr_t) * 8;
276 EXPECT_EQ((addr1
>> (kBits
- 1)) >> (kBits
- 1),
277 (addr2
>> (kBits
- 1)) >> (kBits
- 1));