jak-project/decompiler/Disasm/Instruction.cpp

376 lines
7.8 KiB
C++
Raw Normal View History

2020-08-22 23:30:17 -04:00
/*!
* @file Instruction.cpp
* An EE instruction, represented as an operation, plus a list of source/destination atoms.
* Can print itself (within the context of a LinkedObjectFile).
*/
#include "Instruction.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
2021-02-13 12:05:50 -05:00
#include "third-party/fmt/core.h"
2020-08-22 23:30:17 -04:00
#include <cassert>
namespace decompiler {
2020-08-22 23:30:17 -04:00
/*!
* Convert atom to a string for disassembly.
*/
std::string InstructionAtom::to_string(const std::vector<DecompilerLabel>& labels) const {
2020-08-22 23:30:17 -04:00
switch (kind) {
case REGISTER:
return reg.to_string();
case IMM:
return std::to_string(imm);
case LABEL:
return labels.at(label_id).name;
2020-08-22 23:30:17 -04:00
case VU_ACC:
return "acc";
case VU_Q:
return "Q";
case IMM_SYM:
return sym;
2021-02-13 12:05:50 -05:00
case VF_FIELD:
assert(imm >= 0 && imm < 4);
return fmt::format(".{}", "xyzw"[imm]);
2020-08-22 23:30:17 -04:00
default:
throw std::runtime_error("Unsupported InstructionAtom");
2020-08-22 23:30:17 -04:00
}
}
/*!
* Make this atom a register.
*/
void InstructionAtom::set_reg(Register r) {
kind = REGISTER;
reg = r;
}
/*!
* Make this atom an immediate.
*/
void InstructionAtom::set_imm(int32_t i) {
kind = IMM;
imm = i;
}
/*!
* Make this atom a label.
*/
void InstructionAtom::set_label(int id) {
kind = LABEL;
label_id = id;
}
/*!
* Make this atom the VU ACC register.
*/
void InstructionAtom::set_vu_acc() {
kind = VU_ACC;
}
/*!
* Make this atom the VU0 Q register.
*/
void InstructionAtom::set_vu_q() {
kind = VU_Q;
}
/*!
* Make this atom a symbol.
*/
void InstructionAtom::set_sym(std::string _sym) {
kind = IMM_SYM;
sym = std::move(_sym);
}
2021-02-13 12:05:50 -05:00
/*!
* Make this atom a field (x,y,z,w) of a vf.
*/
void InstructionAtom::set_vf_field(uint32_t value) {
kind = VF_FIELD;
imm = value;
assert(value < 4);
}
2020-08-22 23:30:17 -04:00
/*!
* Get as register, or error if not a register.
*/
Register InstructionAtom::get_reg() const {
assert(kind == REGISTER);
return reg;
}
/*!
* Get as integer immediate, or error if not an integer immediate.
*/
int32_t InstructionAtom::get_imm() const {
assert(kind == IMM);
return imm;
}
/*!
* Get as label index, or error if not a label.
*/
int InstructionAtom::get_label() const {
assert(kind == LABEL);
return label_id;
}
/*!
* Get as symbol, or error if not a symbol.
*/
std::string InstructionAtom::get_sym() const {
assert(kind == IMM_SYM);
return sym;
}
/*!
* True if this atom is some sort of constant that doesn't involve linking.
*/
bool InstructionAtom::is_link_or_label() const {
return kind == IMM_SYM || kind == LABEL;
}
bool InstructionAtom::operator==(const InstructionAtom& other) const {
if (kind != other.kind) {
return false;
}
switch (kind) {
case REGISTER:
return reg == other.reg;
case IMM:
return imm == other.imm;
case LABEL:
return label_id == other.label_id;
case VU_ACC:
case VU_Q:
return true;
default:
assert(false);
}
}
2020-08-22 23:30:17 -04:00
/*!
* Convert just the name of the opcode to a string, omitting src/dst, but including
* suffixes (interlock, broadcasts and destination)
2020-08-22 23:30:17 -04:00
*/
std::string Instruction::op_name_to_string() const {
2020-08-22 23:30:17 -04:00
auto& info = gOpcodeInfo[(int)kind];
// the name
std::string result = info.name;
// optional "interlock" specification.
if (il != 0xff) {
result.append(il ? ".i" : ".ni");
}
// optional "broadcast" specification for COP2 opcodes.
if (cop2_bc != 0xff) {
switch (cop2_bc) {
case 0:
result.push_back('x');
break;
case 1:
result.push_back('y');
break;
case 2:
result.push_back('z');
break;
case 3:
result.push_back('w');
break;
default:
result.push_back('?');
break;
}
}
// optional "destination" specification for COP2 opcodes.
if (cop2_dest != 0xff) {
result += ".";
if (cop2_dest & 8)
result.push_back('x');
if (cop2_dest & 4)
result.push_back('y');
if (cop2_dest & 2)
result.push_back('z');
if (cop2_dest & 1)
result.push_back('w');
}
return result;
}
/*!
* Convert entire instruction to a string.
*/
std::string Instruction::to_string(const std::vector<DecompilerLabel>& labels) const {
auto& info = gOpcodeInfo[(int)kind];
auto result = op_name_to_string();
2020-08-22 23:30:17 -04:00
// relative store and load instructions have a special syntax in MIPS
if (info.is_store) {
assert(n_dst == 0);
assert(n_src == 3);
result += " ";
result += src[0].to_string(labels);
2020-08-22 23:30:17 -04:00
result += ", ";
result += src[1].to_string(labels);
2020-08-22 23:30:17 -04:00
result += "(";
result += src[2].to_string(labels);
2020-08-22 23:30:17 -04:00
result += ")";
} else if (info.is_load) {
assert(n_dst == 1);
assert(n_src == 2);
result += " ";
result += dst[0].to_string(labels);
2020-08-22 23:30:17 -04:00
result += ", ";
result += src[0].to_string(labels);
2020-08-22 23:30:17 -04:00
result += "(";
result += src[1].to_string(labels);
2020-08-22 23:30:17 -04:00
result += ")";
} else {
// for instructions that aren't a store or load, the dest/sources are comma separated.
bool end_comma = false;
for (uint8_t i = 0; i < n_dst; i++) {
result += " " + dst[i].to_string(labels) + ",";
2020-08-22 23:30:17 -04:00
end_comma = true;
}
for (uint8_t i = 0; i < n_src; i++) {
2021-02-13 12:05:50 -05:00
if (src[i].kind == InstructionAtom::VF_FIELD) {
if (end_comma) {
result.pop_back();
}
result += src[i].to_string(labels) + ",";
} else {
result += " " + src[i].to_string(labels) + ",";
}
2020-08-22 23:30:17 -04:00
end_comma = true;
}
if (end_comma) {
result.pop_back();
}
}
return result;
}
/*!
* Was this instruction successfully decoded?
*/
bool Instruction::is_valid() const {
return kind != InstructionKind::UNKNOWN;
}
/*!
* Add a destination atom to this Instruction
*/
void Instruction::add_dst(InstructionAtom& a) {
assert(n_dst < MAX_INTRUCTION_DEST);
dst[n_dst++] = a;
}
/*!
* Add a source atom to this Instruction
*/
void Instruction::add_src(InstructionAtom& a) {
assert(n_src < MAX_INSTRUCTION_SOURCE);
src[n_src++] = a;
}
/*!
* Get a source atom that's an immediate, or error if it doesn't exist.
*/
InstructionAtom& Instruction::get_imm_src() {
for (int i = 0; i < n_src; i++) {
if (src[i].kind == InstructionAtom::IMM) {
return src[i];
}
}
assert(false);
return src[0];
}
/*!
* Try to find a src which is an integer immediate, and return it as an integer.
*/
int32_t Instruction::get_imm_src_int() {
return get_imm_src().get_imm();
}
/*!
* Safe get dst atom
*/
InstructionAtom& Instruction::get_dst(size_t idx) {
assert(idx < n_dst);
return dst[idx];
}
/*!
* Safe get src atom
*/
InstructionAtom& Instruction::get_src(size_t idx) {
assert(idx < n_src);
return src[idx];
}
/*!
* Safe get dst atom
*/
const InstructionAtom& Instruction::get_dst(size_t idx) const {
assert(idx < n_dst);
return dst[idx];
}
/*!
* Safe get src atom
*/
const InstructionAtom& Instruction::get_src(size_t idx) const {
assert(idx < n_src);
return src[idx];
}
/*!
* Get OpcodeInfo for the opcode used in this instruction.
*/
const OpcodeInfo& Instruction::get_info() const {
return gOpcodeInfo[int(kind)];
}
/*!
* Get the target label for this instruction. If the instruction doesn't have a target label,
* return -1.
*/
int Instruction::get_label_target() const {
int result = -1;
for (int i = 0; i < n_src; i++) {
if (src[i].kind == InstructionAtom::AtomKind::LABEL) {
assert(result == -1);
result = src[i].get_label();
}
}
return result;
}
bool Instruction::operator==(const Instruction& other) const {
if (kind != other.kind || n_src != other.n_src || n_dst != other.n_dst ||
cop2_dest != other.cop2_dest || cop2_bc != other.cop2_bc || il != other.il) {
return false;
}
for (int i = 0; i < n_dst; i++) {
if (dst[i] != other.dst[i]) {
return false;
}
}
for (int i = 0; i < n_src; i++) {
if (src[i] != other.src[i]) {
return false;
}
}
return true;
}
} // namespace decompiler