1 /*
2 * Copyright (c) 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 "plugins/ets/runtime/ets_coroutine.h"
17 #include "plugins/ets/runtime/ets_exceptions.h"
18 #include "runtime/include/runtime.h"
19 #include "source_lang_enum.h"
20 #include "types/ets_array.h"
21 #include "types/ets_class.h"
22 #include "types/ets_field.h"
23 #include "types/ets_method.h"
24 #include "types/ets_type.h"
25 #include "types/ets_job.h"
26 #include "types/ets_promise.h"
27 #include "types/ets_typeapi_create.h"
28 #include "types/ets_type_comptime_traits.h"
29 #include "mem/vm_handle.h"
30
31 #include <type_traits>
32
33 namespace ark::ets::intrinsics {
ResolveInvokeMethod(EtsCoroutine * coro,VMHandle<EtsObject> func)34 static EtsMethod *ResolveInvokeMethod(EtsCoroutine *coro, VMHandle<EtsObject> func)
35 {
36 if (func.GetPtr() == nullptr) {
37 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
38 ThrowNullPointerException(ctx, coro);
39 return nullptr;
40 }
41
42 EtsMethod *method = func->GetClass()->GetInstanceMethod(INVOKE_METHOD_NAME, nullptr);
43 ASSERT(method != nullptr);
44
45 if (method->IsAbstract()) {
46 method = func->GetClass()->ResolveVirtualMethod(method);
47 }
48
49 if (UNLIKELY(!method->GetPandaMethod()->Verify())) {
50 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
51 ThrowVerificationException(ctx, method->GetPandaMethod()->GetFullName());
52 return nullptr;
53 }
54
55 return method;
56 }
57
CreateArgsVector(VMHandle<EtsObject> func,EtsMethod * method,VMHandle<EtsArray> arr)58 static PandaVector<Value> CreateArgsVector(VMHandle<EtsObject> func, EtsMethod *method, VMHandle<EtsArray> arr)
59 {
60 size_t methArgsCount = method->GetNumArgs();
61 PandaVector<Value> realArgs {methArgsCount};
62 size_t firstRealArg = 0;
63
64 if (!method->IsStatic()) {
65 realArgs[0] = Value(func->GetCoreType());
66 firstRealArg = 1;
67 }
68
69 size_t numArgs = arr->GetLength();
70 if (methArgsCount - firstRealArg != numArgs) {
71 UNREACHABLE();
72 }
73
74 for (size_t i = 0; i < numArgs; i++) {
75 auto arg = arr->GetCoreType()->Get<ObjectHeader *>(i);
76 auto argType = method->GetArgType(firstRealArg + i);
77 if (argType == EtsType::OBJECT) {
78 realArgs[firstRealArg + i] = Value(arg);
79 continue;
80 }
81 EtsPrimitiveTypeEnumToComptimeConstant(argType, [&](auto type) -> void {
82 using T = EtsTypeEnumToCppType<decltype(type)::value>;
83 realArgs[firstRealArg + i] =
84 Value(EtsBoxPrimitive<T>::FromCoreType(EtsObject::FromCoreType(arg))->GetValue());
85 });
86 }
87
88 return realArgs;
89 }
90
91 template <typename CoroResult>
92 // CC-OFFNXT(huge_method) solid logic
Launch(EtsObject * func,EtsArray * arr,bool abortFlag,bool postToMain=false)93 ObjectHeader *Launch(EtsObject *func, EtsArray *arr, bool abortFlag, bool postToMain = false)
94 {
95 static_assert(std::is_same<CoroResult, EtsJob>::value || std::is_same<CoroResult, EtsPromise>::value);
96
97 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
98 ASSERT(coro != nullptr);
99 if (func == nullptr) {
100 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
101 ThrowNullPointerException(ctx, coro);
102 return nullptr;
103 }
104 if (coro->GetCoroutineManager()->IsCoroutineSwitchDisabled()) {
105 ThrowEtsException(coro, panda_file_items::class_descriptors::INVALID_COROUTINE_OPERATION_ERROR,
106 "Cannot launch coroutines in the current context!");
107 return nullptr;
108 }
109 if (arr == nullptr) {
110 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
111 ThrowNullPointerException(ctx, coro);
112 return nullptr;
113 }
114 [[maybe_unused]] EtsHandleScope scope(coro);
115 VMHandle<EtsArray> array(coro, arr->GetCoreType());
116 VMHandle<EtsObject> function(coro, func->GetCoreType());
117 EtsMethod *method = ResolveInvokeMethod(coro, function);
118 if (method == nullptr) {
119 return nullptr;
120 }
121
122 // create the coro and put it to the ready queue
123 CoroResult *coroResult = CoroResult::Create(coro);
124 if (UNLIKELY(coroResult == nullptr)) {
125 return nullptr;
126 }
127 EtsHandle<CoroResult> coroResultHandle(coro, coroResult);
128
129 PandaEtsVM *etsVm = coro->GetPandaVM();
130 auto *coroManager = coro->GetCoroutineManager();
131 auto ref = etsVm->GetGlobalObjectStorage()->Add(coroResultHandle.GetPtr(), mem::Reference::ObjectType::GLOBAL);
132 auto evt = Runtime::GetCurrent()->GetInternalAllocator()->New<CompletionEvent>(ref, coroManager);
133
134 // since transferring arguments from frame registers (which are local roots for GC) to a C++ vector
135 // introduces the potential risk of pointer invalidation in case GC moves the referenced objects,
136 // we would like to do this transfer below all potential GC invocation points
137 PandaVector<Value> realArgs = CreateArgsVector(function, method, array);
138 auto mode = postToMain ? CoroutineLaunchMode::MAIN_WORKER : CoroutineLaunchMode::DEFAULT;
139 bool launchResult = coro->GetCoroutineManager()->Launch(evt, method->GetPandaMethod(), std::move(realArgs), mode,
140 EtsCoroutine::LAUNCH, abortFlag);
141 if (UNLIKELY(!launchResult)) {
142 // Launch failed. The exception in the current coro should be already set by Launch(),
143 // just return null as the result and clean up the allocated resources.
144 ASSERT(coro->HasPendingException());
145 Runtime::GetCurrent()->GetInternalAllocator()->Delete(evt);
146 return nullptr;
147 }
148
149 return coroResultHandle.GetPtr();
150 }
151
152 extern "C" {
PostToMainThread(EtsObject * func,EtsArray * arr,EtsBoolean abortFlag)153 EtsJob *PostToMainThread(EtsObject *func, EtsArray *arr, EtsBoolean abortFlag)
154 {
155 return static_cast<EtsJob *>(Launch<EtsJob>(func, arr, abortFlag != 0U, true));
156 }
157 }
158
159 extern "C" {
EtsLaunchInternalJobNative(EtsObject * func,EtsArray * arr,EtsBoolean abortFlag)160 EtsJob *EtsLaunchInternalJobNative(EtsObject *func, EtsArray *arr, EtsBoolean abortFlag)
161 {
162 return static_cast<EtsJob *>(Launch<EtsJob>(func, arr, abortFlag != 0U));
163 }
164
EtsLaunchSameWorker(EtsObject * callback)165 void EtsLaunchSameWorker(EtsObject *callback)
166 {
167 auto *coro = EtsCoroutine::GetCurrent();
168 EtsHandleScope scope(coro);
169 VMHandle<EtsObject> hCallback(coro, callback->GetCoreType());
170 ASSERT(hCallback.GetPtr() != nullptr);
171 PandaVector<Value> args = {Value(hCallback->GetCoreType())};
172 auto *method = ResolveInvokeMethod(coro, hCallback);
173 auto *coroMan = coro->GetCoroutineManager();
174 auto evt = Runtime::GetCurrent()->GetInternalAllocator()->New<CompletionEvent>(nullptr, coroMan);
175 [[maybe_unused]] auto launched =
176 coroMan->Launch(evt, method->GetPandaMethod(), std::move(args), CoroutineLaunchMode::SAME_WORKER,
177 EtsCoroutine::TIMER_CALLBACK, true);
178 ASSERT(launched);
179 }
180 }
181 } // namespace ark::ets::intrinsics
182