diff --git a/pw_allocator/BUILD.bazel b/pw_allocator/BUILD.bazel index d40e93c372..30ed3d2add 100644 --- a/pw_allocator/BUILD.bazel +++ b/pw_allocator/BUILD.bazel @@ -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 = [ @@ -97,6 +113,7 @@ pw_cc_library( deps = [ ":allocator", ":block", + "//pw_assert", "//pw_bytes", ], ) @@ -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 = [ diff --git a/pw_allocator/BUILD.gn b/pw_allocator/BUILD.gn index e1da4f9be9..dd8b59226f 100644 --- a/pw_allocator/BUILD.gn +++ b/pw_allocator/BUILD.gn @@ -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" ] } @@ -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" ] @@ -97,6 +107,7 @@ pw_test_group("tests") { tests = [ ":allocator_test", ":block_test", + ":fallback_allocator_test", ":freelist_test", ":freelist_heap_test", ] @@ -107,6 +118,7 @@ pw_source_set("allocator_testing") { public_deps = [ ":allocator", ":block", + dir_pw_assert, dir_pw_bytes, ] sources = [ "allocator_testing.cc" ] @@ -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 = [ diff --git a/pw_allocator/CMakeLists.txt b/pw_allocator/CMakeLists.txt index 06abe0887d..00491f249b 100644 --- a/pw_allocator/CMakeLists.txt +++ b/pw_allocator/CMakeLists.txt @@ -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 @@ -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 @@ -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 diff --git a/pw_allocator/allocator.cc b/pw_allocator/allocator.cc index d9824e6cb6..15cca58bcf 100644 --- a/pw_allocator/allocator.cc +++ b/pw_allocator/allocator.cc @@ -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; } diff --git a/pw_allocator/allocator_test.cc b/pw_allocator/allocator_test.cc index 6886555b4f..bbd3f6c77c 100644 --- a/pw_allocator/allocator_test.cc +++ b/pw_allocator/allocator_test.cc @@ -23,27 +23,22 @@ namespace pw::allocator { namespace { -using test::FakeAllocator; - // Test fixtures. struct AllocatorTest : ::testing::Test { - FakeAllocator allocator; - std::array buffer; + private: + std::array 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(); + constexpr Layout old_layout = Layout::Of(); size_t new_size = old_layout.size(); void* new_ptr = allocator.Reallocate(nullptr, old_layout, new_size); @@ -64,23 +59,20 @@ TEST_F(AllocatorTest, ReallocateNull) { } TEST_F(AllocatorTest, ReallocateZeroNewSize) { - Layout old_layout = Layout::Of(); + constexpr Layout old_layout = Layout::Of(); 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); @@ -89,7 +81,7 @@ TEST_F(AllocatorTest, ReallocateZeroNewSize) { } TEST_F(AllocatorTest, ReallocateSame) { - Layout layout = Layout::Of(); + constexpr Layout layout = Layout::Of(); void* ptr = allocator.Allocate(layout); ASSERT_EQ(allocator.allocate_size(), layout.size()); ASSERT_NE(ptr, nullptr); @@ -114,7 +106,7 @@ TEST_F(AllocatorTest, ReallocateSame) { } TEST_F(AllocatorTest, ReallocateSmaller) { - Layout old_layout = Layout::Of(); + constexpr Layout old_layout = Layout::Of(); void* ptr = allocator.Allocate(old_layout); ASSERT_EQ(allocator.allocate_size(), old_layout.size()); ASSERT_NE(ptr, nullptr); @@ -140,7 +132,7 @@ TEST_F(AllocatorTest, ReallocateSmaller) { } TEST_F(AllocatorTest, ReallocateLarger) { - Layout old_layout = Layout::Of(); + constexpr Layout old_layout = Layout::Of(); void* ptr = allocator.Allocate(old_layout); ASSERT_EQ(allocator.allocate_size(), old_layout.size()); ASSERT_NE(ptr, nullptr); @@ -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. diff --git a/pw_allocator/allocator_testing.cc b/pw_allocator/allocator_testing.cc index e57e963a2a..337b89d20c 100644 --- a/pw_allocator/allocator_testing.cc +++ b/pw_allocator/allocator_testing.cc @@ -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 { @@ -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; @@ -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(); } @@ -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()) { @@ -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()) { @@ -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; @@ -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(); diff --git a/pw_allocator/docs.rst b/pw_allocator/docs.rst index 959cefee8b..81413d1461 100644 --- a/pw_allocator/docs.rst +++ b/pw_allocator/docs.rst @@ -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 ==================== @@ -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 ============== diff --git a/pw_allocator/fallback_allocator.cc b/pw_allocator/fallback_allocator.cc new file mode 100644 index 0000000000..cfa7bd120b --- /dev/null +++ b/pw_allocator/fallback_allocator.cc @@ -0,0 +1,62 @@ + +// Copyright 2023 The Pigweed Authors +// +// 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 +// +// https://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 "pw_allocator/fallback_allocator.h" + +#include "pw_assert/check.h" + +namespace pw::allocator { + +void FallbackAllocator::Initialize(Allocator& primary, Allocator& secondary) { + primary_ = &primary; + secondary_ = &secondary; +} + +Status FallbackAllocator::DoQuery(const void* ptr, + size_t size, + size_t alignment) const { + PW_DCHECK(primary_ != nullptr && secondary_ != nullptr); + auto status = primary_->QueryUnchecked(ptr, size, alignment); + return status.ok() ? status + : secondary_->QueryUnchecked(ptr, size, alignment); +} + +void* FallbackAllocator::DoAllocate(size_t size, size_t alignment) { + PW_DCHECK(primary_ != nullptr && secondary_ != nullptr); + void* ptr = primary_->AllocateUnchecked(size, alignment); + return ptr != nullptr ? ptr : secondary_->AllocateUnchecked(size, alignment); +} + +void FallbackAllocator::DoDeallocate(void* ptr, size_t size, size_t alignment) { + PW_DCHECK(primary_ != nullptr && secondary_ != nullptr); + if (primary_->QueryUnchecked(ptr, size, alignment).ok()) { + primary_->DeallocateUnchecked(ptr, size, alignment); + } else { + secondary_->DeallocateUnchecked(ptr, size, alignment); + } +} + +bool FallbackAllocator::DoResize(void* ptr, + size_t old_size, + size_t old_alignment, + size_t new_size) { + PW_DCHECK(primary_ != nullptr && secondary_ != nullptr); + return primary_->QueryUnchecked(ptr, old_size, old_alignment).ok() + ? primary_->ResizeUnchecked(ptr, old_size, old_alignment, new_size) + : secondary_->ResizeUnchecked( + ptr, old_size, old_alignment, new_size); +} + +} // namespace pw::allocator diff --git a/pw_allocator/fallback_allocator_test.cc b/pw_allocator/fallback_allocator_test.cc new file mode 100644 index 0000000000..982c6cc7d6 --- /dev/null +++ b/pw_allocator/fallback_allocator_test.cc @@ -0,0 +1,223 @@ +// Copyright 2023 The Pigweed Authors +// +// 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 +// +// https://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 "pw_allocator/fallback_allocator.h" + +#include "gtest/gtest.h" +#include "pw_allocator_private/allocator_testing.h" +#include "pw_status/status.h" + +namespace pw::allocator { +namespace { + +// Test fixtures. + +struct FallbackAllocatorTest : ::testing::Test { + private: + std::array buffer1 = {}; + std::array buffer2 = {}; + + public: + test::FakeAllocator primary; + test::FakeAllocator secondary; + FallbackAllocator allocator; + + void SetUp() override { + EXPECT_EQ(primary.Initialize(buffer1), OkStatus()); + EXPECT_EQ(secondary.Initialize(buffer2), OkStatus()); + allocator.Initialize(primary, secondary); + } +}; + +// Unit tests. + +TEST_F(FallbackAllocatorTest, QueryValidPrimary) { + Layout layout = Layout::Of(); + void* ptr = primary.Allocate(layout); + EXPECT_TRUE(primary.Query(ptr, layout).ok()); + EXPECT_EQ(secondary.Query(ptr, layout), Status::OutOfRange()); + EXPECT_TRUE(allocator.Query(ptr, layout).ok()); +} + +TEST_F(FallbackAllocatorTest, QueryValidSecondary) { + Layout layout = Layout::Of(); + void* ptr = secondary.Allocate(layout); + EXPECT_FALSE(primary.Query(ptr, layout).ok()); + EXPECT_TRUE(secondary.Query(ptr, layout).ok()); + EXPECT_TRUE(allocator.Query(ptr, layout).ok()); +} + +TEST_F(FallbackAllocatorTest, QueryInvalidPtr) { + std::array buffer = {}; + test::FakeAllocator other; + ASSERT_TRUE(other.Initialize(buffer).ok()); + Layout layout = Layout::Of(); + void* ptr = other.Allocate(layout); + EXPECT_FALSE(primary.Query(ptr, layout).ok()); + EXPECT_FALSE(secondary.Query(ptr, layout).ok()); + EXPECT_FALSE(allocator.Query(ptr, layout).ok()); +} + +TEST_F(FallbackAllocatorTest, AllocateFromPrimary) { + Layout layout = Layout::Of(); + void* ptr = allocator.Allocate(layout); + EXPECT_NE(ptr, nullptr); + EXPECT_EQ(primary.allocate_size(), layout.size()); + EXPECT_EQ(secondary.allocate_size(), 0U); +} + +TEST_F(FallbackAllocatorTest, AllocateFromSecondary) { + primary.Exhaust(); + Layout layout = Layout::Of(); + void* ptr = allocator.Allocate(layout); + EXPECT_NE(ptr, nullptr); + EXPECT_EQ(primary.allocate_size(), layout.size()); + EXPECT_EQ(secondary.allocate_size(), layout.size()); +} + +TEST_F(FallbackAllocatorTest, AllocateFailure) { + Layout layout = Layout::Of(); + void* ptr = allocator.Allocate(layout); + EXPECT_EQ(ptr, nullptr); + EXPECT_EQ(primary.allocate_size(), layout.size()); + EXPECT_EQ(secondary.allocate_size(), layout.size()); +} + +TEST_F(FallbackAllocatorTest, DeallocateUsingPrimary) { + Layout layout = Layout::Of(); + void* ptr = allocator.Allocate(layout); + ASSERT_NE(ptr, nullptr); + allocator.Deallocate(ptr, layout); + EXPECT_EQ(primary.deallocate_ptr(), ptr); + EXPECT_EQ(primary.deallocate_size(), layout.size()); + EXPECT_EQ(secondary.deallocate_ptr(), nullptr); + EXPECT_EQ(secondary.deallocate_size(), 0U); +} + +TEST_F(FallbackAllocatorTest, DeallocateUsingSecondary) { + primary.Exhaust(); + Layout layout = Layout::Of(); + void* ptr = allocator.Allocate(layout); + ASSERT_NE(ptr, nullptr); + allocator.Deallocate(ptr, layout); + EXPECT_EQ(primary.deallocate_ptr(), nullptr); + EXPECT_EQ(primary.deallocate_size(), 0U); + EXPECT_EQ(secondary.deallocate_ptr(), ptr); + EXPECT_EQ(secondary.deallocate_size(), layout.size()); +} + +TEST_F(FallbackAllocatorTest, ResizePrimary) { + Layout old_layout = Layout::Of(); + void* ptr = allocator.Allocate(old_layout); + ASSERT_NE(ptr, nullptr); + + size_t new_size = sizeof(uint32_t[3]); + EXPECT_TRUE(allocator.Resize(ptr, old_layout, new_size)); + EXPECT_EQ(primary.resize_ptr(), ptr); + EXPECT_EQ(primary.resize_old_size(), old_layout.size()); + EXPECT_EQ(primary.resize_new_size(), new_size); + + // Secondary should not be touched. + EXPECT_EQ(secondary.resize_ptr(), nullptr); + EXPECT_EQ(secondary.resize_old_size(), 0U); + EXPECT_EQ(secondary.resize_new_size(), 0U); +} + +TEST_F(FallbackAllocatorTest, ResizePrimaryFailure) { + Layout old_layout = Layout::Of(); + void* ptr = allocator.Allocate(old_layout); + ASSERT_NE(ptr, nullptr); + primary.Exhaust(); + + size_t new_size = sizeof(uint32_t[3]); + EXPECT_FALSE(allocator.Resize(ptr, old_layout, new_size)); + EXPECT_EQ(primary.resize_ptr(), ptr); + EXPECT_EQ(primary.resize_old_size(), old_layout.size()); + EXPECT_EQ(primary.resize_new_size(), new_size); + + // Secondary should not be touched. + EXPECT_EQ(secondary.resize_ptr(), nullptr); + EXPECT_EQ(secondary.resize_old_size(), 0U); + EXPECT_EQ(secondary.resize_new_size(), 0U); +} + +TEST_F(FallbackAllocatorTest, ResizeSecondary) { + primary.Exhaust(); + Layout old_layout = Layout::Of(); + void* ptr = allocator.Allocate(old_layout); + ASSERT_NE(ptr, nullptr); + + size_t new_size = sizeof(uint32_t[3]); + EXPECT_TRUE(allocator.Resize(ptr, old_layout, new_size)); + EXPECT_EQ(secondary.resize_ptr(), ptr); + EXPECT_EQ(secondary.resize_old_size(), old_layout.size()); + EXPECT_EQ(secondary.resize_new_size(), new_size); + + // Primary should not be touched. + EXPECT_EQ(primary.resize_ptr(), nullptr); + EXPECT_EQ(primary.resize_old_size(), 0U); + EXPECT_EQ(primary.resize_new_size(), 0U); +} + +TEST_F(FallbackAllocatorTest, ResizeSecondaryFailure) { + primary.Exhaust(); + Layout old_layout = Layout::Of(); + void* ptr = allocator.Allocate(old_layout); + ASSERT_NE(ptr, nullptr); + secondary.Exhaust(); + + size_t new_size = sizeof(uint32_t[3]); + EXPECT_FALSE(allocator.Resize(ptr, old_layout, new_size)); + EXPECT_EQ(secondary.resize_ptr(), ptr); + EXPECT_EQ(secondary.resize_old_size(), old_layout.size()); + EXPECT_EQ(secondary.resize_new_size(), new_size); + + // Primary should not be touched. + EXPECT_EQ(primary.resize_ptr(), nullptr); + EXPECT_EQ(primary.resize_old_size(), 0U); + EXPECT_EQ(primary.resize_new_size(), 0U); +} + +TEST_F(FallbackAllocatorTest, ReallocateSameAllocator) { + Layout old_layout = Layout::Of(); + void* ptr1 = allocator.Allocate(old_layout); + ASSERT_NE(ptr1, nullptr); + + // Claim subsequent memeory to force reallocation. + void* ptr2 = allocator.Allocate(old_layout); + ASSERT_NE(ptr2, nullptr); + + size_t new_size = sizeof(uint32_t[3]); + void* new_ptr = allocator.Reallocate(ptr1, old_layout, new_size); + EXPECT_NE(new_ptr, nullptr); + EXPECT_EQ(primary.deallocate_ptr(), ptr1); + EXPECT_EQ(primary.deallocate_size(), old_layout.size()); + EXPECT_EQ(primary.allocate_size(), new_size); +} + +TEST_F(FallbackAllocatorTest, ReallocateDifferentAllocator) { + Layout old_layout = Layout::Of(); + void* ptr = allocator.Allocate(old_layout); + primary.Exhaust(); + + size_t new_size = sizeof(uint32_t[3]); + void* new_ptr = allocator.Reallocate(ptr, old_layout, new_size); + EXPECT_NE(new_ptr, nullptr); + EXPECT_EQ(primary.deallocate_ptr(), ptr); + EXPECT_EQ(primary.deallocate_size(), old_layout.size()); + EXPECT_EQ(secondary.allocate_size(), new_size); +} + +} // namespace +} // namespace pw::allocator diff --git a/pw_allocator/public/pw_allocator/allocator.h b/pw_allocator/public/pw_allocator/allocator.h index 13356ede48..63ec8e5a2a 100644 --- a/pw_allocator/public/pw_allocator/allocator.h +++ b/pw_allocator/public/pw_allocator/allocator.h @@ -73,17 +73,37 @@ class Allocator { /// pointer. /// /// NOTE: This method is in development and should not be considered stable. + /// Do NOT use it in its current form to determine if this allocator can + /// deallocate pointers. Callers MUST only `Deallocate` memory using the same + /// `Allocator` they used to `Allocate` it. This method is currently for + /// internal use only. + /// + /// TODO: b/301677395 - Add dynamic type information to support a + /// `std::pmr`-style `do_is_equal`. Without this information, it is not + /// possible to determine whether another allocator has applied additional + /// constraints to memory that otherwise may appear to be associated with this + /// allocator. /// /// @param[in] ptr The pointer to be queried. /// @param[in] layout Describes the memory pointed at by `ptr`. /// - /// @retval UNIMPLEMENTED This object cannot recognize allocated pointers. - /// @retval OUT_OF_RANGE Pointer cannot be re/deallocated by this object. - /// @retval OK This object can re/deallocate the pointer. + /// @retval UNIMPLEMENTED This object cannot recognize allocated + /// pointers. + /// @retval OUT_OF_RANGE Pointer cannot be re/deallocated by this + /// object. + /// @retval OK This object can re/deallocate the pointer. Status Query(const void* ptr, Layout layout) const { return DoQuery(ptr, layout.size(), layout.alignment()); } + /// Like `Query`, but takes its parameters directly instead of as a `Layout`. + /// + /// Callers should almost always prefer `Query`. This method is meant for use + /// by tests and other allocators implementing the virtual functions below. + Status QueryUnchecked(const void* ptr, size_t size, size_t alignment) const { + return DoQuery(ptr, size, alignment); + } + /// Allocates a block of memory with the specified size and alignment. /// /// Returns `nullptr` if the allocation cannot be made, or the `layout` has a @@ -94,33 +114,65 @@ class Allocator { return DoAllocate(layout.size(), layout.alignment()); } + /// Like `Allocate`, but takes its parameters directly instead of as a + /// `Layout`. + /// + /// Callers should almost always prefer `Allocate`. This method is meant for + /// use by tests and other allocators implementing the virtual functions + /// below. + void* AllocateUnchecked(size_t size, size_t alignment) { + return DoAllocate(size, alignment); + } + /// Releases a previously-allocated block of memory. /// /// The given pointer must have been previously obtained from a call to either /// `Allocate` or `Reallocate`; otherwise the behavior is undefined. /// - /// @param[in] ptr Pointer to previously-allocated memory. - /// @param[in] layout Describes the memory to be deallocated. + /// @param[in] ptr Pointer to previously-allocated memory. + /// @param[in] layout Describes the memory to be deallocated. void Deallocate(void* ptr, Layout layout) { return DoDeallocate(ptr, layout.size(), layout.alignment()); } + /// Like `Deallocate`, but takes its parameters directly instead of as a + /// `Layout`. + /// + /// Callers should almost always prefer `Deallocate`. This method is meant for + /// use by tests and other allocators implementing the virtual functions + /// below. + void DeallocateUnchecked(void* ptr, size_t size, size_t alignment) { + return DoDeallocate(ptr, size, alignment); + } + /// Modifies the size of an previously-allocated block of memory without /// copying any data. /// /// Returns true if its size was changed without copying data to a new /// allocation; otherwise returns false. /// - /// In particular, returns false if the given pointer is null, the - /// `old_layout` has a size of 0, or the `new_size` is 0. + /// In particular, it always returns true if the `old_layout.size()` equals + /// `new_szie`, and always returns false if the given pointer is null, the + /// `old_layout.size()` is 0, or the `new_size` is 0. /// - /// @param[in] ptr Pointer to previously-allocated memory. - /// @param[in] old_layout Describes the previously-allocated memory. - /// @param[in] new_size Requested new size for the memory allocation. + /// @param[in] ptr Pointer to previously-allocated memory. + /// @param[in] old_layout Describes the previously-allocated memory. + /// @param[in] new_size Requested new size for the memory allocation. bool Resize(void* ptr, Layout old_layout, size_t new_size) { return DoResize(ptr, old_layout.size(), old_layout.alignment(), new_size); } + /// Like `Resize`, but takes its parameters directly instead of as a `Layout`. + /// + /// Callers should almost always prefer `Resize`. This method is meant for use + /// by tests and other allocators implementing the virtual functions below. + bool ResizeUnchecked(void* ptr, + size_t old_size, + size_t old_alignment, + size_t new_size) { + return DoResize(ptr, old_size, old_alignment, new_size); + } + /// Modifies the size of a previously-allocated block of memory. /// /// Returns pointer to the modified block of memory, or `nullptr` if the @@ -145,6 +197,19 @@ class Allocator { ptr, old_layout.size(), old_layout.alignment(), new_size); } + /// Like `Reallocate`, but takes its parameters directly instead of as a + /// `Layout`. + /// + /// Callers should almost always prefer `Reallocate`. This method is meant for + /// use by tests and other allocators implementing the virtual functions + /// below. + void* ReallocateUnchecked(void* ptr, + size_t old_size, + size_t old_alignment, + size_t new_size) { + return DoReallocate(ptr, old_size, old_alignment, new_size); + } + private: /// Virtual `Query` function that can be overridden by derived classes. /// diff --git a/pw_allocator/public/pw_allocator/fallback_allocator.h b/pw_allocator/public/pw_allocator/fallback_allocator.h new file mode 100644 index 0000000000..61c20fd2bf --- /dev/null +++ b/pw_allocator/public/pw_allocator/fallback_allocator.h @@ -0,0 +1,54 @@ +// Copyright 2023 The Pigweed Authors +// +// 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 +// +// https://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. +#pragma once + +#include "pw_allocator/allocator.h" +#include "pw_status/status.h" + +namespace pw::allocator { + +/// This class simply dispatches between a primary and secondary allocator. Any +/// attempt to allocate memory will first be handled by the primary allocator. +/// If it cannot allocate memory, e.g. because it is out of memory, the +/// secondary alloator will try to allocate memory instead. +class FallbackAllocator : public Allocator { + public: + constexpr FallbackAllocator() = default; + + /// Sets the primary and secondary allocators. + /// + /// It is an error to call any method without calling this method first. + void Initialize(Allocator& primary, Allocator& secondary); + + private: + /// @copydoc Allocator::Query + Status DoQuery(const void* ptr, size_t size, size_t alignment) const override; + + /// @copydoc Allocator::Allocate + void* DoAllocate(size_t size, size_t alignment) override; + + /// @copydoc Allocator::Deallocate + void DoDeallocate(void* ptr, size_t size, size_t alignment) override; + + /// @copydoc Allocator::Resize + bool DoResize(void* ptr, + size_t old_size, + size_t alignment, + size_t new_size) override; + + Allocator* primary_ = nullptr; + Allocator* secondary_ = nullptr; +}; + +} // namespace pw::allocator diff --git a/pw_allocator/pw_allocator_private/allocator_testing.h b/pw_allocator/pw_allocator_private/allocator_testing.h index 7fc79fbe57..03bf07365c 100644 --- a/pw_allocator/pw_allocator_private/allocator_testing.h +++ b/pw_allocator/pw_allocator_private/allocator_testing.h @@ -29,17 +29,22 @@ namespace pw::allocator::test { /// `Allocator` interface methods, and returns them via accessors. class FakeAllocator : public Allocator { public: - Status Initialize(ByteSpan buffer); + constexpr FakeAllocator() = default; size_t allocate_size() const { return allocate_size_; } - void* deallocate_ptr() const { return deallocate_ptr_; } size_t deallocate_size() const { return deallocate_size_; } - void* resize_ptr() const { return resize_ptr_; } size_t resize_old_size() const { return resize_old_size_; } size_t resize_new_size() const { return resize_new_size_; } + /// Provides memory for the allocator to allocate from. + Status Initialize(ByteSpan buffer); + + /// Allocates all the memory from this object. + void Exhaust(); + + /// Resets the recorded parameters to an initial state. void ResetParameters(); private: @@ -59,12 +64,12 @@ class FakeAllocator : public Allocator { size_t new_size) override; Block* head_ = nullptr; - size_t allocate_size_; - void* deallocate_ptr_; - size_t deallocate_size_; - void* resize_ptr_; - size_t resize_old_size_; - size_t resize_new_size_; + size_t allocate_size_ = 0; + void* deallocate_ptr_ = nullptr; + size_t deallocate_size_ = 0; + void* resize_ptr_ = nullptr; + size_t resize_old_size_ = 0; + size_t resize_new_size_ = 0; }; } // namespace pw::allocator::test