1 // Copyright 2022 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_ 6 #define INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_ 7 8 #include <atomic> 9 #include <cstddef> 10 #include <type_traits> 11 12 #include "cppgc/internal/api-constants.h" 13 #include "cppgc/internal/logging.h" 14 #include "cppgc/sentinel-pointer.h" 15 #include "v8config.h" // NOLINT(build/include_directory) 16 17 namespace cppgc { 18 namespace internal { 19 20 enum class WriteBarrierSlotType { 21 kCompressed, 22 kUncompressed, 23 }; 24 25 #if defined(CPPGC_POINTER_COMPRESSION) 26 27 #if defined(__clang__) 28 // Attribute const allows the compiler to assume that CageBaseGlobal::g_base_ 29 // doesn't change (e.g. across calls) and thereby avoid redundant loads. 30 #define CPPGC_CONST __attribute__((const)) 31 #define CPPGC_REQUIRE_CONSTANT_INIT \ 32 __attribute__((require_constant_initialization)) 33 #else // defined(__clang__) 34 #define CPPGC_CONST 35 #define CPPGC_REQUIRE_CONSTANT_INIT 36 #endif // defined(__clang__) 37 38 class V8_EXPORT CageBaseGlobal final { 39 public: Get()40 V8_INLINE CPPGC_CONST static uintptr_t Get() { 41 CPPGC_DCHECK(IsBaseConsistent()); 42 return g_base_.base; 43 } 44 IsSet()45 V8_INLINE CPPGC_CONST static bool IsSet() { 46 CPPGC_DCHECK(IsBaseConsistent()); 47 return (g_base_.base & ~kLowerHalfWordMask) != 0; 48 } 49 50 private: 51 // We keep the lower halfword as ones to speed up decompression. 52 static constexpr uintptr_t kLowerHalfWordMask = 53 (api_constants::kCagedHeapReservationAlignment - 1); 54 55 static union alignas(api_constants::kCachelineSize) Base { 56 uintptr_t base; 57 char cache_line[api_constants::kCachelineSize]; 58 } g_base_ CPPGC_REQUIRE_CONSTANT_INIT; 59 60 CageBaseGlobal() = delete; 61 IsBaseConsistent()62 V8_INLINE static bool IsBaseConsistent() { 63 return kLowerHalfWordMask == (g_base_.base & kLowerHalfWordMask); 64 } 65 66 friend class CageBaseGlobalUpdater; 67 }; 68 69 #undef CPPGC_REQUIRE_CONSTANT_INIT 70 #undef CPPGC_CONST 71 72 class V8_TRIVIAL_ABI CompressedPointer final { 73 public: 74 using IntegralType = uint32_t; 75 static constexpr auto kWriteBarrierSlotType = 76 WriteBarrierSlotType::kCompressed; 77 CompressedPointer()78 V8_INLINE CompressedPointer() : value_(0u) {} CompressedPointer(const void* ptr)79 V8_INLINE explicit CompressedPointer(const void* ptr) 80 : value_(Compress(ptr)) {} CompressedPointer(std::nullptr_t)81 V8_INLINE explicit CompressedPointer(std::nullptr_t) : value_(0u) {} CompressedPointer(SentinelPointer)82 V8_INLINE explicit CompressedPointer(SentinelPointer) 83 : value_(kCompressedSentinel) {} 84 Load() const85 V8_INLINE const void* Load() const { return Decompress(value_); } LoadAtomic() const86 V8_INLINE const void* LoadAtomic() const { 87 return Decompress( 88 reinterpret_cast<const std::atomic<IntegralType>&>(value_).load( 89 std::memory_order_relaxed)); 90 } 91 Store(const void* ptr)92 V8_INLINE void Store(const void* ptr) { value_ = Compress(ptr); } StoreAtomic(const void* value)93 V8_INLINE void StoreAtomic(const void* value) { 94 reinterpret_cast<std::atomic<IntegralType>&>(value_).store( 95 Compress(value), std::memory_order_relaxed); 96 } 97 Clear()98 V8_INLINE void Clear() { value_ = 0u; } IsCleared() const99 V8_INLINE bool IsCleared() const { return !value_; } 100 IsSentinel() const101 V8_INLINE bool IsSentinel() const { return value_ == kCompressedSentinel; } 102 GetAsInteger() const103 V8_INLINE uint32_t GetAsInteger() const { return value_; } 104 operator ==(CompressedPointer a, CompressedPointer b)105 V8_INLINE friend bool operator==(CompressedPointer a, CompressedPointer b) { 106 return a.value_ == b.value_; 107 } operator !=(CompressedPointer a, CompressedPointer b)108 V8_INLINE friend bool operator!=(CompressedPointer a, CompressedPointer b) { 109 return a.value_ != b.value_; 110 } operator <(CompressedPointer a, CompressedPointer b)111 V8_INLINE friend bool operator<(CompressedPointer a, CompressedPointer b) { 112 return a.value_ < b.value_; 113 } operator <=(CompressedPointer a, CompressedPointer b)114 V8_INLINE friend bool operator<=(CompressedPointer a, CompressedPointer b) { 115 return a.value_ <= b.value_; 116 } operator >(CompressedPointer a, CompressedPointer b)117 V8_INLINE friend bool operator>(CompressedPointer a, CompressedPointer b) { 118 return a.value_ > b.value_; 119 } operator >=(CompressedPointer a, CompressedPointer b)120 V8_INLINE friend bool operator>=(CompressedPointer a, CompressedPointer b) { 121 return a.value_ >= b.value_; 122 } 123 Compress(const void* ptr)124 static V8_INLINE IntegralType Compress(const void* ptr) { 125 static_assert( 126 SentinelPointer::kSentinelValue == 0b10, 127 "The compression scheme relies on the sentinel encoded as 0b10"); 128 static constexpr size_t kGigaCageMask = 129 ~(api_constants::kCagedHeapReservationAlignment - 1); 130 131 CPPGC_DCHECK(CageBaseGlobal::IsSet()); 132 const uintptr_t base = CageBaseGlobal::Get(); 133 CPPGC_DCHECK(!ptr || ptr == kSentinelPointer || 134 (base & kGigaCageMask) == 135 (reinterpret_cast<uintptr_t>(ptr) & kGigaCageMask)); 136 137 #if defined(CPPGC_2GB_CAGE) 138 // Truncate the pointer. 139 auto compressed = 140 static_cast<IntegralType>(reinterpret_cast<uintptr_t>(ptr)); 141 #else // !defined(CPPGC_2GB_CAGE) 142 const auto uptr = reinterpret_cast<uintptr_t>(ptr); 143 // Shift the pointer by one and truncate. 144 auto compressed = static_cast<IntegralType>(uptr >> 1); 145 #endif // !defined(CPPGC_2GB_CAGE) 146 // Normal compressed pointers must have the MSB set. 147 CPPGC_DCHECK((!compressed || compressed == kCompressedSentinel) || 148 (compressed & (1 << 31))); 149 return compressed; 150 } 151 Decompress(IntegralType ptr)152 static V8_INLINE void* Decompress(IntegralType ptr) { 153 CPPGC_DCHECK(CageBaseGlobal::IsSet()); 154 const uintptr_t base = CageBaseGlobal::Get(); 155 // Treat compressed pointer as signed and cast it to uint64_t, which will 156 // sign-extend it. 157 #if defined(CPPGC_2GB_CAGE) 158 const uint64_t mask = static_cast<uint64_t>(static_cast<int32_t>(ptr)); 159 #else // !defined(CPPGC_2GB_CAGE) 160 // Then, shift the result by one. It's important to shift the unsigned 161 // value, as otherwise it would result in undefined behavior. 162 const uint64_t mask = static_cast<uint64_t>(static_cast<int32_t>(ptr)) << 1; 163 #endif // !defined(CPPGC_2GB_CAGE) 164 return reinterpret_cast<void*>(mask & base); 165 } 166 167 private: 168 #if defined(CPPGC_2GB_CAGE) 169 static constexpr IntegralType kCompressedSentinel = 170 SentinelPointer::kSentinelValue; 171 #else // !defined(CPPGC_2GB_CAGE) 172 static constexpr IntegralType kCompressedSentinel = 173 SentinelPointer::kSentinelValue >> 1; 174 #endif // !defined(CPPGC_2GB_CAGE) 175 // All constructors initialize `value_`. Do not add a default value here as it 176 // results in a non-atomic write on some builds, even when the atomic version 177 // of the constructor is used. 178 IntegralType value_; 179 }; 180 181 #endif // defined(CPPGC_POINTER_COMPRESSION) 182 183 class V8_TRIVIAL_ABI RawPointer final { 184 public: 185 using IntegralType = uintptr_t; 186 static constexpr auto kWriteBarrierSlotType = 187 WriteBarrierSlotType::kUncompressed; 188 RawPointer()189 V8_INLINE RawPointer() : ptr_(nullptr) {} RawPointer(const void* ptr)190 V8_INLINE explicit RawPointer(const void* ptr) : ptr_(ptr) {} 191 Load() const192 V8_INLINE const void* Load() const { return ptr_; } LoadAtomic() const193 V8_INLINE const void* LoadAtomic() const { 194 return reinterpret_cast<const std::atomic<const void*>&>(ptr_).load( 195 std::memory_order_relaxed); 196 } 197 Store(const void* ptr)198 V8_INLINE void Store(const void* ptr) { ptr_ = ptr; } StoreAtomic(const void* ptr)199 V8_INLINE void StoreAtomic(const void* ptr) { 200 reinterpret_cast<std::atomic<const void*>&>(ptr_).store( 201 ptr, std::memory_order_relaxed); 202 } 203 Clear()204 V8_INLINE void Clear() { ptr_ = nullptr; } IsCleared() const205 V8_INLINE bool IsCleared() const { return !ptr_; } 206 IsSentinel() const207 V8_INLINE bool IsSentinel() const { return ptr_ == kSentinelPointer; } 208 GetAsInteger() const209 V8_INLINE uintptr_t GetAsInteger() const { 210 return reinterpret_cast<uintptr_t>(ptr_); 211 } 212 operator ==(RawPointer a, RawPointer b)213 V8_INLINE friend bool operator==(RawPointer a, RawPointer b) { 214 return a.ptr_ == b.ptr_; 215 } operator !=(RawPointer a, RawPointer b)216 V8_INLINE friend bool operator!=(RawPointer a, RawPointer b) { 217 return a.ptr_ != b.ptr_; 218 } operator <(RawPointer a, RawPointer b)219 V8_INLINE friend bool operator<(RawPointer a, RawPointer b) { 220 return a.ptr_ < b.ptr_; 221 } operator <=(RawPointer a, RawPointer b)222 V8_INLINE friend bool operator<=(RawPointer a, RawPointer b) { 223 return a.ptr_ <= b.ptr_; 224 } operator >(RawPointer a, RawPointer b)225 V8_INLINE friend bool operator>(RawPointer a, RawPointer b) { 226 return a.ptr_ > b.ptr_; 227 } operator >=(RawPointer a, RawPointer b)228 V8_INLINE friend bool operator>=(RawPointer a, RawPointer b) { 229 return a.ptr_ >= b.ptr_; 230 } 231 232 private: 233 // All constructors initialize `ptr_`. Do not add a default value here as it 234 // results in a non-atomic write on some builds, even when the atomic version 235 // of the constructor is used. 236 const void* ptr_; 237 }; 238 239 #if defined(CPPGC_POINTER_COMPRESSION) 240 using DefaultMemberStorage = CompressedPointer; 241 #else // !defined(CPPGC_POINTER_COMPRESSION) 242 using DefaultMemberStorage = RawPointer; 243 #endif // !defined(CPPGC_POINTER_COMPRESSION) 244 245 } // namespace internal 246 } // namespace cppgc 247 248 #endif // INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_ 249