1 /**
2 * Copyright (c) 2022-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 "plugins/ets/runtime/ets_coroutine.h"
17 #include "runtime/include/value.h"
18 #include "macros.h"
19 #include "mem/refstorage/reference.h"
20 #include "runtime/include/object_header.h"
21 #include "plugins/ets/runtime/types/ets_promise.h"
22 #include "plugins/ets/runtime/types/ets_job.h"
23 #include "plugins/ets/runtime/ets_vm.h"
24 #include "runtime/include/panda_vm.h"
25 #include "plugins/ets/runtime/ets_class_linker_extension.h"
26 #include "plugins/ets/runtime/types/ets_object.h"
27 #include "plugins/ets/runtime/types/ets_box_primitive-inl.h"
28 #include "intrinsics.h"
29
30 namespace ark::ets {
31
32 // ExternalIfaceTable contains std::function, which cannot be trivially constructed even for nullptr
33 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
34 ExternalIfaceTable EtsCoroutine::emptyExternalIfaceTable_ = ExternalIfaceTable();
35
EtsCoroutine(ThreadId id,mem::InternalAllocatorPtr allocator,PandaVM * vm,PandaString name,CoroutineContext * context,std::optional<EntrypointInfo> && epInfo,Type type,CoroutinePriority priority)36 EtsCoroutine::EtsCoroutine(ThreadId id, mem::InternalAllocatorPtr allocator, PandaVM *vm, PandaString name,
37 CoroutineContext *context, std::optional<EntrypointInfo> &&epInfo, Type type,
38 CoroutinePriority priority)
39 : Coroutine(id, allocator, vm, ark::panda_file::SourceLang::ETS, std::move(name), context, std::move(epInfo), type,
40 priority)
41 {
42 ASSERT(vm != nullptr);
43 }
44
GetPandaVM() const45 PandaEtsVM *EtsCoroutine::GetPandaVM() const
46 {
47 return static_cast<PandaEtsVM *>(GetVM());
48 }
49
GetCoroutineManager() const50 CoroutineManager *EtsCoroutine::GetCoroutineManager() const
51 {
52 return GetPandaVM()->GetCoroutineManager();
53 }
54
Initialize()55 void EtsCoroutine::Initialize()
56 {
57 auto allocator = GetVM()->GetHeapManager()->GetInternalAllocator();
58 auto etsNapiEnv = PandaEtsNapiEnv::Create(this, allocator);
59 if (!etsNapiEnv) {
60 LOG(FATAL, RUNTIME) << "Cannot create PandaEtsNapiEnv: " << etsNapiEnv.Error();
61 }
62 etsNapiEnv_ = etsNapiEnv.Value();
63 // Main EtsCoroutine is Initialized before class linker and promise_class_ptr_ will be set after creating the class
64 if (Runtime::GetCurrent()->IsInitialized()) {
65 promiseClassPtr_ = PlatformTypes(GetPandaVM())->corePromise->GetRuntimeClass();
66 SetupNullValue(GetPandaVM()->GetNullValue());
67 GetLocalStorage().Set<EtsCoroutine::DataIdx::ETS_PLATFORM_TYPES_PTR>(
68 ToUintPtr(GetPandaVM()->GetClassLinker()->GetEtsClassLinkerExtension()->GetPlatformTypes()));
69 // NOTE (electronick, #15938): Refactor the managed class-related pseudo TLS fields
70 // initialization in MT ManagedThread ctor and EtsCoroutine::Initialize
71 auto *linkExt = GetPandaVM()->GetClassLinker()->GetEtsClassLinkerExtension();
72 SetStringClassPtr(linkExt->GetClassRoot(ClassRoot::STRING));
73 SetArrayU16ClassPtr(linkExt->GetClassRoot(ClassRoot::ARRAY_U16));
74 SetArrayU8ClassPtr(linkExt->GetClassRoot(ClassRoot::ARRAY_U8));
75 }
76 ASSERT(promiseClassPtr_ != nullptr || !HasManagedEntrypoint());
77
78 Coroutine::Initialize();
79 }
80
CleanUp()81 void EtsCoroutine::CleanUp()
82 {
83 Coroutine::CleanUp();
84 // add the required local storage entries cleanup here!
85 }
86
FreeInternalMemory()87 void EtsCoroutine::FreeInternalMemory()
88 {
89 etsNapiEnv_->FreeInternalMemory();
90 ManagedThread::FreeInternalMemory();
91 }
92
RequestCompletion(Value returnValue)93 void EtsCoroutine::RequestCompletion(Value returnValue)
94 {
95 auto *completionObjRef = GetCompletionEvent()->ReleaseReturnValueObject();
96 if (completionObjRef == nullptr) {
97 Coroutine::RequestCompletion(returnValue);
98 return;
99 }
100 auto *storage = GetVM()->GetGlobalObjectStorage();
101 auto *completionObj = EtsObject::FromCoreType(storage->Get(completionObjRef));
102
103 if (completionObj->IsInstanceOf(PlatformTypes(this)->corePromise)) {
104 RequestPromiseCompletion(completionObjRef, returnValue);
105 } else if (completionObj->IsInstanceOf(PlatformTypes(this)->coreJob)) {
106 RequestJobCompletion(completionObjRef, returnValue);
107 } else {
108 UNREACHABLE();
109 }
110 }
111
RequestJobCompletion(mem::Reference * jobRef,Value returnValue)112 void EtsCoroutine::RequestJobCompletion(mem::Reference *jobRef, Value returnValue)
113 {
114 auto *storage = GetVM()->GetGlobalObjectStorage();
115 auto *job = EtsJob::FromCoreType(storage->Get(jobRef));
116 storage->Remove(jobRef);
117 if (job == nullptr) {
118 LOG(DEBUG, COROUTINES)
119 << "Coroutine \"" << GetName()
120 << "\" has completed, but the associated job has been already collected by the GC. Exception thrown: "
121 << HasPendingException();
122 Coroutine::RequestCompletion(returnValue);
123 return;
124 }
125 [[maybe_unused]] EtsHandleScope scope(this);
126 EtsHandle<EtsJob> hjob(this, job);
127 EtsObject *retObject = nullptr;
128 if (!HasPendingException()) {
129 panda_file::Type returnType = GetReturnType();
130 retObject = GetReturnValueAsObject(returnType, returnValue);
131 if (retObject != nullptr) {
132 LOG_IF(returnType.IsVoid(), DEBUG, COROUTINES) << "Coroutine \"" << GetName() << "\" has completed";
133 LOG_IF(returnType.IsPrimitive(), DEBUG, COROUTINES)
134 << "Coroutine \"" << GetName() << "\" has completed with return value 0x" << std::hex
135 << returnValue.GetAs<uint64_t>();
136 LOG_IF(returnType.IsReference(), DEBUG, COROUTINES)
137 << "Coroutine \"" << GetName() << "\" has completed with return value = ObjectPtr<"
138 << returnValue.GetAs<ObjectHeader *>() << ">";
139 }
140 }
141 if (HasPendingException()) {
142 // An exception may occur while boxin primitive return value in GetReturnValueAsObject
143 auto *exc = GetException();
144 if (!HasAbortFlag()) {
145 ClearException();
146 }
147 LOG(INFO, COROUTINES) << "Coroutine \"" << GetName()
148 << "\" completed with an exception: " << exc->ClassAddr<Class>()->GetName();
149 EtsJob::EtsJobFail(hjob.GetPtr(), EtsObject::FromCoreType(exc));
150 return;
151 }
152 EtsJob::EtsJobFinish(hjob.GetPtr(), retObject);
153 }
154
RequestPromiseCompletion(mem::Reference * promiseRef,Value returnValue)155 void EtsCoroutine::RequestPromiseCompletion(mem::Reference *promiseRef, Value returnValue)
156 {
157 auto *storage = GetVM()->GetGlobalObjectStorage();
158 auto *promise = EtsPromise::FromCoreType(storage->Get(promiseRef));
159 storage->Remove(promiseRef);
160 if (promise == nullptr) {
161 LOG(DEBUG, COROUTINES)
162 << "Coroutine " << GetName()
163 << " has completed, but the associated promise has been already collected by the GC. Exception thrown: "
164 << HasPendingException();
165 Coroutine::RequestCompletion(returnValue);
166 return;
167 }
168 [[maybe_unused]] EtsHandleScope scope(this);
169 EtsHandle<EtsPromise> hpromise(this, promise);
170 EtsObject *retObject = nullptr;
171 if (!HasPendingException()) {
172 panda_file::Type returnType = GetReturnType();
173 retObject = GetReturnValueAsObject(returnType, returnValue);
174 if (retObject != nullptr) {
175 LOG_IF(returnType.IsVoid(), DEBUG, COROUTINES) << "Coroutine " << GetName() << " has completed";
176 LOG_IF(returnType.IsPrimitive(), DEBUG, COROUTINES)
177 << "Coroutine " << GetName() << " has completed with return value 0x" << std::hex
178 << returnValue.GetAs<uint64_t>();
179 LOG_IF(returnType.IsReference(), DEBUG, COROUTINES)
180 << "Coroutine " << GetName() << " has completed with return value = ObjectPtr<"
181 << returnValue.GetAs<ObjectHeader *>() << ">";
182 }
183 }
184 if (retObject != nullptr && retObject->IsInstanceOf(PlatformTypes(this)->corePromise)) {
185 // If the retObject is a rejected Promise, the reject reason will be set as an exception.
186 retObject = GetValueFromPromiseSync(EtsPromise::FromEtsObject(retObject));
187 if (retObject == nullptr) {
188 LOG(INFO, COROUTINES) << "Coroutine " << GetName() << " completion by a promise retval went wrong";
189 }
190 }
191 if (HasPendingException()) {
192 // An exception may occur while boxin primitive return value in GetReturnValueAsObject
193 auto *exc = GetException();
194 ClearException();
195 LOG(INFO, COROUTINES) << "Coroutine " << GetName()
196 << " completed with an exception: " << exc->ClassAddr<Class>()->GetName();
197 intrinsics::EtsPromiseReject(hpromise.GetPtr(), EtsObject::FromCoreType(exc), ToEtsBoolean(false));
198 return;
199 }
200 intrinsics::EtsPromiseResolve(hpromise.GetPtr(), retObject, ToEtsBoolean(false));
201 }
202
GetValueFromPromiseSync(EtsPromise * promise)203 EtsObject *EtsCoroutine::GetValueFromPromiseSync(EtsPromise *promise)
204 {
205 return intrinsics::EtsAwaitPromise(promise);
206 }
207
GetReturnType()208 panda_file::Type EtsCoroutine::GetReturnType()
209 {
210 Method *entrypoint = GetManagedEntrypoint();
211 ASSERT(entrypoint != nullptr);
212 return entrypoint->GetReturnType();
213 }
214
215 // The result will be used to resolve a promise, so this function perfoms a "box" operation on ark::Value
GetReturnValueAsObject(panda_file::Type returnType,Value returnValue)216 EtsObject *EtsCoroutine::GetReturnValueAsObject(panda_file::Type returnType, Value returnValue)
217 {
218 switch (returnType.GetId()) {
219 case panda_file::Type::TypeId::VOID:
220 return nullptr; // a representation of ets "undefined"
221 case panda_file::Type::TypeId::U1:
222 return EtsBoxPrimitive<EtsBoolean>::Create(this, returnValue.GetAs<EtsBoolean>());
223 case panda_file::Type::TypeId::I8:
224 return EtsBoxPrimitive<EtsByte>::Create(this, returnValue.GetAs<EtsByte>());
225 case panda_file::Type::TypeId::I16:
226 return EtsBoxPrimitive<EtsShort>::Create(this, returnValue.GetAs<EtsShort>());
227 case panda_file::Type::TypeId::U16:
228 return EtsBoxPrimitive<EtsChar>::Create(this, returnValue.GetAs<EtsChar>());
229 case panda_file::Type::TypeId::I32:
230 return EtsBoxPrimitive<EtsInt>::Create(this, returnValue.GetAs<EtsInt>());
231 case panda_file::Type::TypeId::F32:
232 return EtsBoxPrimitive<EtsFloat>::Create(this, returnValue.GetAs<EtsFloat>());
233 case panda_file::Type::TypeId::F64:
234 return EtsBoxPrimitive<EtsDouble>::Create(this, returnValue.GetAs<EtsDouble>());
235 case panda_file::Type::TypeId::I64:
236 return EtsBoxPrimitive<EtsLong>::Create(this, returnValue.GetAs<EtsLong>());
237 case panda_file::Type::TypeId::REFERENCE:
238 return EtsObject::FromCoreType(returnValue.GetAs<ObjectHeader *>());
239 default:
240 LOG(FATAL, COROUTINES) << "Unsupported return type: " << returnType;
241 break;
242 }
243 return nullptr;
244 }
245
GetExternalIfaceTable()246 ExternalIfaceTable *EtsCoroutine::GetExternalIfaceTable()
247 {
248 auto *worker = GetWorker();
249 auto *table = worker->GetLocalStorage().Get<CoroutineWorker::DataIdx::EXTERNAL_IFACES, ExternalIfaceTable *>();
250 if (table != nullptr) {
251 return table;
252 }
253 return &emptyExternalIfaceTable_;
254 }
255
OnHostWorkerChanged()256 void EtsCoroutine::OnHostWorkerChanged()
257 {
258 // update the interop context pointer
259 auto *worker = GetWorker();
260 auto *ptr = worker->GetLocalStorage().Get<CoroutineWorker::DataIdx::INTEROP_CTX_PTR, void *>();
261 GetLocalStorage().Set<DataIdx::INTEROP_CTX_PTR>(ptr);
262 }
263
HandleUncaughtException()264 [[noreturn]] void EtsCoroutine::HandleUncaughtException()
265 {
266 ASSERT(HasPendingException());
267 GetPandaVM()->HandleUncaughtException();
268 UNREACHABLE();
269 }
270
ListUnhandledJobs()271 void EtsCoroutine::ListUnhandledJobs()
272 {
273 auto *vm = GetPandaVM();
274 vm->ListUnhandledFailedJobs();
275 }
276
ListUnhandledPromises()277 void EtsCoroutine::ListUnhandledPromises()
278 {
279 auto *vm = GetPandaVM();
280 vm->ListUnhandledRejectedPromises();
281 }
282
ListUnhandledEventsOnProgramExit()283 void EtsCoroutine::ListUnhandledEventsOnProgramExit()
284 {
285 if (Runtime::GetOptions().IsArkAot()) {
286 return;
287 }
288 if (Runtime::GetOptions().IsListUnhandledOnExitJobs(plugins::LangToRuntimeType(panda_file::SourceLang::ETS))) {
289 ListUnhandledJobs();
290 }
291 if (Runtime::GetOptions().IsListUnhandledOnExitPromises(plugins::LangToRuntimeType(panda_file::SourceLang::ETS))) {
292 ListUnhandledPromises();
293 }
294 }
295
296 } // namespace ark::ets
297