// 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 #include #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 PA_CONFIG(POINTER_COMPRESSION) #if !PA_CONFIG(GLUE_CORE_POOLS) #error "Pointer compression only works with glued pools" #endif #if PA_CONFIG(DYNAMICALLY_SELECT_POOL_SIZE) #error "Pointer compression currently supports constant pool size" #endif #endif // PA_CONFIG(POINTER_COMPRESSION) namespace partition_alloc { namespace internal { template constexpr bool IsDecayedSame = std::is_same_v, std::decay_t>; #if PA_CONFIG(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 // PA_CONFIG(POINTER_COMPRESSION) } // namespace internal #if PA_CONFIG(POINTER_COMPRESSION) template 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 >* = nullptr> PA_ALWAYS_INLINE constexpr CompressedPointer( const CompressedPointer& other) { if constexpr (internal::IsDecayedSame) { // 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 >* = nullptr> PA_ALWAYS_INLINE constexpr CompressedPointer( CompressedPointer&& 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 >* = nullptr> PA_ALWAYS_INLINE constexpr CompressedPointer& operator=( const CompressedPointer& other) { CompressedPointer copy(other); value_ = copy.value_; return *this; } template >* = nullptr> PA_ALWAYS_INLINE constexpr CompressedPointer& operator=( CompressedPointer&& 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 >>* = 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 friend class CompressedPointer; static constexpr size_t kBitsForSignExtension = 1; static constexpr size_t kOverallBitsToShift = internal::CompressedPointerBaseGlobal::kBitsToShift + kBitsForSignExtension; PA_ALWAYS_INLINE static 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(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(ptr) & kCorePoolsBaseMask)); #endif // BUILDFLAG(PA_DCHECK_IS_ON) const auto uptr = reinterpret_cast(ptr); // Shift the pointer and truncate. auto compressed = static_cast(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; } PA_ALWAYS_INLINE static 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(static_cast(ptr)) << (kOverallBitsToShift); return reinterpret_cast(mask & base); } UnderlyingType value_; }; template PA_ALWAYS_INLINE constexpr void swap(CompressedPointer& a, CompressedPointer& b) { a.swap(b); } // operators==. template PA_ALWAYS_INLINE bool operator==(CompressedPointer a, CompressedPointer b) { if constexpr (internal::IsDecayedSame) { // 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 PA_ALWAYS_INLINE constexpr bool operator==(CompressedPointer a, U* b) { // Do compression, since it is less expensive. return a == static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator==(T* a, CompressedPointer b) { return b == a; } template PA_ALWAYS_INLINE constexpr bool operator==(CompressedPointer a, std::nullptr_t) { return !a.is_nonnull(); } template PA_ALWAYS_INLINE constexpr bool operator==(std::nullptr_t, CompressedPointer b) { return b == nullptr; } // operators!=. template PA_ALWAYS_INLINE constexpr bool operator!=(CompressedPointer a, CompressedPointer b) { return !(a == b); } template PA_ALWAYS_INLINE constexpr bool operator!=(CompressedPointer a, U* b) { // Do compression, since it is less expensive. return a != static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator!=(T* a, CompressedPointer b) { return b != a; } template PA_ALWAYS_INLINE constexpr bool operator!=(CompressedPointer a, std::nullptr_t) { return a.is_nonnull(); } template PA_ALWAYS_INLINE constexpr bool operator!=(std::nullptr_t, CompressedPointer b) { return b != nullptr; } // operators<. template PA_ALWAYS_INLINE constexpr bool operator<(CompressedPointer a, CompressedPointer b) { if constexpr (internal::IsDecayedSame) { // 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 PA_ALWAYS_INLINE constexpr bool operator<(CompressedPointer a, U* b) { // Do compression, since it is less expensive. return a < static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator<(T* a, CompressedPointer b) { // Do compression, since it is less expensive. return static_cast>(a) < b; } // operators<=. template PA_ALWAYS_INLINE constexpr bool operator<=(CompressedPointer a, CompressedPointer b) { if constexpr (internal::IsDecayedSame) { // 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 PA_ALWAYS_INLINE constexpr bool operator<=(CompressedPointer a, U* b) { // Do compression, since it is less expensive. return a <= static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator<=(T* a, CompressedPointer b) { // Do compression, since it is less expensive. return static_cast>(a) <= b; } // operators>. template PA_ALWAYS_INLINE constexpr bool operator>(CompressedPointer a, CompressedPointer b) { return !(a <= b); } template PA_ALWAYS_INLINE constexpr bool operator>(CompressedPointer a, U* b) { // Do compression, since it is less expensive. return a > static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator>(T* a, CompressedPointer b) { // Do compression, since it is less expensive. return static_cast>(a) > b; } // operators>=. template PA_ALWAYS_INLINE constexpr bool operator>=(CompressedPointer a, CompressedPointer b) { return !(a < b); } template PA_ALWAYS_INLINE constexpr bool operator>=(CompressedPointer a, U* b) { // Do compression, since it is less expensive. return a >= static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator>=(T* a, CompressedPointer b) { // Do compression, since it is less expensive. return static_cast>(a) >= b; } #endif // PA_CONFIG(POINTER_COMPRESSION) // Simple wrapper over the raw pointer. template 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 >* = nullptr> PA_ALWAYS_INLINE constexpr explicit UncompressedPointer( const UncompressedPointer& other) : ptr_(other.ptr_) {} template >* = nullptr> PA_ALWAYS_INLINE constexpr explicit UncompressedPointer( UncompressedPointer&& 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 >* = nullptr> PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=( const UncompressedPointer& other) { ptr_ = other.ptr_; return *this; } template >* = nullptr> PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=( UncompressedPointer&& 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 >>* = 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 friend class UncompressedPointer; T* ptr_; }; template PA_ALWAYS_INLINE constexpr void swap(UncompressedPointer& a, UncompressedPointer& b) { a.swap(b); } // operators==. template PA_ALWAYS_INLINE constexpr bool operator==(UncompressedPointer a, UncompressedPointer b) { return a.get() == b.get(); } template PA_ALWAYS_INLINE constexpr bool operator==(UncompressedPointer a, U* b) { return a == static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator==(T* a, UncompressedPointer b) { return b == a; } template PA_ALWAYS_INLINE constexpr bool operator==(UncompressedPointer a, std::nullptr_t) { return !a.is_nonnull(); } template PA_ALWAYS_INLINE constexpr bool operator==(std::nullptr_t, UncompressedPointer b) { return b == nullptr; } // operators!=. template PA_ALWAYS_INLINE constexpr bool operator!=(UncompressedPointer a, UncompressedPointer b) { return !(a == b); } template PA_ALWAYS_INLINE constexpr bool operator!=(UncompressedPointer a, U* b) { return a != static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator!=(T* a, UncompressedPointer b) { return b != a; } template PA_ALWAYS_INLINE constexpr bool operator!=(UncompressedPointer a, std::nullptr_t) { return a.is_nonnull(); } template PA_ALWAYS_INLINE constexpr bool operator!=(std::nullptr_t, UncompressedPointer b) { return b != nullptr; } // operators<. template PA_ALWAYS_INLINE constexpr bool operator<(UncompressedPointer a, UncompressedPointer b) { return a.get() < b.get(); } template PA_ALWAYS_INLINE constexpr bool operator<(UncompressedPointer a, U* b) { return a < static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator<(T* a, UncompressedPointer b) { return static_cast>(a) < b; } // operators<=. template PA_ALWAYS_INLINE constexpr bool operator<=(UncompressedPointer a, UncompressedPointer b) { return a.get() <= b.get(); } template PA_ALWAYS_INLINE constexpr bool operator<=(UncompressedPointer a, U* b) { return a <= static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator<=(T* a, UncompressedPointer b) { return static_cast>(a) <= b; } // operators>. template PA_ALWAYS_INLINE constexpr bool operator>(UncompressedPointer a, UncompressedPointer b) { return !(a <= b); } template PA_ALWAYS_INLINE constexpr bool operator>(UncompressedPointer a, U* b) { return a > static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator>(T* a, UncompressedPointer b) { return static_cast>(a) > b; } // operators>=. template PA_ALWAYS_INLINE constexpr bool operator>=(UncompressedPointer a, UncompressedPointer b) { return !(a < b); } template PA_ALWAYS_INLINE constexpr bool operator>=(UncompressedPointer a, U* b) { return a >= static_cast>(b); } template PA_ALWAYS_INLINE constexpr bool operator>=(T* a, UncompressedPointer b) { return static_cast>(a) >= b; } } // namespace partition_alloc #endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_COMPRESSED_POINTER_H_