1 /**
2 * Copyright (c) 2021-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 "intrinsics.h"
17 #include "plugins/ets/runtime/ets_utils.h"
18 #include "plugins/ets/runtime/ets_coroutine.h"
19 #include "plugins/ets/runtime/ets_platform_types.h"
20 #include "plugins/ets/runtime/ets_vm.h"
21 #include "plugins/ets/runtime/types/ets_method.h"
22 #include "runtime/coroutines/coroutine_manager.h"
23 #include "plugins/ets/runtime/types/ets_promise.h"
24 #include "plugins/ets/runtime/ets_handle_scope.h"
25 #include "plugins/ets/runtime/ets_handle.h"
26 #include "plugins/ets/runtime/job_queue.h"
27 #include "runtime/coroutines/stackful_coroutine.h"
28 #include "runtime/coroutines/coroutine_events.h"
29 #include "runtime/include/mem/panda_containers.h"
30
31 namespace ark::ets::intrinsics {
32
SubscribePromiseOnResultObject(EtsPromise * outsidePromise,EtsPromise * internalPromise)33 void SubscribePromiseOnResultObject(EtsPromise *outsidePromise, EtsPromise *internalPromise)
34 {
35 PandaVector<Value> args {Value(outsidePromise), Value(internalPromise)};
36
37 PlatformTypes()->corePromiseSubscribeOnAnotherPromise->GetPandaMethod()->Invoke(EtsCoroutine::GetCurrent(),
38 args.data());
39 }
40
EnsureCapacity(EtsCoroutine * coro,EtsHandle<EtsPromise> & hpromise)41 static void EnsureCapacity(EtsCoroutine *coro, EtsHandle<EtsPromise> &hpromise)
42 {
43 ASSERT(hpromise.GetPtr() != nullptr);
44 ASSERT(hpromise->IsLocked());
45 int queueLength = hpromise->GetCallbackQueue(coro) == nullptr ? 0 : hpromise->GetCallbackQueue(coro)->GetLength();
46 if (hpromise->GetQueueSize() != queueLength) {
47 return;
48 }
49 auto newQueueLength = queueLength * 2U + 1U;
50 auto *objectClass = coro->GetPandaVM()->GetClassLinker()->GetClassRoot(EtsClassRoot::OBJECT);
51 auto *newCallbackQueue = EtsObjectArray::Create(objectClass, newQueueLength);
52 if (hpromise->GetQueueSize() != 0) {
53 hpromise->GetCallbackQueue(coro)->CopyDataTo(newCallbackQueue);
54 }
55 hpromise->SetCallbackQueue(coro, newCallbackQueue);
56 auto *newLaunchModeQueue = EtsIntArray::Create(newQueueLength);
57 if (hpromise->GetQueueSize() != 0) {
58 auto *launchModeQueueData = hpromise->GetLaunchModeQueue(coro)->GetData<EtsCoroutine *>();
59 [[maybe_unused]] auto err =
60 memcpy_s(newLaunchModeQueue->GetData<CoroutineLaunchMode>(), newQueueLength * sizeof(EtsInt),
61 launchModeQueueData, queueLength * sizeof(CoroutineLaunchMode));
62 ASSERT(err == EOK);
63 }
64 hpromise->SetLaunchModeQueue(coro, newLaunchModeQueue);
65 }
66
EtsPromiseResolve(EtsPromise * promise,EtsObject * value,EtsBoolean wasLinked)67 void EtsPromiseResolve(EtsPromise *promise, EtsObject *value, EtsBoolean wasLinked)
68 {
69 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
70 if (promise == nullptr) {
71 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
72 ThrowNullPointerException(ctx, coro);
73 return;
74 }
75 [[maybe_unused]] EtsHandleScope scope(coro);
76 EtsHandle<EtsPromise> hpromise(coro, promise);
77 EtsHandle<EtsObject> hvalue(coro, value);
78
79 if (wasLinked == 0) {
80 /* When the value is still a Promise, the lock must be unlocked first. */
81 hpromise->Lock();
82 if (!hpromise->IsPending()) {
83 hpromise->Unlock();
84 return;
85 }
86 if (hvalue.GetPtr() != nullptr && hvalue->IsInstanceOf(PlatformTypes(coro)->corePromise)) {
87 auto internalPromise = EtsPromise::FromEtsObject(hvalue.GetPtr());
88 EtsHandle<EtsPromise> hInternalPromise(coro, internalPromise);
89 hpromise->Unlock();
90 SubscribePromiseOnResultObject(hpromise.GetPtr(), hInternalPromise.GetPtr());
91 return;
92 }
93 hpromise->Resolve(coro, hvalue.GetPtr());
94 hpromise->Unlock();
95 } else {
96 /* When the value is still a Promise, the lock must be unlocked first. */
97 hpromise->Lock();
98 if (!hpromise->IsLinked()) {
99 hpromise->Unlock();
100 return;
101 }
102 if (hvalue.GetPtr() != nullptr && hvalue->IsInstanceOf(PlatformTypes(coro)->corePromise)) {
103 auto internalPromise = EtsPromise::FromEtsObject(hvalue.GetPtr());
104 EtsHandle<EtsPromise> hInternalPromise(coro, internalPromise);
105 hpromise->ChangeStateToPendingFromLinked();
106 hpromise->Unlock();
107 SubscribePromiseOnResultObject(hpromise.GetPtr(), hInternalPromise.GetPtr());
108 return;
109 }
110 hpromise->Resolve(coro, hvalue.GetPtr());
111 hpromise->Unlock();
112 }
113 }
114
EtsPromiseReject(EtsPromise * promise,EtsObject * error,EtsBoolean wasLinked)115 void EtsPromiseReject(EtsPromise *promise, EtsObject *error, EtsBoolean wasLinked)
116 {
117 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
118 if (promise == nullptr) {
119 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
120 ThrowNullPointerException(ctx, coro);
121 return;
122 }
123 [[maybe_unused]] EtsHandleScope scope(coro);
124 EtsHandle<EtsPromise> hpromise(coro, promise);
125 EtsHandle<EtsObject> herror(coro, error);
126 EtsMutex::LockHolder lh(hpromise);
127 if ((!hpromise->IsPending() && wasLinked == 0) || (!hpromise->IsLinked() && wasLinked != 0)) {
128 return;
129 }
130 hpromise->Reject(coro, herror.GetPtr());
131 }
132
EtsPromiseSubmitCallback(EtsPromise * promise,EtsObject * callback)133 void EtsPromiseSubmitCallback(EtsPromise *promise, EtsObject *callback)
134 {
135 auto *coro = EtsCoroutine::GetCurrent();
136 ASSERT(coro != nullptr);
137 auto *coroManager = coro->GetCoroutineManager();
138 auto launchMode = coroManager->IsMainWorker(coro) ? CoroutineLaunchMode::MAIN_WORKER : CoroutineLaunchMode::DEFAULT;
139 [[maybe_unused]] EtsHandleScope scope(coro);
140 EtsHandle<EtsPromise> hpromise(coro, promise);
141 EtsHandle<EtsObject> hcallback(coro, callback);
142 EtsMutex::LockHolder lh(hpromise);
143 if (hpromise->IsPending() || hpromise->IsLinked()) {
144 EnsureCapacity(coro, hpromise);
145 hpromise->SubmitCallback(coro, hcallback.GetPtr(), launchMode);
146 return;
147 }
148 if (Runtime::GetOptions().IsListUnhandledOnExitPromises(plugins::LangToRuntimeType(panda_file::SourceLang::ETS))) {
149 coro->GetPandaVM()->RemoveUnhandledRejectedPromise(hpromise.GetPtr());
150 }
151 ASSERT(hpromise->GetQueueSize() == 0);
152 ASSERT(hpromise->GetCallbackQueue(coro) == nullptr);
153 ASSERT(hpromise->GetLaunchModeQueue(coro) == nullptr);
154 EtsPromise::LaunchCallback(coro, hcallback.GetPtr(), launchMode);
155 }
156
AwaitProxyPromise(EtsCoroutine * currentCoro,EtsHandle<EtsPromise> & promiseHandle)157 static EtsObject *AwaitProxyPromise(EtsCoroutine *currentCoro, EtsHandle<EtsPromise> &promiseHandle)
158 {
159 /**
160 * This is a backed by JS equivalent promise.
161 * ETS mode: error, no one can create such a promise!
162 * JS mode:
163 * - add a callback to JQ, that will:
164 * - resolve the promise with some value OR reject it
165 * - unblock the coro via event
166 * - schedule();
167 * - create a blocker event and link it to the promise
168 * - block current coroutine on the event
169 * - Schedule();
170 * (the last two steps are actually the cm->await()'s job)
171 * - return promise.value() if resolved or throw() it if rejected
172 */
173 ASSERT(promiseHandle.GetPtr() != nullptr);
174 promiseHandle->Wait();
175 ASSERT(!promiseHandle->IsPending() && !promiseHandle->IsLinked());
176
177 // will get here after the JS callback is called
178 if (promiseHandle->IsResolved()) {
179 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been resolved.";
180 return promiseHandle->GetValue(currentCoro);
181 }
182 // rejected
183 if (Runtime::GetOptions().IsListUnhandledOnExitPromises(plugins::LangToRuntimeType(panda_file::SourceLang::ETS))) {
184 currentCoro->GetPandaVM()->RemoveUnhandledRejectedPromise(promiseHandle.GetPtr());
185 }
186 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been rejected.";
187 auto *exc = promiseHandle->GetValue(currentCoro);
188 ASSERT(exc != nullptr);
189 currentCoro->SetException(exc->GetCoreType());
190 return nullptr;
191 }
192
EtsAwaitPromise(EtsPromise * promise)193 EtsObject *EtsAwaitPromise(EtsPromise *promise)
194 {
195 EtsCoroutine *currentCoro = EtsCoroutine::GetCurrent();
196 if (promise == nullptr) {
197 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
198 ThrowNullPointerException(ctx, currentCoro);
199 return nullptr;
200 }
201 if (currentCoro->GetCoroutineManager()->IsCoroutineSwitchDisabled()) {
202 ThrowEtsException(currentCoro, panda_file_items::class_descriptors::INVALID_COROUTINE_OPERATION_ERROR,
203 "Cannot await in the current context!");
204 return nullptr;
205 }
206 [[maybe_unused]] EtsHandleScope scope(currentCoro);
207 EtsHandle<EtsPromise> promiseHandle(currentCoro, promise);
208
209 {
210 ScopedNativeCodeThread n(currentCoro);
211 currentCoro->GetManager()->Schedule();
212 }
213
214 /* CASE 1. This is a converted JS promise */
215 if (promiseHandle->IsProxy()) {
216 return AwaitProxyPromise(currentCoro, promiseHandle);
217 }
218
219 /* CASE 2. This is a native ETS promise */
220 LOG(DEBUG, COROUTINES) << "Promise::await: starting await() for a promise...";
221 promiseHandle->Wait();
222 ASSERT(!promiseHandle->IsPending() && !promiseHandle->IsLinked());
223 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished.";
224
225 /**
226 * The promise is already resolved or rejected. Further actions:
227 * ETS mode:
228 * if resolved: return Promise.value
229 * if rejected: throw Promise.value
230 * JS mode: NOTE!
231 * - suspend coro, create resolved JS promise and put it to the Q, on callback resume the coro
232 * and possibly throw
233 * - JQ::put(current_coro, promise)
234 *
235 */
236 if (promiseHandle->IsResolved()) {
237 LOG(DEBUG, COROUTINES) << "Promise::await: promise is already resolved!";
238 return promiseHandle->GetValue(currentCoro);
239 }
240 if (Runtime::GetOptions().IsListUnhandledOnExitPromises(plugins::LangToRuntimeType(panda_file::SourceLang::ETS))) {
241 currentCoro->GetPandaVM()->RemoveUnhandledRejectedPromise(promiseHandle.GetPtr());
242 }
243 LOG(DEBUG, COROUTINES) << "Promise::await: promise is already rejected!";
244 auto *exc = promiseHandle->GetValue(currentCoro);
245 currentCoro->SetException(exc->GetCoreType());
246 return nullptr;
247 }
248 } // namespace ark::ets::intrinsics
249