• 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 "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