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 "napi/ets_napi.h"
17 #include "plugins/ets/runtime/napi/ets_napi_internal.h"
18 #include "plugins/ets/runtime/napi/ets_napi_invoke_interface.h"
19
20 #include <sstream>
21 #include <vector>
22
23 #include "generated/base_options.h"
24 #include "libpandabase/utils/logger.h"
25 #include "plugins/ets/runtime/ets_coroutine.h"
26 #include "plugins/ets/runtime/ets_vm.h"
27
28 namespace ark::ets::napi {
DestroyEtsVM(EtsVM * vm)29 static ets_int DestroyEtsVM(EtsVM *vm)
30 {
31 if (vm == nullptr) {
32 LOG(ERROR, RUNTIME) << "Cannot destroy eTS VM, it is null";
33 return ETS_ERR;
34 }
35
36 auto runtime = Runtime::GetCurrent();
37 if (runtime == nullptr) {
38 LOG(ERROR, RUNTIME) << "Cannot destroy eTS VM, there is no current runtime";
39 return ETS_ERR;
40 }
41
42 auto pandaVm = PandaEtsVM::FromEtsVM(vm);
43 auto mainVm = runtime->GetPandaVM();
44 if (pandaVm == mainVm) {
45 Runtime::Destroy();
46 } else {
47 PandaEtsVM::Destroy(pandaVm);
48 }
49
50 return ETS_OK;
51 }
52
CheckVersionEtsNapi(ets_int version)53 bool CheckVersionEtsNapi(ets_int version)
54 {
55 return (version == ETS_NAPI_VERSION_1_0);
56 }
57
GetEnv(EtsVM * vm,EtsEnv ** pEnv,ets_int version)58 static ets_int GetEnv([[maybe_unused]] EtsVM *vm, EtsEnv **pEnv, [[maybe_unused]] ets_int version)
59 {
60 if (pEnv == nullptr) {
61 LOG(ERROR, RUNTIME) << "Cannot get environment, p_env is null";
62 return ETS_ERR;
63 }
64
65 auto coroutine = EtsCoroutine::GetCurrent();
66 if (coroutine == nullptr) {
67 LOG(ERROR, RUNTIME) << "Cannot get environment, there is no current coroutine";
68 return ETS_ERR;
69 }
70
71 if (!CheckVersionEtsNapi(version)) {
72 *pEnv = nullptr;
73 return ETS_ERR_VER;
74 }
75 *pEnv = coroutine->GetEtsNapiEnv();
76
77 return ETS_OK;
78 }
79
AttachThread(EtsVM * vm,EtsEnv ** resultEnv,void ** resultJsEnv)80 static ets_int AttachThread(EtsVM *vm, EtsEnv **resultEnv, void **resultJsEnv)
81 {
82 if (vm == nullptr) {
83 LOG(ERROR, RUNTIME) << "Cannot AttachThread, vm is null";
84 return ETS_ERR;
85 }
86 if (Thread::GetCurrent() != nullptr) {
87 LOG(ERROR, RUNTIME) << "Cannot AttachThread, thread has already been attached";
88 return ETS_ERR;
89 }
90 auto *runtime = Runtime::GetCurrent();
91 auto *etsVM = PandaEtsVM::FromEtsVM(vm);
92 auto *coroMan = etsVM->GetCoroutineManager();
93
94 auto *exclusiveCoro = coroMan->CreateExclusiveWorkerForThread(runtime, etsVM);
95 if (exclusiveCoro == nullptr) {
96 LOG(ERROR, RUNTIME) << "Cannot AttachThread, reached the limit of EAWorkers";
97 return ETS_ERR;
98 }
99 ASSERT(exclusiveCoro == Coroutine::GetCurrent());
100 auto *ifaceTable = EtsCoroutine::CastFromThread(coroMan->GetMainThread())->GetExternalIfaceTable();
101 auto *jsEnv = ifaceTable->CreateJSRuntime();
102 if (jsEnv != nullptr) {
103 ifaceTable->CreateInteropCtx(exclusiveCoro, jsEnv);
104 }
105 *resultEnv = PandaEtsNapiEnv::GetCurrent();
106 *resultJsEnv = jsEnv;
107 return ETS_OK;
108 }
109
DetachThread(EtsVM * vm)110 static ets_int DetachThread(EtsVM *vm)
111 {
112 if (vm == nullptr) {
113 LOG(ERROR, RUNTIME) << "Cannot DetachThread, vm is null";
114 return ETS_ERR;
115 }
116 auto *etsVM = PandaEtsVM::FromEtsVM(vm);
117 auto *coroMan = etsVM->GetCoroutineManager();
118 auto result = coroMan->DestroyExclusiveWorker();
119 if (!result) {
120 LOG(ERROR, RUNTIME) << "Cannot DetachThread, thread was not attached";
121 return ETS_ERR;
122 }
123 ASSERT(Thread::GetCurrent() == nullptr);
124 return ETS_OK;
125 }
126
127 static const struct ETS_InvokeInterface S_INVOKE_INTERFACE = {DestroyEtsVM, GetEnv, AttachThread, DetachThread};
128
GetInvokeInterface()129 const ETS_InvokeInterface *GetInvokeInterface()
130 {
131 return &S_INVOKE_INTERFACE;
132 }
133
134 static EtsVMInitArgs g_sDefaultArgs = {0, 0, nullptr};
135
ETS_GetDefaultVMInitArgs(EtsVMInitArgs * vmArgs)136 extern "C" ets_int ETS_GetDefaultVMInitArgs(EtsVMInitArgs *vmArgs)
137 {
138 *vmArgs = g_sDefaultArgs;
139 return ETS_OK;
140 }
141
ETS_GetCreatedVMs(EtsVM ** vmBuf,ets_size bufLen,ets_size * nVms)142 extern "C" ets_int ETS_GetCreatedVMs(EtsVM **vmBuf, ets_size bufLen, ets_size *nVms)
143 {
144 if (nVms == nullptr) {
145 return ETS_ERR;
146 }
147
148 if (Thread::GetCurrent() == nullptr) {
149 *nVms = 0;
150 return ETS_OK;
151 }
152
153 if (auto *coroutine = EtsCoroutine::GetCurrent()) {
154 *nVms = 1;
155
156 if (vmBuf == nullptr || bufLen < 1) {
157 return ETS_ERR;
158 }
159
160 *vmBuf = coroutine->GetPandaVM();
161 } else {
162 *nVms = 0;
163 }
164
165 return ETS_OK;
166 }
167
168 static void *g_logPrintFunction = nullptr;
169
EtsMobileLogPrint(int id,int level,const char * component,const char * fmt,const char * msg)170 static void EtsMobileLogPrint(int id, int level, const char *component, const char *fmt, const char *msg)
171 {
172 int etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_UNKNOWN;
173 switch (level) {
174 case ark::Logger::PandaLog2MobileLog::UNKNOWN:
175 etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_UNKNOWN;
176 break;
177 case ark::Logger::PandaLog2MobileLog::DEFAULT:
178 etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_DEFAULT;
179 break;
180 case ark::Logger::PandaLog2MobileLog::VERBOSE:
181 etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_VERBOSE;
182 break;
183 case ark::Logger::PandaLog2MobileLog::DEBUG:
184 etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_DEBUG;
185 break;
186 case ark::Logger::PandaLog2MobileLog::INFO:
187 etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_INFO;
188 break;
189 case ark::Logger::PandaLog2MobileLog::WARN:
190 etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_WARN;
191 break;
192 case ark::Logger::PandaLog2MobileLog::ERROR:
193 etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_ERROR;
194 break;
195 case ark::Logger::PandaLog2MobileLog::FATAL:
196 etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_FATAL;
197 break;
198 case ark::Logger::PandaLog2MobileLog::SILENT:
199 etsLevel = EtsMobileLogggerLevel::ETS_MOBILE_LOG_LEVEL_SILENT;
200 break;
201 default:
202 LOG(ERROR, RUNTIME) << "No such mobile log option";
203 }
204
205 auto logPrintCallback = reinterpret_cast<FuncMobileLogPrint>(g_logPrintFunction);
206 ASSERT(logPrintCallback != nullptr);
207 logPrintCallback(id, etsLevel, component, fmt, msg);
208 }
209
210 struct ParsedOptions final {
211 // NOLINTBEGIN(misc-non-private-member-variables-in-classes)
212 std::vector<std::string> bootPandaFiles;
213 std::vector<std::string> aotFiles;
214 std::vector<std::string> arkFiles;
215 base_options::Options baseOptions;
216 // NOLINTEND(misc-non-private-member-variables-in-classes)
217
ParsedOptionsark::ets::napi::ParsedOptions218 explicit ParsedOptions(const std::string &exePath) : baseOptions(exePath) {}
219 };
220
SplitString(const std::string & from,char delim)221 static arg_list_t SplitString(const std::string &from, char delim)
222 {
223 arg_list_t pathes;
224 std::istringstream iss {from};
225 std::string path;
226 while (std::getline(iss, path, delim)) {
227 pathes.emplace_back(path);
228 }
229 return pathes;
230 }
231
232 // CC-OFFNXT(G.FUN.01-CPP) big switch-case
ParseOptionsHelper(RuntimeOptions & runtimeOptions,ParsedOptions & parsedOptions,Span<const EtsVMOption> & options)233 static void ParseOptionsHelper(RuntimeOptions &runtimeOptions, ParsedOptions &parsedOptions,
234 Span<const EtsVMOption> &options)
235 {
236 for (auto &o : options) {
237 auto extraStr = reinterpret_cast<const char *>(o.extraInfo);
238
239 switch (o.option) {
240 case EtsOptionType::ETS_LOG_LEVEL:
241 parsedOptions.baseOptions.SetLogLevel(extraStr);
242 break;
243 case EtsOptionType::ETS_MOBILE_LOG:
244 g_logPrintFunction = const_cast<void *>(o.extraInfo);
245 runtimeOptions.SetMobileLog(reinterpret_cast<void *>(EtsMobileLogPrint));
246 break;
247 case EtsOptionType::ETS_BOOT_FILE:
248 parsedOptions.bootPandaFiles.emplace_back(extraStr);
249 break;
250 case EtsOptionType::ETS_AOT_FILE:
251 parsedOptions.aotFiles.emplace_back(extraStr);
252 break;
253 case EtsOptionType::ETS_ARK_FILE:
254 parsedOptions.arkFiles.emplace_back(extraStr);
255 break;
256 case EtsOptionType::ETS_JIT:
257 runtimeOptions.SetCompilerEnableJit(true);
258 break;
259 case EtsOptionType::ETS_NO_JIT:
260 runtimeOptions.SetCompilerEnableJit(false);
261 break;
262 case EtsOptionType::ETS_AOT:
263 runtimeOptions.SetEnableAn(true);
264 break;
265 case EtsOptionType::ETS_NO_AOT:
266 runtimeOptions.SetEnableAn(false);
267 break;
268 case EtsOptionType::ETS_GC_TRIGGER_TYPE:
269 runtimeOptions.SetGcTriggerType(extraStr);
270 break;
271 case EtsOptionType::ETS_GC_TYPE:
272 runtimeOptions.SetGcType(extraStr);
273 break;
274 case EtsOptionType::ETS_RUN_GC_IN_PLACE:
275 runtimeOptions.SetRunGcInPlace(true);
276 break;
277 case EtsOptionType::ETS_INTERPRETER_TYPE:
278 runtimeOptions.SetInterpreterType(extraStr);
279 break;
280 case EtsOptionType::ETS_NATIVE_LIBRARY_PATH:
281 runtimeOptions.SetNativeLibraryPath(SplitString(extraStr, ':'));
282 break;
283 case EtsOptionType::ETS_VERIFICATION_MODE:
284 runtimeOptions.SetVerificationMode(extraStr);
285 break;
286 default:
287 LOG(ERROR, RUNTIME) << "No such option";
288 }
289 }
290 }
291
ParseOptions(const EtsVMInitArgs * args,RuntimeOptions & runtimeOptions)292 static bool ParseOptions(const EtsVMInitArgs *args, RuntimeOptions &runtimeOptions)
293 {
294 ParsedOptions parsedOptions("");
295
296 runtimeOptions.SetLoadRuntimes({"ets"});
297
298 Span<const EtsVMOption> options(args->options, args->nOptions);
299 ParseOptionsHelper(runtimeOptions, parsedOptions, options);
300
301 Logger::Initialize(parsedOptions.baseOptions);
302
303 runtimeOptions.SetBootPandaFiles(parsedOptions.bootPandaFiles);
304 runtimeOptions.SetAotFiles(parsedOptions.aotFiles);
305 runtimeOptions.SetPandaFiles(parsedOptions.arkFiles);
306
307 return true;
308 }
309
ETS_CreateVM(EtsVM ** pVm,EtsEnv ** pEnv,EtsVMInitArgs * vmArgs)310 extern "C" ETS_EXPORT ets_int ETS_CreateVM(EtsVM **pVm, EtsEnv **pEnv, EtsVMInitArgs *vmArgs)
311 {
312 trace::ScopedTrace scopedTrace(__FUNCTION__);
313
314 if (pVm == nullptr || pEnv == nullptr) {
315 return ETS_ERR;
316 }
317
318 if (!CheckVersionEtsNapi(vmArgs->version)) {
319 LOG(ERROR, ETS_NAPI) << "Error: Unsupported Ets napi version = " << vmArgs->version;
320 return ETS_ERR_VER;
321 }
322
323 RuntimeOptions runtimeOptions;
324
325 if (!ParseOptions(vmArgs, runtimeOptions)) {
326 return ETS_ERR;
327 }
328
329 if (!Runtime::Create(runtimeOptions)) {
330 LOG(ERROR, RUNTIME) << "Cannot create runtime";
331 return ETS_ERR;
332 }
333
334 ASSERT(Runtime::GetCurrent() != nullptr);
335
336 auto coroutine = EtsCoroutine::GetCurrent();
337 ASSERT(coroutine != nullptr);
338
339 *pVm = coroutine->GetPandaVM();
340 ASSERT(*pVm != nullptr);
341
342 *pEnv = coroutine->GetEtsNapiEnv();
343 ASSERT(*pEnv != nullptr);
344
345 return ETS_OK;
346 }
347
348 // We have separate shared library with ets_napi called libetsnative.so
349 // libetsnative.so contains same three ETS_* functions as libarkruntime.so
350 // libarktuntime.so exposes three _internal_ETS_* aliases
351 // And libetsnative.so ETS_* functions just forward calls to _internal_ETS_* functions
352 extern "C" ETS_EXPORT ets_int _internal_ETS_GetDefaultVMInitArgs(EtsVMInitArgs *vmArgs)
353 __attribute__((alias("ETS_GetDefaultVMInitArgs")));
354 extern "C" ETS_EXPORT ets_int _internal_ETS_GetCreatedVMs(EtsVM **vmBuf, ets_size bufLen, ets_size *nVms)
355 __attribute__((alias("ETS_GetCreatedVMs")));
356 extern "C" ETS_EXPORT ets_int _internal_ETS_CreateVM(EtsVM **pVm, EtsEnv **pEnv, EtsVMInitArgs *vmArgs)
357 __attribute__((alias("ETS_CreateVM")));
358 } // namespace ark::ets::napi
359