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 "macros.h"
17 #include "runtime/mem/local_object_handle.h"
18 #include "plugins/ets/runtime/interop_js/call/call.h"
19 #include "plugins/ets/runtime/interop_js/call/arg_convertors.h"
20 #include "plugins/ets/runtime/interop_js/call/proto_reader.h"
21 #include "plugins/ets/runtime/interop_js/code_scopes.h"
22 #include "plugins/ets/runtime/ets_stubs-inl.h"
23
24 namespace ark::ets::interop::js {
25
CreateProxyBridgeArgReader(uint8_t * args,uint8_t * inStackArgs)26 static ALWAYS_INLINE inline arch::ArgReader<RUNTIME_ARCH> CreateProxyBridgeArgReader(uint8_t *args,
27 uint8_t *inStackArgs)
28 {
29 Span<uint8_t> inGprArgs(args, arch::ExtArchTraits<RUNTIME_ARCH>::GP_ARG_NUM_BYTES);
30 Span<uint8_t> inFprArgs(inGprArgs.end(), arch::ExtArchTraits<RUNTIME_ARCH>::FP_ARG_NUM_BYTES);
31 return arch::ArgReader<RUNTIME_ARCH>(inGprArgs, inFprArgs, inStackArgs);
32 }
33
34 class CallJSHandler {
35 public:
CallJSHandler(EtsCoroutine * coro,Method * method,uint8_t * args,uint8_t * inStackArgs)36 ALWAYS_INLINE CallJSHandler(EtsCoroutine *coro, Method *method, uint8_t *args, uint8_t *inStackArgs)
37 : coro_(coro),
38 ctx_(InteropCtx::Current(coro_)),
39 protoReader_(method, ctx_->GetClassLinker(), ctx_->LinkerCtx()),
40 argReader_(CreateProxyBridgeArgReader(args, inStackArgs))
41 {
42 }
43
SetupArgreader(bool isInstance)44 ALWAYS_INLINE ObjectHeader *SetupArgreader(bool isInstance)
45 {
46 auto method = protoReader_.GetMethod();
47 protoReader_.Advance(); // skip return type
48 argReader_.template Read<Method *>(); // skip method
49 ASSERT(isInstance == !method->IsStatic());
50 numArgs_ = method->GetNumArgs() - static_cast<uint32_t>(isInstance);
51 return isInstance ? argReader_.Read<ObjectHeader *>() : nullptr;
52 }
53
54 template <panda_file::Type::TypeId PF_TYPEID, typename T>
ReadFixedArg()55 ALWAYS_INLINE T ReadFixedArg()
56 {
57 ASSERT(protoReader_.GetType() == panda_file::Type(PF_TYPEID));
58 protoReader_.Advance();
59 numArgs_--;
60 return argReader_.Read<T>();
61 }
62
63 template <typename T>
ReadFixedRefArg(Class * expected)64 ALWAYS_INLINE T *ReadFixedRefArg([[maybe_unused]] Class *expected)
65 {
66 ASSERT(expected == nullptr || protoReader_.GetClass() == expected);
67 return ReadFixedArg<panda_file::Type::TypeId::REFERENCE, T *>();
68 }
69
SetupJSCallee(napi_value jsThis,napi_value jsFn)70 ALWAYS_INLINE void SetupJSCallee(napi_value jsThis, napi_value jsFn)
71 {
72 jsThis_ = jsThis;
73 jsFn_ = jsFn;
74 }
75
76 template <bool IS_NEWCALL, typename ArgSetup>
HandleImpl(Method * method,uint8_t * args,uint8_t * inStackArgs)77 static ALWAYS_INLINE uint64_t HandleImpl(Method *method, uint8_t *args, uint8_t *inStackArgs)
78 {
79 CallJSHandler st(EtsCoroutine::GetCurrent(), method, args, inStackArgs);
80 return st.Handle<IS_NEWCALL, ArgSetup>();
81 }
82
GetMethod()83 ALWAYS_INLINE Method *GetMethod()
84 {
85 return protoReader_.GetMethod();
86 }
87
88 ~CallJSHandler() = default;
89
90 private:
ForwardException(InteropCtx * ctx,EtsCoroutine * coro)91 static uint64_t __attribute__((noinline)) ForwardException(InteropCtx *ctx, EtsCoroutine *coro)
92 {
93 if (NapiIsExceptionPending(ctx->GetJSEnv())) {
94 ctx->ForwardJSException(coro);
95 }
96 ASSERT(ctx->SanityETSExceptionPending());
97 return 0;
98 }
99
100 template <bool IS_NEWCALL, typename ArgSetup>
101 ALWAYS_INLINE uint64_t Handle();
102
103 template <bool IS_NEWCALL>
104 ALWAYS_INLINE std::optional<napi_value> ConvertArgsAndCall();
105
106 template <bool IS_NEWCALL, typename FRead>
107 ALWAYS_INLINE inline std::optional<napi_value> ConvertVarargsAndCall(FRead &readVal, Span<napi_value> jsargs);
108
109 template <bool IS_NEWCALL>
110 ALWAYS_INLINE std::optional<napi_value> CallConverted(Span<napi_value> jsargs);
111
112 template <bool IS_NEWCALL>
113 ALWAYS_INLINE std::optional<Value> ConvertRetval(napi_value jsRet);
114
115 napi_value HandleSpecialMethod(Span<napi_value> jsargs);
116
117 NO_COPY_SEMANTIC(CallJSHandler);
118 NO_MOVE_SEMANTIC(CallJSHandler);
119
120 EtsCoroutine *const coro_;
121 InteropCtx *const ctx_;
122
123 ProtoReader protoReader_;
124 arch::ArgReader<RUNTIME_ARCH> argReader_;
125 uint32_t numArgs_ {};
126 napi_value jsThis_ {};
127 napi_value jsFn_ {};
128 };
129
130 template <bool IS_NEWCALL, typename ArgSetup>
Handle()131 ALWAYS_INLINE inline uint64_t CallJSHandler::Handle()
132 {
133 [[maybe_unused]] InteropCodeScopeETS codeScope(coro_, __PRETTY_FUNCTION__);
134 napi_env env = ctx_->GetJSEnv();
135 NapiScope jsHandleScope(env);
136
137 if (UNLIKELY(!ArgSetup()(ctx_, this))) {
138 return ForwardException(ctx_, coro_);
139 }
140
141 std::optional<napi_value> jsRes = ConvertArgsAndCall<IS_NEWCALL>();
142 if (UNLIKELY(!jsRes.has_value())) {
143 return ForwardException(ctx_, coro_);
144 }
145 std::optional<Value> etsRes = ConvertRetval<IS_NEWCALL>(jsRes.value());
146 if (UNLIKELY(!etsRes.has_value())) {
147 return ForwardException(ctx_, coro_);
148 }
149 return static_cast<uint64_t>(etsRes.value().GetAsLong());
150 }
151
152 template <bool IS_NEWCALL, typename FRead>
ConvertVarargsAndCall(FRead & readVal,Span<napi_value> jsargs)153 ALWAYS_INLINE inline std::optional<napi_value> CallJSHandler::ConvertVarargsAndCall(FRead &readVal,
154 Span<napi_value> jsargs)
155 {
156 auto *ref = readVal(helpers::TypeIdentity<ObjectHeader *>());
157 auto *klass = ref->template ClassAddr<Class>();
158 if (!klass->IsArrayClass()) {
159 ASSERT(klass == ctx_->GetArrayClass());
160 VMHandle<EtsArrayObject<EtsObject>> etsArr(coro_, ref);
161 auto allJsArgs = ctx_->GetTempArgs<napi_value>(etsArr->GetActualLength() + jsargs.size());
162 for (uint32_t el = 0; el < jsargs.size(); ++el) {
163 allJsArgs[el] = jsargs[el];
164 }
165 for (uint32_t el = 0; el < etsArr->GetActualLength(); ++el) {
166 EtsObject *etsElem = nullptr;
167 etsArr->GetRef(el, &etsElem);
168 auto refConv = JSRefConvertResolve<true>(ctx_, etsElem->GetClass()->GetRuntimeClass());
169 ASSERT(refConv != nullptr);
170 allJsArgs[el + jsargs.size()] = refConv->Wrap(ctx_, etsElem);
171 }
172 return CallConverted<IS_NEWCALL>(*allJsArgs);
173 }
174
175 LocalObjectHandle<coretypes::Array> etsArr(coro_, ref);
176
177 auto allJsArgs = ctx_->GetTempArgs<napi_value>(etsArr->GetLength() + jsargs.size());
178 for (uint32_t el = 0; el < jsargs.size(); ++el) {
179 allJsArgs[el] = jsargs[el];
180 }
181 for (uint32_t el = 0; el < etsArr->GetLength(); ++el) {
182 auto *etsElem = EtsObject::FromCoreType(etsArr->Get<ObjectHeader *>(el));
183 auto refConv = JSRefConvertResolve<true>(ctx_, etsElem->GetClass()->GetRuntimeClass());
184 ASSERT(refConv != nullptr);
185 allJsArgs[el + jsargs.size()] = refConv->Wrap(ctx_, etsElem);
186 }
187 return CallConverted<IS_NEWCALL>(*allJsArgs);
188 }
189
190 template <bool IS_NEWCALL>
ConvertArgsAndCall()191 ALWAYS_INLINE inline std::optional<napi_value> CallJSHandler::ConvertArgsAndCall()
192 {
193 bool isVarArgs = UNLIKELY(protoReader_.GetMethod()->HasVarArgs());
194 auto readVal = [this](auto typeTag) { return argReader_.template Read<typename decltype(typeTag)::type>(); };
195 auto const numNonRest = numArgs_ - (isVarArgs ? 1 : 0);
196 auto jsargs = ctx_->GetTempArgs<napi_value>(numNonRest);
197
198 // scope is required to ConvertRefArgToJS
199 HandleScope<ObjectHeader *> scope(coro_);
200 for (uint32_t argIdx = 0; argIdx < numNonRest; ++argIdx, protoReader_.Advance()) {
201 if (UNLIKELY(!ConvertArgToJS(ctx_, protoReader_, &jsargs[argIdx], readVal))) {
202 return std::nullopt;
203 }
204 }
205
206 napi_value handlerResult = HandleSpecialMethod(*jsargs);
207 if (handlerResult) {
208 return handlerResult;
209 }
210
211 napi_env env = ctx_->GetJSEnv();
212 if (UNLIKELY(GetValueType(env, jsFn_) != napi_function)) {
213 ctx_->ThrowJSTypeError(env, "call target is not a function");
214 return std::nullopt;
215 }
216
217 if (isVarArgs) {
218 return ConvertVarargsAndCall<IS_NEWCALL>(readVal, *jsargs);
219 }
220
221 return CallConverted<IS_NEWCALL>(*jsargs);
222 }
223
HandleSpecialMethod(Span<napi_value> jsargs)224 napi_value CallJSHandler::HandleSpecialMethod(Span<napi_value> jsargs)
225 {
226 napi_value handlerResult {};
227 napi_env env = ctx_->GetJSEnv();
228 ScopedNativeCodeThread nativeScope(coro_);
229 const char *methodName = EtsMethod::FromRuntimeMethod(protoReader_.GetMethod())->GetName();
230 if (methodName != nullptr && std::strlen(methodName) >= SETTER_GETTER_PREFIX_LENGTH) {
231 std::string content = std::string(methodName).substr(SETTER_GETTER_PREFIX_LENGTH);
232 if (std::strncmp(methodName, GETTER_BEGIN, SETTER_GETTER_PREFIX_LENGTH) == 0) {
233 NAPI_CHECK_FATAL(napi_get_named_property(env, jsThis_, content.c_str(), &handlerResult));
234 } else if (std::strncmp(methodName, SETTER_BEGIN, SETTER_GETTER_PREFIX_LENGTH) == 0) {
235 NAPI_CHECK_FATAL(napi_create_string_utf8(env, content.c_str(), NAPI_AUTO_LENGTH, &handlerResult));
236 NAPI_CHECK_FATAL(napi_set_property(env, jsThis_, handlerResult, jsargs[0]));
237 napi_get_undefined(env, &handlerResult);
238 } else if (std::strncmp(methodName, GET_INDEX_METHOD, SETTER_GETTER_PREFIX_LENGTH) == 0) {
239 int32_t idx;
240 NAPI_CHECK_FATAL(napi_get_value_int32(env, jsargs[0], &idx));
241 NAPI_CHECK_FATAL(napi_get_element(env, jsThis_, idx, &handlerResult));
242 } else if (std::strncmp(methodName, SET_INDEX_METHOD, SETTER_GETTER_PREFIX_LENGTH) == 0) {
243 int32_t idx;
244 NAPI_CHECK_FATAL(napi_get_value_int32(env, jsargs[0], &idx));
245 NAPI_CHECK_FATAL(napi_set_element(env, jsThis_, idx, jsargs[1]));
246 napi_get_undefined(env, &handlerResult);
247 } else if (std::strncmp(methodName, ITERATOR_METHOD, SETTER_GETTER_PREFIX_LENGTH) == 0) {
248 napi_value global;
249 NAPI_CHECK_FATAL(napi_get_global(env, &global));
250 napi_value symbol;
251 NAPI_CHECK_FATAL(napi_get_named_property(env, global, "Symbol", &symbol));
252 napi_value symbolIterator;
253 NAPI_CHECK_FATAL(napi_get_named_property(env, symbol, "iterator", &symbolIterator));
254 napi_value iteratorMethod;
255 NAPI_CHECK_FATAL(napi_get_property(env, jsThis_, symbolIterator, &iteratorMethod));
256 if (GetValueType(env, iteratorMethod) == napi_undefined) {
257 NAPI_CHECK_FATAL(napi_get_undefined(env, &handlerResult));
258 return handlerResult;
259 }
260 size_t jsArgc = 0;
261 NAPI_CHECK_FATAL(napi_call_function(env, jsThis_, iteratorMethod, jsArgc, nullptr, &handlerResult));
262 }
263 }
264 return handlerResult;
265 }
266
267 template <bool IS_NEWCALL>
CallConverted(Span<napi_value> jsargs)268 ALWAYS_INLINE inline std::optional<napi_value> CallJSHandler::CallConverted(Span<napi_value> jsargs)
269 {
270 napi_env env = ctx_->GetJSEnv();
271 napi_value jsRetval;
272 napi_status jsStatus;
273 {
274 ScopedNativeCodeThread nativeScope(coro_);
275 if constexpr (IS_NEWCALL) {
276 jsStatus = napi_new_instance(env, jsFn_, jsargs.size(), jsargs.data(), &jsRetval);
277 } else {
278 jsStatus = napi_call_function(env, jsThis_, jsFn_, jsargs.size(), jsargs.data(), &jsRetval);
279 }
280 }
281 if (UNLIKELY(jsStatus != napi_ok)) {
282 INTEROP_FATAL_IF(jsStatus != napi_pending_exception);
283 return std::nullopt;
284 }
285 return jsRetval;
286 }
287
288 template <bool IS_NEWCALL>
ConvertRetval(napi_value jsRet)289 ALWAYS_INLINE inline std::optional<Value> CallJSHandler::ConvertRetval(napi_value jsRet)
290 {
291 [[maybe_unused]] napi_env env = ctx_->GetJSEnv();
292 Value etsRet;
293 protoReader_.Reset();
294
295 if constexpr (IS_NEWCALL) {
296 ASSERT(protoReader_.GetClass() == ctx_->GetJSValueClass());
297 auto res = JSConvertJSValue::Unwrap(ctx_, env, jsRet);
298 if (UNLIKELY(!res.has_value())) {
299 return std::nullopt;
300 }
301 etsRet = Value(res.value()->GetCoreType());
302 } else {
303 auto store = [&etsRet](auto val) { etsRet = Value(val); };
304 if (UNLIKELY(!ConvertArgToEts(ctx_, protoReader_, store, jsRet))) {
305 return std::nullopt;
306 }
307 }
308 return etsRet;
309 }
310
ResolveQualifiedReceiverTarget(napi_env env,napi_value jsVal,coretypes::String * qnameStr)311 static std::optional<std::pair<napi_value, napi_value>> ResolveQualifiedReceiverTarget(napi_env env, napi_value jsVal,
312 coretypes::String *qnameStr)
313 {
314 napi_value jsThis {};
315 ASSERT(qnameStr->IsMUtf8());
316 auto qname = std::string(utf::Mutf8AsCString(qnameStr->GetDataMUtf8()), qnameStr->GetMUtf8Length());
317
318 auto resolveName = [&jsThis, &jsVal, &env](const std::string &name) -> bool {
319 jsThis = jsVal;
320
321 if (!NapiGetNamedProperty(env, jsVal, name.c_str(), &jsVal)) {
322 ASSERT(NapiIsExceptionPending(env));
323 return false;
324 }
325 return true;
326 };
327 jsThis = jsVal;
328 if (UNLIKELY(!WalkQualifiedName(qname, resolveName))) {
329 return std::nullopt;
330 }
331 return std::make_pair(jsThis, jsVal);
332 }
333
334 template <bool IS_NEWCALL>
JSRuntimeCallJSQNameBase(Method * method,uint8_t * args,uint8_t * inStackArgs)335 static ALWAYS_INLINE inline uint64_t JSRuntimeCallJSQNameBase(Method *method, uint8_t *args, uint8_t *inStackArgs)
336 {
337 struct ArgSetup {
338 ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
339 {
340 st->SetupArgreader(false);
341 napi_env env = ctx->GetJSEnv();
342
343 napi_value jsVal = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
344 auto qnameStr = st->ReadFixedRefArg<coretypes::String>(ctx->GetStringClass());
345
346 auto res = ResolveQualifiedReceiverTarget(env, jsVal, qnameStr);
347 if (UNLIKELY(!res.has_value())) {
348 ASSERT(NapiIsExceptionPending(env));
349 return false;
350 }
351
352 st->SetupJSCallee(res->first, res->second);
353 return true;
354 }
355 };
356 return CallJSHandler::HandleImpl<IS_NEWCALL, ArgSetup>(method, args, inStackArgs);
357 }
358
JSRuntimeCallJSQName(Method * method,uint8_t * args,uint8_t * inStackArgs)359 extern "C" uint64_t JSRuntimeCallJSQName(Method *method, uint8_t *args, uint8_t *inStackArgs)
360 {
361 return JSRuntimeCallJSQNameBase<false>(method, args, inStackArgs); // IS_NEWCALL is false
362 }
363 extern "C" void JSRuntimeCallJSQNameBridge(Method *method, ...);
364
JSRuntimeNewCallJSQName(Method * method,uint8_t * args,uint8_t * inStackArgs)365 extern "C" uint64_t JSRuntimeNewCallJSQName(Method *method, uint8_t *args, uint8_t *inStackArgs)
366 {
367 return JSRuntimeCallJSQNameBase<true>(method, args, inStackArgs); // IS_NEWCALL is true
368 }
369 extern "C" void JSRuntimeNewCallJSQNameBridge(Method *method, ...);
370
GetClassQnameOffset(InteropCtx * ctx,Method * method)371 static uint32_t GetClassQnameOffset(InteropCtx *ctx, Method *method)
372 {
373 auto klass = method->GetClass();
374 ctx->GetConstStringStorage()->LoadDynamicCallClass(klass);
375 auto fields = klass->GetStaticFields();
376 ASSERT(fields.size() == 1);
377 return klass->GetFieldPrimitive<uint32_t>(fields[0]);
378 }
379
380 template <bool IS_NEWCALL>
JSRuntimeCallJSBase(Method * method,uint8_t * args,uint8_t * inStackArgs)381 static ALWAYS_INLINE inline uint64_t JSRuntimeCallJSBase(Method *method, uint8_t *args, uint8_t *inStackArgs)
382 {
383 struct ArgSetup {
384 ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
385 {
386 st->SetupArgreader(false);
387 napi_env env = ctx->GetJSEnv();
388
389 napi_value jsVal = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
390
391 auto classQnameOffset = GetClassQnameOffset(ctx, st->GetMethod());
392 auto qnameStart = st->ReadFixedArg<panda_file::Type::TypeId::I32, int32_t>() + classQnameOffset;
393 auto qnameLen = st->ReadFixedArg<panda_file::Type::TypeId::I32, int32_t>();
394 napi_value jsThis {};
395
396 auto success = ctx->GetConstStringStorage()->EnumerateStrings(
397 qnameStart, qnameLen, [&jsThis, &jsVal, env](napi_value jsStr) {
398 jsThis = jsVal;
399
400 if (!NapiGetProperty(env, jsVal, jsStr, &jsVal)) {
401 ASSERT(NapiIsExceptionPending(env));
402 return false;
403 }
404 return true;
405 });
406
407 if (!success) {
408 ASSERT(NapiIsExceptionPending(env));
409 return false;
410 }
411 st->SetupJSCallee(jsThis, jsVal);
412 return true;
413 }
414 };
415 return CallJSHandler::HandleImpl<IS_NEWCALL, ArgSetup>(method, args, inStackArgs);
416 }
417
JSRuntimeCallJS(Method * method,uint8_t * args,uint8_t * inStackArgs)418 extern "C" uint64_t JSRuntimeCallJS(Method *method, uint8_t *args, uint8_t *inStackArgs)
419 {
420 return JSRuntimeCallJSBase<false>(method, args, inStackArgs); // IS_NEWCALL is false
421 }
422 extern "C" void JSRuntimeCallJSBridge(Method *method, ...);
423
JSRuntimeNewCallJS(Method * method,uint8_t * args,uint8_t * inStackArgs)424 extern "C" uint64_t JSRuntimeNewCallJS(Method *method, uint8_t *args, uint8_t *inStackArgs)
425 {
426 return JSRuntimeCallJSBase<true>(method, args, inStackArgs); // IS_NEWCALL is true
427 }
428 extern "C" void JSRuntimeNewCallJSBridge(Method *method, ...);
429
JSRuntimeCallJSByValue(Method * method,uint8_t * args,uint8_t * inStackArgs)430 extern "C" uint64_t JSRuntimeCallJSByValue(Method *method, uint8_t *args, uint8_t *inStackArgs)
431 {
432 struct ArgSetup {
433 ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
434 {
435 st->SetupArgreader(false);
436 napi_env env = ctx->GetJSEnv();
437
438 napi_value jsFn = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
439 napi_value jsThis = JSConvertJSValue::Wrap(env, st->ReadFixedRefArg<JSValue>(ctx->GetJSValueClass()));
440
441 st->SetupJSCallee(jsThis, jsFn);
442 return true;
443 }
444 };
445 return CallJSHandler::HandleImpl<false, ArgSetup>(method, args, inStackArgs);
446 }
447 extern "C" void JSRuntimeCallJSByValueBridge(Method *method, ...);
448
CallJSProxy(Method * method,uint8_t * args,uint8_t * inStackArgs)449 extern "C" uint64_t CallJSProxy(Method *method, uint8_t *args, uint8_t *inStackArgs)
450 {
451 struct ArgSetup {
452 ALWAYS_INLINE bool operator()(InteropCtx *ctx, CallJSHandler *st)
453 {
454 ObjectHeader *etsThis = st->SetupArgreader(true);
455 napi_env env = ctx->GetJSEnv();
456
457 auto refconv = JSRefConvertResolve(ctx, etsThis->ClassAddr<Class>());
458 napi_value jsThis = refconv->Wrap(ctx, EtsObject::FromCoreType(etsThis));
459 ASSERT(GetValueType(env, jsThis) == napi_object);
460 const char *methodName = utf::Mutf8AsCString(st->GetMethod()->GetName().data);
461 napi_value jsFn;
462 if (!NapiGetNamedProperty(env, jsThis, methodName, &jsFn)) {
463 ASSERT(NapiIsExceptionPending(env));
464 return false;
465 }
466
467 st->SetupJSCallee(jsThis, jsFn);
468 return true;
469 }
470 };
471 return CallJSHandler::HandleImpl<false, ArgSetup>(method, args, inStackArgs);
472 }
473
CallJSFunction(Method * method,uint8_t * args,uint8_t * inStackArgs)474 extern "C" uint64_t CallJSFunction(Method *method, uint8_t *args, uint8_t *inStackArgs)
475 {
476 struct ArgSetup {
477 ALWAYS_INLINE bool operator()([[maybe_unused]] InteropCtx *ctx, [[maybe_unused]] CallJSHandler *st)
478 {
479 ObjectHeader *etsThis = st->SetupArgreader(true);
480
481 auto refconv = JSRefConvertResolve(ctx, etsThis->ClassAddr<Class>());
482 napi_value jsCallBackFn = refconv->Wrap(ctx, EtsObject::FromCoreType(etsThis));
483
484 st->SetupJSCallee(jsCallBackFn, jsCallBackFn);
485 return true;
486 }
487 };
488 return CallJSHandler::HandleImpl<false, ArgSetup>(method, args, inStackArgs);
489 }
490
491 extern "C" void CallJSProxyBridge(Method *method, ...);
492
SelectCallJSEntrypoint(InteropCtx * ctx,Method * method)493 static void *SelectCallJSEntrypoint(InteropCtx *ctx, Method *method)
494 {
495 ASSERT(method->IsStatic());
496 ProtoReader protoReader(method, ctx->GetClassLinker(), ctx->LinkerCtx());
497
498 // skip return type
499 protoReader.Advance();
500
501 ASSERT(protoReader.GetClass() == ctx->GetJSValueClass());
502 protoReader.Advance();
503
504 if (protoReader.GetType().IsPrimitive()) {
505 if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
506 return nullptr;
507 }
508 protoReader.Advance();
509
510 if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
511 return nullptr;
512 }
513 return reinterpret_cast<void *>(JSRuntimeCallJSBridge);
514 }
515 if (protoReader.GetClass() == ctx->GetStringClass()) {
516 return reinterpret_cast<void *>(JSRuntimeCallJSQNameBridge);
517 }
518 if (protoReader.GetClass() == ctx->GetJSValueClass()) {
519 return reinterpret_cast<void *>(JSRuntimeCallJSByValueBridge);
520 }
521 InteropFatal("Bad jscall signature");
522 }
523
SelectNewCallJSEntrypoint(InteropCtx * ctx,Method * method)524 static void *SelectNewCallJSEntrypoint(InteropCtx *ctx, Method *method)
525 {
526 ASSERT(method->IsStatic());
527 ProtoReader protoReader(method, ctx->GetClassLinker(), ctx->LinkerCtx());
528
529 if (protoReader.GetClass() != ctx->GetJSValueClass()) {
530 return nullptr;
531 }
532 protoReader.Advance();
533
534 if (protoReader.GetClass() != ctx->GetJSValueClass()) {
535 return nullptr;
536 }
537 protoReader.Advance();
538
539 if (protoReader.GetType().IsPrimitive()) {
540 if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
541 return nullptr;
542 }
543 protoReader.Advance();
544
545 if (protoReader.GetType().GetId() != panda_file::Type::TypeId::I32) {
546 return nullptr;
547 }
548 return reinterpret_cast<void *>(JSRuntimeNewCallJSBridge);
549 }
550 if (protoReader.GetClass() != ctx->GetStringClass()) {
551 return nullptr;
552 }
553 return reinterpret_cast<void *>(JSRuntimeNewCallJSQNameBridge);
554 }
555
GetQnameCount(Class * klass)556 static std::optional<uint32_t> GetQnameCount(Class *klass)
557 {
558 auto pf = klass->GetPandaFile();
559 panda_file::ClassDataAccessor cda(*pf, klass->GetFileId());
560 auto qnameCount =
561 cda.EnumerateAnnotation("Lets/annotation/DynamicCall;", [pf](panda_file::AnnotationDataAccessor &ada) {
562 for (uint32_t i = 0; i < ada.GetCount(); i++) {
563 auto adae = ada.GetElement(i);
564 auto *elemName = pf->GetStringData(adae.GetNameId()).data;
565 if (utf::IsEqual(utf::CStringAsMutf8("value"), elemName)) {
566 return adae.GetArrayValue().GetCount();
567 }
568 }
569 UNREACHABLE();
570 });
571 return qnameCount;
572 }
573
InitCallJSClass(bool isNewCall)574 static uint8_t InitCallJSClass(bool isNewCall)
575 {
576 auto coro = EtsCoroutine::GetCurrent();
577 auto ctx = InteropCtx::Current(coro);
578 auto *klass = GetMethodOwnerClassInFrames(coro, 0)->GetRuntimeClass();
579 INTEROP_LOG(DEBUG) << "Bind bridge call methods for " << utf::Mutf8AsCString(klass->GetDescriptor());
580
581 for (auto &method : klass->GetMethods()) {
582 if (method.IsConstructor()) {
583 continue;
584 }
585 void *ep = nullptr;
586 if (method.IsStatic()) {
587 ep = isNewCall ? SelectNewCallJSEntrypoint(ctx, &method) : SelectCallJSEntrypoint(ctx, &method);
588 }
589 if (ep == nullptr) {
590 InteropFatal("Bad interop call bridge signature");
591 }
592 method.SetCompiledEntryPoint(ep);
593 method.SetNativePointer(nullptr);
594 }
595
596 auto qnameCount = GetQnameCount(klass);
597 // JSCallClass which was manually created in test will not have the required annotation and field
598 if (qnameCount.has_value()) {
599 auto fields = klass->GetStaticFields();
600 INTEROP_FATAL_IF(fields.Size() != 1);
601 INTEROP_FATAL_IF(klass->GetFieldPrimitive<uint32_t>(fields[0]) != 0);
602 auto *stringStorage = ctx->GetConstStringStorage();
603 klass->SetFieldPrimitive<uint32_t>(fields[0], stringStorage->AllocateSlotsInStringBuffer(*qnameCount));
604 }
605 return 1;
606 }
607
JSRuntimeInitJSCallClass()608 uint8_t JSRuntimeInitJSCallClass()
609 {
610 return InitCallJSClass(false);
611 }
612
JSRuntimeInitJSNewClass()613 uint8_t JSRuntimeInitJSNewClass()
614 {
615 return InitCallJSClass(true);
616 }
617
618 } // namespace ark::ets::interop::js
619