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