//===- RISCVInstrInfoTest.cpp - RISCVInstrInfo unit tests -----------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "RISCVInstrInfo.h" #include "RISCVSubtarget.h" #include "RISCVTargetMachine.h" #include "llvm/CodeGen/MachineModuleInfo.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Target/TargetLoweringObjectFile.h" #include "llvm/Target/TargetMachine.h" #include "llvm/Target/TargetOptions.h" #include "gtest/gtest.h" #include using namespace llvm; namespace { class RISCVInstrInfoTest : public testing::TestWithParam { protected: std::unique_ptr TM; std::unique_ptr Ctx; std::unique_ptr ST; std::unique_ptr MMI; std::unique_ptr MF; std::unique_ptr M; static void SetUpTestSuite() { LLVMInitializeRISCVTargetInfo(); LLVMInitializeRISCVTarget(); LLVMInitializeRISCVTargetMC(); } RISCVInstrInfoTest() { std::string Error; auto TT(Triple::normalize(GetParam())); const Target *TheTarget = TargetRegistry::lookupTarget(TT, Error); TargetOptions Options; TM.reset(static_cast(TheTarget->createTargetMachine( TT, "generic", "", Options, std::nullopt, std::nullopt, CodeGenOptLevel::Default))); Ctx = std::make_unique(); M = std::make_unique("Module", *Ctx); M->setDataLayout(TM->createDataLayout()); auto *FType = FunctionType::get(Type::getVoidTy(*Ctx), false); auto *F = Function::Create(FType, GlobalValue::ExternalLinkage, "Test", *M); MMI = std::make_unique(TM.get()); ST = std::make_unique( TM->getTargetTriple(), TM->getTargetCPU(), TM->getTargetCPU(), TM->getTargetFeatureString(), TM->getTargetTriple().isArch64Bit() ? "lp64" : "ilp32", 0, 0, *TM); MF = std::make_unique(*F, *TM, *ST, 42, *MMI); } }; TEST_P(RISCVInstrInfoTest, IsAddImmediate) { const RISCVInstrInfo *TII = ST->getInstrInfo(); DebugLoc DL; MachineInstr *MI1 = BuildMI(*MF, DL, TII->get(RISCV::ADDI), RISCV::X1) .addReg(RISCV::X2) .addImm(-128) .getInstr(); auto MI1Res = TII->isAddImmediate(*MI1, RISCV::X1); ASSERT_TRUE(MI1Res.has_value()); EXPECT_EQ(MI1Res->Reg, RISCV::X2); EXPECT_EQ(MI1Res->Imm, -128); EXPECT_FALSE(TII->isAddImmediate(*MI1, RISCV::X2).has_value()); MachineInstr *MI2 = BuildMI(*MF, DL, TII->get(RISCV::LUI), RISCV::X1).addImm(-128).getInstr(); EXPECT_FALSE(TII->isAddImmediate(*MI2, RISCV::X1)); // Check ADDIW isn't treated as isAddImmediate. if (ST->is64Bit()) { MachineInstr *MI3 = BuildMI(*MF, DL, TII->get(RISCV::ADDIW), RISCV::X1) .addReg(RISCV::X2) .addImm(-128) .getInstr(); EXPECT_FALSE(TII->isAddImmediate(*MI3, RISCV::X1)); } } TEST_P(RISCVInstrInfoTest, GetMemOperandsWithOffsetWidth) { const RISCVInstrInfo *TII = ST->getInstrInfo(); const TargetRegisterInfo *TRI = ST->getRegisterInfo(); DebugLoc DL; SmallVector BaseOps; unsigned Width; int64_t Offset; bool OffsetIsScalable; auto MMO = MF->getMachineMemOperand(MachinePointerInfo(), MachineMemOperand::MOLoad, 1, Align(1)); MachineInstr *MI = BuildMI(*MF, DL, TII->get(RISCV::LB), RISCV::X1) .addReg(RISCV::X2) .addImm(-128) .addMemOperand(MMO) .getInstr(); bool Res = TII->getMemOperandsWithOffsetWidth(*MI, BaseOps, Offset, OffsetIsScalable, Width, TRI); ASSERT_TRUE(Res); ASSERT_EQ(BaseOps.size(), 1u); ASSERT_TRUE(BaseOps.front()->isReg()); EXPECT_EQ(BaseOps.front()->getReg(), RISCV::X2); EXPECT_EQ(Offset, -128); EXPECT_FALSE(OffsetIsScalable); EXPECT_EQ(Width, 1u); BaseOps.clear(); MMO = MF->getMachineMemOperand(MachinePointerInfo(), MachineMemOperand::MOStore, 4, Align(4)); MI = BuildMI(*MF, DL, TII->get(RISCV::FSW)) .addReg(RISCV::F3_F) .addReg(RISCV::X3) .addImm(36) .addMemOperand(MMO); Res = TII->getMemOperandsWithOffsetWidth(*MI, BaseOps, Offset, OffsetIsScalable, Width, TRI); ASSERT_TRUE(Res); ASSERT_EQ(BaseOps.size(), 1u); ASSERT_TRUE(BaseOps.front()->isReg()); EXPECT_EQ(BaseOps.front()->getReg(), RISCV::X3); EXPECT_EQ(Offset, 36); EXPECT_FALSE(OffsetIsScalable); EXPECT_EQ(Width, 4u); BaseOps.clear(); MMO = MF->getMachineMemOperand(MachinePointerInfo(), MachineMemOperand::MOStore, 16, Align(16)); MI = BuildMI(*MF, DL, TII->get(RISCV::PseudoVLE32_V_M1), RISCV::V8) .addReg(RISCV::X3) .addMemOperand(MMO); Res = TII->getMemOperandsWithOffsetWidth(*MI, BaseOps, Offset, OffsetIsScalable, Width, TRI); ASSERT_FALSE(Res); // Vector loads/stored are not handled for now. BaseOps.clear(); MI = BuildMI(*MF, DL, TII->get(RISCV::ADDI), RISCV::X4) .addReg(RISCV::X5) .addImm(16); Res = TII->getMemOperandsWithOffsetWidth(*MI, BaseOps, Offset, OffsetIsScalable, Width, TRI); BaseOps.clear(); MMO = MF->getMachineMemOperand(MachinePointerInfo(), MachineMemOperand::MOStore, 4, Align(4)); MI = BuildMI(*MF, DL, TII->get(RISCV::SW)) .addReg(RISCV::X3) .addFrameIndex(2) .addImm(4) .addMemOperand(MMO); Res = TII->getMemOperandsWithOffsetWidth(*MI, BaseOps, Offset, OffsetIsScalable, Width, TRI); ASSERT_TRUE(Res); ASSERT_EQ(BaseOps.size(), 1u); ASSERT_TRUE(BaseOps.front()->isFI()); EXPECT_EQ(BaseOps.front()->getIndex(), 2); EXPECT_EQ(Offset, 4); EXPECT_FALSE(OffsetIsScalable); EXPECT_EQ(Width, 4u); } static void expectDIEPrintResult(const DIExpression *Expr, StringRef Expected) { std::string Output; raw_string_ostream OS(Output); Expr->print(OS); OS.flush(); EXPECT_EQ(OS.str(), Expected); } TEST_P(RISCVInstrInfoTest, DescribeLoadedValue) { const RISCVInstrInfo *TII = ST->getInstrInfo(); DebugLoc DL; MachineBasicBlock *MBB = MF->CreateMachineBasicBlock(); MF->getProperties().set(MachineFunctionProperties::Property::NoVRegs); // Register move. auto *MI1 = BuildMI(*MBB, MBB->begin(), DL, TII->get(RISCV::ADDI), RISCV::X1) .addReg(RISCV::X2) .addImm(0) .getInstr(); EXPECT_FALSE(TII->describeLoadedValue(*MI1, RISCV::X2).has_value()); std::optional MI1Res = TII->describeLoadedValue(*MI1, RISCV::X1); ASSERT_TRUE(MI1Res.has_value()); ASSERT_TRUE(MI1Res->first.isReg()); EXPECT_EQ(MI1Res->first.getReg(), RISCV::X2); expectDIEPrintResult(MI1Res->second, "!DIExpression()"); // Load immediate. auto *MI2 = BuildMI(*MBB, MBB->begin(), DL, TII->get(RISCV::ADDI), RISCV::X3) .addReg(RISCV::X0) .addImm(111) .getInstr(); std::optional MI2Res = TII->describeLoadedValue(*MI2, RISCV::X3); ASSERT_TRUE(MI2Res.has_value()); ASSERT_TRUE(MI2Res->first.isReg()); EXPECT_EQ(MI2Res->first.getReg(), RISCV::X0); // TODO: Could be a DW_OP_constu if this is recognised as a immediate load // rather than just an addi. expectDIEPrintResult(MI2Res->second, "!DIExpression(DW_OP_plus_uconst, 111)"); // Add immediate. auto *MI3 = BuildMI(*MBB, MBB->begin(), DL, TII->get(RISCV::ADDI), RISCV::X2) .addReg(RISCV::X3) .addImm(222) .getInstr(); std::optional MI3Res = TII->describeLoadedValue(*MI3, RISCV::X2); ASSERT_TRUE(MI3Res.has_value()); ASSERT_TRUE(MI3Res->first.isReg()); EXPECT_EQ(MI3Res->first.getReg(), RISCV::X3); expectDIEPrintResult(MI3Res->second, "!DIExpression(DW_OP_plus_uconst, 222)"); // Load value from memory. // It would be better (more reflective of real-world describeLoadedValue // usage) to test using MachinePointerInfo::getFixedStack, but // unfortunately it would be overly fiddly to make this work. auto MMO = MF->getMachineMemOperand(MachinePointerInfo::getConstantPool(*MF), MachineMemOperand::MOLoad, 1, Align(1)); auto *MI4 = BuildMI(*MBB, MBB->begin(), DL, TII->get(RISCV::LB), RISCV::X1) .addReg(RISCV::X2) .addImm(-128) .addMemOperand(MMO) .getInstr(); std::optional MI4Res = TII->describeLoadedValue(*MI4, RISCV::X1); ASSERT_TRUE(MI4Res.has_value()); ASSERT_TRUE(MI4Res->first.isReg()); EXPECT_EQ(MI4Res->first.getReg(), RISCV::X2); expectDIEPrintResult( MI4Res->second, "!DIExpression(DW_OP_constu, 128, DW_OP_minus, DW_OP_deref_size, 1)"); MF->deleteMachineBasicBlock(MBB); } } // namespace INSTANTIATE_TEST_SUITE_P(RV32And64, RISCVInstrInfoTest, testing::Values("riscv32", "riscv64"));