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