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