|  | // Copyright 2022 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_COMPRESSED_POINTER_H_ | 
|  | #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_COMPRESSED_POINTER_H_ | 
|  |  | 
|  | #include <climits> | 
|  | #include <type_traits> | 
|  |  | 
|  | #include "base/allocator/partition_allocator/partition_address_space.h" | 
|  | #include "base/allocator/partition_allocator/partition_alloc_base/bits.h" | 
|  | #include "base/allocator/partition_allocator/partition_alloc_base/compiler_specific.h" | 
|  | #include "base/allocator/partition_allocator/partition_alloc_base/component_export.h" | 
|  |  | 
|  | #if defined(PA_POINTER_COMPRESSION) | 
|  |  | 
|  | #if !defined(PA_GLUE_CORE_POOLS) | 
|  | #error "Pointer compression only works with glued pools" | 
|  | #endif  //! defined(PA_GLUE_CORE_POOLS) | 
|  | #if defined(PA_DYNAMICALLY_SELECT_POOL_SIZE) | 
|  | #error "Pointer compression currently supports constant pool size" | 
|  | #endif  // defined(PA_DYNAMICALLY_SELECT_POOL_SIZE) | 
|  |  | 
|  | #endif  // defined(PA_POINTER_COMPRESSION) | 
|  |  | 
|  | namespace partition_alloc { | 
|  |  | 
|  | namespace internal { | 
|  |  | 
|  | template <typename T1, typename T2> | 
|  | constexpr bool IsDecayedSame = | 
|  | std::is_same_v<std::decay_t<T1>, std::decay_t<T2>>; | 
|  |  | 
|  | #if defined(PA_POINTER_COMPRESSION) | 
|  |  | 
|  | // Pointer compression works by storing only the 'useful' 32-bit part of the | 
|  | // pointer. The other half (the base) is stored in a global variable | 
|  | // (CompressedPointerBaseGlobal::g_base_), which is used on decompression. To | 
|  | // support fast branchless decompression of nullptr, we use the most significant | 
|  | // bit in the compressed pointer to leverage sign-extension (for non-nullptr | 
|  | // pointers, the most significant bit is set, whereas for nullptr it's not). | 
|  | // Using this bit and supporting heaps larger than 4GB relies on having | 
|  | // alignment bits in pointers. Assuming that all pointers point to at least | 
|  | // 8-byte alignment objects, pointer compression can support heaps of size <= | 
|  | // 16GB. | 
|  | // ((3 alignment bits) = (1 bit for sign-extension) + (2 bits for 16GB heap)). | 
|  | // | 
|  | // Example: heap base: 0x4b0'ffffffff | 
|  | //  - g_base: 0x4b3'ffffffff (lower 34 bits set) | 
|  | //  - normal pointer: 0x4b2'a08b6480 | 
|  | //    - compression: | 
|  | //      - shift right by 3:        0x96'54116c90 | 
|  | //      - truncate:                   0x54116c90 | 
|  | //      - mark MSB:                   0xd4116c90 | 
|  | //    - decompression: | 
|  | //      - sign-extend:       0xffffffff'd4116c90 | 
|  | //      - shift left by 3:   0xfffffffe'a08b6480 | 
|  | //      - 'and' with g_base: 0x000004b2'a08b6480 | 
|  | // | 
|  | //  - nullptr: 0x00000000'00000000 | 
|  | //    - compression: | 
|  | //      - shift right by 3:  0x00000000'00000000 | 
|  | //      - truncate:                   0x00000000 | 
|  | //      - (don't mark MSB for nullptr) | 
|  | //    - decompression: | 
|  | //      - sign-extend:       0x00000000'00000000 | 
|  | //      - shift left by 3:   0x00000000'00000000 | 
|  | //      - 'and' with g_base: 0x00000000'00000000 | 
|  | // | 
|  | // Pointer compression relies on having both the regular and the BRP pool (core | 
|  | // pools) 'glued', so that the same base could be used for both. For simplicity, | 
|  | // the configurations with dynamically selected pool size are not supported. | 
|  | // However, they can be at the cost of performing an extra load for | 
|  | // core-pools-shift-size on both compression and decompression. | 
|  |  | 
|  | class CompressedPointerBaseGlobal final { | 
|  | public: | 
|  | static constexpr size_t kUsefulBits = | 
|  | base::bits::CountTrailingZeroBits(PartitionAddressSpace::CorePoolsSize()); | 
|  | static_assert(kUsefulBits >= sizeof(uint32_t) * CHAR_BIT); | 
|  | static constexpr size_t kBitsToShift = | 
|  | kUsefulBits - sizeof(uint32_t) * CHAR_BIT; | 
|  |  | 
|  | CompressedPointerBaseGlobal() = delete; | 
|  |  | 
|  | // Attribute const allows the compiler to assume that | 
|  | // CompressedPointerBaseGlobal::g_base_ doesn't change (e.g. across calls) and | 
|  | // thereby avoid redundant loads. | 
|  | PA_ALWAYS_INLINE __attribute__((const)) static uintptr_t Get() { | 
|  | PA_DCHECK(IsBaseConsistent()); | 
|  | return g_base_.base; | 
|  | } | 
|  |  | 
|  | PA_ALWAYS_INLINE static bool IsSet() { | 
|  | PA_DCHECK(IsBaseConsistent()); | 
|  | return (g_base_.base & ~kUsefulBitsMask) != 0; | 
|  | } | 
|  |  | 
|  | private: | 
|  | static constexpr uintptr_t kUsefulBitsMask = | 
|  | PartitionAddressSpace::CorePoolsSize() - 1; | 
|  |  | 
|  | static union alignas(kPartitionCachelineSize) | 
|  | PA_COMPONENT_EXPORT(PARTITION_ALLOC) Base { | 
|  | uintptr_t base; | 
|  | char cache_line[kPartitionCachelineSize]; | 
|  | } g_base_ PA_CONSTINIT; | 
|  |  | 
|  | PA_ALWAYS_INLINE static bool IsBaseConsistent() { | 
|  | return kUsefulBitsMask == (g_base_.base & kUsefulBitsMask); | 
|  | } | 
|  |  | 
|  | static void SetBase(uintptr_t base); | 
|  | static void ResetBaseForTesting(); | 
|  |  | 
|  | friend class PartitionAddressSpace; | 
|  | }; | 
|  |  | 
|  | #endif  // defined(PA_POINTER_COMPRESSION) | 
|  |  | 
|  | }  // namespace internal | 
|  |  | 
|  | #if defined(PA_POINTER_COMPRESSION) | 
|  |  | 
|  | template <typename T> | 
|  | class PA_TRIVIAL_ABI CompressedPointer final { | 
|  | public: | 
|  | using UnderlyingType = uint32_t; | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer() = default; | 
|  | PA_ALWAYS_INLINE explicit CompressedPointer(T* ptr) : value_(Compress(ptr)) {} | 
|  | PA_ALWAYS_INLINE constexpr explicit CompressedPointer(std::nullptr_t) | 
|  | : value_(0u) {} | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer(const CompressedPointer&) = | 
|  | default; | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer( | 
|  | CompressedPointer&& other) noexcept = default; | 
|  |  | 
|  | template <typename U, | 
|  | std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr> | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer( | 
|  | const CompressedPointer<U>& other) { | 
|  | if constexpr (internal::IsDecayedSame<T, U>) { | 
|  | // When pointers have the same type modulo constness, avoid the | 
|  | // compress-decompress round. | 
|  | value_ = other.value_; | 
|  | } else { | 
|  | // When the types are different, perform the round, because the pointer | 
|  | // may need to be adjusted. | 
|  | // TODO(1376980): Avoid the cycle here. | 
|  | value_ = Compress(other.get()); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename U, | 
|  | std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr> | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer( | 
|  | CompressedPointer<U>&& other) noexcept | 
|  | : CompressedPointer(other) {} | 
|  |  | 
|  | ~CompressedPointer() = default; | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer& operator=( | 
|  | const CompressedPointer&) = default; | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer& operator=( | 
|  | CompressedPointer&& other) noexcept = default; | 
|  |  | 
|  | template <typename U, | 
|  | std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr> | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer& operator=( | 
|  | const CompressedPointer<U>& other) { | 
|  | CompressedPointer copy(other); | 
|  | value_ = copy.value_; | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | template <typename U, | 
|  | std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr> | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer& operator=( | 
|  | CompressedPointer<U>&& other) noexcept { | 
|  | *this = other; | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | // Don't perform compression when assigning to nullptr. | 
|  | PA_ALWAYS_INLINE constexpr CompressedPointer& operator=(std::nullptr_t) { | 
|  | value_ = 0u; | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | PA_ALWAYS_INLINE T* get() const { return Decompress(value_); } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr bool is_nonnull() const { return value_; } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr UnderlyingType GetAsIntegral() const { | 
|  | return value_; | 
|  | } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr explicit operator bool() const { | 
|  | return is_nonnull(); | 
|  | } | 
|  |  | 
|  | template <typename U = T, | 
|  | std::enable_if_t<!std::is_void_v<std::remove_cv_t<U>>>* = nullptr> | 
|  | PA_ALWAYS_INLINE U& operator*() const { | 
|  | PA_DCHECK(is_nonnull()); | 
|  | return *get(); | 
|  | } | 
|  |  | 
|  | PA_ALWAYS_INLINE T* operator->() const { | 
|  | PA_DCHECK(is_nonnull()); | 
|  | return get(); | 
|  | } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr void swap(CompressedPointer& other) { | 
|  | std::swap(value_, other.value_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | template <typename> | 
|  | friend class CompressedPointer; | 
|  |  | 
|  | static constexpr size_t kBitsForSignExtension = 1; | 
|  | static constexpr size_t kOverallBitsToShift = | 
|  | internal::CompressedPointerBaseGlobal::kBitsToShift + | 
|  | kBitsForSignExtension; | 
|  |  | 
|  | static PA_ALWAYS_INLINE UnderlyingType Compress(T* ptr) { | 
|  | static constexpr size_t kMinimalRequiredAlignment = 8; | 
|  | static_assert((1 << kOverallBitsToShift) == kMinimalRequiredAlignment); | 
|  |  | 
|  | #if BUILDFLAG(PA_DCHECK_IS_ON) | 
|  | PA_DCHECK(reinterpret_cast<uintptr_t>(ptr) % kMinimalRequiredAlignment == | 
|  | 0); | 
|  | PA_DCHECK(internal::CompressedPointerBaseGlobal::IsSet()); | 
|  |  | 
|  | const uintptr_t base = internal::CompressedPointerBaseGlobal::Get(); | 
|  | static constexpr size_t kCorePoolsBaseMask = | 
|  | ~(internal::PartitionAddressSpace::CorePoolsSize() - 1); | 
|  | PA_DCHECK(!ptr || | 
|  | (base & kCorePoolsBaseMask) == | 
|  | (reinterpret_cast<uintptr_t>(ptr) & kCorePoolsBaseMask)); | 
|  | #endif  // BUILDFLAG(PA_DCHECK_IS_ON) | 
|  |  | 
|  | const auto uptr = reinterpret_cast<uintptr_t>(ptr); | 
|  | // Shift the pointer and truncate. | 
|  | auto compressed = static_cast<UnderlyingType>(uptr >> kOverallBitsToShift); | 
|  | // If the pointer is non-null, mark the most-significant-bit to sign-extend | 
|  | // it on decompression. Assuming compression is a significantly less | 
|  | // frequent operation, we let more work here in favor of faster | 
|  | // decompression. | 
|  | // TODO(1376980): Avoid this by overreserving the heap. | 
|  | if (compressed) | 
|  | compressed |= (1u << (sizeof(uint32_t) * CHAR_BIT - 1)); | 
|  |  | 
|  | return compressed; | 
|  | } | 
|  |  | 
|  | static PA_ALWAYS_INLINE T* Decompress(UnderlyingType ptr) { | 
|  | PA_DCHECK(internal::CompressedPointerBaseGlobal::IsSet()); | 
|  | const uintptr_t base = internal::CompressedPointerBaseGlobal::Get(); | 
|  | // Treat compressed pointer as signed and cast it to uint64_t, which will | 
|  | // sign-extend it. Then, shift the result by one. It's important to shift | 
|  | // the already unsigned value, as otherwise it would result in undefined | 
|  | // behavior. | 
|  | const uint64_t mask = static_cast<uint64_t>(static_cast<int32_t>(ptr)) | 
|  | << (kOverallBitsToShift); | 
|  | return reinterpret_cast<T*>(mask & base); | 
|  | } | 
|  |  | 
|  | UnderlyingType value_; | 
|  | }; | 
|  |  | 
|  | template <typename T> | 
|  | PA_ALWAYS_INLINE constexpr void swap(CompressedPointer<T>& a, | 
|  | CompressedPointer<T>& b) { | 
|  | a.swap(b); | 
|  | } | 
|  |  | 
|  | // operators==. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE bool operator==(CompressedPointer<T> a, | 
|  | CompressedPointer<U> b) { | 
|  | if constexpr (internal::IsDecayedSame<T, U>) { | 
|  | // When pointers have the same type modulo constness, simply compare | 
|  | // compressed values. | 
|  | return a.GetAsIntegral() == b.GetAsIntegral(); | 
|  | } else { | 
|  | // When the types are different, compare decompressed pointers, because the | 
|  | // pointers may need to be adjusted. | 
|  | // TODO(1376980): Avoid decompression here. | 
|  | return a.get() == b.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator==(CompressedPointer<T> a, U* b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return a == static_cast<CompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator==(T* a, CompressedPointer<U> b) { | 
|  | return b == a; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | PA_ALWAYS_INLINE constexpr bool operator==(CompressedPointer<T> a, | 
|  | std::nullptr_t) { | 
|  | return !a.is_nonnull(); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator==(std::nullptr_t, | 
|  | CompressedPointer<U> b) { | 
|  | return b == nullptr; | 
|  | } | 
|  |  | 
|  | // operators!=. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(CompressedPointer<T> a, | 
|  | CompressedPointer<U> b) { | 
|  | return !(a == b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(CompressedPointer<T> a, U* b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return a != static_cast<CompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(T* a, CompressedPointer<U> b) { | 
|  | return b != a; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(CompressedPointer<T> a, | 
|  | std::nullptr_t) { | 
|  | return a.is_nonnull(); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(std::nullptr_t, | 
|  | CompressedPointer<U> b) { | 
|  | return b != nullptr; | 
|  | } | 
|  |  | 
|  | // operators<. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<(CompressedPointer<T> a, | 
|  | CompressedPointer<U> b) { | 
|  | if constexpr (internal::IsDecayedSame<T, U>) { | 
|  | // When pointers have the same type modulo constness, simply compare | 
|  | // compressed values. | 
|  | return a.GetAsIntegral() < b.GetAsIntegral(); | 
|  | } else { | 
|  | // When the types are different, compare decompressed pointers, because the | 
|  | // pointers may need to be adjusted. | 
|  | // TODO(1376980): Avoid decompression here. | 
|  | return a.get() < b.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<(CompressedPointer<T> a, U* b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return a < static_cast<CompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<(T* a, CompressedPointer<U> b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return static_cast<CompressedPointer<T>>(a) < b; | 
|  | } | 
|  |  | 
|  | // operators<=. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<=(CompressedPointer<T> a, | 
|  | CompressedPointer<U> b) { | 
|  | if constexpr (internal::IsDecayedSame<T, U>) { | 
|  | // When pointers have the same type modulo constness, simply compare | 
|  | // compressed values. | 
|  | return a.GetAsIntegral() <= b.GetAsIntegral(); | 
|  | } else { | 
|  | // When the types are different, compare decompressed pointers, because the | 
|  | // pointers may need to be adjusted. | 
|  | // TODO(1376980): Avoid decompression here. | 
|  | return a.get() <= b.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<=(CompressedPointer<T> a, U* b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return a <= static_cast<CompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<=(T* a, CompressedPointer<U> b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return static_cast<CompressedPointer<T>>(a) <= b; | 
|  | } | 
|  |  | 
|  | // operators>. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>(CompressedPointer<T> a, | 
|  | CompressedPointer<U> b) { | 
|  | return !(a <= b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>(CompressedPointer<T> a, U* b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return a > static_cast<CompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>(T* a, CompressedPointer<U> b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return static_cast<CompressedPointer<T>>(a) > b; | 
|  | } | 
|  |  | 
|  | // operators>=. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>=(CompressedPointer<T> a, | 
|  | CompressedPointer<U> b) { | 
|  | return !(a < b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>=(CompressedPointer<T> a, U* b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return a >= static_cast<CompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>=(T* a, CompressedPointer<U> b) { | 
|  | // Do compression, since it is less expensive. | 
|  | return static_cast<CompressedPointer<T>>(a) >= b; | 
|  | } | 
|  |  | 
|  | #endif  // defined(PA_POINTER_COMPRESSION) | 
|  |  | 
|  | // Simple wrapper over the raw pointer. | 
|  | template <typename T> | 
|  | class PA_TRIVIAL_ABI UncompressedPointer final { | 
|  | public: | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer() = default; | 
|  | PA_ALWAYS_INLINE constexpr explicit UncompressedPointer(T* ptr) : ptr_(ptr) {} | 
|  | PA_ALWAYS_INLINE constexpr explicit UncompressedPointer(std::nullptr_t) | 
|  | : ptr_(nullptr) {} | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer(const UncompressedPointer&) = | 
|  | default; | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer( | 
|  | UncompressedPointer&& other) noexcept = default; | 
|  |  | 
|  | template <typename U, | 
|  | std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr> | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer( | 
|  | const UncompressedPointer<U>& other) | 
|  | : ptr_(other.ptr_) {} | 
|  |  | 
|  | template <typename U, | 
|  | std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr> | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer( | 
|  | UncompressedPointer<U>&& other) noexcept | 
|  | : ptr_(std::move(other.ptr_)) {} | 
|  |  | 
|  | ~UncompressedPointer() = default; | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=( | 
|  | const UncompressedPointer&) = default; | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=( | 
|  | UncompressedPointer&& other) noexcept = default; | 
|  |  | 
|  | template <typename U, | 
|  | std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr> | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=( | 
|  | const UncompressedPointer<U>& other) { | 
|  | ptr_ = other.ptr_; | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | template <typename U, | 
|  | std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr> | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=( | 
|  | UncompressedPointer<U>&& other) noexcept { | 
|  | ptr_ = std::move(other.ptr_); | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=(std::nullptr_t) { | 
|  | ptr_ = nullptr; | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr T* get() const { return ptr_; } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr bool is_nonnull() const { return ptr_; } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr explicit operator bool() const { | 
|  | return is_nonnull(); | 
|  | } | 
|  |  | 
|  | template <typename U = T, | 
|  | std::enable_if_t<!std::is_void_v<std::remove_cv_t<U>>>* = nullptr> | 
|  | PA_ALWAYS_INLINE constexpr U& operator*() const { | 
|  | PA_DCHECK(is_nonnull()); | 
|  | return *get(); | 
|  | } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr T* operator->() const { | 
|  | PA_DCHECK(is_nonnull()); | 
|  | return get(); | 
|  | } | 
|  |  | 
|  | PA_ALWAYS_INLINE constexpr void swap(UncompressedPointer& other) { | 
|  | std::swap(ptr_, other.ptr_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | template <typename> | 
|  | friend class UncompressedPointer; | 
|  |  | 
|  | T* ptr_; | 
|  | }; | 
|  |  | 
|  | template <typename T> | 
|  | PA_ALWAYS_INLINE constexpr void swap(UncompressedPointer<T>& a, | 
|  | UncompressedPointer<T>& b) { | 
|  | a.swap(b); | 
|  | } | 
|  |  | 
|  | // operators==. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator==(UncompressedPointer<T> a, | 
|  | UncompressedPointer<U> b) { | 
|  | return a.get() == b.get(); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator==(UncompressedPointer<T> a, U* b) { | 
|  | return a == static_cast<UncompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator==(T* a, UncompressedPointer<U> b) { | 
|  | return b == a; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | PA_ALWAYS_INLINE constexpr bool operator==(UncompressedPointer<T> a, | 
|  | std::nullptr_t) { | 
|  | return !a.is_nonnull(); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator==(std::nullptr_t, | 
|  | UncompressedPointer<U> b) { | 
|  | return b == nullptr; | 
|  | } | 
|  |  | 
|  | // operators!=. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(UncompressedPointer<T> a, | 
|  | UncompressedPointer<U> b) { | 
|  | return !(a == b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(UncompressedPointer<T> a, U* b) { | 
|  | return a != static_cast<UncompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(T* a, UncompressedPointer<U> b) { | 
|  | return b != a; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(UncompressedPointer<T> a, | 
|  | std::nullptr_t) { | 
|  | return a.is_nonnull(); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator!=(std::nullptr_t, | 
|  | UncompressedPointer<U> b) { | 
|  | return b != nullptr; | 
|  | } | 
|  |  | 
|  | // operators<. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<(UncompressedPointer<T> a, | 
|  | UncompressedPointer<U> b) { | 
|  | return a.get() < b.get(); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<(UncompressedPointer<T> a, U* b) { | 
|  | return a < static_cast<UncompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<(T* a, UncompressedPointer<U> b) { | 
|  | return static_cast<UncompressedPointer<T>>(a) < b; | 
|  | } | 
|  |  | 
|  | // operators<=. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<=(UncompressedPointer<T> a, | 
|  | UncompressedPointer<U> b) { | 
|  | return a.get() <= b.get(); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<=(UncompressedPointer<T> a, U* b) { | 
|  | return a <= static_cast<UncompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator<=(T* a, UncompressedPointer<U> b) { | 
|  | return static_cast<UncompressedPointer<T>>(a) <= b; | 
|  | } | 
|  |  | 
|  | // operators>. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>(UncompressedPointer<T> a, | 
|  | UncompressedPointer<U> b) { | 
|  | return !(a <= b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>(UncompressedPointer<T> a, U* b) { | 
|  | return a > static_cast<UncompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>(T* a, UncompressedPointer<U> b) { | 
|  | return static_cast<UncompressedPointer<T>>(a) > b; | 
|  | } | 
|  |  | 
|  | // operators>=. | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>=(UncompressedPointer<T> a, | 
|  | UncompressedPointer<U> b) { | 
|  | return !(a < b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>=(UncompressedPointer<T> a, U* b) { | 
|  | return a >= static_cast<UncompressedPointer<U>>(b); | 
|  | } | 
|  |  | 
|  | template <typename T, typename U> | 
|  | PA_ALWAYS_INLINE constexpr bool operator>=(T* a, UncompressedPointer<U> b) { | 
|  | return static_cast<UncompressedPointer<T>>(a) >= b; | 
|  | } | 
|  |  | 
|  | }  // namespace partition_alloc | 
|  |  | 
|  | #endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_COMPRESSED_POINTER_H_ |