blob: 81156101b2f2c58e661033115a158ead4542a874 [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FBL_PACKED_POINTER_H_
#define FBL_PACKED_POINTER_H_
#include <zircon/assert.h>
#include <zircon/types.h>
#include <bit>
#include <cstddef>
#include <type_traits>
namespace fbl {
namespace internal {
template <typename T, size_t kDataBits, bool kCheckAlignment>
struct PackedPointerAlignmentValidator {
static constexpr bool value = true;
};
template <typename T, size_t kDataBits>
struct PackedPointerAlignmentValidator<T, kDataBits, true> {
static constexpr bool value = (alignof(T) >= (1ul << kDataBits));
};
} // namespace internal
// PackedPointer<T, kDataBits, kCheckAlignment> is a pointer wrapper that allows
// storing a small amount of data in the alignment bits of the pointer.
//
// The number of bits available for packing (kDataBits) must be less than or
// equal to the number of trailing zero bits in the alignment of T. For example,
// if alignof(T) == 8, then kDataBits can be up to 3.
//
// PackedPointer provides a safety check at compile time to ensure that the
// requested number of data bits is compatible with the alignment of T. It also
// provides runtime assertions (in debug builds) to ensure that the pointer
// passed to it is correctly aligned and that the data fits within the specified
// number of bits.
//
// In some cases, such as when T is an incomplete type (e.g. when PackedPointer
// is used as a member of T), the compile-time alignment check cannot be
// performed. In these cases, kCheckAlignment can be set to false to disable
// the check.
//
// Example usage:
//
// struct alignas(8) MyStruct { ... };
// PackedPointer<MyStruct, 3> ptr(my_struct_instance, 0x5);
//
// MyStruct* p = ptr.ptr(); // returns my_struct_instance
// uintptr_t d = ptr.data(); // returns 0x5
//
template <typename T, size_t kDataBits, bool kCheckAlignment = true>
class PackedPointer {
public:
static_assert(kDataBits > 0, "PackedPointer requires at least one data bit.");
static_assert(kDataBits < (sizeof(uintptr_t) * 8), "Too many data bits requested.");
static_assert(internal::PackedPointerAlignmentValidator<T, kDataBits, kCheckAlignment>::value,
"T has insufficient alignment for the requested number of data bits.");
static constexpr uintptr_t kDataMask = (1ul << kDataBits) - 1;
static constexpr uintptr_t kPtrMask = ~kDataMask;
constexpr PackedPointer() = default;
constexpr PackedPointer(std::nullptr_t) : value_(0) {}
explicit PackedPointer(T* ptr) : value_(std::bit_cast<uintptr_t>(ptr)) {
// Here (and elsewhere) we check that the pointer provided is correctly aligned, even if the
// kCheckAlignment should have guaranteed this. The motivation is that although a `T*` with
// alignment bits set would be an invalid pointer, this check serves to guard against callers
// misusing the interface and 'pre packing' their data into the pointer, instead of using the
// separate constructors and setters for manipulating pointer and data independently.
ZX_DEBUG_ASSERT_MSG((value_ & kDataMask) == 0,
"Pointer %p is not aligned to at least %zu bytes", ptr,
size_t{1} << kDataBits);
}
PackedPointer(T* ptr, uintptr_t data) : value_(std::bit_cast<uintptr_t>(ptr) | data) {
ZX_DEBUG_ASSERT_MSG((std::bit_cast<uintptr_t>(ptr) & kDataMask) == 0,
"Pointer %p is not aligned to at least %zu bytes", ptr,
size_t{1} << kDataBits);
ZX_DEBUG_ASSERT_MSG((data & kPtrMask) == 0, "Data %zu exceeds %zu bits", data, kDataBits);
}
PackedPointer(std::nullptr_t, uintptr_t data) : value_(data) {
ZX_DEBUG_ASSERT_MSG((data & kPtrMask) == 0, "Data %zu exceeds %zu bits", data, kDataBits);
}
T* ptr() const { return std::bit_cast<T*>(value_ & kPtrMask); }
uintptr_t data() const { return value_ & kDataMask; }
void set_ptr(T* ptr) {
uintptr_t raw_ptr = std::bit_cast<uintptr_t>(ptr);
ZX_DEBUG_ASSERT_MSG((raw_ptr & kDataMask) == 0,
"Pointer %p is not aligned to at least %zu bytes", ptr,
size_t{1} << kDataBits);
value_ = (value_ & kDataMask) | raw_ptr;
}
void set_data(uintptr_t data) {
ZX_DEBUG_ASSERT_MSG((data & kPtrMask) == 0, "Data %zu exceeds %zu bits", data, kDataBits);
value_ = (value_ & kPtrMask) | data;
}
void reset() { value_ = 0; }
// Pointer semantics
T& operator*() const { return *ptr(); }
T* operator->() const { return ptr(); }
explicit operator bool() const { return ptr() != nullptr; }
// Comparison operators
bool operator==(const PackedPointer& other) const { return value_ == other.value_; }
bool operator!=(const PackedPointer& other) const { return value_ != other.value_; }
bool operator==(std::nullptr_t) const { return ptr() == nullptr; }
bool operator!=(std::nullptr_t) const { return ptr() != nullptr; }
private:
uintptr_t value_ = 0;
};
} // namespace fbl
#endif // FBL_PACKED_POINTER_H_