From 68f7ccd8d2e26ab2bb22659b408aba78ace8a00b Mon Sep 17 00:00:00 2001 From: Vyacheslav Cherkashin Date: Mon, 29 Jan 2024 15:41:53 +0300 Subject: [PATCH] interop_js: Add rest parametes support Issue: #I91IS7 Testing: CI tests passed Signed-off-by: Vyacheslav Cherkashin Signed-off-by: churkinaleksey --- static_core/assembler/meta.h | 3 +- static_core/assembler/metadata.yaml | 6 + static_core/disassembler/disassembler.cpp | 3 + .../libpandafile/method_data_accessor.h | 5 + .../runtime/interop_js/call/arg_convertors.h | 154 +++++++++++++++--- .../ets/runtime/interop_js/call/call_ets.cpp | 43 ++++- .../runtime/interop_js/call/proto_reader.h | 5 + .../ets_proxy/ets_field_wrapper.cpp | 2 +- .../interop_js/ets_proxy/ets_method_set.h | 15 +- .../interop_js/intrinsics_api_impl.cpp | 43 ++--- .../ets/runtime/interop_js/js_convert.h | 2 +- .../intrinsics/escompat_ArrayBuffer.cpp | 2 +- .../plugins/ets/runtime/types/ets_array.h | 28 ++-- .../interop_js/cmake/interop_js_tests.cmake | 2 +- .../js_suites/test_rest_params_call.js | 34 ++++ .../tests/scenarios/ets_to_js/scenarios.cpp | 5 + .../tests/scenarios/ets_to_js/scenarios.ets | 55 +++++++ .../js_to_ets/js_suites/scenarios.js | 28 ++++ .../tests/scenarios/js_to_ets/scenarios.cpp | 6 + .../tests/scenarios/js_to_ets/scenarios.ets | 14 +- static_core/runtime/include/method.h | 7 + 21 files changed, 386 insertions(+), 76 deletions(-) create mode 100644 static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/js_suites/test_rest_params_call.js diff --git a/static_core/assembler/meta.h b/static_core/assembler/meta.h index b5f5ce2292..54c9ff7474 100644 --- a/static_core/assembler/meta.h +++ b/static_core/assembler/meta.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023 Huawei Device Co., Ltd. + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -200,6 +200,7 @@ public: void SetAnnotations(std::vector &&annotations) { + ASSERT(annotations_.empty()); annotations_ = std::forward>(annotations); } diff --git a/static_core/assembler/metadata.yaml b/static_core/assembler/metadata.yaml index 6e2e7dac30..cfa54b39be 100644 --- a/static_core/assembler/metadata.yaml +++ b/static_core/assembler/metadata.yaml @@ -76,6 +76,12 @@ attributes: applicable_to: - function +- name: varargs + type: bool + flags: [ACC_VARARGS] + applicable_to: + - function + - name: static type: bool flags: [ACC_STATIC] diff --git a/static_core/disassembler/disassembler.cpp b/static_core/disassembler/disassembler.cpp index 6bef7ef11c..b7c10e06d8 100644 --- a/static_core/disassembler/disassembler.cpp +++ b/static_core/disassembler/disassembler.cpp @@ -883,6 +883,9 @@ void Disassembler::GetMetaData(pandasm::Function *method, const panda_file::File SetEntityAttribute( method, [&methodAccessor]() { return methodAccessor.IsAbstract(); }, "noimpl"); + SetEntityAttribute( + method, [&methodAccessor]() { return methodAccessor.IsVarArgs(); }, "varargs"); + SetEntityAttributeValue( method, [&methodAccessor]() { return methodAccessor.IsPublic(); }, "access.function", "public"); diff --git a/static_core/libpandafile/method_data_accessor.h b/static_core/libpandafile/method_data_accessor.h index 3dfbb4f1a6..aa4ae7fc6f 100644 --- a/static_core/libpandafile/method_data_accessor.h +++ b/static_core/libpandafile/method_data_accessor.h @@ -68,6 +68,11 @@ public: return (accessFlags_ & ACC_ABSTRACT) != 0; } + bool IsVarArgs() const + { + return (accessFlags_ & ACC_VARARGS) != 0; + } + bool IsNative() const { return (accessFlags_ & ACC_NATIVE) != 0; diff --git a/static_core/plugins/ets/runtime/interop_js/call/arg_convertors.h b/static_core/plugins/ets/runtime/interop_js/call/arg_convertors.h index 524456870c..2bad3b2ea1 100644 --- a/static_core/plugins/ets/runtime/interop_js/call/arg_convertors.h +++ b/static_core/plugins/ets/runtime/interop_js/call/arg_convertors.h @@ -21,24 +21,34 @@ namespace ark::ets::interop::js { -template -[[nodiscard]] static ALWAYS_INLINE inline bool ConvertRefArgToEts(InteropCtx *ctx, ProtoReader &protoReader, - FStore &storeRes, napi_value jsVal, - FUnwrapVal &unwrapVal) +template +static ALWAYS_INLINE bool UnwrapVal(InteropCtx *ctx, napi_env env, napi_value jsVal, FStore &storeRes) +{ + using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming) + auto res = Convertor::Unwrap(ctx, env, jsVal); + if (UNLIKELY(!res.has_value())) { + return false; + } + if constexpr (std::is_pointer_v) { + storeRes(AsEtsObject(res.value())->GetCoreType()); + } else { + storeRes(Value(res.value()).GetAsLong()); + } + return true; +} + +template +[[nodiscard]] static ALWAYS_INLINE inline bool ConvertRefArgToEts(InteropCtx *ctx, Class *klass, FStore &storeRes, + napi_value jsVal) { auto env = ctx->GetJSEnv(); - if (IsNull(env, jsVal)) { - storeRes(nullptr); - return true; - } - auto klass = protoReader.GetClass(); // start fastpath if (klass == ctx->GetJSValueClass()) { - return unwrapVal(helpers::TypeIdentity()); + return UnwrapVal(ctx, env, jsVal, storeRes); } if (klass == ctx->GetStringClass()) { - return unwrapVal(helpers::TypeIdentity()); + return UnwrapVal(ctx, env, jsVal, storeRes); } if (IsUndefined(env, jsVal)) { if (UNLIKELY(!klass->IsAssignableFrom(ctx->GetUndefinedClass()))) { @@ -58,27 +68,16 @@ template } template -[[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, ProtoReader &protoReader, - FStore &storeRes, napi_value jsVal) +[[nodiscard]] static ALWAYS_INLINE inline bool ConvertPrimArgToEts(InteropCtx *ctx, panda_file::Type::TypeId id, + FStore &storeRes, napi_value jsVal) { auto env = ctx->GetJSEnv(); auto unwrapVal = [&ctx, &env, &jsVal, &storeRes](auto convTag) { using Convertor = typename decltype(convTag)::type; // convTag acts as lambda template parameter - using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming) - auto res = Convertor::Unwrap(ctx, env, jsVal); - if (UNLIKELY(!res.has_value())) { - return false; - } - if constexpr (std::is_pointer_v) { - storeRes(AsEtsObject(res.value())->GetCoreType()); - } else { - storeRes(Value(res.value()).GetAsLong()); - } - return true; + return UnwrapVal(ctx, env, jsVal, storeRes); }; - - switch (protoReader.GetType().GetId()) { + switch (id) { case panda_file::Type::TypeId::VOID: { return true; // do nothing } @@ -104,8 +103,109 @@ template return unwrapVal(helpers::TypeIdentity()); case panda_file::Type::TypeId::F64: return unwrapVal(helpers::TypeIdentity()); + default: + UNREACHABLE(); + } +} + +template +[[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, panda_file::Type type, FStore &storeRes, + const GetClass &getClass, napi_value jsVal) +{ + auto id = type.GetId(); + auto env = ctx->GetJSEnv(); + if (id == panda_file::Type::TypeId::REFERENCE) { + if (IsNull(env, jsVal)) { + storeRes(nullptr); + return true; + } + return ConvertRefArgToEts(ctx, getClass(), storeRes, jsVal); + } + return ConvertPrimArgToEts(ctx, id, storeRes, jsVal); +} + +template +[[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, ProtoReader &protoReader, + FStore &storeRes, napi_value jsVal) +{ + return ConvertArgToEts( + ctx, protoReader.GetType(), storeRes, [&protoReader]() { return protoReader.GetClass(); }, jsVal); +} + +template +static ObjectHeader **DoPackRestParameters(EtsCoroutine *coro, InteropCtx *ctx, ProtoReader &protoReader, + Span jsargv) +{ + const size_t numRestParams = jsargv.size(); + + RestParamsArray *objArr = [&]() { + if constexpr (std::is_same_v) { + EtsClass *etsClass = EtsClass::FromRuntimeClass(protoReader.GetClass()->GetComponentType()); + return RestParamsArray::Create(etsClass, numRestParams); + } else { + return RestParamsArray::Create(numRestParams); + } + }(); + + auto convertValue = [](auto val) -> typename RestParamsArray::ValueType { + constexpr bool IS_VAL_PTR = std::is_pointer_v || std::is_null_pointer_v; + constexpr bool IS_OBJ_ARR = std::is_same_v; + // Clang-tidy gives false positive error. + if constexpr (IS_OBJ_ARR) { + if constexpr (IS_VAL_PTR) { + return EtsObject::FromCoreType(static_cast(val)); + } + } + if constexpr (!IS_OBJ_ARR) { + if constexpr (!IS_VAL_PTR) { + return *reinterpret_cast(&val); + } + } + UNREACHABLE(); + }; + + VMHandle restArgsArray(coro, objArr->GetCoreType()); + for (uint32_t restArgIdx = 0; restArgIdx < numRestParams; ++restArgIdx) { + auto jsVal = jsargv[restArgIdx]; + auto store = [&convertValue, restArgIdx, &restArgsArray](auto val) { + restArgsArray.GetPtr()->Set(restArgIdx, convertValue(val)); + }; + auto klass = protoReader.GetClass()->GetComponentType(); + auto klassCb = [klass]() { return klass; }; + if (UNLIKELY(!ConvertArgToEts(ctx, klass->GetType(), store, klassCb, jsVal))) { + if (coro->HasPendingException()) { + ctx->ForwardEtsException(coro); + } + ASSERT(ctx->SanityJSExceptionPending()); + return nullptr; + } + } + return reinterpret_cast(restArgsArray.GetAddress()); +} + +[[maybe_unused]] static ObjectHeader **PackRestParameters(EtsCoroutine *coro, InteropCtx *ctx, ProtoReader &protoReader, + Span jsargv) +{ + panda_file::Type restParamsItemType = protoReader.GetClass()->GetComponentType()->GetType(); + switch (restParamsItemType.GetId()) { + case panda_file::Type::TypeId::U1: + return DoPackRestParameters(coro, ctx, protoReader, jsargv); + case panda_file::Type::TypeId::I8: + return DoPackRestParameters(coro, ctx, protoReader, jsargv); + case panda_file::Type::TypeId::I16: + return DoPackRestParameters(coro, ctx, protoReader, jsargv); + case panda_file::Type::TypeId::U16: + return DoPackRestParameters(coro, ctx, protoReader, jsargv); + case panda_file::Type::TypeId::I32: + return DoPackRestParameters(coro, ctx, protoReader, jsargv); + case panda_file::Type::TypeId::I64: + return DoPackRestParameters(coro, ctx, protoReader, jsargv); + case panda_file::Type::TypeId::F32: + return DoPackRestParameters(coro, ctx, protoReader, jsargv); + case panda_file::Type::TypeId::F64: + return DoPackRestParameters(coro, ctx, protoReader, jsargv); case panda_file::Type::TypeId::REFERENCE: - return ConvertRefArgToEts(ctx, protoReader, storeRes, jsVal, unwrapVal); + return DoPackRestParameters(coro, ctx, protoReader, jsargv); default: UNREACHABLE(); } diff --git a/static_core/plugins/ets/runtime/interop_js/call/call_ets.cpp b/static_core/plugins/ets/runtime/interop_js/call/call_ets.cpp index 1f2334f03f..937cb6f7b7 100644 --- a/static_core/plugins/ets/runtime/interop_js/call/call_ets.cpp +++ b/static_core/plugins/ets/runtime/interop_js/call/call_ets.cpp @@ -45,6 +45,9 @@ public: private: template ALWAYS_INLINE bool ConvertArgs(Span etsArgs); + ALWAYS_INLINE ObjectHeader **ConvertRestParams(Span restArgs); + + ALWAYS_INLINE bool CheckNumArgs(size_t numArgs) const; template ALWAYS_INLINE napi_value HandleImpl(); @@ -81,11 +84,12 @@ ALWAYS_INLINE inline bool CallETSHandler::ConvertArgs(Span etsArgs) ObjectHeader **thisObjRoot = IS_STATIC ? nullptr : createRoot(thisObj_->GetCoreType()); using ArgValueBox = std::variant; - auto numArgs = jsargv_.size(); + auto const numArgs = protoReader_.GetMethod()->GetNumArgs() - !IS_STATIC; + auto const numNonRest = numArgs - protoReader_.GetMethod()->HasVarArgs(); auto etsBoxedArgs = ctx_->GetTempArgs(numArgs); // Convert and store in root if necessary - for (uint32_t argIdx = 0; argIdx < numArgs; ++argIdx, protoReader_.Advance()) { + for (uint32_t argIdx = 0; argIdx < numNonRest; ++argIdx, protoReader_.Advance()) { auto jsVal = jsargv_[argIdx]; auto store = [&etsBoxedArgs, &argIdx, createRoot](auto val) { if constexpr (std::is_pointer_v || std::is_null_pointer_v) { @@ -99,6 +103,11 @@ ALWAYS_INLINE inline bool CallETSHandler::ConvertArgs(Span etsArgs) } } + if (protoReader_.GetMethod()->HasVarArgs()) { + const auto restIdx = numArgs - 1; + etsBoxedArgs[restIdx] = ConvertRestParams(jsargv_.SubSpan(restIdx)); + } + // Unbox values if constexpr (!IS_STATIC) { etsArgs[0] = Value(*thisObjRoot); @@ -117,6 +126,33 @@ ALWAYS_INLINE inline bool CallETSHandler::ConvertArgs(Span etsArgs) return true; } +ObjectHeader **CallETSHandler::ConvertRestParams(Span restArgs) +{ + ASSERT(protoReader_.GetType().IsReference()); + ASSERT(protoReader_.GetClass()->IsArrayClass()); + + ObjectHeader **restParamsSlot = PackRestParameters(coro_, ctx_, protoReader_, restArgs); + ASSERT(restParamsSlot != nullptr); + + return restParamsSlot; +} + +bool CallETSHandler::CheckNumArgs(size_t numArgs) const +{ + const auto method = protoReader_.GetMethod(); + bool const hasRestParams = method->HasVarArgs(); + ASSERT((hasRestParams && numArgs > 0) || !hasRestParams); + + if ((hasRestParams && (numArgs - 1) > jsargv_.size()) || (!hasRestParams && numArgs != jsargv_.size())) { + std::string msg = "CallEtsFunction: wrong argc, ets_argc=" + std::to_string(numArgs) + + " js_argc=" + std::to_string(jsargv_.size()) + " ets_method='" + + std::string(method->GetFullName(true)) + "'"; + InteropCtx::ThrowJSTypeError(ctx_->GetJSEnv(), msg); + return false; + } + return true; +} + template napi_value CallETSHandler::HandleImpl() { @@ -128,8 +164,7 @@ napi_value CallETSHandler::HandleImpl() ASSERT(IS_STATIC == (thisObj_ == nullptr)); auto const numArgs = method->GetNumArgs() - (IS_STATIC ? 0 : 1); - if (UNLIKELY(numArgs != jsargv_.size())) { - InteropCtx::ThrowJSTypeError(ctx_->GetJSEnv(), std::string("CallEtsFunction: wrong argc")); + if (UNLIKELY(!CheckNumArgs(numArgs))) { return ForwardException(ctx_, coro_); } diff --git a/static_core/plugins/ets/runtime/interop_js/call/proto_reader.h b/static_core/plugins/ets/runtime/interop_js/call/proto_reader.h index a2c571c8a9..bd657d98d6 100644 --- a/static_core/plugins/ets/runtime/interop_js/call/proto_reader.h +++ b/static_core/plugins/ets/runtime/interop_js/call/proto_reader.h @@ -81,6 +81,11 @@ public: return method_; } + ALWAYS_INLINE const Method *GetMethod() const + { + return method_; + } + private: panda_file::ShortyIterator it_ {}; uint32_t refArgIdx_ {}; diff --git a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_field_wrapper.cpp b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_field_wrapper.cpp index 4ecd01153b..e361fff021 100644 --- a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_field_wrapper.cpp +++ b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_field_wrapper.cpp @@ -252,7 +252,7 @@ static napi_property_descriptor DoMakeNapiProperty(EtsFieldWrapper *wrapper) case panda_file::Type::TypeId::REFERENCE: return setupAccessors(helpers::TypeIdentity()); default: - InteropCtx::Fatal(std::string("ConvertEtsVal: unsupported typeid ") + + InteropCtx::Fatal(std::string("DoMakeNapiProperty: unsupported typeid ") + panda_file::Type::GetSignatureByTypeId(type)); } UNREACHABLE(); diff --git a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_method_set.h b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_method_set.h index ef30743bd3..add92a7a6e 100644 --- a/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_method_set.h +++ b/static_core/plugins/ets/runtime/interop_js/ets_proxy/ets_method_set.h @@ -55,9 +55,17 @@ public: ALWAYS_INLINE EtsMethod *GetMethod(uint32_t parametersNum) const { if (LIKELY(parametersNum < entries_.size())) { - return entries_[parametersNum]; + if (LIKELY(entries_[parametersNum] != nullptr)) { + return entries_[parametersNum]; + } } - return nullptr; + // Try rest params + for (size_t params = std::min(static_cast(parametersNum), entries_.size() - 1); params > 0; params--) { + if (entries_[params] != nullptr && entries_[params]->GetPandaMethod()->HasVarArgs()) { + return entries_[params]; + } + } + return entries_.front(); } template @@ -96,7 +104,8 @@ private: entries_(PandaVector(singleMethod->GetParametersNum() + 1)), anyMethod_(singleMethod) { - entries_[singleMethod->GetParametersNum()] = singleMethod; + entries_[singleMethod->GetParametersNum() - + static_cast(singleMethod->GetPandaMethod()->HasVarArgs())] = singleMethod; } EtsClass *const enclosingClass_; diff --git a/static_core/plugins/ets/runtime/interop_js/intrinsics_api_impl.cpp b/static_core/plugins/ets/runtime/interop_js/intrinsics_api_impl.cpp index e9fc03f964..746014dc62 100644 --- a/static_core/plugins/ets/runtime/interop_js/intrinsics_api_impl.cpp +++ b/static_core/plugins/ets/runtime/interop_js/intrinsics_api_impl.cpp @@ -308,32 +308,33 @@ static JSValue *JSRuntimeLoadModule(EtsString *module) PandaString moduleName = module->GetMutf8(); auto [mod, func] = ResolveModuleName(moduleName); - NapiScope jsHandleScope(env); - - napi_value requireFn; - NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), func.data(), &requireFn)); - - INTEROP_FATAL_IF(GetValueType(env, requireFn) != napi_function); napi_value modObj; { - napi_value jsName; - NAPI_CHECK_FATAL(napi_create_string_utf8(env, mod.data(), NAPI_AUTO_LENGTH, &jsName)); - std::array args = {jsName}; - napi_value recv; - NAPI_CHECK_FATAL(napi_get_undefined(env, &recv)); - auto status = (napi_call_function(env, recv, requireFn, args.size(), args.data(), &modObj)); + ScopedNativeCodeThread etsNativeScope(coro); + NapiScope jsHandleScope(env); - if (status == napi_pending_exception) { - napi_value exp; - NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(env, &exp)); - NAPI_CHECK_FATAL(napi_fatal_exception(env, exp)); - INTEROP_LOG(FATAL) << "Unable to load module due to exception"; - UNREACHABLE(); + napi_value requireFn; + NAPI_CHECK_FATAL(napi_get_named_property(env, GetGlobal(env), func.data(), &requireFn)); + + INTEROP_FATAL_IF(GetValueType(env, requireFn) != napi_function); + { + napi_value jsName; + NAPI_CHECK_FATAL(napi_create_string_utf8(env, mod.data(), NAPI_AUTO_LENGTH, &jsName)); + std::array args = {jsName}; + napi_value recv; + NAPI_CHECK_FATAL(napi_get_undefined(env, &recv)); + auto status = (napi_call_function(env, recv, requireFn, args.size(), args.data(), &modObj)); + if (status == napi_pending_exception) { + napi_value exp; + NAPI_CHECK_FATAL(napi_get_and_clear_last_exception(env, &exp)); + NAPI_CHECK_FATAL(napi_fatal_exception(env, exp)); + INTEROP_LOG(FATAL) << "Unable to load module due to exception"; + UNREACHABLE(); + } + INTEROP_FATAL_IF(status != napi_ok); } - - INTEROP_FATAL_IF(status != napi_ok); + INTEROP_FATAL_IF(IsUndefined(env, modObj)); } - INTEROP_FATAL_IF(IsUndefined(env, modObj)); return JSValue::CreateRefValue(coro, ctx, modObj, napi_object); } diff --git a/static_core/plugins/ets/runtime/interop_js/js_convert.h b/static_core/plugins/ets/runtime/interop_js/js_convert.h index bf9399e171..7d20d50a3a 100644 --- a/static_core/plugins/ets/runtime/interop_js/js_convert.h +++ b/static_core/plugins/ets/runtime/interop_js/js_convert.h @@ -412,7 +412,7 @@ JSCONVERT_UNWRAP(ArrayBuffer) EtsHandle buf(currentCoro, reinterpret_cast(EtsObject::Create(currentCoro, arraybufKlass))); buf->SetByteLength(static_cast(byteLength)); - buf->SetData(currentCoro, EtsArray::CreateForPrimitive(EtsClassRoot::BYTE_ARRAY, byteLength)); + buf->SetData(currentCoro, EtsByteArray::Create(byteLength)); std::copy_n(reinterpret_cast(data), byteLength, buf->GetData()->GetData()); return buf.GetPtr(); } diff --git a/static_core/plugins/ets/runtime/intrinsics/escompat_ArrayBuffer.cpp b/static_core/plugins/ets/runtime/intrinsics/escompat_ArrayBuffer.cpp index 5ccbbe33e8..fc75a1f6a7 100644 --- a/static_core/plugins/ets/runtime/intrinsics/escompat_ArrayBuffer.cpp +++ b/static_core/plugins/ets/runtime/intrinsics/escompat_ArrayBuffer.cpp @@ -45,7 +45,7 @@ extern "C" ObjectHeader *EtsArrayBufferFrom(EtsObject *obj) } auto byteLength = static_cast(array->GetLength() * array->GetElementSize()); buf->SetByteLength(byteLength); - auto *data = EtsArray::CreateForPrimitive(EtsClassRoot::BYTE_ARRAY, byteLength); + auto *data = EtsByteArray::Create(byteLength); buf->SetData(coro, data); if (UNLIKELY(buf->GetData() == nullptr)) { return nullptr; diff --git a/static_core/plugins/ets/runtime/types/ets_array.h b/static_core/plugins/ets/runtime/types/ets_array.h index ded9dc9856..f62f1de044 100644 --- a/static_core/plugins/ets/runtime/types/ets_array.h +++ b/static_core/plugins/ets/runtime/types/ets_array.h @@ -85,19 +85,6 @@ public: return reinterpret_cast(this); } - template - static T *Create(EtsClass *arrayClass, uint32_t length, SpaceType spaceType = SpaceType::SPACE_TYPE_OBJECT) - { - return reinterpret_cast(coretypes::Array::Create(arrayClass->GetRuntimeClass(), length, spaceType)); - } - - template - static T *CreateForPrimitive(EtsClassRoot root, uint32_t length, SpaceType spaceType = SpaceType::SPACE_TYPE_OBJECT) - { - EtsClass *arrayClass = PandaEtsVM::GetCurrent()->GetClassLinker()->GetClassRoot(root); - return Create(arrayClass, length, spaceType); - } - NO_COPY_SEMANTIC(EtsArray); NO_MOVE_SEMANTIC(EtsArray); @@ -105,6 +92,12 @@ protected: // Use type alias to allow using into derived classes using ObjectHeader = ::ark::ObjectHeader; + template + static T *Create(EtsClass *arrayClass, uint32_t length, SpaceType spaceType = SpaceType::SPACE_TYPE_OBJECT) + { + return reinterpret_cast(coretypes::Array::Create(arrayClass->GetRuntimeClass(), length, spaceType)); + } + template void SetImpl(uint32_t idx, T elem) { @@ -121,6 +114,8 @@ protected: template class EtsTypedObjectArray : public EtsArray { public: + using ValueType = Component *; + static EtsTypedObjectArray *Create(EtsClass *objectClass, uint32_t length, ark::SpaceType spaceType = ark::SpaceType::SPACE_TYPE_OBJECT) { @@ -219,8 +214,7 @@ public: static EtsPrimitiveArray *Create(uint32_t length, SpaceType spaceType = SpaceType::SPACE_TYPE_OBJECT) { ASSERT_HAVE_ACCESS_TO_MANAGED_OBJECTS(); - // NOLINTNEXTLINE(readability-magic-numbers) - return EtsArray::CreateForPrimitive(ETS_CLASS_ROOT, length, spaceType); + return EtsArray::Create(GetComponentClass(), length, spaceType); } void Set(uint32_t index, ClassType element) { @@ -230,6 +224,10 @@ public: { return GetImpl(index); } + static EtsClass *GetComponentClass() + { + return PandaEtsVM::GetCurrent()->GetClassLinker()->GetClassRoot(ETS_CLASS_ROOT); + } EtsPrimitiveArray() = delete; ~EtsPrimitiveArray() = delete; diff --git a/static_core/plugins/ets/tests/interop_js/cmake/interop_js_tests.cmake b/static_core/plugins/ets/tests/interop_js/cmake/interop_js_tests.cmake index 3916b47d31..edc86da09f 100644 --- a/static_core/plugins/ets/tests/interop_js/cmake/interop_js_tests.cmake +++ b/static_core/plugins/ets/tests/interop_js/cmake/interop_js_tests.cmake @@ -52,7 +52,7 @@ function(panda_ets_interop_js_gtest TARGET) set(INTEROP_TESTS_DIR "${PANDA_BINARY_ROOT}/tests/ets_interop_js") panda_ets_interop_js_plugin(${TARGET} SOURCES ${ARG_CPP_SOURCES} - LIBRARIES ets_interop_js_gtest ${ARG_LIBRARIES} + LIBRARIES ets_interop_js_gtest ets_interop_js_napi ${ARG_LIBRARIES} LIBRARY_OUTPUT_DIRECTORY "${INTEROP_TESTS_DIR}/lib/module" ) diff --git a/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/js_suites/test_rest_params_call.js b/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/js_suites/test_rest_params_call.js new file mode 100644 index 0000000000..d75ef6e99b --- /dev/null +++ b/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/js_suites/test_rest_params_call.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const { etsVm, getTestModule } = require('scenarios.test.js'); + +const etsMod = getTestModule('scenarios_test'); + +let ets_sum_rest_params = etsMod.getFunction("ets_sum_rest_params"); +let ets_multiply_1arg_by_sum_rest_params = etsMod.getFunction("ets_multiply_1arg_by_sum_rest_params"); +let ets_multiply_sum2args_by_sum_rest_params = etsMod.getFunction("ets_multiply_sum2args_by_sum_rest_params"); +let ets_concat_strings_rest_params = etsMod.getFunction("ets_concat_strings_rest_params"); +let ets_method_rest_params = etsMod.getFunction("ets_call_foo_rest_params"); +let F = etsMod.getClass("RestParamsTest"); +{ + ASSERT_EQ(ets_sum_rest_params(1, 2, 3), (1 + 2 + 3)); + ASSERT_EQ(ets_multiply_1arg_by_sum_rest_params(1, 2, 3, 4), (1)*(2+3+4)); + ASSERT_EQ(ets_multiply_sum2args_by_sum_rest_params(1, 2, 3, 4, 5), (1+2)*(3+4+5)); + ASSERT_EQ(ets_concat_strings_rest_params(), ""); + ASSERT_EQ(ets_concat_strings_rest_params('a', 'b', 'c', 'd'), "abcd"); + ASSERT_EQ(ets_method_rest_params(new F(), new F(), new F()), 9); + let foo = new F(); + ASSERT_EQ(foo.sum_ints(1, 2, 3), (1 + 2 + 3)); +} diff --git a/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/scenarios.cpp b/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/scenarios.cpp index 98cec78586..43a1c4bbb0 100644 --- a/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/scenarios.cpp +++ b/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/scenarios.cpp @@ -173,4 +173,9 @@ TEST_F(EtsInteropScenariosEtsToJs, test_function_return_type_primitive) ASSERT_EQ(true, RunJsTestSuite("js_suites/test_function_return_type_primitive.js")); } +TEST_F(EtsInteropScenariosEtsToJs, test_rest_params) +{ + ASSERT_EQ(true, RunJsTestSuite("js_suites/test_rest_params_call.js")); +} + } // namespace ark::ets::interop::js::testing diff --git a/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/scenarios.ets b/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/scenarios.ets index 55b5b4ee14..aa6cea2e87 100644 --- a/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/scenarios.ets +++ b/static_core/plugins/ets/tests/interop_js/tests/scenarios/ets_to_js/scenarios.ets @@ -174,6 +174,61 @@ function function_return_type_primitive(): number { return 1; } +function ets_sum_rest_params(...args: number[]): number { + let sum = 0; + for (let n of args) { + sum += n; + } + return sum; +} + +function ets_multiply_1arg_by_sum_rest_params(arg0: number, ...args: number[]): number { + let sum = 0; + for (let n of args) { + sum += n; + } + return arg0 * sum; +} + +function ets_multiply_sum2args_by_sum_rest_params(arg0: number, arg1: number, ...args: number[]): number { + let sum = 0; + for (let n of args) { + sum += n; + } + return sum * (arg0 + arg1); +} + +class RestParamsTest { + x = 2; + foo() { + return 1; + } + sum_ints(...args: Int[]) { + let sum = 0; + for (let n of args) { + sum += n; + } + return sum; + } +}; + +function ets_call_foo_rest_params(...args: RestParamsTest[]): number { + let sum = 0; + for (let s of args) { + sum += s.foo(); + sum += s.x; + } + return sum; +} + +function ets_concat_strings_rest_params(...args: String[]): String { + let str = ""; + for (let s of args) { + str += s; + } + return str; +} + function GCJSRuntimeCleanup(): void { try { // trigger FinalizationRegistry cleanup diff --git a/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/js_suites/scenarios.js b/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/js_suites/scenarios.js index 0cc995e888..f1cd6dc70a 100644 --- a/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/js_suites/scenarios.js +++ b/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/js_suites/scenarios.js @@ -176,6 +176,30 @@ function single_required(z, x=123, y=123) { return x + y + z; } +function js_sum_rest_params(...args) { + let sum = 0; + args.forEach(n => sum += n); + return sum; +} + +function js_multiply_1arg_by_sum_rest_params(arg0, ...args) { + let sum = 0; + args.forEach(n => sum += n); + return sum * (arg0); +} + +function js_multiply_sum2args_by_sum_rest_params(arg0, arg1, ...args) { + let sum = 0; + args.forEach(n => sum += n); + return sum * (arg0 + arg1); +} + +function js_concat_strings_rest_params(...args) { + let str = ""; + args.forEach(s => str += s); + return str; +} + exports.standaloneFunctionJs = standaloneFunctionJs; exports.ClassWithMethodJs = ClassWithMethodJs; exports.newInterfaceWithMethod = newInterfaceWithMethod; @@ -202,3 +226,7 @@ exports.functionArgTypePrimitive = functionArgTypePrimitive; exports.functionReturnTypePrimitive = functionReturnTypePrimitive; exports.optional_call = optional_call; exports.single_required = single_required; +exports.js_sum_rest_params = js_sum_rest_params; +exports.js_multiply_1arg_by_sum_rest_params = js_multiply_1arg_by_sum_rest_params; +exports.js_multiply_sum2args_by_sum_rest_params = js_multiply_sum2args_by_sum_rest_params; +exports.js_concat_strings_rest_params = js_concat_strings_rest_params; diff --git a/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/scenarios.cpp b/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/scenarios.cpp index 5dabc82df8..d750f74e12 100644 --- a/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/scenarios.cpp +++ b/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/scenarios.cpp @@ -168,4 +168,10 @@ TEST_F(EtsInteropScenariosJsToEts, Test_optional_call) ASSERT_EQ(ret, true); } +TEST_F(EtsInteropScenariosJsToEts, Test_rest_params) +{ + auto ret = CallEtsMethod("Test_rest_params"); + ASSERT_EQ(ret, true); +} + } // namespace ark::ets::interop::js::testing diff --git a/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/scenarios.ets b/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/scenarios.ets index 6378c51e83..6c671804ff 100644 --- a/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/scenarios.ets +++ b/static_core/plugins/ets/tests/interop_js/tests/scenarios/js_to_ets/scenarios.ets @@ -18,7 +18,9 @@ import { standaloneFunctionJs, ClassWithMethodJs, newInterfaceWithMethod, ClassW functionArgTypeCallable, functionDefaultParameterFunction, functionDefaultIntParameterFunction, functionDefaultStringParameterFunction, functionDefaultFloatParameterFunction, genericTypeParameter, genericTypeReturnValue, functionArgTypeOptionalPrimitive, functionArgTypePrimitive, - functionReturnTypePrimitive } from "pure_js" + functionReturnTypePrimitive, js_sum_rest_params, + js_multiply_1arg_by_sum_rest_params, js_multiply_sum2args_by_sum_rest_params, + js_concat_strings_rest_params } from "pure_js" import { optional_call, @@ -161,3 +163,13 @@ function Test_function_return_type_primitive(): boolean { let res: boolean = functionReturnTypePrimitive() as boolean; return typeof res === "boolean"; } + +function Test_rest_params() { + // NOTE: casts due to bug in frontend + assert js_sum_rest_params(1, 2, 3) as Int == (1+2+3) + assert js_multiply_1arg_by_sum_rest_params(1, 2, 3, 4) as Int == (1) * (2+3+4) + assert js_multiply_sum2args_by_sum_rest_params(1, 2, 3, 4, 5) as Int == (1+2)*(3+4+5); + assert js_concat_strings_rest_params() as String == ""; + assert js_concat_strings_rest_params("a", "b", "c") as String == "abc"; + return true; +} diff --git a/static_core/runtime/include/method.h b/static_core/runtime/include/method.h index 06ea880268..51d0178cd6 100644 --- a/static_core/runtime/include/method.h +++ b/static_core/runtime/include/method.h @@ -557,6 +557,13 @@ public: return (accessFlags_.load(std::memory_order_acquire) & ACC_SYNCHRONIZED) != 0; } + bool HasVarArgs() const + { + // Atomic with acquire order reason: data race with access_flags_ with dependecies on reads after the load which + // should become visible + return (accessFlags_.load(std::memory_order_acquire) & ACC_VARARGS) != 0; + } + bool HasSingleImplementation() const { // Atomic with acquire order reason: data race with access_flags_ with dependecies on reads after the load which