// Copyright 2024 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/first_fit.h" #include #include "pw_allocator/block/detailed_block.h" #include "pw_allocator/block_allocator_testing.h" #include "pw_allocator/buffer.h" #include "pw_allocator/dual_first_fit_block_allocator.h" #include "pw_allocator/first_fit_block_allocator.h" #include "pw_allocator/last_fit_block_allocator.h" #include "pw_unit_test/framework.h" namespace { using ::pw::allocator::Layout; using ::pw::allocator::test::Preallocation; using BlockType = ::pw::allocator::FirstFitBlock; using FirstFitAllocator = ::pw::allocator::FirstFitAllocator; using ::pw::allocator::test::BlockAllocatorTest; // Minimum size of a "large" allocation; allocation less than this size are // considered "small" when using the "dual first fit" strategy. static constexpr size_t kThreshold = BlockAllocatorTest::kSmallInnerSize * 2; class FirstFitAllocatorTest : public BlockAllocatorTest { public: FirstFitAllocatorTest() : BlockAllocatorTest(allocator_) {} private: FirstFitAllocator allocator_; }; TEST_F(FirstFitAllocatorTest, AutomaticallyInit) { FirstFitAllocator allocator(GetBytes()); AutomaticallyInit(allocator); } TEST_F(FirstFitAllocatorTest, ExplicitlyInit) { FirstFitAllocator allocator; ExplicitlyInit(allocator); } TEST_F(FirstFitAllocatorTest, GetCapacity) { GetCapacity(); } TEST_F(FirstFitAllocatorTest, AllocateLarge) { AllocateLarge(); } TEST_F(FirstFitAllocatorTest, AllocateSmall) { AllocateSmall(); } TEST_F(FirstFitAllocatorTest, AllocateLargeAlignment) { AllocateLargeAlignment(); } TEST_F(FirstFitAllocatorTest, AllocateAlignmentFailure) { AllocateAlignmentFailure(); } TEST_F(FirstFitAllocatorTest, AllocatesZeroThreshold) { FirstFitAllocator& allocator = GetAllocator({ {kSmallOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kLargeOuterSize, Preallocation::kFree}, {Preallocation::kSizeRemaining, Preallocation::kUsed}, }); Store(0, allocator.Allocate(Layout(kSmallInnerSize, 1))); EXPECT_EQ(NextAfter(0), Fetch(1)); Store(4, allocator.Allocate(Layout(kLargeInnerSize, 1))); EXPECT_EQ(NextAfter(3), Fetch(4)); EXPECT_EQ(NextAfter(4), Fetch(5)); } TEST_F(FirstFitAllocatorTest, AllocatesMaxThreshold) { FirstFitAllocator& allocator = GetAllocator({ {kLargeOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, {Preallocation::kSizeRemaining, Preallocation::kUsed}, }); allocator.set_threshold(std::numeric_limits::max()); Store(0, allocator.Allocate(Layout(kLargeInnerSize, 1))); EXPECT_EQ(NextAfter(0), Fetch(1)); Store(4, allocator.Allocate(Layout(kSmallInnerSize, 1))); EXPECT_EQ(NextAfter(3), Fetch(4)); EXPECT_EQ(NextAfter(4), Fetch(5)); } TEST_F(FirstFitAllocatorTest, AllocatesUsingThreshold) { FirstFitAllocator& allocator = GetAllocator({ {kLargeOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, {Preallocation::kSizeRemaining, Preallocation::kUsed}, {kLargeOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, }); allocator.set_threshold(kThreshold); Store(0, allocator.Allocate(Layout(kLargeInnerSize, 1))); EXPECT_EQ(NextAfter(0), Fetch(1)); Store(4, allocator.Allocate(Layout(kLargeInnerSize, 1))); EXPECT_EQ(NextAfter(3), Fetch(4)); EXPECT_EQ(NextAfter(4), Fetch(5)); Store(6, allocator.Allocate(Layout(kSmallInnerSize, 1))); EXPECT_EQ(NextAfter(5), Fetch(6)); EXPECT_EQ(NextAfter(6), Fetch(7)); Store(2, allocator.Allocate(Layout(kSmallInnerSize, 1))); EXPECT_EQ(NextAfter(1), Fetch(2)); EXPECT_EQ(NextAfter(2), Fetch(3)); } TEST_F(FirstFitAllocatorTest, DeallocateNull) { DeallocateNull(); } TEST_F(FirstFitAllocatorTest, DeallocateShuffled) { DeallocateShuffled(); } TEST_F(FirstFitAllocatorTest, IterateOverBlocks) { IterateOverBlocks(); } TEST_F(FirstFitAllocatorTest, ResizeNull) { ResizeNull(); } TEST_F(FirstFitAllocatorTest, ResizeLargeSame) { ResizeLargeSame(); } TEST_F(FirstFitAllocatorTest, ResizeLargeSmaller) { ResizeLargeSmaller(); } TEST_F(FirstFitAllocatorTest, ResizeLargeLarger) { ResizeLargeLarger(); } TEST_F(FirstFitAllocatorTest, ResizeLargeLargerFailure) { ResizeLargeLargerFailure(); } TEST_F(FirstFitAllocatorTest, ResizeSmallSame) { ResizeSmallSame(); } TEST_F(FirstFitAllocatorTest, ResizeSmallSmaller) { ResizeSmallSmaller(); } TEST_F(FirstFitAllocatorTest, ResizeSmallLarger) { ResizeSmallLarger(); } TEST_F(FirstFitAllocatorTest, ResizeSmallLargerFailure) { ResizeSmallLargerFailure(); } TEST_F(FirstFitAllocatorTest, ResizeLargeSmallerAcrossThreshold) { FirstFitAllocator& allocator = GetAllocator({ {kThreshold * 2, Preallocation::kUsed}, {Preallocation::kSizeRemaining, Preallocation::kFree}, }); allocator.set_threshold(kThreshold); // Shrinking succeeds, and the pointer is unchanged even though it is now // below the threshold. size_t new_size = kThreshold / 2; ASSERT_TRUE(allocator.Resize(Fetch(0), new_size)); UseMemory(Fetch(0), kThreshold / 2); } TEST_F(FirstFitAllocatorTest, ResizeSmallLargerAcrossThreshold) { FirstFitAllocator& allocator = GetAllocator({ {Preallocation::kSizeRemaining, Preallocation::kUsed}, {kThreshold / 2, Preallocation::kUsed}, {kThreshold * 2, Preallocation::kFree}, }); allocator.set_threshold(kThreshold); // Growing succeeds, and the pointer is unchanged even though it is now // above the threshold. size_t new_size = kThreshold * 2; ASSERT_TRUE(allocator.Resize(Fetch(1), new_size)); UseMemory(Fetch(1), kThreshold * 2); } TEST_F(FirstFitAllocatorTest, MeasureFragmentation) { MeasureFragmentation(); } TEST_F(FirstFitAllocatorTest, PoisonPeriodically) { PoisonPeriodically(); } // TODO(b/376730645): Remove this test when the legacy alias is deprecated. using DualFirstFitBlockAllocator = ::pw::allocator::DualFirstFitBlockAllocator; class DualFirstFitBlockAllocatorTest : public BlockAllocatorTest { public: DualFirstFitBlockAllocatorTest() : BlockAllocatorTest(allocator_) {} private: DualFirstFitBlockAllocator allocator_; }; TEST_F(DualFirstFitBlockAllocatorTest, AllocatesUsingThreshold) { auto& allocator = GetAllocator({ {kLargeOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, {Preallocation::kSizeRemaining, Preallocation::kUsed}, {kLargeOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, }); allocator.set_threshold(kThreshold); Store(0, allocator.Allocate(Layout(kLargeInnerSize, 1))); EXPECT_EQ(NextAfter(0), Fetch(1)); Store(4, allocator.Allocate(Layout(kLargeInnerSize, 1))); EXPECT_EQ(NextAfter(3), Fetch(4)); EXPECT_EQ(NextAfter(4), Fetch(5)); Store(6, allocator.Allocate(Layout(kSmallInnerSize, 1))); EXPECT_EQ(NextAfter(5), Fetch(6)); EXPECT_EQ(NextAfter(6), Fetch(7)); Store(2, allocator.Allocate(Layout(kSmallInnerSize, 1))); EXPECT_EQ(NextAfter(1), Fetch(2)); EXPECT_EQ(NextAfter(2), Fetch(3)); } // TODO(b/376730645): Remove this test when the legacy alias is deprecated. using FirstFitBlockAllocator = ::pw::allocator::FirstFitBlockAllocator; class FirstFitBlockAllocatorTest : public BlockAllocatorTest { public: FirstFitBlockAllocatorTest() : BlockAllocatorTest(allocator_) {} private: FirstFitBlockAllocator allocator_; }; TEST_F(FirstFitBlockAllocatorTest, AllocatesFirstCompatible) { auto& allocator = GetAllocator({ {kSmallOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kLargeOuterSize, Preallocation::kFree}, {Preallocation::kSizeRemaining, Preallocation::kUsed}, }); Store(0, allocator.Allocate(Layout(kSmallInnerSize, 1))); EXPECT_EQ(NextAfter(0), Fetch(1)); Store(4, allocator.Allocate(Layout(kLargeInnerSize, 1))); EXPECT_EQ(NextAfter(3), Fetch(4)); EXPECT_EQ(NextAfter(4), Fetch(5)); } // TODO(b/376730645): Remove this test when the legacy alias is deprecated. using LastFitBlockAllocator = ::pw::allocator::LastFitBlockAllocator; class LastFitBlockAllocatorTest : public BlockAllocatorTest { public: LastFitBlockAllocatorTest() : BlockAllocatorTest(allocator_) {} private: LastFitBlockAllocator allocator_; }; TEST_F(LastFitBlockAllocatorTest, AllocatesLastCompatible) { auto& allocator = GetAllocator({ {kLargeOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, {kSmallerOuterSize, Preallocation::kUsed}, {kSmallOuterSize, Preallocation::kFree}, {Preallocation::kSizeRemaining, Preallocation::kUsed}, }); Store(0, allocator.Allocate(Layout(kLargeInnerSize, 1))); EXPECT_EQ(NextAfter(0), Fetch(1)); Store(4, allocator.Allocate(Layout(kSmallInnerSize, 1))); EXPECT_EQ(NextAfter(3), Fetch(4)); EXPECT_EQ(NextAfter(4), Fetch(5)); } } // namespace