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