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/mem/idle_gc_trigger.h" 17 18#include <algorithm> 19 20#include "ecmascript/mem/mem_controller.h" 21#include "ecmascript/mem/shared_mem_controller.h" 22#include "ecmascript/mem/concurrent_marker.h" 23#include "ecmascript/mem/heap-inl.h" 24 25namespace panda::ecmascript { 26void IdleGCTrigger::NotifyVsyncIdleStart() 27{ 28 TryTriggerIdleLocalOldGC(); 29} 30 31void IdleGCTrigger::NotifyLooperIdleStart([[maybe_unused]] int64_t timestamp, [[maybe_unused]] int idleTime) 32{ 33 LOG_ECMA_IF(optionalLogEnabled_, DEBUG) << "IdleGCTrigger: recv once looper idle time"; 34 idleState_.store(true); 35 if (!TryTriggerIdleLocalOldGC()) { 36 TryTriggerIdleSharedOldGC(); 37 } 38} 39 40void IdleGCTrigger::NotifyLooperIdleEnd([[maybe_unused]] int64_t timestamp) 41{ 42 idleState_.store(false); 43} 44 45void IdleGCTrigger::TryTriggerHandleMarkFinished() 46{ 47 // wait sharedGC finish 48 if (!thread_->IsReadyToSharedConcurrentMark()) { 49 return ; 50 } 51 if (heap_->GetJSThread()->IsMarkFinished() && heap_->GetConcurrentMarker()->IsTriggeredConcurrentMark() 52 && !heap_->GetOnSerializeEvent() && !heap_->InSensitiveStatus()) { 53 heap_->SetCanThrowOOMError(false); 54 heap_->GetConcurrentMarker()->HandleMarkingFinished(); 55 heap_->SetCanThrowOOMError(true); 56 } 57} 58 59void IdleGCTrigger::TryTriggerLocalConcurrentMark() 60{ 61 if (heap_->GetConcurrentMarker()->IsEnabled() && heap_->CheckCanTriggerConcurrentMarking()) { 62 heap_->SetFullMarkRequestedState(true); 63 heap_->SetMarkType(MarkType::MARK_FULL); 64 heap_->TriggerConcurrentMarking(); 65 } 66} 67 68bool IdleGCTrigger::TryTriggerIdleLocalOldGC() 69{ 70 if (heap_->GetJSThread()->IsMarkFinished() && 71 heap_->GetConcurrentMarker()->IsTriggeredConcurrentMark()) { 72 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_REMARK); 73 return true; 74 } 75 if (!IsPossiblePostGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_CONCURRENT_MARK) || 76 !heap_->CheckCanTriggerConcurrentMarking()) { 77 return true; 78 } 79 if (CheckIdleOrHintOldGC<Heap>(heap_) && ReachIdleLocalOldGCThresholds() && !heap_->NeedStopCollection()) { 80 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_CONCURRENT_MARK); 81 return true; 82 } 83 return false; 84} 85 86bool IdleGCTrigger::TryTriggerIdleSharedOldGC() 87{ 88 if (CheckIdleOrHintOldGC<SharedHeap>(sHeap_) && 89 ReachIdleSharedGCThresholds() && !sHeap_->NeedStopCollection() && 90 sHeap_->CheckCanTriggerConcurrentMarking(thread_)) { 91 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::SHARED_CONCURRENT_MARK); 92 return true; 93 } 94 return false; 95} 96 97bool IdleGCTrigger::ReachIdleLocalOldGCThresholds() 98{ 99 bool isFullMarking = heap_->IsConcurrentFullMark() && heap_->GetJSThread()->IsMarking(); 100 bool isNativeSizeLargeTrigger = isFullMarking ? false : heap_->GlobalNativeSizeLargerThanLimitForIdle(); 101 if (isNativeSizeLargeTrigger) { 102 return true; 103 } 104 105 OldSpace *oldSpace = heap_->GetOldSpace(); 106 HugeObjectSpace *hugeObjectSpace = heap_->GetHugeObjectSpace(); 107 size_t idleSizeLimit = static_cast<size_t>(oldSpace->GetInitialCapacity() * 108 IDLE_SPACE_SIZE_LIMIT_RATE); 109 size_t currentSize = oldSpace->GetHeapObjectSize() + hugeObjectSpace->GetHeapObjectSize(); 110 if (currentSize >= idleSizeLimit) { 111 return true; 112 } 113 114 size_t maxCapacity = oldSpace->GetMaximumCapacity() + oldSpace->GetOvershootSize() + 115 oldSpace->GetOutOfMemoryOvershootSize(); 116 size_t currentCapacity = oldSpace->GetCommittedSize() + hugeObjectSpace->GetCommittedSize(); 117 size_t idleCapacityLimit = static_cast<size_t>(maxCapacity * IDLE_SPACE_SIZE_LIMIT_RATE); 118 if (currentCapacity >= idleCapacityLimit) { 119 return true; 120 } 121 122 size_t oldSpaceAllocLimit = heap_->GetGlobalSpaceAllocLimit() + oldSpace->GetOvershootSize(); 123 size_t idleOldSpaceAllocLimit = static_cast<size_t>(oldSpaceAllocLimit * IDLE_SPACE_SIZE_LIMIT_RATE); 124 if (heap_->GetHeapObjectSize() > idleOldSpaceAllocLimit) { 125 return true; 126 } 127 return false; 128} 129 130bool IdleGCTrigger::ReachIdleSharedGCThresholds() 131{ 132 size_t expectSizeLimit = sHeap_->GetOldSpace()->GetInitialCapacity() * IDLE_SPACE_SIZE_LIMIT_RATE; 133 size_t currentOldSize = sHeap_->GetOldSpace()->GetHeapObjectSize(); 134 size_t expectGlobalSizeLimit = sHeap_->GetGlobalSpaceAllocLimit() * IDLE_SPACE_SIZE_LIMIT_RATE; 135 return currentOldSize > expectSizeLimit || sHeap_->GetHeapObjectSize() > expectGlobalSizeLimit; 136} 137 138void IdleGCTrigger::TryPostHandleMarkFinished() 139{ 140 if (IsIdleState()) { 141 PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_REMARK); 142 } 143} 144 145void IdleGCTrigger::PostIdleGCTask(TRIGGER_IDLE_GC_TYPE gcType) 146{ 147 if (triggerGCTaskCallback_ != nullptr && IsPossiblePostGCTask(gcType) && !heap_->NeedStopCollection()) { 148 std::pair<void*, uint8_t> data(heap_->GetEcmaVM(), static_cast<uint8_t>(gcType)); 149 triggerGCTaskCallback_(data); 150 SetPostGCTask(gcType); 151 LOG_GC(INFO) << "IdleGCTrigger: post once " << GetGCTypeName(gcType) << " on idleTime"; 152 } 153 LOG_GC(DEBUG) << "IdleGCTrigger: failed to post once " << GetGCTypeName(gcType); 154} 155 156bool IdleGCTrigger::CheckIdleYoungGC() const 157{ 158 auto newSpace = heap_->GetNewSpace(); 159 LOG_GC(DEBUG) << "IdleGCTrigger: check young GC semi Space size since gc:" 160 << newSpace->GetAllocatedSizeSinceGC(newSpace->GetTop()); 161 return newSpace->GetAllocatedSizeSinceGC(newSpace->GetTop()) > IDLE_MIN_EXPECT_RECLAIM_SIZE; 162} 163 164void IdleGCTrigger::TryTriggerIdleGC(TRIGGER_IDLE_GC_TYPE gcType) 165{ 166 LOG_GC(DEBUG) << "IdleGCTrigger: recv once notify of " << GetGCTypeName(gcType); 167 switch (gcType) { 168 case TRIGGER_IDLE_GC_TYPE::FULL_GC: 169 if (CheckIdleOrHintFullGC<Heap>(heap_) && !heap_->NeedStopCollection()) { 170 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType); 171 heap_->CollectGarbage(TriggerGCType::FULL_GC, GCReason::IDLE); 172 } else if (CheckIdleYoungGC() && !heap_->NeedStopCollection()) { 173 LOG_GC(INFO) << "IdleGCTrigger: trigger young gc"; 174 heap_->CollectGarbage(TriggerGCType::YOUNG_GC, GCReason::IDLE); 175 } 176 break; 177 case TRIGGER_IDLE_GC_TYPE::SHARED_CONCURRENT_MARK: 178 if (CheckIdleOrHintOldGC<SharedHeap>(sHeap_) && sHeap_->CheckCanTriggerConcurrentMarking(thread_) 179 && !sHeap_->NeedStopCollection()) { 180 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType); 181 sHeap_->TriggerConcurrentMarking<TriggerGCType::SHARED_GC, GCReason::IDLE>(thread_); 182 } 183 break; 184 case TRIGGER_IDLE_GC_TYPE::SHARED_FULL_GC: 185 if (CheckIdleOrHintFullGC<SharedHeap>(sHeap_) && !sHeap_->NeedStopCollection()) { 186 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType); 187 sHeap_->CollectGarbage<TriggerGCType::SHARED_FULL_GC, GCReason::IDLE>(thread_); 188 } 189 break; 190 case TRIGGER_IDLE_GC_TYPE::LOCAL_CONCURRENT_MARK: 191 if (CheckIdleOrHintOldGC<Heap>(heap_) && !heap_->NeedStopCollection()) { 192 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType); 193 TryTriggerLocalConcurrentMark(); 194 } 195 break; 196 case TRIGGER_IDLE_GC_TYPE::LOCAL_REMARK: 197 if (!heap_->NeedStopCollection()) { 198 LOG_GC(INFO) << "IdleGCTrigger: trigger " << GetGCTypeName(gcType); 199 TryTriggerHandleMarkFinished(); 200 } 201 break; 202 default: // LCOV_EXCL_BR_LINE 203 LOG_GC(ERROR) << "IdleGCTrigger: this branch is unreachable"; 204 return; 205 } 206 ClearPostGCTask(gcType); 207} 208}