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