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