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