diff --git a/docs/BUILD.gn b/docs/BUILD.gn index c30db8cd6..c653ea2b5 100644 --- a/docs/BUILD.gn +++ b/docs/BUILD.gn @@ -173,6 +173,9 @@ _doxygen_input_files = [ # keep-sorted: start "$dir_pw_allocator/block/public/pw_allocator/block/iterable.h", "$dir_pw_allocator/block/public/pw_allocator/block/poisonable.h", "$dir_pw_allocator/block/public/pw_allocator/block/result.h", + "$dir_pw_allocator/block/public/pw_allocator/block/small_alignable_block.h", + "$dir_pw_allocator/block/public/pw_allocator/block/small_block.h", + "$dir_pw_allocator/block/public/pw_allocator/block/tiny_block.h", "$dir_pw_allocator/block/public/pw_allocator/block/with_layout.h", "$dir_pw_allocator/bucket/public/pw_allocator/bucket/base.h", "$dir_pw_allocator/bucket/public/pw_allocator/bucket/fast_sorted.h", diff --git a/pw_allocator/BUILD.gn b/pw_allocator/BUILD.gn index a9975a51a..2bf037b4e 100644 --- a/pw_allocator/BUILD.gn +++ b/pw_allocator/BUILD.gn @@ -451,7 +451,6 @@ pw_source_set("block_allocator_testing") { deps = [ "$dir_pw_bytes:alignment", "$dir_pw_third_party/fuchsia:stdcompat", - "block:detailed_block", dir_pw_assert, dir_pw_status, ] @@ -767,6 +766,21 @@ pw_size_diff("blocks_size_report") { base = "size_report:base" label = "DetailedBlock" }, + { + target = "size_report:small_block_basic" + base = "size_report:base" + label = "SmallBlock" + }, + { + target = "size_report:small_alignable_block" + base = "size_report:base" + label = "SmallAlignableBlock" + }, + { + target = "size_report:tiny_block" + base = "size_report:base" + label = "TinyBlock" + }, ] } @@ -774,17 +788,17 @@ pw_size_diff("hardening_size_report") { title = "Size impact of various levels of pw_allocator hardening" binaries = [ { - target = "size_report:detailed_block_basic" + target = "size_report:small_block_basic" base = "size_report:base" label = "DetailedBlock with basic assertions enabled" }, { - target = "size_report:detailed_block_robust" + target = "size_report:small_block_robust" base = "size_report:base" label = "DetailedBlock with robust assertions enabled" }, { - target = "size_report:detailed_block_debug" + target = "size_report:small_block_debug" base = "size_report:base" label = "DetailedBlock with debug assertions enabled" }, diff --git a/pw_allocator/api.rst b/pw_allocator/api.rst index c5605e1bb..de3e1228c 100644 --- a/pw_allocator/api.rst +++ b/pw_allocator/api.rst @@ -271,49 +271,49 @@ block to a derived type. .. TODO(b/378549332): Add a diagram of mix-in relationships. -.. _module-pw_allocator-api-basic-block: +.. _module-pw_allocator-api-basic_block: BasicBlock ---------- .. doxygenclass:: pw::allocator::BasicBlock :members: -.. _module-pw_allocator-api-contiguous-block: +.. _module-pw_allocator-api-contiguous_block: ContiguousBlock --------------- .. doxygenclass:: pw::allocator::ContiguousBlock :members: -.. _module-pw_allocator-api-allocatable-block: +.. _module-pw_allocator-api-allocatable_block: AllocatableBlock ---------------- .. doxygenclass:: pw::allocator::AllocatableBlock :members: -.. _module-pw_allocator-api-alignable-block: +.. _module-pw_allocator-api-alignable_block: AlignableBlock -------------- .. doxygenclass:: pw::allocator::AlignableBlock :members: -.. _module-pw_allocator-api-block-with-layout: +.. _module-pw_allocator-api-block_with_layout: BlockWithLayout --------------- .. doxygenclass:: pw::allocator::BlockWithLayout :members: -.. _module-pw_allocator-api-iterable-block: +.. _module-pw_allocator-api-iterable_block: IterableBlock -------------------- .. doxygenclass:: pw::allocator::IterableBlock :members: -.. _module-pw_allocator-api-poisonable-block: +.. _module-pw_allocator-api-poisonable_block: PoisonableBlock --------------- @@ -334,6 +334,40 @@ Block implementations The following combine block mix-ins and provide both the methods they require as well as a concrete representation of the data those methods need. +.. _module-pw_allocator-api-small_block: + +SmallBlock +---------- +This implementation includes just enough mix-ins for fixed-alignment +allocations. + +.. doxygenclass:: pw::allocator::SmallBlock + :members: + +.. _module-pw_allocator-api-small_alignable_block: + +SmallAlignableBlock +------------------- +This implementation includes just enough mix-ins for variable-alignment +allocations. + +.. doxygenclass:: pw::allocator::SmallAlignableBlock + :members: + +.. _module-pw_allocator-api-tiny_block: + +TinyBlock +--------- +This implementation is similar to :ref:`module-pw_allocator-api-small_block`, +but packs its information into just 4 bytes of overhead per allocation. This +constrains both its miniumum and maximum allocatable sizes, and incurs small +code size and performance costs for packing and unpacking header information. + +.. doxygenclass:: pw::allocator::TinyBlock + :members: + +.. _module-pw_allocator-api-detailed_block: + DetailedBlock ------------- This implementation includes all block mix-ins. This makes it very flexible at diff --git a/pw_allocator/block/BUILD.bazel b/pw_allocator/block/BUILD.bazel index 0c7046b52..2a680abf5 100644 --- a/pw_allocator/block/BUILD.bazel +++ b/pw_allocator/block/BUILD.bazel @@ -30,6 +30,8 @@ cc_library( ], ) +# Block mixins + cc_library( name = "alignable", hdrs = ["public/pw_allocator/block/alignable.h"], @@ -121,6 +123,8 @@ cc_library( ], ) +# Block implementations + cc_library( name = "detailed_block", hdrs = ["public/pw_allocator/block/detailed_block.h"], @@ -141,6 +145,46 @@ cc_library( ], ) +cc_library( + name = "small_block_base", + hdrs = ["public/pw_allocator/block/small_block_base.h"], + strip_include_prefix = "public", + deps = [ + ":allocatable", + ":basic", + ":contiguous", + ":iterable", + "//pw_allocator/bucket:fast_sorted", + "//pw_bytes", + ], +) + +cc_library( + name = "small_block", + hdrs = ["public/pw_allocator/block/small_block.h"], + strip_include_prefix = "public", + deps = [":small_block_base"], +) + +cc_library( + name = "small_alignable_block", + hdrs = ["public/pw_allocator/block/small_alignable_block.h"], + strip_include_prefix = "public", + deps = [ + ":alignable", + ":small_block_base", + ], +) + +cc_library( + name = "tiny_block", + hdrs = ["public/pw_allocator/block/tiny_block.h"], + strip_include_prefix = "public", + deps = [":small_block_base"], +) + +# Testing + cc_library( name = "testing", testonly = True, @@ -176,12 +220,37 @@ pw_cc_test( deps = [ ":detailed_block", ":testing", - "//pw_assert", - "//pw_bytes", - "//pw_bytes:alignment", - "//pw_span", - "//pw_status", - "//third_party/fuchsia:stdcompat", + "//pw_unit_test", + ], +) + +pw_cc_test( + name = "small_block_test", + srcs = ["small_block_test.cc"], + deps = [ + ":small_block", + ":testing", + "//pw_unit_test", + ], +) + +pw_cc_test( + name = "small_alignable_block_test", + srcs = ["small_alignable_block_test.cc"], + deps = [ + ":small_alignable_block", + ":testing", + "//pw_unit_test", + ], +) + +pw_cc_test( + name = "tiny_block_test", + srcs = ["tiny_block_test.cc"], + deps = [ + ":testing", + ":tiny_block", + "//pw_unit_test", ], ) @@ -196,6 +265,9 @@ filegroup( "public/pw_allocator/block/iterable.h", "public/pw_allocator/block/poisonable.h", "public/pw_allocator/block/result.h", + "public/pw_allocator/block/small_alignable_block.h", + "public/pw_allocator/block/small_block.h", + "public/pw_allocator/block/tiny_block.h", "public/pw_allocator/block/with_layout.h", ], ) diff --git a/pw_allocator/block/BUILD.gn b/pw_allocator/block/BUILD.gn index 6c3d20cb7..30ec6a222 100644 --- a/pw_allocator/block/BUILD.gn +++ b/pw_allocator/block/BUILD.gn @@ -27,11 +27,13 @@ pw_source_set("result") { public = [ "public/pw_allocator/block/result.h" ] public_deps = [ "$dir_pw_allocator:hardening", - "$dir_pw_assert", - "$dir_pw_status", + dir_pw_assert, + dir_pw_status, ] } +# Block mixins + pw_source_set("alignable") { public_configs = [ ":public_include_path" ] public = [ "public/pw_allocator/block/alignable.h" ] @@ -40,8 +42,8 @@ pw_source_set("alignable") { "$dir_pw_allocator:deallocator", "$dir_pw_allocator:hardening", "$dir_pw_bytes:alignment", - "$dir_pw_status", "$dir_pw_third_party/fuchsia:stdcompat", + dir_pw_status, ] } @@ -54,7 +56,7 @@ pw_source_set("allocatable") { "$dir_pw_allocator:deallocator", "$dir_pw_allocator:hardening", "$dir_pw_bytes:alignment", - "$dir_pw_status", + dir_pw_status, ] } @@ -64,9 +66,9 @@ pw_source_set("basic") { public_deps = [ "$dir_pw_allocator:hardening", "$dir_pw_bytes:alignment", - "$dir_pw_result", - "$dir_pw_status", "$dir_pw_third_party/fuchsia:stdcompat", + dir_pw_result, + dir_pw_status, ] sources = [ "basic.cc" ] deps = [ dir_pw_assert ] @@ -78,8 +80,8 @@ pw_source_set("contiguous") { public_deps = [ ":basic", "$dir_pw_allocator:hardening", - "$dir_pw_bytes", "$dir_pw_third_party/fuchsia:stdcompat", + dir_pw_bytes, ] sources = [ "contiguous.cc" ] deps = [ dir_pw_assert ] @@ -114,6 +116,8 @@ pw_source_set("with_layout") { ] } +# Block implementations + pw_source_set("detailed_block") { public_configs = [ ":public_include_path" ] public = [ "public/pw_allocator/block/detailed_block.h" ] @@ -133,6 +137,42 @@ pw_source_set("detailed_block") { ] } +pw_source_set("small_block_base") { + public_configs = [ ":public_include_path" ] + public = [ "public/pw_allocator/block/small_block_base.h" ] + public_deps = [ + ":allocatable", + ":basic", + ":contiguous", + ":iterable", + "$dir_pw_allocator/bucket:fast_sorted", + dir_pw_bytes, + ] +} + +pw_source_set("small_block") { + public_configs = [ ":public_include_path" ] + public = [ "public/pw_allocator/block/small_block.h" ] + public_deps = [ ":small_block_base" ] +} + +pw_source_set("small_alignable_block") { + public_configs = [ ":public_include_path" ] + public = [ "public/pw_allocator/block/small_alignable_block.h" ] + public_deps = [ + ":alignable", + ":small_block_base", + ] +} + +pw_source_set("tiny_block") { + public_configs = [ ":public_include_path" ] + public = [ "public/pw_allocator/block/tiny_block.h" ] + public_deps = [ ":small_block_base" ] +} + +# Testing + pw_source_set("testing") { public = [ "public/pw_allocator/block/testing.h", @@ -171,9 +211,36 @@ pw_test("detailed_block_test") { sources = [ "detailed_block_test.cc" ] } +pw_test("small_block_test") { + sources = [ "small_block_test.cc" ] + deps = [ + ":small_block", + ":testing", + ] +} + +pw_test("small_alignable_block_test") { + sources = [ "small_alignable_block_test.cc" ] + deps = [ + ":small_alignable_block", + ":testing", + ] +} + +pw_test("tiny_block_test") { + sources = [ "tiny_block_test.cc" ] + deps = [ + ":testing", + ":tiny_block", + ] +} + pw_test_group("tests") { tests = [ ":detailed_block_test", ":result_test", + ":small_alignable_block_test", + ":small_block_test", + ":tiny_block_test", ] } diff --git a/pw_allocator/block/CMakeLists.txt b/pw_allocator/block/CMakeLists.txt index 8ea8f230d..c75197262 100644 --- a/pw_allocator/block/CMakeLists.txt +++ b/pw_allocator/block/CMakeLists.txt @@ -25,6 +25,8 @@ pw_add_library(pw_allocator.block.result INTERFACE pw_status ) +# Block mixins + pw_add_library(pw_allocator.block.alignable INTERFACE HEADERS public/pw_allocator/block/alignable.h @@ -122,6 +124,8 @@ pw_add_library(pw_allocator.block.with_layout INTERFACE pw_allocator.hardening ) +# Block implementations + pw_add_library(pw_allocator.block.detailed_block INTERFACE HEADERS public/pw_allocator/block/detailed_block.h @@ -142,6 +146,8 @@ pw_add_library(pw_allocator.block.detailed_block INTERFACE pw_status ) +# Testing + pw_add_library(pw_allocator.block.testing INTERFACE HEADERS public/pw_allocator/block/testing.h diff --git a/pw_allocator/block/basic.cc b/pw_allocator/block/basic.cc index 6d944adc4..559a3af23 100644 --- a/pw_allocator/block/basic.cc +++ b/pw_allocator/block/basic.cc @@ -22,9 +22,11 @@ namespace pw::allocator::internal { // operation caused the corruption in the methods below. void CheckMisaligned(const void* block, bool is_aligned) { - PW_CHECK(is_aligned, - "A block (%p) is invalid: it is not properly aligned.", - block); + if constexpr (Hardening::kIncludesDebugChecks) { + PW_CHECK(is_aligned, + "A block (%p) is invalid: it is not properly aligned.", + block); + } } } // namespace pw::allocator::internal diff --git a/pw_allocator/block/contiguous.cc b/pw_allocator/block/contiguous.cc index 85f2eb335..a47b4e82d 100644 --- a/pw_allocator/block/contiguous.cc +++ b/pw_allocator/block/contiguous.cc @@ -24,45 +24,55 @@ namespace pw::allocator::internal { void CheckNextMisaligned(const void* block, const void* next, bool next_is_aligned) { - PW_CHECK(next_is_aligned, - "A block (%p) is corrupted: it has a 'next' field (%p) that is not " - "properly aligned.", - block, - next); + if constexpr (Hardening::kIncludesDebugChecks) { + PW_CHECK( + next_is_aligned, + "A block (%p) is corrupted: it has a 'next' field (%p) that is not " + "properly aligned.", + block, + next); + } } void CheckNextPrevMismatched(const void* block, const void* next, const void* next_prev, bool next_prev_matches) { - PW_CHECK(next_prev_matches, - "A block (%p) is corrupted: its 'next' field (%p) has a 'prev' " - "field (%p) that does not match the block.", - block, - next, - next_prev); + if constexpr (Hardening::kIncludesDebugChecks) { + PW_CHECK(next_prev_matches, + "A block (%p) is corrupted: its 'next' field (%p) has a 'prev' " + "field (%p) that does not match the block.", + block, + next, + next_prev); + } } void CheckPrevMisaligned(const void* block, const void* prev, bool prev_is_aligned) { - PW_CHECK(prev_is_aligned, - "A block (%p) is corrupted: it has a 'prev' field (%p) that is not " - "properly aligned.", - block, - prev); + if constexpr (Hardening::kIncludesDebugChecks) { + PW_CHECK( + prev_is_aligned, + "A block (%p) is corrupted: it has a 'prev' field (%p) that is not " + "properly aligned.", + block, + prev); + } } void CheckPrevNextMismatched(const void* block, const void* prev, const void* prev_next, bool prev_next_matches) { - PW_CHECK(prev_next_matches, - "A block (%p) is corrupted: its 'prev' field (%p) has a 'next' " - "field (%p) that does not match the block.", - block, - prev, - prev_next); + if constexpr (Hardening::kIncludesDebugChecks) { + PW_CHECK(prev_next_matches, + "A block (%p) is corrupted: its 'prev' field (%p) has a 'next' " + "field (%p) that does not match the block.", + block, + prev, + prev_next); + } } } // namespace pw::allocator::internal diff --git a/pw_allocator/block/poisonable.cc b/pw_allocator/block/poisonable.cc index 03fd42af6..63daae125 100644 --- a/pw_allocator/block/poisonable.cc +++ b/pw_allocator/block/poisonable.cc @@ -22,15 +22,19 @@ namespace pw::allocator::internal { // operation caused the corruption in the methods below. void CheckPoisonedWhileInUse(const void* block, bool is_free) { - PW_CHECK(is_free, - "A block (%p) is corrupted: it is marked as poisoned while in use", - block); + if constexpr (Hardening::kIncludesDebugChecks) { + PW_CHECK(is_free, + "A block (%p) is corrupted: it is marked as poisoned while in use", + block); + } } void CheckPoisonCorrupted(const void* block, bool pattern_is_intact) { - PW_CHECK(pattern_is_intact, - "A block (%p) is corrupted: its has been modified while free", - block); + if constexpr (Hardening::kIncludesDebugChecks) { + PW_CHECK(pattern_is_intact, + "A block (%p) is corrupted: its has been modified while free", + block); + } } } // namespace pw::allocator::internal diff --git a/pw_allocator/block/public/pw_allocator/block/allocatable.h b/pw_allocator/block/public/pw_allocator/block/allocatable.h index fe3979082..a2284fc74 100644 --- a/pw_allocator/block/public/pw_allocator/block/allocatable.h +++ b/pw_allocator/block/public/pw_allocator/block/allocatable.h @@ -343,6 +343,7 @@ constexpr BlockResult AllocatableBlock::DoAllocLast( if (extra >= Derived::kMinOuterSize) { // Split the large padding off the front. block = block->DoSplitLast(layout.size()); + prev = block->Prev(); result = BlockResult(block, BlockResultPrev::kSplitNew); } else if (extra != 0 && prev != nullptr) { diff --git a/pw_allocator/block/public/pw_allocator/block/small_alignable_block.h b/pw_allocator/block/public/pw_allocator/block/small_alignable_block.h new file mode 100644 index 000000000..e0ce3dc8a --- /dev/null +++ b/pw_allocator/block/public/pw_allocator/block/small_alignable_block.h @@ -0,0 +1,55 @@ +// Copyright 2025 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 "pw_allocator/block/alignable.h" +#include "pw_allocator/block/small_block_base.h" + +namespace pw::allocator { + +/// An alignable version of `SmallBlock`. +class SmallAlignableBlock + : public SmallBlockBase, + public AlignableBlock { + private: + friend SmallBlockBase; + constexpr explicit SmallAlignableBlock(size_t outer_size) + : SmallBlockBase(outer_size) {} + + using Allocatable = AllocatableBlock; + friend Allocatable; + + // `Alignable` overrides. + using Alignable = AlignableBlock; + friend Alignable; + + constexpr StatusWithSize DoCanAlloc(Layout layout) const { + return Alignable::DoCanAlloc(layout); + } + + static constexpr BlockResult DoAllocFirst( + SmallAlignableBlock*&& block, Layout layout) { + return Alignable::DoAllocFirst(std::move(block), layout); + } + + static constexpr BlockResult DoAllocLast( + SmallAlignableBlock*&& block, Layout layout) { + return Alignable::DoAllocLast(std::move(block), layout); + } +}; + +} // namespace pw::allocator diff --git a/pw_allocator/block/public/pw_allocator/block/small_block.h b/pw_allocator/block/public/pw_allocator/block/small_block.h new file mode 100644 index 000000000..33ddba0a5 --- /dev/null +++ b/pw_allocator/block/public/pw_allocator/block/small_block.h @@ -0,0 +1,35 @@ +// Copyright 2025 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 "pw_allocator/block/small_block_base.h" + +namespace pw::allocator { + +/// A compact block implementation. +/// +/// Like its base class, this block is allocatable with a fixed alignment. It is +/// fairly compact in terms of code size, and each block uses 8 bytes of +/// overhead. It can address up to 2^32 bytes of memory. +class SmallBlock : public SmallBlockBase { + private: + friend SmallBlockBase; + constexpr explicit SmallBlock(size_t outer_size) + : SmallBlockBase(outer_size) {} +}; + +} // namespace pw::allocator diff --git a/pw_allocator/block/public/pw_allocator/block/small_block_base.h b/pw_allocator/block/public/pw_allocator/block/small_block_base.h new file mode 100644 index 000000000..5ac1b4b91 --- /dev/null +++ b/pw_allocator/block/public/pw_allocator/block/small_block_base.h @@ -0,0 +1,113 @@ +// Copyright 2025 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 "pw_allocator/block/allocatable.h" +#include "pw_allocator/block/basic.h" +#include "pw_allocator/block/contiguous.h" +#include "pw_allocator/block/iterable.h" +#include "pw_allocator/bucket/fast_sorted.h" +#include "pw_bytes/span.h" + +namespace pw::allocator { + +/// CRTP-style base class for block implementations with limited code size and +/// memory overhead. +/// +/// This implementation encodes its metadata in a header consisting of only two +/// firelds of type `T`. It implements only the block mix-ins necessary to be +/// used with a `BlockAllocator`. +/// +/// @tparam Derived Block implementation derived from this class. +/// @tparam T Field type used to store metadara. +/// @tparam kShift Encoded sizes are left shifted by this amount to +/// produce actual sizes. A larger value allows a larger +/// maximum addressable size, at the cost of a larger +/// minimum allocatable size. +template +struct SmallBlockBase : public BasicBlock, + public ContiguousBlock, + public IterableBlock, + public AllocatableBlock { + protected: + constexpr explicit SmallBlockBase(size_t outer_size) + : prev_and_free_(1U), + next_and_last_(static_cast(outer_size >> kShift) | 1U) {} + + private: + using Basic = BasicBlock; + using Contiguous = ContiguousBlock; + using Allocatable = AllocatableBlock; + + // `Basic` required methods. + friend Basic; + + static constexpr size_t DefaultAlignment() { + return std::max(alignof(GenericFastSortedItem), size_t(2u) << kShift); + } + static constexpr size_t BlockOverhead() { return sizeof(Derived); } + static constexpr size_t MaxAddressableSize() { + return size_t(std::numeric_limits::max()) << kShift; + } + static inline Derived* AsBlock(ByteSpan bytes) { + return ::new (bytes.data()) Derived(bytes.size()); + } + static constexpr size_t MinInnerSize() { return std::max(2U, 1U << kShift); } + static constexpr size_t ReservedWhenFree() { + return sizeof(GenericFastSortedItem); + } + size_t OuterSizeUnchecked() const { return (next_and_last_ & ~1U) << kShift; } + + // `Basic` overrides. + bool DoCheckInvariants(bool strict) const { + return Basic::DoCheckInvariants(strict) && + Contiguous::DoCheckInvariants(strict); + } + + // `Contiguous` required methods. + friend Contiguous; + + constexpr bool IsLastUnchecked() const { return (next_and_last_ & 1U) != 0; } + + constexpr void SetNext(size_t outer_size, Derived* next) { + auto packed_size = static_cast(outer_size >> kShift); + if (next == nullptr) { + next_and_last_ = packed_size | 1U; + } else { + next_and_last_ = packed_size; + next->prev_and_free_ = packed_size | (next->prev_and_free_ & 1U); + } + } + + constexpr size_t PrevOuterSizeUnchecked() const { + return (prev_and_free_ & ~1U) << kShift; + } + + // `Allocatable` required methods. + friend Allocatable; + + constexpr bool IsFreeUnchecked() const { return (prev_and_free_ & 1U) != 0; } + + constexpr void SetFree(bool is_free) { + prev_and_free_ = (prev_and_free_ & ~1U) | (is_free ? 1U : 0U); + } + + T prev_and_free_; + T next_and_last_; +}; + +} // namespace pw::allocator diff --git a/pw_allocator/block/public/pw_allocator/block/tiny_block.h b/pw_allocator/block/public/pw_allocator/block/tiny_block.h new file mode 100644 index 000000000..159f5df36 --- /dev/null +++ b/pw_allocator/block/public/pw_allocator/block/tiny_block.h @@ -0,0 +1,35 @@ +// Copyright 2025 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 "pw_allocator/block/small_block_base.h" + +namespace pw::allocator { + +/// A block implementation with only 4 bytes of overhead. +/// +/// Like its base class, this block is allocatable with a fixed alignment. This +/// class's code size is slightly larger than that of `SmallBlock`, and it can +/// address only 2^18 - 4 bytes of memory. +class TinyBlock : public SmallBlockBase { + private: + friend SmallBlockBase; + constexpr explicit TinyBlock(size_t outer_size) + : SmallBlockBase(outer_size) {} +}; + +} // namespace pw::allocator diff --git a/pw_allocator/block/small_alignable_block_test.cc b/pw_allocator/block/small_alignable_block_test.cc new file mode 100644 index 000000000..7e3932b21 --- /dev/null +++ b/pw_allocator/block/small_alignable_block_test.cc @@ -0,0 +1,31 @@ +// Copyright 2025 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/block/small_alignable_block.h" + +#include + +#include "pw_allocator/block/unit_tests.h" + +namespace { + +using ::pw::allocator::SmallAlignableBlock; +using SmallAlignableBlockTest = + ::pw::allocator::test::BlockTest; + +PW_ALLOCATOR_BASIC_BLOCK_TESTS(SmallAlignableBlockTest) +PW_ALLOCATOR_ALLOCATABLE_BLOCK_TESTS(SmallAlignableBlockTest) +PW_ALLOCATOR_ALIGNABLE_BLOCK_TESTS(SmallAlignableBlockTest) + +} // namespace diff --git a/pw_allocator/block/small_block_test.cc b/pw_allocator/block/small_block_test.cc new file mode 100644 index 000000000..9afe343ca --- /dev/null +++ b/pw_allocator/block/small_block_test.cc @@ -0,0 +1,29 @@ +// Copyright 2025 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/block/small_block.h" + +#include + +#include "pw_allocator/block/unit_tests.h" + +namespace { + +using ::pw::allocator::SmallBlock; +using SmallBlockTest = ::pw::allocator::test::BlockTest; + +PW_ALLOCATOR_BASIC_BLOCK_TESTS(SmallBlockTest) +PW_ALLOCATOR_ALLOCATABLE_BLOCK_TESTS(SmallBlockTest) + +} // namespace diff --git a/pw_allocator/block/tiny_block_test.cc b/pw_allocator/block/tiny_block_test.cc new file mode 100644 index 000000000..8dcde11d5 --- /dev/null +++ b/pw_allocator/block/tiny_block_test.cc @@ -0,0 +1,29 @@ +// Copyright 2025 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/block/tiny_block.h" + +#include + +#include "pw_allocator/block/unit_tests.h" + +namespace { + +using ::pw::allocator::TinyBlock; +using TinyBlockTest = ::pw::allocator::test::BlockTest; + +PW_ALLOCATOR_BASIC_BLOCK_TESTS(TinyBlockTest) +PW_ALLOCATOR_ALLOCATABLE_BLOCK_TESTS(TinyBlockTest) + +} // namespace diff --git a/pw_allocator/design.rst b/pw_allocator/design.rst index 6aca21337..4515a57a8 100644 --- a/pw_allocator/design.rst +++ b/pw_allocator/design.rst @@ -102,27 +102,88 @@ one or more of the block mix-ins. Each block mix-in provides a specific set of features, allowing block implementers to include only what they need. Features provided by these block mix-ins include: -- A :ref:`module-pw_allocator-api-basic-block` can retrieve the memory that +- A :ref:`module-pw_allocator-api-basic_block` can retrieve the memory that makes up its usable space and its size. -- A :ref:`module-pw_allocator-api-contiguous-block` knows the blocks that are +- A :ref:`module-pw_allocator-api-contiguous_block` knows the blocks that are adjacent to it in memory. It can merge with neighboring blocks and split itself into smaller sub-blocks. -- An :ref:`module-pw_allocator-api-allocatable-block` knows when it is free or +- An :ref:`module-pw_allocator-api-allocatable_block` knows when it is free or in-use. It can allocate new blocks from either the beginning or end of its usable space when free. When in-use, it can be freed and merged with neighboring blocks that are free. This ensures that free blocks are only ever adjacent to blocks in use, and vice versa. -- An :ref:`module-pw_allocator-api-alignable-block` can additionally allocate +- An :ref:`module-pw_allocator-api-alignable_block` can additionally allocate blocks from either end at specified alignment boundaries. -- A :ref:`module-pw_allocator-api-block-with-layout` can retrieve the layout +- A :ref:`module-pw_allocator-api-block_with_layout` can retrieve the layout used to allocate it, even if the block itself is larger due to alignment or padding. -- The :ref:`module-pw_allocator-api-iterable-block` type provides iterators +- The :ref:`module-pw_allocator-api-iterable_block` type provides iterators and ranges that can be used to iterate over a sequence of blocks. -- A :ref:`module-pw_allocator-api-poisonable-block` can fill its usable space +- A :ref:`module-pw_allocator-api-poisonable_block` can fill its usable space with a pattern when freed. This pattern can be checked on a subsequent allocation to detect if the memory was illegally modified while free. +You can use these mix-ins to implement your own block type, or use one of the +implementations provided by Pigweed. Each of provided block types implements +some or all of the mix-ins: + +.. list-table:: + :header-rows: 1 + + * - Mix-in + - BuddyBlock + - :ref:`module-pw_allocator-api-tiny_block` + - :ref:`module-pw_allocator-api-small_block` + - :ref:`module-pw_allocator-api-small_alignable_block` + - :ref:`module-pw_allocator-api-detailed_block` + * - :ref:`module-pw_allocator-api-basic_block` + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + * - :ref:`module-pw_allocator-api-contiguous_block` + - + - ✓ + - ✓ + - ✓ + - ✓ + * - :ref:`module-pw_allocator-api-iterable_block` + - + - ✓ + - ✓ + - ✓ + - ✓ + * - :ref:`module-pw_allocator-api-allocatable_block` + - + - ✓ + - ✓ + - ✓ + - ✓ + * - :ref:`module-pw_allocator-api-alignable_block` + - + - + - + - ✓ + - ✓ + * - :ref:`module-pw_allocator-api-poisonable_block` + - + - + - + - + - ✓ + * - :ref:`module-pw_allocator-api-block_with_layout` + - + - + - + - + - ✓ + +.. note:: + ``BuddyBlock`` is a specialized implementation used by + :ref:`module-pw_allocator-api-buddy_allocator`. It is not general enough to + be used with a generic :ref:`module-pw_allocator-api-block_allocator`. + In addition to poisoning, blocks validate their metadata against their neighbors on each allocation and deallocation. A block can fail to be validated if it or its neighbors have had their headers overwritten. In this case, it's unsafe to @@ -150,7 +211,7 @@ For example, a block allocator that uses a "best-fit" strategy needs to be able to efficiently search free blocks by usable size in order to find the smallest candidate that could satisfy the request. -The :ref:`module-pw_allocator-api-basic-block` mix-in requires blocks to specify +The :ref:`module-pw_allocator-api-basic_block` mix-in requires blocks to specify both a ``MinInnerSize`` and ``DefaultAlignment``. Together these ensure that the usable space of free blocks can be treated as intrusive items for containers. The bucket classes that derive from :ref:`module-pw_allocator-api-bucket_base` @@ -171,7 +232,7 @@ For example, a block allocator that uses a "best-fit" strategy needs to be able to efficiently search free blocks by usable size in order to find the smallest candidate that could satisfy the request. -The :ref:`module-pw_allocator-api-basic-block` mix-in requires blocks to specify +The :ref:`module-pw_allocator-api-basic_block` mix-in requires blocks to specify both a ``MinInnerSize`` and ``DefaultAlignment``. Together these ensure that the usable space of free blocks can be treated as intrusive items for containers. The :ref:`module-pw_allocator-api-bucket` provide such containers to store and diff --git a/pw_allocator/guide.rst b/pw_allocator/guide.rst index a8f97d1c3..cdd9b898a 100644 --- a/pw_allocator/guide.rst +++ b/pw_allocator/guide.rst @@ -95,7 +95,7 @@ Module configuration options include: - :ref:`module-pw_allocator-config-block_poison_interval` determines how frequently blocks that implemented the - :ref:`module-pw_allocator-api-poisonable-block` mix-in should apply the poison + :ref:`module-pw_allocator-api-poisonable_block` mix-in should apply the poison pattern on deallocation. - :ref:`module-pw_allocator-config-hardening` allows you to set how many validation checks are enabled. Additional checks can detect more errors at the @@ -446,7 +446,7 @@ will check the integrity of the block header and assert if it has been modified. Additionally, you can enable poisoning to detect additional memory corruptions such as use-after-frees. The :ref:`module-pw_allocator-module-configuration` for ``pw_allocator`` includes the ``PW_ALLOCATOR_BLOCK_POISON_INTERVAL`` option. If -a block derives from :ref:`module-pw_allocator-api-poisonable-block`, the +a block derives from :ref:`module-pw_allocator-api-poisonable_block`, the allocator will "poison" every N-th block it frees. Allocators "poison" blocks by writing a set pattern to the usable memory, and later check on allocation that the pattern is intact. If it is not, something has illegally modified diff --git a/pw_allocator/size_report/BUILD.bazel b/pw_allocator/size_report/BUILD.bazel index ab340aa3f..413b48c2e 100644 --- a/pw_allocator/size_report/BUILD.bazel +++ b/pw_allocator/size_report/BUILD.bazel @@ -40,8 +40,7 @@ cc_library( deps = [ "//pw_allocator:allocator", "//pw_allocator:block_allocator", - "//pw_allocator/block:detailed_block", - "//pw_allocator/bucket:fast_sorted", + "//pw_allocator/block:small_block", "//pw_bloat:bloat_this_binary", "//pw_bytes", ], @@ -63,35 +62,55 @@ pw_cc_size_binary( # Block binaries to be measured for code size. -_detailed_block_deps = [ +pw_cc_size_binary( + name = "detailed_block", + srcs = ["detailed_block.cc"], + deps = _size_report_deps + [ + "//pw_allocator/block:detailed_block", + "//pw_allocator/bucket:fast_sorted", + ], +) + +_small_block_deps = [ ":size_report", - "//pw_allocator/block:detailed_block", - "//pw_allocator/bucket:fast_sorted", + "//pw_allocator/block:small_block", "//pw_bloat:bloat_this_binary", ] pw_cc_size_binary( - name = "detailed_block", - srcs = ["detailed_block.cc"], - deps = _detailed_block_deps + [":hardening_none"], + name = "small_block", + srcs = ["small_block.cc"], + deps = _small_block_deps + [":hardening_none"], ) pw_cc_size_binary( - name = "detailed_block_basic", - srcs = ["detailed_block.cc"], - deps = _detailed_block_deps + ["//pw_allocator:hardening_basic"], + name = "small_alignable_block", + srcs = ["small_alignable_block.cc"], + deps = _size_report_deps + ["//pw_allocator/block:small_alignable_block"], ) pw_cc_size_binary( - name = "detailed_block_robust", - srcs = ["detailed_block.cc"], - deps = _detailed_block_deps + ["//pw_allocator:hardening_robust"], + name = "tiny_block", + srcs = ["tiny_block.cc"], + deps = _size_report_deps + ["//pw_allocator/block:tiny_block"], ) pw_cc_size_binary( - name = "detailed_block_debug", - srcs = ["detailed_block.cc"], - deps = _detailed_block_deps + ["//pw_allocator:hardening_debug"], + name = "small_block_basic", + srcs = ["small_block.cc"], + deps = _small_block_deps + ["//pw_allocator:hardening_basic"], +) + +pw_cc_size_binary( + name = "small_block_robust", + srcs = ["small_block.cc"], + deps = _small_block_deps + ["//pw_allocator:hardening_robust"], +) + +pw_cc_size_binary( + name = "small_block_debug", + srcs = ["small_block.cc"], + deps = _small_block_deps + ["//pw_allocator:hardening_debug"], ) # Bucket binaries to be measured for code size. diff --git a/pw_allocator/size_report/BUILD.gn b/pw_allocator/size_report/BUILD.gn index 57127a222..aad33ca27 100644 --- a/pw_allocator/size_report/BUILD.gn +++ b/pw_allocator/size_report/BUILD.gn @@ -33,8 +33,7 @@ pw_source_set("size_report") { public_deps = [ "$dir_pw_allocator:allocator", "$dir_pw_allocator:block_allocator", - "$dir_pw_allocator/block:detailed_block", - "$dir_pw_allocator/bucket:fast_sorted", + "$dir_pw_allocator/block:small_block", "$dir_pw_bloat:bloat_this_binary", dir_pw_bytes, ] @@ -63,33 +62,53 @@ pw_executable("base") { # Block binaries to be measured for code size. -_detailed_block = { +pw_executable("detailed_block") { + forward_variables_from(_size_report, "*") sources = [ "detailed_block.cc" ] - deps = [ - ":size_report", + deps += [ "$dir_pw_allocator/block:detailed_block", "$dir_pw_allocator/bucket:fast_sorted", + ] +} + +_small_block = { + sources = [ "small_block.cc" ] + deps = [ + ":size_report", + "$dir_pw_allocator/block:small_block", "$dir_pw_bloat:bloat_this_binary", ] } -pw_executable("detailed_block") { - forward_variables_from(_detailed_block, "*") +pw_executable("small_block") { + forward_variables_from(_small_block, "*") configs = [ ":hardening_none" ] } -pw_executable("detailed_block_basic") { - forward_variables_from(_detailed_block, "*") +pw_executable("small_alignable_block") { + forward_variables_from(_size_report, "*") + sources = [ "small_alignable_block.cc" ] + deps += [ "$dir_pw_allocator/block:small_alignable_block" ] +} + +pw_executable("tiny_block") { + forward_variables_from(_size_report, "*") + sources = [ "tiny_block.cc" ] + deps += [ "$dir_pw_allocator/block:tiny_block" ] +} + +pw_executable("small_block_basic") { + forward_variables_from(_small_block, "*") configs = [ "$dir_pw_allocator:hardening_basic" ] } -pw_executable("detailed_block_robust") { - forward_variables_from(_detailed_block, "*") +pw_executable("small_block_robust") { + forward_variables_from(_small_block, "*") configs = [ "$dir_pw_allocator:hardening_robust" ] } -pw_executable("detailed_block_debug") { - forward_variables_from(_detailed_block, "*") +pw_executable("small_block_debug") { + forward_variables_from(_small_block, "*") configs = [ "$dir_pw_allocator:hardening_debug" ] } diff --git a/pw_allocator/size_report/public/pw_allocator/size_report/size_report.h b/pw_allocator/size_report/public/pw_allocator/size_report/size_report.h index 8e0e0cfae..2f47af6c2 100644 --- a/pw_allocator/size_report/public/pw_allocator/size_report/size_report.h +++ b/pw_allocator/size_report/public/pw_allocator/size_report/size_report.h @@ -19,16 +19,15 @@ #include #include "pw_allocator/allocator.h" -#include "pw_allocator/block/detailed_block.h" +#include "pw_allocator/block/small_block.h" #include "pw_allocator/block_allocator.h" -#include "pw_allocator/bucket/fast_sorted.h" #include "pw_bloat/bloat_this_binary.h" #include "pw_bytes/span.h" namespace pw::allocator::size_report { /// Default block type to use for tests. -using BlockType = DetailedBlock; +using BlockType = SmallBlock; /// Type used for exercising an allocator. struct Foo final { diff --git a/pw_allocator/size_report/small_alignable_block.cc b/pw_allocator/size_report/small_alignable_block.cc new file mode 100644 index 000000000..55a0cbd0a --- /dev/null +++ b/pw_allocator/size_report/small_alignable_block.cc @@ -0,0 +1,29 @@ +// Copyright 2025 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/block/small_alignable_block.h" + +#include "pw_allocator/size_report/size_report.h" +#include "pw_bloat/bloat_this_binary.h" + +namespace pw::allocator::size_report { + +int Measure() { + volatile uint32_t mask = bloat::kDefaultMask; + return MeasureBlock(mask); +} + +} // namespace pw::allocator::size_report + +int main() { return pw::allocator::size_report::Measure(); } diff --git a/pw_allocator/size_report/small_block.cc b/pw_allocator/size_report/small_block.cc new file mode 100644 index 000000000..8a8d71ad8 --- /dev/null +++ b/pw_allocator/size_report/small_block.cc @@ -0,0 +1,29 @@ +// Copyright 2025 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/block/small_block.h" + +#include "pw_allocator/size_report/size_report.h" +#include "pw_bloat/bloat_this_binary.h" + +namespace pw::allocator::size_report { + +int Measure() { + volatile uint32_t mask = bloat::kDefaultMask; + return MeasureBlock(mask); +} + +} // namespace pw::allocator::size_report + +int main() { return pw::allocator::size_report::Measure(); } diff --git a/pw_allocator/size_report/tiny_block.cc b/pw_allocator/size_report/tiny_block.cc new file mode 100644 index 000000000..ed968d8c1 --- /dev/null +++ b/pw_allocator/size_report/tiny_block.cc @@ -0,0 +1,29 @@ +// Copyright 2025 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/block/tiny_block.h" + +#include "pw_allocator/size_report/size_report.h" +#include "pw_bloat/bloat_this_binary.h" + +namespace pw::allocator::size_report { + +int Measure() { + volatile uint32_t mask = bloat::kDefaultMask; + return MeasureBlock(mask); +} + +} // namespace pw::allocator::size_report + +int main() { return pw::allocator::size_report::Measure(); }