1 /**
2 * Copyright (c) 2024-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 "libpandabase/os/mutex.h"
18 #include "runtime/include/exceptions.h"
19 #include "runtime/include/thread_scopes.h"
20 #include "runtime/mem/refstorage/reference.h"
21 #include "plugins/ets/runtime/ets_coroutine.h"
22 #include "plugins/ets/runtime/ets_utils.h"
23
24 #include <cstdint>
25 #include <thread>
26
27 namespace ark::ets::intrinsics {
28
RunExclusiveTask(mem::Reference * taskRef,mem::GlobalObjectStorage * refStorage)29 static void RunExclusiveTask(mem::Reference *taskRef, mem::GlobalObjectStorage *refStorage)
30 {
31 ScopedManagedCodeThread managedCode(EtsCoroutine::GetCurrent());
32 auto *taskObj = EtsObject::FromCoreType(refStorage->Get(taskRef));
33 refStorage->Remove(taskRef);
34 LambdaUtils::InvokeVoid(EtsCoroutine::GetCurrent(), taskObj);
35 }
36
TryCreateEACoroutine(PandaEtsVM * etsVM,bool needInterop,bool & limitIsReached,bool & jsEnvEmpty,os::memory::Event & event)37 Coroutine *TryCreateEACoroutine(PandaEtsVM *etsVM, bool needInterop, bool &limitIsReached, bool &jsEnvEmpty,
38 os::memory::Event &event)
39 {
40 auto *runtime = Runtime::GetCurrent();
41 auto *coroMan = etsVM->GetCoroutineManager();
42 auto *ifaceTable = EtsCoroutine::CastFromThread(coroMan->GetMainThread())->GetExternalIfaceTable();
43
44 auto *exclusiveCoro = coroMan->CreateExclusiveWorkerForThread(runtime, etsVM);
45 // exclusiveCoro == nullptr means that we reached the limit of eaworkers count or memory resources
46 if (exclusiveCoro == nullptr) {
47 limitIsReached = true;
48 event.Fire();
49 return nullptr;
50 }
51
52 // early return to avoid waste time on creating jsEnv
53 if (!needInterop) {
54 event.Fire();
55 return exclusiveCoro;
56 }
57
58 auto *jsEnv = ifaceTable->CreateJSRuntime();
59 // current we cannot create JSVM instance without jsEnv
60 // so we cannot create eaworker support interop withoutJSEnv
61 if (jsEnv == nullptr) {
62 jsEnvEmpty = true;
63 event.Fire();
64 return nullptr;
65 }
66
67 ifaceTable->CreateInteropCtx(exclusiveCoro, jsEnv);
68 event.Fire();
69 return exclusiveCoro;
70 }
71
RunTaskOnEACoroutine(PandaEtsVM * etsVM,bool needInterop,mem::Reference * taskRef)72 void RunTaskOnEACoroutine(PandaEtsVM *etsVM, bool needInterop, mem::Reference *taskRef)
73 {
74 auto *refStorage = etsVM->GetGlobalObjectStorage();
75 auto *coroMan = etsVM->GetCoroutineManager();
76
77 if (needInterop) {
78 auto poster = etsVM->CreateCallbackPoster();
79 ASSERT(poster != nullptr);
80 poster->Post(RunExclusiveTask, taskRef, refStorage);
81 // 2 NativeEngine async_t and 1 async_t for each of the two instances of CallbackPoster
82 // CC-OFFNXT(G.NAM.03-CPP) project code style
83 static constexpr uint32_t MANUALLY_HANDLED_ASYNC_COUNT = 4U;
84 WalkEventLoopCallback cntHandles = []([[maybe_unused]] void *handle, void *arg) {
85 auto *cnt = reinterpret_cast<uint32_t *>(arg);
86 (*cnt)++;
87 };
88 // CC-OFFNXT(G.CTL.03) implementation feature
89 while (true) {
90 // NOTE(ksarychev, #25367): change to handle corner cases
91 etsVM->RunEventLoop(EventLoopRunMode::RUN_ONCE);
92 uint32_t handleCount = 0;
93 etsVM->WalkEventLoop(cntHandles, &handleCount);
94 if (handleCount <= MANUALLY_HANDLED_ASYNC_COUNT) {
95 break;
96 }
97 }
98 } else {
99 RunExclusiveTask(taskRef, refStorage);
100 }
101 coroMan->DestroyExclusiveWorker();
102 }
103
ExclusiveLaunch(EtsObject * task,uint8_t needInterop)104 void ExclusiveLaunch(EtsObject *task, uint8_t needInterop)
105 {
106 auto *coro = EtsCoroutine::GetCurrent();
107 ASSERT(coro != nullptr);
108 auto *etsVM = coro->GetPandaVM();
109 if (etsVM->GetCoroutineManager()->IsExclusiveWorkersLimitReached()) {
110 ThrowCoroutinesLimitExceedError("The limit of Exclusive Workers has been reached");
111 return;
112 }
113 auto *refStorage = etsVM->GetGlobalObjectStorage();
114 auto *taskRef = refStorage->Add(task->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
115 ASSERT(taskRef != nullptr);
116 auto limitIsReached = false;
117 auto jsEnvEmpty = false;
118 bool supportInterop = static_cast<bool>(needInterop);
119 auto *worker = coro->GetWorker();
120 auto *interopCtx = worker->GetLocalStorage().Get<CoroutineWorker::DataIdx::INTEROP_CTX_PTR, void *>();
121
122 if (supportInterop && interopCtx == nullptr) {
123 ThrowRuntimeException("Cannot create EAWorker support interop without JsEnv");
124 return;
125 }
126 {
127 ScopedNativeCodeThread nativeScope(coro);
128 auto event = os::memory::Event();
129 auto t = std::thread([&jsEnvEmpty, &limitIsReached, &event, etsVM, taskRef, supportInterop]() {
130 auto *eaCoro = TryCreateEACoroutine(etsVM, supportInterop, limitIsReached, jsEnvEmpty, event);
131 if (eaCoro == nullptr) {
132 return;
133 }
134 RunTaskOnEACoroutine(etsVM, supportInterop, taskRef);
135 });
136 event.Wait();
137 t.detach();
138 }
139 if (limitIsReached) {
140 ThrowCoroutinesLimitExceedError("The limit of Exclusive Workers has been reached");
141 return;
142 }
143 if (jsEnvEmpty) {
144 ThrowRuntimeException("Cannot create EAWorker support interop without JsEnv");
145 return;
146 }
147 }
148
TaskPosterCreate()149 int64_t TaskPosterCreate()
150 {
151 auto *coro = EtsCoroutine::GetCurrent();
152 ASSERT(coro != nullptr);
153 auto poster = coro->GetPandaVM()->CreateCallbackPoster();
154 ASSERT(poster != nullptr);
155 return reinterpret_cast<int64_t>(poster.release());
156 }
157
TaskPosterDestroy(int64_t poster)158 void TaskPosterDestroy(int64_t poster)
159 {
160 auto *taskPoster = reinterpret_cast<CallbackPoster *>(poster);
161 ASSERT(taskPoster != nullptr);
162 Runtime::GetCurrent()->GetInternalAllocator()->Delete(taskPoster);
163 }
164
TaskPosterPost(int64_t poster,EtsObject * task)165 void TaskPosterPost(int64_t poster, EtsObject *task)
166 {
167 auto *taskPoster = reinterpret_cast<CallbackPoster *>(poster);
168 ASSERT(taskPoster != nullptr);
169
170 auto *coro = EtsCoroutine::GetCurrent();
171 ASSERT(coro != nullptr);
172 auto *refStorage = coro->GetPandaVM()->GetGlobalObjectStorage();
173 auto *taskRef = refStorage->Add(task->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
174 taskPoster->Post(RunExclusiveTask, taskRef, refStorage);
175 }
176
177 } // namespace ark::ets::intrinsics
178