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
16 #include "runtime/coroutines/coroutine.h"
17 #include "runtime/include/panda_vm.h"
18 #include "runtime/include/thread_scopes.h"
19 #include "runtime/coroutines/stackful_coroutine.h"
20 #include "runtime/coroutines/stackful_coroutine_manager.h"
21
22 #if defined(PANDA_TSAN_ON)
23 #include <sanitizer/tsan_interface.h>
24 #endif /* PANDA_TSAN_ON */
25
26 namespace ark {
27
28 // clang-tidy cannot detect that we are going to initialize context_ via getcontext()
29 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
StackfulCoroutineContext(uint8_t * stack,size_t stackSizeBytes)30 StackfulCoroutineContext::StackfulCoroutineContext(uint8_t *stack, size_t stackSizeBytes)
31 : stack_(stack), stackSizeBytes_(stackSizeBytes)
32 {
33 fibers::GetCurrentContext(&context_);
34 #if defined(PANDA_TSAN_ON)
35 if (stack == nullptr) {
36 // entrypointless coroutine uses the current thread context
37 tsanFiberCtx_ = __tsan_get_current_fiber();
38 } else {
39 tsanFiberCtx_ = __tsan_create_fiber(0);
40 }
41 #endif /* PANDA_TSAN_ON */
42 }
43
~StackfulCoroutineContext()44 StackfulCoroutineContext::~StackfulCoroutineContext()
45 {
46 #if defined(PANDA_TSAN_ON)
47 if (stack_ != nullptr) {
48 __tsan_destroy_fiber(tsanFiberCtx_);
49 }
50 #else
51 // make clang-tidy happy! this is not a trivial dtor!
52 ;
53 #endif /* PANDA_TSAN_ON */
54 }
55
AttachToCoroutine(Coroutine * co)56 void StackfulCoroutineContext::AttachToCoroutine(Coroutine *co)
57 {
58 CoroutineContext::AttachToCoroutine(co);
59 if (co->HasManagedEntrypoint() || co->HasNativeEntrypoint()) {
60 fibers::UpdateContext(&context_, CoroThreadProc, this, stack_, stackSizeBytes_);
61 }
62 co->GetManager()->RegisterCoroutine(co);
63 SetStatus(Coroutine::Status::RUNNABLE);
64 }
65
RetrieveStackInfo(void * & stackAddr,size_t & stackSize,size_t & guardSize)66 bool StackfulCoroutineContext::RetrieveStackInfo(void *&stackAddr, size_t &stackSize, size_t &guardSize)
67 {
68 stackAddr = stack_;
69 stackSize = stackSizeBytes_;
70 guardSize = 0;
71 return true;
72 }
73
GetStatus() const74 Coroutine::Status StackfulCoroutineContext::GetStatus() const
75 {
76 return status_;
77 }
78
GetManager() const79 StackfulCoroutineManager *StackfulCoroutineContext::GetManager() const
80 {
81 ASSERT(GetCoroutine() != nullptr);
82 return static_cast<StackfulCoroutineManager *>(GetCoroutine()->GetManager());
83 }
84
SetStatus(Coroutine::Status newStatus)85 void StackfulCoroutineContext::SetStatus(Coroutine::Status newStatus)
86 {
87 ASSERT(GetCoroutine() != nullptr);
88 #ifndef NDEBUG
89 PandaString setter = (Thread::GetCurrent() == nullptr) ? "null" : Coroutine::GetCurrent()->GetName();
90 LOG(DEBUG, COROUTINES) << GetCoroutine()->GetName() << ": " << status_ << " -> " << newStatus << " by " << setter;
91 #endif
92 Coroutine::Status oldStatus = status_;
93 status_ = newStatus;
94 GetCoroutine()->OnStatusChanged(oldStatus, newStatus);
95 }
96
Destroy()97 void StackfulCoroutineContext::Destroy()
98 {
99 auto *co = GetCoroutine();
100 if (co->HasManagedEntrypoint()) {
101 // coroutines with an entry point should not be destroyed manually!
102 UNREACHABLE();
103 }
104 ASSERT(co == Coroutine::GetCurrent());
105 ASSERT(co->GetStatus() != ThreadStatus::FINISHED);
106
107 co->UpdateStatus(ThreadStatus::TERMINATING);
108 SetStatus(Coroutine::Status::TERMINATING);
109
110 if (co->GetManager()->TerminateCoroutine(co)) {
111 // detach
112 Coroutine::SetCurrent(nullptr);
113 }
114 }
115
CleanUp()116 void StackfulCoroutineContext::CleanUp()
117 {
118 #ifdef PANDA_ASAN_ON
119 void *contextStackP;
120 size_t contextStackSize;
121 size_t contextGuardSize;
122 RetrieveStackInfo(contextStackP, contextStackSize, contextGuardSize);
123 ASAN_UNPOISON_MEMORY_REGION(contextStackP, contextStackSize);
124 #endif // PANDA_ASAN_ON
125 affinityMask_ = stackful_coroutines::AFFINITY_MASK_NONE;
126 }
127
128 /*static*/
CoroThreadProc(void * ctx)129 void StackfulCoroutineContext::CoroThreadProc(void *ctx)
130 {
131 static_cast<StackfulCoroutineContext *>(ctx)->ThreadProcImpl();
132 }
133
ThreadProcImpl()134 void StackfulCoroutineContext::ThreadProcImpl()
135 {
136 auto *co = GetCoroutine();
137 // profiling: the interval was started in the ctxswitch
138 GetWorker()->GetPerfStats().FinishInterval(CoroutineTimeStats::CTX_SWITCH);
139 // consider changing this to INIT later on...
140 GetWorker()->GetPerfStats().StartInterval(CoroutineTimeStats::SCH_ALL);
141
142 co->NativeCodeBegin();
143 SetStatus(Coroutine::Status::RUNNING);
144 if (co->HasManagedEntrypoint()) {
145 ScopedManagedCodeThread s(co);
146 PandaVector<Value> args = std::move(co->GetManagedEntrypointArguments());
147 // profiling
148 GetWorker()->GetPerfStats().FinishInterval(CoroutineTimeStats::SCH_ALL);
149 Value result = co->GetManagedEntrypoint()->Invoke(co, args.data());
150 co->RequestCompletion(result);
151 } else if (co->HasNativeEntrypoint()) {
152 // profiling: jump to the NATIVE EP, will end the SCH_ALL there
153 co->GetNativeEntrypoint()(co->GetNativeEntrypointParam());
154 }
155 if (co->HasPendingException() && co->HasAbortFlag()) {
156 co->HandleUncaughtException();
157 UNREACHABLE();
158 }
159 SetStatus(Coroutine::Status::TERMINATING);
160 co->GetManager()->TerminateCoroutine(co);
161 }
162
SwitchTo(StackfulCoroutineContext * target)163 bool StackfulCoroutineContext::SwitchTo(StackfulCoroutineContext *target)
164 {
165 ASSERT(target != nullptr);
166 #if defined(PANDA_TSAN_ON)
167 __tsan_switch_to_fiber(target->tsanFiberCtx_, 0);
168 #endif /* PANDA_TSAN_ON */
169 fibers::SwitchContext(&context_, &target->context_);
170 // maybe eventually we will check the return value of SwitchContext() and return false in case of error...
171 return true;
172 }
173
RequestSuspend(bool getsBlocked)174 void StackfulCoroutineContext::RequestSuspend(bool getsBlocked)
175 {
176 SetStatus(getsBlocked ? Coroutine::Status::BLOCKED : Coroutine::Status::RUNNABLE);
177 }
178
RequestResume()179 void StackfulCoroutineContext::RequestResume()
180 {
181 UpdateId(os::thread::GetCurrentThreadId(), GetCoroutine());
182 SetStatus(Coroutine::Status::RUNNING);
183 }
184
RequestUnblock()185 void StackfulCoroutineContext::RequestUnblock()
186 {
187 SetStatus(Coroutine::Status::RUNNABLE);
188 }
189
MainThreadFinished()190 void StackfulCoroutineContext::MainThreadFinished()
191 {
192 SetStatus(Coroutine::Status::TERMINATING);
193 }
194
EnterAwaitLoop()195 void StackfulCoroutineContext::EnterAwaitLoop()
196 {
197 SetStatus(Coroutine::Status::AWAIT_LOOP);
198 }
199
200 } // namespace ark
201