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