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