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