• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-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 <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/call/call.h"
22 #include "plugins/ets/runtime/interop_js/interop_common.h"
23 #include "plugins/ets/runtime/interop_js/code_scopes.h"
24 
25 #include "generated/base_options.h"
26 #include "compiler_options.h"
27 #include "compiler/compiler_logger.h"
28 #include "interop_js/napi_impl/napi_impl.h"
29 #include "plugins/ets/runtime/ets_utils.h"
30 #include "runtime/include/runtime.h"
31 
32 #include "os/thread.h"
33 
34 #include "plugins/ets/runtime/interop_js/interop_context_api.h"
35 
36 namespace ark::ets::interop::js {
37 
Version(napi_env env,napi_callback_info info)38 static napi_value Version(napi_env env, [[maybe_unused]] napi_callback_info info)
39 {
40     ASSERT_SCOPED_NATIVE_CODE();
41     constexpr std::string_view MSG = "0.1";
42 
43     napi_value result;
44     [[maybe_unused]] napi_status status = napi_create_string_utf8(env, MSG.data(), MSG.size(), &result);
45     ASSERT(status == napi_ok);
46 
47     return result;
48 }
49 
GetEtsFunction(napi_env env,napi_callback_info info)50 static napi_value GetEtsFunction(napi_env env, napi_callback_info info)
51 {
52     ASSERT_SCOPED_NATIVE_CODE();
53 
54     size_t jsArgc = 0;
55     NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, nullptr, nullptr, nullptr));
56     if (jsArgc != 2U && jsArgc != 1U) {
57         InteropCtx::ThrowJSError(env, "GetEtsFunction: bad args, actual args count: " + std::to_string(jsArgc));
58         return nullptr;
59     }
60 
61     std::array<napi_value, 2U> jsArgv {};
62     NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, jsArgv.data(), nullptr, nullptr));
63 
64     napi_value jsFunctionName = jsArgc == 1 ? jsArgv[0] : jsArgv[1];
65 
66     std::string packageName;
67     if (jsArgc == 2U) {
68         napi_value jsPackageName = jsArgv[0];
69         if (GetValueType(env, jsPackageName) != napi_string) {
70             InteropCtx::ThrowJSError(env, "GetEtsFunction: package name is not a string");
71         }
72         packageName = GetString(env, jsPackageName);
73     }
74 
75     if (GetValueType(env, jsFunctionName) != napi_string) {
76         InteropCtx::ThrowJSError(env, "GetEtsFunction: function name is not a string");
77         return nullptr;
78     }
79 
80     std::string methodName = GetString(env, jsFunctionName);
81 
82     return ets_proxy::GetETSFunction(env, packageName, methodName);
83 }
84 
85 // This function returns napi_value, thus it is possible to get accessto it`s fields
86 // It gives opportunity to get acces to global variables from ETSGLOBAL
87 // test.ets
88 // export let x = 1
89 // test.js
90 // etsvm.getClass("ETSGLOBAL").x
GetEtsClass(napi_env env,napi_callback_info info)91 static napi_value GetEtsClass(napi_env env, napi_callback_info info)
92 {
93     ASSERT_SCOPED_NATIVE_CODE();
94 
95     size_t jsArgc = 0;
96     NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, nullptr, nullptr, nullptr));
97 
98     if (jsArgc != 1) {
99         InteropCtx::ThrowJSError(env, "GetEtsClass: bad args, actual args count: " + std::to_string(jsArgc));
100         return nullptr;
101     }
102 
103     napi_value jsClassDescriptor {};
104     ASSERT(jsArgc == 1);
105     NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, &jsClassDescriptor, nullptr, nullptr));
106 
107     std::string classDescriptor = GetString(env, jsClassDescriptor);
108     return ets_proxy::GetETSClass(env, classDescriptor);
109 }
110 
GetEtsInstance(napi_env env,napi_callback_info info)111 static napi_value GetEtsInstance(napi_env env, napi_callback_info info)
112 {
113     ASSERT_SCOPED_NATIVE_CODE();
114 
115     size_t jsArgc = 0;
116     NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, nullptr, nullptr, nullptr));
117 
118     if (jsArgc != 1) {
119         InteropCtx::ThrowJSError(env, "GetEtsInstance: bad args, actual args count: " + std::to_string(jsArgc));
120         return nullptr;
121     }
122     napi_value jsClassDescriptor {};
123     NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &jsArgc, &jsClassDescriptor, nullptr, nullptr));
124 
125     std::string classDescriptor = GetString(env, jsClassDescriptor);
126     return ets_proxy::GetETSInstance(env, classDescriptor);
127 }
128 
GetEtsModule(napi_env env,napi_callback_info info)129 static napi_value GetEtsModule(napi_env env, napi_callback_info info)
130 {
131     size_t argc = 1;
132     std::array<napi_value, 1> argv {};
133     NAPI_CHECK_FATAL(napi_get_cb_info(env, info, &argc, argv.data(), nullptr, nullptr));
134     if (argc != 1) {
135         InteropCtx::ThrowJSError(env, "GetEtsModule: expects exactly one argument (module name)");
136         return nullptr;
137     }
138 
139     std::string moduleName = GetString(env, argv[0]);
140     return ets_proxy::GetETSModule(env, moduleName);
141 }
142 
GetArgStrings(napi_env env,napi_value options,bool needFakeArgv0=true)143 static std::optional<std::vector<std::string>> GetArgStrings(napi_env env, napi_value options,
144                                                              bool needFakeArgv0 = true)
145 {
146     uint32_t numOptions;
147     std::vector<std::string> argStrings;
148     if (napi_get_array_length(env, options, &numOptions) == napi_ok) {
149         // options passed as an array
150         argStrings.reserve(numOptions + 1);
151         argStrings.emplace_back("argv[0] placeholder");
152 
153         for (uint32_t i = 0; i < numOptions; ++i) {
154             napi_value option;
155             NAPI_ASSERT_OK(napi_get_element(env, options, i, &option));
156             if (napi_coerce_to_string(env, option, &option) != napi_ok) {
157                 LogError("Option values must be coercible to string");
158                 return std::nullopt;
159             }
160             argStrings.push_back(GetString(env, option));
161         }
162     } else {
163         // options passed as a map
164         napi_value propNames;
165         NAPI_ASSERT_OK(napi_get_property_names(env, options, &propNames));
166         NAPI_ASSERT_OK(napi_get_array_length(env, propNames, &numOptions));
167 
168         argStrings.reserve(needFakeArgv0 ? numOptions + 1 : numOptions);
169         if (needFakeArgv0) {
170             argStrings.emplace_back("argv[0] placeholder");
171         }
172 
173         for (uint32_t i = 0; i < numOptions; ++i) {
174             napi_value key;
175             napi_value value;
176             NAPI_ASSERT_OK(napi_get_element(env, propNames, i, &key));
177             NAPI_ASSERT_OK(napi_get_property(env, options, key, &value));
178             if (napi_coerce_to_string(env, value, &value) != napi_ok) {
179                 LogError("Option values must be coercible to string");
180                 return std::nullopt;
181             }
182             argStrings.push_back("--" + GetString(env, key) + "=" + GetString(env, value));
183         }
184     }
185 
186     return argStrings;
187 }
188 
AddOptions(base_options::Options * baseOptions,ark::RuntimeOptions * runtimeOptions,const std::vector<std::string> & argStrings)189 static bool AddOptions(base_options::Options *baseOptions, ark::RuntimeOptions *runtimeOptions,
190                        const std::vector<std::string> &argStrings)
191 {
192     ark::PandArgParser paParser;
193     baseOptions->AddOptions(&paParser);
194     runtimeOptions->AddOptions(&paParser);
195     ark::compiler::g_options.AddOptions(&paParser);
196 
197     std::vector<const char *> fakeArgv;
198     fakeArgv.reserve(argStrings.size());
199     for (auto const &arg : argStrings) {
200         fakeArgv.push_back(arg.c_str());  // Be careful, do not reallocate referenced strings
201     }
202 
203     if (!paParser.Parse(fakeArgv.size(), fakeArgv.data())) {
204         LogError("Parse options failed. Optional arguments:\n" + paParser.GetHelpString());
205         return false;
206     }
207 
208     auto runtimeOptionsErr = runtimeOptions->Validate();
209     if (runtimeOptionsErr) {
210         LogError("Parse options failed: " + runtimeOptionsErr.value().GetMessage());
211         return false;
212     }
213     ark::compiler::CompilerLogger::SetComponents(ark::compiler::g_options.GetCompilerLog());
214     return true;
215 }
216 
CreateRuntimeLegacy(napi_env env,napi_callback_info info)217 [[maybe_unused]] static napi_value CreateRuntimeLegacy(napi_env env, napi_callback_info info)
218 {
219     // NOTE(konstanting): this is needed for a sole purpose of introducing a dependency on libarktsbase.so.
220     // In the standalone hybrid release build we have a REALLY REALLY strange behavior in the XGC tests:
221     // libarktsbase's mem hooks REQUIRE the libarksbase.so to be loaded into the process address space
222     // BEFORE libarkruntime.so. Otherwise dlsym would return NULL and everything will break. Since the
223     // ets_vm_plugin module gets loaded first, we have to forcefully introduce a dependency on
224     // libartktsbase.so for it, so it can be loaded before libarkruntime.
225     // Sorry. I will try to find a more clean way to handle this ugly situation later on.
226     volatile auto tid = ark::os::thread::GetCurrentThreadId();
227     ++tid;
228     // end of NOTE(konstanting)
229 
230     size_t constexpr ARGC = 1;
231     std::array<napi_value, ARGC> argv {};
232 
233     size_t argc = ARGC;
234     NAPI_ASSERT_OK(napi_get_cb_info(env, info, &argc, argv.data(), nullptr, nullptr));
235 
236     napi_value napiFalse;
237     NAPI_ASSERT_OK(napi_get_boolean(env, false, &napiFalse));
238 
239     if (argc != ARGC) {
240         LogError("CreateRuntimeLegacy: bad args number");
241         return napiFalse;
242     }
243 
244     napi_value options = argv[0];
245     if (GetValueType(env, options) != napi_object) {
246         LogError("CreateRuntimeLegacy: argument is not an object");
247         return napiFalse;
248     }
249 
250     auto argStrings = GetArgStrings(env, options);
251     if (argStrings == std::nullopt) {
252         return napiFalse;
253     }
254 
255     auto addOpts = [&argStrings](base_options::Options *baseOptions, ark::RuntimeOptions *runtimeOptions) {
256         return AddOptions(baseOptions, runtimeOptions, argStrings.value());
257     };
258 
259     bool res = ets::CreateRuntime(addOpts);
260     if (res) {
261         res = ark::ets::interop::js::CreateMainInteropContext(EtsCoroutine::GetCurrent(), env);
262     }
263     napi_value napiRes;
264     NAPI_ASSERT_OK(napi_get_boolean(env, res, &napiRes));
265     return napiRes;
266 }
267 
CreateRuntimeViaAni(napi_env env,napi_callback_info info)268 static napi_value CreateRuntimeViaAni(napi_env env, napi_callback_info info)
269 {
270     // NOTE(konstanting): we need to migrate various CreateRuntime features to this function in order
271     // to fully migrate to ANI_CreateVM. The obvious examples are compiler-specific options and logging,
272     // XGC-related checks and so on. See ets_vm_api.cpp:CreateRuntime and AddOptions function
273     // above for reference.
274 
275     size_t constexpr ARGC = 1;
276     std::array<napi_value, ARGC> argv {};
277 
278     size_t argc = ARGC;
279     NAPI_ASSERT_OK(napi_get_cb_info(env, info, &argc, argv.data(), nullptr, nullptr));
280 
281     napi_value napiFalse;
282     NAPI_ASSERT_OK(napi_get_boolean(env, false, &napiFalse));
283 
284     if (argc != ARGC) {
285         LogError("CreateRuntimeViaAni: bad args number");
286         return napiFalse;
287     }
288 
289     napi_value options = argv[0];
290     if (GetValueType(env, options) != napi_object) {
291         LogError("CreateRuntimeViaAni: argument is not an object");
292         return napiFalse;
293     }
294 
295     auto argStrings = GetArgStrings(env, options, false);
296     if (argStrings == std::nullopt) {
297         LogError("CreateRuntimeViaAni: cannot parse options");
298         return napiFalse;
299     }
300 
301     // NOTE(konstanting, #23205): kind of messy, need to prettify
302     const std::string optionPrefix = "--ext:";
303     std::vector<std::string> extraVmAniStrings;
304     for (auto &opt : *argStrings) {
305         extraVmAniStrings.push_back(optionPrefix + opt);
306     }
307     std::vector<ani_option> optVector;
308     for (auto &aniOptStr : extraVmAniStrings) {
309         ani_option aniOpt = {aniOptStr.data(), nullptr};
310         optVector.push_back(aniOpt);
311     }
312     optVector.push_back(ani_option {"--ext:interop", env});
313     ani_options aniOptions = {optVector.size(), optVector.data()};
314 
315     ani_vm *panda;
316     ani_status aniStatus = ANI_CreateVM(&aniOptions, ANI_VERSION_1, &panda);
317     if (aniStatus != ANI_OK) {
318         LogError("CreateRuntimeViaAni: ANI_CreateVM failed");
319     }
320 
321     napi_value napiRes;
322     NAPI_ASSERT_OK(napi_get_boolean(env, aniStatus == ANI_OK, &napiRes));
323     return napiRes;
324 }
325 
Init(napi_env env,napi_value exports)326 static napi_value Init(napi_env env, napi_value exports)
327 {
328     const std::array desc = {
329         napi_property_descriptor {"version", 0, Version, 0, 0, 0, napi_enumerable, 0},
330         // NOTE(konstanting, #23205): to be deleted
331         napi_property_descriptor {"createRuntimeLegacy", 0, CreateRuntimeLegacy, 0, 0, 0, napi_enumerable, 0},
332         // NOTE(konstanting, #23205): to be renamed once migration is complete
333         napi_property_descriptor {"createRuntime", 0, CreateRuntimeViaAni, 0, 0, 0, napi_enumerable, 0},
334         napi_property_descriptor {"getFunction", 0, GetEtsFunction, 0, 0, 0, napi_enumerable, 0},
335         napi_property_descriptor {"getClass", 0, GetEtsClass, 0, 0, 0, napi_enumerable, 0},
336         napi_property_descriptor {"getInstance", 0, GetEtsInstance, 0, 0, 0, napi_enumerable, 0},
337         napi_property_descriptor {"getModule", 0, GetEtsModule, 0, 0, 0, napi_enumerable, 0},
338     };
339 
340     NAPI_CHECK_FATAL(napi_define_properties(env, exports, desc.size(), desc.data()));
341 
342     NapiImpl::InitNapi();
343 
344     return exports;
345 }
346 
347 }  // namespace ark::ets::interop::js
348 
349 NAPI_MODULE(ETS_INTEROP_JS_NAPI, ark::ets::interop::js::Init)
350