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 
25 namespace panda::ecmascript {
NotifyVsyncIdleStart()26 void IdleGCTrigger::NotifyVsyncIdleStart()
27 {
28     TryTriggerIdleLocalOldGC();
29 }
30 
NotifyLooperIdleStart([[maybe_unused]] int64_t timestamp, [[maybe_unused]] int idleTime)31 void 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 
NotifyLooperIdleEnd([[maybe_unused]] int64_t timestamp)40 void IdleGCTrigger::NotifyLooperIdleEnd([[maybe_unused]] int64_t timestamp)
41 {
42     idleState_.store(false);
43 }
44 
TryTriggerHandleMarkFinished()45 void 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 
TryTriggerLocalConcurrentMark()59 void 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 
TryTriggerIdleLocalOldGC()68 bool 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 
TryTriggerIdleSharedOldGC()86 bool 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 
ReachIdleLocalOldGCThresholds()97 bool 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 
ReachIdleSharedGCThresholds()130 bool 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 
TryPostHandleMarkFinished()138 void IdleGCTrigger::TryPostHandleMarkFinished()
139 {
140     if (IsIdleState()) {
141         PostIdleGCTask(TRIGGER_IDLE_GC_TYPE::LOCAL_REMARK);
142     }
143 }
144 
PostIdleGCTask(TRIGGER_IDLE_GC_TYPE gcType)145 void 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 
CheckIdleYoungGC() const156 bool 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 
TryTriggerIdleGC(TRIGGER_IDLE_GC_TYPE gcType)164 void 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 }