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 <compiler/compiler_options.h>
17 #include <gtest/gtest.h>
18
19 #include <bitset>
20 #include <vector>
21
22 #include "assembly-emitter.h"
23 #include "assembly-parser.h"
24 #include "code_info/code_info_builder.h"
25 #include "libpandabase/utils/cframe_layout.h"
26 #include "runtime/bridge/bridge.h"
27 #include "runtime/include/runtime.h"
28 #include "runtime/include/stack_walker-inl.h"
29 #include "runtime/include/thread_scopes.h"
30 #include "runtime/mem/refstorage/global_object_storage.h"
31 #include "runtime/tests/test_utils.h"
32
33 namespace panda::test {
34
35 using namespace compiler;
36
37 #define HOOK_ASSERT(cond, action) \
38 do { \
39 if (!(cond)) { \
40 std::cerr << "ASSERT FAILED(" << __LINE__ << "): " << #cond << std::endl; \
41 action; \
42 } \
43 } while (0)
44
45 class StackWalkerTest : public testing::Test {
46 public:
47 using Callback = int (*)(uintptr_t, uintptr_t);
48
StackWalkerTest()49 StackWalkerTest()
50 : default_compiler_non_optimizing_(options.IsCompilerNonOptimizing()),
51 default_compiler_regex_(options.GetCompilerRegex())
52 {
53 }
54
~StackWalkerTest()55 ~StackWalkerTest()
56 {
57 options.SetCompilerNonOptimizing(default_compiler_non_optimizing_);
58 options.SetCompilerRegex(default_compiler_regex_);
59 }
60
SetUp()61 void SetUp() override
62 {
63 #ifndef PANDA_COMPILER_ENABLE
64 GTEST_SKIP();
65 #endif
66 }
67
68 void TestModifyManyVregs(bool is_compiled);
69
GetMethod(std::string_view method_name)70 static Method *GetMethod(std::string_view method_name)
71 {
72 PandaString descriptor;
73 auto *thread = MTManagedThread::GetCurrent();
74 thread->ManagedCodeBegin();
75 auto *extension = Runtime::GetCurrent()->GetClassLinker()->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
76 auto cls = extension->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), &descriptor));
77 thread->ManagedCodeEnd();
78 ASSERT(cls);
79 return cls->GetDirectMethod(utf::CStringAsMutf8(method_name.data()));
80 }
81
82 static mem::Reference *global_obj;
83
84 private:
85 bool default_compiler_non_optimizing_;
86 std::string default_compiler_regex_;
87 };
88
89 mem::Reference *StackWalkerTest::global_obj;
90
91 extern "C" int PandaRunnerHook();
92 extern "C" int StackWalkerHookAArch64Bridge();
93
94 class PandaRunner {
95 public:
96 using Callback = int (*)(uintptr_t, uintptr_t);
97
PandaRunner()98 PandaRunner()
99 {
100 auto exec_path = panda::os::file::File::GetExecutablePath();
101
102 options_.SetBootPandaFiles({exec_path.Value() + "/../pandastdlib/arkstdlib.abc"});
103
104 options_.SetShouldLoadBootPandaFiles(true);
105 options_.SetShouldInitializeIntrinsics(false);
106 options_.SetCompilerEnableJit(true);
107 options_.SetNoAsyncJit(true);
108 }
109
110 ~PandaRunner() = default;
111
Run(std::string_view source,Callback hook)112 void Run(std::string_view source, Callback hook)
113 {
114 auto finalizer = [](void *) {
115 callback_ = nullptr;
116 Runtime::Destroy();
117 };
118 std::unique_ptr<void, decltype(finalizer)> runtime_destroyer(&finalizer, finalizer);
119
120 pandasm::Parser parser;
121
122 Runtime::Create(options_);
123 auto thread = MTManagedThread::GetCurrent();
124 {
125 ScopedManagedCodeThread s(thread);
126 auto storage = Runtime::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage();
127 StackWalkerTest::global_obj =
128 storage->Add(panda::mem::AllocateNullifiedPayloadString(1), mem::Reference::ObjectType::GLOBAL);
129 }
130 auto res = parser.Parse(source.data());
131 ASSERT_TRUE(res) << "Parse failed: " << res.Error().message << "\nLine " << res.Error().line_number << ": "
132 << res.Error().whole_line;
133 auto pf = pandasm::AsmEmitter::Emit(res.Value());
134 Runtime::GetCurrent()->GetClassLinker()->AddPandaFile(std::move(pf));
135
136 if (auto method = StackWalkerTest::GetMethod("hook"); method != nullptr) {
137 if constexpr (RUNTIME_ARCH == Arch::AARCH64) {
138 // AARCH64: only used callee-saved registers are saved/restored in prologue/epilogue.
139 // So the code must enter PandaRunnerHook through StackWalkerHookAArch64Bridge,
140 // which saves all callee-saved registers before calling PandaRunnerHook.
141 method->SetCompiledEntryPoint(reinterpret_cast<void *>(StackWalkerHookAArch64Bridge));
142 } else {
143 method->SetCompiledEntryPoint(reinterpret_cast<void *>(PandaRunnerHook));
144 }
145 callback_ = hook;
146 }
147
148 auto eres = Runtime::GetCurrent()->Execute("_GLOBAL::main", {});
149 ASSERT_TRUE(eres) << (unsigned)eres.Error();
150 ASSERT_EQ(eres.Value(), 0);
151 }
152
GetRuntimeOptions()153 RuntimeOptions &GetRuntimeOptions()
154 {
155 return options_;
156 }
157
GetCompilerOptions()158 auto &GetCompilerOptions()
159 {
160 return compiler::options;
161 }
162
163 private:
164 friend int PandaRunnerHook();
165 RuntimeOptions options_;
166 static inline Callback callback_ {nullptr};
167 };
168
PandaRunnerHook()169 NO_OPTIMIZE int PandaRunnerHook()
170 {
171 ASSERT(PandaRunner::callback_);
172 uintptr_t fp = 0;
173 uintptr_t lr = 0;
174 if constexpr (RUNTIME_ARCH == Arch::AARCH64) {
175 ManagedThread::GetCurrent()->SetCurrentFrameIsCompiled(true);
176 fp = reinterpret_cast<uintptr_t>(ManagedThread::GetCurrent()->GetCurrentFrame());
177 lr = ManagedThread::GetCurrent()->GetNativePc();
178 return PandaRunner::callback_(lr, fp);
179 }
180 if constexpr (RUNTIME_ARCH == Arch::AARCH32) {
181 ManagedThread::GetCurrent()->SetCurrentFrameIsCompiled(true);
182 #if (defined(__clang__) || defined(PANDA_TARGET_ARM64))
183 asm("ldr %0, [fp, #0]" : "=r"(fp));
184 asm("ldr %0, [fp, #4]" : "=r"(lr));
185 #else
186 // gcc compile header "push {r4, r11, lr}"
187 asm("ldr %0, [fp, #-4]" : "=r"(fp));
188 asm("ldr %0, [fp, #0]" : "=r"(lr));
189 #endif
190 ManagedThread::GetCurrent()->SetCurrentFrame(reinterpret_cast<Frame *>(fp));
191 return PandaRunner::callback_(lr, fp);
192 }
193 if constexpr (RUNTIME_ARCH == Arch::X86_64) {
194 ManagedThread::GetCurrent()->SetCurrentFrameIsCompiled(true);
195 asm("movq (%%rbp), %0" : "=r"(fp));
196 asm("movq 8(%%rbp), %0" : "=r"(lr));
197 ManagedThread::GetCurrent()->SetCurrentFrame(reinterpret_cast<Frame *>(fp));
198 return PandaRunner::callback_(lr, fp);
199 }
200 return -1;
201 }
202
203 template <typename T>
ConvertToU64(T val)204 uint64_t ConvertToU64(T val)
205 {
206 if constexpr (sizeof(T) == sizeof(uint32_t)) {
207 return bit_cast<uint32_t>(val);
208 } else if constexpr (sizeof(T) == sizeof(uint64_t)) {
209 return bit_cast<uint64_t>(val);
210 } else {
211 return static_cast<uint64_t>(val);
212 }
213 }
214
215 template <Arch arch>
ToCalleeRegister(size_t reg)216 int32_t ToCalleeRegister(size_t reg)
217 {
218 return reg + GetFirstCalleeReg(arch, false);
219 }
220
221 template <Arch arch>
ToCalleeFpRegister(size_t reg)222 int32_t ToCalleeFpRegister(size_t reg)
223 {
224 return reg + GetFirstCalleeReg(arch, true);
225 }
226
TEST_F(StackWalkerTest,ModifyVreg)227 TEST_F(StackWalkerTest, ModifyVreg)
228 {
229 // In Release and FastVerify modes compiler can omit frame pointer,
230 // thus PandaRunner can't work properly in these modes.
231 #if defined(NDEBUG) || defined(PANDA_FAST_VERIFY)
232 if constexpr (RUNTIME_ARCH == Arch::X86_64) {
233 GTEST_SKIP();
234 }
235 #endif
236 if constexpr (RUNTIME_ARCH == Arch::AARCH64) {
237 GTEST_SKIP();
238 }
239 auto source = R"(
240 .function i32 main() {
241 movi.64 v0, 27
242 try_begin:
243 call.short testa, v0
244 jmp try_end
245 try_end:
246 return
247 .catchall try_begin, try_end, try_end
248 }
249 .function i32 testa(i64 a0) {
250 call.short testb, a0
251 return
252 }
253 .function i32 testb(i64 a0) {
254 call.short testc, a0
255 return
256 }
257 .function i32 testc(i64 a0) {
258 lda a0
259 try_begin:
260 call.short hook # change vregs in all frames
261 jnez exit
262 call.short hook # verify vregs in all frames
263 jmp exit
264 exit:
265 return
266 .catchall try_begin, exit, exit
267 }
268 .function i32 hook() {
269 ldai 1
270 return
271 }
272 )";
273
274 PandaRunner runner;
275 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
276 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
277 runner.GetCompilerOptions().SetCompilerRematConst(false);
278 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::testb|_GLOBAL::hook).*");
279 [[maybe_unused]] static constexpr std::array<uint64_t, 3> frame_values = {0x123456789abcdef, 0xaaaabbbbccccdddd,
280 0xabcdef20};
281 static int run_count = 0;
282 runner.Run(source, [](uintptr_t lr, [[maybe_unused]] uintptr_t fp) -> int {
283 StackWalker walker(reinterpret_cast<void *>(fp), true, lr);
284 bool success = false;
285 walker.NextFrame();
286 if (run_count == 0) {
287 bool was_set = false;
288 HOOK_ASSERT(!walker.IsCFrame(), return 1);
289 success = walker.IterateVRegsWithInfo([&was_set, &walker](const auto ®_info, const auto ®) {
290 if (!reg_info.IsAccumulator()) {
291 HOOK_ASSERT(reg.GetLong() == 27, return false);
292 walker.SetVRegValue(reg_info, frame_values[0]);
293 was_set = true;
294 }
295 return true;
296 });
297 HOOK_ASSERT(success, return 1);
298 HOOK_ASSERT(was_set, return 1);
299
300 walker.NextFrame();
301 HOOK_ASSERT(walker.IsCFrame(), return 1);
302 success = walker.IterateVRegsWithInfo([&walker](const auto ®_info, const auto ®) {
303 if (!reg_info.IsAccumulator()) {
304 HOOK_ASSERT(reg.GetLong() == 27, return false);
305 walker.SetVRegValue(reg_info, frame_values[1]);
306 }
307 return true;
308 });
309 HOOK_ASSERT(success, return 1);
310
311 walker.NextFrame();
312 HOOK_ASSERT(walker.IsCFrame(), return 1);
313 success = walker.IterateVRegsWithInfo([&walker](const auto ®_info, const auto ®) {
314 if (!reg_info.IsAccumulator()) {
315 HOOK_ASSERT(reg.GetLong() == 27, return true;);
316 walker.SetVRegValue(reg_info, frame_values[2]);
317 }
318 return true;
319 });
320 HOOK_ASSERT(success, return 1);
321 } else if (run_count == 1) {
322 HOOK_ASSERT(!walker.IsCFrame(), return 1);
323 success = walker.IterateVRegsWithInfo([](const auto ®_info, const auto ®) {
324 if (!reg_info.IsAccumulator()) {
325 HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(frame_values[0]), return true;);
326 }
327 return true;
328 });
329 HOOK_ASSERT(success, return 1);
330
331 walker.NextFrame();
332 HOOK_ASSERT(walker.IsCFrame(), return 1);
333 success = walker.IterateVRegsWithInfo([](const auto ®_info, const auto ®) {
334 if (!reg_info.IsAccumulator()) {
335 HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(frame_values[1]), return true;);
336 }
337 return true;
338 });
339 HOOK_ASSERT(success, return 1);
340
341 walker.NextFrame();
342 HOOK_ASSERT(walker.IsCFrame(), return 1);
343 success = walker.IterateVRegsWithInfo([](const auto ®_info, const auto ®) {
344 if (!reg_info.IsAccumulator()) {
345 HOOK_ASSERT(reg.GetLong() == bit_cast<int64_t>(frame_values[2]), return true;);
346 }
347 return true;
348 });
349 HOOK_ASSERT(success, return 1);
350 } else {
351 return 1;
352 }
353 run_count++;
354 return 0;
355 });
356 ASSERT_EQ(run_count, 2);
357 }
358
TestModifyManyVregs(bool is_compiled)359 void StackWalkerTest::TestModifyManyVregs(bool is_compiled)
360 {
361 auto source = R"(
362 .function i32 main() {
363 movi.64 v0, 5
364 try_begin:
365 call.short test
366 jmp try_end
367 try_end:
368 return
369
370 .catchall try_begin, try_end, try_end
371 }
372
373 .function i32 test() {
374 movi.64 v1, 1
375 movi.64 v2, 2
376 movi.64 v3, 3
377 movi.64 v4, 4
378 movi.64 v5, 5
379 movi.64 v6, 6
380 movi.64 v7, 7
381 movi.64 v8, 8
382 movi.64 v9, 9
383 movi.64 v10, 10
384 movi.64 v11, 11
385 movi.64 v12, 12
386 movi.64 v13, 13
387 movi.64 v14, 14
388 movi.64 v15, 15
389 movi.64 v16, 16
390 movi.64 v17, 17
391 movi.64 v18, 18
392 movi.64 v19, 19
393 movi.64 v20, 20
394 movi.64 v21, 21
395 movi.64 v22, 22
396 movi.64 v23, 23
397 movi.64 v24, 24
398 movi.64 v25, 25
399 movi.64 v26, 26
400 movi.64 v27, 27
401 movi.64 v28, 28
402 movi.64 v29, 29
403 movi.64 v30, 30
404 movi.64 v31, 31
405 try_begin:
406 mov v0, v31
407 newarr v0, v0, i32[]
408 call.short stub
409 jmp try_end
410 try_end:
411 return
412
413 .catchall try_begin, try_end, try_end
414 }
415
416 .function i32 stub() {
417 try_begin:
418 call.short hook # change vregs in all frames
419 jnez exit
420 call.short hook # verify vregs in all frames
421 jmp exit
422 exit:
423 return
424
425 .catchall try_begin, exit, exit
426 }
427
428 .function i32 hook() {
429 ldai 1
430 return
431 }
432 )";
433
434 static bool first_run;
435 static bool compiled;
436
437 PandaRunner runner;
438 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
439 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
440
441 if (!is_compiled) {
442 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main)(?!_GLOBAL::test)(?!_GLOBAL::hook).*");
443 }
444
445 first_run = true;
446 compiled = is_compiled;
447 runner.Run(source, [](uintptr_t lr, [[maybe_unused]] uintptr_t fp) -> int {
448 StackWalker walker(reinterpret_cast<void *>(fp), true, lr);
449
450 HOOK_ASSERT(walker.GetMethod()->GetFullName() == "_GLOBAL::stub", return 1);
451 walker.NextFrame();
452 HOOK_ASSERT(walker.GetMethod()->GetFullName() == "_GLOBAL::test", return 1);
453 HOOK_ASSERT(walker.IsCFrame() == compiled, return 1);
454
455 int reg_index = 1;
456 bool success = false;
457 auto obj = Runtime::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage()->Get(StackWalkerTest::global_obj);
458 if (first_run) {
459 success = walker.IterateVRegsWithInfo([®_index, &walker, &obj](const auto ®_info, const auto ®) {
460 if (!reg_info.IsAccumulator()) {
461 if (reg.HasObject()) {
462 HOOK_ASSERT(reg.GetReference() != nullptr, return false);
463 walker.SetVRegValue(reg_info, obj);
464 } else if (reg_info.GetLocation() != VRegInfo::Location::CONSTANT) {
465 // frame_size is more than nregs_ now for inst `V4_V4_ID16` and `V4_V4_V4_V4_ID16`
466 HOOK_ASSERT(reg.GetLong() == 0 || reg_info.GetIndex() == reg.GetLong(), return false);
467 walker.SetVRegValue(reg_info, reg_info.GetIndex() + 100000000000);
468 }
469 reg_index++;
470 }
471 return true;
472 });
473 HOOK_ASSERT(success, return 1);
474 HOOK_ASSERT(reg_index >= 32, return 1);
475 first_run = false;
476 } else {
477 success = walker.IterateVRegsWithInfo([®_index, &obj](const auto ®_info, const auto ®) {
478 if (!reg_info.IsAccumulator()) {
479 if (reg.HasObject()) {
480 HOOK_ASSERT((reg.GetReference() == reinterpret_cast<ObjectHeader *>(Low32Bits(obj))),
481 return false);
482 } else {
483 if (reg_info.GetLocation() != VRegInfo::Location::CONSTANT) {
484 HOOK_ASSERT(reg.GetLong() == (reg_info.GetIndex() + 100000000000), return false);
485 }
486 reg_index++;
487 }
488 }
489 return true;
490 });
491 HOOK_ASSERT(success, return 1);
492 HOOK_ASSERT(reg_index >= 32, return 1);
493 }
494
495 HOOK_ASSERT(success, return 1);
496 return 0;
497 });
498 }
499
TEST_F(StackWalkerTest,ModifyMultipleVregs)500 TEST_F(StackWalkerTest, ModifyMultipleVregs)
501 {
502 // In Release and FastVerify modes compiler can omit frame pointer, thus, PandaRunner can't work properly in these
503 // modes.
504 #if defined(NDEBUG) || defined(PANDA_FAST_VERIFY)
505 if constexpr (RUNTIME_ARCH == Arch::X86_64) {
506 GTEST_SKIP();
507 }
508 #endif
509 if constexpr (ArchTraits<RUNTIME_ARCH>::SUPPORT_DEOPTIMIZATION) {
510 TestModifyManyVregs(true);
511 TestModifyManyVregs(false);
512 }
513 }
514
TEST_F(StackWalkerTest,ThrowExceptionThroughMultipleFrames)515 TEST_F(StackWalkerTest, ThrowExceptionThroughMultipleFrames)
516 {
517 auto source = R"(
518 .record E {}
519
520 .function u1 f4() {
521 newobj v0, E
522 throw v0
523 return
524 }
525
526 .function u1 f3() {
527 call f4
528 return
529 }
530
531 .function u1 f2() {
532 call f3
533 return
534 }
535
536 .function u1 f1() {
537 call f2
538 return
539 }
540
541 .function u1 main() {
542 try_begin:
543 movi v0, 123
544 call f1
545 ldai 1
546 return
547 try_end:
548
549 catch_block1_begin:
550 movi v1, 123
551 lda v0
552 jne v1, exit_1
553 ldai 0
554 return
555 exit_1:
556 ldai 1
557 return
558
559 .catch E, try_begin, try_end, catch_block1_begin
560 }
561 )";
562
563 PandaRunner runner;
564 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
565 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
566
567 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
568 runner.Run(source, nullptr);
569
570 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2).*");
571 runner.Run(source, nullptr);
572
573 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4|_GLOBAL::f2).*");
574 runner.Run(source, nullptr);
575
576 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f4).*");
577 runner.Run(source, nullptr);
578
579 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
580 runner.Run(source, nullptr);
581
582 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main).*");
583 runner.Run(source, nullptr);
584
585 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
586 runner.Run(source, nullptr);
587
588 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f1|_GLOBAL::f2).*");
589 runner.Run(source, nullptr);
590
591 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4|_GLOBAL::f2).*");
592 runner.Run(source, nullptr);
593
594 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f4).*");
595 runner.Run(source, nullptr);
596
597 runner.GetCompilerOptions().SetCompilerRegex(".*");
598 runner.Run(source, nullptr);
599
600 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f1|_GLOBAL::f2|_GLOBAL::f3).*");
601 runner.Run(source, nullptr);
602
603 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f1|_GLOBAL::f2).*");
604 runner.Run(source, nullptr);
605
606 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::f2).*");
607 runner.Run(source, nullptr);
608
609 runner.GetCompilerOptions().SetCompilerRegex(".*");
610 runner.Run(source, nullptr);
611 }
612
TEST_F(StackWalkerTest,CatchInCompiledCode)613 TEST_F(StackWalkerTest, CatchInCompiledCode)
614 {
615 auto source = R"(
616 .record panda.String <external>
617 .record panda.ArrayIndexOutOfBoundsException <external>
618
619 .function i32 f2() {
620 movi v0, 4
621 newarr v1, v0, i64[]
622 ldai 42
623 ldarr v1 # BoundsException
624 return
625 }
626
627 .function i32 f1() {
628 try_begin:
629 call f2
630 ldai 1
631 return
632 try_end:
633
634 catch_block1_begin:
635 ldai 123
636 return
637
638 .catch panda.ArrayIndexOutOfBoundsException, try_begin, try_end, catch_block1_begin
639 }
640
641 .function u1 main() {
642 call f1
643 movi v0, 123
644 jne v0, exit_1
645 ldai 0
646 return
647 exit_1:
648 ldai 1
649 return
650 }
651 )";
652
653 PandaRunner runner;
654 runner.GetRuntimeOptions().SetCompilerHotnessThreshold(0);
655 runner.GetCompilerOptions().SetCompilerNonOptimizing(true);
656
657 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f2).*");
658 runner.Run(source, nullptr);
659
660 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main|_GLOBAL::f1).*");
661 runner.Run(source, nullptr);
662
663 runner.GetCompilerOptions().SetCompilerRegex("(?!_GLOBAL::main).*");
664 runner.Run(source, nullptr);
665
666 runner.GetCompilerOptions().SetCompilerRegex(".*");
667 runner.Run(source, nullptr);
668 }
669
670 } // namespace panda::test
671