1 /**
2 * Copyright (c) 2021-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 <gtest/gtest.h>
17 #include "runtime/include/runtime.h"
18 #include "runtime/include/panda_vm.h"
19 #include "runtime/include/coretypes/string.h"
20 #include "runtime/include/thread_scopes.h"
21 #include "runtime/mem/gc/gc.h"
22 #include "runtime/mem/gc/gc_trigger.h"
23 #include "runtime/handle_scope-inl.h"
24 #include "test_utils.h"
25
26 namespace ark::test {
27 class GCTriggerTest : public testing::Test {
28 public:
GCTriggerTest()29 GCTriggerTest()
30 {
31 RuntimeOptions options;
32 options.SetShouldLoadBootPandaFiles(false);
33 options.SetShouldInitializeIntrinsics(false);
34 options.SetGcTriggerType("adaptive-heap-trigger");
35
36 Runtime::Create(options);
37 thread_ = ark::MTManagedThread::GetCurrent();
38 thread_->ManagedCodeBegin();
39 }
40
~GCTriggerTest()41 ~GCTriggerTest() override
42 {
43 thread_->ManagedCodeEnd();
44 Runtime::Destroy();
45 }
46
47 NO_COPY_SEMANTIC(GCTriggerTest);
48 NO_MOVE_SEMANTIC(GCTriggerTest);
49
CreateGCTriggerHeap() const50 [[nodiscard]] mem::GCAdaptiveTriggerHeap *CreateGCTriggerHeap() const
51 {
52 return new mem::GCAdaptiveTriggerHeap(nullptr, nullptr, MIN_HEAP_SIZE,
53 mem::GCTriggerHeap::DEFAULT_PERCENTAGE_THRESHOLD,
54 DEFAULT_INCREASE_MULTIPLIER, MIN_EXTRA_HEAP_SIZE, MAX_EXTRA_HEAP_SIZE);
55 }
56
GetTargetFootprint(const mem::GCAdaptiveTriggerHeap * trigger)57 static size_t GetTargetFootprint(const mem::GCAdaptiveTriggerHeap *trigger)
58 {
59 // Atomic with relaxed order reason: simple getter for test
60 return trigger->targetFootprint_.load(std::memory_order_relaxed);
61 }
62
63 protected:
64 static constexpr size_t MIN_HEAP_SIZE = 8_MB;
65 static constexpr size_t MIN_EXTRA_HEAP_SIZE = 1_MB;
66 static constexpr size_t MAX_EXTRA_HEAP_SIZE = 8_MB;
67 static constexpr uint32_t DEFAULT_INCREASE_MULTIPLIER = 3U;
68
69 private:
70 MTManagedThread *thread_ = nullptr;
71 };
72
TEST_F(GCTriggerTest,ThresholdTest)73 TEST_F(GCTriggerTest, ThresholdTest)
74 {
75 static constexpr size_t BEFORE_HEAP_SIZE = 50_MB;
76 static constexpr size_t CURRENT_HEAP_SIZE = MIN_HEAP_SIZE;
77 static constexpr size_t FIRST_THRESHOLD = 2U * MIN_HEAP_SIZE;
78 static constexpr size_t HEAP_SIZE_AFTER_BASE_TRIGGER = (BEFORE_HEAP_SIZE + CURRENT_HEAP_SIZE) / 2U;
79 auto *trigger = CreateGCTriggerHeap();
80 GCTask task(GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE);
81
82 trigger->ComputeNewTargetFootprint(task, BEFORE_HEAP_SIZE, CURRENT_HEAP_SIZE);
83
84 ASSERT_EQ(GetTargetFootprint(trigger), FIRST_THRESHOLD);
85
86 trigger->ComputeNewTargetFootprint(task, BEFORE_HEAP_SIZE, CURRENT_HEAP_SIZE);
87 ASSERT_EQ(GetTargetFootprint(trigger), HEAP_SIZE_AFTER_BASE_TRIGGER);
88 trigger->ComputeNewTargetFootprint(task, BEFORE_HEAP_SIZE, CURRENT_HEAP_SIZE);
89 ASSERT_EQ(GetTargetFootprint(trigger), HEAP_SIZE_AFTER_BASE_TRIGGER);
90 trigger->ComputeNewTargetFootprint(task, BEFORE_HEAP_SIZE, CURRENT_HEAP_SIZE);
91 ASSERT_EQ(GetTargetFootprint(trigger), HEAP_SIZE_AFTER_BASE_TRIGGER);
92 trigger->ComputeNewTargetFootprint(task, BEFORE_HEAP_SIZE, CURRENT_HEAP_SIZE);
93
94 // Check that we could to avoid locale triggering
95 ASSERT_EQ(GetTargetFootprint(trigger), FIRST_THRESHOLD);
96
97 delete trigger;
98 }
99
100 class GCChecker : public mem::GCListener {
101 public:
GCFinished(const GCTask & task,size_t heapSizeBeforeGc,size_t heapSize)102 void GCFinished(const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
103 [[maybe_unused]] size_t heapSize) override
104 {
105 reason_ = task.reason;
106 counter_++;
107 }
108
GetCause() const109 GCTaskCause GetCause() const
110 {
111 return reason_;
112 }
113
GetCounter() const114 size_t GetCounter() const
115 {
116 return counter_;
117 }
118
119 private:
120 GCTaskCause reason_ = GCTaskCause::INVALID_CAUSE;
121 size_t counter_ {0};
122 };
123
GetRuntimeOptions(const std::string & triggerType)124 static RuntimeOptions GetRuntimeOptions(const std::string &triggerType)
125 {
126 RuntimeOptions options;
127 options.SetShouldLoadBootPandaFiles(false);
128 options.SetShouldInitializeIntrinsics(false);
129 if (triggerType == "debug-never") {
130 options.SetGcTriggerType("debug-never");
131 options.SetGcUseNthAllocTrigger(true);
132 } else if (triggerType == "pause-time-goal-trigger") {
133 options.SetGcTriggerType("pause-time-goal-trigger");
134 options.SetRunGcInPlace(true);
135 constexpr size_t YOUNG_SIZE = 512 * 1024;
136 options.SetYoungSpaceSize(YOUNG_SIZE);
137 options.SetInitYoungSpaceSize(YOUNG_SIZE);
138 } else {
139 UNREACHABLE();
140 }
141 return options;
142 }
143
TEST(SchedGCOnNthAllocTriggerTest,TestTrigger)144 TEST(SchedGCOnNthAllocTriggerTest, TestTrigger)
145 {
146 Runtime::Create(GetRuntimeOptions("debug-never"));
147 ManagedThread *thread = ark::ManagedThread::GetCurrent();
148 thread->ManagedCodeBegin();
149 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
150 PandaVM *vm = Runtime::GetCurrent()->GetPandaVM();
151 auto *trigger = vm->GetGCTrigger();
152 ASSERT_EQ(mem::GCTriggerType::ON_NTH_ALLOC, trigger->GetType());
153 auto *schedTrigger = reinterpret_cast<mem::SchedGCOnNthAllocTrigger *>(trigger);
154 GCChecker checker;
155 vm->GetGC()->AddListener(&checker);
156
157 schedTrigger->ScheduleGc(GCTaskCause::YOUNG_GC_CAUSE, 2);
158 coretypes::String::CreateEmptyString(ctx, vm);
159 EXPECT_FALSE(schedTrigger->IsTriggered());
160 EXPECT_EQ(GCTaskCause::INVALID_CAUSE, checker.GetCause());
161 coretypes::String::CreateEmptyString(ctx, vm);
162 EXPECT_EQ(GCTaskCause::YOUNG_GC_CAUSE, checker.GetCause());
163 EXPECT_TRUE(schedTrigger->IsTriggered());
164
165 thread->ManagedCodeEnd();
166 Runtime::Destroy();
167 }
168
TEST(PauseTimeGoalTriggerTest,TestTrigger)169 TEST(PauseTimeGoalTriggerTest, TestTrigger)
170 {
171 Runtime::Create(GetRuntimeOptions("pause-time-goal-trigger"));
172 auto *thread = ark::ManagedThread::GetCurrent();
173 {
174 ScopedManagedCodeThread s(thread);
175 HandleScope<ObjectHeader *> scope(thread);
176
177 auto *runtime = Runtime::GetCurrent();
178 auto ctx = runtime->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
179 auto *vm = runtime->GetPandaVM();
180 auto *trigger = vm->GetGCTrigger();
181 ASSERT_EQ(mem::GCTriggerType::PAUSE_TIME_GOAL_TRIGGER, trigger->GetType());
182 GCChecker checker;
183 vm->GetGC()->AddListener(&checker);
184
185 auto *pauseTimeGoalTrigger = static_cast<ark::mem::PauseTimeGoalTrigger *>(trigger);
186 constexpr size_t INIT_TARGET_FOOTPRINT = 1258291;
187 ASSERT_EQ(INIT_TARGET_FOOTPRINT, pauseTimeGoalTrigger->GetTargetFootprint());
188
189 constexpr size_t ARRAY_LENGTH = 5 * 32 * 1024; // big enough to provoke several collections
190 VMHandle<coretypes::String> dummy(thread, coretypes::String::CreateEmptyString(ctx, vm));
191 VMHandle<coretypes::Array> array(
192 thread, ark::mem::ObjectAllocator::AllocArray(ARRAY_LENGTH, ClassRoot::ARRAY_STRING, false));
193
194 size_t expectedCounter = 1;
195 size_t startIdx = 0;
196 for (size_t i = 0; i < ARRAY_LENGTH; i++) {
197 array->Set(i, coretypes::String::CreateEmptyString(ctx, vm));
198
199 if (expectedCounter == checker.GetCounter()) {
200 // objects become garbage
201 for (size_t j = startIdx; j < i; j++) {
202 array->Set(j, dummy.GetPtr());
203 startIdx = i;
204 }
205 if (expectedCounter < 2) {
206 ASSERT_EQ(GCTaskCause::YOUNG_GC_CAUSE, checker.GetCause());
207 } else if (expectedCounter == 2) {
208 ASSERT_EQ(GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE, checker.GetCause());
209 } else if (expectedCounter == 3) {
210 ASSERT_EQ(GCTaskCause::YOUNG_GC_CAUSE, checker.GetCause());
211 } else if (expectedCounter > 3) {
212 break;
213 }
214
215 expectedCounter++;
216 }
217 }
218
219 ASSERT_GT(expectedCounter, 3);
220
221 // previous mixed collection should update target footprint
222 ASSERT_GT(pauseTimeGoalTrigger->GetTargetFootprint(), INIT_TARGET_FOOTPRINT);
223 }
224 Runtime::Destroy();
225 }
226 } // namespace ark::test
227