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