• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2023 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 <functional>
17 #include <string>
18 #include <unordered_map>
19 
20 #include "libpandabase/utils/utils.h"
21 #include "optimizer/optimizations/regalloc/reg_alloc.h"
22 #include "optimizer/optimizations/regalloc/reg_alloc_linear_scan.h"
23 #include "optimizer/code_generator/codegen.h"
24 #include "optimizer/code_generator/codegen_native.h"
25 #include "optimizer/code_generator/method_properties.h"
26 #include "aarch64/decoder-aarch64.h"
27 #include "aarch64/disasm-aarch64.h"
28 #include "aarch64/operands-aarch64.h"
29 #include "tests/unit_test.h"
30 #include <regex>
31 
32 namespace panda::compiler {
33 
34 class VixlDisasmTest : public GraphTest {
35 public:
VixlDisasmTest()36     VixlDisasmTest() : decoder_(GetAllocator()), disasm_(GetAllocator()) {}
37 
GetDecoder()38     auto &GetDecoder()
39     {
40         return decoder_;
41     }
42 
GetDisasm()43     auto &GetDisasm()
44     {
45         return disasm_;
46     }
47 
48 private:
49     vixl::aarch64::Decoder decoder_;
50     vixl::aarch64::Disassembler disasm_;
51 };
52 
53 class CodegenCallerSavedRegistersTest : public VixlDisasmTest {};
54 
55 class DecoderVisitor : public vixl::aarch64::DecoderVisitor {
56 public:
57 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
58 #define DECLARE(A) \
59     virtual void Visit##A([[maybe_unused]] const vixl::aarch64::Instruction *instr) {}
VISITOR_LIST(DECLARE)60     VISITOR_LIST(DECLARE)
61 #undef DECLARE
62 
63     void Visit(vixl::aarch64::Metadata *metadata, const vixl::aarch64::Instruction *instr) final
64     {
65         auto visitorIt {FORM_TO_VISITOR.find((*metadata)["form"])};
66         ASSERT(visitorIt != std::end(FORM_TO_VISITOR));
67 
68         const auto &visitor {visitorIt->second};
69         ASSERT(visitor != nullptr);
70         visitor(this, instr);
71     }
72 
73 private:
74     using FormToVisitorFnMap = FormToVisitorFnMapT<DecoderVisitor>;
75 
76     static const FormToVisitorFnMap FORM_TO_VISITOR;
77 };
78 
79 class LoadStoreRegistersCollector : public DecoderVisitor {
80 public:
81     // use the same body for all VisitXXX methods to simplify visitor's implementation
82 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
83 #define DECLARE(A)                                                                                             \
84     void Visit##A(const vixl::aarch64::Instruction *instr) override                                            \
85     {                                                                                                          \
86         if (std::string(#A) == "LoadStorePairOffset") {                                                        \
87             if (instr->Mask(vixl::aarch64::LoadStorePairOp::LDP_x) == vixl::aarch64::LoadStorePairOp::LDP_x || \
88                 instr->Mask(vixl::aarch64::LoadStorePairOp::STP_x) == vixl::aarch64::LoadStorePairOp::STP_x) { \
89                 regs_.set(instr->GetRt());                                                                     \
90                 regs_.set(instr->GetRt2());                                                                    \
91                 return;                                                                                        \
92             }                                                                                                  \
93             if (instr->Mask(vixl::aarch64::LoadStorePairOp::LDP_d) == vixl::aarch64::LoadStorePairOp::LDP_d || \
94                 instr->Mask(vixl::aarch64::LoadStorePairOp::STP_d) == vixl::aarch64::LoadStorePairOp::STP_d) { \
95                 vregs_.set(instr->GetRt());                                                                    \
96                 vregs_.set(instr->GetRt2());                                                                   \
97                 return;                                                                                        \
98             }                                                                                                  \
99         }                                                                                                      \
100         if (std::string(#A) == "LoadStoreUnscaledOffset" || std::string(#A) == "LoadStoreUnsignedOffset" ||    \
101             std::string(#A) == "LoadStoreRegisterOffset") {                                                    \
102             if (instr->Mask(vixl::aarch64::LoadStoreOp::LDR_x) == vixl::aarch64::LoadStoreOp::LDR_x ||         \
103                 instr->Mask(vixl::aarch64::LoadStoreOp::STR_x) == vixl::aarch64::LoadStoreOp::STR_x) {         \
104                 regs_.set(instr->GetRt());                                                                     \
105                 return;                                                                                        \
106             }                                                                                                  \
107             if (instr->Mask(vixl::aarch64::LoadStoreOp::LDR_d) == vixl::aarch64::LoadStoreOp::LDR_d ||         \
108                 instr->Mask(vixl::aarch64::LoadStoreOp::STR_d) == vixl::aarch64::LoadStoreOp::STR_d) {         \
109                 vregs_.set(instr->GetRt());                                                                    \
110                 return;                                                                                        \
111             }                                                                                                  \
112         }                                                                                                      \
113     }
114 
VISITOR_LIST(DECLARE)115     VISITOR_LIST(DECLARE)
116 #undef DECLARE
117     RegMask GetAccessedRegisters()
118     {
119         return regs_;
120     }
121 
GetAccessedVRegisters()122     VRegMask GetAccessedVRegisters()
123     {
124         return vregs_;
125     }
126 
127 private:
128     RegMask regs_;
129     VRegMask vregs_;
130 };
131 
132 class LoadStoreInstCollector : public DecoderVisitor {
133 public:
134 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
135 #define DECLARE(A)                                                                                                    \
136     void Visit##A(const vixl::aarch64::Instruction *instr) override                                                   \
137     {                                                                                                                 \
138         if (std::string(#A) == "LoadStorePairOffset") {                                                               \
139             if (instr->Mask(vixl::aarch64::LoadStorePairOp::LDP_x) == vixl::aarch64::LoadStorePairOp::LDP_x) {        \
140                 ldp_++;                                                                                               \
141                 regs_.set(instr->GetRt());                                                                            \
142                 regs_.set(instr->GetRt2());                                                                           \
143             } else if (instr->Mask(vixl::aarch64::LoadStorePairOp::STP_x) == vixl::aarch64::LoadStorePairOp::STP_x) { \
144                 stp_++;                                                                                               \
145                 regs_.set(instr->GetRt());                                                                            \
146                 regs_.set(instr->GetRt2());                                                                           \
147             } else if (instr->Mask(vixl::aarch64::LoadStorePairOp::LDP_d) == vixl::aarch64::LoadStorePairOp::LDP_d) { \
148                 ldpV_++;                                                                                              \
149                 vregs_.set(instr->GetRt());                                                                           \
150                 vregs_.set(instr->GetRt2());                                                                          \
151             } else if (instr->Mask(vixl::aarch64::LoadStorePairOp::STP_d) == vixl::aarch64::LoadStorePairOp::STP_d) { \
152                 stpV_++;                                                                                              \
153                 vregs_.set(instr->GetRt());                                                                           \
154                 vregs_.set(instr->GetRt2());                                                                          \
155             }                                                                                                         \
156         }                                                                                                             \
157     }
158 
VISITOR_LIST(DECLARE)159     VISITOR_LIST(DECLARE)
160 #undef DECLARE
161     auto GetLdpX()
162     {
163         return ldp_;
164     }
165 
GetStpX()166     auto GetStpX()
167     {
168         return stp_;
169     }
170 
GetLdpD()171     auto GetLdpD()
172     {
173         return ldpV_;
174     }
175 
GetStpD()176     auto GetStpD()
177     {
178         return stpV_;
179     }
180 
GetAccessedPairRegisters()181     RegMask GetAccessedPairRegisters()
182     {
183         return regs_;
184     }
185 
GetAccessedPairVRegisters()186     VRegMask GetAccessedPairVRegisters()
187     {
188         return vregs_;
189     }
190 
191 private:
192     size_t ldp_ {0};
193     size_t stp_ {0};
194     size_t ldpV_ {0};
195     size_t stpV_ {0};
196     RegMask regs_;
197     VRegMask vregs_;
198 };
199 
200 // NOLINTBEGIN(readability-magic-numbers,modernize-avoid-c-arrays)
TEST_F(CodegenCallerSavedRegistersTest,SaveOnlyLiveRegisters)201 TEST_F(CodegenCallerSavedRegistersTest, SaveOnlyLiveRegisters)
202 {
203     g_options.SetCompilerSaveOnlyLiveRegisters(true);
204     constexpr auto ARGS_COUNT = 8;
205     GRAPH(GetGraph())
206     {
207         for (int i = 0; i < ARGS_COUNT; i++) {
208             PARAMETER(i, i).u64();
209         }
210         for (int i = 0; i < ARGS_COUNT; i++) {
211             PARAMETER(i + ARGS_COUNT, i + ARGS_COUNT).f64();
212         }
213 
214         BASIC_BLOCK(2U, -1)
215         {
216             INST(16U, Opcode::Add).u64().Inputs(0, 1U);
217             INST(17U, Opcode::Add).u64().Inputs(16U, 2U);
218             INST(18U, Opcode::Add).u64().Inputs(17U, 3U);
219             INST(19U, Opcode::Add).u64().Inputs(18U, 4U);
220             INST(20U, Opcode::Add).u64().Inputs(19U, 5U);
221             INST(21U, Opcode::Add).u64().Inputs(20U, 6U);
222             INST(22U, Opcode::Add).u64().Inputs(21U, 7U);
223             INST(23U, Opcode::Add).f64().Inputs(8U, 9U);
224             INST(24U, Opcode::Add).f64().Inputs(23U, 10U);
225             INST(25U, Opcode::Add).f64().Inputs(24U, 11U);
226             INST(26U, Opcode::Add).f64().Inputs(25U, 12U);
227             INST(27U, Opcode::Add).f64().Inputs(26U, 13U);
228             INST(28U, Opcode::Add).f64().Inputs(27U, 14U);
229             INST(29U, Opcode::Add).f64().Inputs(28U, 15U);
230             INST(30U, Opcode::Cast).u64().SrcType(DataType::FLOAT64).Inputs(29U);
231             INST(31U, Opcode::Add).u64().Inputs(30U, 22U);
232 
233             INST(44U, Opcode::LoadAndInitClass).ref().Inputs().TypeId(68U);
234             INST(32U, Opcode::NewArray).ref().TypeId(8U).Inputs(44U, 31U);
235             INST(33U, Opcode::Return).ref().Inputs(32U);
236         }
237     }
238 
239     SetNumArgs(ARGS_COUNT * 2U);
240     SetNumVirtRegs(0);
241     GraphChecker(GetGraph()).Check();
242     RegAlloc(GetGraph());
243     ASSERT_TRUE(GetGraph()->RunPass<Codegen>());
244 
245     auto codeEntry = reinterpret_cast<vixl::aarch64::Instruction *>(GetGraph()->GetCode().Data());
246     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
247     auto codeExit = codeEntry + GetGraph()->GetCode().Size();
248     ASSERT(codeEntry != nullptr && codeExit != nullptr);
249     auto &decoder {GetDecoder()};
250     LoadStoreRegistersCollector visitor;
251     vixl::aarch64::Decoder::ScopedAddVisitors sv(decoder, {&visitor});
252     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
253     for (auto instr = codeEntry; instr < codeExit; instr += vixl::aarch64::kInstructionSize) {
254         decoder.Decode(instr);
255     }
256     // not using reg lists from vixl::aarch64 to check only r0-r7
257     constexpr auto CALLER_REGS = RegMask((1 << ARGS_COUNT) - 1);  // NOLINT(hicpp-signed-bitwise)
258     EXPECT_EQ(visitor.GetAccessedRegisters() & CALLER_REGS, RegMask {0});
259     EXPECT_TRUE((visitor.GetAccessedVRegisters() & CALLER_REGS).none());
260 }
261 
262 class CodegenSpillFillCoalescingTest : public VixlDisasmTest {
263 public:
CodegenSpillFillCoalescingTest()264     CodegenSpillFillCoalescingTest()
265     {
266         g_options.SetCompilerSpillFillPair(true);
267         g_options.SetCompilerVerifyRegalloc(false);
268     }
269 
CheckSpillFillCoalescingForEvenRegsNumber(bool aligned)270     void CheckSpillFillCoalescingForEvenRegsNumber(bool aligned)
271     {
272         GRAPH(GetGraph())
273         {
274             BASIC_BLOCK(2U, -1)
275             {
276                 INST(0, Opcode::SpillFill);
277                 INST(1, Opcode::ReturnVoid);
278             }
279         }
280 
281         unsigned int alignmentOffset = aligned ? 1 : 0;
282 
283         auto sfInst = INS(0).CastToSpillFill();
284         sfInst->AddSpill(0U, 0U + alignmentOffset, DataType::Type::INT64);
285         sfInst->AddSpill(1U, 1U + alignmentOffset, DataType::Type::INT64);
286         sfInst->AddSpill(0U, 2U + alignmentOffset, DataType::Type::FLOAT64);
287         sfInst->AddSpill(1U, 3U + alignmentOffset, DataType::Type::FLOAT64);
288         sfInst->AddFill(4U + alignmentOffset, 3U, DataType::Type::INT64);
289         sfInst->AddFill(5U + alignmentOffset, 2U, DataType::Type::INT64);
290         sfInst->AddFill(6U + alignmentOffset, 3U, DataType::Type::FLOAT64);
291         sfInst->AddFill(7U + alignmentOffset, 2U, DataType::Type::FLOAT64);
292 
293         SetNumArgs(0);
294         SetNumVirtRegs(0);
295         GraphChecker(GetGraph()).Check();
296         GetGraph()->SetStackSlotsCount(8U + alignmentOffset);
297         RegAlloc(GetGraph());
298         ASSERT_TRUE(GetGraph()->RunPass<Codegen>());
299 
300         auto codeEntry = reinterpret_cast<vixl::aarch64::Instruction *>(GetGraph()->GetCode().Data());
301         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
302         auto codeExit = codeEntry + GetGraph()->GetCode().Size();
303         ASSERT(codeEntry != nullptr && codeExit != nullptr);
304         auto &decoder {GetDecoder()};
305         LoadStoreInstCollector visitor;
306         vixl::aarch64::Decoder::ScopedAddVisitors sv(decoder, {&visitor});
307         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
308         for (auto instr = codeEntry; instr < codeExit; instr += vixl::aarch64::kInstructionSize) {
309             decoder.Decode(instr);
310         }
311         EXPECT_EQ(visitor.GetStpX(), 1 /* 1 use pre increment and not counted */);
312         EXPECT_EQ(visitor.GetLdpX(), 1 /* 1 use post increment and not counted */);
313         EXPECT_EQ(visitor.GetLdpD(), 1);
314         EXPECT_EQ(visitor.GetStpD(), 1);
315 
316         constexpr auto TEST_REGS = RegMask(0xF);
317         EXPECT_EQ(visitor.GetAccessedPairRegisters() & TEST_REGS, RegMask {0xF});
318         EXPECT_EQ(visitor.GetAccessedPairVRegisters() & TEST_REGS, RegMask {0xF});
319     }
320 
FormSpillFillInst(SpillFillInst * sfInst,unsigned int alignmentOffset)321     void FormSpillFillInst(SpillFillInst *sfInst, unsigned int alignmentOffset)
322     {
323         sfInst->AddSpill(0U, 0U + alignmentOffset, DataType::Type::INT64);
324         sfInst->AddSpill(1U, 1U + alignmentOffset, DataType::Type::INT64);
325         sfInst->AddSpill(2U, 2U + alignmentOffset, DataType::Type::INT64);
326         sfInst->AddSpill(0U, 3U + alignmentOffset, DataType::Type::FLOAT64);
327         sfInst->AddSpill(1U, 4U + alignmentOffset, DataType::Type::FLOAT64);
328         sfInst->AddSpill(2U, 5U + alignmentOffset, DataType::Type::FLOAT64);
329         sfInst->AddFill(6U + alignmentOffset, 3U, DataType::Type::INT64);
330         sfInst->AddFill(7U + alignmentOffset, 4U, DataType::Type::INT64);
331         sfInst->AddFill(8U + alignmentOffset, 5U, DataType::Type::INT64);
332         sfInst->AddFill(9U + alignmentOffset, 3U, DataType::Type::FLOAT64);
333         sfInst->AddFill(10U + alignmentOffset, 4U, DataType::Type::FLOAT64);
334         sfInst->AddFill(11U + alignmentOffset, 5U, DataType::Type::FLOAT64);
335     }
336 
CheckSpillFillCoalescingForOddRegsNumber(bool aligned)337     void CheckSpillFillCoalescingForOddRegsNumber(bool aligned)
338     {
339         GRAPH(GetGraph())
340         {
341             BASIC_BLOCK(2U, -1)
342             {
343                 INST(0, Opcode::SpillFill);
344                 INST(1, Opcode::ReturnVoid);
345             }
346         }
347 
348         int alignmentOffset = aligned ? 1 : 0;
349 
350         auto sfInst = INS(0).CastToSpillFill();
351         FormSpillFillInst(sfInst, alignmentOffset);
352 
353         SetNumArgs(0);
354         SetNumVirtRegs(0);
355         GraphChecker(GetGraph()).Check();
356         GetGraph()->SetStackSlotsCount(12U + alignmentOffset);
357         RegAlloc(GetGraph());
358         ASSERT_TRUE(GetGraph()->RunPass<Codegen>());
359 
360         auto codeEntry = reinterpret_cast<vixl::aarch64::Instruction *>(GetGraph()->GetCode().Data());
361         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
362         auto codeExit = codeEntry + GetGraph()->GetCode().Size();
363         ASSERT(codeEntry != nullptr && codeExit != nullptr);
364         auto &decoder {GetDecoder()};
365         LoadStoreInstCollector visitor;
366         vixl::aarch64::Decoder::ScopedAddVisitors sv(decoder, {&visitor});
367         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
368         for (auto instr = codeEntry; instr < codeExit; instr += vixl::aarch64::kInstructionSize) {
369             decoder.Decode(instr);
370         }
371         EXPECT_EQ(visitor.GetStpX(), 1 /* 1 use pre increment and not counted */);
372         EXPECT_EQ(visitor.GetLdpX(), 1 /* 1 use post increment and not counted */);
373         EXPECT_EQ(visitor.GetLdpD(), 1);
374         EXPECT_EQ(visitor.GetStpD(), 1);
375 
376         constexpr auto TEST_REGS = RegMask(0x3F);
377         if (aligned) {
378             EXPECT_EQ(visitor.GetAccessedPairRegisters() & TEST_REGS, RegMask {0b11011});
379             EXPECT_EQ(visitor.GetAccessedPairVRegisters() & TEST_REGS, RegMask {0b110110});
380         } else {
381             EXPECT_EQ(visitor.GetAccessedPairRegisters() & TEST_REGS, RegMask {0b110110});
382             EXPECT_EQ(visitor.GetAccessedPairVRegisters() & TEST_REGS, RegMask {0b11011});
383         }
384     }
385 };
386 
TEST_F(CodegenSpillFillCoalescingTest,CoalesceAccessToUnalignedNeighborSlotsEvenRegsNumber)387 TEST_F(CodegenSpillFillCoalescingTest, CoalesceAccessToUnalignedNeighborSlotsEvenRegsNumber)
388 {
389     CheckSpillFillCoalescingForEvenRegsNumber(false);
390 }
391 
TEST_F(CodegenSpillFillCoalescingTest,CoalesceAccessToAlignedNeighborSlotsEvenRegsNumber)392 TEST_F(CodegenSpillFillCoalescingTest, CoalesceAccessToAlignedNeighborSlotsEvenRegsNumber)
393 {
394     CheckSpillFillCoalescingForEvenRegsNumber(true);
395 }
396 
TEST_F(CodegenSpillFillCoalescingTest,CoalesceAccessToUnalignedNeighborSlotsOddRegsNumber)397 TEST_F(CodegenSpillFillCoalescingTest, CoalesceAccessToUnalignedNeighborSlotsOddRegsNumber)
398 {
399     CheckSpillFillCoalescingForOddRegsNumber(false);
400 }
401 
TEST_F(CodegenSpillFillCoalescingTest,CoalesceAccessToAlignedNeighborSlotsOddRegsNumber)402 TEST_F(CodegenSpillFillCoalescingTest, CoalesceAccessToAlignedNeighborSlotsOddRegsNumber)
403 {
404     CheckSpillFillCoalescingForOddRegsNumber(true);
405 }
406 
407 // clang-format off
408 class CodegenLeafPrologueTest : public VixlDisasmTest {
409 public:
CodegenLeafPrologueTest()410     CodegenLeafPrologueTest()
411     {
412         g_options.SetCompilerVerifyRegalloc(false);
413 #ifndef NDEBUG
414         graph_->SetLowLevelInstructionsEnabled();
415 #endif
416     }
417 
CheckLeafPrologue()418     void CheckLeafPrologue()
419     {
420         // RedundantOps::inc()
421         RuntimeInterface::FieldPtr i = reinterpret_cast<void *>(0xDEADBEEF);  // NOLINT(modernize-use-auto)
422         GRAPH(GetGraph())
423         {
424             PARAMETER(0, 0).ref();
425             BASIC_BLOCK(2U, 3_I)
426             {
427                 INST(1U, Opcode::LoadObject).s64().Inputs(0).TypeId(208U).ObjField(i);
428                 INST(2U, Opcode::AddI).s64().Inputs(1).Imm(1);
429                 INST(3U, Opcode::StoreObject).s64().Inputs(0, 2_I).TypeId(208U).ObjField(i);
430             }
431             BASIC_BLOCK(3U, -1)
432             {
433                 INST(4U, Opcode::ReturnVoid);
434             }
435         }
436         SetNumArgs(1);
437 
438         std::vector<std::string> expectedAsm = {
439 #ifdef PANDA_COMPILER_DEBUG_INFO
440             "stp x29, x30, [sp, #-16]!",  // prolog save FP and LR
441             "mov x29, sp",                // prolog set FP
442             "stur x19, [sp, #-72]",   // prolog save callee-saved
443 #else
444             "stur x19, [sp, #-88]",   // prolog save callee-saved
445 #endif
446             "ldr x19, [x1]",
447             "add x19, x19, #0x1 // (1)",
448             "str x19, [x1]",
449 #ifdef PANDA_COMPILER_DEBUG_INFO
450             "ldur x19, [sp, #-72]",   // epilog restore callee-saved
451             "ldp x29, x30, [sp], #16",    // epilog restore FP and LR
452 #else
453             "ldur x19, [sp, #-88]",   // epilog restore callee-saved
454 #endif
455             "ret"};
456 
457         GraphChecker(GetGraph()).Check();
458         GetGraph()->RunPass<RegAllocLinearScan>();
459         bool setupFrame = GetGraph()->GetMethodProperties().GetRequireFrameSetup();
460         ASSERT_TRUE(setupFrame ? GetGraph()->RunPass<Codegen>() : GetGraph()->RunPass<CodegenNative>());
461         ASSERT_TRUE(GetGraph()->GetCode().Size() == expectedAsm.size() * vixl::aarch64::kInstructionSize);
462         auto codeEntry = reinterpret_cast<vixl::aarch64::Instruction *>(GetGraph()->GetCode().Data());
463         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
464         auto codeExit = codeEntry + GetGraph()->GetCode().Size();
465         size_t codeItems = (codeExit - codeEntry) / vixl::aarch64::kInstructionSize;
466         ASSERT_TRUE(codeItems == expectedAsm.size());
467 
468         auto& decoder {GetDecoder()};
469         auto& disasm {GetDisasm()};
470         vixl::aarch64::Decoder::ScopedAddVisitors sv(decoder, {&disasm});
471         for (size_t item = 0; item < codeItems; ++item) {
472             // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
473             decoder.Decode(codeEntry + item * vixl::aarch64::kInstructionSize);
474             EXPECT_EQ(expectedAsm.at(item), disasm.GetOutput());
475         }
476     }
477 
CheckLeafWithParamsOnStackPrologue()478     void CheckLeafWithParamsOnStackPrologue()
479     {
480         // RedundantOps::sum()
481         RuntimeInterface::FieldPtr i = reinterpret_cast<void *>(0xDEADBEEF);  // NOLINT(modernize-use-auto)
482         GRAPH(GetGraph())
483         {
484             PARAMETER(0U, 0_I).ref();
485             PARAMETER(1U, 1_I).s64();
486             PARAMETER(2U, 2_I).s64();
487             PARAMETER(3U, 3_I).s64();
488             PARAMETER(4U, 4_I).s64();
489             PARAMETER(5U, 5_I).s64();
490             PARAMETER(6U, 6_I).s64();
491             PARAMETER(7U, 7_I).s64();
492             PARAMETER(8U, 8_I).s64();
493 
494             BASIC_BLOCK(2U, -1)
495             {
496                 INST(10U, Opcode::Add).s64().Inputs(1U, 2_I);
497                 INST(11U, Opcode::Add).s64().Inputs(3U, 4_I);
498                 INST(12U, Opcode::Add).s64().Inputs(5U, 6_I);
499                 INST(13U, Opcode::Add).s64().Inputs(7U, 8_I);
500                 INST(14U, Opcode::Add).s64().Inputs(10U, 11_I);
501                 INST(15U, Opcode::Add).s64().Inputs(12U, 13_I);
502                 INST(16U, Opcode::Add).s64().Inputs(14U, 15_I);
503                 INST(17U, Opcode::StoreObject).s64().Inputs(0, 16_I).TypeId(301U).ObjField(i);
504                 INST(18U, Opcode::ReturnVoid);
505             }
506         }
507         SetNumArgs(9U);
508 
509         // In this case two parameters are passed on stack,
510         // thus to address them SP needs to be adjusted in prolog/epilog.
511         std::vector<std::string> expectedAsm = {
512 #ifdef PANDA_COMPILER_DEBUG_INFO
513             "stp x29, x30, [sp, #-16]!",    // prolog save FP and LR
514             "mov x29, sp",                  // prolog set FP
515             "stur x19, [sp, #-104]",    // prolog callee-saved
516             "stp x20, x21, [sp, #-96]",     // prolog callee-saved
517             "stp x22, x23, [sp, #-80]",     // prolog callee-saved
518             "sub sp, sp, #0x230 // (560)",  // prolog adjust SP
519 #else
520             "stur x19, [sp, #-120]",    // prolog callee-saved
521             "stp x20, x21, [sp, #-112]",    // prolog callee-saved
522             "stp x22, x23, [sp, #-96]",     // prolog callee-saved
523             "sub sp, sp, #0x240 // (576)",  // prolog adjust SP
524 #endif
525             "add",                          // "add x19, x2, x3"
526             "add",                          // "add x21, x4, x5"
527             "add",                          // "add x22, x6, x7"
528             "add x16, sp, #0x240 // (576)", // load params from stack
529             "ldp x22, x23, [x16]",          // load params from stack
530             "add",                          // "add x23, x23, x24"
531             "add",                          // "add x19, x19, x21"
532             "add",                          // "add x21, x22, x23"
533             "add",                          // "add x19, x19, x21"
534             "str x19, [x1]",
535             "ldr x19, [sp, #456]",     // restore callee-saved
536             "ldp x20, x21, [sp, #464]",     // restore callee-saved
537             "ldp x22, x23, [sp, #480]",     // restore callee-saved
538 #ifdef PANDA_COMPILER_DEBUG_INFO
539             "add sp, sp, #0x230 // (560)",  // epilog adjust SP
540             "ldp x29, x30, [sp], #16",      // epilog restore FP and LR
541 #else
542             "add sp, sp, #0x240 // (576)",  // epilog adjust SP
543 #endif
544             "ret"};
545 
546         std::regex addRegex("^add[[:blank:]]+x[0-9]+,[[:blank:]]+x[0-9]+,[[:blank:]]+x[0-9]+",
547                              std::regex::egrep | std::regex::icase);
548 
549         GraphChecker(GetGraph()).Check();
550         GetGraph()->RunPass<RegAllocLinearScan>();
551         bool setupFrame = GetGraph()->GetMethodProperties().GetRequireFrameSetup();
552         ASSERT_TRUE(setupFrame ? GetGraph()->RunPass<Codegen>() : GetGraph()->RunPass<CodegenNative>());
553         ASSERT_TRUE(GetGraph()->GetCode().Size() == expectedAsm.size() * vixl::aarch64::kInstructionSize);
554         auto codeEntry = reinterpret_cast<vixl::aarch64::Instruction *>(GetGraph()->GetCode().Data());
555         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
556         auto codeExit = codeEntry + GetGraph()->GetCode().Size();
557         size_t codeItems = (codeExit - codeEntry) / vixl::aarch64::kInstructionSize;
558         ASSERT_TRUE(codeItems == expectedAsm.size());
559 
560         auto& decoder {GetDecoder()};
561         auto& disasm {GetDisasm()};
562         vixl::aarch64::Decoder::ScopedAddVisitors sv(decoder, {&disasm});
563         for (size_t item = 0; item < codeItems; ++item) {
564             // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
565             decoder.Decode(codeEntry + item * vixl::aarch64::kInstructionSize);
566             // replace 'add rx, ry, rz' with 'add' to make comparison independent of regalloc
567             std::string s = std::regex_replace(disasm.GetOutput(), addRegex, "add");
568             EXPECT_EQ(expectedAsm.at(item), s);
569         }
570     }
571 };
572 // clang-format on
573 
TEST_F(CodegenLeafPrologueTest,LeafPrologueGeneration)574 TEST_F(CodegenLeafPrologueTest, LeafPrologueGeneration)
575 {
576     CheckLeafPrologue();
577 }
578 
TEST_F(CodegenLeafPrologueTest,LeafWithParamsOnStackPrologueGeneration)579 TEST_F(CodegenLeafPrologueTest, LeafWithParamsOnStackPrologueGeneration)
580 {
581     CheckLeafWithParamsOnStackPrologue();
582 }
583 
584 class CodegenTest : public VixlDisasmTest {
585 public:
586     template <typename T, size_t LEN>
AssertCode(const T (& expectedCode)[LEN])587     void AssertCode(const T (&expectedCode)[LEN])
588     {
589         auto codeEntry = reinterpret_cast<vixl::aarch64::Instruction *>(GetGraph()->GetCode().Data());
590         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
591         auto codeExit = codeEntry + GetGraph()->GetCode().Size();
592         ASSERT(codeEntry != nullptr && codeExit != nullptr);
593         auto &decoder {GetDecoder()};
594         auto &disasm {GetDisasm()};
595         vixl::aarch64::Decoder::ScopedAddVisitors sv(decoder, {&disasm});
596 
597         size_t index = 0;
598         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
599         for (auto instr = codeEntry; instr < codeExit; instr += vixl::aarch64::kInstructionSize) {
600             decoder.Decode(instr);
601             auto output = disasm.GetOutput();
602             if (index == 0) {
603                 if (std::strncmp(output, expectedCode[index], std::strlen(expectedCode[index])) == 0) {
604                     index++;
605                 }
606                 continue;
607             }
608             if (index >= LEN) {
609                 break;
610             }
611             ASSERT_TRUE(std::strncmp(output, expectedCode[index], std::strlen(expectedCode[index])) == 0);
612             index++;
613         }
614         ASSERT_EQ(index, LEN);
615     }
616 };
617 
TEST_F(CodegenTest,CallVirtual)618 TEST_F(CodegenTest, CallVirtual)
619 {
620     auto graph = GetGraph();
621     GRAPH(graph)
622     {
623         PARAMETER(0, 0).ref();
624         PARAMETER(1, 1).i32();
625         BASIC_BLOCK(2U, -1)
626         {
627             INST(2U, Opcode::SaveState).Inputs(0, 1).SrcVregs({0, 1});
628             INST(3U, Opcode::CallVirtual).v0id().InputsAutoType(0_I, 1_I, 2_I);
629             INST(4U, Opcode::ReturnVoid).v0id();
630         }
631     }
632     EXPECT_TRUE(RegAlloc(graph));
633     EXPECT_TRUE(graph->RunPass<Codegen>());
634     // exclude offset from verification to avoid test modifications
635     const char *expectedCode[] = {"ldr w0, [x1, #", "ldr x0, [x0, #", "ldr x30, [x0, #",
636                                   "blr x30"};  // CallVirtual is encoded without tmp reg
637     AssertCode(expectedCode);
638 }
639 
TEST_F(CodegenTest,EncodeMemCopy)640 TEST_F(CodegenTest, EncodeMemCopy)
641 {
642     auto graph = GetGraph();
643     GRAPH(graph)
644     {
645         CONSTANT(0, 0).i32().DstReg(0U);
646         BASIC_BLOCK(2U, -1)
647         {
648             INST(2U, Opcode::SpillFill);
649             INST(3U, Opcode::Return).i32().Inputs(0).DstReg(0U);
650         }
651     }
652     auto spillFill = INS(2U).CastToSpillFill();
653     // Add moves chain: R0 -> S0 -> S1 -> R0 [u32]
654     spillFill->AddSpillFill(Location::MakeRegister(0), Location::MakeStackSlot(0), DataType::INT32);
655     spillFill->AddSpillFill(Location::MakeStackSlot(0), Location::MakeStackSlot(1), DataType::INT32);
656     spillFill->AddSpillFill(Location::MakeStackSlot(1), Location::MakeRegister(0), DataType::INT32);
657 
658     graph->SetStackSlotsCount(2U);
659 #ifndef NDEBUG
660     graph->SetRegAllocApplied();
661 #endif
662     EXPECT_TRUE(graph->RunPass<Codegen>());
663 
664     // Check that stack slots are 64-bit wide
665     const char *expectedCode[] = {"str x0, [sp, #16]", "ldr x16, [sp, #16]", "str x16, [sp, #8]", "ldr w0, [sp, #8]"};
666     AssertCode(expectedCode);
667 }
668 
TEST_F(CodegenTest,EncodeWithZeroReg)669 TEST_F(CodegenTest, EncodeWithZeroReg)
670 {
671     // MAdd a, b, c <=> c + a * b
672 
673     // a = 0
674     {
675         auto graph = GetGraph();
676         GRAPH(graph)
677         {
678             CONSTANT(0, 0).i64();
679             PARAMETER(1, 0).i64();
680             PARAMETER(2U, 1).i64();
681 
682             BASIC_BLOCK(2U, -1)
683             {
684                 INST(3U, Opcode::MAdd).i64().Inputs(0U, 1_I, 2_I);
685                 INST(4U, Opcode::Return).i64().Inputs(3U);
686             }
687         }
688 
689         EXPECT_TRUE(RegAlloc(graph));
690         EXPECT_TRUE(graph->RunPass<Codegen>());
691 
692         const char *expectedCode[] = {"mov x0, x2"};
693         AssertCode(expectedCode);
694         ResetGraph();
695     }
696 
697     // b = 0
698     {
699         auto graph = GetGraph();
700         GRAPH(graph)
701         {
702             CONSTANT(0, 0).i64();
703             PARAMETER(1U, 0).i64();
704             PARAMETER(2U, 1).i64();
705 
706             BASIC_BLOCK(2U, -1)
707             {
708                 INST(3U, Opcode::MAdd).i64().Inputs(1, 0, 2_I);
709                 INST(4U, Opcode::Return).i64().Inputs(3U);
710             }
711         }
712 
713         EXPECT_TRUE(RegAlloc(graph));
714         EXPECT_TRUE(graph->RunPass<Codegen>());
715 
716         const char *expectedCode[] = {"mov x0, x2"};
717         AssertCode(expectedCode);
718         ResetGraph();
719     }
720 
721     // c = 0
722     {
723         auto graph = GetGraph();
724         GRAPH(graph)
725         {
726             CONSTANT(0, 0).i64();
727             PARAMETER(1U, 0).i64();
728             PARAMETER(2U, 1).i64();
729 
730             BASIC_BLOCK(2U, -1)
731             {
732                 INST(3U, Opcode::MAdd).i64().Inputs(1, 2_I, 0);
733                 INST(4U, Opcode::Return).i64().Inputs(3U);
734             }
735         }
736 
737         EXPECT_TRUE(RegAlloc(graph));
738         EXPECT_TRUE(graph->RunPass<Codegen>());
739 
740         const char *expectedCode[] = {"mul x0, x1, x2"};
741         AssertCode(expectedCode);
742         ResetGraph();
743     }
744 
745     // MSub a, b, c <=> c - a * b
746 
747     // a = 0
748     {
749         auto graph = GetGraph();
750         GRAPH(graph)
751         {
752             CONSTANT(0, 0).i64();
753             PARAMETER(1U, 0).i64();
754             PARAMETER(2U, 1).i64();
755 
756             BASIC_BLOCK(2U, -1)
757             {
758                 INST(3U, Opcode::MSub).i64().Inputs(0, 1, 2_I);
759                 INST(4U, Opcode::Return).i64().Inputs(3U);
760             }
761         }
762 
763         EXPECT_TRUE(RegAlloc(graph));
764         EXPECT_TRUE(graph->RunPass<Codegen>());
765 
766         const char *expectedCode[] = {"mov x0, x2"};
767         AssertCode(expectedCode);
768         ResetGraph();
769     }
770 
771     // b = 0
772     {
773         auto graph = GetGraph();
774         GRAPH(graph)
775         {
776             CONSTANT(0, 0).i64();
777             PARAMETER(1U, 0).i64();
778             PARAMETER(2U, 1).i64();
779 
780             BASIC_BLOCK(2U, -1)
781             {
782                 INST(3U, Opcode::MSub).i64().Inputs(1, 0, 2_I);
783                 INST(4U, Opcode::Return).i64().Inputs(3U);
784             }
785         }
786 
787         EXPECT_TRUE(RegAlloc(graph));
788         EXPECT_TRUE(graph->RunPass<Codegen>());
789 
790         const char *expectedCode[] = {"mov x0, x2"};
791         AssertCode(expectedCode);
792         ResetGraph();
793     }
794 
795     // c = 0
796     {
797         auto graph = GetGraph();
798         GRAPH(graph)
799         {
800             CONSTANT(0, 0).i64();
801             PARAMETER(1, 0).i64();
802             PARAMETER(2U, 1).i64();
803 
804             BASIC_BLOCK(2U, -1)
805             {
806                 INST(3U, Opcode::MSub).i64().Inputs(1, 2_I, 0);
807                 INST(4U, Opcode::Return).i64().Inputs(3U);
808             }
809         }
810 
811         EXPECT_TRUE(RegAlloc(graph));
812         EXPECT_TRUE(graph->RunPass<Codegen>());
813 
814         const char *expectedCode[] = {"mneg x0, x1, x2"};
815         AssertCode(expectedCode);
816         ResetGraph();
817     }
818 
819     // MNeg a, b <=> -(a * b)
820 
821     // a = 0
822     {
823         auto graph = GetGraph();
824         GRAPH(graph)
825         {
826             CONSTANT(0, 0).i64();
827             PARAMETER(1, 0).i64();
828 
829             BASIC_BLOCK(2U, -1)
830             {
831                 INST(3U, Opcode::MNeg).i64().Inputs(0, 1);
832                 INST(4U, Opcode::Return).i64().Inputs(3U);
833             }
834         }
835 
836         EXPECT_TRUE(RegAlloc(graph));
837         EXPECT_TRUE(graph->RunPass<Codegen>());
838 
839         const char *expectedCode[] = {"mov x0, #0"};
840         AssertCode(expectedCode);
841         ResetGraph();
842     }
843 
844     // b = 0
845     {
846         auto graph = GetGraph();
847         GRAPH(graph)
848         {
849             CONSTANT(0, 0).i64();
850             PARAMETER(1, 0).i64();
851 
852             BASIC_BLOCK(2U, -1)
853             {
854                 INST(3U, Opcode::MNeg).i64().Inputs(1, 0);
855                 INST(4U, Opcode::Return).i64().Inputs(3U);
856             }
857         }
858 
859         EXPECT_TRUE(RegAlloc(graph));
860         EXPECT_TRUE(graph->RunPass<Codegen>());
861 
862         const char *expectedCode[] = {"mov x0, #0"};
863         AssertCode(expectedCode);
864         ResetGraph();
865     }
866 }
867 
868 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
869 const DecoderVisitor::FormToVisitorFnMap DecoderVisitor::FORM_TO_VISITOR = {
870     DEFAULT_FORM_TO_VISITOR_MAP(DecoderVisitor)};
871 
872 // NOLINTEND(readability-magic-numbers,modernize-avoid-c-arrays)
873 
874 }  // namespace panda::compiler
875