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_MANAGER_H 16 #define PANDA_RUNTIME_COROUTINES_COROUTINE_MANAGER_H 17 18 #include "runtime/coroutines/coroutine_context.h" 19 #include "runtime/thread_manager.h" 20 #include "runtime/include/runtime.h" 21 #include "runtime/coroutines/coroutine.h" 22 #include "runtime/coroutines/coroutine_events.h" 23 24 namespace ark { 25 26 /// @brief describes the set of adjustable parameters for CoroutineManager and its descendants initialization 27 struct CoroutineManagerConfig { 28 static constexpr uint32_t WORKERS_COUNT_AUTO = 0; 29 30 /// JS-compatible mode, affects async functions, await() and other things 31 bool emulateJs = false; 32 /// Number of coroutine workers for the N:M mode 33 uint32_t workersCount = WORKERS_COUNT_AUTO; 34 /// Collection of performance statistics 35 bool enablePerfStats = false; 36 }; 37 38 /// @brief defines the requested launch mode for a coroutine 39 enum class CoroutineLaunchMode { 40 /// no specific requests 41 DEFAULT, 42 /// schedule to the parent's worker only 43 SAME_WORKER, 44 /// schedule to the main worker only 45 MAIN_WORKER, 46 /// schedule exclusively, moving other coros off the target worker 47 EXCLUSIVE 48 }; 49 50 /// @brief defines the scheduling policy for a coroutine. Maybe in future we would like to add more types. 51 enum class CoroutineSchedulingPolicy { 52 /// choose the least busy worker 53 DEFAULT, 54 /** 55 * same as default but exclude the main worker from available hosts on launch and 56 * disallow non_main -> main transitions on migration 57 */ 58 NON_MAIN_WORKER 59 }; 60 61 /** 62 * @brief The interface of all coroutine manager implementations. 63 * 64 * Manages (registers, unregisters, enumerates) and schedules coroutines for execution using the worker threads. 65 * Creates and destroys the main coroutine. 66 * Provides interfaces for coroutine synchronization. 67 */ 68 class CoroutineManager : public ThreadManager { 69 public: 70 /** 71 * @brief The coroutine factory interface. 72 * 73 * @param name the coroutine name (for debugging and logging purposes) 74 * 75 * @param ctx the instance of implementation-dependend native coroutine context. Usually is provided by the 76 * concrete CoroutineManager descendant. 77 * 78 * @param ep_info if provided (that means, ep_info != std::nullopt), defines the coroutine entry point to execute. 79 * It can be either (the bytecode method, its arguments and the completion event instance to hold the method return 80 * value) or (a native function and its parameter). See Coroutine::EntrypointInfo for details. If this parameter is 81 * std::nullopt (i.e. no entrypoint present) then the following rules apply: 82 * - the factory should only create the coroutine instance and expect that the bytecode for the coroutine will be 83 * invoked elsewhere within the context of the newly created coroutine 84 * - the coroutine should be destroyed manually by the runtime by calling the Destroy() method 85 * - the coroutine does not use the method/arguments passing interface and the completion event interface 86 * - no other coroutine will await this one (although it can await others). 87 * The "main" coroutine (the EP of the application) is the specific example of such "no entrypoint" coroutine 88 * If ep_info is provided then the newly created coroutine will execute the specified method and do all the 89 * initialization/finalization steps, including completion_event management and notification of waiters. 90 * 91 */ 92 using CoroutineFactory = Coroutine *(*)(Runtime *runtime, PandaVM *vm, PandaString name, CoroutineContext *ctx, 93 std::optional<Coroutine::EntrypointInfo> &&epInfo); 94 95 NO_COPY_SEMANTIC(CoroutineManager); 96 NO_MOVE_SEMANTIC(CoroutineManager); 97 98 /// Factory is used to create coroutines when needed. See CoroutineFactory for details. 99 explicit CoroutineManager(CoroutineFactory factory); 100 ~CoroutineManager() override = default; 101 102 /** 103 * @brief Should be called after CoroutineManager creation and before any other method calls. 104 * Initializes internal structures, creates the main coroutine. 105 * 106 * @param config describes the CoroutineManager operation mode 107 */ 108 virtual void Initialize(CoroutineManagerConfig config, Runtime *runtime, PandaVM *vm) = 0; 109 /// Should be called after all execution is finished. Destroys the main coroutine. 110 virtual void Finalize() = 0; 111 /// Add coroutine to registry (used for enumeration and tracking) and perform all the required actions 112 virtual void RegisterCoroutine(Coroutine *co) = 0; 113 /** 114 * @brief Remove coroutine from all internal structures, notify waiters about its completion, correctly 115 * delete coroutine and free its resources 116 * @return returnes true if coroutine has been deleted, false otherwise (e.g. in case of terminating main) 117 */ 118 virtual bool TerminateCoroutine(Coroutine *co) = 0; 119 /** 120 * @brief The public coroutine creation interface. 121 * 122 * @param completionEvent the event used for notification when coroutine completes (also used to pass the return 123 * value to the language-level entities) 124 * @param entrypoint the coroutine entrypoint method 125 * @param arguments array of coroutine's entrypoint arguments 126 */ 127 virtual Coroutine *Launch(CompletionEvent *completionEvent, Method *entrypoint, PandaVector<Value> &&arguments, 128 CoroutineLaunchMode mode) = 0; 129 /// Suspend the current coroutine and schedule the next ready one for execution 130 virtual void Schedule() = 0; 131 /** 132 * @brief Move the current coroutine to the waiting state until awaitee happens and schedule the 133 * next ready coroutine for execution. 134 */ 135 virtual void Await(CoroutineEvent *awaitee) RELEASE(awaitee) = 0; 136 /** 137 * @brief Notify the waiting coroutines that an event has happened, so they can stop waiting and 138 * become ready for execution 139 * @param blocker the blocking event which transitioned from pending to happened state 140 * 141 * NB! this functions deletes @param blocker (assuming that it was allocated via InternalAllocator)! 142 */ 143 virtual void UnblockWaiters(CoroutineEvent *blocker) = 0; 144 145 /** 146 * The designated interface for creating the main coroutine instance. 147 * The program EP will be invoked within its context. 148 */ 149 Coroutine *CreateMainCoroutine(Runtime *runtime, PandaVM *vm); 150 /// Delete the main coroutine instance and free its resources 151 void DestroyMainCoroutine(); 152 153 /** 154 * Create a coroutine instance (including the context) for internal purposes (e.g. verifier) and 155 * set it as the current one. 156 * The created coroutine instance will not have any method to execute. All the control flow must be managed 157 * by the caller. 158 * 159 * @return nullptr if resource limit reached or something went wrong; ptr to the coroutine otherwise 160 */ 161 Coroutine *CreateEntrypointlessCoroutine(Runtime *runtime, PandaVM *vm, bool makeCurrent, PandaString name); 162 void DestroyEntrypointlessCoroutine(Coroutine *co); 163 164 /// Destroy a coroutine with an entrypoint 165 virtual void DestroyEntrypointfulCoroutine(Coroutine *co); 166 167 /// @return unique coroutine id 168 virtual uint32_t AllocateCoroutineId(); 169 /// mark the specified @param id as free 170 virtual void FreeCoroutineId(uint32_t id); 171 172 void SetSchedulingPolicy(CoroutineSchedulingPolicy policy); 173 CoroutineSchedulingPolicy GetSchedulingPolicy() const; 174 175 /// @return true if js compatibility mode is selected for the coroutine manager IsJsMode()176 virtual bool IsJsMode() 177 { 178 return false; 179 } 180 181 /* debugging tools */ 182 /** 183 * Disable coroutine switch for the current worker. 184 * If an attempt to switch the active coroutine is performed when coroutine switch is disabled, the exact actions 185 * are defined by the concrete CoroutineManager implementation (they could include no-op, program halt or something 186 * else). 187 */ 188 virtual void DisableCoroutineSwitch() = 0; 189 /// Enable coroutine switch for the current worker. 190 virtual void EnableCoroutineSwitch() = 0; 191 /// @return true if coroutine switch for the current worker is disabled 192 virtual bool IsCoroutineSwitchDisabled() = 0; 193 194 virtual bool IsMainWorker(Coroutine *coro) const = 0; 195 196 protected: 197 /// Create native coroutine context instance (implementation dependent) 198 virtual CoroutineContext *CreateCoroutineContext(bool coroHasEntrypoint) = 0; 199 /// Delete native coroutine context instance (implementation dependent) 200 virtual void DeleteCoroutineContext(CoroutineContext *) = 0; 201 /** 202 * Create coroutine instance including the native context and link the context and the coroutine. 203 * The coroutine is created using the factory provided in the CoroutineManager constructor and the virtual 204 * context creation function, which should be defined in concrete CoroutineManager implementations. 205 * 206 * @return nullptr if resource limit reached or something went wrong; ptr to the coroutine otherwise 207 */ 208 Coroutine *CreateCoroutineInstance(CompletionEvent *completionEvent, Method *entrypoint, 209 PandaVector<Value> &&arguments, PandaString name); 210 /// Returns number of existing coroutines 211 virtual size_t GetCoroutineCount() = 0; 212 /** 213 * @brief returns number of coroutines that could be created till the resource limit is reached. 214 * The resource limit definition is specific to the exact coroutines/coroutine manager implementation. 215 */ 216 virtual size_t GetCoroutineCountLimit() = 0; 217 218 /// Can be used in descendants to create custom coroutines manually 219 CoroutineFactory GetCoroutineFactory(); 220 221 /// limit the number of IDs for performance reasons 222 static constexpr size_t MAX_COROUTINE_ID = std::min(0xffffU, Coroutine::MAX_COROUTINE_ID); 223 static constexpr size_t UNINITIALIZED_COROUTINE_ID = 0x0U; 224 225 private: 226 CoroutineFactory coFactory_ = nullptr; 227 228 CoroutineSchedulingPolicy schedulingPolicy_ = CoroutineSchedulingPolicy::DEFAULT; 229 230 // coroutine id management 231 os::memory::Mutex idsLock_; 232 std::bitset<MAX_COROUTINE_ID> coroutineIds_ GUARDED_BY(idsLock_); 233 uint32_t lastCoroutineId_ GUARDED_BY(idsLock_) = UNINITIALIZED_COROUTINE_ID; 234 }; 235 236 /// Disables coroutine switch on the current worker for some scope. Can be used recursively. 237 class ScopedDisableCoroutineSwitch { 238 public: ScopedDisableCoroutineSwitch(CoroutineManager * coroManager)239 explicit ScopedDisableCoroutineSwitch(CoroutineManager *coroManager) : coroManager_(coroManager) 240 { 241 ASSERT(coroManager_ != nullptr); 242 coroManager_->DisableCoroutineSwitch(); 243 } 244 ~ScopedDisableCoroutineSwitch()245 ~ScopedDisableCoroutineSwitch() 246 { 247 coroManager_->EnableCoroutineSwitch(); 248 } 249 250 private: 251 CoroutineManager *coroManager_; 252 253 NO_COPY_SEMANTIC(ScopedDisableCoroutineSwitch); 254 NO_MOVE_SEMANTIC(ScopedDisableCoroutineSwitch); 255 }; 256 257 } // namespace ark 258 259 #endif /* PANDA_RUNTIME_COROUTINES_COROUTINE_MANAGER_H */ 260