• 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 <array>
17 #include <atomic>
18 #include <chrono>
19 #include <condition_variable>
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/mem/malloc-proxy-allocator-inl.h"
28 #include "runtime/mem/mem_stats.h"
29 #include "runtime/mem/mem_stats_default.h"
30 #include "runtime/mem/runslots_allocator-inl.h"
31 
32 namespace ark::mem::test {
33 
34 #ifndef PANDA_NIGHTLY_TEST_ON
35 constexpr uint64_t ITERATION = 256;
36 constexpr size_t NUM_THREADS = 2;
37 #else
38 constexpr uint64_t ITERATION = 1 << 17;
39 constexpr size_t NUM_THREADS = 8;
40 #endif
41 
42 using NonObjectAllocator = RunSlotsAllocator<RawMemoryConfig>;
43 
44 class MemStatsTest : public testing::Test {
45 public:
MemStatsTest()46     MemStatsTest()
47     {
48         // we need runtime for creating objects
49         RuntimeOptions options;
50         options.SetShouldLoadBootPandaFiles(false);
51         options.SetShouldInitializeIntrinsics(false);
52         options.SetGcType("stw");  // NOTE(dtrubenk): fix for gen-gc
53         options.SetRunGcInPlace(true);
54         Runtime::Create(options);
55         thread_ = ark::MTManagedThread::GetCurrent();
56         thread_->ManagedCodeBegin();
57     }
58 
~MemStatsTest()59     ~MemStatsTest() override
60     {
61         thread_->ManagedCodeEnd();
62         Runtime::Destroy();
63     }
64 
65     NO_COPY_SEMANTIC(MemStatsTest);
66     NO_MOVE_SEMANTIC(MemStatsTest);
67 
68 protected:
69     // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
70     ark::MTManagedThread *thread_ {};
71 };
72 
73 using MallocProxyNonObjectAllocator = MallocProxyAllocator<RawMemoryConfig>;
74 
75 class RawStatsBeforeTest {
76     size_t rawBytesAllocatedBeforeTest_;
77     size_t rawBytesFreedBeforeTest_;
78     size_t rawBytesFootprintBeforeRest_;
79 
80 public:
RawStatsBeforeTest(MemStatsType * stats)81     explicit RawStatsBeforeTest(MemStatsType *stats)
82         : rawBytesAllocatedBeforeTest_(stats->GetAllocated(SpaceType::SPACE_TYPE_INTERNAL)),
83           rawBytesFreedBeforeTest_(stats->GetFreed(SpaceType::SPACE_TYPE_INTERNAL)),
84           rawBytesFootprintBeforeRest_(stats->GetFootprint(SpaceType::SPACE_TYPE_INTERNAL))
85     {
86     }
87 
GetRawBytesAllocatedBeforeTest() const88     [[nodiscard]] size_t GetRawBytesAllocatedBeforeTest() const
89     {
90         return rawBytesAllocatedBeforeTest_;
91     }
92 
GetRawBytesFreedBeforeTest() const93     [[nodiscard]] size_t GetRawBytesFreedBeforeTest() const
94     {
95         return rawBytesFreedBeforeTest_;
96     }
97 
GetRawBytesFootprintBeforeTest() const98     [[nodiscard]] size_t GetRawBytesFootprintBeforeTest() const
99     {
100         return rawBytesFootprintBeforeRest_;
101     }
102 };
103 
AssertHeapStats(MemStatsType * stats,size_t bytesInHeap,size_t heapBytesAllocated,size_t heapBytesFreed)104 void AssertHeapStats(MemStatsType *stats, size_t bytesInHeap, size_t heapBytesAllocated, size_t heapBytesFreed)
105 {
106     ASSERT_EQ(heapBytesAllocated, stats->GetAllocated(SpaceType::SPACE_TYPE_OBJECT));
107     ASSERT_EQ(heapBytesFreed, stats->GetFreed(SpaceType::SPACE_TYPE_OBJECT));
108     ASSERT_EQ(bytesInHeap, stats->GetFootprint(SpaceType::SPACE_TYPE_OBJECT));
109 }
110 
AssertHeapHumongousStats(MemStatsType * stats,size_t bytesInHeap,size_t heapBytesAllocated,size_t heapBytesFreed)111 void AssertHeapHumongousStats(MemStatsType *stats, size_t bytesInHeap, size_t heapBytesAllocated, size_t heapBytesFreed)
112 {
113     ASSERT_EQ(heapBytesAllocated, stats->GetAllocated(SpaceType::SPACE_TYPE_HUMONGOUS_OBJECT));
114     ASSERT_EQ(heapBytesFreed, stats->GetFreed(SpaceType::SPACE_TYPE_HUMONGOUS_OBJECT));
115     ASSERT_EQ(bytesInHeap, stats->GetFootprint(SpaceType::SPACE_TYPE_HUMONGOUS_OBJECT));
116 }
117 
AssertHeapObjectsStats(MemStatsType * stats,size_t heapObjectsAllocated,size_t heapObjectsFreed,size_t heapHumungousObjectsAllocated,size_t heapHumungousObjectsFreed)118 void AssertHeapObjectsStats(MemStatsType *stats, size_t heapObjectsAllocated, size_t heapObjectsFreed,
119                             size_t heapHumungousObjectsAllocated, size_t heapHumungousObjectsFreed)
120 {
121     ASSERT_EQ(heapObjectsAllocated, stats->GetTotalObjectsAllocated());
122     ASSERT_EQ(heapObjectsFreed, stats->GetTotalObjectsFreed());
123 
124     // On arm-32 platform, we should cast the uint64_t(-1) to size_t(-1)
125     ASSERT_EQ(heapObjectsAllocated - heapHumungousObjectsAllocated,
126               static_cast<size_t>(stats->GetTotalRegularObjectsAllocated()));
127     ASSERT_EQ(heapObjectsFreed - heapHumungousObjectsFreed, static_cast<size_t>(stats->GetTotalRegularObjectsFreed()));
128 
129     ASSERT_EQ(heapHumungousObjectsAllocated, stats->GetTotalHumongousObjectsAllocated());
130     ASSERT_EQ(heapHumungousObjectsFreed, stats->GetTotalHumongousObjectsFreed());
131 
132     ASSERT_EQ(heapObjectsAllocated - heapObjectsFreed, stats->GetObjectsCountAlive());
133     ASSERT_EQ(heapObjectsAllocated - heapObjectsFreed + heapHumungousObjectsAllocated - heapHumungousObjectsFreed,
134               stats->GetRegularObjectsCountAlive());
135     ASSERT_EQ(heapHumungousObjectsAllocated - heapHumungousObjectsFreed, stats->GetHumonguousObjectsCountAlive());
136 }
137 
138 /**
139  * We add bytes which we allocated before tests for our internal structures, but we don't add it to `freed` because
140  * destructors haven't be called yet.
141  */
AssertRawStats(MemStatsType * stats,size_t rawBytesAllocated,size_t rawBytesFreed,size_t rawBytesFootprint,RawStatsBeforeTest & statsBeforeTest)142 void AssertRawStats(MemStatsType *stats, size_t rawBytesAllocated, size_t rawBytesFreed, size_t rawBytesFootprint,
143                     RawStatsBeforeTest &statsBeforeTest)
144 {
145     ASSERT_EQ(rawBytesAllocated + statsBeforeTest.GetRawBytesAllocatedBeforeTest(),
146               stats->GetAllocated(SpaceType::SPACE_TYPE_INTERNAL));
147     ASSERT_EQ(rawBytesFreed + statsBeforeTest.GetRawBytesFreedBeforeTest(),
148               stats->GetFreed(SpaceType::SPACE_TYPE_INTERNAL));
149     ASSERT_EQ(rawBytesFootprint + statsBeforeTest.GetRawBytesFootprintBeforeTest(),
150               stats->GetFootprint(SpaceType::SPACE_TYPE_INTERNAL));
151 }
152 
TEST_F(MemStatsTest,SimpleTest)153 TEST_F(MemStatsTest, SimpleTest)
154 {
155     static constexpr size_t BYTES_OBJECT1 = 10;
156     static constexpr size_t BYTES_OBJECT2 = 12;
157     static constexpr size_t BYTES_RAW_MEMORY_ALLOC1 = 20;
158     static constexpr size_t BYTES_RAW_MEMORY_ALLOC2 = 30002;
159     static constexpr size_t RAW_MEMORY_FREED = 5;
160 
161     auto *stats = thread_->GetVM()->GetMemStats();
162     size_t initHeapBytes = stats->GetAllocated(SpaceType::SPACE_TYPE_OBJECT);
163     size_t initHeapObjects = stats->GetTotalObjectsAllocated();
164     RawStatsBeforeTest rawStatsBeforeTest(stats);
165     stats->RecordAllocateObject(BYTES_OBJECT1, SpaceType::SPACE_TYPE_OBJECT);
166     stats->RecordAllocateObject(BYTES_OBJECT2, SpaceType::SPACE_TYPE_OBJECT);
167     stats->RecordAllocateRaw(BYTES_RAW_MEMORY_ALLOC1, SpaceType::SPACE_TYPE_INTERNAL);
168     stats->RecordAllocateRaw(BYTES_RAW_MEMORY_ALLOC2, SpaceType::SPACE_TYPE_INTERNAL);
169     stats->RecordFreeRaw(RAW_MEMORY_FREED, SpaceType::SPACE_TYPE_INTERNAL);
170 
171     AssertHeapStats(stats, initHeapBytes + BYTES_OBJECT1 + BYTES_OBJECT2, initHeapBytes + BYTES_OBJECT1 + BYTES_OBJECT2,
172                     0);
173     AssertHeapObjectsStats(stats, initHeapObjects + 2U, 0, 0, 0);
174     ASSERT_EQ(initHeapBytes + BYTES_OBJECT1 + BYTES_OBJECT2, stats->GetFootprint(SpaceType::SPACE_TYPE_OBJECT));
175     AssertRawStats(stats, BYTES_RAW_MEMORY_ALLOC1 + BYTES_RAW_MEMORY_ALLOC2, RAW_MEMORY_FREED,
176                    BYTES_RAW_MEMORY_ALLOC1 + BYTES_RAW_MEMORY_ALLOC2 - RAW_MEMORY_FREED, rawStatsBeforeTest);
177     stats->RecordFreeRaw(BYTES_RAW_MEMORY_ALLOC1 + BYTES_RAW_MEMORY_ALLOC2 - RAW_MEMORY_FREED,
178                          SpaceType::SPACE_TYPE_INTERNAL);
179 }
180 
181 // testing MemStats via allocators.
TEST_F(MemStatsTest,NonObjectTestViaMallocAllocator)182 TEST_F(MemStatsTest, NonObjectTestViaMallocAllocator)
183 {
184     static constexpr size_t BYTES_ALLOC1 = 23;
185     static constexpr size_t BYTES_ALLOC2 = 42;
186 
187     mem::MemStatsType *stats = thread_->GetVM()->GetMemStats();
188     RawStatsBeforeTest rawStatsBeforeTest(stats);
189     size_t initHeapBytes = stats->GetAllocated(SpaceType::SPACE_TYPE_OBJECT);
190     size_t initHeapObjects = stats->GetTotalObjectsAllocated();
191     MallocProxyNonObjectAllocator allocator(stats, SpaceType::SPACE_TYPE_INTERNAL);
192 
193     void *a1 = allocator.Alloc(BYTES_ALLOC1);
194     allocator.Free(a1);
195     void *a2 = allocator.Alloc(BYTES_ALLOC2);
196 
197     AssertHeapStats(stats, initHeapBytes, initHeapBytes, 0);
198     AssertHeapObjectsStats(stats, initHeapObjects, 0, 0, 0);
199     AssertRawStats(stats, BYTES_ALLOC1 + BYTES_ALLOC2, BYTES_ALLOC1, BYTES_ALLOC2, rawStatsBeforeTest);
200     allocator.Free(a2);
201 }
202 
203 // testing MemStats via allocators.
TEST_F(MemStatsTest,NonObjectTestViaSlotsAllocator)204 TEST_F(MemStatsTest, NonObjectTestViaSlotsAllocator)
205 {
206     static constexpr uint64_t POOL_SIZE = 4_MB;
207     static constexpr size_t REAL_BYTES_ALLOC1 = 23;
208     // RunSlotsAllocator uses 32 bytes for allocation 23 bytes
209     static constexpr size_t BYTES_IN_ALLOCATOR_ALLOC1 = 32;
210     static constexpr size_t REAL_BYTES_ALLOC2 = 42;
211     static constexpr size_t BYTES_IN_ALLOCATOR_ALLOC2 = 64;
212 
213     mem::MemStatsType *stats = thread_->GetVM()->GetMemStats();
214     size_t initHeapBytes = stats->GetAllocated(SpaceType::SPACE_TYPE_OBJECT);
215     size_t initHeapObjects = stats->GetTotalObjectsAllocated();
216     RawStatsBeforeTest rawStatsBeforeTest(stats);
217 
218     auto *allocator = new NonObjectAllocator(stats, SpaceType::SPACE_TYPE_INTERNAL);
219     void *mem = aligned_alloc(RUNSLOTS_ALIGNMENT_IN_BYTES, POOL_SIZE);
220     allocator->AddMemoryPool(mem, POOL_SIZE);
221 
222     void *a1 = allocator->Alloc(REAL_BYTES_ALLOC1);
223     allocator->Free(a1);
224     void *a2 = allocator->Alloc(REAL_BYTES_ALLOC2);
225 
226     AssertHeapStats(stats, initHeapBytes, initHeapBytes, 0);
227     AssertHeapObjectsStats(stats, initHeapObjects, 0, 0, 0);
228     AssertRawStats(stats, BYTES_IN_ALLOCATOR_ALLOC1 + BYTES_IN_ALLOCATOR_ALLOC2, BYTES_IN_ALLOCATOR_ALLOC1,
229                    BYTES_IN_ALLOCATOR_ALLOC2, rawStatsBeforeTest);
230     allocator->Free(a2);
231     delete allocator;
232     // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
233     std::free(mem);
234 }
235 
236 // allocate and free small object in the heap
TEST_F(MemStatsTest,SmallObject)237 TEST_F(MemStatsTest, SmallObject)
238 {
239     mem::MemStatsType *stats = thread_->GetVM()->GetMemStats();
240     size_t initHeapBytes = stats->GetAllocated(SpaceType::SPACE_TYPE_OBJECT);
241     size_t initHeapObjects = stats->GetTotalObjectsAllocated();
242     RawStatsBeforeTest rawStatsBeforeTest(stats);
243     std::string simpleString = "abcdef12345";
244     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
245     [[maybe_unused]] coretypes::String *stringObject =
246         coretypes::String::CreateFromMUtf8(reinterpret_cast<const uint8_t *>(&simpleString[0]), simpleString.length(),
247                                            ctx, Runtime::GetCurrent()->GetPandaVM());
248     ASSERT_TRUE(stringObject != nullptr);
249     thread_->GetVM()->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE));
250     size_t allocSize = simpleString.size() + sizeof(coretypes::String);
251     size_t aligmentSize = 1UL << RunSlots<>::ConvertToPowerOfTwoUnsafe(allocSize);
252     AssertHeapStats(stats, initHeapBytes, initHeapBytes + aligmentSize, aligmentSize);
253     AssertHeapObjectsStats(stats, initHeapObjects + 1, 1, 0, 0);
254     ASSERT_EQ(rawStatsBeforeTest.GetRawBytesFootprintBeforeTest(), stats->GetFootprint(SpaceType::SPACE_TYPE_INTERNAL));
255 }
256 
257 // allocate and free big object in the heap
TEST_F(MemStatsTest,BigObject)258 TEST_F(MemStatsTest, BigObject)
259 {
260     mem::MemStatsType *stats = thread_->GetVM()->GetMemStats();
261     RawStatsBeforeTest rawStatsBeforeTest(stats);
262     size_t initHeapBytes = stats->GetAllocated(SpaceType::SPACE_TYPE_OBJECT);
263     size_t initHeapObjects = stats->GetTotalObjectsAllocated();
264     std::string simpleString;
265     auto objectAllocator = thread_->GetVM()->GetGC()->GetObjectAllocator();
266     size_t allocSize = objectAllocator->GetRegularObjectMaxSize() + 1;
267     for (size_t j = 0; j < allocSize; j++) {
268         simpleString.append("x");
269     }
270     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
271     [[maybe_unused]] coretypes::String *stringObject =
272         coretypes::String::CreateFromMUtf8(reinterpret_cast<const uint8_t *>(&simpleString[0]), simpleString.length(),
273                                            ctx, Runtime::GetCurrent()->GetPandaVM());
274     ASSERT_TRUE(stringObject != nullptr);
275     thread_->GetVM()->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE));
276     allocSize += sizeof(coretypes::String);
277     size_t aligmentSize = AlignUp(allocSize, GetAlignmentInBytes(FREELIST_DEFAULT_ALIGNMENT));
278 
279     AssertHeapStats(stats, initHeapBytes, initHeapBytes + aligmentSize, aligmentSize);
280     AssertHeapObjectsStats(stats, initHeapObjects + 1, 1, 0, 0);
281     ASSERT_EQ(rawStatsBeforeTest.GetRawBytesFootprintBeforeTest(), stats->GetFootprint(SpaceType::SPACE_TYPE_INTERNAL));
282 }
283 
284 // allocate and free humongous object in the heap
TEST_F(MemStatsTest,HumongousObject)285 TEST_F(MemStatsTest, HumongousObject)
286 {
287     mem::MemStatsType *stats = thread_->GetVM()->GetMemStats();
288     RawStatsBeforeTest rawStatsBeforeTest(stats);
289     size_t initHeapBytes = stats->GetAllocated(SpaceType::SPACE_TYPE_HUMONGOUS_OBJECT);
290     size_t initHeapObjects = stats->GetTotalObjectsAllocated();
291     std::string simpleString;
292     auto objectAllocator = thread_->GetVM()->GetGC()->GetObjectAllocator();
293     size_t allocSize = objectAllocator->GetLargeObjectMaxSize() + 1;
294     for (size_t j = 0; j < allocSize; j++) {
295         simpleString.append("x");
296     }
297     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
298     [[maybe_unused]] coretypes::String *stringObject =
299         coretypes::String::CreateFromMUtf8(reinterpret_cast<const uint8_t *>(&simpleString[0]), simpleString.length(),
300                                            ctx, Runtime::GetCurrent()->GetPandaVM());
301     ASSERT_TRUE(stringObject != nullptr);
302     thread_->GetVM()->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE));
303     // NOLINTNEXTLINE(readability-magic-numbers)
304     AssertHeapHumongousStats(stats, initHeapBytes, initHeapBytes + 2359296U, 2359296U);
305     AssertHeapObjectsStats(stats, initHeapObjects, 0, 1, 1);
306     ASSERT_EQ(rawStatsBeforeTest.GetRawBytesFootprintBeforeTest(), stats->GetFootprint(SpaceType::SPACE_TYPE_INTERNAL));
307 
308     ASSERT_EQ(2359296UL, stats->GetAllocated(SpaceType::SPACE_TYPE_HUMONGOUS_OBJECT));
309     ASSERT_EQ(2359296UL, stats->GetFreed(SpaceType::SPACE_TYPE_HUMONGOUS_OBJECT));
310     ASSERT_EQ(0, stats->GetFootprint(SpaceType::SPACE_TYPE_HUMONGOUS_OBJECT));
311 }
312 
TEST_F(MemStatsTest,TotalFootprint)313 TEST_F(MemStatsTest, TotalFootprint)
314 {
315     static constexpr size_t BYTES_ALLOC1 = 2;
316     static constexpr size_t BYTES_ALLOC2 = 5;
317     static constexpr size_t RAW_ALLOC1 = 15;
318     static constexpr size_t RAW_ALLOC2 = 30;
319 
320     MemStatsDefault stats;
321     stats.RecordAllocateObject(BYTES_ALLOC1, SpaceType::SPACE_TYPE_OBJECT);
322     stats.RecordAllocateObject(BYTES_ALLOC2, SpaceType::SPACE_TYPE_OBJECT);
323     stats.RecordAllocateRaw(RAW_ALLOC1, SpaceType::SPACE_TYPE_INTERNAL);
324     stats.RecordAllocateRaw(RAW_ALLOC2, SpaceType::SPACE_TYPE_INTERNAL);
325 
326     ASSERT_EQ(BYTES_ALLOC1 + BYTES_ALLOC2, stats.GetFootprint(SpaceType::SPACE_TYPE_OBJECT));
327     ASSERT_EQ(BYTES_ALLOC1 + BYTES_ALLOC2 + RAW_ALLOC1 + RAW_ALLOC2, stats.GetTotalFootprint());
328     ASSERT_EQ(RAW_ALLOC1 + RAW_ALLOC2, stats.GetFootprint(SpaceType::SPACE_TYPE_INTERNAL));
329 
330     stats.RecordFreeRaw(RAW_ALLOC1, SpaceType::SPACE_TYPE_INTERNAL);
331 
332     ASSERT_EQ(BYTES_ALLOC1 + BYTES_ALLOC2, stats.GetFootprint(SpaceType::SPACE_TYPE_OBJECT));
333     ASSERT_EQ(BYTES_ALLOC1 + BYTES_ALLOC2 + RAW_ALLOC2, stats.GetTotalFootprint());
334     ASSERT_EQ(RAW_ALLOC2, stats.GetFootprint(SpaceType::SPACE_TYPE_INTERNAL));
335 }
336 
TEST_F(MemStatsTest,Statistics)337 TEST_F(MemStatsTest, Statistics)
338 {
339     static constexpr size_t BYTES_OBJECT = 10;
340     static constexpr size_t BYTES_ALLOC1 = 23;
341     static constexpr size_t BYTES_ALLOC2 = 42;
342 
343     MemStatsDefault stats;
344     stats.RecordAllocateObject(BYTES_OBJECT, SpaceType::SPACE_TYPE_OBJECT);
345     stats.RecordAllocateRaw(BYTES_ALLOC1, SpaceType::SPACE_TYPE_INTERNAL);
346     stats.RecordAllocateRaw(BYTES_ALLOC2, SpaceType::SPACE_TYPE_INTERNAL);
347 
348     auto statistics = stats.GetStatistics();
349     ASSERT_TRUE(statistics.find(std::to_string(BYTES_OBJECT)) != std::string::npos);
350     ASSERT_TRUE(statistics.find(std::to_string(BYTES_ALLOC1 + BYTES_ALLOC2)) != std::string::npos);
351     stats.RecordFreeRaw(BYTES_ALLOC1 + BYTES_ALLOC2, SpaceType::SPACE_TYPE_INTERNAL);
352 }
353 
FillMemStatsForConcurrency(MemStatsDefault & stats,std::condition_variable & readyToStart,std::mutex & cvMutex,std::atomic_size_t & threadsReady,coretypes::String * stringObject)354 void FillMemStatsForConcurrency(MemStatsDefault &stats, std::condition_variable &readyToStart, std::mutex &cvMutex,
355                                 std::atomic_size_t &threadsReady, coretypes::String *stringObject)
356 {
357     {
358         std::unique_lock<std::mutex> lockForReadyToStart(cvMutex);
359         // Atomic with seq_cst order reason: data race with threads_ready with requirement for sequentially consistent
360         // order where threads observe all modifications in the same order
361         threadsReady.fetch_add(1, std::memory_order_seq_cst);
362         // Atomic with seq_cst order reason: data race with threads_ready with requirement for sequentially consistent
363         // order where threads observe all modifications in the same order
364         if (threadsReady.load(std::memory_order_seq_cst) == NUM_THREADS) {
365             // Unlock all threads
366             readyToStart.notify_all();
367         } else {
368             // Atomic with seq_cst order reason: data race with threads_ready with requirement for sequentially
369             // consistent order where threads observe all modifications in the same order
370             readyToStart.wait(lockForReadyToStart,
371                               [&threadsReady] { return threadsReady.load(std::memory_order_seq_cst) == NUM_THREADS; });
372         }
373     }
374     for (size_t i = 1; i <= ITERATION; i++) {
375         for (size_t index = 0; index < SPACE_TYPE_SIZE; index++) {
376             SpaceType type = ToSpaceType(index);
377             if (IsHeapSpace(type)) {
378                 stats.RecordAllocateObject(stringObject->ObjectSize(), type);
379             } else {
380                 stats.RecordAllocateRaw(i * (index + 1), type);
381             }
382         }
383     }
384 
385     for (size_t index = 0; index < SPACE_TYPE_SIZE; index++) {
386         SpaceType type = ToSpaceType(index);
387         if (IsHeapSpace(type)) {
388             stats.RecordFreeObject(stringObject->ObjectSize(), type);
389         } else {
390             stats.RecordFreeRaw(ITERATION * (index + 1), type);
391         }
392     }
393 }
394 
TEST_F(MemStatsTest,TestThreadSafety)395 TEST_F(MemStatsTest, TestThreadSafety)
396 {
397     std::string simpleString = "smallData";
398     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
399     coretypes::String *stringObject =
400         coretypes::String::CreateFromMUtf8(reinterpret_cast<const uint8_t *>(&simpleString[0]), simpleString.length(),
401                                            ctx, Runtime::GetCurrent()->GetPandaVM());
402 
403     MemStatsDefault stats;
404 
405     std::array<std::thread, NUM_THREADS> threads;
406 
407     std::atomic_size_t threadsReady = 0;
408     std::mutex cvMutex;
409     std::condition_variable readyToStart;
410     for (size_t i = 0; i < NUM_THREADS; i++) {
411         threads[i] = std::thread(FillMemStatsForConcurrency, std::ref(stats), std::ref(readyToStart), std::ref(cvMutex),
412                                  std::ref(threadsReady), stringObject);
413     }
414 
415     for (size_t i = 0; i < NUM_THREADS; i++) {
416         threads[i].join();
417     }
418 
419     constexpr uint64_t SUM = (ITERATION + 1) * ITERATION / 2U;
420     constexpr uint64_t TOTAL_ITERATION_COUNT = NUM_THREADS * ITERATION;
421 
422     for (size_t index = 0; index < SPACE_TYPE_SIZE; index++) {
423         SpaceType type = ToSpaceType(index);
424         if (IsHeapSpace(type)) {
425             ASSERT_EQ(stats.GetAllocated(type), TOTAL_ITERATION_COUNT * stringObject->ObjectSize());
426             ASSERT_EQ(stats.GetFreed(type), NUM_THREADS * stringObject->ObjectSize());
427             ASSERT_EQ(stats.GetFootprint(type), (TOTAL_ITERATION_COUNT - NUM_THREADS) * stringObject->ObjectSize());
428         } else {
429             ASSERT_EQ(stats.GetAllocated(type), SUM * NUM_THREADS * (index + 1));
430             ASSERT_EQ(stats.GetFreed(type), TOTAL_ITERATION_COUNT * (index + 1));
431             ASSERT_EQ(stats.GetFootprint(type), (SUM - ITERATION) * NUM_THREADS * (index + 1));
432         }
433     }
434 }
435 
436 }  // namespace ark::mem::test
437