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#include "mem/code_allocator.h"
18#include "mem/pool_manager.h"
19#include "target/aarch64/target.h"
20
21#include "vixl_exec_module.h"
22
23namespace panda::compiler::tests {
24
25class EncoderArm64VixlTest : public ::testing::Test {
26public:
27    EncoderArm64VixlTest()
28    {
29        panda::mem::MemConfig::Initialize(64_MB, 64_MB, 64_MB, 32_MB);
30        PoolManager::Initialize();
31        allocator_ = new ArenaAllocator(SpaceType::SPACE_TYPE_COMPILER);
32        encoder_ = static_cast<aarch64::Aarch64Encoder *>(Encoder::Create(allocator_, Arch::AARCH64, false));
33        encoder_->InitMasm();
34        encoder_->SetRegfile(RegistersDescription::Create(allocator_, Arch::AARCH64));
35
36        exec_module_ = allocator_->New<VixlExecModule>(allocator_, nullptr);
37    }
38
39    ~EncoderArm64VixlTest()
40    {
41        exec_module_->~VixlExecModule();
42        Logger::Destroy();
43        encoder_->~Aarch64Encoder();
44        delete allocator_;
45        PoolManager::Finalize();
46        panda::mem::MemConfig::Finalize();
47    }
48
49    auto GetEncoder()
50    {
51        return encoder_;
52    }
53
54    auto GetAllocator()
55    {
56        return allocator_;
57    }
58
59    void Dump(bool enabled)
60    {
61        if (enabled) {
62            auto size = GetEncoder()->GetCursorOffset();
63            for (uint32_t i = 0; i < size;) {
64                i = encoder_->DisasmInstr(std::cout, i, 0);
65                std::cout << std::endl;
66            }
67        }
68    }
69
70    template <typename T>
71    void TestPcRelativeLoad(size_t data_size, ssize_t offset, bool get_address,
72                            std::initializer_list<const char *> insts)
73    {
74        auto buffer = GetEncoder()->GetMasm()->GetBuffer();
75        T *data = reinterpret_cast<T *>(buffer->template GetOffsetAddress<uint8_t *>(data_size) + offset);
76        T good_value = GetGoodValue<T>();
77        T bad_value = GetBadValue<T>();
78
79        {
80            T tmp_value;
81            memcpy_s(&tmp_value, sizeof(T), data, sizeof(T));
82            ASSERT_EQ(tmp_value, bad_value);
83        }
84
85        // We use memcpy here (instead of just assigning), because the data can be aligned by 4 for 64-bit target, and
86        // asan will complain about this.
87        memcpy_s(data, sizeof(T), &good_value, sizeof(T));
88
89        GetEncoder()->SetCodeOffset(0);
90
91        buffer->Rewind(data_size);
92        Reg reg(0, TypeInfo(T(0)));
93        if (get_address) {
94            Reg addr(1, INT64_TYPE);
95            GetEncoder()->LoadPcRelative(reg, offset, addr);
96        } else {
97            GetEncoder()->LoadPcRelative(reg, offset);
98        }
99        GetEncoder()->EncodeReturn();
100
101        auto start_addr = buffer->GetOffsetAddress<const char *>(data_size);
102        exec_module_->SetInstructions(start_addr, buffer->GetEndAddress<const char *>());
103        exec_module_->Execute();
104
105        {
106            T tmp_value;
107            memcpy_s(&tmp_value, sizeof(T), data, sizeof(T));
108            ASSERT_EQ(tmp_value, good_value);
109        }
110
111        {
112            auto inst = insts.begin();
113            for (uint32_t i = data_size; i < buffer->GetCursorOffset() && inst != insts.end(); ++inst) {
114                std::stringstream ss;
115                i = encoder_->DisasmInstr(ss, i, 0);
116                auto pos = ss.str().find(*inst);
117                EXPECT_NE(pos, std::string::npos) << *inst << " not found";
118                if (pos == std::string::npos) {
119                    return;
120                }
121            }
122        }
123
124        memcpy_s(data, sizeof(T), &bad_value, sizeof(T));
125
126        if (get_address) {
127            EXPECT_EQ(exec_module_->GetSimulator()->ReadXRegister(1), reinterpret_cast<int64_t>(data));
128        }
129        EXPECT_EQ(exec_module_->GetRetValue(), GetGoodValue<T>());
130    }
131
132    void TestOffset(size_t data_size, ssize_t offset)
133    {
134        ASSERT((offset & 3) == 0);
135        if (vixl::IsInt21(offset)) {
136            TestPcRelativeLoad<uint64_t>(data_size, offset, false, {"adr", "ldr"});
137            TestPcRelativeLoad<uint64_t>(data_size, offset, true, {"adr", "ldr"});
138            TestPcRelativeLoad<uint64_t>(data_size, -offset, false, {"adr", "ldr"});
139            TestPcRelativeLoad<uint64_t>(data_size, -offset, true, {"adr", "ldr"});
140            TestPcRelativeLoad<uint32_t>(data_size, offset, false, {"adr", "ldr"});
141            TestPcRelativeLoad<uint32_t>(data_size, offset, true, {"adr", "ldr"});
142            TestPcRelativeLoad<uint32_t>(data_size, -offset, false, {"adr", "ldr"});
143            TestPcRelativeLoad<uint32_t>(data_size, -offset, true, {"adr", "ldr"});
144        } else {
145            if ((offset & 7) == 0) {
146                TestPcRelativeLoad<uint64_t>(data_size, offset, false, {"adrp", "ldr"});
147                TestPcRelativeLoad<uint64_t>(data_size, offset, true, {"adrp", "add", "ldr"});
148                TestPcRelativeLoad<uint64_t>(data_size, -offset, false, {"adrp", "ldr"});
149                TestPcRelativeLoad<uint64_t>(data_size, -offset, true, {"adrp", "add", "ldr"});
150            } else {
151                TestPcRelativeLoad<uint64_t>(data_size, offset, false, {"adrp", "mov", "ldr"});
152                TestPcRelativeLoad<uint64_t>(data_size, offset, true, {"adrp", "add", "ldr"});
153                TestPcRelativeLoad<uint64_t>(data_size, -offset, false, {"adrp", "mov", "ldr"});
154                TestPcRelativeLoad<uint64_t>(data_size, -offset, true, {"adrp", "add", "ldr"});
155            }
156            TestPcRelativeLoad<uint32_t>(data_size, offset, false, {"adrp", "ldr"});
157            TestPcRelativeLoad<uint32_t>(data_size, offset, true, {"adrp", "add", "ldr"});
158            TestPcRelativeLoad<uint32_t>(data_size, -offset, false, {"adrp", "ldr"});
159            TestPcRelativeLoad<uint32_t>(data_size, -offset, true, {"adrp", "add", "ldr"});
160        }
161    }
162
163    template <typename T>
164    static constexpr T GetBadValue()
165    {
166        if constexpr (sizeof(T) == sizeof(uint64_t)) {
167            return BAD_VALUE64;
168        }
169        return BAD_VALUE;
170    }
171
172    template <typename T>
173    static constexpr T GetGoodValue()
174    {
175        if constexpr (sizeof(T) == sizeof(uint64_t)) {
176            return GOOD_VALUE64;
177        }
178        return GOOD_VALUE;
179    }
180
181protected:
182    static constexpr uint32_t BAD_VALUE = 0xdeadbaad;
183    static constexpr uint32_t GOOD_VALUE = 0xcafebabe;
184    static constexpr uint64_t BAD_VALUE64 = 0xdeadbaaddeadbaad;
185    static constexpr uint64_t GOOD_VALUE64 = 0x1234cafebabe4321;
186    static constexpr uint64_t MAX_INT21_VALUE = 0x100000;
187
188private:
189    ArenaAllocator *allocator_ {nullptr};
190    aarch64::Aarch64Encoder *encoder_ {nullptr};
191    VixlExecModule *exec_module_ {nullptr};
192};
193
194TEST_F(EncoderArm64VixlTest, LoadPcRelative)
195{
196    auto encoder = GetEncoder();
197    auto masm = encoder->GetMasm();
198    auto buffer = masm->GetBuffer();
199    static constexpr size_t code_offset = 16;
200    static constexpr size_t buf_size = (MAX_INT21_VALUE - vixl::aarch64::kPageSize) * 2 + code_offset;
201
202    for (size_t i = 0; i < buf_size / sizeof(uint32_t); i++) {
203        buffer->Emit32(GetBadValue<uint32_t>());
204    }
205    // Pre-allocate space for the code
206    for (size_t i = 0; i < 4; i++) {
207        buffer->Emit32(0);
208    }
209    for (size_t i = 0; i < buf_size / sizeof(uint32_t); i++) {
210        buffer->Emit32(GetBadValue<uint32_t>());
211    }
212
213    TestOffset(buf_size, 40);
214    TestOffset(buf_size, 44);
215    TestOffset(buf_size, 0x1374b8);
216    // Check for two pages addrp
217    TestOffset(buf_size, MAX_INT21_VALUE + vixl::aarch64::kPageSize + code_offset + 8);
218    // Check for one page addrp
219    TestOffset(buf_size, MAX_INT21_VALUE + vixl::aarch64::kPageSize + code_offset - 8);
220    TestOffset(buf_size, 0x100404);
221}
222
223}  // namespace panda::compiler::tests
224