• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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 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/include/runtime.h"
22 #include "runtime/include/managed_thread.h"
23 
24 namespace ark {
25 
26 class CoroutineContext;
27 class CompletionEvent;
28 /**
29  * @brief The base class for all coroutines. Holds the managed part of the coroutine context.
30  *
31  * The coroutine context is splitted into managed and native parts.
32  * The managed part is store in this class and its descendants. For the native part see the
33  * CoroutineContext class and its descendants.
34  */
35 class Coroutine : public ManagedThread {
36 public:
37     NO_COPY_SEMANTIC(Coroutine);
38     NO_MOVE_SEMANTIC(Coroutine);
39 
40     /**
41      * Status transitions:
42      *
43      * +---------+                                  +----------+
44      * | CREATED | -------------------------------> |          | <-------------+
45      * +---------+                                  | RUNNABLE |               |
46      *                                         +--- |          | <--+          |
47      *                                         |    +----------+    |          |
48      *                                         |                    |          |
49      *                                         |    +----------+    |     +----------+
50      *                                         +--> |          | ---+     |          |
51      * +------------+      +-------------+          | RUNNING  |          | BLOCKED  |
52      * | AWAIT_LOOP | <--- | TERMINATING | <------- |          | -------> |          |
53      * +------------+      +-------------+          +----------+          +----------+
54      *
55      *
56      * Main coroutine gets AWAIT_LOOP status once it starts the final waiting loop. After all
57      * other coroutines are completed, the main coroutine exits.
58      */
59     enum class Status { CREATED, RUNNABLE, RUNNING, BLOCKED, TERMINATING, AWAIT_LOOP };
60 
61     /// Needed for object locking
62     static constexpr ThreadId MAX_COROUTINE_ID = MarkWord::LIGHT_LOCK_THREADID_MAX_COUNT;
63 
64     /// A helper struct that aggregates all EP related data for a coroutine with a managed EP
65     struct ManagedEntrypointInfo {
66         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
67         CompletionEvent *completionEvent;  ///< not owned by this structure, just passed!
68         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
69         Method *entrypoint;
70         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
71         PandaVector<Value> &&arguments;
72 
73         /**
74          * @param event an instance of CompletionEvent to be used on coroutine completion to pass the
75          * return value to the point where it is needed. Also is used to unblock the coroutines that are waiting for
76          * the one being created to complete.
77          *
78          * @param entry managed method to execute in the context of coroutine.
79          *
80          * @param args the array of EP method arguments
81          */
ManagedEntrypointInfoManagedEntrypointInfo82         explicit ManagedEntrypointInfo(CompletionEvent *event, Method *entry, PandaVector<Value> &&args)
83             : completionEvent(event), entrypoint(entry), arguments(std::move(args))
84         {
85             ASSERT(event != nullptr);
86             ASSERT(entry != nullptr);
87         }
88     };
89 
90     /// a helper struct that aggregates all EP related data for a coroutine with a native EP
91     struct NativeEntrypointInfo {
92         using NativeEntrypointFunc = void (*)(void *);
93         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
94         NativeEntrypointFunc entrypoint;
95         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
96         void *param;
97 
98         /**
99          * @param entry native function to execute in the context of coroutine
100          *
101          * @param arguments a parameter which will be passed to the entrypoint (usually some object pointer)
102          */
NativeEntrypointInfoNativeEntrypointInfo103         explicit NativeEntrypointInfo(NativeEntrypointFunc entry, void *data) : entrypoint(entry), param(data)
104         {
105             ASSERT(data != nullptr);
106             ASSERT(entry != nullptr);
107         }
108     };
109 
110     using EntrypointInfo = std::variant<ManagedEntrypointInfo, NativeEntrypointInfo>;
111 
112     /**
113      * The coroutine factory: creates and initializes a coroutine instance. The preferred way to create a
114      * coroutine. For details see CoroutineManager::CoroutineFactory
115      */
116     static Coroutine *Create(Runtime *runtime, PandaVM *vm, PandaString name, CoroutineContext *context,
117                              std::optional<EntrypointInfo> &&epInfo = std::nullopt);
118     ~Coroutine() override;
119 
120     /// Should be called after creation in order to create native context and do other things
121     virtual void Initialize();
122     /**
123      * Coroutine reinitialization: semantically equivalent to Initialize(),
124      * but is called to prepare a cached Coroutine instance for reuse when it is needed.
125      * Implies that the CleanUp() method was called before caching.
126      */
127     void ReInitialize(PandaString name, CoroutineContext *context, std::optional<EntrypointInfo> &&epInfo);
128     /**
129      * Manual destruction, applicable only to the main coro. Other ones get deleted by the coroutine manager once they
130      * finish execution of their entrypoint method.
131      */
132     void Destroy();
133 
134     void CleanUp() override;
135 
136     bool RetrieveStackInfo(void *&stackAddr, size_t &stackSize, size_t &guardSize) override;
137 
ThreadIsCoroutine(Thread * thread)138     static bool ThreadIsCoroutine(Thread *thread)
139     {
140         ASSERT(thread != nullptr);
141         // NOTE(konstanting, #I67QXC): THREAD_TYPE_TASK -> THREAD_TYPE_COROUTINE and
142         // remove the runtime/scheduler directory contents
143         return thread->GetThreadType() == Thread::ThreadType::THREAD_TYPE_TASK;
144     }
145 
CastFromThread(Thread * thread)146     static Coroutine *CastFromThread(Thread *thread)
147     {
148         ASSERT(thread != nullptr);
149         ASSERT(ThreadIsCoroutine(thread));
150         return static_cast<Coroutine *>(thread);
151     }
152 
GetCurrent()153     static Coroutine *GetCurrent()
154     {
155         Thread *thread = Thread::GetCurrent();
156         ASSERT(thread != nullptr);
157         if (ThreadIsCoroutine(thread)) {
158             return CastFromThread(thread);
159         }
160         return nullptr;
161     }
162 
163     /// Get coroutine status. It is independent from ThreadStatus.
164     Status GetCoroutineStatus() const;
165     /// Get coroutine name.
166     PandaString GetName() const;
167     /// Get unique coroutine ID
GetCoroutineId()168     uint32_t GetCoroutineId() const
169     {
170         return coroutineId_;
171     }
172 
173     /**
174      * Suspend a coroutine, so its status becomes either Status::RUNNABLE or Status::BLOCKED, depending on the suspend
175      * reason.
176      */
177     virtual void RequestSuspend(bool getsBlocked);
178     /// Resume the suspended coroutine, so its status becomes Status::RUNNING.
179     virtual void RequestResume();
180     /// Unblock the blocked coroutine, setting its status to Status::RUNNABLE
181     virtual void RequestUnblock();
182     /**
183      * @brief Indicate that coroutine entrypoint execution is finished. Propagates the coroutine
184      * return value to language level objects.
185      */
186     virtual void RequestCompletion(Value returnValue);
187 
188     /// Get the CompletionEvent instance
GetCompletionEvent()189     CompletionEvent *GetCompletionEvent()
190     {
191         return std::get<ManagedEntrypointData>(entrypoint_).completionEvent;
192     }
193 
HasManagedEntrypoint()194     bool HasManagedEntrypoint() const
195     {
196         return std::holds_alternative<ManagedEntrypointData>(entrypoint_);
197     }
198 
199     /// Get coroutine's managed entrypoint method.
GetManagedEntrypoint()200     Method *GetManagedEntrypoint() const
201     {
202         ASSERT(HasManagedEntrypoint());
203         return std::get<ManagedEntrypointData>(entrypoint_).entrypoint;
204     }
205 
206     /// Get coroutine's managed entrypoint args if any.
GetManagedEntrypointArguments()207     PandaVector<Value> &GetManagedEntrypointArguments()
208     {
209         ASSERT(HasManagedEntrypoint());
210         return std::get<ManagedEntrypointData>(entrypoint_).arguments;
211     }
212 
GetManagedEntrypointArguments()213     const PandaVector<Value> &GetManagedEntrypointArguments() const
214     {
215         ASSERT(HasManagedEntrypoint());
216         return std::get<ManagedEntrypointData>(entrypoint_).arguments;
217     }
218 
HasNativeEntrypoint()219     bool HasNativeEntrypoint() const
220     {
221         return std::holds_alternative<NativeEntrypointData>(entrypoint_);
222     }
223 
224     /// Get coroutine's native entry function (if this is a "native" coroutine)
GetNativeEntrypoint()225     NativeEntrypointInfo::NativeEntrypointFunc GetNativeEntrypoint() const
226     {
227         ASSERT(HasNativeEntrypoint());
228         return std::get<NativeEntrypointData>(entrypoint_).entrypoint;
229     }
230 
231     /// Get coroutine's native entry function parameter (if this is a "native" coroutine)
GetNativeEntrypointParam()232     void *GetNativeEntrypointParam() const
233     {
234         ASSERT(HasNativeEntrypoint());
235         return std::get<NativeEntrypointData>(entrypoint_).param;
236     }
237 
238     template <class T>
GetContext()239     T *GetContext() const
240     {
241         return static_cast<T *>(context_);
242     }
243 
IsSuspendOnStartup()244     bool IsSuspendOnStartup() const
245     {
246         return startSuspended_;
247     }
248 
249 protected:
250     // We would like everyone to use the factory to create a Coroutine, thus ctor is protected
251     explicit Coroutine(ThreadId id, mem::InternalAllocatorPtr allocator, PandaVM *vm,
252                        ark::panda_file::SourceLang threadLang, PandaString name, CoroutineContext *context,
253                        std::optional<EntrypointInfo> &&epInfo);
254 
255     void SetCoroutineStatus(Status newStatus);
256 
257 private:
258     /// a converter function that stores the data from EntrypointInfo in the member variables
259     void SetEntrypointData(std::optional<EntrypointInfo> &&epInfo);
260 
261     PandaString name_;
262     uint32_t coroutineId_ = 0;
263 
264     /// contains managed entrypoint parameters if the coroutine an EP and is "managed"
265     struct ManagedEntrypointData {
266         NO_COPY_SEMANTIC(ManagedEntrypointData);
267         NO_MOVE_SEMANTIC(ManagedEntrypointData);
268 
269         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
270         CompletionEvent *completionEvent = nullptr;  ///< is owned by this structure
271         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
272         Method *entrypoint = nullptr;
273         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
274         PandaVector<Value> arguments;
275 
ManagedEntrypointDataManagedEntrypointData276         explicit ManagedEntrypointData(CompletionEvent *event, Method *entry, PandaVector<Value> &&args)
277             : completionEvent(event), entrypoint(entry), arguments(std::move(args))
278         {
279             ASSERT(event != nullptr);
280             ASSERT(entry != nullptr);
281         }
282 
283         ~ManagedEntrypointData();
284     };
285     /// contains native entrypoint parameters if the coroutine has an EP and is "native"
286     struct NativeEntrypointData {
287         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
288         NativeEntrypointInfo::NativeEntrypointFunc entrypoint;
289         // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
290         void *param;
291 
NativeEntrypointDataNativeEntrypointData292         explicit NativeEntrypointData(NativeEntrypointInfo::NativeEntrypointFunc entry, void *data)
293             : entrypoint(entry), param(data)
294         {
295             ASSERT(data != nullptr);
296             ASSERT(entry != nullptr);
297         }
298     };
299     std::variant<std::monostate, ManagedEntrypointData, NativeEntrypointData> entrypoint_;
300 
301     CoroutineContext *context_ = nullptr;
302     // NOTE(konstanting, #I67QXC): check if we still need this functionality
303     bool startSuspended_ = false;
304 
305     // Allocator calls our protected ctor
306     friend class mem::Allocator;
307     friend class CoroutineContext;
308 };
309 
310 std::ostream &operator<<(std::ostream &os, Coroutine::Status status);
311 
312 }  // namespace ark
313 
314 #endif  // PANDA_RUNTIME_COROUTINES_COROUTINE_H
315