/*
* 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 = 0 b00,
kIndexedUnordered = 0 b01,
kStrided = 0 b10,
kIndexedOrdered = 0 b11,
};
enum class Nf : uint32_t {
k1 = 0 b000,
k2 = 0 b001,
k3 = 0 b010,
k4 = 0 b011,
k5 = 0 b100,
k6 = 0 b101,
k7 = 0 b110,
k8 = 0 b111,
};
enum class VAIEncodings : uint32_t {
kOpIVV = 0 b000,
kOpFVV = 0 b001,
kOpMVV = 0 b010,
kOpIVI = 0 b011,
kOpIVX = 0 b100,
kOpFVF = 0 b101,
kOpMVX = 0 b110,
kOpCFG = 0 b111,
};
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 <int32_t>(imm12) - static_cast <int32_t>(sign << 12 ); // Sign-extend.
}
static uint32_t Decode32UImm7(uint32_t insn32) { return (insn32 >> 25 ) & 0 x7Fu; }
static uint32_t Decode32UImm12(uint32_t insn32) { return (insn32 >> 20 ) & 0 xFFFu; }
static int32_t Decode32StoreOffset(uint32_t insn32) {
uint32_t bit11 = insn32 >> 31 ;
uint32_t bits5_11 = insn32 >> 25 ;
uint32_t bits0_4 = (insn32 >> 7 ) & 0 x1fu;
uint32_t imm = (bits5_11 << 5 ) + bits0_4;
return static_cast <int32_t>(imm) - static_cast <int32_t>(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 & 0 x1u) << 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 & 0 x1u) << 1 ;
}
template <size_t kWidth>
static constexpr int32_t SignExtendBits(uint32_t bits) {
static_assert(kWidth < BitSizeOf<uint32_t>());
const uint32_t sign_bit = (bits >> kWidth) & 1 u;
return static_cast <int32_t>(bits) - static_cast <int32_t>(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 <typename T>
static T Decode16Imm6(uint32_t insn16) {
DCHECK(IsUint<16 >(insn16));
static_assert(std::is_integral_v<T>, "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<T>(bits);
if (std::is_unsigned_v<T>) {
return checked_bits;
}
return SignExtendBits<6 >(checked_bits);
}
// Regular instruction register utils
static uint32_t GetRd(uint32_t insn32) { return (insn32 >> 7 ) & 0 x1fu; }
static uint32_t GetRs1(uint32_t insn32) { return (insn32 >> 15 ) & 0 x1fu; }
static uint32_t GetRs2(uint32_t insn32) { return (insn32 >> 20 ) & 0 x1fu; }
static uint32_t GetRs3(uint32_t insn32) { return insn32 >> 27 ; }
static uint32_t GetRoundingMode(uint32_t insn32) { return (insn32 >> 12 ) & 7 u; }
// Compressed instruction register utils
static uint32_t GetRs1Short16(uint32_t insn16) { return BitFieldExtract(insn16, 7 , 3 ) + 8 u; }
static uint32_t GetRs2Short16(uint32_t insn16) { return BitFieldExtract(insn16, 2 , 3 ) + 8 u; }
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, 32 u);
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, 32 u);
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, 32 u);
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" , ".<reserved-rm>" , ".<reserved-rm>" , /*DYN*/ ""
};
static_assert(std::size(kRoundingModeNames) == 8 );
DCHECK_LT(rm, 8 u);
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<uint32_t>(offset));
}
}
void DisassemblerRiscv64::Printer::Print32Lui(uint32_t insn32) {
DCHECK_EQ(insn32 & 0 x7fu, 0 x37u);
// 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 & 0 x7fu, 0 x17u);
// 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 & 0 x7fu, 0 x6fu);
// 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 ) & 0 x3ffu;
uint32_t bit11 = (insn32 >> 20 ) & 1 u;
uint32_t bits12_19 = (insn32 >> 12 ) & 0 xffu;
uint32_t imm = (bits1_10 << 1 ) + (bit11 << 11 ) + (bits12_19 << 12 ) + (bit20 << 20 );
int32_t offset = static_cast <int32_t>(imm) - static_cast <int32_t>(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 & 0 x7fu, 0 x67u);
DCHECK_EQ((insn32 >> 12 ) & 7 u, 0 u);
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 & 0 x7fu, 0 x63u);
static const char * const kOpcodes[] = {
"beq" , "bne" , nullptr, nullptr, "blt" , "bge" , "bltu" , "bgeu"
};
uint32_t funct3 = (insn32 >> 12 ) & 7 u;
const char * opcode = kOpcodes[funct3];
if (opcode == nullptr) {
os_ << "<unknown32>" ;
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 == 4 u || funct3 == 5 u)) {
// blt zero, rs2, offset ... bgtz rs2, offset
// bge zero, rs2, offset ... blez rs2, offset
os_ << (funct3 == 4 u ? "bgtz " : "blez " ) << XRegName(rs2);
} else {
os_ << opcode << " " << XRegName(rs1) << ", " << XRegName(rs2);
}
os_ << ", " ;
uint32_t bit12 = insn32 >> 31 ;
uint32_t bits5_10 = (insn32 >> 25 ) & 0 x3fu;
uint32_t bits1_4 = (insn32 >> 8 ) & 0 xfu;
uint32_t bit11 = (insn32 >> 7 ) & 1 u;
uint32_t imm = (bit12 << 12 ) + (bit11 << 11 ) + (bits5_10 << 5 ) + (bits1_4 << 1 );
int32_t offset = static_cast <int32_t>(imm) - static_cast <int32_t>(bit12 << 13 ); // Sign-extend.
PrintBranchOffset(offset);
os_ << " ; " << disassembler_->FormatInstructionPointer(insn + offset);
}
void DisassemblerRiscv64::Printer::Print32Load(uint32_t insn32) {
DCHECK_EQ(insn32 & 0 x7fu, 0 x03u);
static const char * const kOpcodes[] = {
"lb" , "lh" , "lw" , "ld" , "lbu" , "lhu" , "lwu" , nullptr
};
uint32_t funct3 = (insn32 >> 12 ) & 7 u;
const char * opcode = kOpcodes[funct3];
if (opcode == nullptr) {
os_ << "<unknown32>" ;
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 & 0 x7fu, 0 x23u);
static const char * const kOpcodes[] = {
"sb" , "sh" , "sw" , "sd" , nullptr, nullptr, nullptr, nullptr
};
uint32_t funct3 = (insn32 >> 12 ) & 7 u;
const char * opcode = kOpcodes[funct3];
if (opcode == nullptr) {
os_ << "<unknown32>" ;
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 ) & 3 u;
DCHECK_EQ(width_index != 0 u, (insn32 & 0 x4000u) != 0 u);
const uint32_t imm7 = Decode32UImm7(insn32);
const enum Nf nf = static_cast <enum Nf>((imm7 >> 4 ) & 0 x7u);
const enum MemAddressMode mop = static_cast <enum MemAddressMode>((imm7 >> 1 ) & 0 x3u);
const uint32_t mew = (insn32 >> 28 ) & 1 u;
if (mew == 1 u) {
// 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 0 b00000: // 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<uint32_t>(nf)][width_index];
case 0 b01000: { // 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<uint32_t>(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<uint32_t>(nf)];
}
}
case 0 b01011: // Vector Unit-Stride Mask Load/Store
if (nf == Nf::k1 && width_index == 0 && (imm7 & 1 u) == 1 u) {
return "m.v" ;
} else {
return nullptr;
}
case 0 b10000: // 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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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 & 0 x7fu, 0 x07u);
int32_t offset = 0 ;
const char * rd = nullptr;
const char * rs2 = nullptr;
const char * vm = "" ;
const uint32_t funct3 = (insn32 >> 12 ) & 7 u;
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) & 0 x1U) == 0 ) {
vm = ", v0.t" ;
}
} else {
rd = FRegName(GetRd(insn32));
offset = Decode32Imm12(insn32);
}
if (mnemonic == nullptr) {
os_ << "<unknown32>" ;
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 & 0 x7fu, 0 x27u);
uint32_t funct3 = (insn32 >> 12 ) & 3 u;
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_ << "<unknown32>" ;
return ;
}
os_ << prefix << "s" << mnemonic << " " << VRegName(GetRd(insn32)) << ", " ;
PrintLoadStoreAddress(GetRs1(insn32), 0 );
if (rs2) {
os_ << ", " << rs2;
}
if ((Decode32UImm7(insn32) & 0 x1U) == 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 & 0 x77u, 0 x13u); // Note: Bit 0x8 selects narrow binop.
bool narrow = (insn32 & 0 x8u) != 0 u;
uint32_t funct3 = (insn32 >> 12 ) & 7 u;
uint32_t funct6 = (insn32 >> 26 ) & 0 x3Fu;
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 & 0 xfc00707fu) == 0 x0800101bu) {
os_ << "slli.uw " << XRegName(rd) << ", " << XRegName(rs1) << ", " << (imm & 0 x3fu);
} else if ((imm ^ 0 x600u) < 3 u && funct3 == 1 u) {
static const char * const kBitOpcodes[] = { "clz" , "ctz" , "cpop" };
os_ << kBitOpcodes[imm ^ 0 x600u] << (narrow ? "w " : " " )
<< XRegName(rd) << ", " << XRegName(rs1);
} else if ((imm ^ 0 x600u) < (narrow ? 32 : 64 ) && funct3 == 5 u) {
os_ << "rori" << (narrow ? "w " : " " )
<< XRegName(rd) << ", " << XRegName(rs1) << ", " << (imm ^ 0 x600u);
} else if (imm == 0 x287u && !narrow && funct3 == 5 u) {
os_ << "orc.b " << XRegName(rd) << ", " << XRegName(rs1);
} else if (imm == 0 x6b8u && !narrow && funct3 == 5 u) {
os_ << "rev8 " << XRegName(rd) << ", " << XRegName(rs1);
} else if (!narrow && funct6 == 0 x12u && (funct3 == /*BCLRI*/ 1u || funct3 == /*BEXTI*/ 5u)) {
os_ << ((funct3 == /*BCLRI*/ 1u) ? "bclri" : "bexti");
} else if (!narrow && funct6 == 0 x1Au && funct3 == 0 x1u /*BINVI*/) {
os_ << "binvi" ;
} else if (!narrow && funct6 == 0 xAu && funct3 == 0 x1u /*BSETI*/) {
os_ << "bseti" ;
} else {
bool bad_high_bits = false ;
if (funct3 == /*SLLI*/ 1u || funct3 == /*SRLI/SRAI*/ 5u) {
imm &= (narrow ? 0 x1fu : 0 x3fu);
uint32_t high_bits = insn32 & (narrow ? 0 xfe000000u : 0 xfc000000u);
if (high_bits == 0 x40000000u && funct3 == /*SRAI*/ 5u) {
os_ << "srai" ;
} else {
os_ << ((funct3 == /*SRLI*/ 5u) ? "srli" : "slli");
bad_high_bits = (high_bits != 0 u);
}
} 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_ << "<unknown32>" ; // 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 & 0 x77u, 0 x33u); // Note: Bit 0x8 selects narrow binop.
bool narrow = (insn32 & 0 x8u) != 0 u;
uint32_t funct3 = (insn32 >> 12 ) & 7 u;
uint32_t funct7 = (insn32 >> 25 ) & 0 x7Fu;
uint32_t rd = GetRd(insn32);
uint32_t rs1 = GetRs1(insn32);
uint32_t rs2 = GetRs2(insn32);
// Print shorter macro instruction notation if available.
if (funct7 == 0 x20u && 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 && funct7 == 4 u && funct3 == /*ADD.UW*/ 0u && rs2 == Zero) {
os_ << "zext.w " << XRegName(rd) << ", " << XRegName(rs1);
} else if (!narrow && funct7 == 0 x24u &&
(funct3 == /*BCLR*/ 1u || funct3 == /*BEXT*/ 5u)) {
os_ << ((funct3 == /*BCLR*/ 1u) ? "bclr" : "bext");
} else if (!narrow && funct7 == 0 x34u && funct3 == /*BINV*/ 1u) {
os_ << "binv" ;
} else if (!narrow && funct7 == 0 x14u && funct3 == /*BSET*/ 1u) {
os_ << "bset" ;
} else {
bool bad_high_bits = false ;
if (funct7 == 0 x20u && (funct3 == /*SUB*/ 0u || funct3 == /*SRA*/ 5u)) {
os_ << ((funct3 == /*SUB*/ 0u) ? "sub" : "sra");
} else if (funct7 == 1 u &&
(!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 (funct7 == 4 u && narrow && funct3 == /*ADD.UW*/ 0u) {
os_ << "add.u" ; // "w" is added below.
} else if (funct7 == 0 x10u && (funct3 & 1 u) == 0 u && funct3 != 0 u) {
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 (funct7 == 0 x20u && !narrow && funct3 >= 4 u && funct3 != 5 u) {
static const char * const kZbbNegOpcodes[] = { "xnor" , nullptr, "orn" , "andn" };
DCHECK(kZbbNegOpcodes[funct3 - 4 u] != nullptr);
os_ << kZbbNegOpcodes[funct3 - 4 u];
} else if (funct7 == 0 x5u && !narrow && funct3 >= 4 u) {
static const char * const kZbbMinMaxOpcodes[] = { "min" , "minu" , "max" , "maxu" };
DCHECK(kZbbMinMaxOpcodes[funct3 - 4 u] != nullptr);
os_ << kZbbMinMaxOpcodes[funct3 - 4 u];
} else if (funct7 == 0 x30u && (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 = funct7 != 0 u;
} else {
DCHECK(narrow);
os_ << "<unknown32>" ; // 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 & 0 x7fu, 0 x2fu);
uint32_t funct3 = (insn32 >> 12 ) & 7 u;
uint32_t funct5 = (insn32 >> 27 );
if ((funct3 != 2 u && funct3 != 3 u) || // There are only 32-bit and 64-bit LR/SC/AMO*.
(((funct5 & 3 u) != 0 u) && funct5 >= 4 u)) { // Only multiples of 4, or 1-3.
os_ << "<unknown32>" ;
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 & 3 u) == 0 u) ? 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 == 2 u) ? ".w" : ".d" ;
const char * aq = (((insn32 >> 26 ) & 1 u) != 0 u) ? ".aq" : "" ;
const char * rl = (((insn32 >> 25 ) & 1 u) != 0 u) ? ".rl" : "" ;
os_ << opcode << type << aq << rl << " " << XRegName(rd) << ", " << XRegName(rs1);
if (funct5 == /*LR*/ 2u) {
if (rs2 != 0 u) {
os_ << " (bad rs2)" ;
}
} else {
os_ << ", " << XRegName(rs2);
}
}
void DisassemblerRiscv64::Printer::Print32FpOp(uint32_t insn32) {
DCHECK_EQ(insn32 & 0 x7fu, 0 x53u);
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 & 1 u) != 0 u) ? ".d" : ".s" ;
if ((funct7 & 2 u) != 0 u) {
os_ << "<unknown32>" ; // Note: This includes the "H" and "Q" extensions.
return ;
}
switch (funct7 >> 2 ) {
case 0 u:
case 1 u:
case 2 u:
case 3 u: {
static const char * const kOpcodes[] = { "fadd" , "fsub" , "fmul" , "fdiv" };
os_ << kOpcodes[funct7 >> 2 ] << type << RoundingModeName(rm) << " "
<< FRegName(rd) << ", " << FRegName(rs1) << ", " << FRegName(rs2);
return ;
}
case 4 u: { // 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 5 u: { // 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 0 x8u: // FCVT between FP numbers.
if ((rs2 ^ 1 u) == (funct7 & 1 u)) {
os_ << ((rs2 != 0 u) ? "fcvt.s.d" : "fcvt.d.s" ) << RoundingModeName(rm) << " "
<< FRegName(rd) << ", " << FRegName(rs1);
}
break ;
case 0 xbu:
if (rs2 == 0 u) {
os_ << "fsqrt" << type << RoundingModeName(rm) << " "
<< FRegName(rd) << ", " << FRegName(rs1);
return ;
}
break ;
case 0 x14u: { // 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 0 x18u: { // 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 0 x1au: { // 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 0 x1cu: // FMV from FPR to GPR, or FCLASS
if (rs2 == 0 u && rm == 0 u) {
os_ << (((funct7 & 1 u) != 0 u) ? "fmv.x.d " : "fmv.x.w " )
<< XRegName(rd) << ", " << FRegName(rs1);
return ;
} else if (rs2 == 0 u && rm == 1 u) {
os_ << "fclass" << type << " " << XRegName(rd) << ", " << FRegName(rs1);
return ;
}
break ;
case 0 x1eu: // FMV from GPR to FPR
if (rs2 == 0 u && rm == 0 u) {
os_ << (((funct7 & 1 u) != 0 u) ? "fmv.d.x " : "fmv.w.x " )
<< FRegName(rd) << ", " << XRegName(rs1);
return ;
}
break ;
default :
break ;
}
os_ << "<unknown32>" ;
}
void DisassemblerRiscv64::Printer::AppendVType(uint32_t vtype) {
const uint32_t lmul_v = vtype & 0 x7U;
const uint32_t vsew_v = (vtype >> 3 ) & 0 x7U;
const uint32_t vta_v = (vtype >> 6 ) & 0 x1U;
const uint32_t vma_v = (vtype >> 7 ) & 0 x1U;
if ((vsew_v & 0 x4U) == 0 u) {
if (lmul_v != 0 b100) {
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 & 0 x3u];
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 = 0 b010000;
static constexpr uint32_t VRXUNARY0 = 0 b010000;
static constexpr uint32_t VXUNARY0 = 0 b010010;
static constexpr uint32_t VMUNARY0 = 0 b010100;
static constexpr uint32_t VWFUNARY0 = 0 b010000;
static constexpr uint32_t VRFUNARY0 = 0 b010000;
static constexpr uint32_t VFUNARY0 = 0 b010010;
static constexpr uint32_t VFUNARY1 = 0 b010011;
static void MaybeSwapOperands(uint32_t funct6,
/*inout*/ const char*& rs1,
/*inout*/ const char*& rs2) {
if ((0 x28u <= funct6 && funct6 < 0 x30u) || funct6 >= 0 x3Cu) {
std::swap(rs1, rs2);
}
}
void DisassemblerRiscv64::Printer::Print32RVVOp(uint32_t insn32) {
// TODO(riscv64): Print pseudo-instruction aliases when applicable.
DCHECK_EQ(insn32 & 0 x7fu, 0 x57u);
const enum VAIEncodings vai = static_cast <enum VAIEncodings>((insn32 >> 12 ) & 7 u);
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, "<vmerge/vmv>" ,
"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 == 0 b010111) {
// 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, "<vmerge/vmv>" ,
"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 == 0 b010111) {
// 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, "<vmerge/vmv>" ,
"vmseq.vi" , "vmsne.vi" , nullptr, nullptr,
"vmsleu.vi" , "vmsle.vi" , "vmsgtu.vi" , "vmsgt.vi" ,
"vsaddu.vi" , "vsadd.vi" , nullptr, nullptr,
nullptr, "vsll.vi" , nullptr, "<vmvNr.v>" ,
"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 == 0 b010111) {
// 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 == 0 b100111) {
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,
"<VWXUNARY0>" , nullptr, "<VXUNARY0>" , nullptr,
"<VMUNARY0>" , 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 (0 x17u <= funct6 && funct6 <= 0 x1Fu) {
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) == 0 u ? "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" ,
"<VRXUNARY0>" , 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) == 0 u ? "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,
"<VWFUNARY0>" , nullptr, "<VFUNARY0>" , "<VFUNARY1>" ,
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) == 0 u ? "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" ,
"<VRFUNARY0>" , nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, "<vfmerge.vfm/vfmv>" ,
"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 == 0 b010111) {
// 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 ) != 0 u) {
if (((insn32 >> 30 ) & 0 x1U) != 0 u) { // vsetivli
const uint32_t zimm = Decode32UImm12(insn32) & ~0 xC00U;
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) & 0 x40u) != 0 u) {
os_ << "\t# incorrect funct7 literal : "
<< StringPrintf("0x%08x" , Decode32UImm7(insn32));
}
}
} else { // vsetvli
const uint32_t zimm = Decode32UImm12(insn32) & ~0 x800U;
os_ << "vsetvli " << XRegName(GetRd(insn32)) << ", " << XRegName(GetRs1(insn32)) << ", " ;
AppendVType(zimm);
}
return ;
}
}
if (opcode == nullptr) {
os_ << "<unknown32>" ;
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 & 0 x73u, 0 x43u); // Note: Bits 0xc select the FMA opcode.
uint32_t funct2 = (insn32 >> 25 ) & 3 u;
if (funct2 >= 2 u) {
os_ << "<unknown32>" ; // Note: This includes the "H" and "Q" extensions.
return ;
}
static const char * const kOpcodes[] = { "fmadd" , "fmsub" , "fnmsub" , "fnmadd" };
os_ << kOpcodes[(insn32 >> 2 ) & 3 u] << ((funct2 != 0 u) ? ".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 & 0 x7fu, 0 x73u);
uint32_t funct3 = (insn32 >> 12 ) & 7 u;
static const char * const kOpcodes[] = {
nullptr, "csrrw" , "csrrs" , "csrrc" , nullptr, "csrrwi" , "csrrsi" , "csrrci"
};
const char * opcode = kOpcodes[funct3];
if (opcode == nullptr) {
os_ << "<unknown32>" ;
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 == 0 xc00u) {
os_ << "rdcycle " << XRegName(rd);
} else if (csr == 0 xc01u) {
os_ << "rdtime " << XRegName(rd);
} else if (csr == 0 xc02u) {
os_ << "rdinstret " << XRegName(rd);
} else {
os_ << "csrr " << XRegName(rd) << ", " << csr;
}
return ;
}
if (rd == 0 u) {
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 & 0 x7fu, 0 x0fu);
if ((insn32 & 0 xf00fffffu) == 0 x0000000fu) {
auto print_flags = [&](uint32_t flags) {
if (flags == 0 u) {
os_ << "0" ;
} else {
DCHECK_LT(flags, 0 x10u);
static const char kFlagNames[] = "wroi" ;
for (size_t bit : { 3 u, 2 u, 1 u, 0 u }) { // Print in the "iorw" order.
if ((flags & (1 u << bit)) != 0 u) {
os_ << kFlagNames[bit];
}
}
}
};
os_ << "fence." ;
print_flags((insn32 >> 24 ) & 0 xfu);
os_ << "." ;
print_flags((insn32 >> 20 ) & 0 xfu);
} else if (insn32 == 0 x8330000fu) {
os_ << "fence.tso" ;
} else if (insn32 == 0 x0000100fu) {
os_ << "fence.i" ;
} else {
os_ << "<unknown32>" ;
}
}
void DisassemblerRiscv64::Printer::Dump32(const uint8_t* insn) {
uint32_t insn32 = static_cast <uint32_t>(insn[0 ]) +
(static_cast <uint32_t>(insn[1 ]) << 8 ) +
(static_cast <uint32_t>(insn[2 ]) << 16 ) +
(static_cast <uint32_t>(insn[3 ]) << 24 );
CHECK_EQ(insn32 & 3 u, 3 u);
os_ << disassembler_->FormatInstructionPointer(insn) << StringPrintf(": %08x\t" , insn32);
switch (insn32 & 0 x7fu) {
case 0 x37u:
Print32Lui(insn32);
break ;
case 0 x17u:
Print32Auipc(insn, insn32);
break ;
case 0 x6fu:
Print32Jal(insn, insn32);
break ;
case 0 x67u:
switch ((insn32 >> 12 ) & 7 u) { // funct3
case 0 :
Print32Jalr(insn, insn32);
break ;
default :
os_ << "<unknown32>" ;
break ;
}
break ;
case 0 x63u:
Print32BCond(insn, insn32);
break ;
case 0 x03u:
Print32Load(insn32);
break ;
case 0 x23u:
Print32Store(insn32);
break ;
case 0 x07u:
Print32FLoad(insn32);
break ;
case 0 x27u:
Print32FStore(insn32);
break ;
case 0 x13u:
case 0 x1bu:
Print32BinOpImm(insn32);
break ;
case 0 x33u:
case 0 x3bu:
Print32BinOp(insn32);
break ;
case 0 x2fu:
Print32Atomic(insn32);
break ;
case 0 x53u:
Print32FpOp(insn32);
break ;
case 0 x57u:
Print32RVVOp(insn32);
break ;
case 0 x43u:
case 0 x47u:
case 0 x4bu:
case 0 x4fu:
Print32FpFma(insn32);
break ;
case 0 x73u:
if ((insn32 & 0 xffefffffu) == 0 x00000073u) {
os_ << ((insn32 == 0 x00000073u) ? "ecall" : "ebreak" );
} else {
Print32Zicsr(insn32);
}
break ;
case 0 x0fu:
Print32Fence(insn32);
break ;
default :
// TODO(riscv64): Disassemble more instructions.
os_ << "<unknown32>" ;
break ;
}
os_ << "\n" ;
}
void DisassemblerRiscv64::Printer::Dump16(const uint8_t* insn) {
uint32_t insn16 = static_cast <uint32_t>(insn[0 ]) + (static_cast <uint32_t>(insn[1 ]) << 8 );
ScopedNewLinePrinter nl(os_);
CHECK_NE(insn16 & 3 u, 3 u);
os_ << disassembler_->FormatInstructionPointer(insn) << StringPrintf(": %04x \t" , insn16);
uint32_t funct3 = BitFieldExtract(insn16, 13 , 3 );
int32_t offset = -1 ;
switch (insn16 & 3 u) {
case 0 b00u: // Quadrant 0
switch (funct3) {
case 0 b000u:
if (insn16 == 0 u) {
os_ << "c.unimp" ;
} else {
uint32_t nzuimm = BitFieldExtract(insn16, 5 , 8 );
if (nzuimm != 0 u) {
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_ << "<unknown16>" ;
}
}
return ;
case 0 b001u:
offset = Decode16CMOffsetD(insn16);
os_ << "c.fld " << FRegName(GetRs2Short16(insn16));
break ;
case 0 b010u:
offset = Decode16CMOffsetW(insn16);
os_ << "c.lw " << XRegName(GetRs2Short16(insn16));
break ;
case 0 b011u:
offset = Decode16CMOffsetD(insn16);
os_ << "c.ld " << XRegName(GetRs2Short16(insn16));
break ;
case 0 b100u: {
uint32_t opcode2 = BitFieldExtract(insn16, 10 , 3 );
uint32_t imm = BitFieldExtract(insn16, 5 , 2 );
switch (opcode2) {
case 0 b000:
offset = Uimm2ToOffset10(imm);
os_ << "c.lbu " << XRegName(GetRs2Short16(insn16));
break ;
case 0 b001:
offset = Uimm2ToOffset1(imm);
os_ << (BitFieldExtract(imm, 1 , 1 ) == 0 u ? "c.lhu " : "c.lh " );
os_ << XRegName(GetRs2Short16(insn16));
break ;
case 0 b010:
offset = Uimm2ToOffset10(imm);
os_ << "c.sb " << XRegName(GetRs2Short16(insn16));
break ;
case 0 b011:
if (BitFieldExtract(imm, 1 , 1 ) == 0 u) {
offset = Uimm2ToOffset1(imm);
os_ << "c.sh " << XRegName(GetRs2Short16(insn16));
break ;
}
FALLTHROUGH_INTENDED;
default :
os_ << "<unknown16>" ;
return ;
}
break ;
}
case 0 b101u:
offset = Decode16CMOffsetD(insn16);
os_ << "c.fsd " << FRegName(GetRs2Short16(insn16));
break ;
case 0 b110u:
offset = Decode16CMOffsetW(insn16);
os_ << "c.sw " << XRegName(GetRs2Short16(insn16));
break ;
case 0 b111u:
offset = Decode16CMOffsetD(insn16);
os_ << "c.sd " << XRegName(GetRs2Short16(insn16));
break ;
default :
LOG(FATAL) << "Unreachable" ;
UNREACHABLE();
}
os_ << ", " ;
PrintLoadStoreAddress(GetRs1Short16(insn16), offset);
return ;
case 0 b01u: // Quadrant 1
switch (funct3) {
case 0 b000u: {
uint32_t rd = GetRs1_16(insn16);
if (rd == 0 ) {
if (Decode16Imm6<uint32_t>(insn16) != 0 u) {
os_ << "<hint16>" ;
} else {
os_ << "c.nop" ;
}
} else {
int32_t imm = Decode16Imm6<int32_t>(insn16);
if (imm != 0 ) {
os_ << "c.addi " << XRegName(rd) << ", " << imm;
} else {
os_ << "<hint16>" ;
}
}
break ;
}
case 0 b001u: {
uint32_t rd = GetRs1_16(insn16);
if (rd != 0 ) {
os_ << "c.addiw " << XRegName(rd) << ", " << Decode16Imm6<int32_t>(insn16);
} else {
os_ << "<unknown16>" ;
}
break ;
}
case 0 b010u: {
uint32_t rd = GetRs1_16(insn16);
if (rd != 0 ) {
os_ << "c.li " << XRegName(rd) << ", " << Decode16Imm6<int32_t>(insn16);
} else {
os_ << "<hint16>" ;
}
break ;
}
case 0 b011u: {
uint32_t rd = GetRs1_16(insn16);
uint32_t imm6_bits = Decode16Imm6<uint32_t>(insn16);
if (imm6_bits != 0 u) {
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<uint32_t>(20 );
os_ << "c.lui " << XRegName(rd) << ", " << (SignExtendBits<6 >(imm6_bits) & mask);
} else {
os_ << "<hint16>" ;
}
} else {
os_ << "<unknown16>" ;
}
break ;
}
case 0 b100u: {
uint32_t funct2 = BitFieldExtract(insn16, 10 , 2 );
switch (funct2) {
case 0 b00: {
int32_t nzuimm = Decode16Imm6<uint32_t>(insn16);
if (nzuimm != 0 ) {
os_ << "c.srli " << XRegName(GetRs1Short16(insn16)) << ", " << nzuimm;
} else {
os_ << "<hint16>" ;
}
break ;
}
case 0 b01: {
int32_t nzuimm = Decode16Imm6<uint32_t>(insn16);
if (nzuimm != 0 ) {
os_ << "c.srai " << XRegName(GetRs1Short16(insn16)) << ", " << nzuimm;
} else {
os_ << "<hint16>" ;
}
break ;
}
case 0 b10:
os_ << "c.andi " << XRegName(GetRs1Short16(insn16)) << ", "
<< Decode16Imm6<int32_t>(insn16);
break ;
case 0 b11: {
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_ << "<unknown16>" ;
}
}
break ;
}
default :
LOG(FATAL) << "Unreachable" ;
UNREACHABLE();
}
break ;
}
case 0 b101u: {
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 0 b110u:
case 0 b111u: {
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 == 0 b110u ? "c.beqz " : "c.bnez " );
os_ << XRegName(GetRs1Short16(insn16)) << ", " ;
PrintBranchOffset(SignExtendBits<9 >(disp));
break ;
}
default :
LOG(FATAL) << "Unreachable" ;
UNREACHABLE();
}
break ;
case 0 b10u: // Quadrant 2
switch (funct3) {
case 0 b000u: {
uint32_t nzuimm = Decode16Imm6<uint32_t>(insn16);
uint32_t rd = GetRs1_16(insn16);
if (rd == 0 || nzuimm == 0 ) {
os_ << "<hint16>" ;
} else {
os_ << "c.slli " << XRegName(rd) << ", " << nzuimm;
}
return ;
}
case 0 b001u: {
offset = Uimm6ToOffsetD16(Decode16Imm6<uint32_t>(insn16));
os_ << "c.fldsp " << FRegName(GetRs1_16(insn16));
break ;
}
case 0 b010u: {
uint32_t rd = GetRs1_16(insn16);
if (rd != 0 ) {
offset = Uimm6ToOffsetW16(Decode16Imm6<uint32_t>(insn16));
os_ << "c.lwsp " << XRegName(GetRs1_16(insn16));
} else {
os_ << "<unknown16>" ;
return ;
}
break ;
}
case 0 b011u: {
uint32_t rd = GetRs1_16(insn16);
if (rd != 0 ) {
offset = Uimm6ToOffsetD16(Decode16Imm6<uint32_t>(insn16));
os_ << "c.ldsp " << XRegName(GetRs1_16(insn16));
} else {
os_ << "<unknown16>" ;
return ;
}
break ;
}
case 0 b100u: {
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_ << "<hint16>" ;
} else {
os_ << "<unknown16>" ;
}
} 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_ << "<hint16>" ;
} else {
os_ << "c.ebreak" ;
}
}
return ;
}
case 0 b101u:
offset = BitFieldExtract(insn16, 7 , 3 ) << 6 | BitFieldExtract(insn16, 10 , 3 ) << 3 ;
os_ << "c.fsdsp " << FRegName(GetRs2_16(insn16));
break ;
case 0 b110u:
offset = BitFieldExtract(insn16, 7 , 2 ) << 6 | BitFieldExtract(insn16, 9 , 4 ) << 2 ;
os_ << "c.swsp " << XRegName(GetRs2_16(insn16));
break ;
case 0 b111u:
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 0 u; // Outside the range.
}
Printer printer(this , os);
if (!IsAligned<2 u>(begin) || GetDisassemblerOptions()->end_address_ - begin == 1 ) {
printer.DumpByte(begin);
return 1 u;
}
if ((*begin & 3 u) == 3 u) {
if (GetDisassemblerOptions()->end_address_ - begin >= 4 ) {
printer.Dump32(begin);
return 4 u;
} else {
printer.Dump2Byte(begin);
return 2 u;
}
} else {
printer.Dump16(begin);
return 2 u;
}
}
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<2 u>(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 & 3 u) == 3 u) {
printer.Dump32(cur);
cur += 4 ;
} else {
printer.Dump16(cur);
cur += 2 ;
}
}
if (end - cur >= 2 ) {
if ((*cur & 3 u) == 3 u) {
// 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
Messung V0.5 in Prozent C=92 H=92 G=91
¤ Dauer der Verarbeitung: 0.31 Sekunden
(vorverarbeitet am 2026-06-29)
¤
*© Formatika GbR, Deutschland