219 lines
5.6 KiB
C++
219 lines
5.6 KiB
C++
//---------------------------------------------------------------------------//
|
|
// 1100101 |_ mov #0, r4 __ //
|
|
// 11 |_ <0xb380 %5c4> / _|_ _____ ___ //
|
|
// 0110 |_ 3.50 -> 3.60 | _\ \ / _ (_-< //
|
|
// |_ base# + offset |_| /_\_\___/__/ //
|
|
//---------------------------------------------------------------------------//
|
|
|
|
#include <fxos/lang.h>
|
|
#include <fxos/util/format.h>
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <cassert>
|
|
|
|
namespace FxOS {
|
|
|
|
//---
|
|
// CPU registers
|
|
//---
|
|
|
|
// clang-format off
|
|
char const *regnames[] = {
|
|
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
|
"r0_bank", "r1_bank", "r2_bank", "r3_bank",
|
|
"r4_bank", "r5_bank", "r6_bank", "r7_bank",
|
|
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
|
|
"mach", "macl", "pr", "pc",
|
|
"sr", "ssr", "spc", "gbr", "vbr", "dbr", "sgr"
|
|
};
|
|
// clang-format on
|
|
|
|
CpuRegister::CpuRegister(std::string name)
|
|
{
|
|
int regcount = (sizeof regnames / sizeof regnames[0]);
|
|
char const *name_c = name.c_str();
|
|
|
|
for(int i = 0; i < regcount; i++) {
|
|
if(!strcmp(regnames[i], name_c)) {
|
|
m_name = CpuRegisterName(i + 1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_name = CpuRegister::UNDEFINED;
|
|
}
|
|
|
|
std::string CpuRegister::str() const noexcept
|
|
{
|
|
int regcount = (sizeof regnames / sizeof regnames[0]);
|
|
int i = m_name - 1;
|
|
|
|
if(i < 0 || i >= regcount)
|
|
return format("<Register%d>", i + 1);
|
|
return regnames[i];
|
|
}
|
|
|
|
int CpuRegister::getRn() const
|
|
{
|
|
if(m_name >= R0 && m_name <= R7)
|
|
return m_name - R0;
|
|
if(m_name >= R8 && m_name <= R15)
|
|
return m_name - R8 + 8;
|
|
|
|
return -1;
|
|
}
|
|
|
|
CpuRegister CpuRegister::makeRn(int n)
|
|
{
|
|
assert(n >= 0 && n < 16 && "makeRn: invalid number");
|
|
CpuRegister r;
|
|
int name = (n >= 8) ? (R8 + (n - 8)) : (R0 + n);
|
|
r.m_name = static_cast<CpuRegisterName>(name);
|
|
return r;
|
|
}
|
|
|
|
//---
|
|
// Instruction operands
|
|
//---
|
|
|
|
AsmOperand::AsmOperand(): m_kind {Reg}, m_base {CpuRegister::R0}, m_opsize {0}
|
|
{
|
|
}
|
|
|
|
AsmOperand::AsmOperand(Kind kind, CpuRegister base, i8 opsize):
|
|
m_kind {kind}, m_base {base}, m_opsize {opsize}
|
|
{
|
|
}
|
|
|
|
AsmOperand::AsmOperand(int disp, i8 opsize, CpuRegister base):
|
|
m_kind {StructDeref}, m_base {base}, m_opsize {opsize}, m_disp_imm {disp}
|
|
{
|
|
}
|
|
|
|
AsmOperand::AsmOperand(CpuRegister index, CpuRegister base, i8 opsize):
|
|
m_kind {ArrayDeref}, m_base {base}, m_index {index}, m_opsize {opsize}
|
|
{
|
|
}
|
|
|
|
AsmOperand::AsmOperand(Kind kind, int disp_imm, i8 opsize):
|
|
m_kind {kind}, m_opsize {opsize}, m_disp_imm {disp_imm}
|
|
{
|
|
}
|
|
|
|
std::string AsmOperand::str() const
|
|
{
|
|
switch(m_kind) {
|
|
case Reg:
|
|
return m_base.str();
|
|
case Deref:
|
|
return format("@%s", m_base.str());
|
|
case PostInc:
|
|
return format("@%s+", m_base.str());
|
|
case PreDec:
|
|
return format("@-%s", m_base.str());
|
|
case StructDeref:
|
|
return format("@(%d,%s)", m_disp_imm, m_base.str().c_str());
|
|
case ArrayDeref:
|
|
return format("@(%s,%s)", m_index.str().c_str(), m_base.str().c_str());
|
|
case PcRel:
|
|
return format("@(%d,pc)", m_disp_imm);
|
|
case PcJump:
|
|
return format("pc+%d", m_disp_imm);
|
|
case PcAddr:
|
|
return format("pc+%u", m_disp_imm);
|
|
case Imm:
|
|
return format("#%d", m_disp_imm);
|
|
default:
|
|
return "(invalid)";
|
|
}
|
|
}
|
|
|
|
u32 AsmOperand::getPCRelativeTarget(u32 pc) const
|
|
{
|
|
int size = m_opsize + (m_opsize == 0);
|
|
|
|
if(m_kind == AsmOperand::PcRel)
|
|
return (pc & -size) + 4 + m_disp_imm;
|
|
if(m_kind == AsmOperand::PcJump)
|
|
return pc + 4 + m_disp_imm;
|
|
if(m_kind == AsmOperand::PcAddr)
|
|
return (pc & -4) + 4 + m_disp_imm;
|
|
|
|
/* SH3 manual says that mova uses the target address of the jump when
|
|
in a delay slot. SH4AL-DSP makes it invalid. Supporting this would
|
|
be very tricky since the target PC is often dynamic (eg. rts). */
|
|
|
|
return -1;
|
|
}
|
|
|
|
//---
|
|
// Instruction management
|
|
//---
|
|
|
|
static char const *instructionMnemonics[] = {
|
|
#define GENDEFS_INSN(NAME, STR) [AsmInstruction::SH_##NAME] = STR,
|
|
#include "gendefs/insn.h"
|
|
#undef GENDEFS_INSN
|
|
};
|
|
|
|
AsmInstruction::AsmInstruction(u32 encoding, char const *mnemonic, int tags,
|
|
int opCount, AsmOperand op1, AsmOperand op2):
|
|
m_encoding {encoding},
|
|
m_opsize {0}, m_opCount {(u8)opCount}, m_tags {(u16)tags}, m_ops {op1, op2}
|
|
{
|
|
std::string mn {mnemonic};
|
|
|
|
if(mn.ends_with(".b"))
|
|
m_opsize = 1;
|
|
else if(mn.ends_with(".w"))
|
|
m_opsize = 2;
|
|
else if(mn.ends_with(".l"))
|
|
m_opsize = 4;
|
|
|
|
if(m_opsize != 0)
|
|
mn = mn.substr(0, mn.size() - 2);
|
|
|
|
int i;
|
|
for(i = 0; i < SH_MAX; i++) {
|
|
if(mn == instructionMnemonics[i]) {
|
|
m_operation = i;
|
|
break;
|
|
}
|
|
}
|
|
assert(i < SH_MAX && "AsmInstruction with unknown operation string");
|
|
}
|
|
|
|
std::string AsmInstruction::mnemonic() const
|
|
{
|
|
return std::string {operationString()} + operationSizeString();
|
|
}
|
|
|
|
char const *AsmInstruction::operationString() const
|
|
{
|
|
assert(m_operation < SH_MAX);
|
|
return instructionMnemonics[m_operation];
|
|
}
|
|
|
|
char const *AsmInstruction::operationSizeString() const
|
|
{
|
|
if(m_opsize == 1)
|
|
return ".b";
|
|
if(m_opsize == 2)
|
|
return ".w";
|
|
if(m_opsize == 4)
|
|
return ".l";
|
|
return "";
|
|
}
|
|
|
|
u32 AsmInstruction::getPCRelativeTarget(u32 pc) const
|
|
{
|
|
/* There can only be at most one PC-relative operand in an instruction */
|
|
for(AsmOperand const &op: operands()) {
|
|
if(op.usesPCRelativeAddressing())
|
|
return op.getPCRelativeTarget(pc);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
} /* namespace FxOS */
|