• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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