diff --git a/lldb/source/Plugins/Instruction/LoongArch/EmulateInstructionLoongArch.h b/lldb/source/Plugins/Instruction/LoongArch/EmulateInstructionLoongArch.h --- a/lldb/source/Plugins/Instruction/LoongArch/EmulateInstructionLoongArch.h +++ b/lldb/source/Plugins/Instruction/LoongArch/EmulateInstructionLoongArch.h @@ -60,6 +60,7 @@ lldb::addr_t ReadPC(bool *success); bool WritePC(lldb::addr_t pc); bool IsLoongArch64() { return m_arch_subtype == llvm::Triple::loongarch64; } + bool TestExecute(uint32_t inst); private: struct Opcode { diff --git a/lldb/source/Plugins/Instruction/LoongArch/EmulateInstructionLoongArch.cpp b/lldb/source/Plugins/Instruction/LoongArch/EmulateInstructionLoongArch.cpp --- a/lldb/source/Plugins/Instruction/LoongArch/EmulateInstructionLoongArch.cpp +++ b/lldb/source/Plugins/Instruction/LoongArch/EmulateInstructionLoongArch.cpp @@ -68,6 +68,16 @@ return nullptr; } +bool EmulateInstructionLoongArch::TestExecute(uint32_t inst) { + Opcode *opcode_data = GetOpcodeForInstruction(inst); + if (!opcode_data) + return false; + // Call the Emulate... function. + if (!(this->*opcode_data->callback)(inst)) + return false; + return true; +} + bool EmulateInstructionLoongArch::EvaluateInstruction(uint32_t options) { uint32_t inst_size = m_opcode.GetByteSize(); uint32_t inst = m_opcode.GetOpcode32(); diff --git a/lldb/unittests/Instruction/CMakeLists.txt b/lldb/unittests/Instruction/CMakeLists.txt --- a/lldb/unittests/Instruction/CMakeLists.txt +++ b/lldb/unittests/Instruction/CMakeLists.txt @@ -1,5 +1,6 @@ add_lldb_unittest(EmulatorTests ARM64/TestAArch64Emulator.cpp + LoongArch/TestLoongArchEmulator.cpp RISCV/TestRISCVEmulator.cpp LINK_LIBS @@ -7,8 +8,9 @@ lldbSymbol lldbTarget lldbPluginInstructionARM64 + lldbPluginInstructionLoongArch lldbPluginInstructionRISCV LINK_COMPONENTS Support - ) \ No newline at end of file + ) diff --git a/lldb/unittests/Instruction/LoongArch/TestLoongArchEmulator.cpp b/lldb/unittests/Instruction/LoongArch/TestLoongArchEmulator.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/Instruction/LoongArch/TestLoongArchEmulator.cpp @@ -0,0 +1,230 @@ +//===-- TestLoongArchEmulator.cpp -----------------------------------------===// +// +// 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 "lldb/Core/Address.h" +#include "lldb/Core/Disassembler.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/RegisterValue.h" +#include "gtest/gtest.h" + +#include "Plugins/Instruction/LoongArch/EmulateInstructionLoongArch.h" +#include "Plugins/Process/Utility/RegisterInfoPOSIX_loongarch64.h" +#include "Plugins/Process/Utility/lldb-loongarch-register-enums.h" + +using namespace llvm; +using namespace lldb; +using namespace lldb_private; + +#define GEN_BCOND_TEST(bit, name, rj_val, rd_val_branched, rd_val_continued) \ + TEST_F(LoongArch##bit##EmulatorTester, test##name##branched) { \ + testBcondBranch(this, name, true, rj_val, rd_val_branched); \ + } \ + TEST_F(LoongArch##bit##EmulatorTester, test##name##continued) { \ + testBcondBranch(this, name, false, rj_val, rd_val_continued); \ + } + +#define GEN_BZCOND_TEST(bit, name, rj_val_branched, rj_val_continued) \ + TEST_F(LoongArch##bit##EmulatorTester, test##name##branched) { \ + testBZcondBranch(this, name, true, rj_val_branched); \ + } \ + TEST_F(LoongArch##bit##EmulatorTester, test##name##continued) { \ + testBZcondBranch(this, name, false, rj_val_continued); \ + } + +struct LoongArch64EmulatorTester : public EmulateInstructionLoongArch, + testing::Test { + RegisterInfoPOSIX_loongarch64::GPR gpr; + RegisterInfoPOSIX_loongarch64::FPR fpr; + + LoongArch64EmulatorTester( + std::string triple = "loongarch64-unknown-linux-gnu") + : EmulateInstructionLoongArch(ArchSpec(triple)) { + EmulateInstruction::SetReadRegCallback(ReadRegisterCallback); + EmulateInstruction::SetWriteRegCallback(WriteRegisterCallback); + } + + static bool ReadRegisterCallback(EmulateInstruction *instruction, void *baton, + const RegisterInfo *reg_info, + RegisterValue ®_value) { + LoongArch64EmulatorTester *tester = + (LoongArch64EmulatorTester *)instruction; + uint32_t reg = reg_info->kinds[eRegisterKindLLDB]; + if (reg >= gpr_r0_loongarch && reg <= gpr_r31_loongarch) + reg_value.SetUInt(tester->gpr.gpr[reg], reg_info->byte_size); + else if (reg == gpr_orig_a0_loongarch) + reg_value.SetUInt(tester->gpr.orig_a0, reg_info->byte_size); + else if (reg == gpr_pc_loongarch) + reg_value.SetUInt(tester->gpr.csr_era, reg_info->byte_size); + else if (reg == gpr_badv_loongarch) + reg_value.SetUInt(tester->gpr.csr_badv, reg_info->byte_size); + else if (reg == fpr_first_loongarch + 32) + // fcc0 + reg_value.SetUInt(tester->fpr.fcc, reg_info->byte_size); + return true; + } + + static bool WriteRegisterCallback(EmulateInstruction *instruction, + void *baton, const Context &context, + const RegisterInfo *reg_info, + const RegisterValue ®_value) { + LoongArch64EmulatorTester *tester = + (LoongArch64EmulatorTester *)instruction; + uint32_t reg = reg_info->kinds[eRegisterKindLLDB]; + if (reg >= gpr_r0_loongarch && reg <= gpr_r31_loongarch) + tester->gpr.gpr[reg] = reg_value.GetAsUInt64(); + else if (reg == gpr_orig_a0_loongarch) + tester->gpr.orig_a0 = reg_value.GetAsUInt64(); + else if (reg == gpr_pc_loongarch) + tester->gpr.csr_era = reg_value.GetAsUInt64(); + else if (reg == gpr_badv_loongarch) + tester->gpr.csr_badv = reg_value.GetAsUInt64(); + return true; + } +}; + +// BEQ BNE BLT BGE BLTU BGEU +static uint32_t EncodeBcondType(uint32_t opcode, uint32_t rj, uint32_t rd, + uint32_t offs16) { + offs16 = offs16 & 0x0000ffff; + return opcode << 26 | offs16 << 10 | rj << 5 | rd; +} + +static uint32_t BEQ(uint32_t rj, uint32_t rd, int32_t offs16) { + return EncodeBcondType(0b010110, rj, rd, uint32_t(offs16)); +} + +static uint32_t BNE(uint32_t rj, uint32_t rd, int32_t offs16) { + return EncodeBcondType(0b010111, rj, rd, uint32_t(offs16)); +} + +static uint32_t BLT(uint32_t rj, uint32_t rd, int32_t offs16) { + return EncodeBcondType(0b011000, rj, rd, uint32_t(offs16)); +} + +static uint32_t BGE(uint32_t rj, uint32_t rd, int32_t offs16) { + return EncodeBcondType(0b011001, rj, rd, uint32_t(offs16)); +} + +static uint32_t BLTU(uint32_t rj, uint32_t rd, int32_t offs16) { + return EncodeBcondType(0b011010, rj, rd, uint32_t(offs16)); +} + +static uint32_t BGEU(uint32_t rj, uint32_t rd, int32_t offs16) { + return EncodeBcondType(0b011011, rj, rd, uint32_t(offs16)); +} + +// BEQZ BNEZ +static uint32_t EncodeBZcondType(uint32_t opcode, uint32_t rj, + uint32_t offs21) { + uint32_t offs20_16 = (offs21 & 0x001f0000) >> 16; + uint32_t offs15_0 = offs21 & 0x0000ffff; + return opcode << 26 | offs15_0 << 10 | rj << 5 | offs20_16; +} + +static uint32_t BEQZ(uint32_t rj, int32_t offs21) { + return EncodeBZcondType(0b010000, rj, uint32_t(offs21)); +} + +static uint32_t BNEZ(uint32_t rj, int32_t offs21) { + return EncodeBZcondType(0b010001, rj, uint32_t(offs21)); +} + +using EncoderBcond = uint32_t (*)(uint32_t rj, uint32_t rd, int32_t offs16); +using EncoderBZcond = uint32_t (*)(uint32_t rj, int32_t offs21); + +TEST_F(LoongArch64EmulatorTester, testJIRL) { + bool success = false; + addr_t old_pc = 0x12000600; + WritePC(old_pc); + // JIRL r1, r12, 0x10 + // | 31 26 | 25 15 | 9 5 | 4 0 | + // | 0 1 0 0 1 1 | 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 | 0 1 1 0 0 | 0 0 0 0 1 | + uint32_t inst = 0b01001100000000000100000110000001; + uint32_t offs16 = 0x10; + gpr.gpr[12] = 0x12000400; + ASSERT_TRUE(TestExecute(inst)); + auto r1 = gpr.gpr[1]; + auto pc = ReadPC(&success); + ASSERT_TRUE(success); + ASSERT_EQ(r1, old_pc + 4); + ASSERT_EQ(pc, gpr.gpr[12] + (offs16 * 4)); +} + +TEST_F(LoongArch64EmulatorTester, testB) { + bool success = false; + addr_t old_pc = 0x12000600; + WritePC(old_pc); + // B 0x10010 + // | 31 26 | 25 10 | 9 0 | + // | 0 1 0 1 0 0 | 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 | 0 0 0 0 0 0 0 0 0 1 | + uint32_t inst = 0b01010000000000000100000000000001; + uint32_t offs26 = 0x10010; + ASSERT_TRUE(TestExecute(inst)); + auto pc = ReadPC(&success); + ASSERT_TRUE(success); + ASSERT_EQ(pc, old_pc + (offs26 * 4)); +} + +TEST_F(LoongArch64EmulatorTester, testBL) { + bool success = false; + addr_t old_pc = 0x12000600; + WritePC(old_pc); + // BL 0x10010 + // | 31 26 | 25 10 | 9 0 | + // | 0 1 0 1 0 1 | 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 | 0 0 0 0 0 0 0 0 0 1 | + uint32_t inst = 0b01010100000000000100000000000001; + uint32_t offs26 = 0x10010; + ASSERT_TRUE(TestExecute(inst)); + auto r1 = gpr.gpr[1]; + auto pc = ReadPC(&success); + ASSERT_TRUE(success); + ASSERT_EQ(r1, old_pc + 4); + ASSERT_EQ(pc, old_pc + (offs26 * 4)); +} + +static void testBcondBranch(LoongArch64EmulatorTester *tester, + EncoderBcond encoder, bool branched, + uint64_t rj_val, uint64_t rd_val) { + bool success = false; + addr_t old_pc = 0x12000600; + tester->WritePC(old_pc); + tester->gpr.gpr[12] = rj_val; + tester->gpr.gpr[13] = rd_val; + // b r12, r13, (-256) + uint32_t inst = encoder(12, 13, -256); + ASSERT_TRUE(tester->TestExecute(inst)); + auto pc = tester->ReadPC(&success); + ASSERT_TRUE(success); + ASSERT_EQ(pc, old_pc + (branched ? (-256 * 4) : 4)); +} + +static void testBZcondBranch(LoongArch64EmulatorTester *tester, + EncoderBZcond encoder, bool branched, + uint64_t rj_val) { + bool success = false; + addr_t old_pc = 0x12000600; + tester->WritePC(old_pc); + tester->gpr.gpr[4] = rj_val; + // bz r4, (-256) + uint32_t inst = encoder(4, -256); + ASSERT_TRUE(tester->TestExecute(inst)); + auto pc = tester->ReadPC(&success); + ASSERT_TRUE(success); + ASSERT_EQ(pc, old_pc + (branched ? (-256 * 4) : 4)); +} + +GEN_BCOND_TEST(64, BEQ, 1, 1, 0) +GEN_BCOND_TEST(64, BNE, 1, 0, 1) +GEN_BCOND_TEST(64, BLT, -2, 1, -3) +GEN_BCOND_TEST(64, BGE, -2, -3, 1) +GEN_BCOND_TEST(64, BLTU, -2, -1, 1) +GEN_BCOND_TEST(64, BGEU, -2, 1, -1) +GEN_BZCOND_TEST(64, BEQZ, 0, 1) +GEN_BZCOND_TEST(64, BNEZ, 1, 0)