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