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