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
16 #include <gtest/gtest.h>
17
18 #include <chrono>
19 #include <thread>
20
21 #include "assembly-parser.h"
22 #include "runtime/compiler_queue_aged_counter_priority.h"
23 #include "runtime/compiler_queue_counter_priority.h"
24 #include "runtime/include/class-inl.h"
25 #include "runtime/include/method.h"
26 #include "runtime/include/runtime.h"
27
28 namespace panda::test {
29
30 class CompilerQueueTest : public testing::Test {
31 public:
CompilerQueueTest()32 CompilerQueueTest()
33 {
34 RuntimeOptions options;
35 options.SetShouldLoadBootPandaFiles(false);
36 options.SetShouldInitializeIntrinsics(false);
37 Runtime::Create(options);
38 thread_ = panda::MTManagedThread::GetCurrent();
39 thread_->ManagedCodeBegin();
40 }
41
~CompilerQueueTest()42 ~CompilerQueueTest()
43 {
44 thread_->ManagedCodeEnd();
45 Runtime::Destroy();
46 }
47
48 protected:
49 panda::MTManagedThread *thread_;
50 };
51
TestClassPrepare()52 static Class *TestClassPrepare()
53 {
54 auto source = R"(
55 .function i32 g() {
56 ldai 0
57 return
58 }
59
60 .function i32 f() {
61 ldai 0
62 return
63 }
64
65 .function void main() {
66 call f
67 return.void
68 }
69 )";
70 pandasm::Parser p;
71
72 auto res = p.Parse(source);
73 auto pf = pandasm::AsmEmitter::Emit(res.Value());
74
75 ClassLinker *class_linker = Runtime::GetCurrent()->GetClassLinker();
76 class_linker->AddPandaFile(std::move(pf));
77
78 PandaString descriptor;
79
80 Class *klass = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY)
81 ->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), &descriptor));
82 return klass;
83 }
84
GetAndCheckMethodIfExists(CompilerQueueInterface * queue,Method * target)85 static void GetAndCheckMethodIfExists(CompilerQueueInterface *queue, Method *target)
86 {
87 auto method = queue->GetTask().GetMethod();
88 // The element may expire and may be deleted
89 if (method != nullptr) {
90 ASSERT_EQ(method, target);
91 }
92 }
93
WaitForExpire(uint millis)94 static void WaitForExpire(uint millis)
95 {
96 constexpr uint delta = 10;
97 uint64_t startTime = time::GetCurrentTimeInMillis();
98 std::this_thread::sleep_for(std::chrono::milliseconds(millis));
99 // sleep_for() works nondeterministically
100 // use an additional check for more confidence
101 // Note, the queue implementation uses GetCurrentTimeInMillis
102 // to update aged counter
103 while (time::GetCurrentTimeInMillis() < startTime + millis) {
104 std::this_thread::sleep_for(std::chrono::milliseconds(delta));
105 }
106 }
107
108 // Testing of CounterQueue
109
TEST_F(CompilerQueueTest,AddGet)110 TEST_F(CompilerQueueTest, AddGet)
111 {
112 Class *klass = TestClassPrepare();
113
114 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
115 ASSERT_NE(main_method, nullptr);
116
117 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
118 ASSERT_NE(f_method, nullptr);
119
120 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
121 ASSERT_NE(g_method, nullptr);
122
123 // Manual range
124 main_method->SetHotnessCounter(1);
125 f_method->SetHotnessCounter(2);
126 g_method->SetHotnessCounter(3);
127
128 RuntimeOptions options;
129 CompilerPriorityCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
130 options.GetCompilerQueueMaxLength(), options.GetCompilerTaskLifeSpan());
131 queue.AddTask(CompilerTask {main_method, 0});
132 queue.AddTask(CompilerTask {f_method, 0});
133 queue.AddTask(CompilerTask {g_method, 0});
134
135 GetAndCheckMethodIfExists(&queue, g_method);
136 GetAndCheckMethodIfExists(&queue, f_method);
137 GetAndCheckMethodIfExists(&queue, main_method);
138 }
139
TEST_F(CompilerQueueTest,EqualCounters)140 TEST_F(CompilerQueueTest, EqualCounters)
141 {
142 Class *klass = TestClassPrepare();
143
144 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
145 ASSERT_NE(main_method, nullptr);
146
147 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
148 ASSERT_NE(f_method, nullptr);
149
150 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
151 ASSERT_NE(g_method, nullptr);
152
153 // Manual range
154 main_method->SetHotnessCounter(3);
155 f_method->SetHotnessCounter(3);
156 g_method->SetHotnessCounter(3);
157
158 RuntimeOptions options;
159 CompilerPriorityCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
160 options.GetCompilerQueueMaxLength(), options.GetCompilerTaskLifeSpan());
161
162 queue.AddTask(CompilerTask {f_method, 0});
163 queue.AddTask(CompilerTask {g_method, 0});
164 queue.AddTask(CompilerTask {main_method, 0});
165
166 GetAndCheckMethodIfExists(&queue, f_method);
167 GetAndCheckMethodIfExists(&queue, g_method);
168 GetAndCheckMethodIfExists(&queue, main_method);
169 }
170
TEST_F(CompilerQueueTest,Expire)171 TEST_F(CompilerQueueTest, Expire)
172 {
173 auto klass = TestClassPrepare();
174
175 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
176 ASSERT_NE(main_method, nullptr);
177
178 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
179 ASSERT_NE(f_method, nullptr);
180
181 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
182 ASSERT_NE(g_method, nullptr);
183
184 RuntimeOptions options;
185 constexpr int CompilerTaskLifeSpan1 = 500;
186 CompilerPriorityCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
187 options.GetCompilerQueueMaxLength(), CompilerTaskLifeSpan1);
188 queue.AddTask(CompilerTask {main_method, 0});
189 queue.AddTask(CompilerTask {f_method, 0});
190 queue.AddTask(CompilerTask {g_method, 0});
191
192 WaitForExpire(1000);
193
194 // All tasks should expire after sleep
195 auto method = queue.GetTask().GetMethod();
196 ASSERT_EQ(method, nullptr);
197
198 constexpr int CompilerTaskLifeSpan2 = 0;
199 CompilerPriorityCounterQueue queue2(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
200 options.GetCompilerQueueMaxLength(), CompilerTaskLifeSpan2);
201 queue2.AddTask(CompilerTask {main_method, 0});
202 queue2.AddTask(CompilerTask {f_method, 0});
203 queue2.AddTask(CompilerTask {g_method, 0});
204
205 // All tasks should expire without sleep
206 method = queue2.GetTask().GetMethod();
207 ASSERT_EQ(method, nullptr);
208 }
209
TEST_F(CompilerQueueTest,Reorder)210 TEST_F(CompilerQueueTest, Reorder)
211 {
212 auto klass = TestClassPrepare();
213
214 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
215 ASSERT_NE(main_method, nullptr);
216
217 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
218 ASSERT_NE(f_method, nullptr);
219
220 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
221 ASSERT_NE(g_method, nullptr);
222
223 main_method->SetHotnessCounter(1);
224 f_method->SetHotnessCounter(2);
225 g_method->SetHotnessCounter(3);
226
227 RuntimeOptions options;
228 CompilerPriorityCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
229 options.GetCompilerQueueMaxLength(), options.GetCompilerTaskLifeSpan());
230
231 // It is possible, that the first added method is expired, and others are not
232 // So, add to queue in reversed order to be sure, that the first method is present anyway
233 queue.AddTask(CompilerTask {g_method, 0});
234 queue.AddTask(CompilerTask {f_method, 0});
235 queue.AddTask(CompilerTask {main_method, 0});
236
237 // Change the order
238 main_method->SetHotnessCounter(6);
239 f_method->SetHotnessCounter(5);
240 g_method->SetHotnessCounter(4);
241
242 GetAndCheckMethodIfExists(&queue, main_method);
243 GetAndCheckMethodIfExists(&queue, f_method);
244 GetAndCheckMethodIfExists(&queue, g_method);
245 }
246
TEST_F(CompilerQueueTest,MaxLimit)247 TEST_F(CompilerQueueTest, MaxLimit)
248 {
249 auto klass = TestClassPrepare();
250
251 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
252 ASSERT_NE(main_method, nullptr);
253
254 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
255 ASSERT_NE(f_method, nullptr);
256
257 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
258 ASSERT_NE(g_method, nullptr);
259
260 main_method->SetHotnessCounter(1);
261 f_method->SetHotnessCounter(2);
262 g_method->SetHotnessCounter(3);
263
264 RuntimeOptions options;
265 constexpr int CompilerQueueMaxLength1 = 100;
266 CompilerPriorityCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
267 CompilerQueueMaxLength1, options.GetCompilerTaskLifeSpan());
268
269 for (int i = 0; i < 40; i++) {
270 queue.AddTask(CompilerTask {main_method, 0});
271 queue.AddTask(CompilerTask {f_method, 0});
272 queue.AddTask(CompilerTask {g_method, 0});
273 }
274
275 // 100 as Max_Limit
276 for (int i = 0; i < 100; i++) {
277 queue.GetTask();
278 // Can not check as the task may expire on an overloaded machine
279 }
280
281 auto method = queue.GetTask().GetMethod();
282 ASSERT_EQ(method, nullptr);
283
284 // check an option
285 constexpr int CompilerQueueMaxLength2 = 1;
286 CompilerPriorityCounterQueue queue2(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
287 CompilerQueueMaxLength2, options.GetCompilerTaskLifeSpan());
288
289 queue2.AddTask(CompilerTask {main_method, 0});
290 queue2.AddTask(CompilerTask {f_method, 0});
291 queue2.AddTask(CompilerTask {g_method, 0});
292
293 method = queue2.GetTask().GetMethod();
294 method = queue2.GetTask().GetMethod();
295 ASSERT_EQ(method, nullptr);
296 }
297
298 // Testing of AgedCounterQueue
299
TEST_F(CompilerQueueTest,AgedAddGet)300 TEST_F(CompilerQueueTest, AgedAddGet)
301 {
302 Class *klass = TestClassPrepare();
303
304 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
305 ASSERT_NE(main_method, nullptr);
306
307 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
308 ASSERT_NE(f_method, nullptr);
309
310 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
311 ASSERT_NE(g_method, nullptr);
312
313 // Manual range
314 main_method->SetHotnessCounter(1000);
315 f_method->SetHotnessCounter(1200);
316 g_method->SetHotnessCounter(1300);
317
318 RuntimeOptions options;
319 CompilerPriorityAgedCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
320 options.GetCompilerQueueMaxLength(), options.GetCompilerDeathCounterValue(),
321 options.GetCompilerEpochDuration());
322 queue.AddTask(CompilerTask {main_method, 0});
323 queue.AddTask(CompilerTask {f_method, 0});
324 queue.AddTask(CompilerTask {g_method, 0});
325
326 GetAndCheckMethodIfExists(&queue, g_method);
327 GetAndCheckMethodIfExists(&queue, f_method);
328 GetAndCheckMethodIfExists(&queue, main_method);
329 }
330
TEST_F(CompilerQueueTest,AgedEqualCounters)331 TEST_F(CompilerQueueTest, AgedEqualCounters)
332 {
333 Class *klass = TestClassPrepare();
334
335 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
336 ASSERT_NE(main_method, nullptr);
337
338 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
339 ASSERT_NE(g_method, nullptr);
340
341 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
342 ASSERT_NE(f_method, nullptr);
343
344 // Manual range
345 main_method->SetHotnessCounter(3000);
346 g_method->SetHotnessCounter(3000);
347 f_method->SetHotnessCounter(3000);
348
349 RuntimeOptions options;
350 CompilerPriorityAgedCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
351 options.GetCompilerQueueMaxLength(), options.GetCompilerDeathCounterValue(),
352 options.GetCompilerEpochDuration());
353 // Add in reversed order, as methods with equal counters will be ordered by timestamp
354 queue.AddTask(CompilerTask {f_method, 0});
355 queue.AddTask(CompilerTask {g_method, 0});
356 queue.AddTask(CompilerTask {main_method, 0});
357
358 GetAndCheckMethodIfExists(&queue, f_method);
359 GetAndCheckMethodIfExists(&queue, g_method);
360 GetAndCheckMethodIfExists(&queue, main_method);
361 }
362
TEST_F(CompilerQueueTest,AgedExpire)363 TEST_F(CompilerQueueTest, AgedExpire)
364 {
365 auto klass = TestClassPrepare();
366
367 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
368 ASSERT_NE(main_method, nullptr);
369
370 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
371 ASSERT_NE(f_method, nullptr);
372
373 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
374 ASSERT_NE(g_method, nullptr);
375
376 main_method->SetHotnessCounter(1000);
377 f_method->SetHotnessCounter(1000);
378 g_method->SetHotnessCounter(1000);
379
380 RuntimeOptions options;
381 constexpr int CompilerEpochDuration1 = 500;
382 CompilerPriorityAgedCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
383 options.GetCompilerQueueMaxLength(), options.GetCompilerDeathCounterValue(),
384 CompilerEpochDuration1);
385 queue.AddTask(CompilerTask {main_method, 0});
386 queue.AddTask(CompilerTask {f_method, 0});
387 queue.AddTask(CompilerTask {g_method, 0});
388
389 WaitForExpire(1600);
390
391 // All tasks should expire after sleep
392 auto method = queue.GetTask().GetMethod();
393 ASSERT_EQ(method, nullptr);
394
395 constexpr int CompilerEpochDuration2 = 1;
396 CompilerPriorityAgedCounterQueue queue2(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
397 options.GetCompilerQueueMaxLength(), options.GetCompilerDeathCounterValue(),
398 CompilerEpochDuration2);
399
400 queue2.AddTask(CompilerTask {main_method, 0});
401 queue2.AddTask(CompilerTask {f_method, 0});
402 queue2.AddTask(CompilerTask {g_method, 0});
403
404 WaitForExpire(5);
405
406 method = queue2.GetTask().GetMethod();
407 ASSERT_EQ(method, nullptr);
408 }
409
TEST_F(CompilerQueueTest,AgedReorder)410 TEST_F(CompilerQueueTest, AgedReorder)
411 {
412 auto klass = TestClassPrepare();
413
414 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
415 ASSERT_NE(main_method, nullptr);
416
417 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
418 ASSERT_NE(f_method, nullptr);
419
420 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
421 ASSERT_NE(g_method, nullptr);
422
423 main_method->SetHotnessCounter(1500);
424 f_method->SetHotnessCounter(2000);
425 g_method->SetHotnessCounter(3000);
426
427 RuntimeOptions options;
428 CompilerPriorityAgedCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
429 options.GetCompilerQueueMaxLength(), options.GetCompilerDeathCounterValue(),
430 options.GetCompilerEpochDuration());
431 // It is possible, that the first added method is expired, and others are not
432 // So, add to queue in reversed order to be sure, that the first method is present anyway
433 queue.AddTask(CompilerTask {g_method, 0});
434 queue.AddTask(CompilerTask {f_method, 0});
435 queue.AddTask(CompilerTask {main_method, 0});
436
437 // Change the order
438 main_method->SetHotnessCounter(6000);
439 f_method->SetHotnessCounter(5000);
440 g_method->SetHotnessCounter(4000);
441
442 GetAndCheckMethodIfExists(&queue, main_method);
443 GetAndCheckMethodIfExists(&queue, f_method);
444 GetAndCheckMethodIfExists(&queue, g_method);
445 }
446
TEST_F(CompilerQueueTest,AgedMaxLimit)447 TEST_F(CompilerQueueTest, AgedMaxLimit)
448 {
449 auto klass = TestClassPrepare();
450
451 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
452 ASSERT_NE(main_method, nullptr);
453
454 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
455 ASSERT_NE(f_method, nullptr);
456
457 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
458 ASSERT_NE(g_method, nullptr);
459
460 main_method->SetHotnessCounter(1000);
461 f_method->SetHotnessCounter(2000);
462 g_method->SetHotnessCounter(3000);
463
464 RuntimeOptions options;
465 CompilerPriorityAgedCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
466 options.GetCompilerQueueMaxLength(), options.GetCompilerDeathCounterValue(),
467 options.GetCompilerEpochDuration());
468
469 for (int i = 0; i < 40; i++) {
470 queue.AddTask(CompilerTask {main_method, 0});
471 queue.AddTask(CompilerTask {f_method, 0});
472 queue.AddTask(CompilerTask {g_method, 0});
473 }
474
475 // 100 as Max_Limit
476 for (int i = 0; i < 100; i++) {
477 queue.GetTask();
478 // Can not check as the task may expire on an overloaded machine
479 }
480
481 auto method = queue.GetTask().GetMethod();
482 ASSERT_EQ(method, nullptr);
483
484 // check an option
485 constexpr int CompilerQueueMaxLength = 1;
486 CompilerPriorityAgedCounterQueue queue2(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
487 CompilerQueueMaxLength, options.GetCompilerDeathCounterValue(),
488 options.GetCompilerEpochDuration());
489
490 queue2.AddTask(CompilerTask {main_method, 0});
491 queue2.AddTask(CompilerTask {f_method, 0});
492 queue2.AddTask(CompilerTask {g_method, 0});
493
494 method = queue2.GetTask().GetMethod();
495 method = queue2.GetTask().GetMethod();
496 ASSERT_EQ(method, nullptr);
497 }
498
TEST_F(CompilerQueueTest,AgedDeathCounter)499 TEST_F(CompilerQueueTest, AgedDeathCounter)
500 {
501 auto klass = TestClassPrepare();
502
503 Method *main_method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
504 ASSERT_NE(main_method, nullptr);
505
506 Method *f_method = klass->GetDirectMethod(utf::CStringAsMutf8("f"));
507 ASSERT_NE(f_method, nullptr);
508
509 Method *g_method = klass->GetDirectMethod(utf::CStringAsMutf8("g"));
510 ASSERT_NE(g_method, nullptr);
511
512 main_method->SetHotnessCounter(10);
513 f_method->SetHotnessCounter(20);
514 g_method->SetHotnessCounter(30000);
515
516 RuntimeOptions options;
517 constexpr int CompilerDeathCounterValue = 50;
518 CompilerPriorityAgedCounterQueue queue(thread_->GetVM()->GetHeapManager()->GetInternalAllocator(),
519 options.GetCompilerQueueMaxLength(), CompilerDeathCounterValue,
520 options.GetCompilerEpochDuration());
521
522 queue.AddTask(CompilerTask {main_method, 0});
523 queue.AddTask(CompilerTask {f_method, 0});
524 queue.AddTask(CompilerTask {g_method, 0});
525
526 auto method = queue.GetTask().GetMethod();
527 ASSERT_EQ(method, g_method);
528 method = queue.GetTask().GetMethod();
529 ASSERT_EQ(method, nullptr);
530 }
531 } // namespace panda::test
532