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