1 /*
2 * Copyright (c) 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 "ecmascript/runtime.h"
17
18 #include "ecmascript/checkpoint/thread_state_transition.h"
19 #include "ecmascript/jit/jit.h"
20 #include "ecmascript/jspandafile/program_object.h"
21 #include "ecmascript/mem/heap-inl.h"
22 namespace panda::ecmascript {
23 using PGOProfilerManager = pgo::PGOProfilerManager;
24
25 int32_t Runtime::vmCount_ = 0;
26 int32_t Runtime::destroyCount_ = 0;
27 bool Runtime::firstVmCreated_ = false;
28 Mutex *Runtime::vmCreationLock_ = new Mutex();
29 Runtime *Runtime::instance_ = nullptr;
30
GetInstance()31 Runtime *Runtime::GetInstance()
32 {
33 ASSERT(instance_ != nullptr);
34 return instance_;
35 }
36
~Runtime()37 Runtime::~Runtime()
38 {
39 LockHolder lock(constpoolLock_);
40 auto iter = globalSharedConstpools_.begin();
41 while (iter != globalSharedConstpools_.end()) {
42 LOG_ECMA(INFO) << "remove js pandafile by vm destruct, file: " << iter->first->GetJSPandaFileDesc();
43 JSPandaFileManager::GetInstance()->RemoveJSPandaFile(iter->first);
44 iter->second.clear();
45 iter++;
46 }
47 globalSharedConstpools_.clear();
48 delete externalRegisteredStringTable_;
49 externalRegisteredStringTable_ = nullptr;
50 }
51
CreateIfFirstVm(const JSRuntimeOptions & options)52 void Runtime::CreateIfFirstVm(const JSRuntimeOptions &options)
53 {
54 LockHolder lock(*vmCreationLock_);
55 if (!firstVmCreated_) {
56 Log::Initialize(options);
57 EcmaVM::InitializeIcuData(options);
58 MemMapAllocator::GetInstance()->Initialize(ecmascript::DEFAULT_REGION_SIZE);
59 PGOProfilerManager::GetInstance()->Initialize(options.GetPGOProfilerPath(),
60 options.GetPGOHotnessThreshold());
61 ASSERT(instance_ == nullptr);
62 instance_ = new Runtime();
63 SharedHeap::CreateNewInstance();
64 DaemonThread::CreateNewInstance();
65 firstVmCreated_ = true;
66 }
67 }
68
InitializeIfFirstVm(EcmaVM * vm)69 void Runtime::InitializeIfFirstVm(EcmaVM *vm)
70 {
71 {
72 LockHolder lock(*vmCreationLock_);
73 if (++vmCount_ == 1) {
74 ThreadManagedScope managedScope(vm->GetAssociatedJSThread());
75 PreInitialization(vm);
76 bool isEnableFastJit = vm->GetJSOptions().IsEnableJIT() && vm->GetJSOptions().GetEnableAsmInterpreter();
77 bool isEnableBaselineJit =
78 vm->GetJSOptions().IsEnableBaselineJIT() && vm->GetJSOptions().GetEnableAsmInterpreter();
79 Jit::GetInstance()->SetEnableOrDisable(vm->GetJSOptions(), isEnableFastJit, isEnableBaselineJit);
80 vm->Initialize();
81 PostInitialization(vm);
82 }
83 }
84 if (!vm->IsInitialized()) {
85 ThreadManagedScope managedScope(vm->GetAssociatedJSThread());
86 vm->Initialize();
87 }
88 }
89
PreInitialization(const EcmaVM * vm)90 void Runtime::PreInitialization(const EcmaVM *vm)
91 {
92 mainThread_ = vm->GetAssociatedJSThread();
93 mainThread_->SetMainThread();
94 nativeAreaAllocator_ = std::make_unique<NativeAreaAllocator>();
95 heapRegionAllocator_ = std::make_unique<HeapRegionAllocator>();
96 stringTable_ = std::make_unique<EcmaStringTable>();
97 SharedHeap::GetInstance()->Initialize(nativeAreaAllocator_.get(), heapRegionAllocator_.get(),
98 const_cast<EcmaVM*>(vm)->GetJSOptions(), DaemonThread::GetInstance());
99 }
100
PostInitialization(const EcmaVM * vm)101 void Runtime::PostInitialization(const EcmaVM *vm)
102 {
103 // Use the main thread's globalconst after it has initialized,
104 // and copy shared parts to other thread's later.
105 sharedConstInited_ = true;
106 globalEnv_ = vm->GetGlobalEnv().GetTaggedValue();
107 globalConst_.CopySharedConstantsFrom(mainThread_->GlobalConstants());
108 SharedHeap::GetInstance()->PostInitialization(&globalConst_, const_cast<EcmaVM*>(vm)->GetJSOptions());
109 SharedModuleManager::GetInstance()->Initialize();
110 }
111
DestroyIfLastVm()112 void Runtime::DestroyIfLastVm()
113 {
114 LockHolder lock(*vmCreationLock_);
115 if (--vmCount_ <= 0) {
116 Jit::GetInstance()->Destroy();
117 SharedModuleManager::GetInstance()->SharedNativeObjDestory();
118 SharedHeap::GetInstance()->WaitAllTasksFinishedAfterAllJSThreadEliminated();
119 DaemonThread::DestroyInstance();
120 SharedHeap::DestroyInstance();
121 AnFileDataManager::GetInstance()->SafeDestroyAllData();
122 MemMapAllocator::GetInstance()->Finalize();
123 PGOProfilerManager::GetInstance()->Destroy();
124 SharedModuleManager::GetInstance()->Destroy();
125 ASSERT(instance_ != nullptr);
126 delete instance_;
127 instance_ = nullptr;
128 firstVmCreated_ = false;
129 }
130 }
131
RegisterThread(JSThread * newThread)132 void Runtime::RegisterThread(JSThread* newThread)
133 {
134 LockHolder lock(threadsLock_);
135 ASSERT(std::find(threads_.begin(), threads_.end(), newThread) == threads_.end());
136 threads_.emplace_back(newThread);
137 // send all current suspended requests to the new thread
138 for (uint32_t i = 0; i < suspendNewCount_; i++) {
139 newThread->SuspendThread(true);
140 }
141 }
142
143 // Note: currently only called when thread is to be destroyed.
UnregisterThread(JSThread * thread)144 void Runtime::UnregisterThread(JSThread* thread)
145 {
146 LockHolder lock(threadsLock_);
147 ASSERT(std::find(threads_.begin(), threads_.end(), thread) != threads_.end());
148 ASSERT(!thread->IsInRunningState());
149 threads_.remove(thread);
150 }
151
SuspendAll(JSThread * current)152 void Runtime::SuspendAll(JSThread *current)
153 {
154 ASSERT(current != nullptr);
155 ASSERT(!current->IsInRunningState());
156 #ifndef NDEBUG
157 ASSERT(!current->HasLaunchedSuspendAll());
158 current->LaunchSuspendAll();
159 #endif
160 SuspendAllThreadsImpl(current);
161 }
162
ResumeAll(JSThread * current)163 void Runtime::ResumeAll(JSThread *current)
164 {
165 ASSERT(current != nullptr);
166 ASSERT(!current->IsInRunningState());
167 #ifndef NDEBUG
168 ASSERT(current->HasLaunchedSuspendAll());
169 current->CompleteSuspendAll();
170 #endif
171 ResumeAllThreadsImpl(current);
172 }
173
SuspendAllThreadsImpl(JSThread * current)174 void Runtime::SuspendAllThreadsImpl(JSThread *current)
175 {
176 SuspendBarrier barrier;
177 for (uint32_t iterCount = 1U;; ++iterCount) {
178 {
179 LockHolder lock(threadsLock_);
180 if (suspendNewCount_ == 0) {
181 suspendNewCount_++;
182 if (threads_.size() == 1) {
183 ASSERT(current == mainThread_ || current->IsDaemonThread());
184 return;
185 }
186 if (threads_.size() < 1) {
187 return;
188 }
189 barrier.Initialize(threads_.size() - 1);
190 for (const auto& thread: threads_) {
191 if (thread == current) {
192 continue;
193 }
194 thread->SuspendThread(+1, &barrier);
195 // The two flags, SUSPEND_REQUEST and ACTIVE_BARRIER, are set by Suspend-Thread guarded by
196 // suspendLock_, so the target thread-I may do not see these two flags in time. As a result, it
197 // can switch its state freely without responding to the ACTIVE_BARRIER flag and the
198 // suspend-thread will always wait it. However, as long as it sees the flags, the actions of
199 // passing barrier will be triggered. When the thread-I switches from NON_RUNNING to RUNNING,
200 // it will firstly pass the barrier and then be blocked by the SUSPEND_REQUEST flag. If the
201 // thread-I switches from RUNNING to NON_RUNNING, it will switch the state and then act on the
202 // barrier. If the thread-I go to checkpoint in RUNNING state, it will act on the barrier
203 // and be blocked by SUSPEND_REQUEST flag.
204 if (thread->IsSuspended()) {
205 // Because of the multi-threads situation, currently thread-I may be in RUNNING state or is
206 // goding to be RUNNING state even inside this branch. In both scenarios, for instance of
207 // RUNNING state, according to the modification order of atomic-variable stateAndFlags_,
208 // thread-I will see the SUSPEND_REQUEST and ACTIVE_BARRIER and act on them before switching
209 // to RUNNING. Besides, notice the using of suspendLock_ inside PassSuspendBarrier(), there
210 // is not data-race for passing barrier.
211 thread->PassSuspendBarrier();
212 }
213 }
214 break;
215 }
216 }
217 if (iterCount < MAX_SUSPEND_RETRIES) {
218 LockHolder lock(threadsLock_);
219 if (suspendNewCount_ != 0) {
220 // Someone has already suspended all threads.
221 // Wait until it finishes.
222 threadSuspendCondVar_.Wait(&threadsLock_);
223 }
224 } else {
225 LOG_ECMA(FATAL) << "Too many SuspendAll retries!";
226 UNREACHABLE();
227 }
228 }
229 barrier.Wait();
230 }
231
ResumeAllThreadsImpl(JSThread * current)232 void Runtime::ResumeAllThreadsImpl(JSThread *current)
233 {
234 LockHolder lock(threadsLock_);
235 if (suspendNewCount_ > 0) {
236 suspendNewCount_--;
237 }
238 if (suspendNewCount_ == 0) {
239 // Signal to waiting to suspend threads
240 threadSuspendCondVar_.Signal();
241 }
242 for (const auto& thread : threads_) {
243 if (thread != current) {
244 thread->ResumeThread(true);
245 }
246 }
247 }
248
IterateSharedRoot(RootVisitor & visitor)249 void Runtime::IterateSharedRoot(RootVisitor &visitor)
250 {
251 IterateSerializeRoot(visitor);
252 SharedModuleManager::GetInstance()->Iterate(visitor);
253 IterateCachedStringRoot(visitor);
254 }
255
IterateSerializeRoot(RootVisitor & v)256 void Runtime::IterateSerializeRoot(RootVisitor &v)
257 {
258 LockHolder lock(serializeLock_);
259 for (auto &it : serializeRootMap_) {
260 it.second->Iterate(v);
261 }
262 }
263
HasCachedConstpool(const JSPandaFile * jsPandaFile)264 bool Runtime::HasCachedConstpool(const JSPandaFile *jsPandaFile)
265 {
266 LockHolder lock(constpoolLock_);
267 return globalSharedConstpools_.find(jsPandaFile) != globalSharedConstpools_.end();
268 }
269
FindConstpool(const JSPandaFile * jsPandaFile,int32_t index)270 JSTaggedValue Runtime::FindConstpool(const JSPandaFile *jsPandaFile, int32_t index)
271 {
272 LockHolder lock(constpoolLock_);
273 return FindConstpoolUnlocked(jsPandaFile, index);
274 }
275
FindConstpoolUnlocked(const JSPandaFile * jsPandaFile,int32_t index)276 JSTaggedValue Runtime::FindConstpoolUnlocked(const JSPandaFile *jsPandaFile, int32_t index)
277 {
278 auto iter = globalSharedConstpools_.find(jsPandaFile);
279 if (iter == globalSharedConstpools_.end()) {
280 return JSTaggedValue::Hole();
281 }
282 auto constpoolIter = iter->second.find(index);
283 if (constpoolIter == iter->second.end()) {
284 return JSTaggedValue::Hole();
285 }
286 return constpoolIter->second;
287 }
288
AddOrUpdateConstpool(const JSPandaFile * jsPandaFile,JSHandle<ConstantPool> constpool,int32_t index)289 JSHandle<ConstantPool> Runtime::AddOrUpdateConstpool(const JSPandaFile *jsPandaFile,
290 JSHandle<ConstantPool> constpool,
291 int32_t index)
292 {
293 LockHolder lock(constpoolLock_);
294 if (globalSharedConstpools_.find(jsPandaFile) == globalSharedConstpools_.end()) {
295 globalSharedConstpools_[jsPandaFile] = CMap<int32_t, JSTaggedValue>();
296 }
297 auto &constpoolMap = globalSharedConstpools_[jsPandaFile];
298 auto iter = constpoolMap.find(index);
299 if (iter == constpoolMap.end()) {
300 int32_t constpoolIndex = GetAndIncreaseSharedConstpoolCount();
301 constpool->SetUnsharedConstpoolIndex(JSTaggedValue(constpoolIndex));
302 constpoolMap.insert({index, constpool.GetTaggedValue()});
303 return constpool;
304 }
305 return JSHandle<ConstantPool>(reinterpret_cast<uintptr_t>(&iter->second));
306 }
307
FindConstpools(const JSPandaFile * jsPandaFile)308 std::optional<std::reference_wrapper<CMap<int32_t, JSTaggedValue>>> Runtime::FindConstpools(
309 const JSPandaFile *jsPandaFile)
310 {
311 LockHolder lock(constpoolLock_);
312 auto iter = globalSharedConstpools_.find(jsPandaFile);
313 if (iter == globalSharedConstpools_.end()) {
314 return std::nullopt;
315 }
316 return iter->second;
317 }
318
ProcessNativeDeleteInSharedGC(const WeakRootVisitor & visitor)319 void Runtime::ProcessNativeDeleteInSharedGC(const WeakRootVisitor &visitor)
320 {
321 // No need lock here, only shared gc will sweep shared constpool, meanwhile other threads are suspended.
322 auto iterator = globalSharedConstpools_.begin();
323 ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "Constpools:" + std::to_string(globalSharedConstpools_.size()));
324 while (iterator != globalSharedConstpools_.end()) {
325 auto &constpools = iterator->second;
326 auto constpoolIter = constpools.begin();
327 while (constpoolIter != constpools.end()) {
328 JSTaggedValue constpoolVal = constpoolIter->second;
329 if (constpoolVal.IsHeapObject()) {
330 TaggedObject *obj = constpoolVal.GetTaggedObject();
331 auto fwd = visitor(obj);
332 if (fwd == nullptr) {
333 int32_t constpoolIndex =
334 ConstantPool::Cast(constpoolVal.GetTaggedObject())->GetUnsharedConstpoolIndex();
335 EraseUnusedConstpool(iterator->first, constpoolIter->first, constpoolIndex);
336 constpoolIter = constpools.erase(constpoolIter);
337 // when shared constpool is not referenced by any objects,
338 // global unshared constpool count can be reuse.
339 freeSharedConstpoolIndex_.insert(constpoolIndex);
340 continue;
341 }
342 if (fwd != reinterpret_cast<TaggedObject *>(obj)) {
343 constpoolIter->second = JSTaggedValue(fwd);
344 }
345 }
346 ++constpoolIter;
347 }
348 if (constpools.size() == 0) {
349 LOG_ECMA(INFO) << "remove js pandafile by gc, file:" << iterator->first->GetJSPandaFileDesc();
350 JSPandaFileManager::GetInstance()->RemoveJSPandaFile(iterator->first);
351 iterator = globalSharedConstpools_.erase(iterator);
352 } else {
353 ++iterator;
354 }
355 }
356 }
357
EraseUnusedConstpool(const JSPandaFile * jsPandaFile,int32_t index,int32_t constpoolIndex)358 void Runtime::EraseUnusedConstpool(const JSPandaFile *jsPandaFile, int32_t index, int32_t constpoolIndex)
359 {
360 GCIterateThreadList([jsPandaFile, index, constpoolIndex](JSThread* thread) {
361 ASSERT(!thread->IsInRunningState());
362 auto context = thread->GetCurrentEcmaContext();
363 context->EraseUnusedConstpool(jsPandaFile, index, constpoolIndex);
364 });
365 }
366
ProcessSharedNativeDelete(const WeakRootVisitor & visitor)367 void Runtime::ProcessSharedNativeDelete(const WeakRootVisitor &visitor)
368 {
369 SharedHeap::GetInstance()->ProcessSharedNativeDelete(visitor);
370 }
371
PushToSharedNativePointerList(JSNativePointer * pointer)372 void Runtime::PushToSharedNativePointerList(JSNativePointer *pointer)
373 {
374 SharedHeap::GetInstance()->PushToSharedNativePointerList(pointer);
375 }
376
InvokeSharedNativePointerCallbacks()377 void Runtime::InvokeSharedNativePointerCallbacks()
378 {
379 auto &callbacks = GetSharedNativePointerCallbacks();
380 while (!callbacks.empty()) {
381 auto callbackPair = callbacks.back();
382 callbacks.pop_back();
383 ASSERT(callbackPair.first != nullptr && callbackPair.second.first != nullptr &&
384 callbackPair.second.second != nullptr);
385 auto callback = callbackPair.first;
386 (*callback)(nullptr, callbackPair.second.first, callbackPair.second.second);
387 }
388 }
389 } // namespace panda::ecmascript
390