1 /**
2 * Copyright (c) 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 #ifndef PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_CALL_ARG_CONVERTORS_H
17 #define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_CALL_ARG_CONVERTORS_H
18
19 #include "plugins/ets/runtime/interop_js/call/proto_reader.h"
20 #include "plugins/ets/runtime/interop_js/js_convert.h"
21
22 namespace ark::ets::interop::js {
23
24 template <typename Convertor, typename FStore>
UnwrapVal(InteropCtx * ctx,napi_env env,napi_value jsVal,FStore & storeRes)25 static ALWAYS_INLINE bool UnwrapVal(InteropCtx *ctx, napi_env env, napi_value jsVal, FStore &storeRes)
26 {
27 using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming)
28 auto res = Convertor::Unwrap(ctx, env, jsVal);
29 if (UNLIKELY(!res.has_value())) {
30 return false;
31 }
32 if constexpr (std::is_pointer_v<cpptype>) {
33 storeRes(AsEtsObject(res.value())->GetCoreType());
34 } else {
35 storeRes(Value(res.value()).GetAsLong());
36 }
37 return true;
38 }
39
40 template <typename FStore>
ConvertRefArgToEts(InteropCtx * ctx,Class * klass,FStore & storeRes,napi_value jsVal)41 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertRefArgToEts(InteropCtx *ctx, Class *klass, FStore &storeRes,
42 napi_value jsVal)
43 {
44 auto env = ctx->GetJSEnv();
45
46 // start fastpath
47 if (klass == ctx->GetJSValueClass()) {
48 return UnwrapVal<JSConvertJSValue>(ctx, env, jsVal, storeRes);
49 }
50 if (klass == ctx->GetStringClass()) {
51 return UnwrapVal<JSConvertString>(ctx, env, jsVal, storeRes);
52 }
53 if (IsUndefined(env, jsVal)) {
54 if (UNLIKELY(!klass->IsAssignableFrom(ctx->GetUndefinedClass()))) {
55 return false;
56 }
57 storeRes(ctx->GetUndefinedObject()->GetCoreType());
58 return true;
59 }
60 // start slowpath
61 auto refconv = JSRefConvertResolve<true>(ctx, klass);
62 if (UNLIKELY(refconv == nullptr)) {
63 return false;
64 }
65 ObjectHeader *res = refconv->Unwrap(ctx, jsVal)->GetCoreType();
66 storeRes(res);
67 return res != nullptr;
68 }
69
70 template <typename FStore>
ConvertPrimArgToEts(InteropCtx * ctx,panda_file::Type::TypeId id,FStore & storeRes,napi_value jsVal)71 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertPrimArgToEts(InteropCtx *ctx, panda_file::Type::TypeId id,
72 FStore &storeRes, napi_value jsVal)
73 {
74 auto env = ctx->GetJSEnv();
75
76 auto unwrapVal = [&ctx, &env, &jsVal, &storeRes](auto convTag) {
77 using Convertor = typename decltype(convTag)::type; // convTag acts as lambda template parameter
78 return UnwrapVal<Convertor>(ctx, env, jsVal, storeRes);
79 };
80 switch (id) {
81 case panda_file::Type::TypeId::VOID: {
82 return true; // do nothing
83 }
84 case panda_file::Type::TypeId::U1:
85 return unwrapVal(helpers::TypeIdentity<JSConvertU1>());
86 case panda_file::Type::TypeId::I8:
87 return unwrapVal(helpers::TypeIdentity<JSConvertI8>());
88 case panda_file::Type::TypeId::U8:
89 return unwrapVal(helpers::TypeIdentity<JSConvertU8>());
90 case panda_file::Type::TypeId::I16:
91 return unwrapVal(helpers::TypeIdentity<JSConvertI16>());
92 case panda_file::Type::TypeId::U16:
93 return unwrapVal(helpers::TypeIdentity<JSConvertU16>());
94 case panda_file::Type::TypeId::I32:
95 return unwrapVal(helpers::TypeIdentity<JSConvertI32>());
96 case panda_file::Type::TypeId::U32:
97 return unwrapVal(helpers::TypeIdentity<JSConvertU32>());
98 case panda_file::Type::TypeId::I64:
99 return unwrapVal(helpers::TypeIdentity<JSConvertI64>());
100 case panda_file::Type::TypeId::U64:
101 return unwrapVal(helpers::TypeIdentity<JSConvertU64>());
102 case panda_file::Type::TypeId::F32:
103 return unwrapVal(helpers::TypeIdentity<JSConvertF32>());
104 case panda_file::Type::TypeId::F64:
105 return unwrapVal(helpers::TypeIdentity<JSConvertF64>());
106 default:
107 UNREACHABLE();
108 }
109 }
110
111 template <typename FStore, typename GetClass>
ConvertArgToEts(InteropCtx * ctx,panda_file::Type type,FStore & storeRes,const GetClass & getClass,napi_value jsVal)112 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, panda_file::Type type, FStore &storeRes,
113 const GetClass &getClass, napi_value jsVal)
114 {
115 auto id = type.GetId();
116 auto env = ctx->GetJSEnv();
117 if (id == panda_file::Type::TypeId::REFERENCE) {
118 if (IsNull(env, jsVal)) {
119 storeRes(nullptr);
120 return true;
121 }
122 return ConvertRefArgToEts(ctx, getClass(), storeRes, jsVal);
123 }
124 return ConvertPrimArgToEts(ctx, id, storeRes, jsVal);
125 }
126
127 template <typename FStore>
ConvertArgToEts(InteropCtx * ctx,ProtoReader & protoReader,FStore & storeRes,napi_value jsVal)128 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToEts(InteropCtx *ctx, ProtoReader &protoReader,
129 FStore &storeRes, napi_value jsVal)
130 {
131 return ConvertArgToEts(
132 ctx, protoReader.GetType(), storeRes, [&protoReader]() { return protoReader.GetClass(); }, jsVal);
133 }
134
135 template <typename RestParamsArray>
DoPackRestParameters(EtsCoroutine * coro,InteropCtx * ctx,ProtoReader & protoReader,Span<napi_value> jsargv)136 static ObjectHeader **DoPackRestParameters(EtsCoroutine *coro, InteropCtx *ctx, ProtoReader &protoReader,
137 Span<napi_value> jsargv)
138 {
139 const size_t numRestParams = jsargv.size();
140
141 RestParamsArray *objArr = [&]() {
142 if constexpr (std::is_same_v<RestParamsArray, EtsObjectArray>) {
143 EtsClass *etsClass = EtsClass::FromRuntimeClass(protoReader.GetClass()->GetComponentType());
144 return RestParamsArray::Create(etsClass, numRestParams);
145 } else {
146 return RestParamsArray::Create(numRestParams);
147 }
148 }();
149
150 auto convertValue = [](auto val) -> typename RestParamsArray::ValueType {
151 constexpr bool IS_VAL_PTR = std::is_pointer_v<decltype(val)> || std::is_null_pointer_v<decltype(val)>;
152 constexpr bool IS_OBJ_ARR = std::is_same_v<RestParamsArray, EtsObjectArray>;
153 // Clang-tidy gives false positive error.
154 if constexpr (IS_OBJ_ARR) {
155 if constexpr (IS_VAL_PTR) {
156 return EtsObject::FromCoreType(static_cast<ObjectHeader *>(val));
157 }
158 }
159 if constexpr (!IS_OBJ_ARR) {
160 if constexpr (!IS_VAL_PTR) {
161 return *reinterpret_cast<typename RestParamsArray::ValueType *>(&val);
162 }
163 }
164 UNREACHABLE();
165 };
166
167 VMHandle<RestParamsArray> restArgsArray(coro, objArr->GetCoreType());
168 for (uint32_t restArgIdx = 0; restArgIdx < numRestParams; ++restArgIdx) {
169 auto jsVal = jsargv[restArgIdx];
170 auto store = [&convertValue, restArgIdx, &restArgsArray](auto val) {
171 restArgsArray.GetPtr()->Set(restArgIdx, convertValue(val));
172 };
173 auto klass = protoReader.GetClass()->GetComponentType();
174 auto klassCb = [klass]() { return klass; };
175 if (UNLIKELY(!ConvertArgToEts(ctx, klass->GetType(), store, klassCb, jsVal))) {
176 if (coro->HasPendingException()) {
177 ctx->ForwardEtsException(coro);
178 }
179 ASSERT(ctx->SanityJSExceptionPending());
180 return nullptr;
181 }
182 }
183 return reinterpret_cast<ObjectHeader **>(restArgsArray.GetAddress());
184 }
185
PackRestParameters(EtsCoroutine * coro,InteropCtx * ctx,ProtoReader & protoReader,Span<napi_value> jsargv)186 [[maybe_unused]] static ObjectHeader **PackRestParameters(EtsCoroutine *coro, InteropCtx *ctx, ProtoReader &protoReader,
187 Span<napi_value> jsargv)
188 {
189 panda_file::Type restParamsItemType = protoReader.GetClass()->GetComponentType()->GetType();
190 switch (restParamsItemType.GetId()) {
191 case panda_file::Type::TypeId::U1:
192 return DoPackRestParameters<EtsBooleanArray>(coro, ctx, protoReader, jsargv);
193 case panda_file::Type::TypeId::I8:
194 return DoPackRestParameters<EtsByteArray>(coro, ctx, protoReader, jsargv);
195 case panda_file::Type::TypeId::I16:
196 return DoPackRestParameters<EtsShortArray>(coro, ctx, protoReader, jsargv);
197 case panda_file::Type::TypeId::U16:
198 return DoPackRestParameters<EtsCharArray>(coro, ctx, protoReader, jsargv);
199 case panda_file::Type::TypeId::I32:
200 return DoPackRestParameters<EtsIntArray>(coro, ctx, protoReader, jsargv);
201 case panda_file::Type::TypeId::I64:
202 return DoPackRestParameters<EtsLongArray>(coro, ctx, protoReader, jsargv);
203 case panda_file::Type::TypeId::F32:
204 return DoPackRestParameters<EtsFloatArray>(coro, ctx, protoReader, jsargv);
205 case panda_file::Type::TypeId::F64:
206 return DoPackRestParameters<EtsDoubleArray>(coro, ctx, protoReader, jsargv);
207 case panda_file::Type::TypeId::REFERENCE:
208 return DoPackRestParameters<EtsObjectArray>(coro, ctx, protoReader, jsargv);
209 default:
210 UNREACHABLE();
211 }
212 }
213
214 template <typename FRead>
ConvertRefArgToJS(InteropCtx * ctx,napi_value * resSlot,FRead & readVal)215 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertRefArgToJS(InteropCtx *ctx, napi_value *resSlot, FRead &readVal)
216 {
217 auto env = ctx->GetJSEnv();
218 auto setResult = [resSlot](napi_value res) {
219 *resSlot = res;
220 return res != nullptr;
221 };
222 auto wrapRef = [&env, setResult](auto convTag, ObjectHeader *ref) -> bool {
223 using Convertor = typename decltype(convTag)::type; // conv_tag acts as lambda template parameter
224 using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming)
225 cpptype value = std::remove_pointer_t<cpptype>::FromEtsObject(EtsObject::FromCoreType(ref));
226 return setResult(Convertor::Wrap(env, value));
227 };
228
229 ObjectHeader *ref = readVal(helpers::TypeIdentity<ObjectHeader *>());
230 if (UNLIKELY(ref == nullptr)) {
231 *resSlot = GetNull(env);
232 return true;
233 }
234 if (UNLIKELY(ref == ctx->GetUndefinedObject()->GetCoreType())) {
235 *resSlot = GetUndefined(env);
236 return true;
237 }
238
239 auto klass = ref->template ClassAddr<Class>();
240 // start fastpath
241 if (klass == ctx->GetJSValueClass()) {
242 return wrapRef(helpers::TypeIdentity<JSConvertJSValue>(), ref);
243 }
244 if (klass == ctx->GetStringClass()) {
245 return wrapRef(helpers::TypeIdentity<JSConvertString>(), ref);
246 }
247 // start slowpath
248 auto refconv = JSRefConvertResolve(ctx, klass);
249 return setResult(refconv->Wrap(ctx, EtsObject::FromCoreType(ref)));
250 }
251
252 template <typename FRead>
ConvertArgToJS(InteropCtx * ctx,ProtoReader & protoReader,napi_value * resSlot,FRead & readVal)253 [[nodiscard]] static ALWAYS_INLINE inline bool ConvertArgToJS(InteropCtx *ctx, ProtoReader &protoReader,
254 napi_value *resSlot, FRead &readVal)
255 {
256 auto env = ctx->GetJSEnv();
257
258 auto wrapPrim = [&env, &readVal, resSlot](auto convTag) -> bool {
259 using Convertor = typename decltype(convTag)::type; // convTag acts as lambda template parameter
260 using cpptype = typename Convertor::cpptype; // NOLINT(readability-identifier-naming)
261 napi_value res = Convertor::Wrap(env, readVal(helpers::TypeIdentity<cpptype>()));
262 *resSlot = res;
263 return res != nullptr;
264 };
265
266 switch (protoReader.GetType().GetId()) {
267 case panda_file::Type::TypeId::VOID: {
268 *resSlot = GetUndefined(env);
269 return true;
270 }
271 case panda_file::Type::TypeId::U1:
272 return wrapPrim(helpers::TypeIdentity<JSConvertU1>());
273 case panda_file::Type::TypeId::I8:
274 return wrapPrim(helpers::TypeIdentity<JSConvertI8>());
275 case panda_file::Type::TypeId::U8:
276 return wrapPrim(helpers::TypeIdentity<JSConvertU8>());
277 case panda_file::Type::TypeId::I16:
278 return wrapPrim(helpers::TypeIdentity<JSConvertI16>());
279 case panda_file::Type::TypeId::U16:
280 return wrapPrim(helpers::TypeIdentity<JSConvertU16>());
281 case panda_file::Type::TypeId::I32:
282 return wrapPrim(helpers::TypeIdentity<JSConvertI32>());
283 case panda_file::Type::TypeId::U32:
284 return wrapPrim(helpers::TypeIdentity<JSConvertU32>());
285 case panda_file::Type::TypeId::I64:
286 return wrapPrim(helpers::TypeIdentity<JSConvertI64>());
287 case panda_file::Type::TypeId::U64:
288 return wrapPrim(helpers::TypeIdentity<JSConvertU64>());
289 case panda_file::Type::TypeId::F32:
290 return wrapPrim(helpers::TypeIdentity<JSConvertF32>());
291 case panda_file::Type::TypeId::F64:
292 return wrapPrim(helpers::TypeIdentity<JSConvertF64>());
293 case panda_file::Type::TypeId::REFERENCE:
294 return ConvertRefArgToJS(ctx, resSlot, readVal);
295 default:
296 UNREACHABLE();
297 }
298 }
299
300 } // namespace ark::ets::interop::js
301
302 #endif // PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_CALL_ARG_CONVERTORS_H
303