1 /** 2 * Copyright (c) 2024-2025 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 PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_MUTEX_H 16 #define PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_MUTEX_H 17 18 #include "libpandabase/mem/object_pointer.h" 19 #include "macros.h" 20 #include "runtime/include/thread_scopes.h" 21 #include "plugins/ets/runtime/ets_coroutine.h" 22 #include "plugins/ets/runtime/types/ets_object.h" 23 #include "plugins/ets/runtime/types/ets_waiters_list.h" 24 25 #include <cstdint> 26 27 namespace ark::ets { 28 29 template <typename T> 30 class EtsHandle; 31 32 namespace test { 33 class EtsSyncPrimitivesTest; 34 } // namespace test 35 36 /// @brief The base class for sync primitives. Should not be used directly. 37 template <typename T> 38 class EtsSyncPrimitive : public EtsObject { 39 public: FromCoreType(ObjectHeader * syncPrimitive)40 static T *FromCoreType(ObjectHeader *syncPrimitive) 41 { 42 return reinterpret_cast<T *>(syncPrimitive); 43 } 44 FromEtsObject(EtsObject * syncPrimitive)45 static T *FromEtsObject(EtsObject *syncPrimitive) 46 { 47 return reinterpret_cast<T *>(syncPrimitive); 48 } 49 AsObject()50 EtsObject *AsObject() 51 { 52 return this; 53 } 54 AsObject()55 const EtsObject *AsObject() const 56 { 57 return this; 58 } 59 GetWaitersList(EtsCoroutine * coro)60 EtsWaitersList *GetWaitersList(EtsCoroutine *coro) 61 { 62 return EtsWaitersList::FromCoreType( 63 ObjectAccessor::GetObject(coro, this, MEMBER_OFFSET(EtsSyncPrimitive, waitersList_))); 64 } 65 SetWaitersList(EtsCoroutine * coro,EtsWaitersList * waitersList)66 void SetWaitersList(EtsCoroutine *coro, EtsWaitersList *waitersList) 67 { 68 ObjectAccessor::SetObject(coro, this, MEMBER_OFFSET(EtsSyncPrimitive, waitersList_), 69 waitersList->GetCoreType()); 70 } 71 72 /** 73 * Blocks current coroutine. It can be used concurrently with other Suspend and Resume 74 * @param awaitee - EtsWaitersList node that contains GenericEvent and probably other useful data 75 * NOTE: `this` and all other raw ObjectHeaders may become invalid after this call due to GC 76 */ SuspendCoroutine(EtsWaitersList::Node * awaitee)77 ALWAYS_INLINE void SuspendCoroutine(EtsWaitersList::Node *awaitee) 78 { 79 auto *coro = EtsCoroutine::GetCurrent(); 80 ASSERT(coro != nullptr); 81 auto *coroManager = coro->GetCoroutineManager(); 82 auto &event = awaitee->GetEvent(); 83 // Need to lock event before PushBack 84 // to avoid use-after-free in CoroutineEvent::Happen method 85 event.Lock(); 86 GetWaitersList(coro)->PushBack(awaitee); 87 ScopedNativeCodeThread nativeCode(coro); 88 coroManager->Await(&event); 89 } 90 91 /** 92 * Unblocks suspended coroutine. It can be used concurrently with Suspend, 93 * BUT not with other Resume (PopFront is not thread-safety) 94 */ ResumeCoroutine()95 ALWAYS_INLINE void ResumeCoroutine() 96 { 97 auto *coro = EtsCoroutine::GetCurrent(); 98 auto *awaitee = GetWaitersList(coro)->PopFront(); 99 awaitee->GetEvent().Happen(); 100 } 101 102 private: 103 ObjectPointer<EtsWaitersList> waitersList_; 104 105 friend class test::EtsSyncPrimitivesTest; 106 }; 107 108 /// @brief Coroutine mutex. This allows to get exclusive access to the critical section. 109 class EtsMutex : public EtsSyncPrimitive<EtsMutex> { 110 public: 111 template <typename T> 112 class LockHolder { 113 public: LockHolder(EtsHandle<T> & hLock)114 explicit LockHolder(EtsHandle<T> &hLock) : hLock_(hLock) 115 { 116 ASSERT(hLock_.GetPtr() != nullptr); 117 hLock_->Lock(); 118 } 119 120 NO_COPY_SEMANTIC(LockHolder); 121 NO_MOVE_SEMANTIC(LockHolder); 122 ~LockHolder()123 ~LockHolder() 124 { 125 ASSERT(hLock_.GetPtr() != nullptr); 126 hLock_->Unlock(); 127 } 128 129 private: 130 EtsHandle<T> &hLock_; 131 }; 132 133 /** 134 * Method gives exclusive access to critical section is case of successful mutex lock. 135 * Otherwise, blocks current coroutine until other coroutine will unlock the mutex. 136 */ 137 void Lock(); 138 139 /** 140 * Calling the method means exiting the critical section. 141 * If there are blocked coroutines, unblocks one of them. 142 */ 143 void Unlock(); 144 145 /// This method should be used to make sure that mutex is locked by current coroutine. 146 bool IsHeld(); 147 148 static EtsMutex *Create(EtsCoroutine *coro); 149 150 private: 151 std::atomic<uint32_t> waiters_; 152 153 friend class test::EtsSyncPrimitivesTest; 154 }; 155 156 /// @brief Coroutine one-shot event. This allows to block current coroutine until event is fired 157 class EtsEvent : public EtsSyncPrimitive<EtsEvent> { 158 public: 159 /** 160 * Blocks current coroutine until another coroutine will fire the same event. 161 * It can be used concurrently with other wait/fire. 162 * It has no effect in case fire has already been caused, but guarantees happens-before. 163 */ 164 void Wait(); 165 166 /** 167 * Unblocks all coroutines that are waiting the same event. 168 * Can be used concurrently with other wait/fire but multiply calls have no effect. 169 */ 170 void Fire(); 171 172 static EtsEvent *Create(EtsCoroutine *coro); 173 174 private: 175 static constexpr uint32_t STATE_BIT = 1U; 176 static constexpr uint32_t FIRE_STATE = 1U; 177 static constexpr uint32_t ONE_WAITER = 2U; 178 IsFireState(uint32_t state)179 static bool IsFireState(uint32_t state) 180 { 181 return (state & STATE_BIT) == FIRE_STATE; 182 } 183 GetNumberOfWaiters(uint32_t state)184 static uint32_t GetNumberOfWaiters(uint32_t state) 185 { 186 return state >> STATE_BIT; 187 } 188 189 std::atomic<uint32_t> state_; 190 191 friend class test::EtsSyncPrimitivesTest; 192 }; 193 194 /// Coroutine conditional variable 195 class EtsCondVar : public EtsSyncPrimitive<EtsCondVar> { 196 public: 197 /** 198 * precondition: mutex is locked 199 * 1. Unlocks mutex 200 * 2. Blocks current coroutine 201 * 3. Locks mutex 202 * This method is thread-safe in relation to other methods of the CondVar 203 */ 204 void Wait(EtsHandle<EtsMutex> &mutex); 205 206 /** 207 * precondition: mutex is locked 208 * Unblocks ONE suspended coroutine associated with this CondVar, if it exists. 209 * This method is thread-safe in relation to other methods of the CondVar 210 */ 211 void NotifyOne([[maybe_unused]] EtsMutex *mutex); 212 213 /** 214 * precondition: mutex is locked 215 * Unblocks ALL suspended coroutine associated with this CondVar. 216 * This method is thread-safe in relation to other methods of the CondVar 217 */ 218 void NotifyAll([[maybe_unused]] EtsMutex *mutex); 219 220 static EtsCondVar *Create(EtsCoroutine *coro); 221 222 private: 223 EtsInt waiters_; 224 225 friend class test::EtsSyncPrimitivesTest; 226 }; 227 228 class EtsQueueSpinlock : public EtsObject { 229 public: 230 class Guard { 231 public: 232 explicit Guard(EtsHandle<EtsQueueSpinlock> &spinlock); 233 NO_COPY_SEMANTIC(Guard); 234 NO_MOVE_SEMANTIC(Guard); 235 ~Guard(); 236 237 private: 238 friend class EtsQueueSpinlock; 239 240 EtsHandle<EtsQueueSpinlock> &spinlock_; 241 std::atomic<bool> isOwner_ {false}; 242 std::atomic<Guard *> next_ {nullptr}; 243 }; 244 FromCoreType(ObjectHeader * syncPrimitive)245 static EtsQueueSpinlock *FromCoreType(ObjectHeader *syncPrimitive) 246 { 247 return reinterpret_cast<EtsQueueSpinlock *>(syncPrimitive); 248 } 249 FromEtsObject(EtsObject * syncPrimitive)250 static EtsQueueSpinlock *FromEtsObject(EtsObject *syncPrimitive) 251 { 252 return reinterpret_cast<EtsQueueSpinlock *>(syncPrimitive); 253 } 254 AsObject()255 EtsObject *AsObject() 256 { 257 return this; 258 } 259 AsObject()260 const EtsObject *AsObject() const 261 { 262 return this; 263 } 264 265 static EtsQueueSpinlock *Create(EtsCoroutine *coro); 266 267 /// This method should be used to make sure that spinlock is acquired by current coroutine. 268 bool IsHeld() const; 269 270 private: 271 class SpinWait { 272 public: operator()273 void operator()() {} 274 }; 275 276 /// Acquires lock and allows to get exclusive access to the critical seciton 277 void Acquire(Guard *waiter); 278 279 /// Releases lock and unblocks waiter 280 void Release(Guard *owner); 281 282 alignas(alignof(EtsLong)) std::atomic<Guard *> tail_; 283 284 friend class test::EtsSyncPrimitivesTest; 285 }; 286 287 } // namespace ark::ets 288 289 #endif // PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_MUTEX_H 290