| 1 | //===- bolt/unittest/Core/MCPlusBuilder.cpp -------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #ifdef AARCH64_AVAILABLE |
| 10 | #include "AArch64Subtarget.h" |
| 11 | #include "MCTargetDesc/AArch64MCTargetDesc.h" |
| 12 | #endif // AARCH64_AVAILABLE |
| 13 | |
| 14 | #ifdef X86_AVAILABLE |
| 15 | #include "X86Subtarget.h" |
| 16 | #endif // X86_AVAILABLE |
| 17 | |
| 18 | #include "bolt/Core/BinaryBasicBlock.h" |
| 19 | #include "bolt/Core/BinaryFunction.h" |
| 20 | #include "bolt/Rewrite/RewriteInstance.h" |
| 21 | #include "llvm/BinaryFormat/ELF.h" |
| 22 | #include "llvm/DebugInfo/DWARF/DWARFContext.h" |
| 23 | #include "llvm/MC/MCInstBuilder.h" |
| 24 | #include "llvm/Support/TargetSelect.h" |
| 25 | #include "gtest/gtest.h" |
| 26 | |
| 27 | using namespace llvm; |
| 28 | using namespace llvm::object; |
| 29 | using namespace llvm::ELF; |
| 30 | using namespace bolt; |
| 31 | |
| 32 | namespace { |
| 33 | struct MCPlusBuilderTester : public testing::TestWithParam<Triple::ArchType> { |
| 34 | void SetUp() override { |
| 35 | initalizeLLVM(); |
| 36 | prepareElf(); |
| 37 | initializeBolt(); |
| 38 | } |
| 39 | |
| 40 | protected: |
| 41 | void initalizeLLVM() { |
| 42 | #define BOLT_TARGET(target) \ |
| 43 | LLVMInitialize##target##TargetInfo(); \ |
| 44 | LLVMInitialize##target##TargetMC(); \ |
| 45 | LLVMInitialize##target##AsmParser(); \ |
| 46 | LLVMInitialize##target##Disassembler(); \ |
| 47 | LLVMInitialize##target##Target(); \ |
| 48 | LLVMInitialize##target##AsmPrinter(); |
| 49 | |
| 50 | #include "bolt/Core/TargetConfig.def" |
| 51 | } |
| 52 | |
| 53 | void prepareElf() { |
| 54 | memcpy(dest: ElfBuf, src: "\177ELF" , n: 4); |
| 55 | ELF64LE::Ehdr *EHdr = reinterpret_cast<typename ELF64LE::Ehdr *>(ElfBuf); |
| 56 | EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64; |
| 57 | EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB; |
| 58 | EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64; |
| 59 | MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF" ); |
| 60 | ObjFile = cantFail(ValOrErr: ObjectFile::createObjectFile(Object: Source)); |
| 61 | } |
| 62 | |
| 63 | void initializeBolt() { |
| 64 | Relocation::Arch = ObjFile->makeTriple().getArch(); |
| 65 | BC = cantFail(ValOrErr: BinaryContext::createBinaryContext( |
| 66 | TheTriple: ObjFile->makeTriple(), SSP: std::make_shared<orc::SymbolStringPool>(), |
| 67 | InputFileName: ObjFile->getFileName(), Features: nullptr, IsPIC: true, DwCtx: DWARFContext::create(Obj: *ObjFile), |
| 68 | Logger: {.Out: llvm::outs(), .Err: llvm::errs()})); |
| 69 | ASSERT_FALSE(!BC); |
| 70 | BC->initializeTarget(TargetBuilder: std::unique_ptr<MCPlusBuilder>( |
| 71 | createMCPlusBuilder(Arch: GetParam(), Analysis: BC->MIA.get(), Info: BC->MII.get(), |
| 72 | RegInfo: BC->MRI.get(), STI: BC->STI.get()))); |
| 73 | } |
| 74 | |
| 75 | void assertRegMask(const BitVector &RegMask, |
| 76 | std::initializer_list<MCPhysReg> ExpectedRegs) { |
| 77 | ASSERT_EQ(RegMask.count(), ExpectedRegs.size()); |
| 78 | for (MCPhysReg Reg : ExpectedRegs) |
| 79 | ASSERT_TRUE(RegMask[Reg]) << "Expected " << BC->MRI->getName(RegNo: Reg) << "." ; |
| 80 | } |
| 81 | |
| 82 | void assertRegMask(std::function<void(BitVector &)> FillRegMask, |
| 83 | std::initializer_list<MCPhysReg> ExpectedRegs) { |
| 84 | BitVector RegMask(BC->MRI->getNumRegs()); |
| 85 | FillRegMask(RegMask); |
| 86 | assertRegMask(RegMask, ExpectedRegs); |
| 87 | } |
| 88 | |
| 89 | void testRegAliases(Triple::ArchType Arch, uint64_t Register, |
| 90 | std::initializer_list<MCPhysReg> ExpectedAliases, |
| 91 | bool OnlySmaller = false) { |
| 92 | if (GetParam() != Arch) |
| 93 | GTEST_SKIP(); |
| 94 | |
| 95 | const BitVector &BV = BC->MIB->getAliases(Reg: Register, OnlySmaller); |
| 96 | assertRegMask(RegMask: BV, ExpectedRegs: ExpectedAliases); |
| 97 | } |
| 98 | |
| 99 | char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {}; |
| 100 | std::unique_ptr<ObjectFile> ObjFile; |
| 101 | std::unique_ptr<BinaryContext> BC; |
| 102 | }; |
| 103 | } // namespace |
| 104 | |
| 105 | #ifdef AARCH64_AVAILABLE |
| 106 | |
| 107 | INSTANTIATE_TEST_SUITE_P(AArch64, MCPlusBuilderTester, |
| 108 | ::testing::Values(Triple::aarch64)); |
| 109 | |
| 110 | TEST_P(MCPlusBuilderTester, AliasX0) { |
| 111 | testRegAliases(Triple::aarch64, AArch64::X0, |
| 112 | {AArch64::W0, AArch64::W0_HI, AArch64::X0, AArch64::W0_W1, |
| 113 | AArch64::X0_X1, AArch64::X0_X1_X2_X3_X4_X5_X6_X7}); |
| 114 | } |
| 115 | |
| 116 | TEST_P(MCPlusBuilderTester, AliasSmallerX0) { |
| 117 | testRegAliases(Triple::aarch64, AArch64::X0, |
| 118 | {AArch64::W0, AArch64::W0_HI, AArch64::X0}, |
| 119 | /*OnlySmaller=*/true); |
| 120 | } |
| 121 | |
| 122 | TEST_P(MCPlusBuilderTester, AArch64_CmpJE) { |
| 123 | if (GetParam() != Triple::aarch64) |
| 124 | GTEST_SKIP(); |
| 125 | BinaryFunction *BF = BC->createInjectedBinaryFunction(Name: "BF" , IsSimple: true); |
| 126 | std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock(); |
| 127 | |
| 128 | InstructionListType Instrs = |
| 129 | BC->MIB->createCmpJE(AArch64::RegNo: X0, Imm: 2, Target: BB->getLabel(), Ctx: BC->Ctx.get()); |
| 130 | BB->addInstructions(Begin: Instrs.begin(), End: Instrs.end()); |
| 131 | BB->addSuccessor(Succ: BB.get()); |
| 132 | |
| 133 | auto II = BB->begin(); |
| 134 | ASSERT_EQ(II->getOpcode(), AArch64::SUBSXri); |
| 135 | ASSERT_EQ(II->getOperand(0).getReg(), AArch64::XZR); |
| 136 | ASSERT_EQ(II->getOperand(1).getReg(), AArch64::X0); |
| 137 | ASSERT_EQ(II->getOperand(2).getImm(), 2); |
| 138 | ASSERT_EQ(II->getOperand(3).getImm(), 0); |
| 139 | II++; |
| 140 | ASSERT_EQ(II->getOpcode(), AArch64::Bcc); |
| 141 | ASSERT_EQ(II->getOperand(0).getImm(), AArch64CC::EQ); |
| 142 | const MCSymbol *Label = BC->MIB->getTargetSymbol(Inst: *II, OpNum: 1); |
| 143 | ASSERT_EQ(Label, BB->getLabel()); |
| 144 | } |
| 145 | |
| 146 | TEST_P(MCPlusBuilderTester, AArch64_CmpJNE) { |
| 147 | if (GetParam() != Triple::aarch64) |
| 148 | GTEST_SKIP(); |
| 149 | BinaryFunction *BF = BC->createInjectedBinaryFunction(Name: "BF" , IsSimple: true); |
| 150 | std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock(); |
| 151 | |
| 152 | InstructionListType Instrs = |
| 153 | BC->MIB->createCmpJNE(AArch64::RegNo: X0, Imm: 2, Target: BB->getLabel(), Ctx: BC->Ctx.get()); |
| 154 | BB->addInstructions(Begin: Instrs.begin(), End: Instrs.end()); |
| 155 | BB->addSuccessor(Succ: BB.get()); |
| 156 | |
| 157 | auto II = BB->begin(); |
| 158 | ASSERT_EQ(II->getOpcode(), AArch64::SUBSXri); |
| 159 | ASSERT_EQ(II->getOperand(0).getReg(), AArch64::XZR); |
| 160 | ASSERT_EQ(II->getOperand(1).getReg(), AArch64::X0); |
| 161 | ASSERT_EQ(II->getOperand(2).getImm(), 2); |
| 162 | ASSERT_EQ(II->getOperand(3).getImm(), 0); |
| 163 | II++; |
| 164 | ASSERT_EQ(II->getOpcode(), AArch64::Bcc); |
| 165 | ASSERT_EQ(II->getOperand(0).getImm(), AArch64CC::NE); |
| 166 | const MCSymbol *Label = BC->MIB->getTargetSymbol(Inst: *II, OpNum: 1); |
| 167 | ASSERT_EQ(Label, BB->getLabel()); |
| 168 | } |
| 169 | |
| 170 | TEST_P(MCPlusBuilderTester, testAccessedRegsImplicitDef) { |
| 171 | if (GetParam() != Triple::aarch64) |
| 172 | GTEST_SKIP(); |
| 173 | |
| 174 | // adds x0, x5, #42 |
| 175 | MCInst Inst = MCInstBuilder(AArch64::ADDSXri) |
| 176 | .addReg(AArch64::X0) |
| 177 | .addReg(AArch64::X5) |
| 178 | .addImm(42) |
| 179 | .addImm(0); |
| 180 | |
| 181 | assertRegMask([&](BitVector &BV) { BC->MIB->getClobberedRegs(Inst, BV); }, |
| 182 | {AArch64::NZCV, AArch64::W0, AArch64::X0, AArch64::W0_HI, |
| 183 | AArch64::X0_X1_X2_X3_X4_X5_X6_X7, AArch64::W0_W1, |
| 184 | AArch64::X0_X1}); |
| 185 | |
| 186 | assertRegMask( |
| 187 | [&](BitVector &BV) { BC->MIB->getTouchedRegs(Inst, BV); }, |
| 188 | {AArch64::NZCV, AArch64::W0, AArch64::W5, AArch64::X0, AArch64::X5, |
| 189 | AArch64::W0_HI, AArch64::W5_HI, AArch64::X0_X1_X2_X3_X4_X5_X6_X7, |
| 190 | AArch64::X2_X3_X4_X5_X6_X7_X8_X9, AArch64::X4_X5_X6_X7_X8_X9_X10_X11, |
| 191 | AArch64::W0_W1, AArch64::W4_W5, AArch64::X0_X1, AArch64::X4_X5}); |
| 192 | |
| 193 | assertRegMask([&](BitVector &BV) { BC->MIB->getWrittenRegs(Inst, BV); }, |
| 194 | {AArch64::NZCV, AArch64::W0, AArch64::X0, AArch64::W0_HI}); |
| 195 | |
| 196 | assertRegMask([&](BitVector &BV) { BC->MIB->getUsedRegs(Inst, BV); }, |
| 197 | {AArch64::W5, AArch64::X5, AArch64::W5_HI}); |
| 198 | |
| 199 | assertRegMask([&](BitVector &BV) { BC->MIB->getSrcRegs(Inst, BV); }, |
| 200 | {AArch64::W5, AArch64::X5, AArch64::W5_HI}); |
| 201 | } |
| 202 | |
| 203 | TEST_P(MCPlusBuilderTester, testAccessedRegsImplicitUse) { |
| 204 | if (GetParam() != Triple::aarch64) |
| 205 | GTEST_SKIP(); |
| 206 | |
| 207 | // b.eq <label> |
| 208 | MCInst Inst = |
| 209 | MCInstBuilder(AArch64::Bcc) |
| 210 | .addImm(AArch64CC::EQ) |
| 211 | .addImm(0); // <label> - should be Expr, but immediate 0 works too. |
| 212 | |
| 213 | assertRegMask(FillRegMask: [&](BitVector &BV) { BC->MIB->getClobberedRegs(Inst, Regs&: BV); }, |
| 214 | ExpectedRegs: {}); |
| 215 | |
| 216 | assertRegMask([&](BitVector &BV) { BC->MIB->getTouchedRegs(Inst, BV); }, |
| 217 | {AArch64::NZCV}); |
| 218 | |
| 219 | assertRegMask(FillRegMask: [&](BitVector &BV) { BC->MIB->getWrittenRegs(Inst, Regs&: BV); }, ExpectedRegs: {}); |
| 220 | |
| 221 | assertRegMask([&](BitVector &BV) { BC->MIB->getUsedRegs(Inst, BV); }, |
| 222 | {AArch64::NZCV}); |
| 223 | |
| 224 | assertRegMask([&](BitVector &BV) { BC->MIB->getSrcRegs(Inst, BV); }, |
| 225 | {AArch64::NZCV}); |
| 226 | } |
| 227 | |
| 228 | TEST_P(MCPlusBuilderTester, testAccessedRegsMultipleDefs) { |
| 229 | if (GetParam() != Triple::aarch64) |
| 230 | GTEST_SKIP(); |
| 231 | |
| 232 | // ldr x0, [x5], #16 |
| 233 | MCInst Inst = MCInstBuilder(AArch64::LDRXpost) |
| 234 | .addReg(AArch64::X5) |
| 235 | .addReg(AArch64::X0) |
| 236 | .addReg(AArch64::X5) |
| 237 | .addImm(16); |
| 238 | |
| 239 | assertRegMask( |
| 240 | [&](BitVector &BV) { BC->MIB->getClobberedRegs(Inst, BV); }, |
| 241 | {AArch64::W0, AArch64::W5, AArch64::X0, AArch64::X5, AArch64::W0_HI, |
| 242 | AArch64::W5_HI, AArch64::X0_X1_X2_X3_X4_X5_X6_X7, |
| 243 | AArch64::X2_X3_X4_X5_X6_X7_X8_X9, AArch64::X4_X5_X6_X7_X8_X9_X10_X11, |
| 244 | AArch64::W0_W1, AArch64::W4_W5, AArch64::X0_X1, AArch64::X4_X5}); |
| 245 | |
| 246 | assertRegMask( |
| 247 | [&](BitVector &BV) { BC->MIB->getTouchedRegs(Inst, BV); }, |
| 248 | {AArch64::W0, AArch64::W5, AArch64::X0, AArch64::X5, AArch64::W0_HI, |
| 249 | AArch64::W5_HI, AArch64::X0_X1_X2_X3_X4_X5_X6_X7, |
| 250 | AArch64::X2_X3_X4_X5_X6_X7_X8_X9, AArch64::X4_X5_X6_X7_X8_X9_X10_X11, |
| 251 | AArch64::W0_W1, AArch64::W4_W5, AArch64::X0_X1, AArch64::X4_X5}); |
| 252 | |
| 253 | assertRegMask([&](BitVector &BV) { BC->MIB->getWrittenRegs(Inst, BV); }, |
| 254 | {AArch64::W0, AArch64::X0, AArch64::W0_HI, AArch64::W5, |
| 255 | AArch64::X5, AArch64::W5_HI}); |
| 256 | |
| 257 | assertRegMask([&](BitVector &BV) { BC->MIB->getUsedRegs(Inst, BV); }, |
| 258 | {AArch64::W5, AArch64::X5, AArch64::W5_HI}); |
| 259 | |
| 260 | assertRegMask([&](BitVector &BV) { BC->MIB->getSrcRegs(Inst, BV); }, |
| 261 | {AArch64::W5, AArch64::X5, AArch64::W5_HI}); |
| 262 | } |
| 263 | |
| 264 | #endif // AARCH64_AVAILABLE |
| 265 | |
| 266 | #ifdef X86_AVAILABLE |
| 267 | |
| 268 | INSTANTIATE_TEST_SUITE_P(X86, MCPlusBuilderTester, |
| 269 | ::testing::Values(Triple::x86_64)); |
| 270 | |
| 271 | TEST_P(MCPlusBuilderTester, AliasAX) { |
| 272 | testRegAliases(Triple::x86_64, X86::AX, |
| 273 | {X86::RAX, X86::EAX, X86::AX, X86::AL, X86::AH}); |
| 274 | } |
| 275 | |
| 276 | TEST_P(MCPlusBuilderTester, AliasSmallerAX) { |
| 277 | testRegAliases(Triple::x86_64, X86::AX, {X86::AX, X86::AL, X86::AH}, |
| 278 | /*OnlySmaller=*/true); |
| 279 | } |
| 280 | |
| 281 | TEST_P(MCPlusBuilderTester, ReplaceRegWithImm) { |
| 282 | if (GetParam() != Triple::x86_64) |
| 283 | GTEST_SKIP(); |
| 284 | BinaryFunction *BF = BC->createInjectedBinaryFunction(Name: "BF" , IsSimple: true); |
| 285 | std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock(); |
| 286 | MCInst Inst; // cmpl %eax, %ebx |
| 287 | Inst.setOpcode(X86::CMP32rr); |
| 288 | Inst.addOperand(MCOperand::createReg(X86::EAX)); |
| 289 | Inst.addOperand(MCOperand::createReg(X86::EBX)); |
| 290 | auto II = BB->addInstruction(Inst); |
| 291 | bool Replaced = BC->MIB->replaceRegWithImm(*II, X86::EBX, 1); |
| 292 | ASSERT_TRUE(Replaced); |
| 293 | ASSERT_EQ(II->getOpcode(), X86::CMP32ri8); |
| 294 | ASSERT_EQ(II->getOperand(0).getReg(), X86::EAX); |
| 295 | ASSERT_EQ(II->getOperand(1).getImm(), 1); |
| 296 | } |
| 297 | |
| 298 | TEST_P(MCPlusBuilderTester, X86_CmpJE) { |
| 299 | if (GetParam() != Triple::x86_64) |
| 300 | GTEST_SKIP(); |
| 301 | BinaryFunction *BF = BC->createInjectedBinaryFunction(Name: "BF" , IsSimple: true); |
| 302 | std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock(); |
| 303 | |
| 304 | InstructionListType Instrs = |
| 305 | BC->MIB->createCmpJE(X86::EAX, 2, BB->getLabel(), BC->Ctx.get()); |
| 306 | BB->addInstructions(Begin: Instrs.begin(), End: Instrs.end()); |
| 307 | BB->addSuccessor(Succ: BB.get()); |
| 308 | |
| 309 | auto II = BB->begin(); |
| 310 | ASSERT_EQ(II->getOpcode(), X86::CMP64ri8); |
| 311 | ASSERT_EQ(II->getOperand(0).getReg(), X86::EAX); |
| 312 | ASSERT_EQ(II->getOperand(1).getImm(), 2); |
| 313 | II++; |
| 314 | ASSERT_EQ(II->getOpcode(), X86::JCC_1); |
| 315 | const MCSymbol *Label = BC->MIB->getTargetSymbol(Inst: *II, OpNum: 0); |
| 316 | ASSERT_EQ(Label, BB->getLabel()); |
| 317 | ASSERT_EQ(II->getOperand(1).getImm(), X86::COND_E); |
| 318 | } |
| 319 | |
| 320 | TEST_P(MCPlusBuilderTester, X86_CmpJNE) { |
| 321 | if (GetParam() != Triple::x86_64) |
| 322 | GTEST_SKIP(); |
| 323 | BinaryFunction *BF = BC->createInjectedBinaryFunction(Name: "BF" , IsSimple: true); |
| 324 | std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock(); |
| 325 | |
| 326 | InstructionListType Instrs = |
| 327 | BC->MIB->createCmpJNE(X86::EAX, 2, BB->getLabel(), BC->Ctx.get()); |
| 328 | BB->addInstructions(Begin: Instrs.begin(), End: Instrs.end()); |
| 329 | BB->addSuccessor(Succ: BB.get()); |
| 330 | |
| 331 | auto II = BB->begin(); |
| 332 | ASSERT_EQ(II->getOpcode(), X86::CMP64ri8); |
| 333 | ASSERT_EQ(II->getOperand(0).getReg(), X86::EAX); |
| 334 | ASSERT_EQ(II->getOperand(1).getImm(), 2); |
| 335 | II++; |
| 336 | ASSERT_EQ(II->getOpcode(), X86::JCC_1); |
| 337 | const MCSymbol *Label = BC->MIB->getTargetSymbol(Inst: *II, OpNum: 0); |
| 338 | ASSERT_EQ(Label, BB->getLabel()); |
| 339 | ASSERT_EQ(II->getOperand(1).getImm(), X86::COND_NE); |
| 340 | } |
| 341 | |
| 342 | #endif // X86_AVAILABLE |
| 343 | |
| 344 | TEST_P(MCPlusBuilderTester, Annotation) { |
| 345 | MCInst Inst; |
| 346 | BC->MIB->createTailCall(Inst, Target: BC->Ctx->createNamedTempSymbol(), |
| 347 | Ctx: BC->Ctx.get()); |
| 348 | MCSymbol *LPSymbol = BC->Ctx->createNamedTempSymbol(Name: "LP" ); |
| 349 | uint64_t Value = INT32_MIN; |
| 350 | // Test encodeAnnotationImm using this indirect way |
| 351 | BC->MIB->addEHInfo(Inst, LP: MCPlus::MCLandingPad(LPSymbol, Value)); |
| 352 | // Round-trip encoding-decoding check for negative values |
| 353 | std::optional<MCPlus::MCLandingPad> EHInfo = BC->MIB->getEHInfo(Inst); |
| 354 | ASSERT_TRUE(EHInfo.has_value()); |
| 355 | MCPlus::MCLandingPad LP = EHInfo.value(); |
| 356 | uint64_t DecodedValue = LP.second; |
| 357 | ASSERT_EQ(Value, DecodedValue); |
| 358 | |
| 359 | // Large int64 should trigger an out of range assertion |
| 360 | Value = 0x1FF'FFFF'FFFF'FFFFULL; |
| 361 | Inst.clear(); |
| 362 | BC->MIB->createTailCall(Inst, Target: BC->Ctx->createNamedTempSymbol(), |
| 363 | Ctx: BC->Ctx.get()); |
| 364 | ASSERT_DEATH(BC->MIB->addEHInfo(Inst, MCPlus::MCLandingPad(LPSymbol, Value)), |
| 365 | "annotation value out of range" ); |
| 366 | } |
| 367 | |