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}