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 <elf.h>
17#include "unit_test.h"
18#include "aot/aot_manager.h"
19#include "aot/aot_builder/aot_builder.h"
20#include "aot/compiled_method.h"
21#include "compiler/code_info/code_info_builder.h"
22#include "os/exec.h"
23#include "assembly-parser.h"
24#include "utils/string_helpers.h"
25#include "events/events.h"
26#include "mem/gc/gc_types.h"
27#include "runtime/include/file_manager.h"
28
29#include <regex>
30
31using panda::panda_file::File;
32
33namespace panda::compiler {
34class AotTest : public AsmTest {
35public:
36    AotTest()
37    {
38        std::string exe_path = GetExecPath();
39        auto pos = exe_path.rfind('/');
40        paoc_path_ = exe_path.substr(0, pos) + "/../bin/ark_aot";
41        aotdump_path_ = exe_path.substr(0, pos) + "/../bin/ark_aotdump";
42    }
43
44    std::string GetPaocDirectory() const
45    {
46        auto pos = paoc_path_.rfind('/');
47        return paoc_path_.substr(0, pos);
48    }
49
50    const char *GetArchAsArgString() const
51    {
52        switch (target_arch) {
53            case Arch::AARCH32:
54                return "arm";
55            case Arch::AARCH64:
56                return "arm64";
57            case Arch::X86:
58                return "x86";
59            case Arch::X86_64:
60                return "x86_64";
61            default:
62                UNREACHABLE();
63        }
64    }
65
66    void RunAotdump(const std::string &aot_filename)
67    {
68        TmpFile tmpfile("aotdump.tmp");
69
70        auto res = os::exec::Exec(aotdump_path_.c_str(), "--show-code=disasm", "--output-file", tmpfile.GetFileName(),
71                                  aot_filename.c_str());
72        ASSERT_TRUE(res) << "aotdump failed with error: " << res.Error().ToString();
73        ASSERT_EQ(res.Value(), 0) << "aotdump return error code: " << res.Value();
74    }
75
76    std::string paoc_path_;
77    std::string aotdump_path_;
78protected:
79    Arch target_arch = Arch::AARCH64;
80};
81
82#ifdef PANDA_COMPILER_TARGET_AARCH64
83TEST_F(AotTest, PaocBootPandaFiles)
84{
85    // Test basic functionality only in host mode.
86    if (RUNTIME_ARCH != Arch::X86_64) {
87        return;
88    }
89    TmpFile panda_fname("test.pf");
90    TmpFile aot_fname("./test.an");
91    static const std::string location = "/data/local/tmp";
92    static const std::string panda_file_path = location + "/" + panda_fname.GetFileName();
93
94    auto source = R"(
95        .function void dummy() {
96            return.void
97        }
98    )";
99
100    {
101        pandasm::Parser parser;
102        auto res = parser.Parse(source);
103        ASSERT_TRUE(res);
104        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname.GetFileName(), res.Value()));
105    }
106
107    // Correct path to arkstdlib.abc
108    {
109        auto pandastdlib_path = GetPaocDirectory() + "/../pandastdlib/arkstdlib.abc";
110        auto res = os::exec::Exec(paoc_path_.c_str(), "--paoc-panda-files", panda_fname.GetFileName(), "--paoc-output",
111                                  aot_fname.GetFileName(), "--paoc-location", location.c_str(), "--paoc-arch",
112                                  GetArchAsArgString(), "--boot-panda-files", pandastdlib_path.c_str());
113        ASSERT_TRUE(res) << "paoc failed with error: " << res.Error().ToString();
114        ASSERT_EQ(res.Value(), 0) << "Aot compiler failed with code " << res.Value();
115        RunAotdump(aot_fname.GetFileName());
116    }
117}
118
119TEST_F(AotTest, PaocLocation)
120{
121    // Test basic functionality only in host mode.
122    if (RUNTIME_ARCH != Arch::X86_64) {
123        return;
124    }
125    TmpFile panda_fname("test.pf");
126    TmpFile aot_fname("./test.an");
127    static const std::string location = "/data/local/tmp";
128    static const std::string panda_file_path = location + "/" + panda_fname.GetFileName();
129
130    auto source = R"(
131        .function u32 add(u64 a0, u64 a1) {
132            add a0, a1
133            return
134        }
135    )";
136
137    {
138        pandasm::Parser parser;
139        auto res = parser.Parse(source);
140        ASSERT_TRUE(res);
141        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname.GetFileName(), res.Value()));
142    }
143
144    {
145        auto pandastdlib_path = GetPaocDirectory() + "/../pandastdlib/arkstdlib.abc";
146        auto res = os::exec::Exec(paoc_path_.c_str(), "--paoc-panda-files", panda_fname.GetFileName(), "--paoc-output",
147                                  aot_fname.GetFileName(), "--paoc-location", location.c_str(), "--paoc-arch=x86_64",
148                                  "--gc-type=epsilon", "--paoc-use-cha=false");
149        ASSERT_TRUE(res) << "paoc failed with error: " << res.Error().ToString();
150        ASSERT_EQ(res.Value(), 0) << "Aot compiler failed with code " << res.Value();
151    }
152
153    AotManager aot_manager;
154    {
155        auto res =
156            aot_manager.AddFile(aot_fname.GetFileName(), nullptr, static_cast<uint32_t>(mem::GCType::EPSILON_GC));
157        ASSERT_TRUE(res) << res.Error();
158    }
159
160    auto aot_file = aot_manager.GetFile(aot_fname.GetFileName());
161    ASSERT_TRUE(aot_file);
162    ASSERT_EQ(aot_file->GetFilesCount(), 1);
163    ASSERT_TRUE(aot_file->FindPandaFile(panda_file_path));
164}
165#endif  // PANDA_COMPILER_TARGET_AARCH64
166
167TEST_F(AotTest, BuildAndLoad)
168{
169    if (RUNTIME_ARCH == Arch::AARCH32) {
170        // TODO(msherstennikov): for some reason dlopen cannot open aot file in qemu-arm
171        return;
172    }
173    uint32_t tid = os::thread::GetCurrentThreadId();
174    std::string tmpfile = helpers::string::Format("/tmp/tmpfile_%04x.pn", tid);
175    static constexpr const char *tmpfile_pf = "test.pf";
176    static constexpr const char *cmdline = "cmdline";
177    static constexpr uint32_t method1_id = 42;
178    static constexpr uint32_t method2_id = 43;
179    const std::string class_name("Foo");
180    std::string method_name(class_name + "::method");
181    std::array<uint8_t, 4> x86_add = {
182        0x8d, 0x04, 0x37,  // lea    eax,[rdi+rdi*1]
183        0xc3               // ret
184    };
185
186    AotBuilder aot_builder;
187    aot_builder.SetArch(RUNTIME_ARCH);
188    aot_builder.SetGcType(2);
189    RuntimeInterfaceMock iruntime;
190    aot_builder.SetRuntime(&iruntime);
191
192    aot_builder.StartFile(tmpfile_pf, 0x12345678);
193
194    auto thread = MTManagedThread::GetCurrent();
195    if (thread != nullptr) {
196        thread->ManagedCodeBegin();
197    }
198    auto runtime = Runtime::GetCurrent();
199    auto etx = runtime->GetClassLinker()->GetExtension(runtime->GetLanguageContext(runtime->GetRuntimeType()));
200    auto klass = etx->CreateClass(reinterpret_cast<const uint8_t *>(class_name.data()), 0, 0,
201                                  AlignUp(sizeof(Class), OBJECT_POINTER_SIZE));
202    if (thread != nullptr) {
203        thread->ManagedCodeEnd();
204    }
205
206    klass->SetFileId(panda_file::File::EntityId(13));
207    aot_builder.StartClass(*klass);
208
209    Method method1(klass, nullptr, File::EntityId(method1_id), File::EntityId(), 0, 1, nullptr);
210    {
211        CodeInfoBuilder code_builder(RUNTIME_ARCH, GetAllocator());
212        ArenaVector<uint8_t> data(GetAllocator()->Adapter());
213        code_builder.Encode(&data);
214        CompiledMethod compiled_method1(RUNTIME_ARCH, &method1);
215        compiled_method1.SetCode(Span(reinterpret_cast<const uint8_t *>(method_name.data()), method_name.size() + 1));
216        compiled_method1.SetCodeInfo(Span(data).ToConst());
217        aot_builder.AddMethod(compiled_method1, 0);
218    }
219
220    Method method2(klass, nullptr, File::EntityId(method2_id), File::EntityId(), 0, 1, nullptr);
221    {
222        CodeInfoBuilder code_builder(RUNTIME_ARCH, GetAllocator());
223        ArenaVector<uint8_t> data(GetAllocator()->Adapter());
224        code_builder.Encode(&data);
225        CompiledMethod compiled_method2(RUNTIME_ARCH, &method2);
226        compiled_method2.SetCode(Span(reinterpret_cast<const uint8_t *>(x86_add.data()), x86_add.size()));
227        compiled_method2.SetCodeInfo(Span(data).ToConst());
228        aot_builder.AddMethod(compiled_method2, 1);
229    }
230
231    aot_builder.EndClass();
232    uint32_t hash = GetHash32String(reinterpret_cast<const uint8_t *>(class_name.data()));
233    aot_builder.InsertEntityPairHeader(hash, 13);
234    aot_builder.InsertClassHashTableSize(1);
235    aot_builder.EndFile();
236
237    aot_builder.Write(cmdline, tmpfile.c_str());
238
239    AotManager aot_manager;
240    auto res = aot_manager.AddFile(tmpfile.c_str(), nullptr, static_cast<uint32_t>(mem::GCType::STW_GC));
241    ASSERT_TRUE(res) << res.Error();
242
243    auto aot_file = aot_manager.GetFile(tmpfile.c_str());
244    ASSERT_TRUE(aot_file);
245    ASSERT_TRUE(strcmp(cmdline, aot_file->GetCommandLine()) == 0U);
246    ASSERT_TRUE(strcmp(tmpfile.c_str(), aot_file->GetFileName()) == 0U);
247    ASSERT_EQ(aot_file->GetFilesCount(), 1U);
248
249    auto pfile = aot_manager.FindPandaFile(tmpfile_pf);
250    ASSERT_NE(pfile, nullptr);
251    auto cls = pfile->GetClass(13);
252    ASSERT_TRUE(cls.IsValid());
253
254    {
255        auto code = cls.FindMethodCodeEntry(0);
256        ASSERT_FALSE(code == nullptr);
257        ASSERT_EQ(method_name, reinterpret_cast<const char *>(code));
258    }
259
260    {
261        auto code = cls.FindMethodCodeEntry(1);
262        ASSERT_FALSE(code == nullptr);
263        ASSERT_EQ(std::memcmp(x86_add.data(), code, x86_add.size()), 0);
264#ifdef PANDA_TARGET_AMD64
265        auto func_add = (int (*)(int, int))code;
266        ASSERT_EQ(func_add(2, 3), 5);
267#endif
268    }
269}
270
271TEST_F(AotTest, PaocSpecifyMethods)
272{
273#ifndef PANDA_EVENTS_ENABLED
274    GTEST_SKIP();
275#endif
276
277    // Test basic functionality only in host mode.
278    if (RUNTIME_ARCH != Arch::X86_64) {
279        return;
280    }
281    TmpFile panda_fname("test.pf");
282    TmpFile paoc_output_name("events-out.csv");
283
284    static const std::string location = "/data/local/tmp";
285    static const std::string panda_file_path = location + "/" + panda_fname.GetFileName();
286
287    auto source = R"(
288        .record A {}
289        .record B {}
290
291        .function i32 A.f1() {
292            ldai 10
293            return
294        }
295
296        .function i32 B.f1() {
297            ldai 20
298            return
299        }
300
301        .function i32 A.f2() {
302            ldai 10
303            return
304        }
305
306        .function i32 B.f2() {
307            ldai 20
308            return
309        }
310
311        .function i32 main() {
312            ldai 0
313            return
314        }
315    )";
316
317    {
318        pandasm::Parser parser;
319        auto res = parser.Parse(source);
320        ASSERT_TRUE(res);
321        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname.GetFileName(), res.Value()));
322    }
323
324    {
325        // paoc will try compiling all the methods from the panda-file that matches `--compiler-regex`
326        auto res =
327            os::exec::Exec(paoc_path_.c_str(), "--paoc-panda-files", panda_fname.GetFileName(),
328                           "--compiler-regex", "B::f1",
329                           "--paoc-mode=jit", "--events-output=csv",
330                           "--events-file", paoc_output_name.GetFileName());
331        ASSERT_TRUE(res) << "paoc failed with error: " << res.Error().ToString();
332        ASSERT_EQ(res.Value(), 0);
333
334        std::ifstream infile(paoc_output_name.GetFileName());
335        std::regex rgx("Compilation,B::f1.*,COMPILED");
336        for (std::string line; std::getline(infile, line);) {
337            if (line.rfind("Compilation", 0) == 0) {
338                ASSERT_TRUE(std::regex_match(line, rgx));
339            }
340        }
341    }
342}
343
344TEST_F(AotTest, PaocMultipleFiles)
345{
346    if (RUNTIME_ARCH != Arch::X86_64) {
347        GTEST_SKIP();
348    }
349
350    TmpFile aot_fname("./test.an");
351    TmpFile panda_fname1("test1.pf");
352    TmpFile panda_fname2("test2.pf");
353
354    {
355        auto source = R"(
356            .function f64 main() {
357                fldai.64 3.1415926
358                return.64
359            }
360        )";
361
362        pandasm::Parser parser;
363        auto res = parser.Parse(source);
364        ASSERT_TRUE(res);
365        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname1.GetFileName(), res.Value()));
366    }
367
368    {
369        auto source = R"(
370            .record MyMath {
371            }
372
373            .function f64 MyMath.getPi() <static> {
374                fldai.64 3.1415926
375                return.64
376            }
377        )";
378
379        pandasm::Parser parser;
380        auto res = parser.Parse(source);
381        ASSERT_TRUE(res);
382        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname2.GetFileName(), res.Value()));
383    }
384
385    {
386        std::stringstream panda_files;
387        panda_files << panda_fname1.GetFileName() << ',' << panda_fname2.GetFileName();
388        auto res = os::exec::Exec(paoc_path_.c_str(), "--paoc-panda-files", panda_files.str().c_str(), "--paoc-output",
389                                  aot_fname.GetFileName(), "--gc-type=epsilon", "--paoc-use-cha=false");
390        ASSERT_TRUE(res) << "paoc failed with error: " << res.Error().ToString();
391        ASSERT_EQ(res.Value(), 0);
392    }
393
394    {
395        AotManager aot_manager;
396        auto res =
397            aot_manager.AddFile(aot_fname.GetFileName(), nullptr, static_cast<uint32_t>(mem::GCType::EPSILON_GC));
398        ASSERT_TRUE(res) << res.Error();
399
400        auto aot_file = aot_manager.GetFile(aot_fname.GetFileName());
401        ASSERT_TRUE(aot_file);
402        ASSERT_EQ(aot_file->GetFilesCount(), 2U);
403    }
404    RunAotdump(aot_fname.GetFileName());
405}
406
407TEST_F(AotTest, PaocGcType)
408{
409    if (RUNTIME_ARCH != Arch::X86_64) {
410        GTEST_SKIP();
411    }
412
413    TmpFile aot_fname("./test.pn");
414    TmpFile panda_fname("test.pf");
415
416    {
417        auto source = R"(
418            .function f64 main() {
419                fldai.64 3.1415926
420                return.64
421            }
422        )";
423
424        pandasm::Parser parser;
425        auto res = parser.Parse(source);
426        ASSERT_TRUE(res);
427        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname.GetFileName(), res.Value()));
428    }
429
430    {
431        auto res = os::exec::Exec(paoc_path_.c_str(), "--paoc-panda-files", panda_fname.GetFileName(), "--paoc-output",
432                                  aot_fname.GetFileName(), "--gc-type=epsilon", "--paoc-use-cha=false");
433        ASSERT_TRUE(res) << "paoc failed with error: " << res.Error().ToString();
434        ASSERT_EQ(res.Value(), 0);
435    }
436
437    {
438        // Wrong gc-type
439        AotManager aot_manager;
440        auto res = aot_manager.AddFile(aot_fname.GetFileName(), nullptr, static_cast<uint32_t>(mem::GCType::STW_GC));
441        ASSERT_FALSE(res) << res.Error();
442        std::string expected_string = "Wrong AotHeader gc-type: epsilon vs stw";
443        ASSERT_NE(res.Error().find(expected_string), std::string::npos);
444    }
445
446    {
447        AotManager aot_manager;
448        auto res =
449            aot_manager.AddFile(aot_fname.GetFileName(), nullptr, static_cast<uint32_t>(mem::GCType::EPSILON_GC));
450        ASSERT_TRUE(res) << res.Error();
451
452        auto aot_file = aot_manager.GetFile(aot_fname.GetFileName());
453        ASSERT_TRUE(aot_file);
454        ASSERT_EQ(aot_file->GetFilesCount(), 1U);
455    }
456    RunAotdump(aot_fname.GetFileName());
457}
458
459TEST_F(AotTest, FileManagerLoadAbc)
460{
461    if (RUNTIME_ARCH != Arch::X86_64) {
462        GTEST_SKIP();
463    }
464
465    TmpFile aot_fname("./test.an");
466    TmpFile panda_fname("./test.pf");
467
468    {
469        auto source = R"(
470            .function f64 main() {
471                fldai.64 3.1415926
472                return.64
473            }
474        )";
475
476        pandasm::Parser parser;
477        auto res = parser.Parse(source);
478        ASSERT_TRUE(res);
479        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname.GetFileName(), res.Value()));
480    }
481
482    {
483        auto runtime = Runtime::GetCurrent();
484        auto gc_type_id = static_cast<uint32_t>(
485            Runtime::GetGCType(runtime->GetOptions(), plugins::RuntimeTypeToLang(runtime->GetRuntimeType())));
486        auto gc_type_name = "--gc-type=epsilon";
487        if (gc_type_id == 2) {
488            gc_type_name = "--gc-type=stw";
489        } else if (gc_type_id == 4) {
490            gc_type_name = "--gc-type=gen-gc";
491        } else {
492            ASSERT_EQ(gc_type_id, 1) << "Invalid GC type\n";
493        }
494        auto res = os::exec::Exec(paoc_path_.c_str(), "--paoc-panda-files", panda_fname.GetFileName(), "--paoc-output",
495                                  aot_fname.GetFileName(), gc_type_name, "--paoc-use-cha=false");
496        ASSERT_TRUE(res) << "paoc failed with error: " << res.Error().ToString();
497        ASSERT_EQ(res.Value(), 0);
498    }
499
500    {
501        auto res = FileManager::LoadAbcFile(panda_fname.GetFileName(), panda_file::File::READ_ONLY);
502        ASSERT_TRUE(res);
503        auto aot_manager = Runtime::GetCurrent()->GetClassLinker()->GetAotManager();
504        auto aot_file = aot_manager->GetFile(aot_fname.GetFileName());
505        ASSERT_TRUE(aot_file);
506        ASSERT_EQ(aot_file->GetFilesCount(), 1);
507    }
508    RunAotdump(aot_fname.GetFileName());
509}
510
511TEST_F(AotTest, FileManagerLoadAn)
512{
513    if (RUNTIME_ARCH == Arch::AARCH32) {
514        // TODO(msherstennikov): for some reason dlopen cannot open aot file in qemu-arm
515        return;
516    }
517    uint32_t tid = os::thread::GetCurrentThreadId();
518    std::string tmpfile = helpers::string::Format("test.an", tid);
519    static constexpr const char *tmpfile_pf = "test.pf";
520    static constexpr const char *cmdline = "cmdline";
521    static constexpr uint32_t method1_id = 42;
522    static constexpr uint32_t method2_id = 43;
523    const std::string class_name("Foo");
524    std::string method_name(class_name + "::method");
525    std::array<uint8_t, 4> x86_add = {
526        0x8d, 0x04, 0x37,  // lea    eax,[rdi+rdi*1]
527        0xc3               // ret
528    };
529
530    AotBuilder aot_builder;
531    aot_builder.SetArch(RUNTIME_ARCH);
532    RuntimeInterfaceMock iruntime;
533    aot_builder.SetRuntime(&iruntime);
534    auto runtime = Runtime::GetCurrent();
535    auto gc_type = Runtime::GetGCType(runtime->GetOptions(), plugins::RuntimeTypeToLang(runtime->GetRuntimeType()));
536    aot_builder.SetGcType(static_cast<uint32_t>(gc_type));
537
538    aot_builder.StartFile(tmpfile_pf, 0x12345678);
539
540    auto thread = MTManagedThread::GetCurrent();
541    if (thread != nullptr) {
542        thread->ManagedCodeBegin();
543    }
544    auto etx = runtime->GetClassLinker()->GetExtension(runtime->GetLanguageContext(runtime->GetRuntimeType()));
545    auto klass = etx->CreateClass(reinterpret_cast<const uint8_t *>(class_name.data()), 0, 0,
546                                  AlignUp(sizeof(Class), OBJECT_POINTER_SIZE));
547    if (thread != nullptr) {
548        thread->ManagedCodeEnd();
549    }
550
551    klass->SetFileId(panda_file::File::EntityId(13));
552    aot_builder.StartClass(*klass);
553
554    Method method1(klass, nullptr, File::EntityId(method1_id), File::EntityId(), 0, 1, nullptr);
555    {
556        CodeInfoBuilder code_builder(RUNTIME_ARCH, GetAllocator());
557        ArenaVector<uint8_t> data(GetAllocator()->Adapter());
558        code_builder.Encode(&data);
559        CompiledMethod compiled_method1(RUNTIME_ARCH, &method1);
560        compiled_method1.SetCode(Span(reinterpret_cast<const uint8_t *>(method_name.data()), method_name.size() + 1));
561        compiled_method1.SetCodeInfo(Span(data).ToConst());
562        aot_builder.AddMethod(compiled_method1, 0);
563    }
564
565    Method method2(klass, nullptr, File::EntityId(method2_id), File::EntityId(), 0, 1, nullptr);
566    {
567        CodeInfoBuilder code_builder(RUNTIME_ARCH, GetAllocator());
568        ArenaVector<uint8_t> data(GetAllocator()->Adapter());
569        code_builder.Encode(&data);
570        CompiledMethod compiled_method2(RUNTIME_ARCH, &method2);
571        compiled_method2.SetCode(Span(reinterpret_cast<const uint8_t *>(x86_add.data()), x86_add.size()));
572        compiled_method2.SetCodeInfo(Span(data).ToConst());
573        aot_builder.AddMethod(compiled_method2, 1);
574    }
575
576    aot_builder.EndClass();
577    uint32_t hash = GetHash32String(reinterpret_cast<const uint8_t *>(class_name.data()));
578    aot_builder.InsertEntityPairHeader(hash, 13);
579    aot_builder.InsertClassHashTableSize(1);
580    aot_builder.EndFile();
581
582    aot_builder.Write(cmdline, tmpfile.c_str());
583    {
584        auto res = FileManager::LoadAnFile(tmpfile.c_str());
585        ASSERT_TRUE(res) << "Fail to load an file";
586    }
587
588    auto aot_manager = Runtime::GetCurrent()->GetClassLinker()->GetAotManager();
589    auto aot_file = aot_manager->GetFile(tmpfile.c_str());
590    ASSERT_TRUE(aot_file);
591    ASSERT_TRUE(strcmp(cmdline, aot_file->GetCommandLine()) == 0U);
592    ASSERT_TRUE(strcmp(tmpfile.c_str(), aot_file->GetFileName()) == 0U);
593    ASSERT_EQ(aot_file->GetFilesCount(), 1U);
594
595    auto pfile = aot_manager->FindPandaFile(tmpfile_pf);
596    ASSERT_NE(pfile, nullptr);
597    auto cls = pfile->GetClass(13);
598    ASSERT_TRUE(cls.IsValid());
599
600    {
601        auto code = cls.FindMethodCodeEntry(0);
602        ASSERT_FALSE(code == nullptr);
603        ASSERT_EQ(method_name, reinterpret_cast<const char *>(code));
604    }
605
606    {
607        auto code = cls.FindMethodCodeEntry(1);
608        ASSERT_FALSE(code == nullptr);
609        ASSERT_EQ(std::memcmp(x86_add.data(), code, x86_add.size()), 0);
610#ifdef PANDA_TARGET_AMD64
611        auto func_add = (int (*)(int, int))code;
612        ASSERT_EQ(func_add(2, 3), 5);
613#endif
614    }
615}
616
617TEST_F(AotTest, PaocClusters)
618{
619    // Test basic functionality only in host mode.
620    if (RUNTIME_ARCH != Arch::X86_64) {
621        return;
622    }
623
624    TmpFile paoc_clusters("clusters.json");
625    std::ofstream(paoc_clusters.GetFileName()) <<
626        R"(
627    {
628        "clusters_map" :
629        {
630            "A::count" : ["unroll_enable"],
631            "B::count2" : ["unroll_disable"],
632            "_GLOBAL::main" : ["inline_disable", 1]
633        },
634
635        "compiler_options" :
636        {
637            "unroll_disable" :
638            {
639                "compiler-loop-unroll" : "false"
640            },
641
642            "unroll_enable" :
643            {
644                "compiler-loop-unroll" : "true",
645                "compiler-loop-unroll-factor" : 42,
646                "compiler-loop-unroll-inst-limit" : 850
647            },
648
649            "inline_disable" :
650            {
651                "compiler-inlining" : "false"
652            }
653        }
654    }
655    )";
656
657    TmpFile panda_fname("test.pf");
658    auto source = R"(
659        .record A {}
660        .record B {}
661
662        .function i32 A.count() <static> {
663            movi v1, 5
664            ldai 0
665        main_loop:
666            jeq v1, main_ret
667            addi 1
668            jmp main_loop
669        main_ret:
670            return
671        }
672
673        .function i32 B.count() <static> {
674            movi v1, 5
675            ldai 0
676        main_loop:
677            jeq v1, main_ret
678            addi 1
679            jmp main_loop
680        main_ret:
681            return
682        }
683
684        .function i32 B.count2() <static> {
685            movi v1, 5
686            ldai 0
687        main_loop:
688            jeq v1, main_ret
689            addi 1
690            jmp main_loop
691        main_ret:
692            return
693        }
694
695        .function i32 main() {
696            call.short A.count
697            sta v0
698            call.short B.count
699            add2 v0
700            call.short B.count2
701            add2 v0
702            return
703        }
704    )";
705
706    {
707        pandasm::Parser parser;
708        auto res = parser.Parse(source);
709        ASSERT_TRUE(res);
710        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname.GetFileName(), res.Value()));
711    }
712
713    {
714        TmpFile compiler_events("events.csv");
715        auto res =
716            os::exec::Exec(paoc_path_.c_str(), "--paoc-panda-files", panda_fname.GetFileName(), "--paoc-clusters",
717                           paoc_clusters.GetFileName(), "--compiler-loop-unroll-factor=7",
718                           "--compiler-enable-events=true", "--compiler-events-path", compiler_events.GetFileName());
719        ASSERT_TRUE(res) << "paoc failed with error: " << res.Error().ToString();
720        ASSERT_EQ(res.Value(), 0);
721
722        bool first_found = false;
723        bool second_found = false;
724        std::ifstream events_file(compiler_events.GetFileName());
725
726        std::regex rgx_unroll_applied_cluster("A::count,loop-unroll,.*,unroll_factor:42,.*");
727        std::regex rgx_unroll_restored_default("B::count,loop-unroll,.*,unroll_factor:7,.*");
728
729        for (std::string line; std::getline(events_file, line);) {
730            if (line.rfind("loop-unroll") != std::string::npos) {
731                if (!first_found) {
732                    // Check that the cluster is applied:
733                    ASSERT_TRUE(std::regex_match(line, rgx_unroll_applied_cluster));
734                    first_found = true;
735                    continue;
736                }
737                ASSERT_FALSE(second_found);
738                // Check that the option is restored:
739                ASSERT_TRUE(std::regex_match(line, rgx_unroll_restored_default));
740                second_found = true;
741            }
742        }
743        ASSERT_TRUE(first_found && second_found);
744    }
745}
746
747TEST_F(AotTest, PandaFiles)
748{
749#ifndef PANDA_EVENTS_ENABLED
750    GTEST_SKIP();
751#endif
752
753    if (RUNTIME_ARCH != Arch::X86_64) {
754        GTEST_SKIP();
755    }
756
757    TmpFile aot_fname("./test.an");
758    TmpFile panda_fname1("test1.pf");
759    TmpFile panda_fname2("test2.pf");
760    TmpFile paoc_output_name("events-out.csv");
761
762    {
763        auto source = R"(
764            .record Z {}
765            .function i32 Z.zoo() <static> {
766                ldai 45
767                return
768            }
769        )";
770
771        pandasm::Parser parser;
772        auto res = parser.Parse(source);
773        ASSERT_TRUE(res);
774        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname1.GetFileName(), res.Value()));
775    }
776
777    {
778        auto source = R"(
779            .record Z <external>
780            .function i32 Z.zoo() <external, static>
781            .record X {}
782            .function i32 X.main() {
783                call.short Z.zoo
784                return
785            }
786        )";
787
788        pandasm::Parser parser;
789        auto res = parser.Parse(source);
790        ASSERT_TRUE(res);
791        ASSERT_TRUE(pandasm::AsmEmitter::Emit(panda_fname2.GetFileName(), res.Value()));
792    }
793
794    {
795        std::stringstream panda_files;
796        panda_files << panda_fname1.GetFileName() << ',' << panda_fname2.GetFileName();
797        auto res = os::exec::Exec(paoc_path_.c_str(), "--paoc-panda-files", panda_fname2.GetFileName(), "--panda-files",
798                                  panda_fname1.GetFileName(), "--events-output=csv", "--events-file",
799                                  paoc_output_name.GetFileName());
800        ASSERT_TRUE(res) << "paoc failed with error: " << res.Error().ToString();
801        ASSERT_EQ(res.Value(), 0);
802
803        std::ifstream infile(paoc_output_name.GetFileName());
804        // Inlining attempt proofs that Z::zoo was available to inline
805        std::regex rgx("Inline,.*Z::zoo.*");
806        bool inline_attempt = false;
807        for (std::string line; std::getline(infile, line);) {
808            inline_attempt |= std::regex_match(line, rgx);
809        }
810        ASSERT_TRUE(inline_attempt);
811    }
812}
813
814}  // namespace panda::compiler
815