Skip to content

Commit

Permalink
pw_allocator: Add FallbackAllocator
Browse files Browse the repository at this point in the history
This CL adds FallbackAllocator. For each allocation request, it will
first try to allocate memory using its primary allocator, then its
secondary allocator.

Change-Id: Ic85290c696ca0b7287135bd35f676d9ab63e0f1e
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/171837
Reviewed-by: Taylor Cramer <[email protected]>
Commit-Queue: Aaron Green <[email protected]>
  • Loading branch information
nopsledder authored and CQ Bot Account committed Sep 24, 2023
1 parent 694439e commit 4ef538b
Show file tree
Hide file tree
Showing 12 changed files with 546 additions and 54 deletions.
30 changes: 30 additions & 0 deletions pw_allocator/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ pw_cc_library(
],
)

pw_cc_library(
name = "fallback_allocator",
srcs = [
"fallback_allocator.cc",
],
hdrs = [
"public/pw_allocator/fallback_allocator.h",
],
includes = ["public"],
deps = [
":allocator",
"//pw_assert",
"//pw_status",
],
)

pw_cc_library(
name = "freelist",
srcs = [
Expand Down Expand Up @@ -97,6 +113,7 @@ pw_cc_library(
deps = [
":allocator",
":block",
"//pw_assert",
"//pw_bytes",
],
)
Expand Down Expand Up @@ -126,6 +143,19 @@ pw_cc_test(
],
)

pw_cc_test(
name = "fallback_allocator_test",
srcs = [
"fallback_allocator_test.cc",
],
deps = [
":allocator_testing",
":fallback_allocator",
"//pw_status",
"//pw_unit_test",
],
)

pw_cc_test(
name = "freelist_test",
srcs = [
Expand Down
25 changes: 23 additions & 2 deletions pw_allocator/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ group("pw_allocator") {
pw_source_set("allocator") {
public_configs = [ ":default_config" ]
public = [ "public/pw_allocator/allocator.h" ]
public_deps = [ dir_pw_result ]
public_deps = [ dir_pw_status ]
deps = [
dir_pw_assert,
dir_pw_bytes,
dir_pw_status,
]
sources = [ "allocator.cc" ]
}
Expand All @@ -65,6 +64,17 @@ pw_source_set("block") {
sources = [ "block.cc" ]
}

pw_source_set("fallback_allocator") {
public_configs = [ ":default_config" ]
public = [ "public/pw_allocator/fallback_allocator.h" ]
public_deps = [
":allocator",
dir_pw_assert,
dir_pw_status,
]
sources = [ "fallback_allocator.cc" ]
}

pw_source_set("freelist") {
public_configs = [ ":default_config" ]
configs = [ ":enable_heap_poison" ]
Expand Down Expand Up @@ -97,6 +107,7 @@ pw_test_group("tests") {
tests = [
":allocator_test",
":block_test",
":fallback_allocator_test",
":freelist_test",
":freelist_heap_test",
]
Expand All @@ -107,6 +118,7 @@ pw_source_set("allocator_testing") {
public_deps = [
":allocator",
":block",
dir_pw_assert,
dir_pw_bytes,
]
sources = [ "allocator_testing.cc" ]
Expand All @@ -130,6 +142,15 @@ pw_test("block_test") {
sources = [ "block_test.cc" ]
}

pw_test("fallback_allocator_test") {
deps = [
":allocator_testing",
":fallback_allocator",
dir_pw_status,
]
sources = [ "fallback_allocator_test.cc" ]
}

pw_test("freelist_test") {
configs = [ ":enable_heap_poison" ]
deps = [
Expand Down
26 changes: 26 additions & 0 deletions pw_allocator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ pw_add_library(pw_allocator.block STATIC
block.cc
)

pw_add_library(pw_allocator.fallback_allocator STATIC
HEADERS
public/pw_allocator/fallback_allocator.h
PUBLIC_INCLUDES
public
PUBLIC_DEPS
pw_allocator.allocator
pw_assert
pw_status
SOURCES
fallback_allocator.cc
)

pw_add_library(pw_allocator.freelist STATIC
HEADERS
public/pw_allocator/freelist.h
Expand Down Expand Up @@ -75,6 +88,7 @@ pw_add_library(pw_allocator.allocator_testing STATIC
PUBLIC_DEPS
pw_allocator.allocator
pw_allocator.block
pw_assert
pw_bytes
SOURCES
allocator_testing.cc
Expand Down Expand Up @@ -103,6 +117,18 @@ pw_add_test(pw_allocator.block_test
pw_allocator
)

pw_add_test(pw_allocator.fallback_allocator_test
PRIVATE_DEPS
pw_allocator.allocator_testing
pw_allocator.fallback_allocator
pw_status
SOURCES
fallback_allocator_test.cc
GROUPS
modules
pw_allocator
)

pw_add_test(pw_allocator.freelist_test
SOURCES
freelist_test.cc
Expand Down
3 changes: 3 additions & 0 deletions pw_allocator/allocator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ void* Allocator::DoReallocate(void* ptr,
size_t old_size,
size_t old_alignment,
size_t new_size) {
if (new_size == 0) {
return nullptr;
}
if (DoResize(ptr, old_size, old_alignment, new_size)) {
return ptr;
}
Expand Down
42 changes: 17 additions & 25 deletions pw_allocator/allocator_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,22 @@
namespace pw::allocator {
namespace {

using test::FakeAllocator;

// Test fixtures.

struct AllocatorTest : ::testing::Test {
FakeAllocator allocator;
std::array<std::byte, 256> buffer;
private:
std::array<std::byte, 256> buffer = {};

void SetUp() override { EXPECT_EQ(allocator.Initialize(buffer), OkStatus()); }
};
public:
test::FakeAllocator allocator;

struct TestStruct {
uint32_t a;
uint32_t b;
uint32_t c;
void SetUp() override { EXPECT_EQ(allocator.Initialize(buffer), OkStatus()); }
};

// Unit tests

TEST_F(AllocatorTest, ReallocateNull) {
Layout old_layout = Layout::Of<uint32_t>();
constexpr Layout old_layout = Layout::Of<uint32_t>();
size_t new_size = old_layout.size();
void* new_ptr = allocator.Reallocate(nullptr, old_layout, new_size);

Expand All @@ -64,23 +59,20 @@ TEST_F(AllocatorTest, ReallocateNull) {
}

TEST_F(AllocatorTest, ReallocateZeroNewSize) {
Layout old_layout = Layout::Of<TestStruct>();
constexpr Layout old_layout = Layout::Of<uint32_t[3]>();
void* ptr = allocator.Allocate(old_layout);
ASSERT_EQ(allocator.allocate_size(), old_layout.size());
ASSERT_NE(ptr, nullptr);
allocator.ResetParameters();

size_t new_size = 0;
void* new_ptr = allocator.Reallocate(ptr, old_layout, new_size);

// Reallocate should call Resize.
EXPECT_EQ(allocator.resize_ptr(), ptr);
EXPECT_EQ(allocator.resize_old_size(), old_layout.size());
EXPECT_EQ(allocator.resize_new_size(), new_size);

// Resize should fail and Reallocate should call Allocate.
EXPECT_EQ(allocator.allocate_size(), new_size);

// Deallocate should not be called.
// Reallocate does not call Resize, Allocate, or Deallocate.
EXPECT_EQ(allocator.resize_ptr(), nullptr);
EXPECT_EQ(allocator.resize_old_size(), 0U);
EXPECT_EQ(allocator.resize_new_size(), 0U);
EXPECT_EQ(allocator.allocate_size(), 0U);
EXPECT_EQ(allocator.deallocate_ptr(), nullptr);
EXPECT_EQ(allocator.deallocate_size(), 0U);

Expand All @@ -89,7 +81,7 @@ TEST_F(AllocatorTest, ReallocateZeroNewSize) {
}

TEST_F(AllocatorTest, ReallocateSame) {
Layout layout = Layout::Of<TestStruct>();
constexpr Layout layout = Layout::Of<uint32_t[3]>();
void* ptr = allocator.Allocate(layout);
ASSERT_EQ(allocator.allocate_size(), layout.size());
ASSERT_NE(ptr, nullptr);
Expand All @@ -114,7 +106,7 @@ TEST_F(AllocatorTest, ReallocateSame) {
}

TEST_F(AllocatorTest, ReallocateSmaller) {
Layout old_layout = Layout::Of<TestStruct>();
constexpr Layout old_layout = Layout::Of<uint32_t[3]>();
void* ptr = allocator.Allocate(old_layout);
ASSERT_EQ(allocator.allocate_size(), old_layout.size());
ASSERT_NE(ptr, nullptr);
Expand All @@ -140,7 +132,7 @@ TEST_F(AllocatorTest, ReallocateSmaller) {
}

TEST_F(AllocatorTest, ReallocateLarger) {
Layout old_layout = Layout::Of<uint32_t>();
constexpr Layout old_layout = Layout::Of<uint32_t>();
void* ptr = allocator.Allocate(old_layout);
ASSERT_EQ(allocator.allocate_size(), old_layout.size());
ASSERT_NE(ptr, nullptr);
Expand All @@ -154,7 +146,7 @@ TEST_F(AllocatorTest, ReallocateLarger) {
ASSERT_NE(next, nullptr);
allocator.ResetParameters();

size_t new_size = sizeof(TestStruct);
size_t new_size = sizeof(uint32_t[3]);
void* new_ptr = allocator.Reallocate(ptr, old_layout, new_size);

// Reallocate should call Resize.
Expand Down
19 changes: 12 additions & 7 deletions pw_allocator/allocator_testing.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "pw_allocator_private/allocator_testing.h"

#include "pw_assert/check.h"
#include "pw_bytes/alignment.h"

namespace pw::allocator::test {
Expand All @@ -30,6 +31,12 @@ Status FakeAllocator::Initialize(ByteSpan buffer) {
return OkStatus();
}

void FakeAllocator::Exhaust() {
for (Block* block = head_; block != nullptr; block = NextBlock(block)) {
block->MarkUsed();
}
}

void FakeAllocator::ResetParameters() {
allocate_size_ = 0;
deallocate_ptr_ = nullptr;
Expand All @@ -40,9 +47,7 @@ void FakeAllocator::ResetParameters() {
}

Status FakeAllocator::DoQuery(const void* ptr, size_t size, size_t) const {
if (head_ == nullptr) {
return Status::FailedPrecondition();
}
PW_CHECK(head_ != nullptr);
if (size == 0 || ptr == nullptr) {
return Status::OutOfRange();
}
Expand All @@ -61,10 +66,8 @@ Status FakeAllocator::DoQuery(const void* ptr, size_t size, size_t) const {
}

void* FakeAllocator::DoAllocate(size_t size, size_t) {
PW_CHECK(head_ != nullptr);
allocate_size_ = size;
if (head_ == nullptr || size == 0) {
return nullptr;
}
for (Block* block = head_; block != nullptr; block = NextBlock(block)) {
Block* fragment = nullptr;
if (!block->Used() && block->Split(size, &fragment).ok()) {
Expand All @@ -76,6 +79,7 @@ void* FakeAllocator::DoAllocate(size_t size, size_t) {
}

void FakeAllocator::DoDeallocate(void* ptr, size_t size, size_t alignment) {
PW_CHECK(head_ != nullptr);
deallocate_ptr_ = ptr;
deallocate_size_ = size;
if (!DoQuery(ptr, size, alignment).ok()) {
Expand All @@ -91,6 +95,7 @@ bool FakeAllocator::DoResize(void* ptr,
size_t old_size,
size_t old_alignment,
size_t new_size) {
PW_CHECK(head_ != nullptr);
resize_ptr_ = ptr;
resize_old_size_ = old_size;
resize_new_size_ = new_size;
Expand All @@ -105,7 +110,7 @@ bool FakeAllocator::DoResize(void* ptr,
block->MarkFree();
block->MergeNext().IgnoreError();
Block* fragment = nullptr;
if (block->Split(new_size, &fragment) != Status::OutOfRange()) {
if (block->Split(new_size, &fragment) == Status::OutOfRange()) {
block->Split(old_size, &fragment).IgnoreError();
}
block->MarkUsed();
Expand Down
8 changes: 7 additions & 1 deletion pw_allocator/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ for a dynamic allocator. This is composed of the following parts:
splitting and merging of blocks.
- ``freelist``: A freelist, suitable for fast lookups of available memory chunks
(i.e. ``block`` s).
- ``allocator``: An interface for memory allocators.
- ``allocator``: An interface for memory allocators. Several concrete
implementations are also provided.

Heap Integrity Check
====================
Expand All @@ -30,6 +31,11 @@ Allocator
.. doxygenclass:: pw::allocator::Allocator
:members:

Provided implementations of the ``Allocator`` interface include:

- ``FallbackAllocator``: Dispatches first to a primary allocator, and, if that
fails, to a secondary alloator.

Heap Poisoning
==============

Expand Down
Loading

0 comments on commit 4ef538b

Please sign in to comment.