1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
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 
17 #pragma once
18 
19 #include <folly/Traits.h>
20 #include <folly/synchronization/AsymmetricMemoryBarrier.h>
21 #include <folly/synchronization/Hazptr-fwd.h>
22 #include <folly/synchronization/HazptrDomain.h>
23 #include <folly/synchronization/HazptrRec.h>
24 #include <folly/synchronization/HazptrThrLocal.h>
25 
26 namespace folly {
27 
28 ///
29 /// Classes related to hazard pointer holders.
30 ///
31 
32 /**
33  *  hazptr_holder
34  *
35  *  Class for automatic acquisition and release of hazard pointers,
36  *  and interface for hazard pointer operations.
37  *
38  *  Usage example:
39  *    T* ptr;
40  *    {
41  *      hazptr_holder h = make_hazard_pointer();
42  *      ptr = h.protect(src);
43  *      //  ... *ptr is protected ...
44  *      h.reset_protection();
45  *      // ... *ptr is not protected ...
46  *      ptr = src.load();
47  *      while (!h.try_protect(ptr, src)) {}
48  *      // ... *ptr is protected ...
49  *    }
50  *    // ... *ptr is not protected
51  */
52 template <template <typename> class Atom>
53 class hazptr_holder {
54   hazptr_rec<Atom>* hprec_;
55 
56   template <uint8_t M, template <typename> class A>
57   friend class hazptr_local;
58   friend hazptr_holder<Atom> make_hazard_pointer<Atom>(hazptr_domain<Atom>&);
59   template <uint8_t M, template <typename> class A>
60   friend hazptr_array<M, A> make_hazard_pointer_array();
61 
62   /** Private constructor used by make_hazard_pointer and
63       make_hazard_pointer_array */
hazptr_holder(hazptr_rec<Atom> * hprec)64   FOLLY_ALWAYS_INLINE explicit hazptr_holder(hazptr_rec<Atom>* hprec)
65       : hprec_(hprec) {}
66 
67  public:
68   /** Default empty constructor */
hazptr_holder()69   FOLLY_ALWAYS_INLINE hazptr_holder() noexcept : hprec_(nullptr) {}
70 
71   /** For nonempty construction use make_hazard_pointer. */
72 
73   /** Move constructor */
hazptr_holder(hazptr_holder && rhs)74   FOLLY_ALWAYS_INLINE hazptr_holder(hazptr_holder&& rhs) noexcept
75       : hprec_(std::exchange(rhs.hprec_, nullptr)) {}
76 
77   hazptr_holder(const hazptr_holder&) = delete;
78   hazptr_holder& operator=(const hazptr_holder&) = delete;
79 
80   /** Destructor */
~hazptr_holder()81   FOLLY_ALWAYS_INLINE ~hazptr_holder() {
82     if (LIKELY(hprec_ != nullptr)) {
83       hprec_->reset_hazptr();
84       auto domain = hprec_->domain();
85 #if FOLLY_HAZPTR_THR_LOCAL
86       if (LIKELY(domain == &default_hazptr_domain<Atom>())) {
87         if (LIKELY(hazptr_tc_tls<Atom>().try_put(hprec_))) {
88           return;
89         }
90       }
91 #endif
92       domain->release_hprec(hprec_);
93     }
94   }
95 
96   /** Move operator */
97   FOLLY_ALWAYS_INLINE hazptr_holder& operator=(hazptr_holder&& rhs) noexcept {
98     /* Self-move is a no-op.  */
99     if (LIKELY(this != &rhs)) {
100       this->~hazptr_holder();
101       new (this) hazptr_holder(rhs.hprec_);
102       rhs.hprec_ = nullptr;
103     }
104     return *this;
105   }
106 
107   /** Hazard pointer operations */
108 
109   /** try_protect */
110   template <typename T>
try_protect(T * & ptr,const Atom<T * > & src)111   FOLLY_ALWAYS_INLINE bool try_protect(T*& ptr, const Atom<T*>& src) noexcept {
112     return try_protect(ptr, src, [](T* t) { return t; });
113   }
114 
115   template <typename T, typename Func>
try_protect(T * & ptr,const Atom<T * > & src,Func f)116   FOLLY_ALWAYS_INLINE bool try_protect(
117       T*& ptr, const Atom<T*>& src, Func f) noexcept {
118     /* Filtering the protected pointer through function Func is useful
119        for stealing bits of the pointer word */
120     auto p = ptr;
121     reset_protection(f(p));
122     /*** Full fence ***/ folly::asymmetricLightBarrier();
123     ptr = src.load(std::memory_order_acquire);
124     if (UNLIKELY(p != ptr)) {
125       reset_protection();
126       return false;
127     }
128     return true;
129   }
130 
131   /** protect */
132   template <typename T>
protect(const Atom<T * > & src)133   FOLLY_ALWAYS_INLINE T* protect(const Atom<T*>& src) noexcept {
134     return protect(src, [](T* t) { return t; });
135   }
136 
137   template <typename T, typename Func>
protect(const Atom<T * > & src,Func f)138   FOLLY_ALWAYS_INLINE T* protect(const Atom<T*>& src, Func f) noexcept {
139     T* ptr = src.load(std::memory_order_relaxed);
140     while (!try_protect(ptr, src, f)) {
141       /* Keep trying */;
142     }
143     return ptr;
144   }
145 
146   /** reset_protection */
147   template <typename T>
reset_protection(const T * ptr)148   FOLLY_ALWAYS_INLINE void reset_protection(const T* ptr) noexcept {
149     auto p = static_cast<hazptr_obj<Atom>*>(const_cast<T*>(ptr));
150     DCHECK(hprec_); // UB if *this is empty
151     hprec_->reset_hazptr(p);
152   }
153 
154   FOLLY_ALWAYS_INLINE void reset_protection(std::nullptr_t = nullptr) noexcept {
155     DCHECK(hprec_); // UB if *this is empty
156     hprec_->reset_hazptr();
157   }
158 
159   /* Swap ownership of hazard pointers between hazptr_holder-s. */
160   /* Note: The owned hazard pointers remain unmodified during the swap
161    * and continue to protect the respective objects that they were
162    * protecting before the swap, if any. */
swap(hazptr_holder<Atom> & rhs)163   FOLLY_ALWAYS_INLINE void swap(hazptr_holder<Atom>& rhs) noexcept {
164     std::swap(this->hprec_, rhs.hprec_);
165   }
166 
167   /** Returns a pointer to the owned hazptr_rec */
hprec()168   FOLLY_ALWAYS_INLINE hazptr_rec<Atom>* hprec() const noexcept {
169     return hprec_;
170   }
171 
172   /** Set the pointer to the owned hazptr_rec */
set_hprec(hazptr_rec<Atom> * hprec)173   FOLLY_ALWAYS_INLINE void set_hprec(hazptr_rec<Atom>* hprec) noexcept {
174     hprec_ = hprec;
175   }
176 }; // hazptr_holder
177 
178 /**
179  *  Free function make_hazard_pointer constructs nonempty holder
180  */
181 template <template <typename> class Atom>
make_hazard_pointer(hazptr_domain<Atom> & domain)182 FOLLY_ALWAYS_INLINE hazptr_holder<Atom> make_hazard_pointer(
183     hazptr_domain<Atom>& domain) {
184 #if FOLLY_HAZPTR_THR_LOCAL
185   if (LIKELY(&domain == &default_hazptr_domain<Atom>())) {
186     auto hprec = hazptr_tc_tls<Atom>().try_get();
187     if (LIKELY(hprec != nullptr)) {
188       return hazptr_holder<Atom>(hprec);
189     }
190   }
191 #endif
192   auto hprec = domain.acquire_hprecs(1);
193   DCHECK(hprec);
194   DCHECK(hprec->next_avail() == nullptr);
195   return hazptr_holder<Atom>(hprec);
196 }
197 
198 /**
199  *  Free function. Swaps hazptr_holder-s.
200  */
201 template <template <typename> class Atom>
swap(hazptr_holder<Atom> & lhs,hazptr_holder<Atom> & rhs)202 FOLLY_ALWAYS_INLINE void swap(
203     hazptr_holder<Atom>& lhs, hazptr_holder<Atom>& rhs) noexcept {
204   lhs.swap(rhs);
205 }
206 
207 /**
208  *  Type used by hazptr_array and hazptr_local.
209  */
210 template <template <typename> class Atom>
211 using aligned_hazptr_holder = aligned_storage_for_t<hazptr_holder<Atom>>;
212 
213 /**
214  *  hazptr_array
215  *
216  *  Optimized template for bulk construction and destruction of hazard
217  *  pointers.
218  *
219  *  WARNING: Do not move from or to individual hazptr_holder-s.
220  *  Only move the whole hazptr_array.
221  *
222  *  NOTE: It is allowed to swap an individual hazptr_holder that
223  *  belongs to hazptr_array with (a) a hazptr_holder object, or (b) a
224  *  hazptr_holder that is part of hazptr_array, under the conditions:
225  *  (i) both hazptr_holder-s are either both empty or both nonempty
226  *  and (ii) both belong to the same domain.
227  */
228 template <uint8_t M, template <typename> class Atom>
229 class hazptr_array {
230   static_assert(M > 0, "M must be a positive integer.");
231 
232   aligned_hazptr_holder<Atom> raw_[M];
233   bool empty_;
234 
235   friend hazptr_array<M, Atom> make_hazard_pointer_array<M, Atom>();
236 
237   /** Private constructor used by make_hazard_pointer_array */
hazptr_array(std::nullptr_t)238   FOLLY_ALWAYS_INLINE explicit hazptr_array(std::nullptr_t) noexcept {}
239 
240  public:
241   /** Default empty constructor */
hazptr_array()242   FOLLY_ALWAYS_INLINE hazptr_array() noexcept : empty_(true) {
243     auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
244     for (uint8_t i = 0; i < M; ++i) {
245       new (&h[i]) hazptr_holder<Atom>();
246     }
247   }
248 
249   /** For nonempty construction use make_hazard_pointer_array. */
250 
251   /** Move constructor */
hazptr_array(hazptr_array && other)252   FOLLY_ALWAYS_INLINE hazptr_array(hazptr_array&& other) noexcept {
253     auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
254     auto hother = reinterpret_cast<hazptr_holder<Atom>*>(&other.raw_);
255     for (uint8_t i = 0; i < M; ++i) {
256       new (&h[i]) hazptr_holder<Atom>(std::move(hother[i]));
257     }
258     empty_ = other.empty_;
259     other.empty_ = true;
260   }
261 
262   hazptr_array(const hazptr_array&) = delete;
263   hazptr_array& operator=(const hazptr_array&) = delete;
264 
265   /** Destructor */
~hazptr_array()266   FOLLY_ALWAYS_INLINE ~hazptr_array() {
267     if (empty_) {
268       return;
269     }
270     auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
271 #if FOLLY_HAZPTR_THR_LOCAL
272     auto& tc = hazptr_tc_tls<Atom>();
273     auto count = tc.count();
274     auto cap = hazptr_tc<Atom>::capacity();
275     if (UNLIKELY((M + count) > cap)) {
276       tc.evict((M + count) - cap);
277       count = cap - M;
278     }
279     for (uint8_t i = 0; i < M; ++i) {
280       h[i].reset_protection();
281       tc[count + i].fill(h[i].hprec());
282       h[i].set_hprec(nullptr);
283     }
284     tc.set_count(count + M);
285 #else
286     for (uint8_t i = 0; i < M; ++i) {
287       h[i].~hazptr_holder();
288     }
289 #endif
290   }
291 
292   /** Move operator */
293   FOLLY_ALWAYS_INLINE hazptr_array& operator=(hazptr_array&& other) noexcept {
294     auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
295     for (uint8_t i = 0; i < M; ++i) {
296       h[i] = std::move(other[i]);
297     }
298     empty_ = other.empty_;
299     other.empty_ = true;
300     return *this;
301   }
302 
303   /** [] operator */
304   FOLLY_ALWAYS_INLINE hazptr_holder<Atom>& operator[](uint8_t i) noexcept {
305     auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
306     DCHECK(i < M);
307     return h[i];
308   }
309 }; // hazptr_array
310 
311 /**
312  *  Free function make_hazard_pointer_array constructs nonempty array
313  */
314 template <uint8_t M, template <typename> class Atom>
make_hazard_pointer_array()315 FOLLY_ALWAYS_INLINE hazptr_array<M, Atom> make_hazard_pointer_array() {
316   hazptr_array<M, Atom> a(nullptr);
317   auto h = reinterpret_cast<hazptr_holder<Atom>*>(&a.raw_);
318 #if FOLLY_HAZPTR_THR_LOCAL
319   static_assert(
320       M <= hazptr_tc<Atom>::capacity(),
321       "M must be within the thread cache capacity.");
322   auto& tc = hazptr_tc_tls<Atom>();
323   auto count = tc.count();
324   if (UNLIKELY(M > count)) {
325     tc.fill(M - count);
326     count = M;
327   }
328   uint8_t offset = count - M;
329   for (uint8_t i = 0; i < M; ++i) {
330     auto hprec = tc[offset + i].get();
331     DCHECK(hprec != nullptr);
332     new (&h[i]) hazptr_holder<Atom>(hprec);
333   }
334   tc.set_count(offset);
335 #else
336   auto hprec = hazard_pointer_default_domain<Atom>().acquire_hprecs(M);
337   for (uint8_t i = 0; i < M; ++i) {
338     DCHECK(hprec);
339     auto next = hprec->next_avail();
340     hprec->set_next_avail(nullptr);
341     new (&h[i]) hazptr_holder<Atom>(hprec);
342     hprec = next;
343   }
344   DCHECK(hprec == nullptr);
345 #endif
346   a.empty_ = false;
347   return a;
348 }
349 
350 /**
351  *  hazptr_local
352  *
353  *  Optimized for construction and destruction of one or more
354  *  nonempty hazptr_holder-s with local scope.
355  *
356  *  WARNING 1: Do not move from or to individual hazptr_holder-s.
357  *
358  *  WARNING 2: There can only be one hazptr_local active for the same
359  *  thread at any time. This is not tracked and checked by the
360  *  implementation (except in debug mode) because it would negate the
361  *  performance gains of this class.
362  */
363 template <uint8_t M, template <typename> class Atom>
364 class hazptr_local {
365   static_assert(M > 0, "M must be a positive integer.");
366 
367   aligned_hazptr_holder<Atom> raw_[M];
368 
369  public:
370   /** Constructor */
hazptr_local()371   FOLLY_ALWAYS_INLINE hazptr_local() {
372     auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
373 #if FOLLY_HAZPTR_THR_LOCAL
374     static_assert(
375         M <= hazptr_tc<Atom>::capacity(),
376         "M must be <= hazptr_tc::capacity().");
377     auto& tc = hazptr_tc_tls<Atom>();
378     auto count = tc.count();
379     if (UNLIKELY(M > count)) {
380       tc.fill(M - count);
381     }
382     if (kIsDebug) {
383       DCHECK(!tc.local());
384       tc.set_local(true);
385     }
386     for (uint8_t i = 0; i < M; ++i) {
387       auto hprec = tc[i].get();
388       DCHECK(hprec != nullptr);
389       new (&h[i]) hazptr_holder<Atom>(hprec);
390     }
391 #else
392     for (uint8_t i = 0; i < M; ++i) {
393       new (&h[i]) hazptr_holder<Atom>(make_hazard_pointer<Atom>());
394     }
395 #endif
396   }
397 
398   hazptr_local(const hazptr_local&) = delete;
399   hazptr_local& operator=(const hazptr_local&) = delete;
400   hazptr_local(hazptr_local&&) = delete;
401   hazptr_local& operator=(hazptr_local&&) = delete;
402 
403   /** Destructor */
~hazptr_local()404   FOLLY_ALWAYS_INLINE ~hazptr_local() {
405     auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
406 #if FOLLY_HAZPTR_THR_LOCAL
407     if (kIsDebug) {
408       auto& tc = hazptr_tc_tls<Atom>();
409       DCHECK(tc.local());
410       tc.set_local(false);
411     }
412     for (uint8_t i = 0; i < M; ++i) {
413       h[i].reset_protection();
414     }
415 #else
416     for (uint8_t i = 0; i < M; ++i) {
417       h[i].~hazptr_holder();
418     }
419 #endif
420   }
421 
422   /** [] operator */
423   FOLLY_ALWAYS_INLINE hazptr_holder<Atom>& operator[](uint8_t i) noexcept {
424     auto h = reinterpret_cast<hazptr_holder<Atom>*>(&raw_);
425     DCHECK(i < M);
426     return h[i];
427   }
428 }; // hazptr_local
429 
430 } // namespace folly
431