diff --git a/docs/BUILD.gn b/docs/BUILD.gn index 6a0284dec..235c26bd2 100644 --- a/docs/BUILD.gn +++ b/docs/BUILD.gn @@ -199,6 +199,7 @@ _doxygen_input_files = [ # keep-sorted: start "$dir_pw_allocator/public/pw_allocator/synchronized_allocator.h", "$dir_pw_allocator/public/pw_allocator/test_harness.h", "$dir_pw_allocator/public/pw_allocator/testing.h", + "$dir_pw_allocator/public/pw_allocator/tlsf_allocator.h", "$dir_pw_allocator/public/pw_allocator/tracking_allocator.h", "$dir_pw_allocator/public/pw_allocator/typed_pool.h", "$dir_pw_allocator/public/pw_allocator/unique_ptr.h", diff --git a/pw_allocator/BUILD.bazel b/pw_allocator/BUILD.bazel index b2ccc9dcc..803cb75fb 100644 --- a/pw_allocator/BUILD.bazel +++ b/pw_allocator/BUILD.bazel @@ -374,6 +374,20 @@ cc_library( ], ) +cc_library( + name = "tlsf_allocator", + hdrs = ["public/pw_allocator/tlsf_allocator.h"], + includes = ["public"], + deps = [ + ":block_allocator", + ":config", + "//pw_allocator/block:detailed_block", + "//pw_allocator/bucket:fast_sorted", + "//pw_allocator/bucket:sorted", + "//third_party/fuchsia:stdcompat", + ], +) + cc_library( name = "tracking_allocator", hdrs = [ @@ -732,6 +746,16 @@ pw_cc_test( ], ) +pw_cc_test( + name = "tlsf_allocator_test", + srcs = ["tlsf_allocator_test.cc"], + deps = [ + ":block_allocator_testing", + ":tlsf_allocator", + "//pw_unit_test", + ], +) + pw_cc_test( name = "tracking_allocator_test", srcs = [ diff --git a/pw_allocator/BUILD.gn b/pw_allocator/BUILD.gn index 6a6e405e2..599b16895 100644 --- a/pw_allocator/BUILD.gn +++ b/pw_allocator/BUILD.gn @@ -316,6 +316,19 @@ pw_source_set("synchronized_allocator") { ] } +pw_source_set("tlsf_allocator") { + public_configs = [ ":public_include_path" ] + public = [ "public/pw_allocator/tlsf_allocator.h" ] + public_deps = [ + ":block_allocator", + ":config", + "$dir_pw_third_party/fuchsia:stdcompat", + "block:detailed_block", + "bucket:fast_sorted", + "bucket:sorted", + ] +} + pw_source_set("tracking_allocator") { public_configs = [ ":public_include_path" ] public = [ @@ -586,6 +599,14 @@ pw_test("synchronized_allocator_test") { sources = [ "synchronized_allocator_test.cc" ] } +pw_test("tlsf_allocator_test") { + deps = [ + ":block_allocator_testing", + ":tlsf_allocator", + ] + sources = [ "tlsf_allocator_test.cc" ] +} + pw_test("tracking_allocator_test") { deps = [ ":testing", @@ -638,6 +659,7 @@ pw_test_group("tests") { ":null_allocator_test", ":pmr_allocator_test", ":synchronized_allocator_test", + ":tlsf_allocator_test", ":tracking_allocator_test", ":typed_pool_test", ":unique_ptr_test", @@ -702,6 +724,11 @@ pw_size_diff("concrete_allocators_size_report") { base = "size_report:null_allocator" label = "LibCAllocator" }, + { + target = "size_report:tlsf_allocator" + base = "size_report:null_allocator" + label = "TlsfAllocator" + }, { target = "size_report:worst_fit" base = "size_report:null_allocator" diff --git a/pw_allocator/CMakeLists.txt b/pw_allocator/CMakeLists.txt index b99b3212a..a7a13758e 100644 --- a/pw_allocator/CMakeLists.txt +++ b/pw_allocator/CMakeLists.txt @@ -342,6 +342,20 @@ pw_add_library(pw_allocator.synchronized_allocator INTERFACE pw_sync.borrow ) +pw_add_library(pw_allocator.tlsf_allocator INTERFACE + HEADERS + public/pw_allocator/tlsf_allocator.h + PUBLIC_INCLUDES + public + PUBLIC_DEPS + pw_allocator.block_allocator + pw_allocator.block.detailed_block + pw_allocator.bucket.sorted + pw_allocator.bucket.fast_sorted + pw_allocator.config + pw_third_party.fuchsia.stdcompat +) + pw_add_library(pw_allocator.tracking_allocator INTERFACE HEADERS public/pw_allocator/metrics.h @@ -675,6 +689,17 @@ pw_add_test(pw_allocator.synchronized_allocator_test pw_allocator ) +pw_add_test(pw_allocator.tlsf_allocator_test + SOURCES + tlsf_allocator_test.cc + PRIVATE_DEPS + pw_allocator.block_allocator_testing + pw_allocator.tlsf_allocator + GROUPS + modules + pw_allocator +) + pw_add_test(pw_allocator.tracking_allocator_test SOURCES tracking_allocator_test.cc diff --git a/pw_allocator/api.rst b/pw_allocator/api.rst index 6588cf8d6..281e3934a 100644 --- a/pw_allocator/api.rst +++ b/pw_allocator/api.rst @@ -96,6 +96,9 @@ This module provides several concrete allocator implementations of the BlockAllocator ============== +Several allocators use :ref:`module-pw_allocator-api-block` types to manage +memory, and derive from this abstract base type. + .. doxygenclass:: pw::allocator::BlockAllocator :members: @@ -113,6 +116,13 @@ BestFitAllocator .. doxygenclass:: pw::allocator::BestFitAllocator :members: +.. _module-pw_allocator-api-tlsf_allocator: + +TlsfAllocator +------------- +.. doxygenclass:: pw::allocator::TlsfAllocator + :members: + .. _module-pw_allocator-api-worst_fit_allocator: WorstFitAllocator @@ -218,8 +228,8 @@ includes some utility classes. .. _module-pw_allocator-api-block: -Block interfaces -================ +Block +===== A block is an allocatable region of memory, and is the fundamental type managed by several of the concrete allocator implementations. Blocks are defined using several stateless "mix-in" interface types. These provide specific diff --git a/pw_allocator/benchmarks/BUILD.bazel b/pw_allocator/benchmarks/BUILD.bazel index b0135ef71..9d79a035e 100644 --- a/pw_allocator/benchmarks/BUILD.bazel +++ b/pw_allocator/benchmarks/BUILD.bazel @@ -120,6 +120,19 @@ cc_binary( ], ) +cc_binary( + name = "tlsf_benchmark", + testonly = True, + srcs = [ + "tlsf_benchmark.cc", + ], + deps = [ + ":benchmark", + "//pw_allocator:tlsf_allocator", + "//pw_random", + ], +) + cc_binary( name = "worst_fit_benchmark", testonly = True, diff --git a/pw_allocator/benchmarks/BUILD.gn b/pw_allocator/benchmarks/BUILD.gn index 1719c4cb2..44fe5670e 100644 --- a/pw_allocator/benchmarks/BUILD.gn +++ b/pw_allocator/benchmarks/BUILD.gn @@ -102,6 +102,15 @@ pw_executable("last_fit_benchmark") { ] } +pw_executable("tlsf_benchmark") { + sources = [ "tlsf_benchmark.cc" ] + deps = [ + ":benchmark", + "$dir_pw_allocator:tlsf_allocator", + "$dir_pw_random", + ] +} + pw_executable("worst_fit_benchmark") { sources = [ "worst_fit_benchmark.cc" ] deps = [ diff --git a/pw_allocator/benchmarks/tlsf_benchmark.cc b/pw_allocator/benchmarks/tlsf_benchmark.cc new file mode 100644 index 000000000..3cc6d13ea --- /dev/null +++ b/pw_allocator/benchmarks/tlsf_benchmark.cc @@ -0,0 +1,44 @@ +// 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 +#include +#include + +#include "pw_allocator/benchmarks/benchmark.h" +#include "pw_allocator/benchmarks/config.h" +#include "pw_allocator/tlsf_allocator.h" + +namespace pw::allocator { + +constexpr metric::Token kTlsfBenchmark = + PW_TOKENIZE_STRING("two-layer, segregated-fit benchmark"); + +std::array buffer; + +void DoTlsfBenchmark() { + TlsfAllocator allocator(buffer); + DefaultBlockAllocatorBenchmark benchmark(kTlsfBenchmark, allocator); + benchmark.set_prng_seed(1); + benchmark.set_available(benchmarks::kCapacity); + benchmark.GenerateRequests(benchmarks::kMaxSize, benchmarks::kNumRequests); + benchmark.metrics().Dump(); +} + +} // namespace pw::allocator + +int main() { + pw::allocator::DoTlsfBenchmark(); + return 0; +} diff --git a/pw_allocator/public/pw_allocator/tlsf_allocator.h b/pw_allocator/public/pw_allocator/tlsf_allocator.h new file mode 100644 index 000000000..272823f50 --- /dev/null +++ b/pw_allocator/public/pw_allocator/tlsf_allocator.h @@ -0,0 +1,275 @@ +// 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. +#pragma once + +#include +#include +#include + +#include "pw_allocator/block/detailed_block.h" +#include "pw_allocator/block_allocator.h" +#include "pw_allocator/bucket/fast_sorted.h" +#include "pw_allocator/bucket/sorted.h" + +namespace pw::allocator { + +/// Alias for a default block type that is compatible with `TlsfAllocator`. +template +using TlsfBlock = DetailedBlock; + +/// Default values for the template parameters of `TlsfAllocator`. +/// +/// By default, this is tuned for allocations between 64B and 64KB. +struct TlsfDefaults { + /// Default maximum inner size of the smallest bucket in a TLSF allocator's + /// two-dimensional array of buckets. + static constexpr size_t kMinSize = 64; + + /// Default number of rows in a TLSF allocator's two-dimensional array of + /// buckets. + static constexpr size_t kNumShelves = 10; +}; + +/// Pair used to index a bucket in a two dimensional array. +struct TlsfIndices { + size_t shelf; + size_t bucket; +}; + +/// Two-layered, segregated fit allocator. +/// +/// This allocator uses a two-dimensional array of buckets to quickly satisfy +/// memory allocations with best-fit blocks as described by +/// http://www.gii.upv.es/tlsf/files/papers/ecrts04_tlsf.pdf +/// +/// This class refers to the "second-level arrays" in that paper as "shelves". +/// Each shelf holds an array of Buckets, and an instance of this class holds an +/// array of shelves. Conceptually, buckets can be thought of as being +/// organized on a set of "shelves", with each shelf having 16 buckets arranged +/// from smallest maximum inner size to largest. The smallest maximum inner size +/// on a shelf is a power of 2, and the shelves are arranged from the `kMinSize` +/// on the "bottom" to the largest maximum inner sizes on the "top". The last +/// bucket on the topmost shelf is unbounded to handle any blocks of arbitrary +/// size. +/// +/// For example, if `kMinSize` is 64, and `kNumShelves` is 10, than the maximum +/// inner sizes of buckets on each shelf could be represented as: +/// +/// @code +/// { +/// shelves_[9]: { 32k, 34k, ..., 62k, inf }, +/// ...: { ..., ..., ..., ..., ... }, +/// shelves_[1]: { 128, 136, ..., 240, 248 }, +/// shelves_[0]: { 64, 68, ..., 120, 124 }, +/// } +/// @endcode +/// +/// @tparam BlockType Block implementation +/// @tparam kMinSize Maximum inner size of blocks in the first bucket on +/// lowest shelf. +/// @tparam kNumShelves Number of rows in the two-dimensional array. +template , + size_t kMinSize = TlsfDefaults::kMinSize, + size_t kNumShelves = TlsfDefaults::kNumShelves> +class TlsfAllocator : public BlockAllocator { + private: + using Base = BlockAllocator; + using BucketType = FastSortedBucket; + + static constexpr size_t kNumBucketsPerShelf = 16; + static constexpr size_t kBucketBits = cpp20::countr_zero(kNumBucketsPerShelf); + using Shelf = std::array; + + static_assert(kMinSize >= kNumBucketsPerShelf, + "kMinSize must be at least 16."); + static_assert( + kMinSize >= sizeof(GenericFastSortedItem), + "kMinSize must be large enough to hold a FastSortedBucket item."); + static_assert((kMinSize & (kMinSize - 1)) == 0, + "kMinSize must be a power of two."); + + static_assert(kNumShelves <= 32, "kNumShelves cannot be larger than 32"); + + public: + /// Constexpr constructor. Callers must explicitly call `Init`. + constexpr explicit TlsfAllocator() { + size_t size = kMinSize; + size_t step = kMinSize / kNumBucketsPerShelf; + for (Shelf& shelf : shelves_) { + for (BucketType& bucket : shelf) { + size += step; + bucket.set_max_inner_size(size - 1); + } + step *= 2; + } + + // The largest bucket is unbounded. + BucketType& largest = shelves_[kNumShelves - 1][kNumBucketsPerShelf - 1]; + largest.set_max_inner_size(std::numeric_limits::max()); + } + + /// Non-constexpr constructor that automatically calls `Init`. + /// + /// @param[in] region Region of memory to use when satisfying allocation + /// requests. The region MUST be valid as an argument + /// to `BlockType::Init`. + explicit TlsfAllocator(ByteSpan region) : TlsfAllocator() { + Base::Init(region); + } + + private: + /// @copydoc BlockAllocator::ChooseBlock + BlockResult ChooseBlock(Layout layout) override; + + /// @copydoc BlockAllocator::ReserveBlock + void ReserveBlock(BlockType& block) override; + + /// @copydoc BlockAllocator::RecycleBlock + void RecycleBlock(BlockType& block) override; + + /// Returns the shelf and bucket indices for the bucket with the smallest + /// maximum inner size greater than the given size. + static TlsfIndices MapToIndices(size_t size); + + /// Updates the shelf and bucket bitmaps to reflect whether the given + /// `bucket`, which corresponds to the given `indices`, is empty. + void UpdateBitmaps(const TlsfIndices& indices, const BucketType& bucket); + + uint32_t shelf_bitmap_ = 0; + std::array bucket_bitmaps_; + std::array shelves_; + ForwardSortedBucket small_bucket_; +}; + +// Template method implementations. + +template +BlockResult +TlsfAllocator::ChooseBlock(Layout layout) { + BlockType* block = nullptr; + if (layout.size() < small_bucket_.max_inner_size()) { + block = small_bucket_.RemoveCompatible(layout); + if (block != nullptr) { + return BlockType::AllocFirst(std::move(block), layout); + } + } + TlsfIndices indices = MapToIndices(layout.size()); + while (indices.shelf < kNumShelves) { + // Use the bitmaps to find the next largest non-empty bucket. + uint16_t bucket_bitmap = + bucket_bitmaps_[indices.shelf] & (~0U << indices.bucket); + if (bucket_bitmap != 0) { + // There's at least one non-empty bucket on the current shelf whose + // blocks are at least as large as the requested size. + indices.bucket = cpp20::countr_zero(bucket_bitmap); + } else { + // The buckets for large enough blocks on this shelf are all empty. + // Move up to the first shelf with non-empty buckets and find the + // non-empty bucket with the smallest blocks. + uint32_t shelf_bitmap = shelf_bitmap_ & (~0U << (indices.shelf + 1)); + if (shelf_bitmap == 0) { + break; + } + indices.shelf = cpp20::countr_zero(shelf_bitmap); + indices.bucket = cpp20::countr_zero(bucket_bitmaps_[indices.shelf]); + } + + // Check if any blocks in the bucket satisfy the request. + FastSortedBucket& bucket = + shelves_[indices.shelf][indices.bucket]; + block = bucket.RemoveCompatible(layout); + if (block != nullptr) { + UpdateBitmaps(indices, bucket); + return BlockType::AllocFirst(std::move(block), layout); + } + + // Move to the next largest bucket. + indices.bucket++; + if (indices.bucket == kNumBucketsPerShelf) { + indices.shelf++; + indices.bucket = 0; + } + } + return BlockResult(nullptr, Status::NotFound()); +} + +template +void TlsfAllocator::ReserveBlock( + BlockType& block) { + if (block.InnerSize() <= sizeof(SortedItem)) { + std::ignore = small_bucket_.Remove(block); + return; + } + TlsfIndices indices = MapToIndices(block.InnerSize()); + FastSortedBucket& large_bucket = + shelves_[indices.shelf][indices.bucket]; + if (large_bucket.Remove(block)) { + UpdateBitmaps(indices, large_bucket); + } +} + +template +void TlsfAllocator::RecycleBlock( + BlockType& block) { + if (block.InnerSize() <= sizeof(SortedItem)) { + std::ignore = small_bucket_.Add(block); + return; + } + TlsfIndices indices = MapToIndices(block.InnerSize()); + FastSortedBucket& large_bucket = + shelves_[indices.shelf][indices.bucket]; + std::ignore = large_bucket.Add(block); + UpdateBitmaps(indices, large_bucket); +} + +template +TlsfIndices TlsfAllocator::MapToIndices( + size_t size) { + if (size <= kMinSize) { + return TlsfIndices{.shelf = 0, .bucket = 0}; + } + + // Most significant bit set determines the shelf. + size_t shelf = cpp20::countr_zero(cpp20::bit_floor(size)); + // Each shelf has 16 buckets, so next 4 bits determine the bucket. + auto bucket = static_cast((size >> (shelf - kBucketBits)) & 0xF); + + // Adjust for minimum size, and clamp to the valid range. + shelf -= cpp20::countr_zero(kMinSize); + if (shelf >= kNumShelves) { + shelf = kNumShelves - 1; + bucket = kNumBucketsPerShelf - 1; + } + return TlsfIndices{.shelf = static_cast(shelf), .bucket = bucket}; +} + +template +void TlsfAllocator::UpdateBitmaps( + const TlsfIndices& indices, const BucketType& bucket) { + uint16_t bucket_bitmap = 1U << indices.bucket; + uint32_t shelf_bitmap = 1U << indices.shelf; + + if (!bucket.empty()) { + bucket_bitmaps_[indices.shelf] |= bucket_bitmap; + shelf_bitmap_ |= shelf_bitmap; + return; + } + + bucket_bitmaps_[indices.shelf] &= ~bucket_bitmap; + if (bucket_bitmaps_[indices.shelf] == 0) { + shelf_bitmap_ &= ~shelf_bitmap; + } +} + +} // namespace pw::allocator diff --git a/pw_allocator/py/pw_allocator/benchmarks.py b/pw_allocator/py/pw_allocator/benchmarks.py index 901a35a87..5af797132 100644 --- a/pw_allocator/py/pw_allocator/benchmarks.py +++ b/pw_allocator/py/pw_allocator/benchmarks.py @@ -43,6 +43,7 @@ def _parse_args() -> argparse.Namespace: 'first_fit', 'last_fit', 'worst_fit', + 'tlsf', ] BY_ALLOC_COUNT = 'by allocation count' diff --git a/pw_allocator/size_report/BUILD.bazel b/pw_allocator/size_report/BUILD.bazel index 49cb69090..4b4da26f0 100644 --- a/pw_allocator/size_report/BUILD.bazel +++ b/pw_allocator/size_report/BUILD.bazel @@ -158,6 +158,15 @@ pw_cc_binary( ], ) +pw_cc_binary( + name = "tlsf_allocator", + srcs = ["tlsf_allocator.cc"], + deps = [ + "//pw_allocator:size_reporter", + "//pw_allocator:tlsf_allocator", + ], +) + pw_cc_binary( name = "tracking_allocator_no_metrics", srcs = ["tracking_allocator_no_metrics.cc"], diff --git a/pw_allocator/size_report/BUILD.gn b/pw_allocator/size_report/BUILD.gn index 9515004f8..72319f47f 100644 --- a/pw_allocator/size_report/BUILD.gn +++ b/pw_allocator/size_report/BUILD.gn @@ -141,6 +141,14 @@ pw_executable("synchronized_allocator_mutex") { ] } +pw_executable("tlsf_allocator") { + sources = [ "tlsf_allocator.cc" ] + deps = [ + "..:size_reporter", + "..:tlsf_allocator", + ] +} + pw_executable("tracking_allocator_all_metrics") { sources = [ "tracking_allocator_all_metrics.cc" ] deps = [ diff --git a/pw_allocator/size_report/tlsf_allocator.cc b/pw_allocator/size_report/tlsf_allocator.cc new file mode 100644 index 000000000..721ad5554 --- /dev/null +++ b/pw_allocator/size_report/tlsf_allocator.cc @@ -0,0 +1,27 @@ +// 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/tlsf_allocator.h" + +#include "pw_allocator/size_reporter.h" + +int main() { + pw::allocator::SizeReporter reporter; + reporter.SetBaseline(); + + pw::allocator::TlsfAllocator<> allocator(reporter.buffer()); + reporter.Measure(allocator); + + return 0; +} diff --git a/pw_allocator/tlsf_allocator_test.cc b/pw_allocator/tlsf_allocator_test.cc new file mode 100644 index 000000000..eea1f9a83 --- /dev/null +++ b/pw_allocator/tlsf_allocator_test.cc @@ -0,0 +1,123 @@ +// 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/tlsf_allocator.h" + +#include + +#include "pw_allocator/block_allocator_testing.h" +#include "pw_unit_test/framework.h" + +namespace { + +// Test fixtures. + +using ::pw::allocator::Layout; +using ::pw::allocator::test::Preallocation; +using BlockType = ::pw::allocator::TlsfBlock; +using TlsfAllocator = ::pw::allocator::TlsfAllocator; +using BlockAllocatorTest = + ::pw::allocator::test::BlockAllocatorTest; + +class TlsfAllocatorTest : public BlockAllocatorTest { + public: + TlsfAllocatorTest() : BlockAllocatorTest(allocator_) {} + + private: + TlsfAllocator allocator_; +}; + +// Unit tests. + +TEST_F(TlsfAllocatorTest, AutomaticallyInit) { + TlsfAllocator allocator(GetBytes()); + AutomaticallyInit(allocator); +} + +TEST_F(TlsfAllocatorTest, ExplicitlyInit) { + TlsfAllocator allocator; + ExplicitlyInit(allocator); +} + +TEST_F(TlsfAllocatorTest, GetCapacity) { GetCapacity(); } + +TEST_F(TlsfAllocatorTest, AllocateLarge) { AllocateLarge(); } + +TEST_F(TlsfAllocatorTest, AllocateSmall) { AllocateSmall(); } + +TEST_F(TlsfAllocatorTest, AllocateLargeAlignment) { AllocateLargeAlignment(); } + +TEST_F(TlsfAllocatorTest, AllocateAlignmentFailure) { + AllocateAlignmentFailure(); +} + +TEST_F(TlsfAllocatorTest, AllocatesBestCompatible) { + auto& allocator = GetAllocator({ + {kLargeOuterSize, Preallocation::kFree}, + {kSmallerOuterSize, Preallocation::kUsed}, + {kSmallOuterSize, Preallocation::kFree}, + {kSmallerOuterSize, Preallocation::kUsed}, + {kLargerOuterSize, Preallocation::kFree}, + {Preallocation::kSizeRemaining, Preallocation::kUsed}, + }); + + void* ptr1 = allocator.Allocate(Layout(kSmallInnerSize, 1)); + EXPECT_LT(Fetch(1), ptr1); + EXPECT_LT(ptr1, Fetch(3)); + + void* ptr2 = allocator.Allocate(Layout(kSmallInnerSize, 1)); + EXPECT_LT(ptr2, Fetch(1)); + + // A second small block fits in the leftovers of the first "Large" block. + void* ptr3 = allocator.Allocate(Layout(kSmallInnerSize, 1)); + EXPECT_LT(ptr3, Fetch(1)); + + allocator.Deallocate(ptr1); + allocator.Deallocate(ptr2); + allocator.Deallocate(ptr3); +} + +TEST_F(TlsfAllocatorTest, DeallocateNull) { DeallocateNull(); } + +TEST_F(TlsfAllocatorTest, DeallocateShuffled) { DeallocateShuffled(); } + +TEST_F(TlsfAllocatorTest, IterateOverBlocks) { IterateOverBlocks(); } + +TEST_F(TlsfAllocatorTest, ResizeNull) { ResizeNull(); } + +TEST_F(TlsfAllocatorTest, ResizeLargeSame) { ResizeLargeSame(); } + +TEST_F(TlsfAllocatorTest, ResizeLargeSmaller) { ResizeLargeSmaller(); } + +TEST_F(TlsfAllocatorTest, ResizeLargeLarger) { ResizeLargeLarger(); } + +TEST_F(TlsfAllocatorTest, ResizeLargeLargerFailure) { + ResizeLargeLargerFailure(); +} + +TEST_F(TlsfAllocatorTest, ResizeSmallSame) { ResizeSmallSame(); } + +TEST_F(TlsfAllocatorTest, ResizeSmallSmaller) { ResizeSmallSmaller(); } + +TEST_F(TlsfAllocatorTest, ResizeSmallLarger) { ResizeSmallLarger(); } + +TEST_F(TlsfAllocatorTest, ResizeSmallLargerFailure) { + ResizeSmallLargerFailure(); +} + +TEST_F(TlsfAllocatorTest, MeasureFragmentation) { MeasureFragmentation(); } + +TEST_F(TlsfAllocatorTest, PoisonPeriodically) { PoisonPeriodically(); } + +} // namespace