1 /**
2 * Copyright (c) 2021-2022 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/lambda_utils.h"
18 #include "plugins/ets/runtime/ets_coroutine.h"
19 #include "plugins/ets/runtime/ets_vm.h"
20 #include "runtime/coroutines/coroutine_manager.h"
21 #include "plugins/ets/runtime/types/ets_promise.h"
22 #include "plugins/ets/runtime/ets_handle_scope.h"
23 #include "plugins/ets/runtime/ets_handle.h"
24 #include "plugins/ets/runtime/types/ets_void.h"
25 #include "runtime/handle_scope-inl.h"
26 #include "runtime/handle_scope.h"
27 #include "plugins/ets/runtime/job_queue.h"
28
29 namespace panda::ets::intrinsics {
30
31 // length parameter represents the current size of the queue as opposed to its length field which represents its
32 // capacity
OnPromiseCompletion(EtsCoroutine * coro,EtsHandle<EtsPromise> & promise,EtsHandle<EtsObjectArray> & queue,EtsInt length)33 static void OnPromiseCompletion(EtsCoroutine *coro, EtsHandle<EtsPromise> &promise, EtsHandle<EtsObjectArray> &queue,
34 EtsInt length)
35 {
36 // Since handle cannot be created for nullptr use 'promise' object as a special marker of empty value
37 VMMutableHandle<ObjectHeader> exception(coro, promise.GetPtr());
38 for (EtsInt i = 0; i < length; ++i) {
39 EtsObject *callback = queue->Get(i);
40 if (callback != nullptr) {
41 queue->Set(i, nullptr);
42 LambdaUtils::InvokeVoid(coro, callback);
43 if (coro->HasPendingException() && exception.GetPtr() == promise.GetPtr()) {
44 exception.Update(coro->GetException());
45 coro->ClearException();
46 }
47 }
48 }
49 promise->ClearQueues(coro);
50 if (exception.GetPtr() != promise.GetPtr()) {
51 coro->SetException(exception.GetPtr());
52 }
53 coro->GetPandaVM()->FirePromiseStateChanged(promise);
54 }
55
EtsPromiseResolve(EtsPromise * promise,EtsObject * value)56 EtsVoid *EtsPromiseResolve(EtsPromise *promise, EtsObject *value)
57 {
58 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
59 if (promise == nullptr) {
60 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
61 ThrowNullPointerException(ctx, coro);
62 return EtsVoid::GetInstance();
63 }
64 if (promise->GetState() != EtsPromise::STATE_PENDING) {
65 return EtsVoid::GetInstance();
66 }
67 [[maybe_unused]] EtsHandleScope scope(coro);
68 EtsHandle<EtsPromise> hpromise(coro, promise);
69 EtsHandle<EtsObjectArray> thenQueue(coro, hpromise->GetThenQueue(coro));
70 hpromise->Resolve(coro, value);
71 OnPromiseCompletion(coro, hpromise, thenQueue, hpromise->GetThenQueueSize());
72 return EtsVoid::GetInstance();
73 }
74
EtsPromiseReject(EtsPromise * promise,EtsObject * error)75 EtsVoid *EtsPromiseReject(EtsPromise *promise, EtsObject *error)
76 {
77 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
78 if (promise == nullptr) {
79 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
80 ThrowNullPointerException(ctx, coro);
81 return EtsVoid::GetInstance();
82 }
83 if (promise->GetState() != EtsPromise::STATE_PENDING) {
84 return EtsVoid::GetInstance();
85 }
86 [[maybe_unused]] EtsHandleScope scope(coro);
87 EtsHandle<EtsPromise> hpromise(coro, promise);
88 EtsHandle<EtsObjectArray> catchQueue(coro, hpromise->GetCatchQueue(coro));
89 hpromise->Reject(coro, error);
90 OnPromiseCompletion(coro, hpromise, catchQueue, hpromise->GetCatchQueueSize());
91 return EtsVoid::GetInstance();
92 }
93
EtsPromiseAddToJobQueue(EtsObject * callback)94 EtsVoid *EtsPromiseAddToJobQueue(EtsObject *callback)
95 {
96 auto *jobQueue = EtsCoroutine::GetCurrent()->GetPandaVM()->GetJobQueue();
97 if (jobQueue != nullptr) {
98 jobQueue->AddJob(callback);
99 }
100 return EtsVoid::GetInstance();
101 }
102
EtsPromiseCreateLink(EtsObject * source,EtsPromise * target)103 void EtsPromiseCreateLink(EtsObject *source, EtsPromise *target)
104 {
105 EtsCoroutine *currentCoro = EtsCoroutine::GetCurrent();
106 auto *jobQueue = currentCoro->GetPandaVM()->GetJobQueue();
107 if (jobQueue != nullptr) {
108 jobQueue->CreateLink(source, target->AsObject());
109 }
110 }
111
EtsAwaitPromise(EtsPromise * promise)112 EtsObject *EtsAwaitPromise(EtsPromise *promise)
113 {
114 EtsCoroutine *currentCoro = EtsCoroutine::GetCurrent();
115 if (promise == nullptr) {
116 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
117 ThrowNullPointerException(ctx, currentCoro);
118 return nullptr;
119 }
120 [[maybe_unused]] EtsHandleScope scope(currentCoro);
121 EtsHandle<EtsPromise> promiseHandle(currentCoro, promise);
122
123 /* CASE 1. This is a converted JS promise */
124
125 if (promiseHandle->IsProxy()) {
126 /**
127 * This is a backed by JS equivalent promise.
128 * STS mode: error, no one can create such a promise!
129 * JS mode:
130 * - add a callback to JQ, that will:
131 * - resolve the promise with some value OR reject it
132 * - unblock the coro via event
133 * - schedule();
134 * - create a blocker event and link it to the promise
135 * - block current coroutine on the event
136 * - Schedule();
137 * (the last two steps are actually the cm->await()'s job)
138 * - return promise.value() if resolved or throw() it if rejected
139 */
140 EtsPromiseCreateLink(promiseHandle->GetLinkedPromise(currentCoro), promiseHandle.GetPtr());
141
142 PandaUniquePtr<CoroutineEvent> e = MakePandaUnique<GenericEvent>();
143 e->Lock();
144 promiseHandle->SetEventPtr(e.get());
145 currentCoro->GetCoroutineManager()->Await(e.get());
146 // will get here after the JS callback is called
147 if (promiseHandle->IsResolved()) {
148 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been resolved.";
149 return promiseHandle->GetValue(currentCoro);
150 }
151 // rejected
152 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been rejected.";
153 auto *exc = promiseHandle->GetValue(currentCoro);
154 currentCoro->SetException(exc->GetCoreType());
155 return nullptr;
156 }
157
158 /* CASE 2. This is a native STS promise */
159 promiseHandle->Lock();
160 if (!promiseHandle->IsPending()) {
161 // already settled!
162 promiseHandle->Unlock();
163
164 /**
165 * The promise is already resolved of rejected. Further actions:
166 * STS mode:
167 * if resolved: return Promise.value
168 * if rejected: throw Promise.value
169 * JS mode: NOTE!
170 * - suspend coro, create resolved JS promise and put it to the Q, on callback resume the coro
171 * and possibly throw
172 * - JQ::put(current_coro, promise)
173 *
174 */
175 if (promiseHandle->IsResolved()) {
176 LOG(DEBUG, COROUTINES) << "Promise::await: promise is already resolved!";
177 return promiseHandle->GetValue(currentCoro);
178 }
179 LOG(DEBUG, COROUTINES) << "Promise::await: promise is already rejected!";
180 auto *exc = promiseHandle->GetValue(currentCoro);
181 currentCoro->SetException(exc->GetCoreType());
182 return nullptr;
183 }
184
185 // the promise is not resolved yet
186 CoroutineEvent *e = promiseHandle->GetEventPtr();
187 if (e != nullptr) {
188 /**
189 * The promise is linked to come coroutine return value.
190 * Further actions:
191 * STS mode:
192 * if resolved: return P.value
193 * if rejected: throw P.value
194 * JS mode: ??? NOTE
195 */
196 LOG(DEBUG, COROUTINES) << "Promise::await: starting await() for a pending promise...";
197 // NOTE(konstanting, #I67QXC): try to make the Promise/Event locking sequence easier for understanding
198 e->Lock();
199 promiseHandle->Unlock();
200 currentCoro->GetCoroutineManager()->Await(e); // will unlock the event
201
202 // will get here once the promise is resolved
203 if (promiseHandle->IsResolved()) {
204 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been resolved.";
205 return promiseHandle->GetValue(currentCoro);
206 }
207 // rejected
208 LOG(DEBUG, COROUTINES) << "Promise::await: await() finished, promise has been rejected.";
209 auto *exc = promiseHandle->GetValue(currentCoro);
210 currentCoro->SetException(exc->GetCoreType());
211 return nullptr;
212 }
213
214 LOG(DEBUG, COROUTINES) << "Promise::await: promise is not linked to an event (standalone)";
215 /**
216 * This promise is not linked to any coroutine return value (standalone promise).
217 * Further actions:
218 * STS mode:
219 * create Event, connect it to promise
220 * CM::Await(event) // who will resolve P and P.event?
221 * JS mode: ??? NOTE
222 */
223
224 return nullptr;
225 }
226 } // namespace panda::ets::intrinsics
227