• 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_STACKFUL_COROUTINE_H
16 #define PANDA_RUNTIME_COROUTINES_STACKFUL_COROUTINE_H
17 
18 #include "runtime/fibers/fiber_context.h"
19 #include "runtime/coroutines/coroutine_context.h"
20 #include "runtime/include/panda_vm.h"
21 #include "runtime/coroutines/stackful_common.h"
22 #include "runtime/coroutines/stackful_coroutine_worker.h"
23 
24 namespace ark {
25 
26 /**
27  * @brief Native context of a coroutine. "Fiber"-based implementation.
28  *
29  * This implementation uses panda fibers library to implement native coroutine context.
30  */
31 class StackfulCoroutineContext : public CoroutineContext {
32 public:
33     NO_COPY_SEMANTIC(StackfulCoroutineContext);
34     NO_MOVE_SEMANTIC(StackfulCoroutineContext);
35 
36     /**
37      * @param stack specifies the lowest address of the stack region to use;
38      * it should have at least @param stack_size_bytes bytes accessible. If the stack grows down on the
39      * target architecture, then the initial stack pointer of the coroutine will be set to
40      * (stack + stack_size_bytes)
41      */
42     explicit StackfulCoroutineContext(uint8_t *stack, size_t stackSizeBytes);
43     ~StackfulCoroutineContext() override = default;
44 
45     /**
46      * Prepares the context for execution, links it to the managed context part (Coroutine instance) and registers the
47      * created coroutine in the CoroutineManager (in the RUNNABLE status)
48      */
49     void AttachToCoroutine(Coroutine *co) override;
50     /**
51      * Manually destroys the context. Should be called by the Coroutine instance as a part of main coroutine
52      * destruction. All other coroutines and their contexts are destroyed by the CoroutineManager once the coroutine
53      * entrypoint execution finishes
54      */
55     void Destroy() override;
56 
57     void CleanUp() override;
58 
59     bool RetrieveStackInfo(void *&stackAddr, size_t &stackSize, size_t &guardSize) override;
60 
61     /**
62      * Suspends the execution context, sets its status to either Status::RUNNABLE or Status::BLOCKED, depending on the
63      * suspend reason.
64      */
65     void RequestSuspend(bool getsBlocked) override;
66     /// Resumes the suspended context, sets status to RUNNING.
67     void RequestResume() override;
68     /// Unblock the coroutine and set its status to Status::RUNNABLE
69     void RequestUnblock() override;
70 
71     // should be called then the main thread is done executing the program entrypoint
72     void MainThreadFinished();
73     /// Moves the main coroutine to Status::AWAIT_LOOP
74     void EnterAwaitLoop();
75 
76     /// Coroutine status is a part of native context, because it might require some synchronization on access
77     Coroutine::Status GetStatus() const override;
78 
79     /**
80      * Transfer control to the target context
81      * NB: this method will return only after the control is transferred back to the caller context
82      */
83     bool SwitchTo(StackfulCoroutineContext *target);
84 
85     /// @return the lowest address of the coroutine native stack (provided in the ctx contructor)
GetStackLoAddrPtr()86     uint8_t *GetStackLoAddrPtr() const
87     {
88         return stack_;
89     }
90 
91     /// Executes a foreign lambda function within this context (does not corrupt the saved context)
92     template <class L>
ExecuteOnThisContext(L * lambda,StackfulCoroutineContext * requester)93     bool ExecuteOnThisContext(L *lambda, StackfulCoroutineContext *requester)
94     {
95         ASSERT(requester != nullptr);
96         return rpcCallContext_.Execute(lambda, &requester->context_, &context_);
97     }
98 
99     /// assign this coroutine to a worker thread
SetWorker(StackfulCoroutineWorker * w)100     void SetWorker(StackfulCoroutineWorker *w)
101     {
102         worker_ = w;
103     }
104 
105     /// get the currently assigned worker thread
GetWorker()106     StackfulCoroutineWorker *GetWorker() const
107     {
108         return worker_;
109     }
110 
111     /// @return current coroutine's affinity bits
GetAffinityMask()112     stackful_coroutines::AffinityMask GetAffinityMask() const
113     {
114         return affinityMask_;
115     }
116 
SetAffinityMask(stackful_coroutines::AffinityMask mask)117     void SetAffinityMask(stackful_coroutines::AffinityMask mask)
118     {
119         affinityMask_ = mask;
120     }
121 
122 protected:
123     void SetStatus(Coroutine::Status newStatus) override;
124 
125 private:
126     void ThreadProcImpl();
127     static void CoroThreadProc(void *ctx);
128 
129     /// @brief The remote lambda call functionality implementation.
130     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
131     class RemoteCall {
132     public:
133         NO_COPY_SEMANTIC(RemoteCall);
134         NO_MOVE_SEMANTIC(RemoteCall);
135 
136         template <class L>
Execute(L * lambda,fibers::FiberContext * requesterContextPtr,fibers::FiberContext * hostContextPtr)137         bool Execute(L *lambda, fibers::FiberContext *requesterContextPtr, fibers::FiberContext *hostContextPtr)
138         {
139             ASSERT(Coroutine::GetCurrent()->GetVM()->GetThreadManager()->GetMainThread() !=
140                    ManagedThread::GetCurrent());
141 
142             callInProgress_ = true;
143             requesterContextPtr_ = requesterContextPtr;
144             lambda_ = lambda;
145 
146             fibers::CopyContext(&guestContext_, hostContextPtr);
147             fibers::UpdateContextKeepStack(&guestContext_, RemoteCall::Proxy<L>, this);
148             fibers::SwitchContext(requesterContextPtr_, &guestContext_);
149 
150             return true;
151         }
152 
153         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
154         RemoteCall() = default;
155         ~RemoteCall() = default;
156 
157     private:
158         template <class L>
Proxy(void * ctx)159         static void Proxy(void *ctx)
160         {
161             auto *thisInstance = static_cast<RemoteCall *>(ctx);
162             ASSERT(thisInstance->callInProgress_);
163 
164             (*static_cast<L *>(thisInstance->lambda_))();
165 
166             thisInstance->callInProgress_ = false;
167             fibers::SwitchContext(&thisInstance->guestContext_, thisInstance->requesterContextPtr_);
168         }
169 
170         bool callInProgress_ = false;
171         fibers::FiberContext *requesterContextPtr_ = nullptr;
172         fibers::FiberContext guestContext_;
173         void *lambda_ = nullptr;
174     } rpcCallContext_;
175 
176     uint8_t *stack_ = nullptr;
177     size_t stackSizeBytes_ = 0;
178     fibers::FiberContext context_;
179     Coroutine::Status status_ {Coroutine::Status::CREATED};
180     StackfulCoroutineWorker *worker_ = nullptr;
181     stackful_coroutines::AffinityMask affinityMask_ = stackful_coroutines::AFFINITY_MASK_NONE;
182 };
183 
184 }  // namespace ark
185 
186 #endif  // PANDA_RUNTIME_COROUTINES_STACKFUL_COROUTINE_H
187