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 <atomic>
20 #include <cassert>
21 #include <cstdint>
22 #include <tuple>
23 #include <type_traits>
24 
25 #include <folly/Portability.h>
26 
27 #ifdef _WIN32
28 #include <intrin.h>
29 #endif
30 
31 namespace folly {
32 
33 namespace detail {
34 
atomic_compare_exchange_succ(bool cond,std::memory_order succ,std::memory_order fail)35 constexpr std::memory_order atomic_compare_exchange_succ(
36     bool cond, std::memory_order succ, std::memory_order fail) {
37   constexpr auto const relaxed = std::memory_order_relaxed;
38   constexpr auto const release = std::memory_order_release;
39   constexpr auto const acq_rel = std::memory_order_acq_rel;
40 
41   assert(fail != release);
42   assert(fail != acq_rel);
43 
44   auto const bump = succ == release ? acq_rel : succ;
45   auto const high = fail < bump ? bump : fail;
46   return !cond || fail == relaxed ? succ : high;
47 }
48 
49 //  atomic_compare_exchange_succ
50 //
51 //  Return a success order with, conditionally, the failure order mixed in.
52 //
53 //  Until C++17, atomic compare-exchange operations require the success order to
54 //  be at least as strong as the failure order. C++17 drops this requirement. As
55 //  an implication, were this rule in force, an implementation is free to ignore
56 //  the explicit failure order and to infer one from the success order.
57 //
58 //    https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange
59 //
60 //  The libstdc++ library with assertions enabled enforces this rule, including
61 //  under C++17, but only until and not since libstdc++12.
62 //
63 //    https://github.com/gcc-mirror/gcc/commit/dba1ab212292839572fda60df00965e094a11252
64 //
65 //  Clang TSAN ignores the passed failure order and infers failure order from
66 //  success order in atomic compare-exchange operations, which is broken for
67 //  cases like success-release/failure-acquire.
68 //
69 //    https://github.com/google/sanitizers/issues/970
70 //
71 //  Handle all of these cases.
atomic_compare_exchange_succ(std::memory_order succ,std::memory_order fail)72 constexpr std::memory_order atomic_compare_exchange_succ(
73     std::memory_order succ, std::memory_order fail) {
74   constexpr auto const cond = false //
75       || (FOLLY_CPLUSPLUS < 201702L) //
76       || (kGlibcxxVer && kGlibcxxVer < 12 && kGlibcxxAssertions) //
77       || (kIsSanitizeThread && kIsClang);
78   return atomic_compare_exchange_succ(cond, succ, fail);
79 }
80 
81 } // namespace detail
82 
83 template <template <typename> class Atom, typename T>
atomic_compare_exchange_weak_explicit(Atom<T> * obj,type_t<T> * expected,type_t<T> desired,std::memory_order succ,std::memory_order fail)84 bool atomic_compare_exchange_weak_explicit(
85     Atom<T>* obj,
86     type_t<T>* expected,
87     type_t<T> desired,
88     std::memory_order succ,
89     std::memory_order fail) {
90   succ = detail::atomic_compare_exchange_succ(succ, fail);
91   return obj->compare_exchange_weak(*expected, desired, succ, fail);
92 }
93 
94 template <template <typename> class Atom, typename T>
atomic_compare_exchange_strong_explicit(Atom<T> * obj,type_t<T> * expected,type_t<T> desired,std::memory_order succ,std::memory_order fail)95 bool atomic_compare_exchange_strong_explicit(
96     Atom<T>* obj,
97     type_t<T>* expected,
98     type_t<T> desired,
99     std::memory_order succ,
100     std::memory_order fail) {
101   succ = detail::atomic_compare_exchange_succ(succ, fail);
102   return obj->compare_exchange_strong(*expected, desired, succ, fail);
103 }
104 
105 namespace detail {
106 
107 // TODO: Remove the non-default implementations when both gcc and clang
108 // can recognize single bit set/reset patterns and compile them down to locked
109 // bts and btr instructions.
110 //
111 // Currently, at the time of writing it seems like gcc7 and greater can make
112 // this optimization and clang cannot - https://gcc.godbolt.org/z/Q83rxX
113 
114 template <typename Atomic>
atomic_fetch_set_fallback(Atomic & atomic,std::size_t bit,std::memory_order order)115 bool atomic_fetch_set_fallback(
116     Atomic& atomic, std::size_t bit, std::memory_order order) {
117   using Integer = decltype(atomic.load());
118   auto mask = Integer(Integer{0b1} << bit);
119   return (atomic.fetch_or(mask, order) & mask);
120 }
121 
122 template <typename Atomic>
atomic_fetch_reset_fallback(Atomic & atomic,std::size_t bit,std::memory_order order)123 bool atomic_fetch_reset_fallback(
124     Atomic& atomic, std::size_t bit, std::memory_order order) {
125   using Integer = decltype(atomic.load());
126   auto mask = Integer(Integer{0b1} << bit);
127   return (atomic.fetch_and(Integer(~mask), order) & mask);
128 }
129 
130 /**
131  * A simple trait to determine if the given type is an instantiation of
132  * std::atomic
133  */
134 template <typename T>
135 constexpr auto is_atomic = false;
136 template <typename Integer>
137 constexpr auto is_atomic<std::atomic<Integer>> = true;
138 
139 #if FOLLY_X64
140 
141 #if defined(_MSC_VER)
142 
143 template <typename Integer>
atomic_fetch_set_native(std::atomic<Integer> & atomic,std::size_t bit,std::memory_order order)144 inline bool atomic_fetch_set_native(
145     std::atomic<Integer>& atomic, std::size_t bit, std::memory_order order) {
146   static_assert(alignof(std::atomic<Integer>) == alignof(Integer), "");
147   static_assert(sizeof(std::atomic<Integer>) == sizeof(Integer), "");
148   assert(atomic.is_lock_free());
149 
150   if /* constexpr */ (sizeof(Integer) == 4) {
151     return _interlockedbittestandset(
152         reinterpret_cast<volatile long*>(&atomic), static_cast<long>(bit));
153   } else if /* constexpr */ (sizeof(Integer) == 8) {
154     return _interlockedbittestandset64(
155         reinterpret_cast<volatile long long*>(&atomic),
156         static_cast<long long>(bit));
157   } else {
158     assert(sizeof(Integer) != 4 && sizeof(Integer) != 8);
159     return atomic_fetch_set_fallback(atomic, bit, order);
160   }
161 }
162 
163 template <typename Atomic>
atomic_fetch_set_native(Atomic & atomic,std::size_t bit,std::memory_order order)164 inline bool atomic_fetch_set_native(
165     Atomic& atomic, std::size_t bit, std::memory_order order) {
166   static_assert(!std::is_same<Atomic, std::atomic<std::uint32_t>>{}, "");
167   static_assert(!std::is_same<Atomic, std::atomic<std::uint64_t>>{}, "");
168   return atomic_fetch_set_fallback(atomic, bit, order);
169 }
170 
171 template <typename Integer>
atomic_fetch_reset_native(std::atomic<Integer> & atomic,std::size_t bit,std::memory_order order)172 inline bool atomic_fetch_reset_native(
173     std::atomic<Integer>& atomic, std::size_t bit, std::memory_order order) {
174   static_assert(alignof(std::atomic<Integer>) == alignof(Integer), "");
175   static_assert(sizeof(std::atomic<Integer>) == sizeof(Integer), "");
176   assert(atomic.is_lock_free());
177 
178   if /* constexpr */ (sizeof(Integer) == 4) {
179     return _interlockedbittestandreset(
180         reinterpret_cast<volatile long*>(&atomic), static_cast<long>(bit));
181   } else if /* constexpr */ (sizeof(Integer) == 8) {
182     return _interlockedbittestandreset64(
183         reinterpret_cast<volatile long long*>(&atomic),
184         static_cast<long long>(bit));
185   } else {
186     assert(sizeof(Integer) != 4 && sizeof(Integer) != 8);
187     return atomic_fetch_reset_fallback(atomic, bit, order);
188   }
189 }
190 
191 template <typename Atomic>
atomic_fetch_reset_native(Atomic & atomic,std::size_t bit,std::memory_order mo)192 inline bool atomic_fetch_reset_native(
193     Atomic& atomic, std::size_t bit, std::memory_order mo) {
194   static_assert(!std::is_same<Atomic, std::atomic<std::uint32_t>>{}, "");
195   static_assert(!std::is_same<Atomic, std::atomic<std::uint64_t>>{}, "");
196   return atomic_fetch_reset_fallback(atomic, bit, mo);
197 }
198 
199 #else
200 
201 template <typename Integer>
atomic_fetch_set_native(std::atomic<Integer> & atomic,std::size_t bit,std::memory_order order)202 inline bool atomic_fetch_set_native(
203     std::atomic<Integer>& atomic, std::size_t bit, std::memory_order order) {
204   auto previous = false;
205 
206   if /* constexpr */ (sizeof(Integer) == 2) {
207     auto pointer = reinterpret_cast<std::uint16_t*>(&atomic);
208     asm volatile("lock; btsw %1, (%2); setc %0"
209                  : "=r"(previous)
210                  : "ri"(static_cast<std::uint16_t>(bit)), "r"(pointer)
211                  : "memory", "flags");
212   } else if /* constexpr */ (sizeof(Integer) == 4) {
213     auto pointer = reinterpret_cast<std::uint32_t*>(&atomic);
214     asm volatile("lock; btsl %1, (%2); setc %0"
215                  : "=r"(previous)
216                  : "ri"(static_cast<std::uint32_t>(bit)), "r"(pointer)
217                  : "memory", "flags");
218   } else if /* constexpr */ (sizeof(Integer) == 8) {
219     auto pointer = reinterpret_cast<std::uint64_t*>(&atomic);
220     asm volatile("lock; btsq %1, (%2); setc %0"
221                  : "=r"(previous)
222                  : "ri"(static_cast<std::uint64_t>(bit)), "r"(pointer)
223                  : "memory", "flags");
224   } else {
225     assert(sizeof(Integer) == 1);
226     return atomic_fetch_set_fallback(atomic, bit, order);
227   }
228 
229   return previous;
230 }
231 
232 template <typename Atomic>
atomic_fetch_set_native(Atomic & atomic,std::size_t bit,std::memory_order order)233 inline bool atomic_fetch_set_native(
234     Atomic& atomic, std::size_t bit, std::memory_order order) {
235   static_assert(!is_atomic<Atomic>, "");
236   return atomic_fetch_set_fallback(atomic, bit, order);
237 }
238 
239 template <typename Integer>
atomic_fetch_reset_native(std::atomic<Integer> & atomic,std::size_t bit,std::memory_order order)240 inline bool atomic_fetch_reset_native(
241     std::atomic<Integer>& atomic, std::size_t bit, std::memory_order order) {
242   auto previous = false;
243 
244   if /* constexpr */ (sizeof(Integer) == 2) {
245     auto pointer = reinterpret_cast<std::uint16_t*>(&atomic);
246     asm volatile("lock; btrw %1, (%2); setc %0"
247                  : "=r"(previous)
248                  : "ri"(static_cast<std::uint16_t>(bit)), "r"(pointer)
249                  : "memory", "flags");
250   } else if /* constexpr */ (sizeof(Integer) == 4) {
251     auto pointer = reinterpret_cast<std::uint32_t*>(&atomic);
252     asm volatile("lock; btrl %1, (%2); setc %0"
253                  : "=r"(previous)
254                  : "ri"(static_cast<std::uint32_t>(bit)), "r"(pointer)
255                  : "memory", "flags");
256   } else if /* constexpr */ (sizeof(Integer) == 8) {
257     auto pointer = reinterpret_cast<std::uint64_t*>(&atomic);
258     asm volatile("lock; btrq %1, (%2); setc %0"
259                  : "=r"(previous)
260                  : "ri"(static_cast<std::uint64_t>(bit)), "r"(pointer)
261                  : "memory", "flags");
262   } else {
263     assert(sizeof(Integer) == 1);
264     return atomic_fetch_reset_fallback(atomic, bit, order);
265   }
266 
267   return previous;
268 }
269 
270 template <typename Atomic>
atomic_fetch_reset_native(Atomic & atomic,std::size_t bit,std::memory_order order)271 bool atomic_fetch_reset_native(
272     Atomic& atomic, std::size_t bit, std::memory_order order) {
273   static_assert(!is_atomic<Atomic>, "");
274   return atomic_fetch_reset_fallback(atomic, bit, order);
275 }
276 
277 #endif
278 
279 #else
280 
281 template <typename Atomic>
atomic_fetch_set_native(Atomic &,std::size_t,std::memory_order)282 bool atomic_fetch_set_native(Atomic&, std::size_t, std::memory_order) noexcept {
283   // This should never be called on non x86_64 platforms.
284   std::terminate();
285 }
286 template <typename Atomic>
atomic_fetch_reset_native(Atomic &,std::size_t,std::memory_order)287 bool atomic_fetch_reset_native(
288     Atomic&, std::size_t, std::memory_order) noexcept {
289   // This should never be called on non x86_64 platforms.
290   std::terminate();
291 }
292 
293 #endif
294 
295 } // namespace detail
296 
297 template <typename Atomic>
atomic_fetch_set(Atomic & atomic,std::size_t bit,std::memory_order mo)298 bool atomic_fetch_set(Atomic& atomic, std::size_t bit, std::memory_order mo) {
299   using Integer = decltype(atomic.load());
300   static_assert(std::is_unsigned<Integer>{}, "");
301   static_assert(!std::is_const<Atomic>{}, "");
302   assert(bit < (sizeof(Integer) * 8));
303 
304   // do the optimized thing on x86 builds.  Also, some versions of TSAN do not
305   // properly instrument the inline assembly, so avoid it when TSAN is enabled
306   if (folly::kIsArchAmd64 && !folly::kIsSanitizeThread) {
307     return detail::atomic_fetch_set_native(atomic, bit, mo);
308   } else {
309     // otherwise default to the default implementation using fetch_or()
310     return detail::atomic_fetch_set_fallback(atomic, bit, mo);
311   }
312 }
313 
314 template <typename Atomic>
atomic_fetch_reset(Atomic & atomic,std::size_t bit,std::memory_order mo)315 bool atomic_fetch_reset(Atomic& atomic, std::size_t bit, std::memory_order mo) {
316   using Integer = decltype(atomic.load());
317   static_assert(std::is_unsigned<Integer>{}, "");
318   static_assert(!std::is_const<Atomic>{}, "");
319   assert(bit < (sizeof(Integer) * 8));
320 
321   // do the optimized thing on x86 builds.  Also, some versions of TSAN do not
322   // properly instrument the inline assembly, so avoid it when TSAN is enabled
323   if (folly::kIsArchAmd64 && !folly::kIsSanitizeThread) {
324     return detail::atomic_fetch_reset_native(atomic, bit, mo);
325   } else {
326     // otherwise default to the default implementation using fetch_and()
327     return detail::atomic_fetch_reset_fallback(atomic, bit, mo);
328   }
329 }
330 
331 } // namespace folly
332