• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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