Files
sdk/runtime/vm/disassembler_mips.cc
T
zra@google.com dad3270d95 Implements far branch targets for MIPS.
This change adds a flag --mips-far-branches, which when set causes
relative branches to be converted to absolute branches by determining
the PC, adding the branch offset to it, and using the jr or jalr
instruction to jump if the branch test passes.

This procedure clobbers the assembler temporary TMP, so TMP can no
longer be used in a branch test or in the branch delay slot of a
relative branch. Because TMP can't be used in a branch test, a
new temporary is introduced CMPRES2. This change replaces TMP with
CMPRES1 or another register where TMP can no longer be used.

Tests that were failing due to a too-far relative have the
--mips-far-branches flag set.

R=regis@google.com

Review URL: https://codereview.chromium.org//20369003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@25543 260f80e4-7a28-3924-810f-c04153c831b5
2013-07-26 23:30:32 +00:00

802 lines
19 KiB
C++

// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/disassembler.h"
#include "vm/globals.h" // Needed here to get TARGET_ARCH_MIPS.
#if defined(TARGET_ARCH_MIPS)
#include "platform/assert.h"
namespace dart {
class MIPSDecoder : public ValueObject {
public:
MIPSDecoder(char* buffer, size_t buffer_size)
: buffer_(buffer),
buffer_size_(buffer_size),
buffer_pos_(0) {
buffer_[buffer_pos_] = '\0';
}
~MIPSDecoder() {}
// Writes one disassembled instruction into 'buffer' (0-terminated).
// Returns true if the instruction was successfully decoded, false otherwise.
void InstructionDecode(Instr* instr);
private:
// Bottleneck functions to print into the out_buffer.
void Print(const char* str);
// Printing of common values.
void PrintRegister(Register reg);
void PrintFRegister(FRegister reg);
void PrintFormat(Instr* instr);
int FormatRegister(Instr* instr, const char* format);
int FormatFRegister(Instr* instr, const char* format);
int FormatOption(Instr* instr, const char* format);
void Format(Instr* instr, const char* format);
void Unknown(Instr* instr);
void DecodeSpecial(Instr* instr);
void DecodeSpecial2(Instr* instr);
void DecodeRegImm(Instr* instr);
void DecodeCop1(Instr* instr);
// Convenience functions.
char* get_buffer() const { return buffer_; }
char* current_position_in_buffer() { return buffer_ + buffer_pos_; }
size_t remaining_size_in_buffer() { return buffer_size_ - buffer_pos_; }
char* buffer_; // Decode instructions into this buffer.
size_t buffer_size_; // The size of the character buffer.
size_t buffer_pos_; // Current character position in buffer.
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(MIPSDecoder);
};
// Support for assertions in the MIPSDecoder formatting functions.
#define STRING_STARTS_WITH(string, compare_string) \
(strncmp(string, compare_string, strlen(compare_string)) == 0)
// Append the str to the output buffer.
void MIPSDecoder::Print(const char* str) {
char cur = *str++;
while (cur != '\0' && (buffer_pos_ < (buffer_size_ - 1))) {
buffer_[buffer_pos_++] = cur;
cur = *str++;
}
buffer_[buffer_pos_] = '\0';
}
static const char* reg_names[kNumberOfCpuRegisters] = {
"r0" , "r1" , "r2" , "r3" , "r4" , "r5" , "r6" , "r7" ,
"r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15",
"r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
"r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31",
};
static const char* freg_names[kNumberOfFRegisters] = {
"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
"f8" , "f9" , "f10", "f11", "f12", "f13", "f14", "f15",
"f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
"f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31",
};
void MIPSDecoder::PrintRegister(Register reg) {
ASSERT(0 <= reg);
ASSERT(reg < kNumberOfCpuRegisters);
Print(reg_names[reg]);
}
void MIPSDecoder::PrintFRegister(FRegister reg) {
ASSERT(0 <= reg);
ASSERT(reg < kNumberOfFRegisters);
Print(freg_names[reg]);
}
// Handle all register based formatting in these functions to reduce the
// complexity of FormatOption.
int MIPSDecoder::FormatRegister(Instr* instr, const char* format) {
ASSERT(format[0] == 'r');
switch (format[1]) {
case 's': { // 'rs: Rs register
PrintRegister(instr->RsField());
return 2;
}
case 't': { // 'rt: Rt register
PrintRegister(instr->RtField());
return 2;
}
case 'd': { // 'rd: Rd register
PrintRegister(instr->RdField());
return 2;
}
}
UNREACHABLE();
return -1;
}
int MIPSDecoder::FormatFRegister(Instr* instr, const char* format) {
ASSERT(format[0] == 'f');
switch (format[1]) {
case 's': { // 'fs: Fs register
PrintFRegister(instr->FsField());
return 2;
}
case 't': { // 'ft: Ft register
PrintFRegister(instr->FtField());
return 2;
}
case 'd': { // 'fd: Fd register
PrintFRegister(instr->FdField());
return 2;
}
}
UNREACHABLE();
return -1;
}
void MIPSDecoder::PrintFormat(Instr *instr) {
switch (instr->FormatField()) {
case FMT_S: {
Print("s");
break;
}
case FMT_D: {
Print("d");
break;
}
case FMT_W: {
Print("w");
break;
}
case FMT_L: {
Print("l");
break;
}
case FMT_PS: {
Print("ps");
break;
}
default: {
Print("unknown");
break;
}
}
}
// FormatOption takes a formatting string and interprets it based on
// the current instructions. The format string points to the first
// character of the option string (the option escape has already been
// consumed by the caller.) FormatOption returns the number of
// characters that were consumed from the formatting string.
int MIPSDecoder::FormatOption(Instr* instr, const char* format) {
switch (format[0]) {
case 'c': {
ASSERT(STRING_STARTS_WITH(format, "code"));
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%d", instr->BreakCodeField());
return 4;
}
case 'h': {
ASSERT(STRING_STARTS_WITH(format, "hint"));
if (instr->SaField() == 0x10) {
// The high bit of the SA field is the only one that means something for
// JALR and JR. TODO(zra): Fill in the other cases for PREF if needed.
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
".hb");
} else if (instr->SaField() != 0) {
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
".unknown");
}
return 4;
}
case 'd': {
ASSERT(STRING_STARTS_WITH(format, "dest"));
int off = instr->SImmField() << 2;
uword destination =
reinterpret_cast<uword>(instr) + off + Instr::kInstrSize;
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%#"Px"",
destination);
return 4;
}
case 'i': {
ASSERT(STRING_STARTS_WITH(format, "imm"));
if (format[3] == 'u') {
int32_t imm = instr->UImmField();
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"0x%x",
imm);
} else {
ASSERT(STRING_STARTS_WITH(format, "imms"));
int32_t imm = instr->SImmField();
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%d",
imm);
}
return 4;
}
case 'r': {
return FormatRegister(instr, format);
}
case 'f': {
if (format[1] == 'm') {
ASSERT(STRING_STARTS_WITH(format, "fmt"));
PrintFormat(instr);
return 3;
} else {
return FormatFRegister(instr, format);
}
}
case 's': {
ASSERT(STRING_STARTS_WITH(format, "sa"));
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%d",
instr->SaField());
return 2;
}
default: {
UNREACHABLE();
}
}
UNREACHABLE();
return -1;
}
// Format takes a formatting string for a whole instruction and prints it into
// the output buffer. All escaped options are handed to FormatOption to be
// parsed further.
void MIPSDecoder::Format(Instr* instr, const char* format) {
char cur = *format++;
while ((cur != 0) && (buffer_pos_ < (buffer_size_ - 1))) {
if (cur == '\'') { // Single quote is used as the formatting escape.
format += FormatOption(instr, format);
} else {
buffer_[buffer_pos_++] = cur;
}
cur = *format++;
}
buffer_[buffer_pos_] = '\0';
}
// For currently unimplemented decodings the disassembler calls Unknown(instr)
// which will just print "unknown" of the instruction bits.
void MIPSDecoder::Unknown(Instr* instr) {
Format(instr, "unknown");
}
void MIPSDecoder::DecodeSpecial(Instr* instr) {
ASSERT(instr->OpcodeField() == SPECIAL);
switch (instr->FunctionField()) {
case ADDU: {
Format(instr, "addu 'rd, 'rs, 'rt");
break;
}
case AND: {
Format(instr, "and 'rd, 'rs, 'rt");
break;
}
case BREAK: {
Format(instr, "break 'code");
break;
}
case DIV: {
Format(instr, "div 'rs, 'rt");
break;
}
case DIVU: {
Format(instr, "divu 'rs, 'rt");
break;
}
case JALR: {
Format(instr, "jalr'hint 'rd, 'rs");
break;
}
case JR: {
Format(instr, "jr'hint 'rs");
break;
}
case MFHI: {
Format(instr, "mfhi 'rd");
break;
}
case MFLO: {
Format(instr, "mflo 'rd");
break;
}
case MOVCI: {
if (instr->Bit(16)) {
Format(instr, "movt 'rd, 'rs");
} else {
Format(instr, "movf 'rd, 'rs");
}
break;
}
case MOVN: {
Format(instr, "movn 'rd, 'rs, 'rt");
break;
}
case MOVZ: {
Format(instr, "movz 'rd, 'rs, 'rt");
break;
}
case MTHI: {
Format(instr, "mthi 'rs");
break;
}
case MTLO: {
Format(instr, "mtlo 'rs");
break;
}
case MULT: {
Format(instr, "mult 'rs, 'rt");
break;
}
case MULTU: {
Format(instr, "multu 'rs, 'rt");
break;
}
case NOR: {
Format(instr, "nor 'rd, 'rs, 'rt");
break;
}
case OR: {
if (instr->RsField() == 0 && instr->RtField() == 0) {
Format(instr, "mov 'rd, 0");
} else if (instr->RsField() == R0) {
Format(instr, "mov 'rd, 'rt");
} else if (instr->RtField() == R0) {
Format(instr, "mov 'rd, 'rs");
} else {
Format(instr, "or 'rd, 'rs, 'rt");
}
break;
}
case SLL: {
if ((instr->RdField() == R0) &&
(instr->RtField() == R0) &&
(instr->SaField() == 0)) {
Format(instr, "nop");
} else {
Format(instr, "sll 'rd, 'rt, 'sa");
}
break;
}
case SLLV: {
Format(instr, "sllv 'rd, 'rt, 'rs");
break;
}
case SLT: {
Format(instr, "slt 'rd, 'rs, 'rt");
break;
}
case SLTU: {
Format(instr, "sltu 'rd, 'rs, 'rt");
break;
}
case SRA: {
if (instr->RsField() == 0) {
Format(instr, "sra 'rd, 'rt, 'sa");
} else {
Unknown(instr);
}
break;
}
case SRAV: {
Format(instr, "srav 'rd, 'rt, 'rs");
break;
}
case SRL: {
if (instr->RsField() == 0) {
Format(instr, "srl 'rd, 'rt, 'sa");
} else {
Unknown(instr);
}
break;
}
case SRLV: {
if (instr->SaField() == 0) {
Format(instr, "srlv 'rd, 'rt, 'rs");
} else {
Unknown(instr);
}
break;
}
case SUBU: {
Format(instr, "subu 'rd, 'rs, 'rt");
break;
}
case XOR: {
Format(instr, "xor 'rd, 'rs, 'rt");
break;
}
default: {
Unknown(instr);
break;
}
}
}
void MIPSDecoder::DecodeSpecial2(Instr* instr) {
ASSERT(instr->OpcodeField() == SPECIAL2);
switch (instr->FunctionField()) {
case MADD: {
Format(instr, "madd 'rs, 'rt");
break;
}
case MADDU: {
Format(instr, "maddu 'rs, 'rt");
break;
}
case CLO: {
Format(instr, "clo 'rd, 'rs");
break;
}
case CLZ: {
Format(instr, "clz 'rd, 'rs");
break;
}
default: {
Unknown(instr);
break;
}
}
}
void MIPSDecoder::DecodeRegImm(Instr* instr) {
ASSERT(instr->OpcodeField() == REGIMM);
switch (instr->RegImmFnField()) {
case BGEZ: {
Format(instr, "bgez 'rs, 'dest");
break;
}
case BGEZAL: {
Format(instr, "bgezal 'rs, 'dest");
break;
}
case BLTZAL: {
Format(instr, "bltzal 'rs, 'dest");
break;
}
case BGEZL: {
Format(instr, "bgezl 'rs, 'dest");
break;
}
case BLTZ: {
Format(instr, "bltz 'rs, 'dest");
break;
}
case BLTZL: {
Format(instr, "bltzl 'rs, 'dest");
break;
}
default: {
Unknown(instr);
break;
}
}
}
void MIPSDecoder::DecodeCop1(Instr* instr) {
ASSERT(instr->OpcodeField() == COP1);
if (instr->HasFormat()) {
// If the rs field is a valid format, then the function field identifies
// the instruction.
switch (instr->Cop1FunctionField()) {
case COP1_ADD: {
Format(instr, "add.'fmt 'fd, 'fs, 'ft");
break;
}
case COP1_SUB: {
Format(instr, "sub.'fmt 'fd, 'fs, 'ft");
break;
}
case COP1_MUL: {
Format(instr, "mul.'fmt 'fd, 'fs, 'ft");
break;
}
case COP1_DIV: {
Format(instr, "div.'fmt 'fd, 'fs, 'ft");
break;
}
case COP1_SQRT: {
Format(instr, "sqrt.'fmt 'fd, 'fs");
break;
}
case COP1_MOV: {
Format(instr, "mov.'fmt 'fd, 'fs");
break;
}
case COP1_C_F: {
Format(instr, "c.f.'fmt 'fs, 'ft");
break;
}
case COP1_C_UN: {
Format(instr, "c.un.'fmt 'fs, 'ft");
break;
}
case COP1_C_EQ: {
Format(instr, "c.eq.'fmt 'fs, 'ft");
break;
}
case COP1_C_UEQ: {
Format(instr, "c.ueq.'fmt 'fs, 'ft");
break;
}
case COP1_C_OLT: {
Format(instr, "c.olt.'fmt 'fs, 'ft");
break;
}
case COP1_C_ULT: {
Format(instr, "c.ult.'fmt 'fs, 'ft");
break;
}
case COP1_C_OLE: {
Format(instr, "c.ole.'fmt 'fs, 'ft");
break;
}
case COP1_C_ULE: {
Format(instr, "c.ule.'fmt 'fs, 'ft");
break;
}
case COP1_CVT_D: {
Format(instr, "cvt.d.'fmt 'fd, 'fs");
break;
}
case COP1_CVT_W: {
Format(instr, "cvt.w.'fmt 'fd, 'fs");
break;
}
default: {
Unknown(instr);
break;
}
}
} else {
// If the rs field isn't a valid format, then it must be a sub-opcode.
switch (instr->Cop1SubField()) {
case COP1_MF: {
if (instr->Bits(0, 11) != 0) {
Unknown(instr);
} else {
Format(instr, "mfc1 'rt, 'fs");
}
break;
}
case COP1_MT: {
if (instr->Bits(0, 11) != 0) {
Unknown(instr);
} else {
Format(instr, "mtc1 'rt, 'fs");
}
break;
}
case COP1_BC: {
ASSERT(instr->Bit(17) == 0);
if (instr->Bit(16) == 1) { // Branch on true.
Format(instr, "bc1t 'dest");
} else { // Branch on false.
Format(instr, "bc1f 'dest");
}
break;
}
default: {
Unknown(instr);
break;
}
}
}
}
void MIPSDecoder::InstructionDecode(Instr* instr) {
switch (instr->OpcodeField()) {
case SPECIAL: {
DecodeSpecial(instr);
break;
}
case SPECIAL2: {
DecodeSpecial2(instr);
break;
}
case REGIMM: {
DecodeRegImm(instr);
break;
}
case COP1: {
DecodeCop1(instr);
break;
}
case ADDIU: {
Format(instr, "addiu 'rt, 'rs, 'imms");
break;
}
case ANDI: {
Format(instr, "andi 'rt, 'rs, 'immu");
break;
}
case BEQ: {
Format(instr, "beq 'rs, 'rt, 'dest");
break;
}
case BEQL: {
Format(instr, "beql 'rs, 'rt, 'dest");
break;
}
case BGTZ: {
Format(instr, "bgtz 'rs, 'dest");
break;
}
case BGTZL: {
Format(instr, "bgtzl 'rs, 'dest");
break;
}
case BLEZ: {
Format(instr, "blez 'rs, 'dest");
break;
}
case BLEZL: {
Format(instr, "blezl 'rs, 'dest");
break;
}
case BNE: {
Format(instr, "bne 'rs, 'rt, 'dest");
break;
}
case BNEL: {
Format(instr, "bnel 'rs, 'rt, 'dest");
break;
}
case LB: {
Format(instr, "lb 'rt, 'imms('rs)");
break;
}
case LBU: {
Format(instr, "lbu 'rt, 'imms('rs)");
break;
}
case LDC1: {
Format(instr, "ldc1 'ft, 'imms('rs)");
break;
}
case LH: {
Format(instr, "lh 'rt, 'imms('rs)");
break;
}
case LHU: {
Format(instr, "lhu 'rt, 'imms('rs)");
break;
}
case LUI: {
Format(instr, "lui 'rt, 'immu");
break;
}
case LW: {
Format(instr, "lw 'rt, 'imms('rs)");
break;
}
case LWC1: {
Format(instr, "lwc1 'ft, 'imms('rs)");
break;
}
case ORI: {
Format(instr, "ori 'rt, 'rs, 'immu");
break;
}
case SB: {
Format(instr, "sb 'rt, 'imms('rs)");
break;
}
case SLTI: {
Format(instr, "slti 'rt, 'rs, 'imms");
break;
}
case SLTIU: {
Format(instr, "sltu 'rt, 'rs, 'immu");
break;
}
case SH: {
Format(instr, "sh 'rt, 'imms('rs)");
break;
}
case SDC1: {
Format(instr, "sdc1 'ft, 'imms('rs)");
break;
}
case SW: {
Format(instr, "sw 'rt, 'imms('rs)");
break;
}
case SWC1: {
Format(instr, "swc1 'ft, 'imms('rs)");
break;
}
case XORI: {
Format(instr, "xori 'rt, 'rs, 'immu");
break;
}
default: {
Unknown(instr);
break;
}
}
}
void Disassembler::DecodeInstruction(char* hex_buffer, intptr_t hex_size,
char* human_buffer, intptr_t human_size,
int* out_instr_len, uword pc) {
MIPSDecoder decoder(human_buffer, human_size);
Instr* instr = Instr::At(pc);
decoder.InstructionDecode(instr);
OS::SNPrint(hex_buffer, hex_size, "%08x", instr->InstructionBits());
if (out_instr_len) {
*out_instr_len = Instr::kInstrSize;
}
}
void Disassembler::Disassemble(uword start,
uword end,
DisassemblyFormatter* formatter,
const Code::Comments& comments) {
ASSERT(formatter != NULL);
char hex_buffer[kHexadecimalBufferSize]; // Instruction in hexadecimal form.
char human_buffer[kUserReadableBufferSize]; // Human-readable instruction.
uword pc = start;
intptr_t comment_finger = 0;
while (pc < end) {
const intptr_t offset = pc - start;
while (comment_finger < comments.Length() &&
comments.PCOffsetAt(comment_finger) <= offset) {
formatter->Print(
" ;; %s\n",
String::Handle(comments.CommentAt(comment_finger)).ToCString());
comment_finger++;
}
int instruction_length;
DecodeInstruction(hex_buffer, sizeof(hex_buffer),
human_buffer, sizeof(human_buffer),
&instruction_length, pc);
formatter->ConsumeInstruction(hex_buffer,
sizeof(hex_buffer),
human_buffer,
sizeof(human_buffer),
pc);
pc += instruction_length;
}
return;
}
} // namespace dart
#endif // defined TARGET_ARCH_MIPS