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