• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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_RUNTIME_COROUTINES_COROUTINE_H
16 #define PANDA_RUNTIME_COROUTINES_COROUTINE_H
17 
18 #include <atomic>
19 #include <optional>
20 #include "runtime/coroutines/coroutine_events.h"
21 #include "runtime/coroutines/coroutine_worker.h"
22 #include "runtime/include/runtime.h"
23 #include "runtime/include/managed_thread.h"
24 #ifdef ARK_HYBRID
25 #include "thread/thread_holder.h"
26 #endif
27 
28 namespace ark {
29 #ifdef ARK_HYBRID
30 using CommonRootVisitor = panda::CommonRootVisitor;
31 
32 extern "C" void VisitCoroutine(void *coroutine, CommonRootVisitor visitor);
33 #endif
34 
35 class CoroutineContext;
36 class CompletionEvent;
37 /**
38  * @brief The base class for all coroutines. Holds the managed part of the coroutine context.
39  *
40  * The coroutine context is splitted into managed and native parts.
41  * The managed part is store in this class and its descendants. For the native part see the
42  * CoroutineContext class and its descendants.
43  */
44 class Coroutine : public ManagedThread {
45 public:
46     NO_COPY_SEMANTIC(Coroutine);
47     NO_MOVE_SEMANTIC(Coroutine);
48 
49     /**
50      * Status transitions:
51      *
52      * +---------+                                    +----------+
53      * | CREATED | ---------------------------------> |          | <-------------+
54      * +---------+                                    | RUNNABLE |               |
55      *                                           +--- |          | <--+          |
56      *                                           |    +----------+    |          |
57      *                                           |                    |          |
58      *                                           |    +----------+    |     +----------+
59      *                                           +--> |          | ---+     |          |
60      * +------------+        +-------------+          | RUNNING  |          | BLOCKED  |
61      * | AWAIT_LOOP | [<---] | TERMINATING | <------- |          | -------> |          |
62      * +------------+        +-------------+          +----------+          +----------+
63      *
64      * ACTIVE = (RUNNABLE | RUNNING) & WORKER_ASSIGNED
65      * NOT_ACTIVE = (CREATED | BLOCKED | TERMINATING | AWAIT_LOOP) | NO_WORKER_ASSIGNED
66      *
67      * Notes:
68      * Main coroutine may optionally (in the "threaded" backend) go from TERMINATING to AWAIT_LOOP
69      * in order to wait for other coroutines to complete. After all other coroutines are completed,
70      * the main coroutine exits.
71      */
72     enum class Status { CREATED, RUNNABLE, RUNNING, BLOCKED, TERMINATING, AWAIT_LOOP };
73     /// the type of work that a coroutine performs
74     enum class Type { MUTATOR, SCHEDULER, FINALIZER };
75 
76     /// Needed for object locking
77     static constexpr ThreadId MAX_COROUTINE_ID = MarkWord::LIGHT_LOCK_THREADID_MAX_COUNT;
78 
79     /// A helper struct that aggregates all EP related data for a coroutine with a managed EP
80     struct ManagedEntrypointInfo {
81         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
82         CompletionEvent *completionEvent;  ///< not owned by this structure, just passed!
83         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
84         Method *entrypoint;
85         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
86         PandaVector<Value> &&arguments;
87 
88         /**
89          * @param event an instance of CompletionEvent to be used on coroutine completion to pass the
90          * return value to the point where it is needed. Also is used to unblock the coroutines that are waiting for
91          * the one being created to complete.
92          *
93          * @param entry managed method to execute in the context of coroutine.
94          *
95          * @param args the array of EP method arguments
96          */
ManagedEntrypointInfoManagedEntrypointInfo97         explicit ManagedEntrypointInfo(CompletionEvent *event, Method *entry, PandaVector<Value> &&args)
98             : completionEvent(event), entrypoint(entry), arguments(std::move(args))
99         {
100             ASSERT(event != nullptr);
101             ASSERT(entry != nullptr);
102         }
103     };
104 
105     /// a helper struct that aggregates all EP related data for a coroutine with a native EP
106     struct NativeEntrypointInfo {
107         using NativeEntrypointFunc = void (*)(void *);
108         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
109         NativeEntrypointFunc entrypoint;
110         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
111         void *param;
112 
113         /**
114          * @param entry native function to execute in the context of coroutine
115          *
116          * @param arguments a parameter which will be passed to the entrypoint (usually some object pointer)
117          */
NativeEntrypointInfoNativeEntrypointInfo118         explicit NativeEntrypointInfo(NativeEntrypointFunc entry, void *data) : entrypoint(entry), param(data)
119         {
120             ASSERT(data != nullptr);
121             ASSERT(entry != nullptr);
122         }
123     };
124 
125     using EntrypointInfo = std::variant<ManagedEntrypointInfo, NativeEntrypointInfo>;
126 
127     /**
128      * The coroutine factory: creates and initializes a coroutine instance. The preferred way to create a
129      * coroutine. For details see CoroutineManager::CoroutineFactory
130      */
131     static Coroutine *Create(Runtime *runtime, PandaVM *vm, PandaString name, CoroutineContext *context,
132                              std::optional<EntrypointInfo> &&epInfo = std::nullopt, Type type = Type::MUTATOR,
133                              CoroutinePriority priority = CoroutinePriority::MEDIUM_PRIORITY);
134     ~Coroutine() override;
135 
136     /// Should be called after creation in order to create native context and do other things
137     virtual void Initialize();
138     /**
139      * Coroutine reinitialization: semantically equivalent to Initialize(),
140      * but is called to prepare a cached Coroutine instance for reuse when it is needed.
141      * Implies that the CleanUp() method was called before caching.
142      */
143     void ReInitialize(PandaString name, CoroutineContext *context, std::optional<EntrypointInfo> &&epInfo,
144                       CoroutinePriority priority);
145     /**
146      * Manual destruction, applicable only to the main coro. Other ones get deleted by the coroutine manager once they
147      * finish execution of their entrypoint method.
148      */
149     void Destroy();
150 
151     void CleanUp() override;
152 
153     bool RetrieveStackInfo(void *&stackAddr, size_t &stackSize, size_t &guardSize) override;
154 
ThreadIsCoroutine(Thread * thread)155     static bool ThreadIsCoroutine(Thread *thread)
156     {
157         ASSERT(thread != nullptr);
158         // NOTE(konstanting, #IAD5MH): THREAD_TYPE_TASK -> THREAD_TYPE_COROUTINE and
159         // remove the runtime/scheduler directory contents
160         return thread->GetThreadType() == Thread::ThreadType::THREAD_TYPE_TASK;
161     }
162 
CastFromThread(Thread * thread)163     static Coroutine *CastFromThread(Thread *thread)
164     {
165         ASSERT(thread != nullptr);
166         ASSERT(ThreadIsCoroutine(thread));
167         return static_cast<Coroutine *>(thread);
168     }
169 
GetCurrent()170     static Coroutine *GetCurrent()
171     {
172         Thread *thread = Thread::GetCurrent();
173         ASSERT(thread != nullptr);
174         if (ThreadIsCoroutine(thread)) {
175             return CastFromThread(thread);
176         }
177         return nullptr;
178     }
179 
180     /// Get coroutine status. It is independent from ThreadStatus.
181     Status GetCoroutineStatus() const;
182     /// Get coroutine name.
183     PandaString GetName() const;
184     /// Get unique coroutine ID
GetCoroutineId()185     uint32_t GetCoroutineId() const
186     {
187         return coroutineId_;
188     }
189 
190     /// @brief list unhandled language specific events on program exit
ListUnhandledEventsOnProgramExit()191     virtual void ListUnhandledEventsOnProgramExit() {};
192 
193     /**
194      * Suspend a coroutine, so its status becomes either Status::RUNNABLE or Status::BLOCKED, depending on the suspend
195      * reason.
196      */
197     virtual void RequestSuspend(bool getsBlocked);
198     /// Resume the suspended coroutine, so its status becomes Status::RUNNING.
199     virtual void RequestResume();
200     /// Unblock the blocked coroutine, setting its status to Status::RUNNABLE
201     virtual void RequestUnblock();
202     /**
203      * @brief Indicate that coroutine entrypoint execution is finished. Propagates the coroutine
204      * return value to language level objects.
205      */
206     virtual void RequestCompletion(Value returnValue);
207 
208     /// Get the CompletionEvent instance
GetCompletionEvent()209     CompletionEvent *GetCompletionEvent()
210     {
211         return std::get<ManagedEntrypointData>(entrypoint_).completionEvent;
212     }
213 
HasManagedEntrypoint()214     bool HasManagedEntrypoint() const
215     {
216         return std::holds_alternative<ManagedEntrypointData>(entrypoint_);
217     }
218 
219     /// Get coroutine's managed entrypoint method.
GetManagedEntrypoint()220     Method *GetManagedEntrypoint() const
221     {
222         ASSERT(HasManagedEntrypoint());
223         return std::get<ManagedEntrypointData>(entrypoint_).entrypoint;
224     }
225 
226     /// Get coroutine's managed entrypoint args if any.
GetManagedEntrypointArguments()227     PandaVector<Value> &GetManagedEntrypointArguments()
228     {
229         ASSERT(HasManagedEntrypoint());
230         return std::get<ManagedEntrypointData>(entrypoint_).arguments;
231     }
232 
GetManagedEntrypointArguments()233     const PandaVector<Value> &GetManagedEntrypointArguments() const
234     {
235         ASSERT(HasManagedEntrypoint());
236         return std::get<ManagedEntrypointData>(entrypoint_).arguments;
237     }
238 
HasNativeEntrypoint()239     bool HasNativeEntrypoint() const
240     {
241         return std::holds_alternative<NativeEntrypointData>(entrypoint_);
242     }
243 
244     /// Get coroutine's native entry function (if this is a "native" coroutine)
GetNativeEntrypoint()245     NativeEntrypointInfo::NativeEntrypointFunc GetNativeEntrypoint() const
246     {
247         ASSERT(HasNativeEntrypoint());
248         return std::get<NativeEntrypointData>(entrypoint_).entrypoint;
249     }
250 
251     /// Get coroutine's native entry function parameter (if this is a "native" coroutine)
GetNativeEntrypointParam()252     void *GetNativeEntrypointParam() const
253     {
254         ASSERT(HasNativeEntrypoint());
255         return std::get<NativeEntrypointData>(entrypoint_).param;
256     }
257 
258     template <class T>
GetContext()259     T *GetContext() const
260     {
261         return static_cast<T *>(context_);
262     }
263 
GetManager()264     CoroutineManager *GetManager() const
265     {
266         return manager_;
267     }
268 
269     /**
270      * Assign this coroutine to a worker. Please do not call this method directly unless it is absolutely
271      * necessary (e.g. in the threaded backend). Use worker's interfaces like AddRunnableCoroutine(),
272      * AddRunningCoroutine().
273      */
274     void SetWorker(CoroutineWorker *w);
275 
276     /// get the currently assigned worker
GetWorker()277     CoroutineWorker *GetWorker() const
278     {
279         return worker_;
280     }
281 
282     /// @return true if the coroutine is ACTIVE (see the status transition diagram and the notes below)
283     bool IsActive();
284 
285     /// @return type of work that the coroutine performs
GetType()286     Type GetType() const
287     {
288         return type_;
289     }
290 
IsSuspendOnStartup()291     bool IsSuspendOnStartup() const
292     {
293         return startSuspended_;
294     }
295 
296     /// @return coroutine priority which is used in the runnable queue
GetPriority()297     CoroutinePriority GetPriority() const
298     {
299         return priority_;
300     }
301 
302     /// @return immediate launcher and reset it to nullptr
ReleaseImmediateLauncher()303     Coroutine *ReleaseImmediateLauncher()
304     {
305         return std::exchange(immediateLauncher_, nullptr);
306     }
307 
308     /// Set immediate launcher
SetImmediateLauncher(Coroutine * il)309     void SetImmediateLauncher(Coroutine *il)
310     {
311         ASSERT(immediateLauncher_ == nullptr);
312         immediateLauncher_ = il;
313     }
314 
315     /// Possibly noreturn. Call this if the coroutine got an unexpected exception.
HandleUncaughtException()316     virtual void HandleUncaughtException() {};
317 
318     /* event handlers */
OnHostWorkerChanged()319     virtual void OnHostWorkerChanged() {};
320     virtual void OnStatusChanged(Status oldStatus, Status newStatus);
321 
322 #ifdef ARK_HYBRID
Visit(CommonRootVisitor visitor)323     void Visit(CommonRootVisitor visitor)
324     {
325         visitor(nullptr);
326     }
327 #endif
328 
329     void LinkToExternalHolder(bool useSharedHolder);
330 
331     /**
332      * Set a coroutine parameter abortFlag
333      * meaning that the coroutine could abort the program in an uncaugh exeption occured
334      */
SetAbortFlag(bool abortFlag)335     void SetAbortFlag(bool abortFlag)
336     {
337         abortFlag_ = abortFlag;
338     }
339 
340     /// Check if the abortFlag is set
HasAbortFlag()341     bool HasAbortFlag() const
342     {
343         return abortFlag_;
344     }
345 
346 protected:
347     // We would like everyone to use the factory to create a Coroutine, thus ctor is protected
348     explicit Coroutine(ThreadId id, mem::InternalAllocatorPtr allocator, PandaVM *vm,
349                        ark::panda_file::SourceLang threadLang, PandaString name, CoroutineContext *context,
350                        std::optional<EntrypointInfo> &&epInfo, Type type, CoroutinePriority priority);
351 
352     void SetCoroutineStatus(Status newStatus);
353 
354 private:
355     /// a converter function that stores the data from EntrypointInfo in the member variables
356     void SetEntrypointData(std::optional<EntrypointInfo> &&epInfo);
357 
358     PandaString name_;
359     uint32_t coroutineId_ = 0;
360 
361     /// contains managed entrypoint parameters if the coroutine an EP and is "managed"
362     struct ManagedEntrypointData {
363         NO_COPY_SEMANTIC(ManagedEntrypointData);
364         NO_MOVE_SEMANTIC(ManagedEntrypointData);
365 
366         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
367         CompletionEvent *completionEvent = nullptr;  ///< is owned by this structure
368         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
369         Method *entrypoint = nullptr;
370         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
371         PandaVector<Value> arguments;
372 
ManagedEntrypointDataManagedEntrypointData373         explicit ManagedEntrypointData(CompletionEvent *event, Method *entry, PandaVector<Value> &&args)
374             : completionEvent(event), entrypoint(entry), arguments(std::move(args))
375         {
376             ASSERT(event != nullptr);
377             ASSERT(entry != nullptr);
378         }
379 
380         ~ManagedEntrypointData();
381     };
382     /// contains native entrypoint parameters if the coroutine has an EP and is "native"
383     struct NativeEntrypointData {
384         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
385         NativeEntrypointInfo::NativeEntrypointFunc entrypoint;
386         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
387         void *param;
388 
NativeEntrypointDataNativeEntrypointData389         explicit NativeEntrypointData(NativeEntrypointInfo::NativeEntrypointFunc entry, void *data)
390             : entrypoint(entry), param(data)
391         {
392             ASSERT(data != nullptr);
393             ASSERT(entry != nullptr);
394         }
395     };
396     std::variant<std::monostate, ManagedEntrypointData, NativeEntrypointData> entrypoint_;
397 
398     CoroutineContext *context_ = nullptr;
399     CoroutineWorker *worker_ = nullptr;
400     CoroutineManager *manager_ = nullptr;
401     // NOTE(konstanting, #IAD5MH): check if we still need this functionality
402     bool startSuspended_ = false;
403     bool abortFlag_ = false;
404     Type type_ = Type::MUTATOR;
405     CoroutinePriority priority_;
406     /**
407      * Immediate launcher is a caller coroutine that will be back edge for the callee coroutine.
408      * This means that the next context to switch from callee is the context of immediateLauncher_.
409      */
410     Coroutine *immediateLauncher_ = nullptr;
411 
412     // Allocator calls our protected ctor
413     friend class mem::Allocator;
414     friend class CoroutineContext;
415 };
416 
417 std::ostream &operator<<(std::ostream &os, Coroutine::Status status);
418 
419 }  // namespace ark
420 
421 #endif  // PANDA_RUNTIME_COROUTINES_COROUTINE_H
422