• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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