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