/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "disassembler_riscv64.h" #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "base/bit_utils.h" #include "base/casts.h" using android::base::StringPrintf; namespace art { namespace riscv64 { class DisassemblerRiscv64::Printer { public: Printer(DisassemblerRiscv64* disassembler, std::ostream& os) : disassembler_(disassembler), os_(os) {} void Dump32(const uint8_t* insn); void Dump16(const uint8_t* insn); void Dump2Byte(const uint8_t* data); void DumpByte(const uint8_t* data); private: // This enumeration should mirror the declarations in runtime/arch/riscv64/registers_riscv64.h. // We do not include that file to avoid a dependency on libart. enum { Zero = 0, RA = 1, FP = 8, TR = 9, }; enum class MemAddressMode : uint32_t { kUnitStride = 0b00, kIndexedUnordered = 0b01, kStrided = 0b10, kIndexedOrdered = 0b11, }; enum class Nf : uint32_t { k1 = 0b000, k2 = 0b001, k3 = 0b010, k4 = 0b011, k5 = 0b100, k6 = 0b101, k7 = 0b110, k8 = 0b111, }; enum class VAIEncodings : uint32_t { kOpIVV = 0b000, kOpFVV = 0b001, kOpMVV = 0b010, kOpIVI = 0b011, kOpIVX = 0b100, kOpFVF = 0b101, kOpMVX = 0b110, kOpCFG = 0b111, }; class ScopedNewLinePrinter { std::ostream& os_; public: explicit ScopedNewLinePrinter(std::ostream& os) : os_(os) {} ~ScopedNewLinePrinter() { os_ << '\n'; } }; static const char* XRegName(uint32_t regno); static const char* FRegName(uint32_t regno); static const char* VRegName(uint32_t regno); static const char* RoundingModeName(uint32_t rm); // Regular instruction immediate utils static int32_t Decode32Imm12(uint32_t insn32) { uint32_t sign = (insn32 >> 31); uint32_t imm12 = (insn32 >> 20); return static_cast(imm12) - static_cast(sign << 12); // Sign-extend. } static uint32_t Decode32UImm7(uint32_t insn32) { return (insn32 >> 25) & 0x7Fu; } static uint32_t Decode32UImm12(uint32_t insn32) { return (insn32 >> 20) & 0xFFFu; } static int32_t Decode32StoreOffset(uint32_t insn32) { uint32_t bit11 = insn32 >> 31; uint32_t bits5_11 = insn32 >> 25; uint32_t bits0_4 = (insn32 >> 7) & 0x1fu; uint32_t imm = (bits5_11 << 5) + bits0_4; return static_cast(imm) - static_cast(bit11 << 12); // Sign-extend. } // Compressed instruction immediate utils // Extracts the offset from a compressed instruction // where `offset[5:3]` is in bits `[12:10]` and `offset[2|6]` is in bits `[6:5]` static uint32_t Decode16CMOffsetW(uint32_t insn16) { DCHECK(IsUint<16>(insn16)); return BitFieldExtract(insn16, 5, 1) << 6 | BitFieldExtract(insn16, 10, 3) << 3 | BitFieldExtract(insn16, 6, 1) << 2; } // Extracts the offset from a compressed instruction // where `offset[5:3]` is in bits `[12:10]` and `offset[7:6]` is in bits `[6:5]` static uint32_t Decode16CMOffsetD(uint32_t insn16) { DCHECK(IsUint<16>(insn16)); return BitFieldExtract(insn16, 5, 2) << 6 | BitFieldExtract(insn16, 10, 3) << 3; } // Re-orders raw immediatate into real value // where `imm[5:3]` is in bits `[5:3]` and `imm[8:6]` is in bits `[2:0]` static uint32_t Uimm6ToOffsetD16(uint32_t uimm6) { DCHECK(IsUint<6>(uimm6)); return (BitFieldExtract(uimm6, 3, 3) << 3) | (BitFieldExtract(uimm6, 0, 3) << 6); } // Re-orders raw immediatate to form real value // where `imm[5:2]` is in bits `[5:2]` and `imm[7:6]` is in bits `[1:0]` static uint32_t Uimm6ToOffsetW16(uint32_t uimm6) { DCHECK(IsUint<6>(uimm6)); return (BitFieldExtract(uimm6, 2, 4) << 2) | (BitFieldExtract(uimm6, 0, 2) << 6); } // Re-orders raw immediatate to form real value // where `imm[1]` is in bit `[0]` and `imm[0]` is in bit `[1]` static uint32_t Uimm2ToOffset10(uint32_t uimm2) { DCHECK(IsUint<2>(uimm2)); return (uimm2 >> 1) | (uimm2 & 0x1u) << 1; } // Re-orders raw immediatate to form real value // where `imm[1]` is in bit `[0]` and `imm[0]` is `0` static uint32_t Uimm2ToOffset1(uint32_t uimm2) { DCHECK(IsUint<2>(uimm2)); return (uimm2 & 0x1u) << 1; } template static constexpr int32_t SignExtendBits(uint32_t bits) { static_assert(kWidth < BitSizeOf()); const uint32_t sign_bit = (bits >> kWidth) & 1u; return static_cast(bits) - static_cast(sign_bit << kWidth); } // Extracts the immediate from a compressed instruction // where `imm[5]` is in bit `[12]` and `imm[4:0]` is in bits `[6:2]` // and performs sign-extension if required template static T Decode16Imm6(uint32_t insn16) { DCHECK(IsUint<16>(insn16)); static_assert(std::is_integral_v, "T must be integral"); const T bits = BitFieldInsert(BitFieldExtract(insn16, 2, 5), BitFieldExtract(insn16, 12, 1), 5, 1); const T checked_bits = dchecked_integral_cast(bits); if (std::is_unsigned_v) { return checked_bits; } return SignExtendBits<6>(checked_bits); } // Regular instruction register utils static uint32_t GetRd(uint32_t insn32) { return (insn32 >> 7) & 0x1fu; } static uint32_t GetRs1(uint32_t insn32) { return (insn32 >> 15) & 0x1fu; } static uint32_t GetRs2(uint32_t insn32) { return (insn32 >> 20) & 0x1fu; } static uint32_t GetRs3(uint32_t insn32) { return insn32 >> 27; } static uint32_t GetRoundingMode(uint32_t insn32) { return (insn32 >> 12) & 7u; } // Compressed instruction register utils static uint32_t GetRs1Short16(uint32_t insn16) { return BitFieldExtract(insn16, 7, 3) + 8u; } static uint32_t GetRs2Short16(uint32_t insn16) { return BitFieldExtract(insn16, 2, 3) + 8u; } static uint32_t GetRs1_16(uint32_t insn16) { return BitFieldExtract(insn16, 7, 5); } static uint32_t GetRs2_16(uint32_t insn16) { return BitFieldExtract(insn16, 2, 5); } void PrintBranchOffset(int32_t offset); void PrintLoadStoreAddress(uint32_t rs1, int32_t offset); void Print32Lui(uint32_t insn32); void Print32Auipc(const uint8_t* insn, uint32_t insn32); void Print32Jal(const uint8_t* insn, uint32_t insn32); void Print32Jalr(const uint8_t* insn, uint32_t insn32); void Print32BCond(const uint8_t* insn, uint32_t insn32); void Print32Load(uint32_t insn32); void Print32Store(uint32_t insn32); void Print32FLoad(uint32_t insn32); void Print32FStore(uint32_t insn32); void Print32BinOpImm(uint32_t insn32); void Print32BinOp(uint32_t insn32); void Print32Atomic(uint32_t insn32); void Print32FpOp(uint32_t insn32); void Print32RVVOp(uint32_t insn32); void Print32FpFma(uint32_t insn32); void Print32Zicsr(uint32_t insn32); void Print32Fence(uint32_t insn32); void AppendVType(uint32_t zimm); static const char* DecodeRVVMemMnemonic(const uint32_t insn32, bool is_load, /*out*/ const char** rs2); DisassemblerRiscv64* const disassembler_; std::ostream& os_; }; const char* DisassemblerRiscv64::Printer::XRegName(uint32_t regno) { static const char* const kXRegisterNames[] = { "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "fp", // s0/fp "tr", // s1/tr - ART thread register "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6", }; static_assert(std::size(kXRegisterNames) == 32); DCHECK_LT(regno, 32u); return kXRegisterNames[regno]; } const char* DisassemblerRiscv64::Printer::FRegName(uint32_t regno) { static const char* const kFRegisterNames[] = { "ft0", "ft1", "ft2", "ft3", "ft4", "ft5", "ft6", "ft7", "fs0", "fs1", "fa0", "fa1", "fa2", "fa3", "fa4", "fa5", "fa6", "fa7", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", "fs10", "fs11", "ft8", "ft9", "ft10", "ft11", }; static_assert(std::size(kFRegisterNames) == 32); DCHECK_LT(regno, 32u); return kFRegisterNames[regno]; } const char* DisassemblerRiscv64::Printer::VRegName(uint32_t regno) { static const char* const kVRegisterNames[] = { "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11", "V12", "V13", "V14", "V15", "V16", "V17", "V18", "V19", "V20", "V21", "V22", "V23", "V24", "V25", "V26", "V27", "V28", "V29", "V30", "V31", }; static_assert(std::size(kVRegisterNames) == 32); DCHECK_LT(regno, 32u); return kVRegisterNames[regno]; } const char* DisassemblerRiscv64::Printer::RoundingModeName(uint32_t rm) { // Note: We do not print the rounding mode for DYN. static const char* const kRoundingModeNames[] = { ".rne", ".rtz", ".rdn", ".rup", ".rmm", ".", ".", /*DYN*/ "" }; static_assert(std::size(kRoundingModeNames) == 8); DCHECK_LT(rm, 8u); return kRoundingModeNames[rm]; } void DisassemblerRiscv64::Printer::PrintBranchOffset(int32_t offset) { os_ << (offset >= 0 ? "+" : "") << offset; } void DisassemblerRiscv64::Printer::PrintLoadStoreAddress(uint32_t rs1, int32_t offset) { if (offset != 0) { os_ << StringPrintf("%d", offset); } os_ << "(" << XRegName(rs1) << ")"; if (rs1 == TR && offset >= 0) { // Add entrypoint name. os_ << " ; "; disassembler_->GetDisassemblerOptions()->thread_offset_name_function_( os_, dchecked_integral_cast(offset)); } } void DisassemblerRiscv64::Printer::Print32Lui(uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x37u); // TODO(riscv64): Should we also print the actual sign-extend value? os_ << StringPrintf("lui %s, %u", XRegName(GetRd(insn32)), insn32 >> 12); } void DisassemblerRiscv64::Printer::Print32Auipc([[maybe_unused]] const uint8_t* insn, uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x17u); // TODO(riscv64): Should we also print the calculated address? os_ << StringPrintf("auipc %s, %u", XRegName(GetRd(insn32)), insn32 >> 12); } void DisassemblerRiscv64::Printer::Print32Jal(const uint8_t* insn, uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x6fu); // Print an alias if available. uint32_t rd = GetRd(insn32); os_ << (rd == Zero ? "j " : "jal "); if (rd != Zero && rd != RA) { os_ << XRegName(rd) << ", "; } uint32_t bit20 = (insn32 >> 31); uint32_t bits1_10 = (insn32 >> 21) & 0x3ffu; uint32_t bit11 = (insn32 >> 20) & 1u; uint32_t bits12_19 = (insn32 >> 12) & 0xffu; uint32_t imm = (bits1_10 << 1) + (bit11 << 11) + (bits12_19 << 12) + (bit20 << 20); int32_t offset = static_cast(imm) - static_cast(bit20 << 21); // Sign-extend. PrintBranchOffset(offset); os_ << " ; " << disassembler_->FormatInstructionPointer(insn + offset); // TODO(riscv64): When we implement shared thunks to reduce AOT slow-path code size, // check if this JAL lands at an entrypoint load from TR and, if so, print its name. } void DisassemblerRiscv64::Printer::Print32Jalr([[maybe_unused]] const uint8_t* insn, uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x67u); DCHECK_EQ((insn32 >> 12) & 7u, 0u); uint32_t rd = GetRd(insn32); uint32_t rs1 = GetRs1(insn32); int32_t imm12 = Decode32Imm12(insn32); // Print shorter macro instruction notation if available. if (rd == Zero && rs1 == RA && imm12 == 0) { os_ << "ret"; } else if (rd == Zero && imm12 == 0) { os_ << "jr " << XRegName(rs1); } else if (rd == RA && imm12 == 0) { os_ << "jalr " << XRegName(rs1); } else { // TODO(riscv64): Should we also print the calculated address if the preceding // instruction is AUIPC? (We would need to record the previous instruction.) os_ << "jalr " << XRegName(rd) << ", "; // Use the same format as llvm-objdump: "rs1" if `imm12` is zero, otherwise "imm12(rs1)". if (imm12 == 0) { os_ << XRegName(rs1); } else { os_ << imm12 << "(" << XRegName(rs1) << ")"; } } } void DisassemblerRiscv64::Printer::Print32BCond(const uint8_t* insn, uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x63u); static const char* const kOpcodes[] = { "beq", "bne", nullptr, nullptr, "blt", "bge", "bltu", "bgeu" }; uint32_t funct3 = (insn32 >> 12) & 7u; const char* opcode = kOpcodes[funct3]; if (opcode == nullptr) { os_ << ""; return; } // Print shorter macro instruction notation if available. uint32_t rs1 = GetRs1(insn32); uint32_t rs2 = GetRs2(insn32); if (rs2 == Zero) { os_ << opcode << "z " << XRegName(rs1); } else if (rs1 == Zero && (funct3 == 4u || funct3 == 5u)) { // blt zero, rs2, offset ... bgtz rs2, offset // bge zero, rs2, offset ... blez rs2, offset os_ << (funct3 == 4u ? "bgtz " : "blez ") << XRegName(rs2); } else { os_ << opcode << " " << XRegName(rs1) << ", " << XRegName(rs2); } os_ << ", "; uint32_t bit12 = insn32 >> 31; uint32_t bits5_10 = (insn32 >> 25) & 0x3fu; uint32_t bits1_4 = (insn32 >> 8) & 0xfu; uint32_t bit11 = (insn32 >> 7) & 1u; uint32_t imm = (bit12 << 12) + (bit11 << 11) + (bits5_10 << 5) + (bits1_4 << 1); int32_t offset = static_cast(imm) - static_cast(bit12 << 13); // Sign-extend. PrintBranchOffset(offset); os_ << " ; " << disassembler_->FormatInstructionPointer(insn + offset); } void DisassemblerRiscv64::Printer::Print32Load(uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x03u); static const char* const kOpcodes[] = { "lb", "lh", "lw", "ld", "lbu", "lhu", "lwu", nullptr }; uint32_t funct3 = (insn32 >> 12) & 7u; const char* opcode = kOpcodes[funct3]; if (opcode == nullptr) { os_ << ""; return; } os_ << opcode << " " << XRegName(GetRd(insn32)) << ", "; PrintLoadStoreAddress(GetRs1(insn32), Decode32Imm12(insn32)); // TODO(riscv64): If previous instruction is AUIPC for current `rs1` and we load // from the range specified by assembler options, print the loaded literal. } void DisassemblerRiscv64::Printer::Print32Store(uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x23u); static const char* const kOpcodes[] = { "sb", "sh", "sw", "sd", nullptr, nullptr, nullptr, nullptr }; uint32_t funct3 = (insn32 >> 12) & 7u; const char* opcode = kOpcodes[funct3]; if (opcode == nullptr) { os_ << ""; return; } os_ << opcode << " " << XRegName(GetRs2(insn32)) << ", "; PrintLoadStoreAddress(GetRs1(insn32), Decode32StoreOffset(insn32)); } const char* DisassemblerRiscv64::Printer::DecodeRVVMemMnemonic(const uint32_t insn32, bool is_load, /*out*/ const char** rs2) { const uint32_t width_index = (insn32 >> 12) & 3u; DCHECK_EQ(width_index != 0u, (insn32 & 0x4000u) != 0u); const uint32_t imm7 = Decode32UImm7(insn32); const enum Nf nf = static_cast((imm7 >> 4) & 0x7u); const enum MemAddressMode mop = static_cast((imm7 >> 1) & 0x3u); const uint32_t mew = (insn32 >> 28) & 1u; if (mew == 1u) { // 7.3. Vector Load/Store Width Encoding // The mew bit (inst[28]) when set is expected to be used to encode // expanded memory sizes of 128 bits and above, // but these encodings are currently reserved. return nullptr; } switch (mop) { case MemAddressMode::kUnitStride: { const uint32_t umop = GetRs2(insn32); switch (umop) { case 0b00000: // Vector Unit-Stride Load/Store static constexpr const char* kVUSMnemonics[8][4] = { {"e8.v", "e16.v", "e32.v", "e64.v"}, {"seg2e8.v", "seg2e16.v", "seg2e32.v", "seg2e64.v"}, {"seg3e8.v", "seg3e16.v", "seg3e32.v", "seg3e64.v"}, {"seg4e8.v", "seg4e16.v", "seg4e32.v", "seg4e64.v"}, {"seg5e8.v", "seg5e16.v", "seg5e32.v", "seg5e64.v"}, {"seg6e8.v", "seg6e16.v", "seg6e32.v", "seg6e64.v"}, {"seg7e8.v", "seg7e16.v", "seg7e32.v", "seg7e64.v"}, {"seg8e8.v", "seg8e16.v", "seg8e32.v", "seg8e64.v"}, }; return kVUSMnemonics[enum_cast(nf)][width_index]; case 0b01000: { // Vector Whole Register Load/Store if (is_load) { static constexpr const char* kVWRLMnemonics[8][4] = { {"1re8.v", "1re16.v", "1re32.v", "1re64.v"}, {"2re8.v", "2re16.v", "2re32.v", "2re64.v"}, {nullptr, nullptr, nullptr, nullptr}, {"4re8.v", "4re16.v", "4re32.v", "4re64.v"}, {nullptr, nullptr, nullptr, nullptr}, {nullptr, nullptr, nullptr, nullptr}, {nullptr, nullptr, nullptr, nullptr}, {"8re8.v", "8re16.v", "8re32.v", "8re64.v"}, }; return kVWRLMnemonics[enum_cast(nf)][width_index]; } else { if (width_index != 0) { return nullptr; } static constexpr const char* kVWRSMnemonics[8] = { "1r", "2r", nullptr, "4r", nullptr, nullptr, nullptr, "8r" }; return kVWRSMnemonics[enum_cast(nf)]; } } case 0b01011: // Vector Unit-Stride Mask Load/Store if (nf == Nf::k1 && width_index == 0 && (imm7 & 1u) == 1u) { return "m.v"; } else { return nullptr; } case 0b10000: // Vector Unit-Stride Fault-Only-First Load static constexpr const char* kVUSFFLMnemonics[8][4] = { {"e8ff.v", "e16ff.v", "e32ff.v", "e64ff.v"}, {"seg2e8ff.v", "seg2e16ff.v", "seg2e32ff.v", "seg2e64ff.v"}, {"seg3e8ff.v", "seg3e16ff.v", "seg3e32ff.v", "seg3e64ff.v"}, {"seg4e8ff.v", "seg4e16ff.v", "seg4e32ff.v", "seg4e64ff.v"}, {"seg5e8ff.v", "seg5e16ff.v", "seg5e32ff.v", "seg5e64ff.v"}, {"seg6e8ff.v", "seg6e16ff.v", "seg6e32ff.v", "seg6e64ff.v"}, {"seg7e8ff.v", "seg7e16ff.v", "seg7e32ff.v", "seg7e64ff.v"}, {"seg8e8ff.v", "seg8e16ff.v", "seg8e32ff.v", "seg8e64ff.v"}, }; return is_load ? kVUSFFLMnemonics[enum_cast(nf)][width_index] : nullptr; default: // Unknown return nullptr; } } case MemAddressMode::kIndexedUnordered: { static constexpr const char* kVIUMnemonics[8][4] = { {"uxei8.v", "uxei16.v", "uxei32.v", "uxei64.v"}, {"uxseg2ei8.v", "uxseg2ei16.v", "uxseg2ei32.v", "uxseg2ei64.v"}, {"uxseg3ei8.v", "uxseg3ei16.v", "uxseg3ei32.v", "uxseg3ei64.v"}, {"uxseg4ei8.v", "uxseg4ei16.v", "uxseg4ei32.v", "uxseg4ei64.v"}, {"uxseg5ei8.v", "uxseg5ei16.v", "uxseg5ei32.v", "uxseg5ei64.v"}, {"uxseg6ei8.v", "uxseg6ei16.v", "uxseg6ei32.v", "uxseg6ei64.v"}, {"uxseg7ei8.v", "uxseg7ei16.v", "uxseg7ei32.v", "uxseg7ei64.v"}, {"uxseg8ei8.v", "uxseg8ei16.v", "uxseg8ei32.v", "uxseg8ei64.v"}, }; *rs2 = VRegName(GetRs2(insn32)); return kVIUMnemonics[enum_cast(nf)][width_index]; } case MemAddressMode::kStrided: { static constexpr const char* kVSMnemonics[8][4] = { {"se8.v", "se16.v", "se32.v", "se64.v"}, {"sseg2e8.v", "sseg2e16.v", "sseg2e32.v", "sseg2e64.v"}, {"sseg3e8.v", "sseg3e16.v", "sseg3e32.v", "sseg3e64.v"}, {"sseg4e8.v", "sseg4e16.v", "sseg4e32.v", "sseg4e64.v"}, {"sseg5e8.v", "sseg5e16.v", "sseg5e32.v", "sseg5e64.v"}, {"sseg6e8.v", "sseg6e16.v", "sseg6e32.v", "sseg6e64.v"}, {"sseg7e8.v", "sseg7e16.v", "sseg7e32.v", "sseg7e64.v"}, {"sseg8e8.v", "sseg8e16.v", "sseg8e32.v", "sseg8e64.v"}, }; *rs2 = XRegName(GetRs2(insn32)); return kVSMnemonics[enum_cast(nf)][width_index]; } case MemAddressMode::kIndexedOrdered: { static constexpr const char* kVIOMnemonics[8][4] = { {"oxei8.v", "oxei16.v", "oxei32.v", "oxei64.v"}, {"oxseg2ei8.v", "oxseg2ei16.v", "oxseg2ei32.v", "oxseg2ei64.v"}, {"oxseg3ei8.v", "oxseg3ei16.v", "oxseg3ei32.v", "oxseg3ei64.v"}, {"oxseg4ei8.v", "oxseg4ei16.v", "oxseg4ei32.v", "oxseg4ei64.v"}, {"oxseg5ei8.v", "oxseg5ei16.v", "oxseg5ei32.v", "oxseg5ei64.v"}, {"oxseg6ei8.v", "oxseg6ei16.v", "oxseg6ei32.v", "oxseg6ei64.v"}, {"oxseg7ei8.v", "oxseg7ei16.v", "oxseg7ei32.v", "oxseg7ei64.v"}, {"oxseg8ei8.v", "oxseg8ei16.v", "oxseg8ei32.v", "oxseg8ei64.v"}, }; *rs2 = VRegName(GetRs2(insn32)); return kVIOMnemonics[enum_cast(nf)][width_index]; } } } static constexpr const char* kFpMemMnemonics[] = { nullptr, "h", "w", "d", "q", nullptr, nullptr, nullptr }; void DisassemblerRiscv64::Printer::Print32FLoad(uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x07u); int32_t offset = 0; const char* rd = nullptr; const char* rs2 = nullptr; const char* vm = ""; const uint32_t funct3 = (insn32 >> 12) & 7u; const char* mnemonic = kFpMemMnemonics[funct3]; const char* prefix = "f"; if (mnemonic == nullptr) { // Vector Loads prefix = "v"; mnemonic = DecodeRVVMemMnemonic(insn32, /*is_load=*/true, &rs2); rd = VRegName(GetRd(insn32)); if ((Decode32UImm7(insn32) & 0x1U) == 0) { vm = ", v0.t"; } } else { rd = FRegName(GetRd(insn32)); offset = Decode32Imm12(insn32); } if (mnemonic == nullptr) { os_ << ""; return; } os_ << prefix << "l" << mnemonic << " " << rd << ", "; PrintLoadStoreAddress(GetRs1(insn32), offset); if (rs2) { os_ << ", " << rs2; } os_ << vm; // TODO(riscv64): If previous instruction is AUIPC for current `rs1` and we load // from the range specified by assembler options, print the loaded literal. } void DisassemblerRiscv64::Printer::Print32FStore(uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x27u); uint32_t funct3 = (insn32 >> 12) & 3u; const char* prefix = "f"; const char* mnemonic = kFpMemMnemonics[funct3]; if (mnemonic == nullptr) { // Vector Stores const char* rs2 = nullptr; prefix = "v"; mnemonic = DecodeRVVMemMnemonic(insn32, /*is_load=*/false, &rs2); if (mnemonic == nullptr) { os_ << ""; return; } os_ << prefix << "s" << mnemonic << " " << VRegName(GetRd(insn32)) << ", "; PrintLoadStoreAddress(GetRs1(insn32), 0); if (rs2) { os_ << ", " << rs2; } if ((Decode32UImm7(insn32) & 0x1U) == 0) { os_ << ", v0.t"; } } else { os_ << prefix << "s" << mnemonic << " " << FRegName(GetRs2(insn32)) << ", "; PrintLoadStoreAddress(GetRs1(insn32), Decode32StoreOffset(insn32)); } } void DisassemblerRiscv64::Printer::Print32BinOpImm(uint32_t insn32) { DCHECK_EQ(insn32 & 0x77u, 0x13u); // Note: Bit 0x8 selects narrow binop. bool narrow = (insn32 & 0x8u) != 0u; uint32_t funct3 = (insn32 >> 12) & 7u; uint32_t rd = GetRd(insn32); uint32_t rs1 = GetRs1(insn32); int32_t imm = Decode32Imm12(insn32); // Print shorter macro instruction notation if available. if (funct3 == /*ADDI*/ 0u && imm == 0u) { if (narrow) { os_ << "sextw " << XRegName(rd) << ", " << XRegName(rs1); } else if (rd == Zero && rs1 == Zero) { os_ << "nop"; // Only canonical nop. Non-Zero `rd == rs1` nops are printed as "mv". } else { os_ << "mv " << XRegName(rd) << ", " << XRegName(rs1); } } else if (!narrow && funct3 == /*XORI*/ 4u && imm == -1) { os_ << "not " << XRegName(rd) << ", " << XRegName(rs1); } else if (!narrow && funct3 == /*ANDI*/ 7u && imm == 0xff) { os_ << "zextb " << XRegName(rd) << ", " << XRegName(rs1); } else if (!narrow && funct3 == /*SLTIU*/ 3u && imm == 1) { os_ << "seqz " << XRegName(rd) << ", " << XRegName(rs1); } else if ((insn32 & 0xfc00707fu) == 0x0800101bu) { os_ << "slli.uw " << XRegName(rd) << ", " << XRegName(rs1) << ", " << (imm & 0x3fu); } else if ((imm ^ 0x600u) < 3u && funct3 == 1u) { static const char* const kBitOpcodes[] = { "clz", "ctz", "cpop" }; os_ << kBitOpcodes[imm ^ 0x600u] << (narrow ? "w " : " ") << XRegName(rd) << ", " << XRegName(rs1); } else if ((imm ^ 0x600u) < (narrow ? 32 : 64) && funct3 == 5u) { os_ << "rori" << (narrow ? "w " : " ") << XRegName(rd) << ", " << XRegName(rs1) << ", " << (imm ^ 0x600u); } else if (imm == 0x287u && !narrow && funct3 == 5u) { os_ << "orc.b " << XRegName(rd) << ", " << XRegName(rs1); } else if (imm == 0x6b8u && !narrow && funct3 == 5u) { os_ << "rev8 " << XRegName(rd) << ", " << XRegName(rs1); } else { bool bad_high_bits = false; if (funct3 == /*SLLI*/ 1u || funct3 == /*SRLI/SRAI*/ 5u) { imm &= (narrow ? 0x1fu : 0x3fu); uint32_t high_bits = insn32 & (narrow ? 0xfe000000u : 0xfc000000u); if (high_bits == 0x40000000u && funct3 == /*SRAI*/ 5u) { os_ << "srai"; } else { os_ << ((funct3 == /*SRLI*/ 5u) ? "srli" : "slli"); bad_high_bits = (high_bits != 0u); } } else if (!narrow || funct3 == /*ADDI*/ 0u) { static const char* const kOpcodes[] = { "addi", nullptr, "slti", "sltiu", "xori", nullptr, "ori", "andi" }; DCHECK(kOpcodes[funct3] != nullptr); os_ << kOpcodes[funct3]; } else { os_ << ""; // There is no SLTIW/SLTIUW/XORIW/ORIW/ANDIW. return; } os_ << (narrow ? "w " : " ") << XRegName(rd) << ", " << XRegName(rs1) << ", " << imm; if (bad_high_bits) { os_ << " (invalid high bits)"; } } } void DisassemblerRiscv64::Printer::Print32BinOp(uint32_t insn32) { DCHECK_EQ(insn32 & 0x77u, 0x33u); // Note: Bit 0x8 selects narrow binop. bool narrow = (insn32 & 0x8u) != 0u; uint32_t funct3 = (insn32 >> 12) & 7u; uint32_t rd = GetRd(insn32); uint32_t rs1 = GetRs1(insn32); uint32_t rs2 = GetRs2(insn32); uint32_t high_bits = insn32 & 0xfe000000u; // Print shorter macro instruction notation if available. if (high_bits == 0x40000000u && funct3 == /*SUB*/ 0u && rs1 == Zero) { os_ << (narrow ? "negw " : "neg ") << XRegName(rd) << ", " << XRegName(rs2); } else if (!narrow && funct3 == /*SLT*/ 2u && rs2 == Zero) { os_ << "sltz " << XRegName(rd) << ", " << XRegName(rs1); } else if (!narrow && funct3 == /*SLT*/ 2u && rs1 == Zero) { os_ << "sgtz " << XRegName(rd) << ", " << XRegName(rs2); } else if (!narrow && funct3 == /*SLTU*/ 3u && rs1 == Zero) { os_ << "snez " << XRegName(rd) << ", " << XRegName(rs2); } else if (narrow && high_bits == 0x08000000u && funct3 == /*ADD.UW*/ 0u && rs2 == Zero) { os_ << "zext.w " << XRegName(rd) << ", " << XRegName(rs1); } else { bool bad_high_bits = false; if (high_bits == 0x40000000u && (funct3 == /*SUB*/ 0u || funct3 == /*SRA*/ 5u)) { os_ << ((funct3 == /*SUB*/ 0u) ? "sub" : "sra"); } else if (high_bits == 0x02000000u && (!narrow || (funct3 == /*MUL*/ 0u || funct3 >= /*DIV/DIVU/REM/REMU*/ 4u))) { static const char* const kOpcodes[] = { "mul", "mulh", "mulhsu", "mulhu", "div", "divu", "rem", "remu" }; os_ << kOpcodes[funct3]; } else if (high_bits == 0x08000000u && narrow && funct3 == /*ADD.UW*/ 0u) { os_ << "add.u"; // "w" is added below. } else if (high_bits == 0x20000000u && (funct3 & 1u) == 0u && funct3 != 0u) { static const char* const kZbaOpcodes[] = { nullptr, "sh1add", "sh2add", "sh3add" }; DCHECK(kZbaOpcodes[funct3 >> 1] != nullptr); os_ << kZbaOpcodes[funct3 >> 1] << (narrow ? ".u" /* "w" is added below. */ : ""); } else if (high_bits == 0x40000000u && !narrow && funct3 >= 4u && funct3 != 5u) { static const char* const kZbbNegOpcodes[] = { "xnor", nullptr, "orn", "andn" }; DCHECK(kZbbNegOpcodes[funct3 - 4u] != nullptr); os_ << kZbbNegOpcodes[funct3 - 4u]; } else if (high_bits == 0x0a000000u && !narrow && funct3 >= 4u) { static const char* const kZbbMinMaxOpcodes[] = { "min", "minu", "max", "maxu" }; DCHECK(kZbbMinMaxOpcodes[funct3 - 4u] != nullptr); os_ << kZbbMinMaxOpcodes[funct3 - 4u]; } else if (high_bits == 0x60000000u && (funct3 == /*ROL*/ 1u || funct3 == /*ROL*/ 5u)) { os_ << (funct3 == /*ROL*/ 1u ? "rol" : "ror"); } else if (!narrow || (funct3 == /*ADD*/ 0u || funct3 == /*SLL*/ 1u || funct3 == /*SRL*/ 5u)) { static const char* const kOpcodes[] = { "add", "sll", "slt", "sltu", "xor", "srl", "or", "and" }; os_ << kOpcodes[funct3]; bad_high_bits = (high_bits != 0u); } else { DCHECK(narrow); os_ << ""; // Some of the above instructions do not have a narrow version. return; } os_ << (narrow ? "w " : " ") << XRegName(rd) << ", " << XRegName(rs1) << ", " << XRegName(rs2); if (bad_high_bits) { os_ << " (invalid high bits)"; } } } void DisassemblerRiscv64::Printer::Print32Atomic(uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x2fu); uint32_t funct3 = (insn32 >> 12) & 7u; uint32_t funct5 = (insn32 >> 27); if ((funct3 != 2u && funct3 != 3u) || // There are only 32-bit and 64-bit LR/SC/AMO*. (((funct5 & 3u) != 0u) && funct5 >= 4u)) { // Only multiples of 4, or 1-3. os_ << ""; return; } static const char* const kMul4Opcodes[] = { "amoadd", "amoxor", "amoor", "amoand", "amomin", "amomax", "amominu", "amomaxu" }; static const char* const kOtherOpcodes[] = { nullptr, "amoswap", "lr", "sc" }; const char* opcode = ((funct5 & 3u) == 0u) ? kMul4Opcodes[funct5 >> 2] : kOtherOpcodes[funct5]; DCHECK(opcode != nullptr); uint32_t rd = GetRd(insn32); uint32_t rs1 = GetRs1(insn32); uint32_t rs2 = GetRs2(insn32); const char* type = (funct3 == 2u) ? ".w" : ".d"; const char* aq = (((insn32 >> 26) & 1u) != 0u) ? ".aq" : ""; const char* rl = (((insn32 >> 25) & 1u) != 0u) ? ".rl" : ""; os_ << opcode << type << aq << rl << " " << XRegName(rd) << ", " << XRegName(rs1); if (funct5 == /*LR*/ 2u) { if (rs2 != 0u) { os_ << " (bad rs2)"; } } else { os_ << ", " << XRegName(rs2); } } void DisassemblerRiscv64::Printer::Print32FpOp(uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x53u); uint32_t rd = GetRd(insn32); uint32_t rs1 = GetRs1(insn32); uint32_t rs2 = GetRs2(insn32); // Sometimes used to to differentiate opcodes. uint32_t rm = GetRoundingMode(insn32); // Sometimes used to to differentiate opcodes. uint32_t funct7 = insn32 >> 25; const char* type = ((funct7 & 1u) != 0u) ? ".d" : ".s"; if ((funct7 & 2u) != 0u) { os_ << ""; // Note: This includes the "H" and "Q" extensions. return; } switch (funct7 >> 2) { case 0u: case 1u: case 2u: case 3u: { static const char* const kOpcodes[] = { "fadd", "fsub", "fmul", "fdiv" }; os_ << kOpcodes[funct7 >> 2] << type << RoundingModeName(rm) << " " << FRegName(rd) << ", " << FRegName(rs1) << ", " << FRegName(rs2); return; } case 4u: { // FSGN* // Print shorter macro instruction notation if available. static const char* const kOpcodes[] = { "fsgnj", "fsgnjn", "fsgnjx" }; if (rm < std::size(kOpcodes)) { if (rs1 == rs2) { static const char* const kAltOpcodes[] = { "fmv", "fneg", "fabs" }; static_assert(std::size(kOpcodes) == std::size(kAltOpcodes)); os_ << kAltOpcodes[rm] << type << " " << FRegName(rd) << ", " << FRegName(rs1); } else { os_ << kOpcodes[rm] << type << " " << FRegName(rd) << ", " << FRegName(rs1) << ", " << FRegName(rs2); } return; } break; } case 5u: { // FMIN/FMAX static const char* const kOpcodes[] = { "fmin", "fmax" }; if (rm < std::size(kOpcodes)) { os_ << kOpcodes[rm] << type << " " << FRegName(rd) << ", " << FRegName(rs1) << ", " << FRegName(rs2); return; } break; } case 0x8u: // FCVT between FP numbers. if ((rs2 ^ 1u) == (funct7 & 1u)) { os_ << ((rs2 != 0u) ? "fcvt.s.d" : "fcvt.d.s") << RoundingModeName(rm) << " " << FRegName(rd) << ", " << FRegName(rs1); } break; case 0xbu: if (rs2 == 0u) { os_ << "fsqrt" << type << RoundingModeName(rm) << " " << FRegName(rd) << ", " << FRegName(rs1); return; } break; case 0x14u: { // FLE/FLT/FEQ static const char* const kOpcodes[] = { "fle", "flt", "feq" }; if (rm < std::size(kOpcodes)) { os_ << kOpcodes[rm] << type << " " << XRegName(rd) << ", " << FRegName(rs1) << ", " << FRegName(rs2); return; } break; } case 0x18u: { // FCVT from floating point numbers to integers static const char* const kIntTypes[] = { "w", "wu", "l", "lu" }; if (rs2 < std::size(kIntTypes)) { os_ << "fcvt." << kIntTypes[rs2] << type << RoundingModeName(rm) << " " << XRegName(rd) << ", " << FRegName(rs1); return; } break; } case 0x1au: { // FCVT from integers to floating point numbers static const char* const kIntTypes[] = { "w", "wu", "l", "lu" }; if (rs2 < std::size(kIntTypes)) { os_ << "fcvt" << type << "." << kIntTypes[rs2] << RoundingModeName(rm) << " " << FRegName(rd) << ", " << XRegName(rs1); return; } break; } case 0x1cu: // FMV from FPR to GPR, or FCLASS if (rs2 == 0u && rm == 0u) { os_ << (((funct7 & 1u) != 0u) ? "fmv.x.d " : "fmv.x.w ") << XRegName(rd) << ", " << FRegName(rs1); return; } else if (rs2 == 0u && rm == 1u) { os_ << "fclass" << type << " " << XRegName(rd) << ", " << FRegName(rs1); return; } break; case 0x1eu: // FMV from GPR to FPR if (rs2 == 0u && rm == 0u) { os_ << (((funct7 & 1u) != 0u) ? "fmv.d.x " : "fmv.w.x ") << FRegName(rd) << ", " << XRegName(rs1); return; } break; default: break; } os_ << ""; } void DisassemblerRiscv64::Printer::AppendVType(uint32_t vtype) { const uint32_t lmul_v = vtype & 0x7U; const uint32_t vsew_v = (vtype >> 3) & 0x7U; const uint32_t vta_v = (vtype >> 6) & 0x1U; const uint32_t vma_v = (vtype >> 7) & 0x1U; if ((vsew_v & 0x4U) == 0u) { if (lmul_v != 0b100) { static const char* const vsews[] = {"e8", "e16", "e32", "e64"}; static const char* const lmuls[] = { "m1", "m2", "m4", "m8", nullptr, "mf8", "mf4", "mf2" }; const char* vma = vma_v ? "ma" : "mu"; const char* vta = vta_v ? "ta" : "tu"; const char* vsew = vsews[vsew_v & 0x3u]; const char* lmul = lmuls[lmul_v]; os_ << vsew << ", " << lmul << ", " << vta << ", " << vma; return; } } os_ << StringPrintf("0x%08x", vtype) << "\t# incorrect VType literal"; } static constexpr uint32_t VWXUNARY0 = 0b010000; static constexpr uint32_t VRXUNARY0 = 0b010000; static constexpr uint32_t VXUNARY0 = 0b010010; static constexpr uint32_t VMUNARY0 = 0b010100; static constexpr uint32_t VWFUNARY0 = 0b010000; static constexpr uint32_t VRFUNARY0 = 0b010000; static constexpr uint32_t VFUNARY0 = 0b010010; static constexpr uint32_t VFUNARY1 = 0b010011; static void MaybeSwapOperands(uint32_t funct6, /*inout*/ const char*& rs1, /*inout*/ const char*& rs2) { if ((0x28u <= funct6 && funct6 < 0x30u) || funct6 >= 0x3Cu) { std::swap(rs1, rs2); } } void DisassemblerRiscv64::Printer::Print32RVVOp(uint32_t insn32) { // TODO(riscv64): Print pseudo-instruction aliases when applicable. DCHECK_EQ(insn32 & 0x7fu, 0x57u); const enum VAIEncodings vai = static_cast((insn32 >> 12) & 7u); const uint32_t funct7 = Decode32UImm7(insn32); const uint32_t funct6 = funct7 >> 1; const bool masked = (funct7 & 1) == 0; const char* vm = masked ? ", v0.t" : ""; const char* opcode = nullptr; const char* rd = nullptr; const char* rs1 = nullptr; const char* rs2 = nullptr; switch (vai) { case VAIEncodings::kOpIVV: { static constexpr const char* kOPIVVOpcodes[64] = { "vadd.vv", nullptr, "vsub.vv", nullptr, "vminu.vv", "vmin.vv", "vmaxu.vv", "vmax.vv", nullptr, "vand.vv", "vor.vv", "vxor.vv", "vrgather.vv", nullptr, "vrgatherei16.vv", nullptr, "vadc.vvm", "vmadc.vvm", "vsbc.vvm", "vmsbc.vvm", nullptr, nullptr, nullptr, "", "vmseq.vv", "vmsne.vv", "vmsltu.vv", "vmslt.vv", "vmsleu.vv", "vmsle.vv", nullptr, nullptr, "vsaddu.vv", "vsadd.vv", "vssubu.vv", "vssub.vv", nullptr, "vsll.vv", nullptr, "vsmul.vv", "vsrl.vv", "vsra.vv", "vssrl.vv", "vssra.vv", "vnsrl.wv", "vnsra.wv", "vnclipu.wv", "vnclip.wv", "vwredsumu.vs", "vwredsum.vs", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }; rs2 = VRegName(GetRs2(insn32)); if (funct6 == 0b010111) { // vmerge/vmv if (masked) { opcode = "vmerge.vvm"; vm = ", v0"; } else if (GetRs2(insn32) == 0) { opcode = "vmv.v.v"; rs2 = nullptr; } else { opcode = nullptr; } } else { opcode = kOPIVVOpcodes[funct6]; } rd = VRegName(GetRd(insn32)); rs1 = VRegName(GetRs1(insn32)); break; } case VAIEncodings::kOpIVX: { static constexpr const char* kOPIVXOpcodes[64] = { "vadd.vx", nullptr, "vsub.vx", "vrsub.vx", "vminu.vx", "vmin.vx", "vmaxu.vx", "vmax.vx", nullptr, "vand.vx", "vor.vx", "vxor.vx", "vrgather.vx", nullptr, "vslideup.vx", "vslidedown.vx", "vadc.vxm", "vmadc.vxm", "vsbc.vxm", "vmsbc.vxm", nullptr, nullptr, nullptr, "", "vmseq.vx", "vmsne.vx", "vmsltu.vx", "vmslt.vx", "vmsleu.vx", "vmsle.vx", "vmsgtu.vx", "vmsgt.vx", "vsaddu.vx", "vsadd.vx", "vssubu.vx", "vssub.vx", nullptr, "vsll.vx", nullptr, "vsmul.vx", "vsrl.vx", "vsra.vx", "vssrl.vx", "vssra.vx", "vnsrl.wx", "vnsra.wx", "vnclipu.wx", "vnclip.wx", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }; rs2 = VRegName(GetRs2(insn32)); if (funct6 == 0b010111) { // vmerge/vmv if (masked) { opcode = "vmerge.vxm"; vm = ", v0"; } else if (GetRs2(insn32) == 0) { opcode = "vmv.v.x"; rs2 = nullptr; } else { opcode = nullptr; } } else { opcode = kOPIVXOpcodes[funct6]; } rd = VRegName(GetRd(insn32)); rs1 = XRegName(GetRs1(insn32)); break; } case VAIEncodings::kOpIVI: { static constexpr const char* kOPIVIOpcodes[64] = { "vadd.vi", nullptr, nullptr, "vrsub.vi", nullptr, nullptr, nullptr, nullptr, nullptr, "vand.vi", "vor.vi", "vxor.vi", "vrgather.vi", nullptr, "vslideup.vi", "vslidedown.vi", "vadc.vim", "vmadc.vim", nullptr, nullptr, nullptr, nullptr, nullptr, "", "vmseq.vi", "vmsne.vi", nullptr, nullptr, "vmsleu.vi", "vmsle.vi", "vmsgtu.vi", "vmsgt.vi", "vsaddu.vi", "vsadd.vi", nullptr, nullptr, nullptr, "vsll.vi", nullptr, "", "vsrl.vi", "vsra.vi", "vssrl.vi", "vssra.vi", "vnsrl.wi", "vnsra.wi", "vnclipu.wi", "vnclip.wi", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }; rs2 = VRegName(GetRs2(insn32)); if (funct6 == 0b010111) { // vmerge/vmv if (masked) { opcode = "vmerge.vim"; vm = ", v0"; } else if (GetRs2(insn32) == 0) { opcode = "vmv.v.i"; rs2 = nullptr; } else { opcode = nullptr; } } else if (funct6 == 0b100111) { uint32_t rs1V = GetRs1(insn32); static constexpr const char* kVmvnrOpcodes[8] = { "vmv1r.v", "vmv2r.v", nullptr, "vmv4r.v", nullptr, nullptr, nullptr, "vmv8r.v", }; if (IsUint<3>(rs1V)) { opcode = kVmvnrOpcodes[rs1V]; } } else { opcode = kOPIVIOpcodes[funct6]; } rd = VRegName(GetRd(insn32)); break; } case VAIEncodings::kOpMVV: { switch (funct6) { case VWXUNARY0: { static constexpr const char* kVWXUNARY0Opcodes[32] = { "vmv.x.s", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "vcpop.m", "vfirst.m", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }; opcode = kVWXUNARY0Opcodes[GetRs1(insn32)]; rd = XRegName(GetRd(insn32)); rs2 = VRegName(GetRs2(insn32)); break; } case VXUNARY0: { static constexpr const char* kVXUNARY0Opcodes[32] = { nullptr, nullptr, "vzext.vf8", "vsext.vf8", "vzext.vf4", "vsext.vf4", "vzext.vf2", "vsext.vf2", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }; opcode = kVXUNARY0Opcodes[GetRs1(insn32)]; rd = VRegName(GetRd(insn32)); rs2 = VRegName(GetRs2(insn32)); break; } case VMUNARY0: { static constexpr const char* kVMUNARY0Opcodes[32] = { nullptr, "vmsbf.m", "vmsof.m", "vmsif.m", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "viota.m", "vid.v", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }; opcode = kVMUNARY0Opcodes[GetRs1(insn32)]; rd = VRegName(GetRd(insn32)); rs2 = VRegName(GetRs2(insn32)); break; } default: { static constexpr const char* kOPMVVOpcodes[64] = { "vredsum.vs", "vredand.vs", "vredor.vs", "vredxor.vs", "vredminu.vs", "vredmin.vs", "vredmaxu.vs", "vredmax.vs", "vaaddu.vv", "vaadd.vv", "vasubu.vv", "vasub.vv", nullptr, nullptr, nullptr, nullptr, "", nullptr, "", nullptr, "", nullptr, nullptr, "vcompress.vm", "vmandn.mm", "vmand.mm", "vmor.mm", "vmxor.mm", "vmorn.mm", "vmnand.mm", "vmnor.mm", "vmxnor.mm", "vdivu.vv", "vdiv.vv", "vremu.vv", "vrem.vv", "vmulhu.vv", "vmul.vv", "vmulhsu.vv", "vmulh.vv", nullptr, "vmadd.vv", nullptr, "vnmsub.vv", nullptr, "vmacc.vv", nullptr, "vnmsac.vv", "vwaddu.vv", "vwadd.vv", "vwsubu.vv", "vwsub.vv", "vwaddu.wv", "vwadd.wv", "vwsubu.wv", "vwsub.wv", "vwmulu.vv", nullptr, "vwmulsu.vv", "vwmul.vv", "vwmaccu.vv", "vwmacc.vv", nullptr, "vwmaccsu.vv", }; opcode = kOPMVVOpcodes[funct6]; rd = VRegName(GetRd(insn32)); rs1 = VRegName(GetRs1(insn32)); rs2 = VRegName(GetRs2(insn32)); if (0x17u <= funct6 && funct6 <= 0x1Fu) { if (masked) { // for vcompress.vm and *.mm encodings with vm=0 are reserved opcode = nullptr; } } MaybeSwapOperands(funct6, rs1, rs2); break; } } break; } case VAIEncodings::kOpMVX: { switch (funct6) { case VRXUNARY0: { opcode = GetRs2(insn32) == 0u ? "vmv.s.x" : nullptr; rd = VRegName(GetRd(insn32)); rs1 = XRegName(GetRs1(insn32)); break; } default: { static constexpr const char* kOPMVXOpcodes[64] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "vaaddu.vx", "vaadd.vx", "vasubu.vx", "vasub.vx", nullptr, nullptr, "vslide1up.vx", "vslide1down.vx", "", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "vdivu.vx", "vdiv.vx", "vremu.vx", "vrem.vx", "vmulhu.vx", "vmul.vx", "vmulhsu.vx", "vmulh.vx", nullptr, "vmadd.vx", nullptr, "vnmsub.vx", nullptr, "vmacc.vx", nullptr, "vnmsac.vx", "vwaddu.vx", "vwadd.vx", "vwsubu.vx", "vwsub.vx", "vwaddu.wv", "vwadd.wv", "vwsubu.wv", "vwsub.wv", "vwmulu.vx", nullptr, "vwmulsu.vx", "vwmul.vx", "vwmaccu.vx", "vwmacc.vx", "vwmaccus.vx", "vwmaccsu.vx", }; opcode = kOPMVXOpcodes[funct6]; rd = VRegName(GetRd(insn32)); rs1 = XRegName(GetRs1(insn32)); rs2 = VRegName(GetRs2(insn32)); MaybeSwapOperands(funct6, rs1, rs2); break; } } break; } case VAIEncodings::kOpFVV: { switch (funct6) { case VWFUNARY0: { opcode = GetRs1(insn32) == 0u ? "vfmv.f.s" : nullptr; rd = XRegName(GetRd(insn32)); rs2 = VRegName(GetRs2(insn32)); break; } case VFUNARY0: { static constexpr const char* kVFUNARY0Opcodes[32] = { "vfcvt.xu.f.v", "vfcvt.x.f.v", "vfcvt.f.xu.v", "vfcvt.f.x.v", nullptr, nullptr, "vfcvt.rtz.xu.f.v", "vfcvt.rtz.x.f.v", "vfwcvt.xu.f.v", "vfwcvt.x.f.v", "vfwcvt.f.xu.v", "vfwcvt.f.x.v", "vfwcvt.f.f.v", nullptr, "vfwcvt.rtz.xu.f.v", "vfwcvt.rtz.x.f.v", "vfncvt.xu.f.w", "vfncvt.x.f.w", "vfncvt.f.xu.w", "vfncvt.f.x.w", "vfncvt.f.f.w", "vfncvt.rod.f.f.w", "vfncvt.rtz.xu.f.w", "vfncvt.rtz.x.f.w", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }; opcode = kVFUNARY0Opcodes[GetRs1(insn32)]; rd = VRegName(GetRd(insn32)); rs2 = VRegName(GetRs2(insn32)); break; } case VFUNARY1: { static constexpr const char* kVFUNARY1Opcodes[32] = { "vfsqrt.v", nullptr, nullptr, nullptr, "vfrsqrt7.v", "vfrec7.v", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "vfclass.v", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }; opcode = kVFUNARY1Opcodes[GetRs1(insn32)]; rd = VRegName(GetRd(insn32)); rs2 = VRegName(GetRs2(insn32)); break; } default: { static constexpr const char* kOPFVVOpcodes[64] = { "vfadd.vv", "vfredusum.vs", "vfsub.vv", "vfredosum.vs", "vfmin.vv", "vfredmin.vs", "vfmax.vv", "vfredmax.vs", "vfsgnj.vv", "vfsgnjn.vv", "vfsgnjx.vv", nullptr, nullptr, nullptr, nullptr, nullptr, "", nullptr, "", "", nullptr, nullptr, nullptr, nullptr, "vmfeq.vv", "vmfle.vv", nullptr, "vmflt.vv", "vmfne.vv", nullptr, nullptr, nullptr, "vfdiv.vv", nullptr, nullptr, nullptr, "vfmul.vv", nullptr, nullptr, nullptr, "vfmadd.vv", "vfnmadd.vv", "vfmsub.vv", "vfnmsub.vv", "vfmacc.vv", "vfnmacc.vv", "vfmsac.vv", "vfnmsac.vv", "vfwadd.vv", "vfwredusum.vs", "vfwsub.vv", "vfwredosum.vs", "vfwadd.wv", nullptr, "vfwsub.wv", nullptr, "vfwmul.vv", nullptr, nullptr, nullptr, "vfwmacc.vv", "vfwnmacc.vv", "vfwmsac.vv", "vfwnmsac.vv", }; opcode = kOPFVVOpcodes[funct6]; rd = VRegName(GetRd(insn32)); rs1 = VRegName(GetRs1(insn32)); rs2 = VRegName(GetRs2(insn32)); MaybeSwapOperands(funct6, rs1, rs2); break; } } break; } case VAIEncodings::kOpFVF: { switch (funct6) { case VRFUNARY0: { opcode = GetRs2(insn32) == 0u ? "vfmv.s.f" : nullptr; rd = VRegName(GetRd(insn32)); rs1 = FRegName(GetRs1(insn32)); break; } default: { static constexpr const char* kOPFVFOpcodes[64] = { "vfadd.vf", nullptr, "vfsub.vf", nullptr, "vfmin.vf", nullptr, "vfmax.vf", nullptr, "vfsgnj.vf", "vfsgnjn.vf", "vfsgnjx.vf", nullptr, nullptr, nullptr, "vfslide1up.vf", "vfslide1down.vf", "", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "", "vmfeq.vf", "vmfle.vf", nullptr, "vmflt.vf", "vmfne.vf", "vmfgt.vf", nullptr, "vmfge.vf", "vfdiv.vf", "vfrdiv.vf", nullptr, nullptr, "vfmul.vf", nullptr, nullptr, "vfrsub.vf", "vfmadd.vf", "vfnmadd.vf", "vfmsub.vf", "vfnmsub.vf", "vfmacc.vf", "vfnmacc.vf", "vfmsac.vf", "vfnmsac.vf", "vfwadd.vf", nullptr, "vfwsub.vf", nullptr, "vfwadd.wf", nullptr, "vfwsub.wf", nullptr, "vfwmul.vf", nullptr, nullptr, nullptr, "vfwmacc.vf", "vfwnmacc.vf", "vfwmsac.vf", "vfwnmsac.vf", }; rs2 = VRegName(GetRs2(insn32)); if (funct6 == 0b010111) { // vfmerge/vfmv if (masked) { opcode = "vfmerge.vfm"; vm = ", v0"; } else if (GetRs2(insn32) == 0) { opcode = "vfmv.v.f"; rs2 = nullptr; } else { opcode = nullptr; } } else { opcode = kOPFVFOpcodes[funct6]; } rd = VRegName(GetRd(insn32)); rs1 = FRegName(GetRs1(insn32)); MaybeSwapOperands(funct6, rs1, rs2); break; } } break; } case VAIEncodings::kOpCFG: { // vector ALU control instructions if ((insn32 >> 31) != 0u) { if (((insn32 >> 30) & 0x1U) != 0u) { // vsetivli const uint32_t zimm = Decode32UImm12(insn32) & ~0xC00U; const uint32_t imm5 = GetRs1(insn32); os_ << "vsetivli " << XRegName(GetRd(insn32)) << ", " << StringPrintf("0x%08x", imm5) << ", "; AppendVType(zimm); } else { // vsetvl os_ << "vsetvl " << XRegName(GetRd(insn32)) << ", " << XRegName(GetRs1(insn32)) << ", " << XRegName(GetRs2(insn32)); if ((Decode32UImm7(insn32) & 0x40u) != 0u) { os_ << "\t# incorrect funct7 literal : " << StringPrintf("0x%08x", Decode32UImm7(insn32)); } } } else { // vsetvli const uint32_t zimm = Decode32UImm12(insn32) & ~0x800U; os_ << "vsetvli " << XRegName(GetRd(insn32)) << ", " << XRegName(GetRs1(insn32)) << ", "; AppendVType(zimm); } return; } } if (opcode == nullptr) { os_ << ""; return; } os_ << opcode << " " << rd; if (rs2 != nullptr) { os_ << ", " << rs2; } if (rs1 != nullptr) { os_ << ", " << rs1; } else if (vai == VAIEncodings::kOpIVI) { os_ << StringPrintf(", 0x%08x", GetRs1(insn32)); } os_ << vm; } void DisassemblerRiscv64::Printer::Print32FpFma(uint32_t insn32) { DCHECK_EQ(insn32 & 0x73u, 0x43u); // Note: Bits 0xc select the FMA opcode. uint32_t funct2 = (insn32 >> 25) & 3u; if (funct2 >= 2u) { os_ << ""; // Note: This includes the "H" and "Q" extensions. return; } static const char* const kOpcodes[] = { "fmadd", "fmsub", "fnmsub", "fnmadd" }; os_ << kOpcodes[(insn32 >> 2) & 3u] << ((funct2 != 0u) ? ".d" : ".s") << RoundingModeName(GetRoundingMode(insn32)) << " " << FRegName(GetRd(insn32)) << ", " << FRegName(GetRs1(insn32)) << ", " << FRegName(GetRs2(insn32)) << ", " << FRegName(GetRs3(insn32)); } void DisassemblerRiscv64::Printer::Print32Zicsr(uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x73u); uint32_t funct3 = (insn32 >> 12) & 7u; static const char* const kOpcodes[] = { nullptr, "csrrw", "csrrs", "csrrc", nullptr, "csrrwi", "csrrsi", "csrrci" }; const char* opcode = kOpcodes[funct3]; if (opcode == nullptr) { os_ << ""; return; } uint32_t rd = GetRd(insn32); uint32_t rs1_or_uimm = GetRs1(insn32); uint32_t csr = insn32 >> 20; // Print shorter macro instruction notation if available. if (funct3 == /*CSRRW*/ 1u && rd == 0u && rs1_or_uimm == 0u && csr == 0xc00u) { os_ << "unimp"; return; } else if (funct3 == /*CSRRS*/ 2u && rs1_or_uimm == 0u) { if (csr == 0xc00u) { os_ << "rdcycle " << XRegName(rd); } else if (csr == 0xc01u) { os_ << "rdtime " << XRegName(rd); } else if (csr == 0xc02u) { os_ << "rdinstret " << XRegName(rd); } else { os_ << "csrr " << XRegName(rd) << ", " << csr; } return; } if (rd == 0u) { static const char* const kAltOpcodes[] = { nullptr, "csrw", "csrs", "csrc", nullptr, "csrwi", "csrsi", "csrci" }; DCHECK(kAltOpcodes[funct3] != nullptr); os_ << kAltOpcodes[funct3] << " " << csr << ", "; } else { os_ << opcode << " " << XRegName(rd) << ", " << csr << ", "; } if (funct3 >= /*CSRRWI/CSRRSI/CSRRCI*/ 4u) { os_ << rs1_or_uimm; } else { os_ << XRegName(rs1_or_uimm); } } void DisassemblerRiscv64::Printer::Print32Fence(uint32_t insn32) { DCHECK_EQ(insn32 & 0x7fu, 0x0fu); if ((insn32 & 0xf00fffffu) == 0x0000000fu) { auto print_flags = [&](uint32_t flags) { if (flags == 0u) { os_ << "0"; } else { DCHECK_LT(flags, 0x10u); static const char kFlagNames[] = "wroi"; for (size_t bit : { 3u, 2u, 1u, 0u }) { // Print in the "iorw" order. if ((flags & (1u << bit)) != 0u) { os_ << kFlagNames[bit]; } } } }; os_ << "fence."; print_flags((insn32 >> 24) & 0xfu); os_ << "."; print_flags((insn32 >> 20) & 0xfu); } else if (insn32 == 0x8330000fu) { os_ << "fence.tso"; } else if (insn32 == 0x0000100fu) { os_ << "fence.i"; } else { os_ << ""; } } void DisassemblerRiscv64::Printer::Dump32(const uint8_t* insn) { uint32_t insn32 = static_cast(insn[0]) + (static_cast(insn[1]) << 8) + (static_cast(insn[2]) << 16) + (static_cast(insn[3]) << 24); CHECK_EQ(insn32 & 3u, 3u); os_ << disassembler_->FormatInstructionPointer(insn) << StringPrintf(": %08x\t", insn32); switch (insn32 & 0x7fu) { case 0x37u: Print32Lui(insn32); break; case 0x17u: Print32Auipc(insn, insn32); break; case 0x6fu: Print32Jal(insn, insn32); break; case 0x67u: switch ((insn32 >> 12) & 7u) { // funct3 case 0: Print32Jalr(insn, insn32); break; default: os_ << ""; break; } break; case 0x63u: Print32BCond(insn, insn32); break; case 0x03u: Print32Load(insn32); break; case 0x23u: Print32Store(insn32); break; case 0x07u: Print32FLoad(insn32); break; case 0x27u: Print32FStore(insn32); break; case 0x13u: case 0x1bu: Print32BinOpImm(insn32); break; case 0x33u: case 0x3bu: Print32BinOp(insn32); break; case 0x2fu: Print32Atomic(insn32); break; case 0x53u: Print32FpOp(insn32); break; case 0x57u: Print32RVVOp(insn32); break; case 0x43u: case 0x47u: case 0x4bu: case 0x4fu: Print32FpFma(insn32); break; case 0x73u: if ((insn32 & 0xffefffffu) == 0x00000073u) { os_ << ((insn32 == 0x00000073u) ? "ecall" : "ebreak"); } else { Print32Zicsr(insn32); } break; case 0x0fu: Print32Fence(insn32); break; default: // TODO(riscv64): Disassemble more instructions. os_ << ""; break; } os_ << "\n"; } void DisassemblerRiscv64::Printer::Dump16(const uint8_t* insn) { uint32_t insn16 = static_cast(insn[0]) + (static_cast(insn[1]) << 8); ScopedNewLinePrinter nl(os_); CHECK_NE(insn16 & 3u, 3u); os_ << disassembler_->FormatInstructionPointer(insn) << StringPrintf(": %04x \t", insn16); uint32_t funct3 = BitFieldExtract(insn16, 13, 3); int32_t offset = -1; switch (insn16 & 3u) { case 0b00u: // Quadrant 0 switch (funct3) { case 0b000u: if (insn16 == 0u) { os_ << "c.unimp"; } else { uint32_t nzuimm = BitFieldExtract(insn16, 5, 8); if (nzuimm != 0u) { uint32_t decoded = BitFieldExtract(nzuimm, 0, 1) << 3 | BitFieldExtract(nzuimm, 1, 1) << 2 | BitFieldExtract(nzuimm, 2, 4) << 6 | BitFieldExtract(nzuimm, 6, 2) << 4; os_ << "c.addi4spn " << XRegName(GetRs2Short16(insn16)) << ", sp, " << decoded; } else { os_ << ""; } } return; case 0b001u: offset = Decode16CMOffsetD(insn16); os_ << "c.fld " << FRegName(GetRs2Short16(insn16)); break; case 0b010u: offset = Decode16CMOffsetW(insn16); os_ << "c.lw " << XRegName(GetRs2Short16(insn16)); break; case 0b011u: offset = Decode16CMOffsetD(insn16); os_ << "c.ld " << XRegName(GetRs2Short16(insn16)); break; case 0b100u: { uint32_t opcode2 = BitFieldExtract(insn16, 10, 3); uint32_t imm = BitFieldExtract(insn16, 5, 2); switch (opcode2) { case 0b000: offset = Uimm2ToOffset10(imm); os_ << "c.lbu " << XRegName(GetRs2Short16(insn16)); break; case 0b001: offset = Uimm2ToOffset1(imm); os_ << (BitFieldExtract(imm, 1, 1) == 0u ? "c.lhu " : "c.lh "); os_ << XRegName(GetRs2Short16(insn16)); break; case 0b010: offset = Uimm2ToOffset10(imm); os_ << "c.sb " << XRegName(GetRs2Short16(insn16)); break; case 0b011: if (BitFieldExtract(imm, 1, 1) == 0u) { offset = Uimm2ToOffset1(imm); os_ << "c.sh " << XRegName(GetRs2Short16(insn16)); break; } FALLTHROUGH_INTENDED; default: os_ << ""; return; } break; } case 0b101u: offset = Decode16CMOffsetD(insn16); os_ << "c.fsd " << FRegName(GetRs2Short16(insn16)); break; case 0b110u: offset = Decode16CMOffsetW(insn16); os_ << "c.sw " << XRegName(GetRs2Short16(insn16)); break; case 0b111u: offset = Decode16CMOffsetD(insn16); os_ << "c.sd " << XRegName(GetRs2Short16(insn16)); break; default: LOG(FATAL) << "Unreachable"; UNREACHABLE(); } os_ << ", "; PrintLoadStoreAddress(GetRs1Short16(insn16), offset); return; case 0b01u: // Quadrant 1 switch (funct3) { case 0b000u: { uint32_t rd = GetRs1_16(insn16); if (rd == 0) { if (Decode16Imm6(insn16) != 0u) { os_ << ""; } else { os_ << "c.nop"; } } else { int32_t imm = Decode16Imm6(insn16); if (imm != 0) { os_ << "c.addi " << XRegName(rd) << ", " << imm; } else { os_ << ""; } } break; } case 0b001u: { uint32_t rd = GetRs1_16(insn16); if (rd != 0) { os_ << "c.addiw " << XRegName(rd) << ", " << Decode16Imm6(insn16); } else { os_ << ""; } break; } case 0b010u: { uint32_t rd = GetRs1_16(insn16); if (rd != 0) { os_ << "c.li " << XRegName(rd) << ", " << Decode16Imm6(insn16); } else { os_ << ""; } break; } case 0b011u: { uint32_t rd = GetRs1_16(insn16); uint32_t imm6_bits = Decode16Imm6(insn16); if (imm6_bits != 0u) { if (rd == 2) { int32_t nzimm = BitFieldExtract(insn16, 6, 1) << 4 | BitFieldExtract(insn16, 2, 1) << 5 | BitFieldExtract(insn16, 5, 1) << 6 | BitFieldExtract(insn16, 3, 2) << 7 | BitFieldExtract(insn16, 12, 1) << 9; os_ << "c.addi16sp sp, " << SignExtendBits<10>(nzimm); } else if (rd != 0) { // sign-extend bits and mask with 0xfffff as llvm-objdump does uint32_t mask = MaskLeastSignificant(20); os_ << "c.lui " << XRegName(rd) << ", " << (SignExtendBits<6>(imm6_bits) & mask); } else { os_ << ""; } } else { os_ << ""; } break; } case 0b100u: { uint32_t funct2 = BitFieldExtract(insn16, 10, 2); switch (funct2) { case 0b00: { int32_t nzuimm = Decode16Imm6(insn16); if (nzuimm != 0) { os_ << "c.srli " << XRegName(GetRs1Short16(insn16)) << ", " << nzuimm; } else { os_ << ""; } break; } case 0b01: { int32_t nzuimm = Decode16Imm6(insn16); if (nzuimm != 0) { os_ << "c.srai " << XRegName(GetRs1Short16(insn16)) << ", " << nzuimm; } else { os_ << ""; } break; } case 0b10: os_ << "c.andi " << XRegName(GetRs1Short16(insn16)) << ", " << Decode16Imm6(insn16); break; case 0b11: { constexpr static const char* mnemonics[] = { "c.sub", "c.xor", "c.or", "c.and", "c.subw", "c.addw", "c.mul", nullptr }; uint32_t opc = BitFieldInsert( BitFieldExtract(insn16, 5, 2), BitFieldExtract(insn16, 12, 1), 2, 1); DCHECK(IsUint<3>(opc)); const char* mnem = mnemonics[opc]; if (mnem != nullptr) { os_ << mnem << " " << XRegName(GetRs1Short16(insn16)) << ", " << XRegName(GetRs2Short16(insn16)); } else { constexpr static const char* zbc_mnemonics[] = { "c.zext.b", "c.sext.b", "c.zext.h", "c.sext.h", "c.zext.w", "c.not", nullptr, nullptr, }; mnem = zbc_mnemonics[BitFieldExtract(insn16, 2, 3)]; if (mnem != nullptr) { os_ << mnem << " " << XRegName(GetRs1Short16(insn16)); } else { os_ << ""; } } break; } default: LOG(FATAL) << "Unreachable"; UNREACHABLE(); } break; } case 0b101u: { int32_t disp = BitFieldExtract(insn16, 3, 3) << 1 | BitFieldExtract(insn16, 11, 1) << 4 | BitFieldExtract(insn16, 2, 1) << 5 | BitFieldExtract(insn16, 7, 1) << 6 | BitFieldExtract(insn16, 6, 1) << 7 | BitFieldExtract(insn16, 9, 2) << 8 | BitFieldExtract(insn16, 8, 1) << 10 | BitFieldExtract(insn16, 12, 1) << 11; os_ << "c.j "; PrintBranchOffset(SignExtendBits<12>(disp)); break; } case 0b110u: case 0b111u: { int32_t disp = BitFieldExtract(insn16, 3, 2) << 1 | BitFieldExtract(insn16, 10, 2) << 3 | BitFieldExtract(insn16, 2, 1) << 5 | BitFieldExtract(insn16, 5, 2) << 6 | BitFieldExtract(insn16, 12, 1) << 8; os_ << (funct3 == 0b110u ? "c.beqz " : "c.bnez "); os_ << XRegName(GetRs1Short16(insn16)) << ", "; PrintBranchOffset(SignExtendBits<9>(disp)); break; } default: LOG(FATAL) << "Unreachable"; UNREACHABLE(); } break; case 0b10u: // Quadrant 2 switch (funct3) { case 0b000u: { uint32_t nzuimm = Decode16Imm6(insn16); uint32_t rd = GetRs1_16(insn16); if (rd == 0 || nzuimm == 0) { os_ << ""; } else { os_ << "c.slli " << XRegName(rd) << ", " << nzuimm; } return; } case 0b001u: { offset = Uimm6ToOffsetD16(Decode16Imm6(insn16)); os_ << "c.fldsp " << FRegName(GetRs1_16(insn16)); break; } case 0b010u: { uint32_t rd = GetRs1_16(insn16); if (rd != 0) { offset = Uimm6ToOffsetW16(Decode16Imm6(insn16)); os_ << "c.lwsp " << XRegName(GetRs1_16(insn16)); } else { os_ << ""; return; } break; } case 0b011u: { uint32_t rd = GetRs1_16(insn16); if (rd != 0) { offset = Uimm6ToOffsetD16(Decode16Imm6(insn16)); os_ << "c.ldsp " << XRegName(GetRs1_16(insn16)); } else { os_ << ""; return; } break; } case 0b100u: { uint32_t rd_rs1 = GetRs1_16(insn16); uint32_t rs2 = GetRs2_16(insn16); uint32_t b12 = BitFieldExtract(insn16, 12, 1); if (b12 == 0) { if (rd_rs1 != 0 && rs2 != 0) { os_ << "c.mv " << XRegName(rd_rs1) << ", " << XRegName(rs2); } else if (rd_rs1 != 0) { os_ << "c.jr " << XRegName(rd_rs1); } else if (rs2 != 0) { os_ << ""; } else { os_ << ""; } } else { if (rd_rs1 != 0 && rs2 != 0) { os_ << "c.add " << XRegName(rd_rs1) << ", " << XRegName(rs2); } else if (rd_rs1 != 0) { os_ << "c.jalr " << XRegName(rd_rs1); } else if (rs2 != 0) { os_ << ""; } else { os_ << "c.ebreak"; } } return; } case 0b101u: offset = BitFieldExtract(insn16, 7, 3) << 6 | BitFieldExtract(insn16, 10, 3) << 3; os_ << "c.fsdsp " << FRegName(GetRs2_16(insn16)); break; case 0b110u: offset = BitFieldExtract(insn16, 7, 2) << 6 | BitFieldExtract(insn16, 9, 4) << 2; os_ << "c.swsp " << XRegName(GetRs2_16(insn16)); break; case 0b111u: offset = BitFieldExtract(insn16, 7, 3) << 6 | BitFieldExtract(insn16, 10, 3) << 3; os_ << "c.sdsp " << XRegName(GetRs2_16(insn16)); break; default: LOG(FATAL) << "Unreachable"; UNREACHABLE(); } os_ << ", "; PrintLoadStoreAddress(/* sp */ 2, offset); break; default: LOG(FATAL) << "Unreachable"; UNREACHABLE(); } } void DisassemblerRiscv64::Printer::Dump2Byte(const uint8_t* data) { uint32_t value = data[0] + (data[1] << 8); os_ << disassembler_->FormatInstructionPointer(data) << StringPrintf(": %04x \t.2byte %u\n", value, value); } void DisassemblerRiscv64::Printer::DumpByte(const uint8_t* data) { uint32_t value = *data; os_ << disassembler_->FormatInstructionPointer(data) << StringPrintf(": %02x \t.byte %u\n", value, value); } size_t DisassemblerRiscv64::Dump(std::ostream& os, const uint8_t* begin) { if (begin < GetDisassemblerOptions()->base_address_ || begin >= GetDisassemblerOptions()->end_address_) { return 0u; // Outside the range. } Printer printer(this, os); if (!IsAligned<2u>(begin) || GetDisassemblerOptions()->end_address_ - begin == 1) { printer.DumpByte(begin); return 1u; } if ((*begin & 3u) == 3u) { if (GetDisassemblerOptions()->end_address_ - begin >= 4) { printer.Dump32(begin); return 4u; } else { printer.Dump2Byte(begin); return 2u; } } else { printer.Dump16(begin); return 2u; } } void DisassemblerRiscv64::Dump(std::ostream& os, const uint8_t* begin, const uint8_t* end) { Printer printer(this, os); const uint8_t* cur = begin; if (cur < end && !IsAligned<2u>(cur)) { // Unaligned, dump as a `.byte` to get to an aligned address. printer.DumpByte(cur); cur += 1; } if (cur >= end) { return; } while (end - cur >= 4) { if ((*cur & 3u) == 3u) { printer.Dump32(cur); cur += 4; } else { printer.Dump16(cur); cur += 2; } } if (end - cur >= 2) { if ((*cur & 3u) == 3u) { // Not enough data for a 32-bit instruction. Dump as `.2byte`. printer.Dump2Byte(cur); } else { printer.Dump16(cur); } cur += 2; } if (end != cur) { CHECK_EQ(end - cur, 1); printer.DumpByte(cur); } } } // namespace riscv64 } // namespace art