1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
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  *     http://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 META_BASE_ATOMICS_H
16 #define META_BASE_ATOMICS_H
17 
18 #include <stdint.h>
19 
20 #include <core/namespace.h>
21 #if defined(_MSC_VER) && defined(WIN32)
22 #include <intrin.h>
23 #endif
24 
25 CORE_BEGIN_NAMESPACE();
26 /*
27  * Implementation of InterlockedIncrement/InterlockedDecrement (int32_t atomics)
28  * Bare minimum to implement thread safe reference counters.
29  **/
30 
31 #if defined(_MSC_VER) && defined(WIN32)
32 // On windows and visual studio, we just forward to the matching OS methods.
AtomicIncrement(volatile int32_t * a)33 inline int32_t AtomicIncrement(volatile int32_t* a)
34 {
35     return ::_InterlockedIncrement((long*)a);
36 }
AtomicDecrement(volatile int32_t * a)37 inline int32_t AtomicDecrement(volatile int32_t* a)
38 {
39     return ::_InterlockedDecrement((long*)a);
40 }
AtomicRead(const volatile int32_t * a)41 inline int32_t AtomicRead(const volatile int32_t* a)
42 {
43     return ::_InterlockedExchangeAdd((long*)a, 0);
44 }
AtomicIncrementIfNotZero(volatile int32_t * a)45 inline int32_t AtomicIncrementIfNotZero(volatile int32_t* a)
46 {
47     int32_t v = AtomicRead(a);
48     while (v) {
49         int32_t temp = v;
50         v = ::_InterlockedCompareExchange((long*)a, v + 1, v);
51         if (v == temp) {
52             return temp;
53         }
54     }
55     return v;
56 }
57 
58 // Trivial spinlock implemented with atomics.
59 // NOTE: this does NOT yield while waiting, so use this ONLY in places where lock contention times are expected to be
60 // trivial. Also does not ensure fairness. but most likely enough for our reference counting purposes.
61 // and of course is non-recursive, so you can only lock once in a thread.
62 class SpinLock {
63 public:
Lock()64     void Lock()
65     {
66         while (_InterlockedCompareExchange(&lock_, taken_, free_) == taken_) {
67         }
68     }
Unlock()69     void Unlock()
70     {
71         _InterlockedExchange(&lock_, free_);
72     }
73 
74 private:
75     long lock_ = 0;
76     static constexpr long taken_ = 1;
77     static constexpr long free_ = 0;
78 };
79 #elif defined(__has_builtin) && __has_builtin(__atomic_add_fetch) && __has_builtin(__atomic_load_n) && \
80     __has_builtin(__atomic_compare_exchange_n)
81 /* gcc built in atomics, supported on clang also */
AtomicIncrement(volatile int32_t * a)82 inline int32_t AtomicIncrement(volatile int32_t* a)
83 {
84     return __atomic_add_fetch(a, 1, __ATOMIC_ACQ_REL);
85 }
AtomicDecrement(volatile int32_t * a)86 inline int32_t AtomicDecrement(volatile int32_t* a)
87 {
88     return __atomic_add_fetch(a, -1, __ATOMIC_ACQ_REL);
89 }
AtomicRead(const volatile int32_t * a)90 inline int32_t AtomicRead(const volatile int32_t* a)
91 {
92     return __atomic_load_n(a, __ATOMIC_ACQUIRE);
93 }
AtomicIncrementIfNotZero(volatile int32_t * a)94 inline int32_t AtomicIncrementIfNotZero(volatile int32_t* a)
95 {
96     int32_t v = AtomicRead(a);
97     while (v) {
98         int32_t temp = v;
99         if (__atomic_compare_exchange_n(a, &v, temp + 1, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
100             return temp;
101         }
102     }
103     return v;
104 }
105 // Trivial spinlock implemented with atomics.
106 // NOTE: this does NOT yield while waiting, so use this ONLY in places where lock contention times are expected to be
107 // trivial. Also does not ensure fairness. but most likely enough for our reference counting purposes.
108 // and of course is non-recursive, so you can only lock once in a thread.
109 class SpinLock {
110 public:
Lock()111     void Lock()
112     {
113         long taken = 1;
114         long expect = 0;
115         while (!__atomic_compare_exchange(&lock_, &expect, &taken, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
116             expect = 0;
117         }
118     }
Unlock()119     void Unlock()
120     {
121         long free = 0;
122         __atomic_store(&lock_, &free, __ATOMIC_SEQ_CST);
123     }
124 
125 private:
126     long lock_ = 0;
127 };
128 
129 #else
130 #error Compiler / Platform specific atomic methods not implemented !
131 #endif
132 
133 /**
134  * @brief Scoped helper to lock and unlock spin locks.
135  */
136 class ScopedSpinLock {
137 public:
138     ScopedSpinLock(const ScopedSpinLock&) = delete;
139     ScopedSpinLock& operator=(const ScopedSpinLock&) = delete;
140     ScopedSpinLock(ScopedSpinLock&&) = delete;
141     ScopedSpinLock& operator=(ScopedSpinLock&&) = delete;
142 
ScopedSpinLock(SpinLock & l)143     explicit ScopedSpinLock(SpinLock& l) : lock_(l)
144     {
145         lock_.Lock();
146     }
~ScopedSpinLock()147     ~ScopedSpinLock()
148     {
149         lock_.Unlock();
150     }
151 
152 private:
153     SpinLock& lock_;
154 };
155 
156 CORE_END_NAMESPACE();
157 #endif
158