1 // Copyright 2017 The Abseil Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef ABSL_BASE_INTERNAL_ATOMIC_HOOK_H_ 16 #define ABSL_BASE_INTERNAL_ATOMIC_HOOK_H_ 17 18 #include <atomic> 19 #include <cassert> 20 #include <cstdint> 21 #include <utility> 22 23 #include "absl/base/attributes.h" 24 #include "absl/base/config.h" 25 26 #if defined(_MSC_VER) && !defined(__clang__) 27 #define ABSL_HAVE_WORKING_CONSTEXPR_STATIC_INIT 0 28 #else 29 #define ABSL_HAVE_WORKING_CONSTEXPR_STATIC_INIT 1 30 #endif 31 32 #if defined(_MSC_VER) 33 #define ABSL_HAVE_WORKING_ATOMIC_POINTER 0 34 #else 35 #define ABSL_HAVE_WORKING_ATOMIC_POINTER 1 36 #endif 37 38 namespace absl { 39 ABSL_NAMESPACE_BEGIN 40 namespace base_internal { 41 42 template <typename T> 43 class AtomicHook; 44 45 // To workaround AtomicHook not being constant-initializable on some platforms, 46 // prefer to annotate instances with `ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES` 47 // instead of `ABSL_CONST_INIT`. 48 #if ABSL_HAVE_WORKING_CONSTEXPR_STATIC_INIT 49 #define ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES ABSL_CONST_INIT 50 #else 51 #define ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES 52 #endif 53 54 // `AtomicHook` is a helper class, templatized on a raw function pointer type, 55 // for implementing Abseil customization hooks. It is a callable object that 56 // dispatches to the registered hook. Objects of type `AtomicHook` must have 57 // static or thread storage duration. 58 // 59 // A default constructed object performs a no-op (and returns a default 60 // constructed object) if no hook has been registered. 61 // 62 // Hooks can be pre-registered via constant initialization, for example: 63 // 64 // ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES static AtomicHook<void(*)()> 65 // my_hook(DefaultAction); 66 // 67 // and then changed at runtime via a call to `Store()`. 68 // 69 // Reads and writes guarantee memory_order_acquire/memory_order_release 70 // semantics. 71 template <typename ReturnType, typename... Args> 72 class AtomicHook<ReturnType (*)(Args...)> { 73 public: 74 using FnPtr = ReturnType (*)(Args...); 75 76 // Constructs an object that by default performs a no-op (and 77 // returns a default constructed object) when no hook as been registered. AtomicHook()78 constexpr AtomicHook() : AtomicHook(DummyFunction) {} 79 80 // Constructs an object that by default dispatches to/returns the 81 // pre-registered default_fn when no hook has been registered at runtime. 82 #if ABSL_HAVE_WORKING_ATOMIC_POINTER && ABSL_HAVE_WORKING_CONSTEXPR_STATIC_INIT AtomicHook(FnPtr default_fn)83 explicit constexpr AtomicHook(FnPtr default_fn) 84 : hook_(default_fn), default_fn_(default_fn) {} 85 #elif ABSL_HAVE_WORKING_CONSTEXPR_STATIC_INIT AtomicHook(FnPtr default_fn)86 explicit constexpr AtomicHook(FnPtr default_fn) 87 : hook_(kUninitialized), default_fn_(default_fn) {} 88 #else 89 // As of January 2020, on all known versions of MSVC this constructor runs in 90 // the global constructor sequence. If `Store()` is called by a dynamic 91 // initializer, we want to preserve the value, even if this constructor runs 92 // after the call to `Store()`. If not, `hook_` will be 93 // zero-initialized by the linker and we have no need to set it. 94 // https://developercommunity.visualstudio.com/content/problem/336946/class-with-constexpr-constructor-not-using-static.html AtomicHook(FnPtr default_fn)95 explicit constexpr AtomicHook(FnPtr default_fn) 96 : /* hook_(deliberately omitted), */ default_fn_(default_fn) { 97 static_assert(kUninitialized == 0, "here we rely on zero-initialization"); 98 } 99 #endif 100 101 // Stores the provided function pointer as the value for this hook. 102 // 103 // This is intended to be called once. Multiple calls are legal only if the 104 // same function pointer is provided for each call. The store is implemented 105 // as a memory_order_release operation, and read accesses are implemented as 106 // memory_order_acquire. Store(FnPtr fn)107 void Store(FnPtr fn) { 108 bool success = DoStore(fn); 109 static_cast<void>(success); 110 assert(success); 111 } 112 113 // Invokes the registered callback. If no callback has yet been registered, a 114 // default-constructed object of the appropriate type is returned instead. 115 template <typename... CallArgs> operator()116 ReturnType operator()(CallArgs&&... args) const { 117 return DoLoad()(std::forward<CallArgs>(args)...); 118 } 119 120 // Returns the registered callback, or nullptr if none has been registered. 121 // Useful if client code needs to conditionalize behavior based on whether a 122 // callback was registered. 123 // 124 // Note that atomic_hook.Load()() and atomic_hook() have different semantics: 125 // operator()() will perform a no-op if no callback was registered, while 126 // Load()() will dereference a null function pointer. Prefer operator()() to 127 // Load()() unless you must conditionalize behavior on whether a hook was 128 // registered. Load()129 FnPtr Load() const { 130 FnPtr ptr = DoLoad(); 131 return (ptr == DummyFunction) ? nullptr : ptr; 132 } 133 134 private: DummyFunction(Args...)135 static ReturnType DummyFunction(Args...) { 136 return ReturnType(); 137 } 138 139 // Current versions of MSVC (as of September 2017) have a broken 140 // implementation of std::atomic<T*>: Its constructor attempts to do the 141 // equivalent of a reinterpret_cast in a constexpr context, which is not 142 // allowed. 143 // 144 // This causes an issue when building with LLVM under Windows. To avoid this, 145 // we use a less-efficient, intptr_t-based implementation on Windows. 146 #if ABSL_HAVE_WORKING_ATOMIC_POINTER 147 // Return the stored value, or DummyFunction if no value has been stored. DoLoad()148 FnPtr DoLoad() const { return hook_.load(std::memory_order_acquire); } 149 150 // Store the given value. Returns false if a different value was already 151 // stored to this object. DoStore(FnPtr fn)152 bool DoStore(FnPtr fn) { 153 assert(fn); 154 FnPtr expected = default_fn_; 155 const bool store_succeeded = hook_.compare_exchange_strong( 156 expected, fn, std::memory_order_acq_rel, std::memory_order_acquire); 157 const bool same_value_already_stored = (expected == fn); 158 return store_succeeded || same_value_already_stored; 159 } 160 161 std::atomic<FnPtr> hook_; 162 #else // !ABSL_HAVE_WORKING_ATOMIC_POINTER 163 // Use a sentinel value unlikely to be the address of an actual function. 164 static constexpr intptr_t kUninitialized = 0; 165 166 static_assert(sizeof(intptr_t) >= sizeof(FnPtr), 167 "intptr_t can't contain a function pointer"); 168 DoLoad()169 FnPtr DoLoad() const { 170 const intptr_t value = hook_.load(std::memory_order_acquire); 171 if (value == kUninitialized) { 172 return default_fn_; 173 } 174 return reinterpret_cast<FnPtr>(value); 175 } 176 DoStore(FnPtr fn)177 bool DoStore(FnPtr fn) { 178 assert(fn); 179 const auto value = reinterpret_cast<intptr_t>(fn); 180 intptr_t expected = kUninitialized; 181 const bool store_succeeded = hook_.compare_exchange_strong( 182 expected, value, std::memory_order_acq_rel, std::memory_order_acquire); 183 const bool same_value_already_stored = (expected == value); 184 return store_succeeded || same_value_already_stored; 185 } 186 187 std::atomic<intptr_t> hook_; 188 #endif 189 190 const FnPtr default_fn_; 191 }; 192 193 #undef ABSL_HAVE_WORKING_ATOMIC_POINTER 194 #undef ABSL_HAVE_WORKING_CONSTEXPR_STATIC_INIT 195 196 } // namespace base_internal 197 ABSL_NAMESPACE_END 198 } // namespace absl 199 200 #endif // ABSL_BASE_INTERNAL_ATOMIC_HOOK_H_ 201