• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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