Skip to content

Commit

Permalink
pw_bytes: Add PackedPtr
Browse files Browse the repository at this point in the history
Every object types has an alignment requirement, and for most types this
is greater than one. As a result, pointers to these types have one or
more least significant bits that are always zero. For certain types,
such as intrusive container types, it is desirable to reuse these bits
and avoid creating additional fields.

This class facilitates those use cases by providing a wrapper type that
can be treated like a pointer in many cases, but that can also be used
to set and get a value from the unused bits.

This storage is not free: this class must perform bit operations on
every dereference, adding to its performance cost and code size. This
class should only be used when the benefits of saving memory overhead
outweigh these costs.

Change-Id: I87f1831bd5b143ce87e01a7efce72be13df011ef
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/235104
Reviewed-by: Taylor Cramer <[email protected]>
Lint: Lint 🤖 <[email protected]>
Reviewed-by: Wyatt Hepler <[email protected]>
Commit-Queue: Aaron Green <[email protected]>
  • Loading branch information
nopsledder authored and CQ Bot Account committed Sep 18, 2024
1 parent faac617 commit cda5ba6
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ _doxygen_input_files = [ # keep-sorted: start
"$dir_pw_bytes/public/pw_bytes/alignment.h",
"$dir_pw_bytes/public/pw_bytes/bit.h",
"$dir_pw_bytes/public/pw_bytes/byte_builder.h",
"$dir_pw_bytes/public/pw_bytes/packed_ptr.h",
"$dir_pw_channel/public/pw_channel/channel.h",
"$dir_pw_channel/public/pw_channel/epoll_channel.h",
"$dir_pw_channel/public/pw_channel/forwarding_channel.h",
Expand Down
20 changes: 20 additions & 0 deletions pw_bytes/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ cc_library(
deps = ["//third_party/fuchsia:stdcompat"],
)

cc_library(
name = "packed_ptr",
hdrs = ["public/pw_bytes/packed_ptr.h"],
includes = ["public"],
deps = [
"//pw_assert",
"//third_party/fuchsia:stdcompat",
],
)

pw_cc_test(
name = "alignment_test",
srcs = ["alignment_test.cc"],
Expand Down Expand Up @@ -111,6 +121,16 @@ pw_cc_test(
],
)

pw_cc_test(
name = "packed_ptr_test",
srcs = ["packed_ptr_test.cc"],
deps = [
":packed_ptr",
"//pw_compilation_testing:negative_compilation_testing",
"//pw_unit_test",
],
)

pw_cc_test(
name = "suffix_test",
srcs = ["suffix_test.cc"],
Expand Down
16 changes: 16 additions & 0 deletions pw_bytes/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,23 @@ pw_source_set("bit") {
public_deps = [ "$dir_pw_third_party/fuchsia:stdcompat" ]
}

pw_source_set("packed_ptr") {
public_configs = [ ":public_include_path" ]
public = [ "public/pw_bytes/packed_ptr.h" ]
public_deps = [
"$dir_pw_third_party/fuchsia:stdcompat",
dir_pw_assert,
]
}

pw_test_group("tests") {
tests = [
":alignment_test",
":array_test",
":bit_test",
":byte_builder_test",
":endian_test",
":packed_ptr_test",
":suffix_test",
":units_test",
]
Expand Down Expand Up @@ -102,6 +112,12 @@ pw_test("endian_test") {
sources = [ "endian_test.cc" ]
}

pw_test("packed_ptr_test") {
deps = [ ":packed_ptr" ]
sources = [ "packed_ptr_test.cc" ]
negative_compilation_tests = true
}

pw_test("suffix_test") {
deps = [ ":pw_bytes" ]
sources = [ "suffix_test.cc" ]
Expand Down
21 changes: 21 additions & 0 deletions pw_bytes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ pw_add_library(pw_bytes.bit INTERFACE
pw_third_party.fuchsia.stdcompat
)

pw_add_library(pw_bytes.packed_ptr INTERFACE
HEADERS
public/pw_bytes/packed_ptr.h
PUBLIC_INCLUDES
public
PUBLIC_DEPS
pw_assert
pw_third_party.fuchsia.stdcompat
)

pw_add_test(pw_bytes.alignment_test
SOURCES
alignment_test.cc
Expand Down Expand Up @@ -107,6 +117,17 @@ pw_add_test(pw_bytes.endian_test
pw_bytes
)

pw_add_test(pw_bytes.packed_ptr_test
SOURCES
packed_ptr_test.cc
PRIVATE_DEPS
pw_compilation_testing._pigweed_only_negative_compilation
pw_bytes.packed_ptr
GROUPS
modules
pw_bytes
)

pw_add_test(pw_bytes.suffix_test
SOURCES
suffix_test.cc
Expand Down
8 changes: 8 additions & 0 deletions pw_bytes/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ Dependencies
Features
--------

pw_bytes/packed_ptr.h
======================
Wrapper type that allows storing data in the least significant bits of a
pointer that would otherwise be unused.

.. doxygenclass:: pw::PackedPtr
:members:

pw_bytes/alignment.h
====================
Functions for aligning sizes and addresses to memory alignment boundaries.
Expand Down
189 changes: 189 additions & 0 deletions pw_bytes/packed_ptr_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@

// Copyright 2022 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_bytes/packed_ptr.h"

#include <cstdint>

#include "pw_compilation_testing/negative_compilation.h"
#include "pw_unit_test/framework.h"

namespace {

template <size_t kAlign>
struct alignas(kAlign) Aligned {
uint16_t val_ = 0;
};

TEST(PackedPtrTest, NumBits) {
EXPECT_EQ(pw::PackedPtr<Aligned<2>>::NumBits(), 1U);
EXPECT_EQ(pw::PackedPtr<Aligned<4>>::NumBits(), 2U);
EXPECT_EQ(pw::PackedPtr<Aligned<8>>::NumBits(), 3U);
EXPECT_EQ(pw::PackedPtr<Aligned<16>>::NumBits(), 4U);
EXPECT_EQ(pw::PackedPtr<Aligned<32>>::NumBits(), 5U);
EXPECT_EQ(pw::PackedPtr<Aligned<64>>::NumBits(), 6U);
EXPECT_EQ(pw::PackedPtr<Aligned<128>>::NumBits(), 7U);
EXPECT_EQ(pw::PackedPtr<Aligned<256>>::NumBits(), 8U);
}

#if PW_NC_TEST(NumBits_AlignmentIsOne)
PW_NC_EXPECT("Alignment must be more than one to pack any bits");
TEST(PackedPtrTest, NumBits_Char) {
EXPECT_EQ(pw::PackedPtr<char>::NumBits(), 0U);
}
#elif PW_NC_TEST(Construct_AlignmentIsOne)
PW_NC_EXPECT("Alignment must be more than one to pack any bits");
[[maybe_unused]] pw::PackedPtr<std::byte> bad;
#endif // PW_NC_TEST

TEST(PackedPtrTest, Construct_Default) {
pw::PackedPtr<Aligned<4>> ptr;
EXPECT_EQ(ptr.get(), nullptr);
EXPECT_EQ(ptr.packed_value(), 0U);
}

TEST(PackedPtrTest, Construct_FromArgs) {
Aligned<16> obj;
pw::PackedPtr ptr(&obj, 1);
EXPECT_EQ(ptr.get(), &obj);
EXPECT_EQ(ptr.packed_value(), 1U);
}

TEST(PackedPtrTest, Construct_Copy) {
Aligned<16> obj;
pw::PackedPtr ptr1(&obj, 2);
pw::PackedPtr ptr2(ptr1);
EXPECT_EQ(ptr1.get(), &obj);
EXPECT_EQ(ptr1.packed_value(), 2U);
EXPECT_EQ(ptr2.get(), &obj);
EXPECT_EQ(ptr2.packed_value(), 2U);
}

TEST(PackedPtrTest, Construct_Copy_AddConst) {
Aligned<16> obj;
pw::PackedPtr ptr1(&obj, 2);
pw::PackedPtr<const Aligned<16>> ptr2(ptr1);
EXPECT_EQ(ptr1.get(), &obj);
EXPECT_EQ(ptr1.packed_value(), 2U);
EXPECT_EQ(ptr2.get(), &obj);
EXPECT_EQ(ptr2.packed_value(), 2U);
}

TEST(PackedPtrTest, Construct_Move) {
Aligned<16> obj;
pw::PackedPtr ptr1(&obj, 3);
pw::PackedPtr ptr2(std::move(ptr1));
// NOLINTBEGIN(bugprone-use-after-move)
EXPECT_EQ(ptr1.get(), nullptr);
EXPECT_EQ(ptr1.packed_value(), 0U);
// NOLINTEND(bugprone-use-after-move)
EXPECT_EQ(ptr2.get(), &obj);
EXPECT_EQ(ptr2.packed_value(), 3U);
}

TEST(PackedPtrTest, Construct_Move_AddConst) {
Aligned<16> obj;
pw::PackedPtr ptr1(&obj, 3);
pw::PackedPtr<const Aligned<16>> ptr2(std::move(ptr1));
// NOLINTBEGIN(bugprone-use-after-move)
EXPECT_EQ(ptr1.get(), nullptr);
EXPECT_EQ(ptr1.packed_value(), 0U);
// NOLINTEND(bugprone-use-after-move)
EXPECT_EQ(ptr2.get(), &obj);
EXPECT_EQ(ptr2.packed_value(), 3U);
}

// Ensure we can create PackedPtrs to types that include PackedPtrs to
// themsevles.
struct Recursive {
size_t field_ = 0;
pw::PackedPtr<Recursive> ptr_;
};

TEST(PackedPtrTest, Construct_Recursive) {
pw::PackedPtr<Recursive> ptr;
EXPECT_EQ(ptr.get(), nullptr);
EXPECT_EQ(ptr.packed_value(), 0U);
}

TEST(PackedPtrTest, Copy) {
Aligned<16> obj;
pw::PackedPtr ptr1(&obj, 4);
pw::PackedPtr ptr2 = ptr1;
EXPECT_EQ(ptr1.get(), &obj);
EXPECT_EQ(ptr1.packed_value(), 4U);
EXPECT_EQ(ptr2.get(), &obj);
EXPECT_EQ(ptr2.packed_value(), 4U);
}

TEST(PackedPtrTest, Copy_AddConst) {
Aligned<16> obj;
pw::PackedPtr ptr1(&obj, 4);
pw::PackedPtr<const Aligned<16>> ptr2 = ptr1;
EXPECT_EQ(ptr1.get(), &obj);
EXPECT_EQ(ptr1.packed_value(), 4U);
EXPECT_EQ(ptr2.get(), &obj);
EXPECT_EQ(ptr2.packed_value(), 4U);
}

TEST(PackedPtrTest, Move) {
Aligned<16> obj;
pw::PackedPtr ptr1(&obj, 6);
pw::PackedPtr ptr2 = std::move(ptr1);
// NOLINTBEGIN(bugprone-use-after-move)
EXPECT_EQ(ptr1.get(), nullptr);
EXPECT_EQ(ptr1.packed_value(), 0U);
// NOLINTEND(bugprone-use-after-move)
EXPECT_EQ(ptr2.get(), &obj);
EXPECT_EQ(ptr2.packed_value(), 6U);
}

TEST(PackedPtrTest, Move_AddConst) {
Aligned<16> obj;
pw::PackedPtr ptr1(&obj, 6);
pw::PackedPtr<const Aligned<16>> ptr2 = std::move(ptr1);
// NOLINTBEGIN(bugprone-use-after-move)
EXPECT_EQ(ptr1.get(), nullptr);
EXPECT_EQ(ptr1.packed_value(), 0U);
// NOLINTEND(bugprone-use-after-move)
EXPECT_EQ(ptr2.get(), &obj);
EXPECT_EQ(ptr2.packed_value(), 6U);
}

TEST(PackedPtrTest, Dereference) {
Aligned<4> obj{1u};
pw::PackedPtr ptr(&obj, 0);
EXPECT_EQ((*ptr).val_, 1u);
}

TEST(PackedPtrTest, Dereference_Const) {
Aligned<4> obj{22u};
const pw::PackedPtr ptr(&obj, 0);
EXPECT_EQ((*ptr).val_, 22u);
}

TEST(PackedPtrTest, StructureDereference) {
Aligned<4> obj{333u};
pw::PackedPtr ptr(&obj, 0);
EXPECT_EQ(ptr->val_, 333u);
}

TEST(PackedPtrTest, StructureDereference_Const) {
Aligned<4> obj{4444u};
const pw::PackedPtr ptr(&obj, 0);
EXPECT_EQ(ptr->val_, 4444u);
}

} // namespace
Loading

0 comments on commit cda5ba6

Please sign in to comment.