Files
filament/libs/utils/test/test_Allocators.cpp
2018-08-03 10:38:22 -07:00

322 lines
8.9 KiB
C++

/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <vector>
#include <algorithm>
#include <functional>
#include <bitset>
#include <gtest/gtest.h>
#include <utils/Allocator.h>
using namespace utils;
TEST(AllocatorTest, LinearAllocator) {
char scratch[1024];
void* p = nullptr;
void* q = nullptr;
LinearAllocator la(scratch, scratch+sizeof(scratch));
p = la.alloc(1024, 1, 0);
// check we can allocate the whole block
EXPECT_EQ(scratch, p);
// check we can free everything and reallocate the whole block
la.reset();
p = la.alloc(1024, 1, 0);
EXPECT_EQ(scratch, p);
// check we can rewind
la.rewind(scratch + 512);
p = la.alloc(512, 1, 0);
EXPECT_EQ(scratch + 512, p);
// check we can't allocate more than the area size
la.reset();
p = la.alloc(1025, 1, 0);
EXPECT_EQ(nullptr, p);
// check that after failure, we can allocate to the area size
p = la.alloc(1024, 1, 0);
EXPECT_EQ(scratch, p);
// check small allocations
la.reset();
p = la.alloc(1, 1, 0);
EXPECT_EQ(scratch, p);
p = la.alloc(7, 1, 0);
EXPECT_EQ(scratch+1, p);
p = la.alloc(8, 1, 0);
EXPECT_EQ(scratch+8, p);
// check alignment
p = la.alloc(1, 1, 0);
p = la.alloc(24, 32, 0);
EXPECT_NE(nullptr, p);
EXPECT_EQ(0, uintptr_t(p) & 31);
// now check that next allocation doesn't overlap previous one
q = la.alloc(1, 1, 0);
EXPECT_EQ(uintptr_t(q), uintptr_t(p) + 24);
// check alignment + offset
p = la.alloc(3, 1, 0);
p = la.alloc(sizeof(float)*4, 32, 4);
EXPECT_EQ(0, uintptr_t(p) & 31);
// now check that next allocation doesn't overlap previous one
q = la.alloc(1, 1, 0);
EXPECT_EQ(uintptr_t(q), uintptr_t(p) + sizeof(float)*4);
}
TEST(AllocatorTest, PoolAllocator) {
char scratch[1024 + 31];
void* p = nullptr;
void* q = nullptr;
std::bitset<16> used;
// verify buffers have not been clobbered
auto check = [](char const* p, int v, size_t s)->bool {
for (size_t i = 0; i<s ; ++i) {
if (p[i] != v) {
return false;
}
}
return true;
};
// pool of 64-bytes objects aligned on 32 bytes
PoolAllocator<64, 32> pa(scratch, scratch+sizeof(scratch));
void* const b = pointermath::align(scratch, 32, 0);
// repeat the test multiple times
for (size_t k=0 ; k<16 ; k++) {
// make sure we can allocate exactly 16 of those objects
for (size_t i = 0; i < 16; i++) {
p = pa.alloc();
EXPECT_NE(nullptr, p);
EXPECT_EQ(0, uintptr_t(p) & 31);
size_t j = (uintptr_t(p) - uintptr_t(b)) / 64;
//printf("%3d", j);
memset(p, int(j + 1), 64);
}
//printf("\n");
// check an extra one fails
q = pa.alloc();
EXPECT_EQ(nullptr, q);
// check that buffers where not clobbered
q = b;
for (size_t i = 0; i < 16; i++) {
EXPECT_TRUE(check((char const*)q, int(i + 1), 64));
q = pointermath::add(q, 64);
}
// now free all our buffers
used.set();
q = b;
for (size_t i = 0; i < 16; i++) {
// use gray-coding so we don't free exactly linearly
size_t j = ((i^k) >> 1) ^ (i^k);
p = pointermath::add(q, j * 64);
pa.free(p);
used[j] = false;
if (j > 0 && used[j - 1]) {
// check that the previous buffer didn't get clobbered
EXPECT_TRUE(check((char const*) pointermath::add(p, -64), int(j - 1 + 1), 64));
}
if (j < 15 && used[j + 1]) {
// check that the following buffer didn't get clobbered
EXPECT_TRUE(check((char const*) pointermath::add(p, +64), int(j + 1 + 1), 64));
}
}
EXPECT_FALSE(used.any());
}
}
TEST(AllocatorTest, CppAllocator) {
struct Tracking {
Tracking() noexcept { }
Tracking(const char* name, size_t size) noexcept { }
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) {
allocations.push_back(p);
}
void onFree(void* p) {
auto pos = std::find(allocations.begin(), allocations.end(), p);
EXPECT_TRUE(pos != allocations.end());
}
std::vector<void*> allocations;
};
using CppArena = Arena<PoolAllocator<8, 8, sizeof(void*)>, LockingPolicy::NoLock, Tracking>;
static int count = 0;
struct Foo {
~Foo() {
++count;
}
struct Tag {
CppArena* arena;
};
static void* operator new(size_t size, CppArena& arena) {
void* p = arena.alloc(size, alignof(Foo), sizeof(Tag));
Tag* tag = static_cast<Tag*>(p) - 1;
tag->arena = &arena;
return p;
}
static void operator delete(void* p, size_t s) {
// don't do anything
Tag* tag = static_cast<Tag*>(p) - 1;
tag->arena->free(p);
}
char dummy[8];
};
CppArena arena("CppArena", 1024);
// check we can override operator new and use one of our allocator
Foo* p0 = new(arena) Foo;
EXPECT_NE(nullptr, p0);
Foo* p1 = new(arena) Foo;
EXPECT_NE(nullptr, p1);
EXPECT_EQ(0, count);
delete p0;
EXPECT_EQ(1, count);
delete p1;
EXPECT_EQ(2, count);
}
TEST(AllocatorTest, ScopedStackArena) {
void* p = nullptr;
struct Foo {
Foo(std::function<void(void)> f) : dtor(f) { }
~Foo() { dtor(); }
private:
std::function<void(void)> dtor;
};
struct Pod {
int a;
float b;
};
struct PodWithDtor {
int a;
float b;
~PodWithDtor() { };
};
int dtorCalled = 0;
using Allocator = Arena<LinearAllocator, LockingPolicy::NoLock>;
Allocator allocator("ArenaScope", 1024);
{
ArenaScope<Allocator> ssa(allocator);
Foo* f0 = ssa.make<Foo>([&dtorCalled](){ dtorCalled++; });
EXPECT_NE(nullptr, f0);
Foo* f1 = ssa.make<Foo>([&dtorCalled](){ dtorCalled++; });
EXPECT_NE(nullptr, f1);
Foo* f2 = ssa.make<Foo>([&dtorCalled](){ dtorCalled++; });
EXPECT_NE(nullptr, f2);
EXPECT_EQ(0, dtorCalled);
}
allocator.getAllocator().reset();
// check dtors have been called
EXPECT_EQ(3, dtorCalled);
{
ArenaScope<Allocator> ssa(allocator);
// check that we can allocate everything at this point
p = ssa.allocate(1024);
EXPECT_NE(nullptr, p);
}
allocator.getAllocator().reset();
{
ArenaScope<Allocator> ssa(allocator);
// check that we fail allocating too much
p = ssa.allocate(1025);
EXPECT_EQ(nullptr, p);
}
allocator.getAllocator().reset();
{
ArenaScope<Allocator> ssa(allocator);
Pod* p0 = ssa.make<Pod>();
Pod* p1 = ssa.make<Pod>();
EXPECT_EQ(sizeof(Pod), uintptr_t(p1) - uintptr_t(p0));
PodWithDtor* pd0 = ssa.make<PodWithDtor>();
PodWithDtor* pd1 = ssa.make<PodWithDtor>();
EXPECT_NE(sizeof(PodWithDtor), uintptr_t(pd1) - uintptr_t(pd0));
}
allocator.getAllocator().reset();
}
TEST(AllocatorTest, STLAllocator) {
struct Tracking {
Tracking() noexcept { }
Tracking(const char* name, size_t size) noexcept { }
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) {
allocations.push_back(p);
}
void onFree(void* p) {
auto pos = std::find(allocations.begin(), allocations.end(), p);
EXPECT_TRUE(pos != allocations.end());
allocations.erase(pos);
}
std::vector<void*> allocations;
};
using Arena = Arena<LinearAllocator, LockingPolicy::NoLock, Tracking>;
Arena arena("arena", 1204);
STLAllocator<int, Arena> allocator(arena);
{
#if !defined(WIN32)
std::vector<int, STLAllocator<int, Arena>> vector(allocator);
vector.push_back(1);
EXPECT_GT(arena.getListener().allocations.size(), 0);
vector.push_back(2);
vector.push_back(3);
vector.push_back(4);
vector.clear();
#else
// Disabled under windows due to incompatibility between clang and Microsoft STL.
# warning "Custom Allocator Test Disabled."
#endif
}
EXPECT_EQ(0, arena.getListener().allocations.size());
}