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