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 "plugins/ets/runtime/napi/ets_napi_helpers.h"
17
18 #include "libpandafile/shorty_iterator.h"
19 #include "macros.h"
20 #include "plugins/ets/runtime/ets_coroutine.h"
21 #include "plugins/ets/runtime/mem/ets_reference.h"
22 #include "plugins/ets/runtime/napi/ets_napi.h"
23 #include "plugins/ets/runtime/types/ets_method.h"
24 #include "plugins/ets/runtime/types/ets_object.h"
25 #include "plugins/ets/runtime/types/ets_promise.h"
26 #include "plugins/ets/runtime/ets_handle_scope.h"
27 #include "plugins/ets/runtime/ets_handle.h"
28 #include "plugins/ets/runtime/ets_exceptions.h"
29 #include "plugins/ets/runtime/ets_class_linker_extension.h"
30 #include "runtime/arch/helpers.h"
31 #include "runtime/include/managed_thread.h"
32 #include "runtime/include/method.h"
33 #include "runtime/include/runtime.h"
34 #include "runtime/include/runtime_notification.h"
35
36 #include <cstdint>
37
38 namespace ark::ets::napi {
39 namespace {
40 using Type = panda_file::Type;
41 using TypeId = panda_file::Type::TypeId;
42
43 using ExtArchTraits = arch::ExtArchTraits<RUNTIME_ARCH>;
44 using ArgReader = arch::ArgReader<RUNTIME_ARCH>;
45 using ArgCounter = arch::ArgCounter<RUNTIME_ARCH>;
46
47 class ArgWriter : public arch::ArgWriter<RUNTIME_ARCH> {
48 public:
ArgWriter(Span<uint8_t> gprArgs,Span<uint8_t> fprArgs,uint8_t * stackArgs)49 ArgWriter(Span<uint8_t> gprArgs, Span<uint8_t> fprArgs, uint8_t *stackArgs)
50 : arch::ArgWriter<RUNTIME_ARCH>(gprArgs, fprArgs, stackArgs)
51 {
52 }
53 ~ArgWriter() = default;
54
55 template <class T>
Write(T v)56 ALWAYS_INLINE typename std::enable_if_t<std::is_same<T, ObjectHeader **>::value, void> Write(T v)
57 {
58 arch::ArgWriter<RUNTIME_ARCH>::Write<EtsReference *>(
59 EtsReferenceStorage::NewEtsStackRef(reinterpret_cast<EtsObject **>(v)));
60 }
61
62 template <class T>
Write(T v)63 ALWAYS_INLINE typename std::enable_if_t<!std::is_same<T, ObjectHeader **>::value, void> Write(T v)
64 {
65 // Check T is not some kind of pointer to ObjectHeader
66 static_assert(!std::is_same_v<ObjectHeader, std::remove_cv_t<typename helpers::RemoveAllPointers<T>::type>>);
67 arch::ArgWriter<RUNTIME_ARCH>::Write(v);
68 }
69
70 NO_COPY_SEMANTIC(ArgWriter);
71 NO_MOVE_SEMANTIC(ArgWriter);
72 };
73 } // namespace
74
75 extern "C" void EtsNapiEntryPoint();
76
GetEtsNapiEntryPoint()77 const void *GetEtsNapiEntryPoint()
78 {
79 return reinterpret_cast<const void *>(EtsNapiEntryPoint);
80 }
81
82 extern "C" void EtsNapiCriticalNativeEntryPoint();
83
GetEtsNapiCriticalEntryPoint()84 const void *GetEtsNapiCriticalEntryPoint()
85 {
86 return reinterpret_cast<const void *>(EtsNapiCriticalNativeEntryPoint);
87 }
88
EtsNapiCalcStackArgsSpaceSize(Method * method,bool isCritical)89 extern "C" uint32_t EtsNapiCalcStackArgsSpaceSize(Method *method, bool isCritical)
90 {
91 ASSERT(method != nullptr);
92
93 ArgCounter counter;
94 if (!isCritical) {
95 counter.Count<EtsEnv *>(); // EtsEnv arg
96 counter.Count<ObjectHeader *>(); // class or this arg
97 }
98
99 panda_file::ShortyIterator it(method->GetShorty());
100 ++it; // Skip the return type
101 panda_file::ShortyIterator end;
102 while (it != end) {
103 Type type = *it++;
104 switch (type.GetId()) {
105 case TypeId::U1:
106 counter.Count<bool>();
107 break;
108 case TypeId::I8:
109 case TypeId::U8:
110 counter.Count<uint8_t>();
111 break;
112 case TypeId::I16:
113 case TypeId::U16:
114 counter.Count<uint16_t>();
115 break;
116 case TypeId::I32:
117 case TypeId::U32:
118 counter.Count<uint32_t>();
119 break;
120 case TypeId::F32:
121 counter.Count<float>();
122 break;
123 case TypeId::F64:
124 counter.Count<double>();
125 break;
126 case TypeId::I64:
127 case TypeId::U64:
128 counter.Count<uint64_t>();
129 break;
130 case TypeId::REFERENCE:
131 counter.Count<ObjectHeader *>();
132 break;
133 default:
134 UNREACHABLE();
135 break;
136 }
137 }
138
139 return counter.GetStackSpaceSize();
140 }
141
142 // Disable warning because the function uses ARCH_COPY_METHOD_ARGS macro.
143 // The macro uses computed goto
144 #if defined(__clang__)
145 #pragma clang diagnostic push
146 #pragma clang diagnostic ignored "-Wgnu-label-as-value"
147 #elif defined(__GNUC__)
148 #pragma GCC diagnostic push
149 #pragma GCC diagnostic ignored "-Wpedantic"
150 #endif
151
152 // input stack structure output stack structure
153 // +-------+ <- out_args +-------+
154 // | ... | | x0-x7 |
155 // +-------+ <- in_reg_args +-------+
156 // | x0-x7 | | d0-d7 |
157 // +-------+ +-------+
158 // | d0-d7 | | stack |
159 // +-------+ | arg 0 |
160 // | ... | +-------+
161 // +-------+ <- in_stack_args | ... |
162 // | stack | +-------+
163 // | arg 0 | | stack |
164 // +-------+ | arg N |
165 // | ... | +-------+
EtsNapiBeginCritical(Method * method,uint8_t * inRegsArgs,uint8_t * inStackArgs,uint8_t * outArgs,ManagedThread * thread)166 extern "C" void EtsNapiBeginCritical(Method *method, uint8_t *inRegsArgs, uint8_t *inStackArgs, uint8_t *outArgs,
167 ManagedThread *thread)
168 {
169 ASSERT(method->IsStatic());
170 ASSERT(thread == ManagedThread::GetCurrent());
171
172 Span<uint8_t> inGprArgs(inRegsArgs, ExtArchTraits::GP_ARG_NUM_BYTES);
173 Span<uint8_t> inFprArgs(inGprArgs.end(), ExtArchTraits::FP_ARG_NUM_BYTES);
174 ArgReader argReader(inGprArgs, inFprArgs, inStackArgs);
175
176 Span<uint8_t> outGprArgs(outArgs, ExtArchTraits::GP_ARG_NUM_BYTES);
177 Span<uint8_t> outFprArgs(outGprArgs.end(), ExtArchTraits::FP_ARG_NUM_BYTES);
178 auto outStackArgs = outFprArgs.end();
179 ArgWriter argWriter(outGprArgs, outFprArgs, outStackArgs);
180
181 argReader.Read<Method *>(); // Skip method
182
183 if (UNLIKELY(method->GetNativePointer() == nullptr)) {
184 auto coroutine = EtsCoroutine::CastFromThread(thread);
185 coroutine->GetPandaVM()->ResolveNativeMethod(method);
186 }
187
188 ARCH_COPY_METHOD_ARGS(method, argReader, argWriter);
189
190 Runtime::GetCurrent()->GetNotificationManager()->MethodEntryEvent(thread, method);
191 }
192
193 // Input stack =======> Output stack
194 // 0xFFFF
195 // | | | |
196 // | Prev frame | | Prev frame |
197 // | ... | | ... |
198 // +------------------------+ +------------------------+
199 // | ... | | ... |
200 // | stack args | | stack args | <--------+
201 // | ... | | ... | |
202 // +---+---+----------------+ <- inStackArgs --> +----------------+---+---+ |
203 // | | | LR | | LR | | | |
204 // | | | FP | | FP | | | |
205 // | | | Method * | | Method * | | | |
206 // | | c | FLAGS | | FLAGS | c | | |
207 // | | f +----------------+ +----------------+ f | | |
208 // | | r | ... | | ... | r | | |
209 // | | a | locals | | locals | a | | |
210 // | | m | ... | | ... | m | | |
211 // | | e +----------------+ +----------------+ e | | |
212 // | N | | ... | | ... | | N | |
213 // | A | | callee saved | | callee saved | | A | |
214 // | P | | ... | | ... | | P | |
215 // | I +---+----------------+ +----------------+---+ I | |
216 // | | ... | | ... | | |
217 // | | float args | | float args | | |
218 // | f | ... | | ... | f | |
219 // | r +--------------------+ +--------------------+ r | |
220 // | a | ... | | ... | a | |
221 // | m | general args | | general args | m | <----+ |
222 // | e | ... | | ... | e | | |
223 // | | arg0|Method* | |arg0|class/null(opt)| | | |
224 // | +--------------------+ <-- inRegsArgs --> +--------------------+ | | | References
225 // | | | | ... | | | | to ObjectHeader *s
226 // | | | | NAPI float args | | | | on the stack
227 // | | | | (on regs) | | | |
228 // | | | | ... | | | |
229 // | | | +--------------------+ | | |
230 // | | | | ... | | | |
231 // | | space for | | NAPI general args | | -----+ |
232 // | | NAPI args | | (on regs) | | |
233 // | | | | ... | | |
234 // | | | +--------------------+ | |
235 // | | | | ... | | |
236 // | | | | NAPI args | | ---------+
237 // | | | | (on stack) | |
238 // | | | | ... | |
239 // +---+--------------------+ <- outStackArgs -> +--------------------+---+
240 // | | | |
241 // 0x0000
PrepareArgsOnStack(Method * method,uint8_t * inRegsArgs,uint8_t * inStackArgs,uint8_t * outStackArgs,PandaEnv * pandaEnv)242 static uint8_t *PrepareArgsOnStack(Method *method, uint8_t *inRegsArgs, uint8_t *inStackArgs, uint8_t *outStackArgs,
243 PandaEnv *pandaEnv)
244 {
245 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
246 auto outRegsArgs = inRegsArgs - ExtArchTraits ::FP_ARG_NUM_BYTES - ExtArchTraits ::GP_ARG_NUM_BYTES;
247
248 ASSERT(outStackArgs <= outRegsArgs);
249 ASSERT(outRegsArgs < inRegsArgs);
250 ASSERT(inRegsArgs < inStackArgs);
251
252 Span<uint8_t> inGprArgs(inRegsArgs, ExtArchTraits::GP_ARG_NUM_BYTES);
253 Span<uint8_t> inFprArgs(inGprArgs.end(), ExtArchTraits::FP_ARG_NUM_BYTES);
254 ArgReader argReader(inGprArgs, inFprArgs, inStackArgs);
255
256 Span<uint8_t> outGprArgs(outRegsArgs, ExtArchTraits::GP_ARG_NUM_BYTES);
257 Span<uint8_t> outFprArgs(outGprArgs.end(), ExtArchTraits::FP_ARG_NUM_BYTES);
258 ArgWriter argWriter(outGprArgs, outFprArgs, outStackArgs);
259
260 argReader.Read<Method *>(); // Skip method
261
262 EtsMethod *etsMethod = EtsMethod::FromRuntimeMethod(method);
263 EtsReference *classOrThisRef = nullptr;
264 if (method->IsStatic()) {
265 if (etsMethod->IsFunction()) {
266 // NOTE:
267 // Replace the method pointer (Method *) with a pointer to the nullptr
268 // to avoid GC crash during traversal of method arguments.
269 auto classPtr = reinterpret_cast<EtsObject **>(inRegsArgs);
270 *classPtr = nullptr;
271 } else {
272 // Handle class object
273 auto classObj = EtsClass::FromRuntimeClass(method->GetClass())->AsObject();
274 ASSERT(classObj != nullptr);
275
276 // Replace the method pointer (Method *) with a pointer to the class object
277 auto classPtr = reinterpret_cast<EtsObject **>(inRegsArgs);
278 *classPtr = classObj;
279
280 classOrThisRef = EtsReferenceStorage::NewEtsStackRef(classPtr);
281 }
282 } else {
283 ASSERT(method->GetNumArgs() != 0);
284 ASSERT(!method->GetArgType(0).IsPrimitive());
285 ASSERT(!etsMethod->IsFunction());
286
287 // Handle this arg
288 auto thisPtr = const_cast<EtsObject **>(argReader.ReadPtr<EtsObject *>());
289 classOrThisRef = EtsReferenceStorage::NewEtsStackRef(thisPtr);
290 }
291
292 if (UNLIKELY(method->GetNativePointer() == nullptr)) {
293 // NOTE: Mark method that it has deprecated native API. Delete this code when #22435 is resolved.
294 method->SetAccessFlags(method->GetAccessFlags() | ACC_DEPRECATED_NATIVE_API);
295 }
296
297 // Prepare eTS NAPI args
298 if (UNLIKELY(etsMethod->IsDeprecatedNativeAPI())) {
299 argWriter.Write(static_cast<EtsEnv *>(pandaEnv));
300 } else {
301 argWriter.Write(static_cast<ani_env *>(pandaEnv));
302 }
303 if (!etsMethod->IsFunction()) {
304 argWriter.Write(classOrThisRef);
305 }
306 ARCH_COPY_METHOD_ARGS(method, argReader, argWriter);
307 // Completed the preparation of eTS NAPI arguments on the stack
308
309 return outRegsArgs;
310 }
311
EtsNapiBegin(Method * method,uint8_t * inRegsArgs,uint8_t * inStackArgs,uint8_t * outStackArgs,ManagedThread * thread)312 extern "C" uint8_t *EtsNapiBegin(Method *method, uint8_t *inRegsArgs, uint8_t *inStackArgs, uint8_t *outStackArgs,
313 ManagedThread *thread)
314 {
315 ASSERT(!method->IsSynchronized());
316 ASSERT(thread == ManagedThread::GetCurrent());
317
318 EtsCoroutine *coroutine = EtsCoroutine::CastFromThread(thread);
319 PandaEnv *pandaEnv = coroutine->GetEtsNapiEnv();
320
321 uint8_t *outRegsArgs = PrepareArgsOnStack(method, inRegsArgs, inStackArgs, outStackArgs, pandaEnv);
322
323 // ATTENTION!!!
324 // Don't move the following code above, because only from this point on,
325 // the stack of the current frame is correct, so we can safely walk it
326 // (e.g. stop at a safepoint and walk the stack args of NAPI frame)
327
328 if (UNLIKELY(method->GetNativePointer() == nullptr)) {
329 // NOTE: Delete this code and add throw an error when #22435 is resolved.
330 coroutine->GetPandaVM()->ResolveNativeMethod(method);
331 }
332
333 Runtime::GetCurrent()->GetNotificationManager()->MethodEntryEvent(thread, method);
334
335 constexpr uint32_t MAX_LOCAL_REF = 4096;
336 if (UNLIKELY(!pandaEnv->GetEtsReferenceStorage()->PushLocalEtsFrame(MAX_LOCAL_REF))) {
337 LOG(FATAL, RUNTIME) << "eTS NAPI push local frame failed";
338 }
339
340 EtsMethod *etsMethod = EtsMethod::FromRuntimeMethod(method);
341 if (!etsMethod->IsFastNative()) {
342 thread->NativeCodeBegin();
343 }
344
345 return outRegsArgs;
346 }
347
348 #if defined(__clang__)
349 #pragma clang diagnostic pop
350 #elif defined(__GNUC__)
351 #pragma GCC diagnostic pop
352 #endif
353
EtsNapiEnd(Method * method,ManagedThread * thread,bool isFastNative)354 extern "C" void EtsNapiEnd(Method *method, ManagedThread *thread, bool isFastNative)
355 {
356 ASSERT(method != nullptr);
357 ASSERT(!method->IsSynchronized());
358 ASSERT(thread == ManagedThread::GetCurrent());
359
360 if (!isFastNative) {
361 thread->NativeCodeEnd();
362 }
363
364 Runtime::GetCurrent()->GetNotificationManager()->MethodExitEvent(thread, method);
365
366 auto coroutine = EtsCoroutine::CastFromThread(thread);
367 auto storage = coroutine->GetEtsNapiEnv()->GetEtsReferenceStorage();
368 storage->PopLocalEtsFrame(nullptr);
369 }
370
EtsNapiObjEnd(Method * method,EtsReference * etsRef,ManagedThread * thread,bool isFastNative)371 extern "C" EtsObject *EtsNapiObjEnd(Method *method, EtsReference *etsRef, ManagedThread *thread, bool isFastNative)
372 {
373 ASSERT(method != nullptr);
374 ASSERT(!method->IsSynchronized());
375 ASSERT(thread == ManagedThread::GetCurrent());
376
377 // End native scope first to get into managed scope for object manipulation
378 if (!isFastNative) {
379 thread->NativeCodeEnd();
380 }
381
382 Runtime::GetCurrent()->GetNotificationManager()->MethodExitEvent(thread, method);
383
384 EtsObject *ret = nullptr;
385
386 auto coroutine = EtsCoroutine::CastFromThread(thread);
387 auto storage = coroutine->GetEtsNapiEnv()->GetEtsReferenceStorage();
388 if (etsRef != nullptr) {
389 ret = storage->GetEtsObject(etsRef);
390 }
391
392 storage->PopLocalEtsFrame(nullptr);
393
394 return ret;
395 }
396
IsEtsMethodFastNative(Method * method)397 extern "C" bool IsEtsMethodFastNative(Method *method)
398 {
399 return EtsMethod::FromRuntimeMethod(method)->IsFastNative();
400 }
401
402 // Disable warning because the function uses ARCH_COPY_METHOD_ARGS macro.
403 // The macro uses computed goto
404 #if defined(__clang__)
405 #pragma clang diagnostic push
406 #pragma clang diagnostic ignored "-Wgnu-label-as-value"
407 #elif defined(__GNUC__)
408 #pragma GCC diagnostic push
409 #pragma GCC diagnostic ignored "-Wpedantic"
410 #endif
EtsAsyncCall(Method * method,EtsCoroutine * currentCoro,uint8_t * regArgs,uint8_t * stackArgs)411 extern "C" ObjectPointerType EtsAsyncCall(Method *method, EtsCoroutine *currentCoro, uint8_t *regArgs,
412 uint8_t *stackArgs)
413 {
414 PandaEtsVM *vm = currentCoro->GetPandaVM();
415 auto *coroManager = currentCoro->GetCoroutineManager();
416 EtsClassLinker *etsClassLinker = vm->GetClassLinker();
417 Method *impl = etsClassLinker->GetAsyncImplMethod(method, currentCoro);
418 if (impl == nullptr) {
419 ASSERT(currentCoro->HasPendingException());
420 // Exception is thrown by GetAsyncImplMethod
421 return 0;
422 }
423 ASSERT(!currentCoro->HasPendingException());
424 if (coroManager->IsCoroutineSwitchDisabled()) {
425 ThrowEtsException(currentCoro, panda_file_items::class_descriptors::INVALID_COROUTINE_OPERATION_ERROR,
426 "Cannot call async in the current context!");
427 return 0;
428 }
429
430 PandaVector<Value> args;
431 args.reserve(method->GetNumArgs());
432 Span<uint8_t> gprArgs(regArgs, ExtArchTraits::GP_ARG_NUM_BYTES);
433 Span<uint8_t> fprArgs(gprArgs.end(), ExtArchTraits::FP_ARG_NUM_BYTES);
434 ArgReader argReader(gprArgs, fprArgs, stackArgs);
435 argReader.Read<Method *>(); // Skip method
436 if (method->IsStatic()) {
437 // Replace the method pointer (Method *) by the pointer to the object class
438 // to satisfy stack walker
439 auto classObj = EtsClass::FromRuntimeClass(method->GetClass())->AsObject();
440 ASSERT(classObj != nullptr);
441 auto classPtr = reinterpret_cast<EtsObject **>(regArgs);
442 *classPtr = classObj;
443 } else {
444 // Handle this arg
445 ASSERT(method->GetNumArgs() != 0);
446 ASSERT(!method->GetArgType(0).IsPrimitive());
447 args.push_back(Value(*const_cast<ObjectHeader **>(argReader.ReadPtr<ObjectHeader *>())));
448 }
449
450 arch::ValueWriter writer(&args);
451 ARCH_COPY_METHOD_ARGS(method, argReader, writer);
452
453 // Create object after arg fix ^^^.
454 // Arg fix is needed for StackWalker. So if GC gets triggered in EtsPromise::Create
455 // it StackWalker correctly finds all vregs.
456 EtsPromise *promise = EtsPromise::Create(currentCoro);
457 if (UNLIKELY(promise == nullptr)) {
458 ThrowOutOfMemoryError(currentCoro, "Cannot allocate Promise");
459 return 0;
460 }
461 auto promiseRef = vm->GetGlobalObjectStorage()->Add(promise, mem::Reference::ObjectType::GLOBAL);
462 auto evt = Runtime::GetCurrent()->GetInternalAllocator()->New<CompletionEvent>(promiseRef, coroManager);
463
464 [[maybe_unused]] EtsHandleScope scope(currentCoro);
465 EtsHandle<EtsPromise> promiseHandle(currentCoro, promise);
466 bool launchResult = coroManager->LaunchImmediately(evt, impl, std::move(args), CoroutineLaunchMode::SAME_WORKER,
467 EtsCoroutine::ASYNC_CALL, false);
468 if (UNLIKELY(!launchResult)) {
469 ASSERT(currentCoro->HasPendingException());
470 // OOM is thrown by Launch
471 Runtime::GetCurrent()->GetInternalAllocator()->Delete(evt);
472 return 0;
473 }
474 return ToObjPtr(promiseHandle.GetPtr());
475 }
476 #if defined(__clang__)
477 #pragma clang diagnostic pop
478 #elif defined(__GNUC__)
479 #pragma GCC diagnostic pop
480 #endif
481 } // namespace ark::ets::napi
482