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 <node_api.h>
17 #include "plugins/ets/runtime/ets_panda_file_items.h"
18 #include "plugins/ets/runtime/ets_vm_api.h"
19 #include "plugins/ets/runtime/interop_js/interop_context.h"
20 #include "plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h"
21 #include "plugins/ets/runtime/interop_js/event_loop_module.h"
22 #include "plugins/ets/runtime/interop_js/call/call.h"
23 #include "plugins/ets/runtime/interop_js/interop_common.h"
24 #include "plugins/ets/runtime/interop_js/ts2ets_copy.h"
25 #include "plugins/ets/runtime/interop_js/code_scopes.h"
26 #include "plugins/ets/runtime/interop_js/timer_module.h"
27 #include "generated/base_options.h"
28 #include "compiler_options.h"
29 #include "compiler/compiler_logger.h"
30 #include "interop_js/napi_impl/napi_impl.h"
31
32 namespace ark::ets::interop::js {
33
Version(napi_env env,napi_callback_info info)34 static napi_value Version(napi_env env, [[maybe_unused]] napi_callback_info info)
35 {
36 ASSERT_SCOPED_NATIVE_CODE();
37 constexpr std::string_view MSG = "0.1";
38
39 napi_value result;
40 [[maybe_unused]] napi_status status = napi_create_string_utf8(env, MSG.data(), MSG.size(), &result);
41 ASSERT(status == napi_ok);
42
43 return result;
44 }
45
Fatal(napi_env env,napi_callback_info info)46 static napi_value Fatal([[maybe_unused]] napi_env env, [[maybe_unused]] napi_callback_info info)
47 {
48 [[maybe_unused]] JSNapiEnvScope napiScope(InteropCtx::Current(), env);
49 InteropCtx::Fatal("etsVm.Fatal");
50 }
51
GetEtsFunction(napi_env env,napi_callback_info info)52 static napi_value GetEtsFunction(napi_env env, napi_callback_info info)
53 {
54 ASSERT_SCOPED_NATIVE_CODE();
55
56 size_t jsArgc = 0;
57 NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, nullptr, nullptr, nullptr));
58 if (jsArgc != 2U && jsArgc != 1U) {
59 InteropCtx::ThrowJSError(env, "GetEtsFunction: bad args, actual args count: " + std::to_string(jsArgc));
60 return nullptr;
61 }
62
63 std::array<napi_value, 2U> jsArgv {};
64 NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, jsArgv.data(), nullptr, nullptr));
65
66 napi_value jsFunctionName = jsArgc == 1 ? jsArgv[0] : jsArgv[1];
67
68 std::string packageName;
69 if (jsArgc == 2U) {
70 napi_value jsPackageName = jsArgv[0];
71 if (GetValueType(env, jsPackageName) != napi_string) {
72 InteropCtx::ThrowJSError(env, "GetEtsFunction: package name is not a string");
73 }
74 packageName = GetString(env, jsPackageName);
75 }
76
77 if (GetValueType(env, jsFunctionName) != napi_string) {
78 InteropCtx::ThrowJSError(env, "GetEtsFunction: function name is not a string");
79 return nullptr;
80 }
81
82 std::string methodName = GetString(env, jsFunctionName);
83
84 return ets_proxy::GetETSFunction(env, packageName, methodName);
85 }
86
GetEtsClass(napi_env env,napi_callback_info info)87 static napi_value GetEtsClass(napi_env env, napi_callback_info info)
88 {
89 ASSERT_SCOPED_NATIVE_CODE();
90
91 size_t jsArgc = 0;
92 NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, nullptr, nullptr, nullptr));
93
94 if (jsArgc != 1) {
95 InteropCtx::ThrowJSError(env, "GetEtsClass: bad args, actual args count: " + std::to_string(jsArgc));
96 return nullptr;
97 }
98
99 napi_value jsClassDescriptor {};
100 ASSERT(jsArgc == 1);
101 NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, &jsClassDescriptor, nullptr, nullptr));
102
103 std::string classDescriptor = GetString(env, jsClassDescriptor);
104 return ets_proxy::GetETSClass(env, classDescriptor);
105 }
106
CallEtsFunctionImpl(EtsCoroutine * coro,InteropCtx * ctx,Span<napi_value> jsargv)107 static napi_value CallEtsFunctionImpl(EtsCoroutine *coro, InteropCtx *ctx, Span<napi_value> jsargv)
108 {
109 auto env = ctx->GetJSEnv();
110
111 if (UNLIKELY(jsargv.Empty())) {
112 InteropCtx::ThrowJSError(env, "CallEtsFunction: method name required");
113 return nullptr;
114 }
115 if (UNLIKELY(GetValueType(env, jsargv[0]) != napi_string)) {
116 InteropCtx::ThrowJSError(env, "CallEtsFunction: method name is not a string");
117 return nullptr;
118 }
119
120 auto callTarget = GetString(env, jsargv[0]);
121 INTEROP_LOG(DEBUG) << "CallEtsFunction: method name: " << callTarget;
122
123 auto methodRes = ResolveEntryPoint(ctx, callTarget);
124 if (UNLIKELY(!methodRes)) {
125 InteropCtx::ThrowJSError(env, "CallEtsFunction: " + callTarget + " " + methodRes.Error());
126 return nullptr;
127 }
128 return CallETSStatic(coro, ctx, methodRes.Value(), jsargv.SubSpan(1));
129 }
130
Call(napi_env env,napi_callback_info info)131 static napi_value Call(napi_env env, napi_callback_info info)
132 {
133 auto coro = EtsCoroutine::GetCurrent();
134 auto ctx = InteropCtx::Current(coro);
135 INTEROP_CODE_SCOPE_JS(coro, env);
136
137 size_t argc = 0;
138 [[maybe_unused]] napi_status status = napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr);
139 ASSERT(status == napi_ok);
140
141 auto argv = ctx->GetTempArgs<napi_value>(argc);
142 napi_value thisArg {};
143 void *data = nullptr;
144 status = napi_get_cb_info(env, info, &argc, argv->data(), &thisArg, &data);
145 ASSERT(status == napi_ok);
146
147 return CallEtsFunctionImpl(coro, ctx, *argv);
148 }
149
CallWithCopy(napi_env env,napi_callback_info info)150 static napi_value CallWithCopy(napi_env env, napi_callback_info info)
151 {
152 ASSERT_SCOPED_NATIVE_CODE();
153
154 size_t argc = 0;
155 [[maybe_unused]] napi_status status = napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr);
156 ASSERT(status == napi_ok);
157
158 auto coro = EtsCoroutine::GetCurrent();
159 auto argv = InteropCtx::Current(coro)->GetTempArgs<napi_value>(argc);
160 napi_value thisArg {};
161 void *data = nullptr;
162 status = napi_get_cb_info(env, info, &argc, argv->data(), &thisArg, &data);
163 ASSERT(status == napi_ok);
164
165 return InvokeEtsMethodImpl(env, argv->data(), argc, false);
166 }
167
RegisterTimerModule(napi_env jsEnv)168 static bool RegisterTimerModule(napi_env jsEnv)
169 {
170 EtsVM *vm = nullptr;
171 ets_size count = 0;
172 ETS_GetCreatedVMs(&vm, 1, &count);
173 if (count != 1) {
174 LogError("No one VM is created");
175 return false;
176 }
177 EtsEnv *etsEnv = nullptr;
178 vm->GetEnv(&etsEnv, ETS_NAPI_VERSION_1_0);
179 return TimerModule::Init(etsEnv, jsEnv);
180 }
181
GetArgs(napi_env env,napi_callback_info info)182 static std::vector<napi_value> GetArgs(napi_env env, napi_callback_info info)
183 {
184 size_t argc = 0;
185 NAPI_ASSERT_OK(napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr));
186
187 std::vector<napi_value> argv(argc);
188 napi_value thisArg {};
189 void *data = nullptr;
190 NAPI_ASSERT_OK(napi_get_cb_info(env, info, &argc, argv.data(), &thisArg, &data));
191
192 return argv;
193 }
194
RegisterEventLoopModule()195 static void RegisterEventLoopModule()
196 {
197 auto coro = EtsCoroutine::GetCurrent();
198 ASSERT(coro == coro->GetPandaVM()->GetCoroutineManager()->GetMainThread());
199 coro->GetPandaVM()->CreateCallbackPosterFactory<EventLoopCallbackPosterFactoryImpl>();
200 }
201
CreateEtsRuntime(napi_env env,napi_callback_info info)202 static napi_value CreateEtsRuntime(napi_env env, napi_callback_info info)
203 {
204 napi_value napiFalse;
205 NAPI_ASSERT_OK(napi_get_boolean(env, false, &napiFalse));
206
207 auto argv = GetArgs(env, info);
208 if (argv.size() != 4U) {
209 LogError("CreateEtsRuntime: exactly 4 arguments are required");
210 return napiFalse;
211 }
212
213 napi_valuetype type;
214 napi_typeof(env, argv[0], &type);
215 if (type != napi_string) {
216 LogError("CreateEtsRuntime: first argument is not a string");
217 return napiFalse;
218 }
219 auto stdlibPath = GetString(env, argv[0]);
220
221 napi_typeof(env, argv[1], &type);
222 if (type != napi_string) {
223 LogError("CreateEtsRuntime: second argument is not a string");
224 return napiFalse;
225 }
226 auto indexPath = GetString(env, argv[1]);
227
228 napi_typeof(env, argv[2U], &type);
229 if (type != napi_boolean) {
230 LogError("CreateEtsRuntime: third argument is not a boolean");
231 return napiFalse;
232 }
233 bool useJit;
234 napi_get_value_bool(env, argv[2U], &useJit);
235
236 napi_typeof(env, argv[3U], &type);
237 if (type != napi_boolean) {
238 LogError("CreateEtsRuntime: fourth argument is not a boolean");
239 return napiFalse;
240 }
241 bool useAot;
242 napi_get_value_bool(env, argv[3U], &useAot);
243
244 bool res = ark::ets::CreateRuntime(stdlibPath, indexPath, useJit, useAot);
245 res &= RegisterTimerModule(env);
246 RegisterEventLoopModule();
247 if (res) {
248 auto coro = EtsCoroutine::GetCurrent();
249 ScopedManagedCodeThread scoped(coro);
250 napi_add_env_cleanup_hook(
251 env, [](void *) { DestroyRuntime(); }, nullptr);
252 InteropCtx::Init(coro, env);
253 }
254 napi_value napiRes;
255 NAPI_ASSERT_OK(napi_get_boolean(env, res, &napiRes));
256 return napiRes;
257 }
258
GetArgStrings(napi_env env,napi_value options)259 static std::optional<std::vector<std::string>> GetArgStrings(napi_env env, napi_value options)
260 {
261 uint32_t numOptions;
262 std::vector<std::string> argStrings;
263 if (napi_get_array_length(env, options, &numOptions) == napi_ok) {
264 // options passed as an array
265 argStrings.reserve(numOptions + 1);
266 argStrings.emplace_back("argv[0] placeholder");
267
268 for (uint32_t i = 0; i < numOptions; ++i) {
269 napi_value option;
270 NAPI_ASSERT_OK(napi_get_element(env, options, i, &option));
271 if (napi_coerce_to_string(env, option, &option) != napi_ok) {
272 LogError("Option values must be coercible to string");
273 return std::nullopt;
274 }
275 argStrings.push_back(GetString(env, option));
276 }
277 } else {
278 // options passed as a map
279 napi_value propNames;
280 NAPI_ASSERT_OK(napi_get_property_names(env, options, &propNames));
281 NAPI_ASSERT_OK(napi_get_array_length(env, propNames, &numOptions));
282
283 argStrings.reserve(numOptions + 1);
284 argStrings.emplace_back("argv[0] placeholder");
285
286 for (uint32_t i = 0; i < numOptions; ++i) {
287 napi_value key;
288 napi_value value;
289 NAPI_ASSERT_OK(napi_get_element(env, propNames, i, &key));
290 NAPI_ASSERT_OK(napi_get_property(env, options, key, &value));
291 if (napi_coerce_to_string(env, value, &value) != napi_ok) {
292 LogError("Option values must be coercible to string");
293 return std::nullopt;
294 }
295 argStrings.push_back("--" + GetString(env, key) + "=" + GetString(env, value));
296 }
297 }
298
299 return argStrings;
300 }
301
AddOptions(base_options::Options * baseOptions,ark::RuntimeOptions * runtimeOptions,const std::vector<std::string> & argStrings)302 static bool AddOptions(base_options::Options *baseOptions, ark::RuntimeOptions *runtimeOptions,
303 const std::vector<std::string> &argStrings)
304 {
305 ark::PandArgParser paParser;
306 baseOptions->AddOptions(&paParser);
307 runtimeOptions->AddOptions(&paParser);
308 ark::compiler::g_options.AddOptions(&paParser);
309
310 std::vector<const char *> fakeArgv;
311 fakeArgv.reserve(argStrings.size());
312 for (auto const &arg : argStrings) {
313 fakeArgv.push_back(arg.c_str()); // Be careful, do not reallocate referenced strings
314 }
315
316 if (!paParser.Parse(fakeArgv.size(), fakeArgv.data())) {
317 LogError("Parse options failed. Optional arguments:\n" + paParser.GetHelpString());
318 return false;
319 }
320
321 auto runtimeOptionsErr = runtimeOptions->Validate();
322 if (runtimeOptionsErr) {
323 LogError("Parse options failed: " + runtimeOptionsErr.value().GetMessage());
324 return false;
325 }
326 ark::compiler::CompilerLogger::SetComponents(ark::compiler::g_options.GetCompilerLog());
327 return true;
328 }
329
CreateRuntime(napi_env env,napi_callback_info info)330 static napi_value CreateRuntime(napi_env env, napi_callback_info info)
331 {
332 size_t constexpr ARGC = 1;
333 std::array<napi_value, ARGC> argv {};
334
335 size_t argc = ARGC;
336 NAPI_ASSERT_OK(napi_get_cb_info(env, info, &argc, argv.data(), nullptr, nullptr));
337
338 napi_value napiFalse;
339 NAPI_ASSERT_OK(napi_get_boolean(env, false, &napiFalse));
340
341 if (argc != ARGC) {
342 LogError("CreateEtsRuntime: bad args number");
343 return napiFalse;
344 }
345
346 napi_value options = argv[0];
347 if (GetValueType(env, options) != napi_object) {
348 LogError("CreateEtsRuntime: argument is not an object");
349 return napiFalse;
350 }
351
352 auto argStrings = GetArgStrings(env, options);
353 if (argStrings == std::nullopt) {
354 return napiFalse;
355 }
356
357 auto addOpts = [&argStrings](base_options::Options *baseOptions, ark::RuntimeOptions *runtimeOptions) {
358 return AddOptions(baseOptions, runtimeOptions, argStrings.value());
359 };
360
361 bool res = ets::CreateRuntime(addOpts);
362 res &= RegisterTimerModule(env);
363 RegisterEventLoopModule();
364 if (res) {
365 auto coro = EtsCoroutine::GetCurrent();
366 ScopedManagedCodeThread scoped(coro);
367 napi_add_env_cleanup_hook(
368 env, [](void *) { DestroyRuntime(); }, nullptr);
369 InteropCtx::Init(coro, env);
370 }
371 napi_value napiRes;
372 NAPI_ASSERT_OK(napi_get_boolean(env, res, &napiRes));
373 return napiRes;
374 }
375
Init(napi_env env,napi_value exports)376 static napi_value Init(napi_env env, napi_value exports)
377 {
378 const std::array desc = {
379 napi_property_descriptor {"version", 0, Version, 0, 0, 0, napi_enumerable, 0},
380 napi_property_descriptor {"fatal", 0, Fatal, 0, 0, 0, napi_enumerable, 0},
381 napi_property_descriptor {"call", 0, Call, 0, 0, 0, napi_enumerable, 0},
382 napi_property_descriptor {"callWithCopy", 0, CallWithCopy, 0, 0, 0, napi_enumerable, 0},
383 napi_property_descriptor {"createEtsRuntime", 0, CreateEtsRuntime, 0, 0, 0, napi_enumerable, 0},
384 napi_property_descriptor {"createRuntime", 0, CreateRuntime, 0, 0, 0, napi_enumerable, 0},
385 napi_property_descriptor {"getFunction", 0, GetEtsFunction, 0, 0, 0, napi_enumerable, 0},
386 napi_property_descriptor {"getClass", 0, GetEtsClass, 0, 0, 0, napi_enumerable, 0},
387 };
388
389 NAPI_CHECK_FATAL(napi_define_properties(env, exports, desc.size(), desc.data()));
390
391 NapiImpl::InitNapi();
392
393 return exports;
394 }
395
396 } // namespace ark::ets::interop::js
397
398 NAPI_MODULE(ETS_INTEROP_JS_NAPI, ark::ets::interop::js::Init)
399