• 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 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