• 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 "macros.h"
17 #include "plugins/ets/runtime/types/ets_string.h"
18 #include "plugins/ets/runtime/types/ets_method.h"
19 #include "plugins/ets/runtime/interop_js/js_value_call.h"
20 #include "plugins/ets/runtime/interop_js/napi_env_scope.h"
21 #include "plugins/ets/runtime/interop_js/js_convert.h"
22 #include "plugins/ets/runtime/interop_js/interop_common.h"
23 #include "runtime/include/panda_vm.h"
24 #include "runtime/include/class_linker-inl.h"
25 #include "runtime/handle_scope-inl.h"
26 
27 #include "runtime/mem/vm_handle-inl.h"
28 
29 namespace panda::ets::interop::js {
30 
31 // Convert js->ets for refs, throws ETS/JS exceptions
32 template <typename FUnwrapVal, typename FClsResolv, typename FStoreRef>
ConvertNapiValRef(InteropCtx * ctx,FClsResolv & resolveRefCls,FStoreRef & storeRef,napi_value jsVal,FUnwrapVal & unwrapVal)33 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertNapiValRef(InteropCtx *ctx, FClsResolv &resolveRefCls,
34                                                                  FStoreRef &storeRef, napi_value jsVal,
35                                                                  FUnwrapVal &unwrapVal)
36 {
37     auto env = ctx->GetJSEnv();
38 
39     if (UNLIKELY(IsNull(env, jsVal))) {
40         storeRef(nullptr);
41         return true;
42     }
43 
44     auto klass = resolveRefCls();
45 
46     // start fastpath
47     if (klass == ctx->GetVoidClass()) {
48         return unwrapVal(helpers::TypeIdentity<JSConvertEtsVoid>());
49     }
50     if (klass == ctx->GetJSValueClass()) {
51         return unwrapVal(helpers::TypeIdentity<JSConvertJSValue>());
52     }
53     if (klass == ctx->GetStringClass()) {
54         return unwrapVal(helpers::TypeIdentity<JSConvertString>());
55     }
56     if (UNLIKELY(IsUndefined(env, jsVal))) {
57         if (!klass->IsAssignableFrom(ctx->GetUndefinedClass())) {
58             return false;
59         }
60         storeRef(ctx->GetUndefinedObject()->GetCoreType());
61         return true;
62     }
63     // start slowpath
64     auto refconv = JSRefConvertResolve<true>(ctx, klass);
65     if (UNLIKELY(refconv == nullptr)) {
66         return false;
67     }
68     ObjectHeader *res = refconv->Unwrap(ctx, jsVal)->GetCoreType();
69     storeRef(res);
70     return res != nullptr;
71 }
72 
73 // Convert js->ets, throws ETS/JS exceptions
74 template <typename FClsResolv, typename FStorePrim, typename FStoreRef>
ConvertNapiVal(InteropCtx * ctx,FClsResolv & resolveRefCls,FStorePrim & storePrim,FStoreRef & storeRef,panda_file::Type type,napi_value jsVal)75 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertNapiVal(InteropCtx *ctx, FClsResolv &resolveRefCls,
76                                                               FStorePrim &storePrim, FStoreRef &storeRef,
77                                                               panda_file::Type type, napi_value jsVal)
78 {
79     auto env = ctx->GetJSEnv();
80 
81     auto unwrapVal = [&ctx, &env, &jsVal, &storeRef, &storePrim](auto convTag) {
82         using Convertor = typename decltype(convTag)::type;  // convTag acts as lambda template parameter
83         using cpptype = typename Convertor::cpptype;         // NOLINT(readability-identifier-naming)
84         auto res = Convertor::Unwrap(ctx, env, jsVal);
85         if (UNLIKELY(!res.has_value())) {
86             return false;
87         }
88         if constexpr (std::is_pointer_v<cpptype>) {
89             storeRef(AsEtsObject(res.value())->GetCoreType());
90         } else {
91             storePrim(Value(res.value()).GetAsLong());
92         }
93         return true;
94     };
95 
96     switch (type.GetId()) {
97         case panda_file::Type::TypeId::VOID: {
98             // do nothing
99             return true;
100         }
101         case panda_file::Type::TypeId::U1:
102             return unwrapVal(helpers::TypeIdentity<JSConvertU1>());
103         case panda_file::Type::TypeId::I8:
104             return unwrapVal(helpers::TypeIdentity<JSConvertI8>());
105         case panda_file::Type::TypeId::U8:
106             return unwrapVal(helpers::TypeIdentity<JSConvertU8>());
107         case panda_file::Type::TypeId::I16:
108             return unwrapVal(helpers::TypeIdentity<JSConvertI16>());
109         case panda_file::Type::TypeId::U16:
110             return unwrapVal(helpers::TypeIdentity<JSConvertU16>());
111         case panda_file::Type::TypeId::I32:
112             return unwrapVal(helpers::TypeIdentity<JSConvertI32>());
113         case panda_file::Type::TypeId::U32:
114             return unwrapVal(helpers::TypeIdentity<JSConvertU32>());
115         case panda_file::Type::TypeId::I64:
116             return unwrapVal(helpers::TypeIdentity<JSConvertI64>());
117         case panda_file::Type::TypeId::U64:
118             return unwrapVal(helpers::TypeIdentity<JSConvertU64>());
119         case panda_file::Type::TypeId::F32:
120             return unwrapVal(helpers::TypeIdentity<JSConvertF32>());
121         case panda_file::Type::TypeId::F64:
122             return unwrapVal(helpers::TypeIdentity<JSConvertF64>());
123         case panda_file::Type::TypeId::REFERENCE:
124             return ConvertNapiValRef(ctx, resolveRefCls, storeRef, jsVal, unwrapVal);
125         default: {
126             ctx->Fatal(std::string("ConvertNapiVal: unsupported typeid ") +
127                        panda_file::Type::GetSignatureByTypeId(type));
128         }
129     }
130     UNREACHABLE();
131 }
132 
133 // Convert ets->js for refs, throws JS exceptions
134 template <typename FClsResolv, typename FStore, typename FRead>
ConvertEtsValRef(InteropCtx * ctx,FClsResolv & resolveRefCls,FStore & storeRes,FRead & readVal)135 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertEtsValRef(InteropCtx *ctx,
136                                                                 [[maybe_unused]] FClsResolv &resolveRefCls,
137                                                                 FStore &storeRes, FRead &readVal)
138 {
139     auto env = ctx->GetJSEnv();
140 
141     auto wrapRef = [&env, &storeRes](auto convTag, ObjectHeader *ref) -> bool {
142         using Convertor = typename decltype(convTag)::type;  // conv_tag acts as lambda template parameter
143         using cpptype = typename Convertor::cpptype;         // NOLINT(readability-identifier-naming)
144         cpptype value = std::remove_pointer_t<cpptype>::FromEtsObject(EtsObject::FromCoreType(ref));
145         napi_value res = Convertor::Wrap(env, value);
146         storeRes(res);
147         return res != nullptr;
148     };
149 
150     ObjectHeader *ref = readVal(helpers::TypeIdentity<ObjectHeader *>());
151     if (UNLIKELY(ref == nullptr)) {
152         storeRes(GetNull(env));
153         return true;
154     }
155     if (UNLIKELY(ref == ctx->GetUndefinedObject()->GetCoreType())) {
156         storeRes(GetUndefined(env));
157         return true;
158     }
159 
160     auto klass = ref->template ClassAddr<Class>();
161 
162     ASSERT(resolveRefCls()->IsAssignableFrom(klass));
163     // start fastpath
164     if (klass == ctx->GetVoidClass()) {
165         return wrapRef(helpers::TypeIdentity<JSConvertEtsVoid>(), ref);
166     }
167     if (klass == ctx->GetJSValueClass()) {
168         return wrapRef(helpers::TypeIdentity<JSConvertJSValue>(), ref);
169     }
170     if (klass == ctx->GetStringClass()) {
171         return wrapRef(helpers::TypeIdentity<JSConvertString>(), ref);
172     }
173     // start slowpath
174     auto refconv = JSRefConvertResolve(ctx, klass);
175     auto res = refconv->Wrap(ctx, EtsObject::FromCoreType(ref));
176     storeRes(res);
177     return res != nullptr;
178 }
179 
180 // Convert ets->js, throws JS exceptions
181 template <typename FClsResolv, typename FStore, typename FRead>
ConvertEtsVal(InteropCtx * ctx,FClsResolv & resolveRefCls,FStore & storeRes,panda_file::Type type,FRead & readVal)182 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertEtsVal(InteropCtx *ctx,
183                                                              [[maybe_unused]] FClsResolv &resolveRefCls,
184                                                              FStore &storeRes, panda_file::Type type, FRead &readVal)
185 {
186     auto env = ctx->GetJSEnv();
187 
188     auto wrapPrim = [&env, &readVal, &storeRes](auto convTag) -> bool {
189         using Convertor = typename decltype(convTag)::type;  // convTag acts as lambda template parameter
190         using cpptype = typename Convertor::cpptype;         // NOLINT(readability-identifier-naming)
191         napi_value res = Convertor::Wrap(env, readVal(helpers::TypeIdentity<cpptype>()));
192         storeRes(res);
193         return res != nullptr;
194     };
195 
196     switch (type.GetId()) {
197         case panda_file::Type::TypeId::VOID: {
198             storeRes(GetUndefined(env));
199             return true;
200         }
201         case panda_file::Type::TypeId::U1:
202             return wrapPrim(helpers::TypeIdentity<JSConvertU1>());
203         case panda_file::Type::TypeId::I8:
204             return wrapPrim(helpers::TypeIdentity<JSConvertI8>());
205         case panda_file::Type::TypeId::U8:
206             return wrapPrim(helpers::TypeIdentity<JSConvertU8>());
207         case panda_file::Type::TypeId::I16:
208             return wrapPrim(helpers::TypeIdentity<JSConvertI16>());
209         case panda_file::Type::TypeId::U16:
210             return wrapPrim(helpers::TypeIdentity<JSConvertU16>());
211         case panda_file::Type::TypeId::I32:
212             return wrapPrim(helpers::TypeIdentity<JSConvertI32>());
213         case panda_file::Type::TypeId::U32:
214             return wrapPrim(helpers::TypeIdentity<JSConvertU32>());
215         case panda_file::Type::TypeId::I64:
216             return wrapPrim(helpers::TypeIdentity<JSConvertI64>());
217         case panda_file::Type::TypeId::U64:
218             return wrapPrim(helpers::TypeIdentity<JSConvertU64>());
219         case panda_file::Type::TypeId::F32:
220             return wrapPrim(helpers::TypeIdentity<JSConvertF32>());
221         case panda_file::Type::TypeId::F64:
222             return wrapPrim(helpers::TypeIdentity<JSConvertF64>());
223         case panda_file::Type::TypeId::REFERENCE:
224             return ConvertEtsValRef(ctx, resolveRefCls, storeRes, readVal);
225         default: {
226             ctx->Fatal(std::string("ConvertEtsVal: unsupported typeid ") +
227                        panda_file::Type::GetSignatureByTypeId(type));
228         }
229     }
230     UNREACHABLE();
231 }
232 
233 using ArgValueBox = std::variant<uint64_t, ObjectHeader **>;
234 
235 template <bool IS_STATIC>
EtsCallImpl(EtsCoroutine * coro,InteropCtx * ctx,Method * method,Span<napi_value> jsargv,EtsObject * thisObj)236 napi_value EtsCallImpl(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span<napi_value> jsargv, EtsObject *thisObj)
237 {
238     ASSERT_MANAGED_CODE();
239 
240     auto classLinker = Runtime::GetCurrent()->GetClassLinker();
241 
242     auto pf = method->GetPandaFile();
243     panda_file::ProtoDataAccessor pda(*pf, panda_file::MethodDataAccessor::GetProtoId(*pf, method->GetFileId()));
244     pda.EnumerateTypes([](panda_file::Type) {});  // preload reftypes span
245 
246     auto resolveRefCls = [&classLinker, &pf, &pda, &ctx](uint32_t idx) {
247         auto klass = classLinker->GetLoadedClass(*pf, pda.GetReferenceType(idx), ctx->LinkerCtx());
248         ASSERT(klass != nullptr);
249         return klass;
250     };
251     auto loadRefCls = [&classLinker, &pf, &pda, &ctx](uint32_t idx) {
252         return classLinker->GetClass(*pf, pda.GetReferenceType(idx), ctx->LinkerCtx());
253     };
254 
255     panda_file::ShortyIterator it(method->GetShorty());
256     auto refArgIdx = static_cast<uint32_t>(!(*it++).IsPrimitive());  // skip retval
257 
258     ASSERT(method->IsStatic() == IS_STATIC);
259     static constexpr size_t ETS_ARGS_DISP = IS_STATIC ? 0 : 1;
260 
261     auto const numArgs = method->GetNumArgs() - ETS_ARGS_DISP;
262     if (UNLIKELY(numArgs != jsargv.size())) {
263         InteropCtx::ThrowJSTypeError(ctx->GetJSEnv(), std::string("CallEtsFunction: wrong argc"));
264         return nullptr;
265     }
266 
267     auto etsArgs = ctx->GetTempArgs<Value>(method->GetNumArgs());
268     {
269         HandleScope<ObjectHeader *> etsHandleScope(coro);
270 
271         VMHandle<ObjectHeader> thisObjHandle {};
272         if constexpr (!IS_STATIC) {
273             thisObjHandle = VMHandle<ObjectHeader>(coro, thisObj->GetCoreType());
274         } else {
275             (void)thisObj;
276             ASSERT(thisObj == nullptr);
277         }
278 
279         auto etsBoxedArgs = ctx->GetTempArgs<ArgValueBox>(numArgs);
280 
281         // Convert and box in VMHandle if necessary
282         for (uint32_t argIdx = 0; argIdx < numArgs; ++argIdx, it.IncrementWithoutCheck()) {
283             panda_file::Type type = *it;
284             auto jsVal = jsargv[argIdx];
285             auto clsResolver = [&loadRefCls, &refArgIdx]() { return loadRefCls(refArgIdx++); };
286             auto storePrim = [&etsBoxedArgs, &argIdx](uint64_t val) { etsBoxedArgs[argIdx] = val; };
287             auto storeRef = [&coro, &etsBoxedArgs, &argIdx](ObjectHeader *obj) {
288                 uintptr_t addr = VMHandle<ObjectHeader>(coro, obj).GetAddress();
289                 etsBoxedArgs[argIdx] = reinterpret_cast<ObjectHeader **>(addr);
290             };
291             if (UNLIKELY(!ConvertNapiVal(ctx, clsResolver, storePrim, storeRef, type, jsVal))) {
292                 if (coro->HasPendingException()) {
293                     ctx->ForwardEtsException(coro);
294                 }
295                 ASSERT(ctx->SanityJSExceptionPending());
296                 return nullptr;
297             }
298         }
299 
300         // Unbox VMHandles
301         for (size_t i = 0; i < numArgs; ++i) {
302             ArgValueBox &box = etsBoxedArgs[i];
303             if (std::holds_alternative<ObjectHeader **>(box)) {
304                 ObjectHeader **slot = std::get<1>(box);
305                 etsArgs[ETS_ARGS_DISP + i] = Value(slot != nullptr ? *slot : nullptr);
306             } else {
307                 etsArgs[ETS_ARGS_DISP + i] = Value(std::get<0>(box));
308             }
309         }
310         if constexpr (!IS_STATIC) {
311             etsArgs[0] = Value(thisObjHandle.GetPtr());
312         }
313     }
314 
315     ctx->GetInteropFrames().push_back({coro->GetCurrentFrame(), false});
316 
317     Value etsRes = method->Invoke(coro, etsArgs->data());
318 
319     ctx->GetInteropFrames().pop_back();
320 
321     if (UNLIKELY(coro->HasPendingException())) {
322         ctx->ForwardEtsException(coro);
323         return nullptr;
324     }
325 
326     napi_value jsRes;
327     {
328         auto type = method->GetReturnType();
329         auto clsResolver = [&resolveRefCls]() { return resolveRefCls(0); };
330         auto storeRes = [&jsRes](napi_value res) { jsRes = res; };
331         auto readVal = [&etsRes](auto typeTag) { return etsRes.GetAs<typename decltype(typeTag)::type>(); };
332         if (UNLIKELY(!ConvertEtsVal(ctx, clsResolver, storeRes, type, readVal))) {
333             ASSERT(ctx->SanityJSExceptionPending());
334             return nullptr;
335         }
336     }
337     return jsRes;
338 }
339 
340 // Explicit instantiation
341 template napi_value EtsCallImpl<false>(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span<napi_value> jsargv,
342                                        EtsObject *thisObj);
343 template napi_value EtsCallImpl<true>(EtsCoroutine *coro, InteropCtx *ctx, Method *method, Span<napi_value> jsargv,
344                                       EtsObject *thisObj);
345 
CallEtsFunctionImpl(napi_env env,Span<napi_value> jsargv)346 napi_value CallEtsFunctionImpl(napi_env env, Span<napi_value> jsargv)
347 {
348     auto coro = EtsCoroutine::GetCurrent();
349     auto ctx = InteropCtx::Current(coro);
350     [[maybe_unused]] EtsJSNapiEnvScope scope(ctx, env);
351 
352     // NOLINTNEXTLINE(readability-container-size-empty)
353     if (UNLIKELY(jsargv.size() < 1)) {
354         InteropCtx::ThrowJSError(env, "CallEtsFunction: method name required");
355         return nullptr;
356     }
357 
358     if (UNLIKELY(GetValueType(env, jsargv[0]) != napi_string)) {
359         InteropCtx::ThrowJSError(env, "CallEtsFunction: method name is not a string");
360         return nullptr;
361     }
362 
363     auto callTarget = GetString(env, jsargv[0]);
364     std::string packageName {};
365     std::string methodName {};
366 
367     auto packageSep = callTarget.rfind('.');
368     if (packageSep != std::string::npos) {
369         packageName = callTarget.substr(0, packageSep + 1);
370         methodName = callTarget.substr(packageSep + 1, callTarget.size());
371     } else {
372         methodName = callTarget;
373     }
374 
375     auto entrypoint = packageName + std::string("ETSGLOBAL::") + methodName;
376     INTEROP_LOG(DEBUG) << "CallEtsFunction: method name: " << entrypoint;
377 
378     auto methodRes = Runtime::GetCurrent()->ResolveEntryPoint(entrypoint);
379     if (UNLIKELY(!methodRes)) {
380         InteropCtx::ThrowJSError(env, "CallEtsFunction: can't resolve method " + entrypoint);
381         return nullptr;
382     }
383 
384     ScopedManagedCodeThread managedScope(coro);
385     return EtsCallImplStatic(coro, ctx, methodRes.Value(), jsargv.SubSpan(1));
386 }
387 
EtsLambdaProxyInvoke(napi_env env,napi_callback_info cbinfo)388 napi_value EtsLambdaProxyInvoke(napi_env env, napi_callback_info cbinfo)
389 {
390     auto coro = EtsCoroutine::GetCurrent();
391     auto ctx = InteropCtx::Current(coro);
392     [[maybe_unused]] EtsJSNapiEnvScope envscope(ctx, env);
393 
394     size_t argc;
395     napi_value athis;
396     void *data;
397     NAPI_CHECK_FATAL(napi_get_cb_info(env, cbinfo, &argc, nullptr, &athis, &data));
398     auto jsArgs = ctx->GetTempArgs<napi_value>(argc);
399     NAPI_CHECK_FATAL(napi_get_cb_info(env, cbinfo, &argc, jsArgs->data(), &athis, &data));
400 
401     auto sharedRef = static_cast<ets_proxy::SharedReference *>(data);
402     ASSERT(sharedRef != nullptr);
403 
404     ScopedManagedCodeThread managedScope(coro);
405     auto etsThis = sharedRef->GetEtsObject(ctx);
406     ASSERT(etsThis != nullptr);
407     auto method = etsThis->GetClass()->GetMethod("invoke");
408     ASSERT(method != nullptr);
409 
410     return EtsCallImplInstance(coro, ctx, method->GetPandaMethod(), *jsArgs, etsThis);
411 }
412 
413 template <bool IS_NEWCALL, typename FSetupArgs>
JSRuntimeJSCallImpl(FSetupArgs & setupArgs,Method * method,uint8_t * args,uint8_t * inStackArgs)414 static ALWAYS_INLINE inline uint64_t JSRuntimeJSCallImpl(FSetupArgs &setupArgs, Method *method, uint8_t *args,
415                                                          uint8_t *inStackArgs)
416 {
417     auto coro = EtsCoroutine::GetCurrent();
418     auto ctx = InteropCtx::Current(coro);
419     auto classLinker = Runtime::GetCurrent()->GetClassLinker();
420 
421     auto pf = method->GetPandaFile();
422     panda_file::ProtoDataAccessor pda(*pf, panda_file::MethodDataAccessor::GetProtoId(*pf, method->GetFileId()));
423     pda.EnumerateTypes([](panda_file::Type) {});  // preload reftypes span
424 
425     auto resolveRefCls = [&classLinker, &pf, &pda, &ctx](uint32_t idx) {
426         auto klass = classLinker->GetLoadedClass(*pf, pda.GetReferenceType(idx), ctx->LinkerCtx());
427         ASSERT(klass != nullptr);
428         return klass;
429     };
430     [[maybe_unused]] auto loadRefCls = [&classLinker, &pf, &pda, &ctx](uint32_t idx) {
431         return classLinker->GetClass(*pf, pda.GetReferenceType(idx), ctx->LinkerCtx());
432     };
433 
434     Span<uint8_t> inGprArgs(args, arch::ExtArchTraits<RUNTIME_ARCH>::GP_ARG_NUM_BYTES);
435     Span<uint8_t> inFprArgs(inGprArgs.end(), arch::ExtArchTraits<RUNTIME_ARCH>::FP_ARG_NUM_BYTES);
436     arch::ArgReader<RUNTIME_ARCH> argReader(inGprArgs, inFprArgs, inStackArgs);
437 
438     napi_env env = ctx->GetJSEnv();
439     NapiScope jsHandleScope(env);
440 
441     napi_value jsThis;
442     napi_value jsFn;
443     panda_file::ShortyIterator it;
444     uint32_t numArgs;
445     uint32_t refArgIdx;
446     {
447         argReader.Read<Method *>();  // skip method
448 
449         auto setupRes = setupArgs(ctx, env, method, argReader);
450         if (UNLIKELY(!setupRes.has_value())) {
451             ctx->ForwardJSException(coro);
452             return 0;
453         }
454         std::tie(jsThis, jsFn, it, numArgs, refArgIdx) = setupRes.value();
455 
456         if (UNLIKELY(GetValueType(env, jsFn) != napi_function)) {
457             ctx->ThrowJSTypeError(env, "call target is not a function");
458             ctx->ForwardJSException(coro);
459             return 0;
460         }
461     }
462 
463     auto jsargs = ctx->GetTempArgs<napi_value>(numArgs);
464 
465     for (uint32_t argIdx = 0; argIdx < numArgs; ++argIdx, it.IncrementWithoutCheck()) {
466         auto clsResolver = [&resolveRefCls, &refArgIdx]() { return resolveRefCls(refArgIdx++); };
467         auto storeRes = [&jsargs, &argIdx](napi_value res) { jsargs[argIdx] = res; };
468         auto readVal = [&argReader](auto typeTag) { return argReader.Read<typename decltype(typeTag)::type>(); };
469         if (UNLIKELY(!ConvertEtsVal(ctx, clsResolver, storeRes, *it, readVal))) {
470             ctx->ForwardJSException(coro);
471             return 0;
472         }
473     }
474 
475     napi_value jsRet;
476     napi_status jsStatus;
477     {
478         ctx->GetInteropFrames().push_back({coro->GetCurrentFrame(), true});
479         ScopedNativeCodeThread nativeScope(coro);
480 
481         if constexpr (IS_NEWCALL) {
482             jsStatus = napi_new_instance(env, jsFn, jsargs->size(), jsargs->data(), &jsRet);
483         } else {
484             jsStatus = napi_call_function(env, jsThis, jsFn, jsargs->size(), jsargs->data(), &jsRet);
485         }
486 
487         ctx->GetInteropFrames().pop_back();
488     }
489 
490     if (UNLIKELY(jsStatus != napi_ok)) {
491         INTEROP_FATAL_IF(jsStatus != napi_pending_exception);
492         ctx->ForwardJSException(coro);
493         return 0;
494     }
495 
496     Value etsRet;
497 
498     if constexpr (IS_NEWCALL) {
499         INTEROP_FATAL_IF(resolveRefCls(0) != ctx->GetJSValueClass());
500         auto res = JSConvertJSValue::Unwrap(ctx, env, jsRet);
501         if (!res.has_value()) {
502             ctx->Fatal("newcall result unwrap failed, but shouldnt");
503         }
504         etsRet = Value(res.value()->GetCoreType());
505     } else {
506         panda_file::Type type = method->GetReturnType();
507         auto clsResolver = [&loadRefCls]() { return loadRefCls(0); };
508         auto storePrim = [&etsRet](uint64_t val) { etsRet = Value(val); };
509         auto storeRef = [&etsRet](ObjectHeader *obj) { etsRet = Value(obj); };
510         if (UNLIKELY(!ConvertNapiVal(ctx, clsResolver, storePrim, storeRef, type, jsRet))) {
511             if (NapiIsExceptionPending(env)) {
512                 ctx->ForwardJSException(coro);
513             }
514             ASSERT(ctx->SanityETSExceptionPending());
515             return 0;
516         }
517     }
518 
519     return static_cast<uint64_t>(etsRet.GetAsLong());
520 }
521 
CompilerResolveQualifiedJSCall(napi_env env,napi_value jsVal,coretypes::String * qnameStr)522 static inline std::optional<std::pair<napi_value, napi_value>> CompilerResolveQualifiedJSCall(
523     napi_env env, napi_value jsVal, coretypes::String *qnameStr)
524 {
525     napi_value jsThis {};
526     ASSERT(qnameStr->IsMUtf8());
527     auto qname = std::string_view(utf::Mutf8AsCString(qnameStr->GetDataMUtf8()), qnameStr->GetMUtf8Length());
528 
529     auto resolveName = [&jsThis, &jsVal, &env](const std::string &name) -> bool {
530         jsThis = jsVal;
531         INTEROP_LOG(DEBUG) << "JSRuntimeJSCall: resolve name: " << name;
532         napi_status rc = napi_get_named_property(env, jsVal, name.c_str(), &jsVal);
533         if (UNLIKELY(rc == napi_object_expected || NapiThrownGeneric(rc))) {
534             ASSERT(NapiIsExceptionPending(env));
535             return false;
536         }
537         return true;
538     };
539     jsThis = jsVal;
540     if (UNLIKELY(!WalkQualifiedName(qname, resolveName))) {
541         return std::nullopt;
542     }
543     return std::make_pair(jsThis, jsVal);
544 }
545 
546 template <bool IS_NEWCALL>
JSRuntimeJSCallBase(Method * method,uint8_t * args,uint8_t * inStackArgs)547 static ALWAYS_INLINE inline uint64_t JSRuntimeJSCallBase(Method *method, uint8_t *args, uint8_t *inStackArgs)
548 {
549     auto argsetup =
550         []([[maybe_unused]] InteropCtx * ctx, napi_env env, Method * methodd, arch::ArgReader<RUNTIME_ARCH> & argReader)
551             __attribute__((always_inline))
552                 ->std::optional<std::tuple<napi_value, napi_value, panda_file::ShortyIterator, uint32_t, uint32_t>>
553     {
554         ASSERT(methodd->IsStatic());
555 
556         panda_file::ShortyIterator it(methodd->GetShorty());
557         uint32_t numArgs = methodd->GetNumArgs() - 2;
558         uint32_t refArgIdx = !(*it++).IsPrimitive() + 2;
559         it.IncrementWithoutCheck();
560         it.IncrementWithoutCheck();
561 
562         napi_value jsVal = JSConvertJSValue::Wrap(env, argReader.Read<JSValue *>());
563         auto qnameStr = argReader.Read<coretypes::String *>();
564         ASSERT(qnameStr->ClassAddr<Class>()->IsStringClass());
565         auto res = CompilerResolveQualifiedJSCall(env, jsVal, qnameStr);
566         if (UNLIKELY(!res.has_value())) {
567             ASSERT(NapiIsExceptionPending(env));
568             return std::nullopt;
569         }
570         return std::make_tuple(res->first, res->second, it, numArgs, refArgIdx);
571     };
572     return JSRuntimeJSCallImpl<IS_NEWCALL>(argsetup, method, args, inStackArgs);
573 }
574 
JSRuntimeJSCall(Method * method,uint8_t * args,uint8_t * inStackArgs)575 extern "C" uint64_t JSRuntimeJSCall(Method *method, uint8_t *args, uint8_t *inStackArgs)
576 {
577     return JSRuntimeJSCallBase<false>(method, args, inStackArgs);  // IS_NEWCALL is false
578 }
579 extern "C" void JSRuntimeJSCallBridge(Method *method, ...);
580 
JSRuntimeJSNew(Method * method,uint8_t * args,uint8_t * inStackArgs)581 extern "C" uint64_t JSRuntimeJSNew(Method *method, uint8_t *args, uint8_t *inStackArgs)
582 {
583     return JSRuntimeJSCallBase<true>(method, args, inStackArgs);  // IS_NEWCALL is true
584 }
585 extern "C" void JSRuntimeJSNewBridge(Method *method, ...);
586 
JSRuntimeJSCallByValue(Method * method,uint8_t * args,uint8_t * inStackArgs)587 extern "C" uint64_t JSRuntimeJSCallByValue(Method *method, uint8_t *args, uint8_t *inStackArgs)
588 {
589     auto argsetup =
590         []([[maybe_unused]] InteropCtx * ctx, napi_env env, Method * methodd, arch::ArgReader<RUNTIME_ARCH> & argReader)
591             __attribute__((always_inline))
592                 ->std::optional<std::tuple<napi_value, napi_value, panda_file::ShortyIterator, uint32_t, uint32_t>>
593     {
594         ASSERT(methodd->IsStatic());
595 
596         panda_file::ShortyIterator it(methodd->GetShorty());
597         uint32_t numArgs = methodd->GetNumArgs() - 2;
598         uint32_t refArgIdx = static_cast<uint32_t>(!(*it++).IsPrimitive()) + 2;
599         it.IncrementWithoutCheck();
600         it.IncrementWithoutCheck();
601 
602         napi_value jsFn = JSConvertJSValue::Wrap(env, argReader.Read<JSValue *>());
603         napi_value jsThis = JSConvertJSValue::Wrap(env, argReader.Read<JSValue *>());
604 
605         return std::make_tuple(jsThis, jsFn, it, numArgs, refArgIdx);
606     };
607     return JSRuntimeJSCallImpl<false>(argsetup, method, args, inStackArgs);  // IS_NEWCALL is false
608 }
609 extern "C" void JSRuntimeJSCallByValueBridge(Method *method, ...);
610 
JSProxyCall(Method * method,uint8_t * args,uint8_t * inStackArgs)611 extern "C" uint64_t JSProxyCall(Method *method, uint8_t *args, uint8_t *inStackArgs)
612 {
613     auto argsetup =
614         [](InteropCtx * ctx, napi_env env, Method * methodd, arch::ArgReader<RUNTIME_ARCH> & argReader)
615             __attribute__((always_inline))
616                 ->std::optional<std::tuple<napi_value, napi_value, panda_file::ShortyIterator, uint32_t, uint32_t>>
617     {
618         INTEROP_LOG(DEBUG) << "JSRuntimeJSCallImpl: JSProxy call: " << methodd->GetFullName(true);
619         ASSERT(!methodd->IsStatic());
620 
621         panda_file::ShortyIterator it(methodd->GetShorty());
622         uint32_t numArgs = methodd->GetNumArgs() - 1;
623         uint32_t refArgIdx = static_cast<uint32_t>(!(*it++).IsPrimitive());
624 
625         auto *etsThis = argReader.Read<EtsObject *>();
626         Class *cls = etsThis->GetClass()->GetRuntimeClass();
627         auto refconv = JSRefConvertResolve(ctx, cls);
628         napi_value jsThis = refconv->Wrap(ctx, etsThis);
629         ASSERT(GetValueType(env, jsThis) == napi_object);
630         const char *methodName = utf::Mutf8AsCString(methodd->GetName().data);
631         napi_value jsFn;
632         napi_status rc = napi_get_named_property(env, jsThis, methodName, &jsFn);
633         if (UNLIKELY(rc == napi_object_expected || NapiThrownGeneric(rc))) {
634             ASSERT(NapiIsExceptionPending(env));
635             return std::nullopt;
636         }
637         return std::make_tuple(jsThis, jsFn, it, numArgs, refArgIdx);
638     };
639     return JSRuntimeJSCallImpl<false>(argsetup, method, args, inStackArgs);  // IS_NEWCALL is false
640 }
641 extern "C" void JSProxyCallBridge(Method *method, ...);
642 
643 template <bool IS_NEWCALL>
InitJSCallSignatures(coretypes::String * clsStr)644 static void InitJSCallSignatures(coretypes::String *clsStr)
645 {
646     auto coro = EtsCoroutine::GetCurrent();
647     auto ctx = InteropCtx::Current(coro);
648     auto classLinker = Runtime::GetCurrent()->GetClassLinker();
649 
650     std::string classDescriptor(utf::Mutf8AsCString(clsStr->GetDataMUtf8()));
651     INTEROP_LOG(DEBUG) << "Intialize jscall signatures for " << classDescriptor;
652     EtsClass *etsClass = coro->GetPandaVM()->GetClassLinker()->GetClass(classDescriptor.c_str());
653     INTEROP_FATAL_IF(etsClass == nullptr);
654     auto klass = etsClass->GetRuntimeClass();
655 
656     INTEROP_LOG(DEBUG) << "Bind bridge call methods for " << utf::Mutf8AsCString(klass->GetDescriptor());
657 
658     for (auto &method : klass->GetMethods()) {
659         if (method.IsConstructor()) {
660             continue;
661         }
662         ASSERT(method.IsStatic());
663         auto pf = method.GetPandaFile();
664         panda_file::ProtoDataAccessor pda(*pf, panda_file::MethodDataAccessor::GetProtoId(*pf, method.GetFileId()));
665         pda.EnumerateTypes([](panda_file::Type) {});  // preload reftypes span
666 
667         void *methodEp = nullptr;
668         if constexpr (IS_NEWCALL) {
669             methodEp = reinterpret_cast<void *>(JSRuntimeJSNewBridge);
670         } else {
671             uint32_t const argReftypeShift = method.GetReturnType().IsReference() ? 1 : 0;
672             ASSERT(method.GetArgType(0).IsReference());  // arg0 is always a reference
673             ASSERT(method.GetArgType(1).IsReference());  // arg1 is always a reference
674             auto cls1 = classLinker->GetClass(*pf, pda.GetReferenceType(1 + argReftypeShift), ctx->LinkerCtx());
675             if (cls1->IsStringClass()) {
676                 methodEp = reinterpret_cast<void *>(JSRuntimeJSCallBridge);
677             } else {
678                 ASSERT(cls1 == ctx->GetJSValueClass());
679                 methodEp = reinterpret_cast<void *>(JSRuntimeJSCallByValueBridge);
680             }
681         }
682         method.SetCompiledEntryPoint(methodEp);
683         method.SetNativePointer(nullptr);
684     }
685 }
686 
JSRuntimeInitJSCallClass(EtsString * clsStr)687 uint8_t JSRuntimeInitJSCallClass(EtsString *clsStr)
688 {
689     InitJSCallSignatures<false>(clsStr->GetCoreType());
690     return 1;
691 }
692 
JSRuntimeInitJSNewClass(EtsString * clsStr)693 uint8_t JSRuntimeInitJSNewClass(EtsString *clsStr)
694 {
695     InitJSCallSignatures<true>(clsStr->GetCoreType());
696     return 1;
697 }
698 
699 }  // namespace panda::ets::interop::js
700