• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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