PackedSyncPtr.h 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. /*
  2. * Copyright 2011-present Facebook, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #pragma once
  17. #include <type_traits>
  18. #include <glog/logging.h>
  19. #include <folly/Portability.h>
  20. #include <folly/synchronization/SmallLocks.h>
  21. #if !FOLLY_X64 && !FOLLY_PPC64 && !FOLLY_AARCH64
  22. #error "PackedSyncPtr is x64, ppc64 or aarch64 specific code."
  23. #endif
  24. /*
  25. * An 8-byte pointer with an integrated spin lock and 15-bit integer
  26. * (you can use this for a size of the allocation, if you want, or
  27. * something else, or nothing).
  28. *
  29. * This is using an x64-specific detail about the effective virtual
  30. * address space. Long story short: the upper two bytes of all our
  31. * pointers will be zero in reality---and if you have a couple billion
  32. * such pointers in core, it makes pretty good sense to try to make
  33. * use of that memory. The exact details can be perused here:
  34. *
  35. * http://en.wikipedia.org/wiki/X86-64#Canonical_form_addresses
  36. *
  37. * This is not a "smart" pointer: nothing automagical is going on
  38. * here. Locking is up to the user. Resource deallocation is up to
  39. * the user. Locks are never acquired or released outside explicit
  40. * calls to lock() and unlock().
  41. *
  42. * Change the value of the raw pointer with set(), but you must hold
  43. * the lock when calling this function if multiple threads could be
  44. * using this class.
  45. *
  46. * TODO(jdelong): should we use the low order bit for the lock, so we
  47. * get a whole 16-bits for our integer? (There's also 2 more bits
  48. * down there if the pointer comes from malloc.)
  49. *
  50. * @author Spencer Ahrens <sahrens@fb.com>
  51. * @author Jordan DeLong <delong.j@fb.com>
  52. */
  53. namespace folly {
  54. template <class T>
  55. class PackedSyncPtr {
  56. // This just allows using this class even with T=void. Attempting
  57. // to use the operator* or operator[] on a PackedSyncPtr<void> will
  58. // still properly result in a compile error.
  59. typedef typename std::add_lvalue_reference<T>::type reference;
  60. public:
  61. /*
  62. * If you default construct one of these, you must call this init()
  63. * function before using it.
  64. *
  65. * (We are avoiding a constructor to ensure gcc allows us to put
  66. * this class in packed structures.)
  67. */
  68. void init(T* initialPtr = nullptr, uint16_t initialExtra = 0) {
  69. auto intPtr = reinterpret_cast<uintptr_t>(initialPtr);
  70. CHECK(!(intPtr >> 48));
  71. data_.init(intPtr);
  72. setExtra(initialExtra);
  73. }
  74. /*
  75. * Sets a new pointer. You must hold the lock when calling this
  76. * function, or else be able to guarantee no other threads could be
  77. * using this PackedSyncPtr<>.
  78. */
  79. void set(T* t) {
  80. auto intPtr = reinterpret_cast<uintptr_t>(t);
  81. auto shiftedExtra = uintptr_t(extra()) << 48;
  82. CHECK(!(intPtr >> 48));
  83. data_.setData(intPtr | shiftedExtra);
  84. }
  85. /*
  86. * Get the pointer.
  87. *
  88. * You can call any of these without holding the lock, with the
  89. * normal types of behavior you'll get on x64 from reading a pointer
  90. * without locking.
  91. */
  92. T* get() const {
  93. return reinterpret_cast<T*>(data_.getData() & (-1ull >> 16));
  94. }
  95. T* operator->() const {
  96. return get();
  97. }
  98. reference operator*() const {
  99. return *get();
  100. }
  101. reference operator[](std::ptrdiff_t i) const {
  102. return get()[i];
  103. }
  104. // Synchronization (logically const, even though this mutates our
  105. // locked state: you can lock a const PackedSyncPtr<T> to read it).
  106. void lock() const {
  107. data_.lock();
  108. }
  109. void unlock() const {
  110. data_.unlock();
  111. }
  112. bool try_lock() const {
  113. return data_.try_lock();
  114. }
  115. /*
  116. * Access extra data stored in unused bytes of the pointer.
  117. *
  118. * It is ok to call this without holding the lock.
  119. */
  120. uint16_t extra() const {
  121. return data_.getData() >> 48;
  122. }
  123. /*
  124. * Don't try to put anything into this that has the high bit set:
  125. * that's what we're using for the mutex.
  126. *
  127. * Don't call this without holding the lock.
  128. */
  129. void setExtra(uint16_t extra) {
  130. CHECK(!(extra & 0x8000));
  131. auto ptr = data_.getData() & (-1ull >> 16);
  132. data_.setData((uintptr_t(extra) << 48) | ptr);
  133. }
  134. private:
  135. PicoSpinLock<uintptr_t> data_;
  136. } FOLLY_PACK_ATTR;
  137. static_assert(
  138. std::is_pod<PackedSyncPtr<void>>::value,
  139. "PackedSyncPtr must be kept a POD type.");
  140. static_assert(
  141. sizeof(PackedSyncPtr<void>) == 8,
  142. "PackedSyncPtr should be only 8 bytes---something is "
  143. "messed up");
  144. template <typename T>
  145. std::ostream& operator<<(std::ostream& os, const PackedSyncPtr<T>& ptr) {
  146. os << "PackedSyncPtr(" << ptr.get() << ", " << ptr.extra() << ")";
  147. return os;
  148. }
  149. } // namespace folly