xref: /aosp_15_r20/external/pigweed/pw_bytes/public/pw_bytes/packed_ptr.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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