1 // Copyright 2024 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 #pragma once 15 16 #include <cstddef> 17 #include <cstdint> 18 19 #include "lib/stdcompat/bit.h" 20 #include "pw_assert/assert.h" 21 22 namespace pw { 23 24 /// Pointer wrapper that can store extra data in otherwise unused bits. 25 /// 26 /// Pointers to types that have an alignment of more than 1 will always have 27 /// their least significant bit(s) set to zero. For example, if `alignof(T)` is 28 /// 8, than the 3 least significant bits are always 0. In certain contexts where 29 /// overhead needs to be tightly constrained, reusing these bits and avoiding 30 /// additional fields may be worth the performance and code size penalties that 31 /// arise from masking values. 32 template <typename T> 33 class PackedPtr { 34 public: 35 // Calculate the number of available bits in a function, since the type T may 36 // include fields of type `PackedPtr<T>` and not be fully defined when the 37 // `PackedPtr<T>` class is instantiated. NumBits()38 static constexpr size_t NumBits() { 39 CheckAlignment(); 40 return 31 - cpp20::countl_zero(static_cast<uint32_t>(alignof(T))); 41 } 42 PackedPtr()43 constexpr explicit PackedPtr() { CheckAlignment(); } 44 ~PackedPtr() = default; 45 PackedPtr(T * ptr,uintptr_t packed_value)46 constexpr PackedPtr(T* ptr, uintptr_t packed_value) : PackedPtr() { 47 set(ptr); 48 set_packed_value(packed_value); 49 } 50 51 template <typename T2, 52 typename = std::enable_if_t<std::is_convertible_v<T2, T>>> PackedPtr(const PackedPtr<T2> & other)53 constexpr PackedPtr(const PackedPtr<T2>& other) { 54 *this = other; 55 } 56 57 template <typename T2, 58 typename = std::enable_if_t<std::is_convertible_v<T2, T>>> 59 constexpr PackedPtr& operator=(const PackedPtr<T2>& other) { 60 data_ = other.data_; 61 return *this; 62 } 63 64 template <typename T2, 65 typename = std::enable_if_t<std::is_convertible_v<T2, T>>> PackedPtr(PackedPtr<T2> && other)66 constexpr PackedPtr(PackedPtr<T2>&& other) { 67 *this = std::move(other); 68 } 69 70 template <typename T2, 71 typename = std::enable_if_t<std::is_convertible_v<T2, T>>> 72 constexpr PackedPtr& operator=(PackedPtr<T2>&& other) { 73 data_ = std::exchange(other.data_, 0); 74 return *this; 75 } 76 77 constexpr T& operator*() const { return *(get()); } 78 constexpr T* operator->() const { return get(); } 79 80 /// Returns the pointer. get()81 constexpr T* get() const { return cpp20::bit_cast<T*>(data_ & ~kValueMask); } 82 83 /// Returns the packed_value packed into the unused bits of the pointer. packed_value()84 constexpr uintptr_t packed_value() const { return data_ & kValueMask; } 85 86 /// Sets the pointer without changing the packed value. set(T * ptr)87 constexpr void set(T* ptr) { 88 auto packed_ptr = cpp20::bit_cast<uintptr_t, T*>(ptr); 89 PW_DASSERT((packed_ptr & kValueMask) == 0); 90 data_ = packed_ptr | packed_value(); 91 } 92 93 /// Packs a packed_value into the unused bits of the pointer. 94 /// 95 /// @pre The given packed_value must fit in the available bits. set_packed_value(uintptr_t packed_value)96 constexpr void set_packed_value(uintptr_t packed_value) { 97 PW_DASSERT(packed_value <= kValueMask); 98 data_ = (data_ & ~kValueMask) | packed_value; 99 } 100 101 private: 102 // Check the alignment of T in a function, since the type T may include fields 103 // of type `PackedPtr<T>` and not be fully defined when the `PackedPtr<T>` 104 // class is instantiated. CheckAlignment()105 static constexpr void CheckAlignment() { 106 static_assert(alignof(T) > 1, 107 "Alignment must be more than one to pack any bits"); 108 } 109 110 static constexpr uintptr_t kValueMask = (1 << NumBits()) - 1; 111 112 // Allows copying and moving between convertible types. 113 template <typename> 114 friend class PackedPtr; 115 116 uintptr_t data_ = 0; 117 }; 118 119 } // namespace pw 120