1 /**
2 * Copyright (c) 2021-2022 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 #include <array>
16 #include <atomic>
17 #include <chrono>
18 #include <condition_variable>
19 #include <limits>
20 #include <thread>
21
22 #include "gtest/gtest.h"
23 #include "iostream"
24 #include "runtime/include/coretypes/string.h"
25 #include "runtime/include/runtime.h"
26 #include "runtime/include/panda_vm.h"
27 #include "runtime/handle_scope-inl.h"
28 #include "runtime/mem/gc/g1/g1-allocator.h"
29 #include "runtime/mem/gc/generational-gc-base.h"
30 #include "runtime/mem/malloc-proxy-allocator-inl.h"
31 #include "runtime/mem/mem_stats.h"
32 #include "runtime/mem/mem_stats_default.h"
33 #include "runtime/mem/runslots_allocator-inl.h"
34
35 namespace panda::mem::test {
36
37 class G1GCFullGCTest : public testing::Test {
38 public:
39 using ObjVec = PandaVector<ObjectHeader *>;
40 using HanVec = PandaVector<VMHandle<ObjectHeader *> *>;
41 static constexpr size_t ROOT_MAX_SIZE = 100000U;
42
43 static constexpr GCTaskCause MIXED_G1_GC_CAUSE = GCTaskCause::YOUNG_GC_CAUSE;
44 static constexpr GCTaskCause FULL_GC_CAUSE = GCTaskCause::EXPLICIT_CAUSE;
45
46 enum class TargetSpace { YOUNG, TENURED, LARGE, HUMONG };
47
48 class GCCounter : public GCListener {
49 public:
GCStarted(size_t heap_size)50 void GCStarted([[maybe_unused]] size_t heap_size) override
51 {
52 count++;
53 }
54
GCFinished(const GCTask & task,size_t heap_size_before_gc,size_t heap_size)55 void GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heap_size_before_gc,
56 [[maybe_unused]] size_t heap_size) override
57 {
58 }
59
60 int count = 0;
61 };
62
63 template <typename F>
64 class GCHook : public GCListener {
65 public:
GCHook(F hook)66 explicit GCHook(F hook) : hook_(hook) {};
67
GCStarted(size_t heap_size)68 void GCStarted([[maybe_unused]] size_t heap_size) override {}
69
GCFinished(const GCTask & task,size_t heap_size_before_gc,size_t heap_size)70 void GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heap_size_before_gc,
71 [[maybe_unused]] size_t heap_size) override
72 {
73 hook_();
74 }
75
76 F hook_;
77 };
78
SetupRuntime(const std::string & gc_type)79 void SetupRuntime(const std::string &gc_type)
80 {
81 RuntimeOptions options;
82 options.SetShouldLoadBootPandaFiles(false);
83 options.SetShouldInitializeIntrinsics(false);
84 options.SetUseTlabForAllocations(false);
85 options.SetGcType(gc_type);
86 options.SetGcTriggerType("debug");
87 options.SetGcDebugTriggerStart(std::numeric_limits<int>::max());
88 options.SetCompilerEnableJit(false);
89 options.SetHeapSizeLimit(33554432);
90 options.SetYoungSpaceSize(4194304);
91 [[maybe_unused]] bool success = Runtime::Create(options);
92 ASSERT(success);
93
94 thread_ = panda::MTManagedThread::GetCurrent();
95 gc_type_ = Runtime::GetGCType(options, plugins::RuntimeTypeToLang(options.GetRuntimeType()));
96 [[maybe_unused]] auto gc = thread_->GetVM()->GetGC();
97 ASSERT(gc->GetType() == panda::mem::GCTypeFromString(gc_type));
98 ASSERT(gc->IsGenerational());
99 thread_->ManagedCodeBegin();
100 }
101
ResetRuntime()102 void ResetRuntime()
103 {
104 DeleteHandles();
105 internal_allocator_->Delete(gccnt_);
106 thread_->ManagedCodeEnd();
107 bool success = Runtime::Destroy();
108 ASSERT_TRUE(success) << "Cannot destroy Runtime";
109 }
110
111 template <typename F, size_t multi>
112 ObjVec MakeAllocations(size_t min_size, size_t max_size, size_t count, size_t *allocated, size_t *requested,
113 F space_checker, bool check_oom_in_tenured = false);
114
115 void InitRoot();
116 void MakeObjectsAlive(ObjVec objects, int every = 1);
117 void MakeObjectsPermAlive(ObjVec objects, int every = 1);
118 void MakeObjectsGarbage(size_t start_idx, size_t after_end_idx, int every = 1);
119 void DumpHandles();
120 void DumpAliveObjects();
121 void DeleteHandles();
122 bool IsInYoung(uintptr_t addr);
123
124 template <class LanguageConfig>
125 void PrepareTest();
126
TearDown()127 void TearDown() override {}
128
129 panda::MTManagedThread *thread_;
130 GCType gc_type_;
131
132 LanguageContext ctx_ {nullptr};
133 ObjectAllocatorBase *object_allocator_;
134 mem::InternalAllocatorPtr internal_allocator_;
135 PandaVM *vm_;
136 GC *gc_;
137 std::vector<HanVec> handles_;
138 MemStatsType *ms_;
139 GCStats *gc_ms_;
140 coretypes::Array *root_ = nullptr;
141 size_t root_size_ = 0;
142 GCCounter *gccnt_;
143 };
144
145 template <typename F, size_t multi>
MakeAllocations(size_t min_size,size_t max_size,size_t count,size_t * allocated,size_t * requested,F space_checker,bool check_oom_in_tenured)146 G1GCFullGCTest::ObjVec G1GCFullGCTest::MakeAllocations(size_t min_size, size_t max_size, size_t count,
147 size_t *allocated, size_t *requested,
148 [[maybe_unused]] F space_checker, bool check_oom_in_tenured)
149 {
150 ASSERT(min_size <= max_size);
151 *allocated = 0;
152 *requested = 0;
153 // Create array of object templates based on count and max size
154 PandaVector<PandaString> obj_templates(count);
155 size_t obj_size = sizeof(coretypes::String) + min_size;
156 for (size_t i = 0; i < count; ++i) {
157 PandaString simple_string;
158 simple_string.resize(obj_size - sizeof(coretypes::String));
159 obj_templates[i] = std::move(simple_string);
160 obj_size += (max_size / count + i); // +i to mess with the alignment
161 if (obj_size > max_size) {
162 obj_size = max_size;
163 }
164 }
165 ObjVec result;
166 result.reserve(count * multi);
167 for (size_t j = 0; j < count; ++j) {
168 size_t size = obj_templates[j].length() + sizeof(coretypes::String);
169 if (check_oom_in_tenured) {
170 // Leaving 5MB in tenured seems OK
171 auto free =
172 reinterpret_cast<GenerationalSpaces *>(object_allocator_->GetHeapSpace())->GetCurrentFreeTenuredSize();
173 if (size + 5000000 > free) {
174 return result;
175 }
176 }
177 for (size_t i = 0; i < multi; ++i) {
178 coretypes::String *string_obj = coretypes::String::CreateFromMUtf8(
179 reinterpret_cast<const uint8_t *>(&obj_templates[j][0]), obj_templates[j].length(), ctx_, vm_);
180 ASSERT(string_obj != nullptr);
181 ASSERT(space_checker(ToUintPtr(string_obj)));
182 *allocated += GetAlignedObjectSize(size);
183 *requested += size;
184 result.push_back(string_obj);
185 }
186 }
187 return result;
188 }
189
InitRoot()190 void G1GCFullGCTest::InitRoot()
191 {
192 ClassLinker *class_linker = Runtime::GetCurrent()->GetClassLinker();
193 Class *klass = class_linker->GetExtension(
194 panda_file::SourceLang::PANDA_ASSEMBLY)->GetClass(ctx_.GetStringArrayClassDescriptor());
195 ASSERT_NE(klass, nullptr);
196 root_ = coretypes::Array::Create(klass, ROOT_MAX_SIZE);
197 root_size_ = 0;
198 MakeObjectsPermAlive({root_});
199 }
200
MakeObjectsAlive(ObjVec objects,int every)201 void G1GCFullGCTest::MakeObjectsAlive(ObjVec objects, int every)
202 {
203 int cnt = every;
204 for (auto *obj : objects) {
205 cnt--;
206 if (cnt != 0) {
207 continue;
208 }
209 root_->Set(root_size_, obj);
210 root_size_++;
211 ASSERT(root_size_ < ROOT_MAX_SIZE);
212 cnt = every;
213 }
214 }
215
MakeObjectsGarbage(size_t start_idx,size_t after_end_idx,int every)216 void G1GCFullGCTest::MakeObjectsGarbage(size_t start_idx, size_t after_end_idx, int every)
217 {
218 int cnt = every;
219 for (size_t i = start_idx; i < after_end_idx; ++i) {
220 cnt--;
221 if (cnt != 0) {
222 continue;
223 }
224 root_->Set(i, 0);
225 root_size_++;
226 cnt = every;
227 }
228 }
229
MakeObjectsPermAlive(ObjVec objects,int every)230 void G1GCFullGCTest::MakeObjectsPermAlive(ObjVec objects, int every)
231 {
232 HanVec result;
233 result.reserve(objects.size() / every);
234 int cnt = every;
235 for (auto *obj : objects) {
236 cnt--;
237 if (cnt != 0) {
238 continue;
239 }
240 result.push_back(internal_allocator_->New<VMHandle<ObjectHeader *>>(thread_, obj));
241 cnt = every;
242 }
243 handles_.push_back(result);
244 }
245
DumpHandles()246 void G1GCFullGCTest::DumpHandles()
247 {
248 for (auto &hv : handles_) {
249 for (auto *handle : hv) {
250 std::cout << "vector " << (void *)&hv << " handle " << (void *)handle << " obj " << handle->GetPtr()
251 << std::endl;
252 }
253 }
254 }
255
DumpAliveObjects()256 void G1GCFullGCTest::DumpAliveObjects()
257 {
258 std::cout << "Alive root array : " << handles_[0][0]->GetPtr() << std::endl;
259 for (size_t i = 0; i < root_size_; ++i) {
260 if (root_->Get<ObjectHeader *>(i) != nullptr) {
261 std::cout << "Alive idx " << i << " : " << root_->Get<ObjectHeader *>(i) << std::endl;
262 }
263 }
264 }
265
DeleteHandles()266 void G1GCFullGCTest::DeleteHandles()
267 {
268 for (auto &hv : handles_) {
269 for (auto *handle : hv) {
270 internal_allocator_->Delete(handle);
271 }
272 }
273 handles_.clear();
274 }
275
276 template <class LanguageConfig>
PrepareTest()277 void G1GCFullGCTest::PrepareTest()
278 {
279 if constexpr (std::is_same<LanguageConfig, panda::PandaAssemblyLanguageConfig>::value) {
280 DeleteHandles();
281 ctx_ = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
282 object_allocator_ = thread_->GetVM()->GetHeapManager()->GetObjectAllocator().AsObjectAllocator();
283 vm_ = Runtime::GetCurrent()->GetPandaVM();
284 internal_allocator_ = Runtime::GetCurrent()->GetClassLinker()->GetAllocator();
285 gc_ = vm_->GetGC();
286 ms_ = vm_->GetMemStats();
287 gc_ms_ = vm_->GetGCStats();
288 gccnt_ = internal_allocator_->New<GCCounter>();
289 gc_->AddListener(gccnt_);
290 InitRoot();
291 } else {
292 UNREACHABLE(); // NYI
293 }
294 }
295
IsInYoung(uintptr_t addr)296 bool G1GCFullGCTest::IsInYoung(uintptr_t addr)
297 {
298 switch (gc_type_) {
299 case GCType::GEN_GC: {
300 return object_allocator_->IsAddressInYoungSpace(addr);
301 }
302 case GCType::G1_GC: {
303 auto mem_pool = PoolManager::GetMmapMemPool();
304 if (mem_pool->GetSpaceTypeForAddr(reinterpret_cast<ObjectHeader *>(addr)) != SpaceType::SPACE_TYPE_OBJECT) {
305 return false;
306 }
307 return Region::AddrToRegion<false>(ToVoidPtr(addr))->HasFlag(RegionFlag::IS_EDEN);
308 }
309 default:
310 UNREACHABLE(); // NYI
311 }
312 return false;
313 }
314
TEST_F(G1GCFullGCTest,TestIntensiveAlloc)315 TEST_F(G1GCFullGCTest, TestIntensiveAlloc)
316 {
317 std::string gctype = static_cast<std::string>(GCStringFromType(GCType::G1_GC));
318 SetupRuntime(gctype);
319 {
320 HandleScope<ObjectHeader *> scope(thread_);
321 PrepareTest<panda::PandaAssemblyLanguageConfig>();
322 [[maybe_unused]] size_t bytes, raw_objects_size;
323
324 [[maybe_unused]] size_t young_size =
325 reinterpret_cast<GenerationalSpaces *>(reinterpret_cast<ObjectAllocatorGenBase *>(
326 object_allocator_)->GetHeapSpace())->GetCurrentMaxYoungSize();
327 [[maybe_unused]] size_t heap_size = mem::MemConfig::GetHeapSizeLimit();
328 [[maybe_unused]] auto g1_alloc = reinterpret_cast<ObjectAllocatorG1<MT_MODE_MULTI> *>(object_allocator_);
329 [[maybe_unused]] size_t max_y_size = g1_alloc->GetYoungAllocMaxSize();
330
331 [[maybe_unused]] auto y_space_check = [](uintptr_t addr) -> bool { return IsInYoung(addr); };
332 [[maybe_unused]] auto h_space_check = [](uintptr_t addr) -> bool { return !IsInYoung(addr); };
333 [[maybe_unused]] auto t_free =
334 reinterpret_cast<GenerationalSpaces *>(object_allocator_->GetHeapSpace())->GetCurrentFreeTenuredSize();
335 const size_t y_obj_size = max_y_size / 10;
336 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
337 size_t initial_heap = ms_->GetFootprintHeap();
338
339 {
340 // Ordinary young shall not be broken when intermixed with explicits
341 int i = 0;
342 size_t allocated = 0;
343 while (allocated < 2 * heap_size) {
344 ObjVec ov1 = MakeAllocations<decltype(y_space_check), 1>(y_obj_size, y_obj_size, 1, &bytes,
345 &raw_objects_size, y_space_check);
346 allocated += bytes;
347 if (i++ % 100 == 0) {
348 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
349 }
350 }
351 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
352 ASSERT_EQ(initial_heap, ms_->GetFootprintHeap());
353 }
354
355 {
356 // Intensive allocations surviving 1 young
357 auto old_root_size = root_size_;
358 size_t allocated = 0;
359 bool gc_happened = false;
360 GCHook gchook([&old_root_size, this, &gc_happened]() {
361 MakeObjectsGarbage(old_root_size, this->root_size_);
362 old_root_size = this->root_size_;
363 gc_happened = 1;
364 });
365 gc_->AddListener(&gchook);
366 while (allocated < 4 * heap_size) {
367 ObjVec ov1 = MakeAllocations<decltype(y_space_check), 1>(y_obj_size, y_obj_size, 1, &bytes,
368 &raw_objects_size, y_space_check);
369 MakeObjectsAlive(ov1, 1);
370 t_free = reinterpret_cast<GenerationalSpaces *>(
371 object_allocator_->GetHeapSpace())->GetCurrentFreeTenuredSize();
372 allocated += bytes;
373 }
374 MakeObjectsGarbage(old_root_size, root_size_);
375 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
376 ASSERT_EQ(initial_heap, ms_->GetFootprintHeap());
377 gc_->RemoveListener(&gchook);
378 }
379
380 MakeObjectsGarbage(0, root_size_);
381 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
382 ASSERT_EQ(initial_heap, ms_->GetFootprintHeap());
383 }
384 ResetRuntime();
385 }
386
TEST_F(G1GCFullGCTest,TestExplicitFullNearLimit)387 TEST_F(G1GCFullGCTest, TestExplicitFullNearLimit)
388 {
389 std::string gctype = static_cast<std::string>(GCStringFromType(GCType::G1_GC));
390 SetupRuntime(gctype);
391 {
392 HandleScope<ObjectHeader *> scope(thread_);
393 PrepareTest<panda::PandaAssemblyLanguageConfig>();
394 [[maybe_unused]] size_t bytes, raw_objects_size;
395
396 [[maybe_unused]] size_t young_size =
397 reinterpret_cast<GenerationalSpaces *>(reinterpret_cast<ObjectAllocatorGenBase *>(
398 object_allocator_)->GetHeapSpace())->GetCurrentMaxYoungSize();
399 [[maybe_unused]] size_t heap_size = mem::MemConfig::GetHeapSizeLimit();
400 [[maybe_unused]] auto g1_alloc = reinterpret_cast<ObjectAllocatorG1<MT_MODE_MULTI> *>(object_allocator_);
401 [[maybe_unused]] size_t max_y_size = g1_alloc->GetYoungAllocMaxSize();
402
403 [[maybe_unused]] auto y_space_check = [](uintptr_t addr) -> bool { return IsInYoung(addr); };
404 [[maybe_unused]] auto h_space_check = [](uintptr_t addr) -> bool { return !IsInYoung(addr); };
405 [[maybe_unused]] auto t_free =
406 reinterpret_cast<GenerationalSpaces *>(object_allocator_->GetHeapSpace())->GetCurrentFreeTenuredSize();
407 const size_t y_obj_size = max_y_size / 10;
408 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
409 size_t initial_heap = ms_->GetFootprintHeap();
410
411 {
412 // Allocating until we are close to OOM, then do release this mem,
413 // do explicit full and allocate the same size once again
414 auto old_root_size = root_size_;
415 int i = 0;
416 while (t_free > 2.2 * young_size) {
417 ObjVec ov1 = MakeAllocations<decltype(y_space_check), 1>(y_obj_size, y_obj_size, 1, &bytes,
418 &raw_objects_size, y_space_check);
419 MakeObjectsAlive(ov1, 1);
420 t_free = reinterpret_cast<GenerationalSpaces *>(
421 object_allocator_->GetHeapSpace())->GetCurrentFreeTenuredSize();
422 i++;
423 }
424 gc_->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE));
425 MakeObjectsGarbage(old_root_size, root_size_);
426
427 old_root_size = root_size_;
428 while (--i > 0) {
429 ObjVec ov1 = MakeAllocations<decltype(y_space_check), 1>(y_obj_size, y_obj_size, 1, &bytes,
430 &raw_objects_size, y_space_check);
431 MakeObjectsAlive(ov1, 1);
432 t_free = reinterpret_cast<GenerationalSpaces *>(
433 object_allocator_->GetHeapSpace())->GetCurrentFreeTenuredSize();
434 }
435 MakeObjectsGarbage(old_root_size, root_size_);
436 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
437 }
438
439 MakeObjectsGarbage(0, root_size_);
440 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
441 ASSERT_EQ(initial_heap, ms_->GetFootprintHeap());
442 }
443 ResetRuntime();
444 }
445
TEST_F(G1GCFullGCTest,TestOOMFullNearLimit)446 TEST_F(G1GCFullGCTest, TestOOMFullNearLimit)
447 {
448 std::string gctype = static_cast<std::string>(GCStringFromType(GCType::G1_GC));
449 SetupRuntime(gctype);
450 {
451 HandleScope<ObjectHeader *> scope(thread_);
452 PrepareTest<panda::PandaAssemblyLanguageConfig>();
453 [[maybe_unused]] size_t bytes, raw_objects_size;
454
455 [[maybe_unused]] size_t young_size =
456 reinterpret_cast<GenerationalSpaces *>(reinterpret_cast<ObjectAllocatorGenBase *>(
457 object_allocator_)->GetHeapSpace())->GetCurrentMaxYoungSize();
458 [[maybe_unused]] size_t heap_size = mem::MemConfig::GetHeapSizeLimit();
459 [[maybe_unused]] auto g1_alloc = reinterpret_cast<ObjectAllocatorG1<MT_MODE_MULTI> *>(object_allocator_);
460 [[maybe_unused]] size_t max_y_size = g1_alloc->GetYoungAllocMaxSize();
461
462 [[maybe_unused]] auto y_space_check = [](uintptr_t addr) -> bool { return IsInYoung(addr); };
463 [[maybe_unused]] auto h_space_check = [](uintptr_t addr) -> bool { return !IsInYoung(addr); };
464 [[maybe_unused]] auto t_free =
465 reinterpret_cast<GenerationalSpaces *>(object_allocator_->GetHeapSpace())->GetCurrentFreeTenuredSize();
466 const size_t y_obj_size = max_y_size / 10;
467 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
468 size_t initial_heap = ms_->GetFootprintHeap();
469
470 {
471 // Allocating until we are close to OOM, then do release this mem,
472 // then allocate the same size once again checking if we can handle it w/o OOM
473 auto old_root_size = root_size_;
474 int i = 0;
475 while (t_free > 2.2 * young_size) {
476 ObjVec ov1 = MakeAllocations<decltype(y_space_check), 1>(y_obj_size, y_obj_size, 1, &bytes,
477 &raw_objects_size, y_space_check);
478 MakeObjectsAlive(ov1, 1);
479 t_free = reinterpret_cast<GenerationalSpaces *>(
480 object_allocator_->GetHeapSpace())->GetCurrentFreeTenuredSize();
481 i++;
482 }
483 MakeObjectsGarbage(old_root_size, root_size_);
484
485 old_root_size = root_size_;
486 while (--i > 0) {
487 ObjVec ov1 = MakeAllocations<decltype(y_space_check), 1>(y_obj_size, y_obj_size, 1, &bytes,
488 &raw_objects_size, y_space_check);
489 MakeObjectsAlive(ov1, 1);
490 t_free = reinterpret_cast<GenerationalSpaces *>(
491 object_allocator_->GetHeapSpace())->GetCurrentFreeTenuredSize();
492 }
493 MakeObjectsGarbage(old_root_size, root_size_);
494 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
495 }
496
497 MakeObjectsGarbage(0, root_size_);
498 gc_->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
499 ASSERT_EQ(initial_heap, ms_->GetFootprintHeap());
500 }
501 ResetRuntime();
502 }
503 } // namespace panda::mem::test
504