• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
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 BASE_ALLOCATOR_PARTITION_ALLOCATOR_COMPRESSED_POINTER_H_
6 #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_COMPRESSED_POINTER_H_
7 
8 #include <climits>
9 #include <type_traits>
10 
11 #include "base/allocator/partition_allocator/partition_address_space.h"
12 #include "base/allocator/partition_allocator/partition_alloc_base/bits.h"
13 #include "base/allocator/partition_allocator/partition_alloc_base/compiler_specific.h"
14 #include "base/allocator/partition_allocator/partition_alloc_base/component_export.h"
15 
16 #if PA_CONFIG(POINTER_COMPRESSION)
17 
18 #if !PA_CONFIG(GLUE_CORE_POOLS)
19 #error "Pointer compression only works with glued pools"
20 #endif
21 #if PA_CONFIG(DYNAMICALLY_SELECT_POOL_SIZE)
22 #error "Pointer compression currently supports constant pool size"
23 #endif
24 
25 #endif  // PA_CONFIG(POINTER_COMPRESSION)
26 
27 namespace partition_alloc {
28 
29 namespace internal {
30 
31 template <typename T1, typename T2>
32 constexpr bool IsDecayedSame =
33     std::is_same_v<std::decay_t<T1>, std::decay_t<T2>>;
34 
35 #if PA_CONFIG(POINTER_COMPRESSION)
36 
37 // Pointer compression works by storing only the 'useful' 32-bit part of the
38 // pointer. The other half (the base) is stored in a global variable
39 // (CompressedPointerBaseGlobal::g_base_), which is used on decompression. To
40 // support fast branchless decompression of nullptr, we use the most significant
41 // bit in the compressed pointer to leverage sign-extension (for non-nullptr
42 // pointers, the most significant bit is set, whereas for nullptr it's not).
43 // Using this bit and supporting heaps larger than 4GB relies on having
44 // alignment bits in pointers. Assuming that all pointers point to at least
45 // 8-byte alignment objects, pointer compression can support heaps of size <=
46 // 16GB.
47 // ((3 alignment bits) = (1 bit for sign-extension) + (2 bits for 16GB heap)).
48 //
49 // Example: heap base: 0x4b0'ffffffff
50 //  - g_base: 0x4b3'ffffffff (lower 34 bits set)
51 //  - normal pointer: 0x4b2'a08b6480
52 //    - compression:
53 //      - shift right by 3:        0x96'54116c90
54 //      - truncate:                   0x54116c90
55 //      - mark MSB:                   0xd4116c90
56 //    - decompression:
57 //      - sign-extend:       0xffffffff'd4116c90
58 //      - shift left by 3:   0xfffffffe'a08b6480
59 //      - 'and' with g_base: 0x000004b2'a08b6480
60 //
61 //  - nullptr: 0x00000000'00000000
62 //    - compression:
63 //      - shift right by 3:  0x00000000'00000000
64 //      - truncate:                   0x00000000
65 //      - (don't mark MSB for nullptr)
66 //    - decompression:
67 //      - sign-extend:       0x00000000'00000000
68 //      - shift left by 3:   0x00000000'00000000
69 //      - 'and' with g_base: 0x00000000'00000000
70 //
71 // Pointer compression relies on having both the regular and the BRP pool (core
72 // pools) 'glued', so that the same base could be used for both. For simplicity,
73 // the configurations with dynamically selected pool size are not supported.
74 // However, they can be at the cost of performing an extra load for
75 // core-pools-shift-size on both compression and decompression.
76 
77 class CompressedPointerBaseGlobal final {
78  public:
79   static constexpr size_t kUsefulBits =
80       base::bits::CountTrailingZeroBits(PartitionAddressSpace::CorePoolsSize());
81   static_assert(kUsefulBits >= sizeof(uint32_t) * CHAR_BIT);
82   static constexpr size_t kBitsToShift =
83       kUsefulBits - sizeof(uint32_t) * CHAR_BIT;
84 
85   CompressedPointerBaseGlobal() = delete;
86 
87   // Attribute const allows the compiler to assume that
88   // CompressedPointerBaseGlobal::g_base_ doesn't change (e.g. across calls) and
89   // thereby avoid redundant loads.
Get()90   PA_ALWAYS_INLINE __attribute__((const)) static uintptr_t Get() {
91     PA_DCHECK(IsBaseConsistent());
92     return g_base_.base;
93   }
94 
IsSet()95   PA_ALWAYS_INLINE static bool IsSet() {
96     PA_DCHECK(IsBaseConsistent());
97     return (g_base_.base & ~kUsefulBitsMask) != 0;
98   }
99 
100  private:
101   static constexpr uintptr_t kUsefulBitsMask =
102       PartitionAddressSpace::CorePoolsSize() - 1;
103 
104   static union alignas(kPartitionCachelineSize)
PA_COMPONENT_EXPORT(PARTITION_ALLOC)105       PA_COMPONENT_EXPORT(PARTITION_ALLOC) Base {
106     uintptr_t base;
107     char cache_line[kPartitionCachelineSize];
108   } g_base_ PA_CONSTINIT;
109 
IsBaseConsistent()110   PA_ALWAYS_INLINE static bool IsBaseConsistent() {
111     return kUsefulBitsMask == (g_base_.base & kUsefulBitsMask);
112   }
113 
114   static void SetBase(uintptr_t base);
115   static void ResetBaseForTesting();
116 
117   friend class PartitionAddressSpace;
118 };
119 
120 #endif  // PA_CONFIG(POINTER_COMPRESSION)
121 
122 }  // namespace internal
123 
124 #if PA_CONFIG(POINTER_COMPRESSION)
125 
126 template <typename T>
127 class PA_TRIVIAL_ABI CompressedPointer final {
128  public:
129   using UnderlyingType = uint32_t;
130 
131   PA_ALWAYS_INLINE constexpr CompressedPointer() = default;
CompressedPointer(T * ptr)132   PA_ALWAYS_INLINE explicit CompressedPointer(T* ptr) : value_(Compress(ptr)) {}
CompressedPointer(std::nullptr_t)133   PA_ALWAYS_INLINE constexpr explicit CompressedPointer(std::nullptr_t)
134       : value_(0u) {}
135 
136   PA_ALWAYS_INLINE constexpr CompressedPointer(const CompressedPointer&) =
137       default;
138   PA_ALWAYS_INLINE constexpr CompressedPointer(
139       CompressedPointer&& other) noexcept = default;
140 
141   template <typename U,
142             std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr>
CompressedPointer(const CompressedPointer<U> & other)143   PA_ALWAYS_INLINE constexpr CompressedPointer(
144       const CompressedPointer<U>& other) {
145     if constexpr (internal::IsDecayedSame<T, U>) {
146       // When pointers have the same type modulo constness, avoid the
147       // compress-decompress round.
148       value_ = other.value_;
149     } else {
150       // When the types are different, perform the round, because the pointer
151       // may need to be adjusted.
152       // TODO(1376980): Avoid the cycle here.
153       value_ = Compress(other.get());
154     }
155   }
156 
157   template <typename U,
158             std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr>
CompressedPointer(CompressedPointer<U> && other)159   PA_ALWAYS_INLINE constexpr CompressedPointer(
160       CompressedPointer<U>&& other) noexcept
161       : CompressedPointer(other) {}
162 
163   ~CompressedPointer() = default;
164 
165   PA_ALWAYS_INLINE constexpr CompressedPointer& operator=(
166       const CompressedPointer&) = default;
167   PA_ALWAYS_INLINE constexpr CompressedPointer& operator=(
168       CompressedPointer&& other) noexcept = default;
169 
170   template <typename U,
171             std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr>
172   PA_ALWAYS_INLINE constexpr CompressedPointer& operator=(
173       const CompressedPointer<U>& other) {
174     CompressedPointer copy(other);
175     value_ = copy.value_;
176     return *this;
177   }
178 
179   template <typename U,
180             std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr>
181   PA_ALWAYS_INLINE constexpr CompressedPointer& operator=(
182       CompressedPointer<U>&& other) noexcept {
183     *this = other;
184     return *this;
185   }
186 
187   // Don't perform compression when assigning to nullptr.
188   PA_ALWAYS_INLINE constexpr CompressedPointer& operator=(std::nullptr_t) {
189     value_ = 0u;
190     return *this;
191   }
192 
get()193   PA_ALWAYS_INLINE T* get() const { return Decompress(value_); }
194 
is_nonnull()195   PA_ALWAYS_INLINE constexpr bool is_nonnull() const { return value_; }
196 
GetAsIntegral()197   PA_ALWAYS_INLINE constexpr UnderlyingType GetAsIntegral() const {
198     return value_;
199   }
200 
201   PA_ALWAYS_INLINE constexpr explicit operator bool() const {
202     return is_nonnull();
203   }
204 
205   template <typename U = T,
206             std::enable_if_t<!std::is_void_v<std::remove_cv_t<U>>>* = nullptr>
207   PA_ALWAYS_INLINE U& operator*() const {
208     PA_DCHECK(is_nonnull());
209     return *get();
210   }
211 
212   PA_ALWAYS_INLINE T* operator->() const {
213     PA_DCHECK(is_nonnull());
214     return get();
215   }
216 
swap(CompressedPointer & other)217   PA_ALWAYS_INLINE constexpr void swap(CompressedPointer& other) {
218     std::swap(value_, other.value_);
219   }
220 
221  private:
222   template <typename>
223   friend class CompressedPointer;
224 
225   static constexpr size_t kBitsForSignExtension = 1;
226   static constexpr size_t kOverallBitsToShift =
227       internal::CompressedPointerBaseGlobal::kBitsToShift +
228       kBitsForSignExtension;
229 
Compress(T * ptr)230   PA_ALWAYS_INLINE static UnderlyingType Compress(T* ptr) {
231     static constexpr size_t kMinimalRequiredAlignment = 8;
232     static_assert((1 << kOverallBitsToShift) == kMinimalRequiredAlignment);
233 
234 #if BUILDFLAG(PA_DCHECK_IS_ON)
235     PA_DCHECK(reinterpret_cast<uintptr_t>(ptr) % kMinimalRequiredAlignment ==
236               0);
237     PA_DCHECK(internal::CompressedPointerBaseGlobal::IsSet());
238 
239     const uintptr_t base = internal::CompressedPointerBaseGlobal::Get();
240     static constexpr size_t kCorePoolsBaseMask =
241         ~(internal::PartitionAddressSpace::CorePoolsSize() - 1);
242     PA_DCHECK(!ptr ||
243               (base & kCorePoolsBaseMask) ==
244                   (reinterpret_cast<uintptr_t>(ptr) & kCorePoolsBaseMask));
245 #endif  // BUILDFLAG(PA_DCHECK_IS_ON)
246 
247     const auto uptr = reinterpret_cast<uintptr_t>(ptr);
248     // Shift the pointer and truncate.
249     auto compressed = static_cast<UnderlyingType>(uptr >> kOverallBitsToShift);
250     // If the pointer is non-null, mark the most-significant-bit to sign-extend
251     // it on decompression. Assuming compression is a significantly less
252     // frequent operation, we let more work here in favor of faster
253     // decompression.
254     // TODO(1376980): Avoid this by overreserving the heap.
255     if (compressed) {
256       compressed |= (1u << (sizeof(uint32_t) * CHAR_BIT - 1));
257     }
258 
259     return compressed;
260   }
261 
Decompress(UnderlyingType ptr)262   PA_ALWAYS_INLINE static T* Decompress(UnderlyingType ptr) {
263     PA_DCHECK(internal::CompressedPointerBaseGlobal::IsSet());
264     const uintptr_t base = internal::CompressedPointerBaseGlobal::Get();
265     // Treat compressed pointer as signed and cast it to uint64_t, which will
266     // sign-extend it. Then, shift the result by one. It's important to shift
267     // the already unsigned value, as otherwise it would result in undefined
268     // behavior.
269     const uint64_t mask = static_cast<uint64_t>(static_cast<int32_t>(ptr))
270                           << (kOverallBitsToShift);
271     return reinterpret_cast<T*>(mask & base);
272   }
273 
274   UnderlyingType value_;
275 };
276 
277 template <typename T>
swap(CompressedPointer<T> & a,CompressedPointer<T> & b)278 PA_ALWAYS_INLINE constexpr void swap(CompressedPointer<T>& a,
279                                      CompressedPointer<T>& b) {
280   a.swap(b);
281 }
282 
283 // operators==.
284 template <typename T, typename U>
285 PA_ALWAYS_INLINE bool operator==(CompressedPointer<T> a,
286                                  CompressedPointer<U> b) {
287   if constexpr (internal::IsDecayedSame<T, U>) {
288     // When pointers have the same type modulo constness, simply compare
289     // compressed values.
290     return a.GetAsIntegral() == b.GetAsIntegral();
291   } else {
292     // When the types are different, compare decompressed pointers, because the
293     // pointers may need to be adjusted.
294     // TODO(1376980): Avoid decompression here.
295     return a.get() == b.get();
296   }
297 }
298 
299 template <typename T, typename U>
300 PA_ALWAYS_INLINE constexpr bool operator==(CompressedPointer<T> a, U* b) {
301   // Do compression, since it is less expensive.
302   return a == static_cast<CompressedPointer<U>>(b);
303 }
304 
305 template <typename T, typename U>
306 PA_ALWAYS_INLINE constexpr bool operator==(T* a, CompressedPointer<U> b) {
307   return b == a;
308 }
309 
310 template <typename T>
311 PA_ALWAYS_INLINE constexpr bool operator==(CompressedPointer<T> a,
312                                            std::nullptr_t) {
313   return !a.is_nonnull();
314 }
315 
316 template <typename T, typename U>
317 PA_ALWAYS_INLINE constexpr bool operator==(std::nullptr_t,
318                                            CompressedPointer<U> b) {
319   return b == nullptr;
320 }
321 
322 // operators!=.
323 template <typename T, typename U>
324 PA_ALWAYS_INLINE constexpr bool operator!=(CompressedPointer<T> a,
325                                            CompressedPointer<U> b) {
326   return !(a == b);
327 }
328 
329 template <typename T, typename U>
330 PA_ALWAYS_INLINE constexpr bool operator!=(CompressedPointer<T> a, U* b) {
331   // Do compression, since it is less expensive.
332   return a != static_cast<CompressedPointer<U>>(b);
333 }
334 
335 template <typename T, typename U>
336 PA_ALWAYS_INLINE constexpr bool operator!=(T* a, CompressedPointer<U> b) {
337   return b != a;
338 }
339 
340 template <typename T>
341 PA_ALWAYS_INLINE constexpr bool operator!=(CompressedPointer<T> a,
342                                            std::nullptr_t) {
343   return a.is_nonnull();
344 }
345 
346 template <typename T, typename U>
347 PA_ALWAYS_INLINE constexpr bool operator!=(std::nullptr_t,
348                                            CompressedPointer<U> b) {
349   return b != nullptr;
350 }
351 
352 // operators<.
353 template <typename T, typename U>
354 PA_ALWAYS_INLINE constexpr bool operator<(CompressedPointer<T> a,
355                                           CompressedPointer<U> b) {
356   if constexpr (internal::IsDecayedSame<T, U>) {
357     // When pointers have the same type modulo constness, simply compare
358     // compressed values.
359     return a.GetAsIntegral() < b.GetAsIntegral();
360   } else {
361     // When the types are different, compare decompressed pointers, because the
362     // pointers may need to be adjusted.
363     // TODO(1376980): Avoid decompression here.
364     return a.get() < b.get();
365   }
366 }
367 
368 template <typename T, typename U>
369 PA_ALWAYS_INLINE constexpr bool operator<(CompressedPointer<T> a, U* b) {
370   // Do compression, since it is less expensive.
371   return a < static_cast<CompressedPointer<U>>(b);
372 }
373 
374 template <typename T, typename U>
375 PA_ALWAYS_INLINE constexpr bool operator<(T* a, CompressedPointer<U> b) {
376   // Do compression, since it is less expensive.
377   return static_cast<CompressedPointer<T>>(a) < b;
378 }
379 
380 // operators<=.
381 template <typename T, typename U>
382 PA_ALWAYS_INLINE constexpr bool operator<=(CompressedPointer<T> a,
383                                            CompressedPointer<U> b) {
384   if constexpr (internal::IsDecayedSame<T, U>) {
385     // When pointers have the same type modulo constness, simply compare
386     // compressed values.
387     return a.GetAsIntegral() <= b.GetAsIntegral();
388   } else {
389     // When the types are different, compare decompressed pointers, because the
390     // pointers may need to be adjusted.
391     // TODO(1376980): Avoid decompression here.
392     return a.get() <= b.get();
393   }
394 }
395 
396 template <typename T, typename U>
397 PA_ALWAYS_INLINE constexpr bool operator<=(CompressedPointer<T> a, U* b) {
398   // Do compression, since it is less expensive.
399   return a <= static_cast<CompressedPointer<U>>(b);
400 }
401 
402 template <typename T, typename U>
403 PA_ALWAYS_INLINE constexpr bool operator<=(T* a, CompressedPointer<U> b) {
404   // Do compression, since it is less expensive.
405   return static_cast<CompressedPointer<T>>(a) <= b;
406 }
407 
408 // operators>.
409 template <typename T, typename U>
410 PA_ALWAYS_INLINE constexpr bool operator>(CompressedPointer<T> a,
411                                           CompressedPointer<U> b) {
412   return !(a <= b);
413 }
414 
415 template <typename T, typename U>
416 PA_ALWAYS_INLINE constexpr bool operator>(CompressedPointer<T> a, U* b) {
417   // Do compression, since it is less expensive.
418   return a > static_cast<CompressedPointer<U>>(b);
419 }
420 
421 template <typename T, typename U>
422 PA_ALWAYS_INLINE constexpr bool operator>(T* a, CompressedPointer<U> b) {
423   // Do compression, since it is less expensive.
424   return static_cast<CompressedPointer<T>>(a) > b;
425 }
426 
427 // operators>=.
428 template <typename T, typename U>
429 PA_ALWAYS_INLINE constexpr bool operator>=(CompressedPointer<T> a,
430                                            CompressedPointer<U> b) {
431   return !(a < b);
432 }
433 
434 template <typename T, typename U>
435 PA_ALWAYS_INLINE constexpr bool operator>=(CompressedPointer<T> a, U* b) {
436   // Do compression, since it is less expensive.
437   return a >= static_cast<CompressedPointer<U>>(b);
438 }
439 
440 template <typename T, typename U>
441 PA_ALWAYS_INLINE constexpr bool operator>=(T* a, CompressedPointer<U> b) {
442   // Do compression, since it is less expensive.
443   return static_cast<CompressedPointer<T>>(a) >= b;
444 }
445 
446 #endif  // PA_CONFIG(POINTER_COMPRESSION)
447 
448 // Simple wrapper over the raw pointer.
449 template <typename T>
450 class PA_TRIVIAL_ABI UncompressedPointer final {
451  public:
452   PA_ALWAYS_INLINE constexpr UncompressedPointer() = default;
UncompressedPointer(T * ptr)453   PA_ALWAYS_INLINE constexpr explicit UncompressedPointer(T* ptr) : ptr_(ptr) {}
UncompressedPointer(std::nullptr_t)454   PA_ALWAYS_INLINE constexpr explicit UncompressedPointer(std::nullptr_t)
455       : ptr_(nullptr) {}
456 
457   PA_ALWAYS_INLINE constexpr UncompressedPointer(const UncompressedPointer&) =
458       default;
459   PA_ALWAYS_INLINE constexpr UncompressedPointer(
460       UncompressedPointer&& other) noexcept = default;
461 
462   template <typename U,
463             std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr>
UncompressedPointer(const UncompressedPointer<U> & other)464   PA_ALWAYS_INLINE constexpr explicit UncompressedPointer(
465       const UncompressedPointer<U>& other)
466       : ptr_(other.ptr_) {}
467 
468   template <typename U,
469             std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr>
UncompressedPointer(UncompressedPointer<U> && other)470   PA_ALWAYS_INLINE constexpr explicit UncompressedPointer(
471       UncompressedPointer<U>&& other) noexcept
472       : ptr_(std::move(other.ptr_)) {}
473 
474   ~UncompressedPointer() = default;
475 
476   PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=(
477       const UncompressedPointer&) = default;
478   PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=(
479       UncompressedPointer&& other) noexcept = default;
480 
481   template <typename U,
482             std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr>
483   PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=(
484       const UncompressedPointer<U>& other) {
485     ptr_ = other.ptr_;
486     return *this;
487   }
488 
489   template <typename U,
490             std::enable_if_t<std::is_convertible_v<U*, T*>>* = nullptr>
491   PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=(
492       UncompressedPointer<U>&& other) noexcept {
493     ptr_ = std::move(other.ptr_);
494     return *this;
495   }
496 
497   PA_ALWAYS_INLINE constexpr UncompressedPointer& operator=(std::nullptr_t) {
498     ptr_ = nullptr;
499     return *this;
500   }
501 
get()502   PA_ALWAYS_INLINE constexpr T* get() const { return ptr_; }
503 
is_nonnull()504   PA_ALWAYS_INLINE constexpr bool is_nonnull() const { return ptr_; }
505 
506   PA_ALWAYS_INLINE constexpr explicit operator bool() const {
507     return is_nonnull();
508   }
509 
510   template <typename U = T,
511             std::enable_if_t<!std::is_void_v<std::remove_cv_t<U>>>* = nullptr>
512   PA_ALWAYS_INLINE constexpr U& operator*() const {
513     PA_DCHECK(is_nonnull());
514     return *get();
515   }
516 
517   PA_ALWAYS_INLINE constexpr T* operator->() const {
518     PA_DCHECK(is_nonnull());
519     return get();
520   }
521 
swap(UncompressedPointer & other)522   PA_ALWAYS_INLINE constexpr void swap(UncompressedPointer& other) {
523     std::swap(ptr_, other.ptr_);
524   }
525 
526  private:
527   template <typename>
528   friend class UncompressedPointer;
529 
530   T* ptr_;
531 };
532 
533 template <typename T>
swap(UncompressedPointer<T> & a,UncompressedPointer<T> & b)534 PA_ALWAYS_INLINE constexpr void swap(UncompressedPointer<T>& a,
535                                      UncompressedPointer<T>& b) {
536   a.swap(b);
537 }
538 
539 // operators==.
540 template <typename T, typename U>
541 PA_ALWAYS_INLINE constexpr bool operator==(UncompressedPointer<T> a,
542                                            UncompressedPointer<U> b) {
543   return a.get() == b.get();
544 }
545 
546 template <typename T, typename U>
547 PA_ALWAYS_INLINE constexpr bool operator==(UncompressedPointer<T> a, U* b) {
548   return a == static_cast<UncompressedPointer<U>>(b);
549 }
550 
551 template <typename T, typename U>
552 PA_ALWAYS_INLINE constexpr bool operator==(T* a, UncompressedPointer<U> b) {
553   return b == a;
554 }
555 
556 template <typename T>
557 PA_ALWAYS_INLINE constexpr bool operator==(UncompressedPointer<T> a,
558                                            std::nullptr_t) {
559   return !a.is_nonnull();
560 }
561 
562 template <typename T, typename U>
563 PA_ALWAYS_INLINE constexpr bool operator==(std::nullptr_t,
564                                            UncompressedPointer<U> b) {
565   return b == nullptr;
566 }
567 
568 // operators!=.
569 template <typename T, typename U>
570 PA_ALWAYS_INLINE constexpr bool operator!=(UncompressedPointer<T> a,
571                                            UncompressedPointer<U> b) {
572   return !(a == b);
573 }
574 
575 template <typename T, typename U>
576 PA_ALWAYS_INLINE constexpr bool operator!=(UncompressedPointer<T> a, U* b) {
577   return a != static_cast<UncompressedPointer<U>>(b);
578 }
579 
580 template <typename T, typename U>
581 PA_ALWAYS_INLINE constexpr bool operator!=(T* a, UncompressedPointer<U> b) {
582   return b != a;
583 }
584 
585 template <typename T>
586 PA_ALWAYS_INLINE constexpr bool operator!=(UncompressedPointer<T> a,
587                                            std::nullptr_t) {
588   return a.is_nonnull();
589 }
590 
591 template <typename T, typename U>
592 PA_ALWAYS_INLINE constexpr bool operator!=(std::nullptr_t,
593                                            UncompressedPointer<U> b) {
594   return b != nullptr;
595 }
596 
597 // operators<.
598 template <typename T, typename U>
599 PA_ALWAYS_INLINE constexpr bool operator<(UncompressedPointer<T> a,
600                                           UncompressedPointer<U> b) {
601   return a.get() < b.get();
602 }
603 
604 template <typename T, typename U>
605 PA_ALWAYS_INLINE constexpr bool operator<(UncompressedPointer<T> a, U* b) {
606   return a < static_cast<UncompressedPointer<U>>(b);
607 }
608 
609 template <typename T, typename U>
610 PA_ALWAYS_INLINE constexpr bool operator<(T* a, UncompressedPointer<U> b) {
611   return static_cast<UncompressedPointer<T>>(a) < b;
612 }
613 
614 // operators<=.
615 template <typename T, typename U>
616 PA_ALWAYS_INLINE constexpr bool operator<=(UncompressedPointer<T> a,
617                                            UncompressedPointer<U> b) {
618   return a.get() <= b.get();
619 }
620 
621 template <typename T, typename U>
622 PA_ALWAYS_INLINE constexpr bool operator<=(UncompressedPointer<T> a, U* b) {
623   return a <= static_cast<UncompressedPointer<U>>(b);
624 }
625 
626 template <typename T, typename U>
627 PA_ALWAYS_INLINE constexpr bool operator<=(T* a, UncompressedPointer<U> b) {
628   return static_cast<UncompressedPointer<T>>(a) <= b;
629 }
630 
631 // operators>.
632 template <typename T, typename U>
633 PA_ALWAYS_INLINE constexpr bool operator>(UncompressedPointer<T> a,
634                                           UncompressedPointer<U> b) {
635   return !(a <= b);
636 }
637 
638 template <typename T, typename U>
639 PA_ALWAYS_INLINE constexpr bool operator>(UncompressedPointer<T> a, U* b) {
640   return a > static_cast<UncompressedPointer<U>>(b);
641 }
642 
643 template <typename T, typename U>
644 PA_ALWAYS_INLINE constexpr bool operator>(T* a, UncompressedPointer<U> b) {
645   return static_cast<UncompressedPointer<T>>(a) > b;
646 }
647 
648 // operators>=.
649 template <typename T, typename U>
650 PA_ALWAYS_INLINE constexpr bool operator>=(UncompressedPointer<T> a,
651                                            UncompressedPointer<U> b) {
652   return !(a < b);
653 }
654 
655 template <typename T, typename U>
656 PA_ALWAYS_INLINE constexpr bool operator>=(UncompressedPointer<T> a, U* b) {
657   return a >= static_cast<UncompressedPointer<U>>(b);
658 }
659 
660 template <typename T, typename U>
661 PA_ALWAYS_INLINE constexpr bool operator>=(T* a, UncompressedPointer<U> b) {
662   return static_cast<UncompressedPointer<T>>(a) >= b;
663 }
664 
665 }  // namespace partition_alloc
666 
667 #endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_COMPRESSED_POINTER_H_
668