1 /** 2 * Copyright (c) 2023-2025 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; 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_PRINT(false, "This method should not be called"); 96 ASSERT(requester != nullptr); 97 return rpcCallContext_.Execute(lambda, &requester->context_, &context_); 98 } 99 100 /// get the currently assigned worker thread GetWorker()101 StackfulCoroutineWorker *GetWorker() const 102 { 103 auto *coro = GetCoroutine(); 104 ASSERT(coro != nullptr); 105 return reinterpret_cast<StackfulCoroutineWorker *>(coro->GetWorker()); 106 } 107 108 /// @return current coroutine's affinity bits GetAffinityMask()109 stackful_coroutines::AffinityMask GetAffinityMask() const 110 { 111 return affinityMask_; 112 } 113 SetAffinityMask(stackful_coroutines::AffinityMask mask)114 void SetAffinityMask(stackful_coroutines::AffinityMask mask) 115 { 116 affinityMask_ = mask; 117 } 118 119 /// @return true if migration from worker is allowed IsMigrationAllowed()120 bool IsMigrationAllowed() const 121 { 122 std::bitset<stackful_coroutines::MAX_WORKERS_COUNT> mask(affinityMask_); 123 return mask.count() > 1; 124 } 125 126 protected: 127 void SetStatus(Coroutine::Status newStatus) override; 128 StackfulCoroutineManager *GetManager() const; 129 130 private: 131 void ThreadProcImpl(); 132 static void CoroThreadProc(void *ctx); 133 134 /// @brief The remote lambda call functionality implementation. 135 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) 136 class RemoteCall { 137 public: 138 NO_COPY_SEMANTIC(RemoteCall); 139 NO_MOVE_SEMANTIC(RemoteCall); 140 141 template <class L> Execute(L * lambda,fibers::FiberContext * requesterContextPtr,fibers::FiberContext * hostContextPtr)142 bool Execute(L *lambda, fibers::FiberContext *requesterContextPtr, fibers::FiberContext *hostContextPtr) 143 { 144 ASSERT(Coroutine::GetCurrent()->GetVM()->GetThreadManager()->GetMainThread() != 145 ManagedThread::GetCurrent()); 146 147 callInProgress_ = true; 148 requesterContextPtr_ = requesterContextPtr; 149 lambda_ = lambda; 150 151 fibers::CopyContext(&guestContext_, hostContextPtr); 152 fibers::UpdateContextKeepStack(&guestContext_, RemoteCall::Proxy<L>, this); 153 fibers::SwitchContext(requesterContextPtr_, &guestContext_); 154 155 return true; 156 } 157 158 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) 159 RemoteCall() = default; 160 ~RemoteCall() = default; 161 162 private: 163 template <class L> Proxy(void * ctx)164 static void Proxy(void *ctx) 165 { 166 auto *thisInstance = static_cast<RemoteCall *>(ctx); 167 ASSERT(thisInstance->callInProgress_); 168 169 (*static_cast<L *>(thisInstance->lambda_))(); 170 171 thisInstance->callInProgress_ = false; 172 fibers::SwitchContext(&thisInstance->guestContext_, thisInstance->requesterContextPtr_); 173 } 174 175 bool callInProgress_ = false; 176 fibers::FiberContext *requesterContextPtr_ = nullptr; 177 fibers::FiberContext guestContext_; 178 void *lambda_ = nullptr; 179 } rpcCallContext_; 180 181 uint8_t *stack_ = nullptr; 182 size_t stackSizeBytes_ = 0; 183 fibers::FiberContext context_; 184 Coroutine::Status status_ {Coroutine::Status::CREATED}; 185 stackful_coroutines::AffinityMask affinityMask_ = stackful_coroutines::AFFINITY_MASK_NONE; 186 #if defined(PANDA_TSAN_ON) 187 void *tsanFiberCtx_ {nullptr}; 188 #endif /* PANDA_TSAN_ON */ 189 }; 190 191 } // namespace ark 192 193 #endif // PANDA_RUNTIME_COROUTINES_STACKFUL_COROUTINE_H 194