1 /**
2 * Copyright (c) 2021-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
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_vm.h"
20 #include "plugins/ets/runtime/types/ets_method.h"
21 #include "runtime/coroutines/coroutine_manager.h"
22 #include "plugins/ets/runtime/types/ets_promise.h"
23 #include "plugins/ets/runtime/ets_handle_scope.h"
24 #include "plugins/ets/runtime/ets_handle.h"
25 #include "plugins/ets/runtime/job_queue.h"
26 #include "runtime/coroutines/stackful_coroutine.h"
27 #include "runtime/coroutines/coroutine_events.h"
28 #include "runtime/include/mem/panda_containers.h"
29
30 namespace ark::ets::intrinsics {
31
SubscribePromiseOnResultObject(EtsPromise * outsidePromise,EtsPromise * internalPromise)32 void SubscribePromiseOnResultObject(EtsPromise *outsidePromise, EtsPromise *internalPromise)
33 {
34 PandaVector<Value> args {Value(outsidePromise), Value(internalPromise)};
35
36 auto subscribeOnAnotherPromise = [&args]() {
37 EtsCoroutine::GetCurrent()->GetPandaVM()->GetClassLinker()->GetSubscribeOnAnotherPromiseMethod()->Invoke(
38 EtsCoroutine::GetCurrent(), args.data());
39 };
40
41 auto *mainT = EtsCoroutine::GetCurrent()->GetPandaVM()->GetCoroutineManager()->GetMainThread();
42 Coroutine *mainCoro = Coroutine::CastFromThread(mainT);
43 Coroutine *current = Coroutine::GetCurrent();
44 if (current != mainCoro && mainCoro->GetId() == current->GetId()) {
45 // Call ExecuteOnThisContext is possible only in the same thread.
46 mainCoro->GetContext<StackfulCoroutineContext>()->ExecuteOnThisContext(
47 &subscribeOnAnotherPromise, EtsCoroutine::GetCurrent()->GetContext<StackfulCoroutineContext>());
48 } else {
49 subscribeOnAnotherPromise();
50 }
51 }
52
EnsureCapacity(EtsCoroutine * coro,EtsHandle<EtsPromise> & hpromise)53 static void EnsureCapacity(EtsCoroutine *coro, EtsHandle<EtsPromise> &hpromise)
54 {
55 ASSERT(hpromise->IsLocked());
56 int queueLength = hpromise->GetCallbackQueue(coro) == nullptr ? 0 : hpromise->GetCallbackQueue(coro)->GetLength();
57 if (hpromise->GetQueueSize() != queueLength) {
58 return;
59 }
60 auto newQueueLength = queueLength * 2U + 1U;
61 auto *objectClass = coro->GetPandaVM()->GetClassLinker()->GetObjectClass();
62 auto *newCallbackQueue = EtsObjectArray::Create(objectClass, newQueueLength);
63 if (hpromise->GetQueueSize() != 0) {
64 hpromise->GetCallbackQueue(coro)->CopyDataTo(newCallbackQueue);
65 }
66 hpromise->SetCallbackQueue(coro, newCallbackQueue);
67 auto *newLaunchModeQueue = EtsIntArray::Create(newQueueLength);
68 if (hpromise->GetQueueSize() != 0) {
69 auto *launchModeQueueData = hpromise->GetLaunchModeQueue(coro)->GetData<EtsCoroutine *>();
70 [[maybe_unused]] auto err =
71 memcpy_s(newLaunchModeQueue->GetData<CoroutineLaunchMode>(), newQueueLength * sizeof(EtsInt),
72 launchModeQueueData, queueLength * sizeof(CoroutineLaunchMode));
73 ASSERT(err == EOK);
74 }
75 hpromise->SetLaunchModeQueue(coro, newLaunchModeQueue);
76 }
77
EtsPromiseResolve(EtsPromise * promise,EtsObject * value)78 void EtsPromiseResolve(EtsPromise *promise, EtsObject *value)
79 {
80 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
81 if (promise == nullptr) {
82 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
83 ThrowNullPointerException(ctx, coro);
84 return;
85 }
86 [[maybe_unused]] EtsHandleScope scope(coro);
87 EtsHandle<EtsPromise> hpromise(coro, promise);
88 EtsHandle<EtsObject> hvalue(coro, value);
89 EtsMutex::LockHolder lh(hpromise);
90 if (hpromise->GetState() != EtsPromise::STATE_PENDING) {
91 return;
92 }
93 if (hvalue.GetPtr() != nullptr && hvalue->IsInstanceOf(coro->GetPandaVM()->GetClassLinker()->GetPromiseClass())) {
94 auto internalPromise = EtsPromise::FromEtsObject(hvalue.GetPtr());
95 EtsHandle<EtsPromise> hInternalPromise(coro, internalPromise);
96 if (hInternalPromise->IsPending() || coro->GetCoroutineManager()->IsJsMode()) {
97 SubscribePromiseOnResultObject(hpromise.GetPtr(), hInternalPromise.GetPtr());
98 return;
99 }
100 if (hInternalPromise->IsRejected()) {
101 hpromise->Reject(coro, hInternalPromise->GetValue(coro));
102 return;
103 }
104 // We can use internal promise's value as return value
105 hvalue = EtsHandle<EtsObject>(coro, hInternalPromise->GetValue(coro));
106 }
107 hpromise->Resolve(coro, hvalue.GetPtr());
108 }
109
EtsPromiseReject(EtsPromise * promise,EtsObject * error)110 void EtsPromiseReject(EtsPromise *promise, EtsObject *error)
111 {
112 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
113 if (promise == nullptr) {
114 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
115 ThrowNullPointerException(ctx, coro);
116 return;
117 }
118 [[maybe_unused]] EtsHandleScope scope(coro);
119 EtsHandle<EtsPromise> hpromise(coro, promise);
120 EtsHandle<EtsObject> herror(coro, error);
121 EtsMutex::LockHolder lh(hpromise);
122 if (hpromise->GetState() != EtsPromise::STATE_PENDING) {
123 return;
124 }
125 hpromise->Reject(coro, herror.GetPtr());
126 }
127
EtsPromiseSubmitCallback(EtsPromise * promise,EtsObject * callback)128 void EtsPromiseSubmitCallback(EtsPromise *promise, EtsObject *callback)
129 {
130 auto *coro = EtsCoroutine::GetCurrent();
131 auto *coroManager = coro->GetCoroutineManager();
132 auto launchMode = coroManager->IsMainWorker(coro) ? CoroutineLaunchMode::MAIN_WORKER : CoroutineLaunchMode::DEFAULT;
133 [[maybe_unused]] EtsHandleScope scope(coro);
134 EtsHandle<EtsPromise> hpromise(coro, promise);
135 EtsHandle<EtsObject> hcallback(coro, callback);
136 EtsMutex::LockHolder lh(hpromise);
137 if (hpromise->GetState() == EtsPromise::STATE_PENDING) {
138 EnsureCapacity(coro, hpromise);
139 hpromise->SubmitCallback(coro, hcallback.GetPtr(), launchMode);
140 return;
141 }
142 ASSERT(hpromise->GetQueueSize() == 0);
143 ASSERT(hpromise->GetCallbackQueue(coro) == nullptr);
144 ASSERT(hpromise->GetLaunchModeQueue(coro) == nullptr);
145 EtsPromise::LaunchCallback(coro, hcallback.GetPtr(), launchMode);
146 }
147
EtsPromiseCreateLink(EtsObject * source,EtsPromise * target)148 void EtsPromiseCreateLink(EtsObject *source, EtsPromise *target)
149 {
150 EtsCoroutine *currentCoro = EtsCoroutine::GetCurrent();
151 auto *jobQueue = currentCoro->GetPandaVM()->GetJobQueue();
152 if (jobQueue != nullptr) {
153 jobQueue->CreateLink(source, target->AsObject());
154 }
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 * STS 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 EtsPromiseCreateLink(promiseHandle->GetLinkedPromise(currentCoro), promiseHandle.GetPtr());
174
175 promiseHandle->Wait();
176 ASSERT(!promiseHandle->IsPending());
177
178 // will get here after the JS callback is called
179 if (promiseHandle->IsResolved()) {
180 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been resolved.";
181 return promiseHandle->GetValue(currentCoro);
182 }
183 // rejected
184 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been rejected.";
185 auto *exc = promiseHandle->GetValue(currentCoro);
186 currentCoro->SetException(exc->GetCoreType());
187 return nullptr;
188 }
189
EtsAwaitPromise(EtsPromise * promise)190 EtsObject *EtsAwaitPromise(EtsPromise *promise)
191 {
192 EtsCoroutine *currentCoro = EtsCoroutine::GetCurrent();
193 if (promise == nullptr) {
194 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
195 ThrowNullPointerException(ctx, currentCoro);
196 return nullptr;
197 }
198 [[maybe_unused]] EtsHandleScope scope(currentCoro);
199 EtsHandle<EtsPromise> promiseHandle(currentCoro, promise);
200
201 /* CASE 1. This is a converted JS promise */
202 if (promiseHandle->IsProxy()) {
203 return AwaitProxyPromise(currentCoro, promiseHandle);
204 }
205
206 /* CASE 2. This is a native STS promise */
207 LOG(DEBUG, COROUTINES) << "Promise::await: starting await() for a promise...";
208 promiseHandle->Wait();
209 ASSERT(!promiseHandle->IsPending());
210 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished.";
211
212 /**
213 * The promise is already resolved or rejected. Further actions:
214 * STS mode:
215 * if resolved: return Promise.value
216 * if rejected: throw Promise.value
217 * JS mode: NOTE!
218 * - suspend coro, create resolved JS promise and put it to the Q, on callback resume the coro
219 * and possibly throw
220 * - JQ::put(current_coro, promise)
221 *
222 */
223 if (promiseHandle->IsResolved()) {
224 LOG(DEBUG, COROUTINES) << "Promise::await: promise is already resolved!";
225 return promiseHandle->GetValue(currentCoro);
226 }
227 LOG(DEBUG, COROUTINES) << "Promise::await: promise is already rejected!";
228 auto *exc = promiseHandle->GetValue(currentCoro);
229 currentCoro->SetException(exc->GetCoreType());
230 return nullptr;
231 }
232 } // namespace ark::ets::intrinsics
233