summaryrefslogtreecommitdiff
path: root/src/m68k.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/m68k.cpp')
-rw-r--r--src/m68k.cpp2012
1 files changed, 2012 insertions, 0 deletions
diff --git a/src/m68k.cpp b/src/m68k.cpp
new file mode 100644
index 0000000..254c898
--- /dev/null
+++ b/src/m68k.cpp
@@ -0,0 +1,2012 @@
+/* SPDX-License-Identifier: Unlicense
+ */
+
+#include "m68k.h"
+#include "data_buffer.h"
+#include "common.h"
+
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+enum class MoveDirection: bool {
+ kRegisterToMemory = 0,
+ kMemoryToRegister = 1,
+};
+
+enum class ShiftDirection: bool {
+ kRight = 0,
+ kLeft = 1,
+};
+
+enum class ShiftKind: int {
+ kArithmeticShift = 0,
+ kLogicalShift = 1,
+ kRotateX = 2,
+ kRotate = 3,
+};
+
+constexpr Arg FetchImmediate(const uint32_t address, const DataView &code, const OpSize s)
+{
+ if (s == OpSize::kInvalid) {
+ return Arg{};
+ } else if (s == OpSize::kLong) {
+ if (address + kInstructionSizeStepBytes < code.size) {
+ const int32_t value = GetI32BE(code.buffer + address);
+ return Arg::Immediate(value);
+ }
+ } else if (address < code.size) {
+ const int16_t value = GetI16BE(code.buffer + address);
+ if (s == OpSize::kByte) {
+ // Technically it is impossible to have value lower that -128 in 8
+ // bits signed integer, but the second byte being 0xff is actually
+ // a valid thing and it is how values from -255 to -129 are
+ // represented.
+ if (value > 255 || value < -255) {
+ // Invalid immediate value for instruction with .b suffix
+ return Arg{};
+ }
+ }
+ return Arg::Immediate(value);
+ }
+ return Arg{};
+}
+
+constexpr Arg FetchArg(
+ const uint32_t address, const DataView &code, const int m, const int xn, const OpSize s)
+{
+ switch (m) {
+ case 0: // Dn
+ return Arg::Dn(xn);
+ case 1: // An
+ return Arg::An(xn);
+ case 2: // (An)
+ return Arg::AnAddr(xn);
+ case 3: // (An)+
+ return Arg::AnAddrIncr(xn);
+ case 4: // -(An)
+ return Arg::AnAddrDecr(xn);
+ case 5: // (d16, An), Additional Word
+ if (address < code.size) {
+ const int16_t d16 = GetI16BE(code.buffer + address);
+ return Arg::D16AnAddr(xn, d16);
+ }
+ break;
+ case 6: // (d8, An, Xi), Brief Extension Word
+ if (address < code.size) {
+ const uint16_t briefext = GetU16BE(code.buffer + address);
+ if (briefext & 0x0700) {
+ // briefext must have zeros on 8, 9 an 10-th bits,
+ // i.e. xxxx_x000_xxxx_xxxx
+ break;
+ }
+ // Xi number (lower 3 bits, mask 0x7) with An/Dn bit (mask 0x8)
+ const uint8_t xi = (briefext >> 12) & 0xf;
+ const OpSize s2 = ((briefext >> 11) & 1) ? OpSize::kLong : OpSize::kWord;
+ const int8_t d8 = briefext & 0xff;
+ return Arg::D8AnXiAddr(xn, xi, s2, d8);
+ }
+ break;
+ case 7:
+ switch (xn) {
+ case 0: // (xxx).W, Additional Word
+ if (address < code.size) {
+ const int32_t w = GetI16BE(code.buffer + address);
+ return Arg::Word(w);
+ }
+ break;
+ case 1: // (xxx).L, Additional Long
+ if (address + kInstructionSizeStepBytes < code.size) {
+ const int32_t l = GetI32BE(code.buffer + address);
+ return Arg::Long(l);
+ }
+ break;
+ case 2: // (d16, PC), Additional Word
+ if (address < code.size) {
+ const int16_t d16 = GetI16BE(code.buffer + address);
+ return Arg::D16PCAddr(d16);
+ }
+ break;
+ case 3: // (d8, PC, Xi), Brief Extension Word
+ if (address < code.size) {
+ const uint16_t briefext = GetU16BE(code.buffer + address);
+ if (briefext & 0x0700) {
+ // briefext must have zeros on 8, 9 an 10-th bits,
+ // i.e. xxxx_x000_xxxx_xxxx
+ break;
+ }
+ // Xi number (lower 3 bits, mask 0x7) with An/Dn bit (mask 0x8)
+ const uint8_t xi = (briefext >> 12) & 0xf;
+ const OpSize s2 = ((briefext >> 11) & 1) ? OpSize::kLong : OpSize::kWord;
+ const int8_t d8 = briefext & 0xff;
+ return Arg::D8PCXiAddr(xn, xi, s2, d8);
+ }
+ break;
+ case 4: // #imm
+ return FetchImmediate(address, code, s);
+ case 5: // Does not exist
+ case 6: // Does not exist
+ case 7: // Does not exist
+ break;
+ }
+ break;
+ }
+ return Arg{};
+}
+
+static Arg FetchArg(
+ const uint32_t address, const DataView &code, const uint16_t instr, const OpSize s)
+{
+ const int addrmode = instr & 0x3f;
+ const int m = (addrmode >> 3) & 7;
+ const int xn = addrmode & 7;
+ return FetchArg(address, code, m, xn, s);
+}
+
+static size_t disasm_verbatim(DisasmNode &node, const uint16_t instr)
+{
+ node.op = Op::Raw(instr);
+ return node.size;
+}
+
+static size_t disasm_jsr_jmp(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const OpSize opsize = OpSize::kWord;
+ const auto a = FetchArg(node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (a.mode) {
+ case AddrMode::kInvalid:
+ case AddrMode::kDn: // 4e80..4e87 / 4ec0..4ec7
+ case AddrMode::kAn: // 4e88..4e8f / 4ec8..4ecf
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr: // 4e90..4e97 / 4ed0..4ed7
+ // NOTE: dynamic jump, ref_addr may possibly be obtained during the
+ // trace
+ break;
+ case AddrMode::kAnAddrIncr: // 4e98..4e9f / 4ed8..4edf
+ case AddrMode::kAnAddrDecr: // 4ea0..4ea7 / 4ee0..4ee7
+ return disasm_verbatim(node, instr);
+ case AddrMode::kD16AnAddr: // 4ea8..4eaf / 4ee8..4eef
+ // NOTE: dynamic jump, ref_addr may possibly be obtained during the
+ // trace
+ break;
+ case AddrMode::kD8AnXiAddr: // 4eb0..4eb7 / 4ef0..4ef7
+ // NOTE: dynamic jump, ref_addr may possibly be obtained during the
+ // trace
+ break;
+ case AddrMode::kWord: // 4eb8 / 4ef8
+ {
+ const uint32_t ref_addr = static_cast<uint32_t>(a.lword);
+ node.ref1_addr = ref_addr;
+ node.ref_kinds = kRef1AbsMask;
+ }
+ break;
+ case AddrMode::kLong: // 4eb9 / 4ef9
+ {
+ const uint32_t ref_addr = static_cast<uint32_t>(a.lword);
+ node.ref1_addr = ref_addr;
+ node.ref_kinds = kRef1AbsMask;
+ }
+ break;
+ case AddrMode::kD16PCAddr: // 4eba / 4efa
+ {
+ const uint32_t ref_addr = node.address + kInstructionSizeStepBytes +
+ static_cast<uint32_t>(a.d16_pc.d16);
+ node.ref1_addr = ref_addr;
+ node.ref_kinds = kRef1RelMask;
+ }
+ break;
+ case AddrMode::kD8PCXiAddr: // 4ebb / 4efb
+ // NOTE: dynamic jump, ref_addr may possibly be obtained during the
+ // trace
+ break;
+ case AddrMode::kImmediate: // 4ebc / 4efc
+ return disasm_verbatim(node, instr);
+ }
+ const bool is_jmp = instr & 0x40;
+ node.ref_kinds |= is_jmp ? 0 : kRefCallMask;
+ node.op = Op::Typical(is_jmp ? OpCode::kJMP : OpCode::kJSR, OpSize::kNone, a);
+ return node.size = kInstructionSizeStepBytes + a.Size(opsize);
+}
+
+static size_t disasm_ext(DisasmNode &node, const OpSize opsize, const Arg arg)
+{
+ assert(arg.mode == AddrMode::kDn);
+ node.op = Op::Typical(OpCode::kEXT, opsize, arg);
+ return node.size = kInstructionSizeStepBytes + arg.Size(opsize);
+}
+
+static size_t disasm_ext_movem(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const auto dir = static_cast<MoveDirection>((instr >> 10) & 1);
+ const unsigned m = (instr >> 3) & 7;
+ const unsigned xn = instr & 7;
+ const auto opsize = static_cast<OpSize>(((instr >> 6) & 1) + 1);
+ if (m == 0 && dir == MoveDirection::kRegisterToMemory) {
+ return disasm_ext(node, opsize, Arg::Dn(xn));
+ }
+ if (node.address + kInstructionSizeStepBytes >= code.size) {
+ // Not enough space for regmask, but maybe it is just EXT?
+ return disasm_verbatim(node, instr);
+ }
+ const unsigned regmask = GetU16BE(code.buffer + node.address + kInstructionSizeStepBytes);
+ if (regmask == 0) {
+ // This is just not representable: at least one register must be specified
+ return disasm_verbatim(node, instr);
+ }
+ const auto a = FetchArg(
+ node.address + kInstructionSizeStepBytes * 2, code, m, xn, opsize);
+ switch (a.mode) {
+ case AddrMode::kInvalid:
+ case AddrMode::kDn: // 4880..4887 / 4c80..4c87 / 48c0..48c7 / 4cc0..4cc7
+ case AddrMode::kAn: // 4888..488f / 4c88..4c8f / 48c8..48cf / 4cc8..4ccf
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr: // 4890..4897 / 4c90..4c97 / 48d0..48d7 / 4cd0..4cd7
+ break;
+ case AddrMode::kAnAddrIncr: // 4898..489f / 4c89..4c9f / 48d8..48df / 4cd8..4cdf
+ if (dir == MoveDirection::kRegisterToMemory) {
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ case AddrMode::kAnAddrDecr: // 48a0..48a7 / 4ca0..4ca7 / 48e0..48e7 / 4ce0..4ce7
+ if (dir == MoveDirection::kMemoryToRegister) {
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ case AddrMode::kD16AnAddr: // 48a8..48af / 4c8a..4caf / 48e8..48ef / 4ce8..4cef
+ case AddrMode::kD8AnXiAddr: // 48b0..48b7 / 4cb0..4cb7 / 48f0..48f7 / 4cf0..4cf7
+ break;
+ case AddrMode::kWord: // 48b8 / 4cb8 / 48f8 / 4cf8
+ case AddrMode::kLong: // 48b9 / 4cb9 / 48f9 / 4cf9
+ if (dir == MoveDirection::kRegisterToMemory) {
+ node.ref2_addr = static_cast<uint32_t>(a.lword);
+ node.ref_kinds = kRef2AbsMask | kRef2WriteMask;
+ } else {
+ node.ref1_addr = static_cast<uint32_t>(a.lword);
+ node.ref_kinds = kRef1AbsMask | kRef1ReadMask;
+ }
+ break;
+ case AddrMode::kD16PCAddr: // 48ba / 4cba / 48fa / 4cfa
+ case AddrMode::kD8PCXiAddr: // 48bb / 4cbb / 48fb / 4cfb
+ if (dir == MoveDirection::kRegisterToMemory) {
+ return disasm_verbatim(node, instr);
+ } else if (a.mode == AddrMode::kD16PCAddr) {
+ // XXX: kRefPcRelFix2Bytes flag is a hack that needed to correctly
+ // print label for PC relative referenced value of MOVEM. Alongside
+ // with *NOT* adding kInstructionSizeStepBytes to ref1_addr. Still
+ // figuring that out.
+ node.ref1_addr = node.address + kInstructionSizeStepBytes * 2 +
+ static_cast<uint32_t>(a.d16_pc.d16);
+ node.ref_kinds = kRef1RelMask | kRef1ReadMask | kRefPcRelFix2Bytes;
+ }
+ break;
+ case AddrMode::kImmediate: // 4ebc / 4efc
+ return disasm_verbatim(node, instr);
+ }
+ if (dir == MoveDirection::kMemoryToRegister) {
+ const auto arg2 = (a.mode == AddrMode::kAnAddrDecr)
+ ? Arg::RegMaskPredecrement(regmask) : Arg::RegMask(regmask);
+ node.op = Op::Typical(OpCode::kMOVEM, opsize, a, arg2);
+ } else {
+ const auto arg1 = (a.mode == AddrMode::kAnAddrDecr)
+ ? Arg::RegMaskPredecrement(regmask) : Arg::RegMask(regmask);
+ node.op = Op::Typical(OpCode::kMOVEM, opsize, arg1, a);
+ }
+ return node.size = kInstructionSizeStepBytes * 2 + a.Size(opsize);
+}
+
+static size_t disasm_lea(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const OpSize opsize = OpSize::kLong;
+ const auto addr = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (addr.mode) {
+ case AddrMode::kInvalid:
+ case AddrMode::kDn:
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ break;
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ break;
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ node.ref1_addr = static_cast<uint32_t>(addr.lword);
+ node.ref_kinds = kRef1AbsMask | kRef1ReadMask;
+ break;
+ case AddrMode::kD16PCAddr:
+ node.ref1_addr = node.address + kInstructionSizeStepBytes +
+ static_cast<uint32_t>(addr.d16_pc.d16);
+ node.ref_kinds = kRef1RelMask | kRef1ReadMask;
+ break;
+ case AddrMode::kD8PCXiAddr:
+ break;
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ const unsigned an = ((instr >> 9) & 7);
+ const auto reg = Arg::An(an);
+ node.op = Op::Typical(OpCode::kLEA, opsize, addr, reg);
+ return node.size = kInstructionSizeStepBytes + addr.Size(opsize) + reg.Size(opsize);
+}
+
+static size_t disasm_chk(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const OpSize opsize = OpSize::kWord;
+ const auto src = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (src.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ const unsigned dn = ((instr >> 9) & 7);
+ const auto dst = Arg::Dn(dn);
+ node.op = Op::Typical(OpCode::kCHK, opsize, src, dst);
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize);
+}
+
+static size_t disasm_bra_bsr_bcc(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const int16_t dispmt0 = static_cast<int8_t>(instr & 0xff);
+ if (dispmt0 == -1) {
+ // This will definitely lead to executing invalid instruction and is
+ // also invalid for GNU AS to assemble
+ return disasm_verbatim(node, instr);
+ }
+ const auto opsize = dispmt0 ? OpSize::kShort : OpSize::kWord;
+ if (dispmt0 == 0) {
+ // Check the boundaries
+ if (node.address + kInstructionSizeStepBytes >= code.size) {
+ return disasm_verbatim(node, instr);
+ }
+ node.size = kInstructionSizeStepBytes * 2;
+ } else {
+ node.size = kInstructionSizeStepBytes;
+ }
+ const int16_t dispmt = kInstructionSizeStepBytes + (dispmt0
+ ? dispmt0 : GetI16BE(code.buffer + node.address + kInstructionSizeStepBytes));
+ const uint32_t ref_addr = static_cast<uint32_t>(node.address + dispmt);
+ Condition condition = static_cast<Condition>((instr >> 8) & 0xf);
+ // False condition Indicates BSR
+ node.ref1_addr = ref_addr;
+ node.ref_kinds = kRef1RelMask | ((condition == Condition::kF) ? kRefCallMask : 0);
+ node.op = Op{OpCode::kBcc, opsize, condition, Arg::Displacement(dispmt)};
+ return node.size;
+}
+
+static OpCode OpCodeForBitOps(const unsigned opcode)
+{
+ switch (opcode) {
+ case 0: return OpCode::kBTST;
+ case 1: return OpCode::kBCHG;
+ case 2: return OpCode::kBCLR;
+ case 3: return OpCode::kBSET;
+ }
+ assert(false);
+ return OpCode::kNone;
+}
+
+static size_t disasm_movep(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const unsigned dn = ((instr >> 9) & 7);
+ const unsigned an = instr & 7;
+ const OpSize opsize = ((instr >> 6) & 1) ? OpSize::kLong : OpSize::kWord;
+ const auto dir = static_cast<MoveDirection>(!((instr >> 7) & 1));
+ const auto addr = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, 5, an, opsize);
+ if (addr.mode == AddrMode::kInvalid) {
+ // Boundary check failed, most likely
+ return disasm_verbatim(node, instr);
+ }
+ assert(addr.mode == AddrMode::kD16AnAddr);
+ const auto reg = Arg::Dn(dn);
+ if (dir == MoveDirection::kRegisterToMemory) {
+ node.op = Op::Typical(OpCode::kMOVEP, opsize, reg, addr);
+ } else {
+ node.op = Op::Typical(OpCode::kMOVEP, opsize, addr, reg);
+ }
+ return node.size = kInstructionSizeStepBytes + addr.Size(opsize) + reg.Size(opsize);
+}
+
+static size_t disasm_src_arg_bitops_movep(
+ DisasmNode &node,
+ const uint16_t instr,
+ const DataView &code,
+ const bool has_dn_src = true)
+{
+ const unsigned m = (instr >> 3) & 7;
+ if ((m == 1) && has_dn_src) {
+ return disasm_movep(node, instr, code);
+ }
+ const unsigned dn = ((instr >> 9) & 7);
+ const unsigned xn = instr & 7;
+ const OpSize opsize0 = OpSize::kByte;
+ // Fetch AddrMode::kDn if has_dn_src, otherwise fetch AddrMode::kImmediate
+ // byte
+ const auto src = FetchArg(
+ node.address + kInstructionSizeStepBytes,
+ code,
+ (has_dn_src) ? 0 : 7,
+ dn,
+ opsize0);
+ if (src.mode == AddrMode::kInvalid) {
+ return disasm_verbatim(node, instr);
+ }
+ if (has_dn_src) {
+ assert(src.mode == AddrMode::kDn);
+ } else {
+ assert(dn == 4);
+ assert(src.mode == AddrMode::kImmediate);
+ }
+ const auto dst = FetchArg(
+ node.address + kInstructionSizeStepBytes + src.Size(opsize0), code, m, xn, opsize0);
+ const unsigned opcode = (instr >> 6) & 3;
+ switch (dst.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ if (opcode != 0) {
+ // PC relative destination address argument available for BTST only
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ const auto opsize = dst.mode == AddrMode::kDn ? OpSize::kLong : OpSize::kByte;
+ node.op = Op::Typical(OpCodeForBitOps(opcode), opsize, src, dst);
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize0) + dst.Size(opsize0);
+}
+
+static size_t disasm_bitops(DisasmNode &n, const uint16_t i, const DataView &c)
+{
+ return disasm_src_arg_bitops_movep(n, i, c, false);
+}
+
+static size_t disasm_logical_immediate_to(
+ DisasmNode &node, OpCode opcode, OpSize opsize, Arg imm)
+{
+ node.op = Op::Typical(opcode, opsize, imm, (opsize == OpSize::kByte) ? Arg::CCR() : Arg::SR());
+ return node.size = kInstructionSizeStepBytes * 2;
+}
+
+static OpCode OpCodeForLogicalImmediate(const unsigned opcode)
+{
+ switch (opcode) {
+ case 0: return OpCode::kORI;
+ case 1: return OpCode::kANDI;
+ case 2: return OpCode::kSUBI;
+ case 3: return OpCode::kADDI;
+ case 4: break;
+ case 5: return OpCode::kEORI;
+ case 6: return OpCode::kCMPI;
+ case 7: break;
+ }
+ assert(false);
+ return OpCode::kNone;
+}
+
+static size_t disasm_bitops_movep(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const bool has_source_reg = (instr >> 8) & 1;
+ if (has_source_reg) {
+ return disasm_src_arg_bitops_movep(node, instr, code);
+ }
+ const unsigned opcode = (instr >> 9) & 7;
+ if (opcode == 7) {
+ // Does not exist
+ return disasm_verbatim(node, instr);
+ }
+ if (opcode == 4) {
+ return disasm_bitops(node, instr, code);
+ }
+ const int m = (instr >> 3) & 7;
+ const int xn = instr & 7;
+ const auto opsize = static_cast<OpSize>((instr >> 6) & 3);
+ if (opsize == OpSize::kInvalid) {
+ // Does not exist
+ return disasm_verbatim(node, instr);
+ }
+ // Anticipating #imm which means "to CCR"/"to SR", depending on OpSize
+ if (m == 7 && xn == 4) {
+ if (opcode == 2 || opcode == 3 || opcode == 6) {
+ // CMPI, SUBI and ANDI neither have immediate destination arguments
+ // nor "to CCR"/"to SR" variations
+ return disasm_verbatim(node, instr);
+ }
+ if (opsize == OpSize::kLong) {
+ // Does not exist
+ return disasm_verbatim(node, instr);
+ }
+ }
+ const auto src = FetchImmediate(node.address + kInstructionSizeStepBytes, code, opsize);
+ if (src.mode == AddrMode::kInvalid) {
+ return disasm_verbatim(node, instr);
+ }
+ assert(src.mode == AddrMode::kImmediate);
+ const OpCode mnemonic = OpCodeForLogicalImmediate(opcode);
+ if (m == 7 && xn == 4) {
+ return disasm_logical_immediate_to(node, mnemonic, opsize, src);
+ }
+ const auto dst = FetchArg(
+ node.address + kInstructionSizeStepBytes + src.Size(opsize), code, m, xn, opsize);
+ switch (dst.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ if (opcode != 6) {
+ // PC relative destination address argument available for CMPI only
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ node.op = Op::Typical(mnemonic, opsize, src, dst);
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize);
+}
+
+static size_t disasm_move_movea(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const int opsize_raw = (instr >> 12) & 3;
+ const OpSize opsize = (opsize_raw == 1)
+ ? OpSize::kByte : (opsize_raw == 3 ? OpSize::kWord : OpSize::kLong);
+ const auto src = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (src.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ if (opsize == OpSize::kByte) {
+ // Does not exist
+ return disasm_verbatim(node, instr);
+ }
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ break;
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ node.ref1_addr = static_cast<uint32_t>(src.lword);
+ node.ref_kinds |= kRef1AbsMask | kRef1ReadMask;
+ break;
+ case AddrMode::kD16PCAddr:
+ node.ref1_addr = node.address + kInstructionSizeStepBytes +
+ static_cast<uint32_t>(src.d16_pc.d16);
+ node.ref_kinds |= kRef1RelMask | kRef1ReadMask;
+ break;
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ break;
+ }
+ const int m = (instr >> 6) & 7;
+ const int xn = (instr >> 9) & 7;
+ const auto dst = FetchArg(
+ node.address + kInstructionSizeStepBytes + src.Size(opsize), code, m, xn, opsize);
+ switch (dst.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ if (opsize == OpSize::kByte) {
+ // Does not exist
+ return disasm_verbatim(node, instr);
+ }
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ break;
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ node.ref2_addr = static_cast<uint32_t>(dst.lword);
+ node.ref_kinds |= kRef2AbsMask | kRef2WriteMask;
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ // XXX Assuming that moving long immediate value into address register is
+ // basically a sneaky LEA. It may not be true in some cases.
+ if (src.type == ArgType::kImmediate && dst.type == ArgType::kAn) {
+ if (opsize == OpSize::kLong) {
+ node.ref1_addr = static_cast<uint32_t>(src.lword);
+ node.ref_kinds |= kRef1ImmMask | kRef1ReadMask;
+ } else if (opsize == OpSize::kWord) {
+ node.ref1_addr = static_cast<int16_t>(static_cast<uint16_t>(src.lword));
+ node.ref_kinds |= kRef1ImmMask | kRef1ReadMask;
+ }
+ }
+ const auto opcode = (dst.mode == AddrMode::kAn) ? OpCode::kMOVEA : OpCode::kMOVE;
+ node.op = Op::Typical(opcode, opsize, src, dst);
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize);
+}
+
+static size_t disasm_move_from_sr(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const auto opsize = OpSize::kWord;
+ const auto dst = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (dst.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ node.op = Op::Typical(OpCode::kMOVE, opsize, Arg::SR(), dst);
+ return node.size = kInstructionSizeStepBytes + dst.Size(opsize);
+}
+
+static size_t disasm_move_to(
+ DisasmNode &node, const uint16_t instr, const DataView &code, const ArgType reg)
+{
+ const auto opsize = OpSize::kWord;
+ const auto src = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (src.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ break;
+ }
+ node.op = Op::Typical(OpCode::kMOVE, opsize, src, Arg{{reg}, {0}});
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize);
+}
+
+static OpCode opcode_for_negx_clr_neg_not(const unsigned opcode)
+{
+ switch (opcode) {
+ case 0: return OpCode::kNEGX;
+ case 1: return OpCode::kCLR;
+ case 2: return OpCode::kNEG;
+ case 3: return OpCode::kNOT;
+ }
+ assert(false);
+ return OpCode::kNone;
+}
+
+static size_t disasm_move_negx_clr_neg_not(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const auto opsize = static_cast<OpSize>((instr >> 6) & 3);
+ const unsigned opcode = (instr >> 9) & 3;
+ if (opsize == OpSize::kInvalid) {
+ switch (opcode) {
+ case 0:
+ return disasm_move_from_sr(node, instr, code);
+ case 1:
+ return disasm_verbatim(node, instr);
+ case 2:
+ return disasm_move_to(node, instr, code, ArgType::kCCR);
+ case 3:
+ return disasm_move_to(node, instr, code, ArgType::kSR);
+ }
+ assert(false);
+ return disasm_verbatim(node, instr);
+ }
+ const auto a = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (a.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ node.op = Op::Typical(opcode_for_negx_clr_neg_not(opcode), opsize, a);
+ return node.size = kInstructionSizeStepBytes + a.Size(opsize);
+}
+
+static size_t disasm_trivial(
+ DisasmNode &node, const OpCode opcode)
+{
+ node.op = Op::Typical(opcode, OpSize::kNone);
+ return node.size;
+}
+
+static size_t disasm_tas(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const auto opsize = OpSize::kByte;
+ const auto a = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (a.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ node.op = Op::Typical(OpCode::kTAS, opsize, a);
+ return node.size = kInstructionSizeStepBytes + a.Size(opsize);
+}
+
+static size_t disasm_tst_tas_illegal(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const auto opsize = static_cast<OpSize>((instr >> 6) & 3);
+ const int m = (instr >> 3) & 7;
+ const int xn = instr & 7;
+ if (opsize == OpSize::kInvalid) {
+ if (m == 7 && xn == 4){
+ return disasm_trivial(node, OpCode::kILLEGAL);
+ }
+ return disasm_tas(node, instr, code);
+ }
+ const auto a = FetchArg(node.address + kInstructionSizeStepBytes, code, m, xn, opsize);
+ switch (a.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ break;
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ node.op = Op::Typical(OpCode::kTST, opsize, a);
+ return node.size = kInstructionSizeStepBytes + a.Size(opsize);
+}
+
+static size_t disasm_trap(DisasmNode &node, const uint16_t instr)
+{
+ const unsigned vector = instr & 0xf;
+ node.op = Op::Typical(OpCode::kTRAP, OpSize::kNone, Arg::Immediate(vector));
+ return node.size = kInstructionSizeStepBytes;
+}
+
+static size_t disasm_link_unlink(DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const bool unlk = (instr >> 3) & 1;
+ const unsigned xn = instr & 7;
+ if (unlk) {
+ node.op = Op::Typical(OpCode::kUNLK, OpSize::kNone, Arg::AddrModeXn(ArgType::kAn, xn));
+ return node.size = kInstructionSizeStepBytes;
+ }
+ const auto opsize = OpSize::kWord;
+ const auto src = FetchImmediate(node.address + kInstructionSizeStepBytes, code, opsize);
+ if (src.mode != AddrMode::kImmediate) {
+ return disasm_verbatim(node, instr);
+ }
+ node.op = Op::Typical(OpCode::kLINK, opsize, Arg::AddrModeXn(ArgType::kAn, xn), src);
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize);
+}
+
+static size_t disasm_move_usp(DisasmNode &node, const uint16_t instr)
+{
+ const unsigned xn = instr & 7;
+ const auto dir = static_cast<MoveDirection>((instr >> 3) & 1);
+ if (dir == MoveDirection::kRegisterToMemory) {
+ node.op = Op::Typical(
+ OpCode::kMOVE, OpSize::kLong, Arg::An(xn), Arg::USP());
+ } else {
+ node.op = Op::Typical(
+ OpCode::kMOVE, OpSize::kLong, Arg::USP(), Arg::An(xn));
+ }
+ return node.size = kInstructionSizeStepBytes;
+}
+
+static size_t disasm_nbcd_swap_pea(DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const bool is_nbcd = !((instr >> 6) & 1);
+ const OpSize opsize0 = OpSize::kWord;
+ const auto arg = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize0);
+ bool is_swap{};
+ switch (arg.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ if (!is_nbcd) {
+ is_swap = true;
+ }
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ break;
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ if (!is_nbcd) {
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ break;
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ node.ref1_addr = static_cast<uint32_t>(arg.lword);
+ node.ref_kinds = kRef1AbsMask | kRef1ReadMask;
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ if (is_nbcd) {
+ return disasm_verbatim(node, instr);
+ }
+ if (arg.mode == AddrMode::kD16PCAddr) {
+ node.ref1_addr = node.address + kInstructionSizeStepBytes +
+ static_cast<uint32_t>(arg.d16_pc.d16);
+ node.ref_kinds = kRef1RelMask | kRef1ReadMask;
+ }
+ break;
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ const auto opcode = is_nbcd ? OpCode::kNBCD : is_swap ? OpCode::kSWAP : OpCode::kPEA;
+ const auto opsize = is_nbcd ? OpSize::kByte : is_swap ? OpSize::kWord : OpSize::kLong;
+ node.op = Op::Typical(opcode, opsize, arg);
+ return node.size = kInstructionSizeStepBytes + arg.Size(opsize0);
+}
+
+static size_t disasm_stop(DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const auto a = FetchImmediate(node.address + kInstructionSizeStepBytes, code, OpSize::kWord);
+ if (a.mode != AddrMode::kImmediate) {
+ return disasm_verbatim(node, instr);
+ }
+ node.op = Op::Typical(OpCode::kSTOP, OpSize::kNone, a);
+ return node.size = kInstructionSizeStepBytes * 2;
+}
+
+static size_t disasm_chunk_4(DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ if ((instr & 0xf900) == 0x4000) {
+ return disasm_move_negx_clr_neg_not(node, instr, code);
+ } else if ((instr & 0xff80) == 0x4800) {
+ // NOTE: EXT is handled with MOVEM
+ return disasm_nbcd_swap_pea(node, instr, code);
+ } else if ((instr & 0xff00) == 0x4a00) {
+ return disasm_tst_tas_illegal(node, instr, code);
+ } else if ((instr & 0xfff0) == 0x4e40) {
+ return disasm_trap(node, instr);
+ } else if ((instr & 0xfff0) == 0x4e50) {
+ return disasm_link_unlink(node, instr, code);
+ } else if ((instr & 0xfff0) == 0x4e60) {
+ return disasm_move_usp(node, instr);
+ } else if ((instr & 0xfff8) == 0x4e70) {
+ if (instr == 0x4e70) {
+ return disasm_trivial(node, OpCode::kRESET);
+ } else if (instr == 0x4e71) {
+ return disasm_trivial(node, OpCode::kNOP);
+ } else if (instr == 0x4e72) {
+ return disasm_stop(node, instr, code);
+ } else if (instr == 0x4e73) {
+ return disasm_trivial(node, OpCode::kRTE);
+ } else if (instr == 0x4e75) {
+ return disasm_trivial(node, OpCode::kRTS);
+ } else if (instr == 0x4e76) {
+ return disasm_trivial(node, OpCode::kTRAPV);
+ } else if (instr == 0x4e77) {
+ return disasm_trivial(node, OpCode::kRTR);
+ }
+ } else if ((instr & 0xff80) == 0x4e80) {
+ return disasm_jsr_jmp(node, instr, code);
+ } else if ((instr & 0xfb80) == 0x4880) {
+ return disasm_ext_movem(node, instr, code);
+ } else if ((instr & 0xf1c0) == 0x41c0) {
+ return disasm_lea(node, instr, code);
+ } else if ((instr & 0xf1c0) == 0x4180) {
+ return disasm_chk(node, instr, code);
+ }
+ return disasm_verbatim(node, instr);
+}
+
+static size_t disasm_addq_subq(
+ DisasmNode &node, const uint16_t instr, const DataView &code, const OpSize opsize)
+{
+ const auto a = FetchArg(node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (a.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn: // 5x00..5x07 / 5x40..5x47 / 5x80..5x87
+ break;
+ case AddrMode::kAn: // 5x08..5x0f / 5x48..5x4f / 5x88..5x8f
+ if (opsize == OpSize::kByte) {
+ // 5x08..5x0f
+ // addqb and subqb with An do not exist
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ case AddrMode::kAnAddr: // 5x10..5x17 / 5x50..5x57 / 5x90..5x97
+ case AddrMode::kAnAddrIncr: // 5x18..5x1f / 5x58..5x5f / 5x98..5x9f
+ case AddrMode::kAnAddrDecr: // 5x20..5x27 / 5x60..5x67 / 5xa0..5xa7
+ case AddrMode::kD16AnAddr: // 5x28..5x2f / 5x68..5x6f / 5xa8..5xaf
+ case AddrMode::kD8AnXiAddr: // 5x30..5x37 / 5x70..5x77 / 5xb0..5xb7
+ case AddrMode::kWord: // 5x38 / 5x78 / 5xb8
+ case AddrMode::kLong: // 5x39 / 5x79 / 5xb9
+ break;
+ case AddrMode::kD16PCAddr: // 5x3a / 5x7a / 5xba
+ case AddrMode::kD8PCXiAddr: // 5x3b / 5x7b / 5xbb
+ case AddrMode::kImmediate: // 5x3c / 5x7c / 5xbc
+ // Does not exist
+ return disasm_verbatim(node, instr);
+ }
+ const unsigned imm = ((uint8_t((instr >> 9) & 7) - 1) & 7) + 1;
+ const auto opcode = ((instr >> 8) & 1) ? OpCode::kSUBQ : OpCode::kADDQ;
+ node.op = Op::Typical(opcode, opsize, Arg::Immediate(imm), a);
+ return node.size = kInstructionSizeStepBytes + a.Size(opsize);
+}
+
+static size_t disasm_dbcc(DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ if (node.address + kInstructionSizeStepBytes >= code.size) {
+ return disasm_verbatim(node, instr);
+ }
+ const int16_t dispmt_raw = GetI16BE(code.buffer + node.address + kInstructionSizeStepBytes);
+ const int32_t dispmt = dispmt_raw + kInstructionSizeStepBytes;
+ node.ref2_addr = static_cast<uint32_t>(node.address + dispmt);
+ node.ref_kinds = kRef2RelMask;
+ node.op = Op{
+ OpCode::kDBcc,
+ OpSize::kWord,
+ static_cast<Condition>((instr >> 8) & 0xf),
+ Arg::AddrModeXn(ArgType::kDn, (instr & 7)),
+ Arg::Displacement(dispmt),
+ };
+ return node.size = kInstructionSizeStepBytes * 2;
+}
+
+static size_t disasm_scc_dbcc(DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const OpSize opsize = OpSize::kWord;
+ const auto a = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (a.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn: // 5xc0..5xc7, Dn
+ break;
+ case AddrMode::kAn: // 5xc8..5xcf, An
+ return disasm_dbcc(node, instr, code);
+ case AddrMode::kAnAddr: // 5xd0..5xd7
+ case AddrMode::kAnAddrIncr: // 5xd8..5xdf
+ case AddrMode::kAnAddrDecr: // 5xe0..5xe7
+ case AddrMode::kD16AnAddr: // 5xe8..5xef
+ case AddrMode::kD8AnXiAddr: // 5xf0..5xf7
+ case AddrMode::kWord: // 5xf8 (xxx).W
+ case AddrMode::kLong: // 5xf9 (xxx).L
+ break;
+ case AddrMode::kD16PCAddr: // 5xfa
+ case AddrMode::kD8PCXiAddr: // 5xfb
+ case AddrMode::kImmediate: // 5xfc
+ // Does not exist
+ return disasm_verbatim(node, instr);
+ }
+ node.op = Op{OpCode::kScc, OpSize::kByte, static_cast<Condition>((instr >> 8) & 0xf), a};
+ return node.size = kInstructionSizeStepBytes + a.Size(opsize);
+}
+
+static size_t disasm_addq_subq_scc_dbcc(DisasmNode &n, const uint16_t instr, const DataView &c)
+{
+ const auto opsize = static_cast<OpSize>((instr >> 6) & 3);
+ if (opsize == OpSize::kInvalid) {
+ return disasm_scc_dbcc(n, instr, c);
+ }
+ return disasm_addq_subq(n, instr, c, opsize);
+}
+
+static size_t disasm_moveq(DisasmNode &node, const uint16_t instr)
+{
+ if (instr & 0x100) {
+ // Does not exist
+ return disasm_verbatim(node, instr);
+ }
+ const int xn = (instr >> 9) & 7;
+ const auto dst = Arg::Dn(xn);
+ const int8_t data = instr & 0xff;
+ const OpSize opsize = OpSize::kLong;
+ node.op = Op::Typical(OpCode::kMOVEQ, opsize, Arg::Immediate(data), dst);
+ return node.size = kInstructionSizeStepBytes + dst.Size(opsize);
+}
+
+static size_t disasm_divu_divs_mulu_muls(
+ DisasmNode &node,
+ const uint16_t instr,
+ const DataView &code,
+ const OpCode opcode)
+{
+ const auto opsize = OpSize::kWord;
+ const auto src = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (src.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ break;
+ }
+ const unsigned dn = (instr >> 9) & 7;
+ const auto dst = Arg::Dn(dn);
+ node.op = Op::Typical(opcode, opsize, src, dst);
+ return node.size = kInstructionSizeStepBytes + dst.Size(opsize) + src.Size(opsize);
+}
+
+static size_t disasm_addx_subx_abcd_sbcd(
+ DisasmNode &node, const uint16_t instr, const OpCode opcode)
+{
+ const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3);
+ // Must be already handled by parent call
+ assert(opsize != OpSize::kInvalid);
+ const int m = (instr >> 3) & 1;
+ const int xn = instr & 7;
+ const int xi = (instr >> 9) & 7;
+ const auto src = m ? Arg::AnAddrDecr(xn) : Arg::Dn(xn);
+ const auto dst = m ? Arg::AnAddrDecr(xi) : Arg::Dn(xi);
+ // XXX GNU AS does not know ABCD.B, it only knows ABCD, but happily consumes
+ // SBCD.B and others. That's why it is OpSize::kNone specifically for ABCD
+ // mnemonic. It is probably a bug in GNU AS.
+ node.op = Op::Typical(opcode, (opcode == OpCode::kABCD) ? OpSize::kNone : opsize, src, dst);
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize);
+}
+
+static size_t disasm_or_and(
+ DisasmNode &node,
+ const uint16_t instr,
+ const DataView &code,
+ const OpSize opsize,
+ const OpCode opcode)
+{
+ const bool dir_to_addr = (instr >> 8) & 1;
+ const auto addr = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (addr.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ if (dir_to_addr) {
+ // Switching dir when bot operands are data registers is not allowed
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ if (dir_to_addr) {
+ // PC relative cannot be destination
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ case AddrMode::kImmediate:
+ if (dir_to_addr) {
+ // immediate cannot be destination
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ }
+ const auto reg = Arg::Dn((instr >> 9) & 7);
+ if (dir_to_addr) {
+ node.op = Op::Typical(opcode, opsize, reg, addr);
+ } else {
+ node.op = Op::Typical(opcode, opsize, addr, reg);
+ }
+ return node.size = kInstructionSizeStepBytes + addr.Size(opsize) + reg.Size(opsize);
+}
+
+static size_t disasm_divu_divs_sbcd_or(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ // Also ensures that opsize == OpSize::kByte, i.e. 0b00
+ if ((instr & 0x1f0) == 0x100) {
+ return disasm_addx_subx_abcd_sbcd(node, instr, OpCode::kSBCD);
+ }
+ const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3);
+ if (opsize == OpSize::kInvalid) {
+ const bool is_signed = (instr >> 8) & 1;
+ const auto opcode = is_signed ? OpCode::kDIVS : OpCode::kDIVU;
+ return disasm_divu_divs_mulu_muls(node, instr, code, opcode);
+ }
+ return disasm_or_and(node, instr, code, opsize, OpCode::kOR);
+}
+
+static size_t disasm_adda_suba_cmpa(
+ DisasmNode &node, const uint16_t instr, const DataView &code, const OpCode opcode)
+{
+ const OpSize opsize = static_cast<OpSize>(((instr >> 8) & 1) + 1);
+ const auto src = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (src.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ case AddrMode::kAn:
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ break;
+ }
+ const unsigned an = (instr >> 9) & 7;
+ const auto dst = Arg::An(an);
+ node.op = Op::Typical(opcode, opsize, src, dst);
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize);
+}
+
+static size_t disasm_add_sub_cmp(
+ DisasmNode &node,
+ const uint16_t instr,
+ const DataView &code,
+ const OpCode opcode,
+ const OpSize opsize,
+ const bool dir_to_addr)
+{
+ const auto addr = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (addr.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ if (dir_to_addr || opsize == OpSize::kByte) {
+ // An cannot be destination and An cannot be used as byte
+ return disasm_verbatim(node, instr);
+ }
+ /* Fall through */
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ break;
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ if (dir_to_addr) {
+ node.ref2_addr = static_cast<uint32_t>(addr.lword);
+ node.ref_kinds = kRef2AbsMask | kRef2ReadMask;
+ } else {
+ node.ref1_addr = static_cast<uint32_t>(addr.lword);
+ node.ref_kinds = kRef1AbsMask | kRef1ReadMask;
+ }
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ if (dir_to_addr) {
+ // PC relative cannot be destination
+ return disasm_verbatim(node, instr);
+ }
+ if (addr.mode == AddrMode::kD16PCAddr) {
+ node.ref1_addr = node.address + kInstructionSizeStepBytes +
+ static_cast<uint32_t>(addr.d16_pc.d16);
+ node.ref_kinds = kRef1RelMask | kRef1ReadMask;
+ }
+ break;
+ case AddrMode::kImmediate:
+ if (dir_to_addr) {
+ // immediate cannot be destination
+ return disasm_verbatim(node, instr);
+ }
+ break;
+ }
+ const unsigned dn = (instr >> 9) & 7;
+ const auto reg = Arg::Dn(dn);
+ if (dir_to_addr) {
+ node.op = Op::Typical(opcode, opsize, reg, addr);
+ } else {
+ node.op = Op::Typical(opcode, opsize, addr, reg);
+ }
+ return node.size = kInstructionSizeStepBytes + addr.Size(opsize) + reg.Size(opsize);
+}
+
+static size_t disasm_cmpm(DisasmNode &node, const uint16_t instr)
+{
+ const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3);
+ // Must be already handled by parent call
+ assert(opsize != OpSize::kInvalid);
+ // M has to be set to 0b001
+ assert(((instr >> 3) & 7) == 1);
+ const int xn = instr & 7;
+ const int xi = (instr >> 9) & 7;
+ const auto src = Arg::AnAddrIncr(xn);
+ const auto dst = Arg::AnAddrIncr(xi);
+ node.op = Op::Typical(OpCode::kCMPM, opsize, src, dst);
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize);
+}
+
+static size_t disasm_eor(DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3);
+ const auto addr = FetchArg(
+ node.address + kInstructionSizeStepBytes, code, instr, opsize);
+ switch (addr.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ // PC relative and immediate cannot be destination
+ return disasm_verbatim(node, instr);
+ }
+ const auto reg = Arg::Dn((instr >> 9) & 7);
+ node.op = Op::Typical(OpCode::kEOR, opsize, reg, addr);
+ return node.size = kInstructionSizeStepBytes + addr.Size(opsize) + reg.Size(opsize);
+}
+
+static size_t disasm_eor_cmpm_cmp_cmpa(
+ DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3);
+ if (opsize == OpSize::kInvalid) {
+ return disasm_adda_suba_cmpa(node, instr, code, OpCode::kCMPA);
+ }
+ const bool dir_to_addr = ((instr >> 8) & 1);
+ if (!dir_to_addr) {
+ return disasm_add_sub_cmp(node, instr, code, OpCode::kCMP, opsize, dir_to_addr);
+ }
+ const int m = (instr >> 3) & 7;
+ if (m == 1) {
+ return disasm_cmpm(node, instr);
+ }
+ return disasm_eor(node, instr, code);
+}
+
+static size_t disasm_exg(DisasmNode &node, const uint16_t instr)
+{
+ assert((instr & 0x130) == 0x100);
+ const int m1 = (instr >> 3) & 1;
+ const int m2 = (instr >> 6) & 3;
+ assert(m2 != 0); // Therefore m == 0 and m == 1 are impossible
+ assert(m2 != 3); // Therefore m == 6 and m == 7 are impossible
+ const int m = (m2 << 1) | m1;
+ assert(m != 4); // Only m == 2, m == 3 and m == 5 values are allowed
+ const int xn = instr & 7;
+ const int xi = (instr >> 9) & 7;
+ const auto src = (m == 3) ? Arg::An(xi) : Arg::Dn(xi);
+ const auto dst = (m == 2) ? Arg::Dn(xn) : Arg::An(xn);
+ // GNU AS does not accept size suffix for EXG, although it's size is always
+ // long word.
+ const auto opsize = OpSize::kNone;
+ node.op = Op::Typical(OpCode::kEXG, opsize, src, dst);
+ return node.size = kInstructionSizeStepBytes + src.Size(opsize) + dst.Size(opsize);
+}
+
+static size_t disasm_chunk_c(DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ if ((instr & 0x1f0) == 0x100) {
+ return disasm_addx_subx_abcd_sbcd(node, instr, OpCode::kABCD);
+ }
+ const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3);
+ if (opsize == OpSize::kInvalid) {
+ const bool is_signed = (instr >> 8) & 1;
+ const auto opcode = is_signed ? OpCode::kMULS : OpCode::kMULU;
+ return disasm_divu_divs_mulu_muls(node, instr, code, opcode);
+ }
+ const unsigned m_split = instr & 0x1f8;
+ if (m_split == 0x188 || m_split == 0x148 || m_split == 0x140) {
+ return disasm_exg(node, instr);
+ }
+ return disasm_or_and(node, instr, code, opsize, OpCode::kAND);
+}
+
+static size_t disasm_add_sub_x_a(
+ DisasmNode &node, const uint16_t instr, const DataView &code, const OpCode opcode)
+{
+ const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3);
+ if (opsize == OpSize::kInvalid) {
+ return disasm_adda_suba_cmpa(node, instr, code, (opcode == OpCode::kSUB) ? OpCode::kSUBA : OpCode::kADDA);
+ }
+ const bool dir_to_addr = (instr >> 8) & 1;
+ const unsigned m = (instr >> 3) & 7;
+ if (dir_to_addr && (m == 0 || m == 1)) {
+ return disasm_addx_subx_abcd_sbcd(node, instr, (opcode == OpCode::kSUB) ? OpCode::kSUBX : OpCode::kADDX);
+ }
+ return disasm_add_sub_cmp(node, instr, code, opcode, opsize, dir_to_addr);
+}
+
+static OpCode ShiftKindToOpcode(const ShiftKind k, const ShiftDirection d)
+{
+ switch (k) {
+ case ShiftKind::kArithmeticShift:
+ return d == ShiftDirection::kLeft ? OpCode::kASL : OpCode::kASR;
+ case ShiftKind::kLogicalShift:
+ return d == ShiftDirection::kLeft ? OpCode::kLSL : OpCode::kLSR;
+ case ShiftKind::kRotateX:
+ return d == ShiftDirection::kLeft ? OpCode::kROXL : OpCode::kROXR;
+ case ShiftKind::kRotate:
+ return d == ShiftDirection::kLeft ? OpCode::kROL : OpCode::kROR;
+ }
+ assert(false);
+ return OpCode::kNone;
+}
+
+static bool IsValidShiftKind(const ShiftKind k)
+{
+ return static_cast<int>(k) < 4;
+}
+
+static size_t disasm_shift_rotate(DisasmNode &node, const uint16_t instr, const DataView &code)
+{
+ const OpSize opsize = static_cast<OpSize>((instr >> 6) & 3);
+ const unsigned xn = instr & 7;
+ const uint8_t rotation = (instr >> 9) & 7;
+ const ShiftKind kind = (opsize == OpSize::kInvalid)
+ ? static_cast<ShiftKind>(rotation)
+ : static_cast<ShiftKind>((instr >> 3) & 3);
+ if (!IsValidShiftKind(kind)) {
+ return disasm_verbatim(node, instr);
+ }
+ const auto dst = (opsize == OpSize::kInvalid)
+ ? FetchArg(node.address + kInstructionSizeStepBytes, code, instr, opsize)
+ : Arg::Dn(xn);
+ if (opsize == OpSize::kInvalid) {
+ switch (dst.mode) {
+ case AddrMode::kInvalid:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kDn:
+ // Intersects with situation when args are "#1,%dx". GNU AS would
+ // not understand shift instruction with single argument of "%dx".
+ return disasm_verbatim(node, instr);
+ break;
+ case AddrMode::kAn:
+ return disasm_verbatim(node, instr);
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ case AddrMode::kLong:
+ break;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ case AddrMode::kImmediate:
+ return disasm_verbatim(node, instr);
+ }
+ }
+ const unsigned imm = ((rotation - 1) & 7) + 1;
+ const unsigned src = (opsize == OpSize::kInvalid) ? 1 : rotation;
+ const auto dir = static_cast<ShiftDirection>((instr >> 8) & 1);
+ if (opsize == OpSize::kInvalid) {
+ node.op = Op::Typical(ShiftKindToOpcode(kind, dir), opsize, dst);
+ } else {
+ const unsigned m = (instr >> 5) & 1;
+ const auto arg1 = m ? Arg::AddrModeXn(ArgType::kDn, src) : Arg::Immediate(imm);
+ node.op = Op::Typical(ShiftKindToOpcode(kind, dir), opsize, arg1, dst);
+ }
+ return node.size = kInstructionSizeStepBytes + dst.Size(opsize);
+}
+
+static size_t m68k_disasm(DisasmNode &n, uint16_t i, const DataView &c)
+{
+ switch ((i & 0xf000) >> 12) {
+ case 0x0:
+ return disasm_bitops_movep(n, i, c);
+ case 0x1:
+ case 0x2:
+ case 0x3:
+ return disasm_move_movea(n, i, c);
+ case 0x4:
+ return disasm_chunk_4(n, i, c);
+ case 0x5:
+ return disasm_addq_subq_scc_dbcc(n, i, c);
+ case 0x6:
+ return disasm_bra_bsr_bcc(n, i, c);
+ case 0x7:
+ return disasm_moveq(n, i);
+ case 0x8:
+ return disasm_divu_divs_sbcd_or(n, i, c);
+ case 0x9:
+ return disasm_add_sub_x_a(n, i, c, OpCode::kSUB);
+ case 0xa:
+ // Does not exist
+ return disasm_verbatim(n, i);
+ case 0xb:
+ return disasm_eor_cmpm_cmp_cmpa(n, i, c);
+ case 0xc:
+ return disasm_chunk_c(n, i, c);
+ case 0xd:
+ return disasm_add_sub_x_a(n, i, c, OpCode::kADD);
+ case 0xe:
+ return disasm_shift_rotate(n, i, c);
+ case 0xf:
+ // Does not exist
+ return disasm_verbatim(n, i);
+ }
+ assert(false);
+ return disasm_verbatim(n, i);
+}
+
+size_t DisasmNode::Disasm(const DataView &code)
+{
+ // We assume that machine have no MMU and ROM data always starts at 0
+ assert(this->address < code.size);
+ size = kInstructionSizeStepBytes;
+ ref_kinds = 0;
+ ref1_addr = 0;
+ ref2_addr = 0;
+ const uint16_t instr = GetU16BE(code.buffer + this->address);
+ if (IsInstruction(this->type)) {
+ return m68k_disasm(*this, instr, code);
+ } else {
+ // Data should not be disassembled
+ return disasm_verbatim(*this, instr);
+ }
+}
+
+size_t DisasmNode::DisasmAsRaw(const DataView &code)
+{
+ // We assume that machine have no MMU and ROM data always starts at 0
+ assert(this->address < code.size);
+ size = kInstructionSizeStepBytes;
+ ref_kinds = 0;
+ ref1_addr = 0;
+ ref2_addr = 0;
+ const uint16_t instr = GetU16BE(code.buffer + this->address);
+ return disasm_verbatim(*this, instr);
+}
+
+static const char *ToString(const OpCode opcode, const Condition condition)
+{
+ switch (opcode) {
+ case OpCode::kNone:
+ assert(false);
+ break;
+ case OpCode::kRaw: return ".short";
+ case OpCode::kORI: return "ori";
+ case OpCode::kANDI: return "andi";
+ case OpCode::kSUBI: return "subi";
+ case OpCode::kADDI: return "addi";
+ case OpCode::kEORI: return "eori";
+ case OpCode::kCMPI: return "cmpi";
+ case OpCode::kBTST: return "btst";
+ case OpCode::kBCHG: return "bchg";
+ case OpCode::kBCLR: return "bclr";
+ case OpCode::kBSET: return "bset";
+ case OpCode::kMOVEP: return "movep";
+ case OpCode::kMOVEA: return "movea";
+ case OpCode::kMOVE: return "move";
+ case OpCode::kNEGX: return "negx";
+ case OpCode::kCLR: return "clr";
+ case OpCode::kNEG: return "neg";
+ case OpCode::kNOT: return "not";
+ case OpCode::kEXT: return "ext";
+ case OpCode::kNBCD: return "nbcd";
+ case OpCode::kSWAP: return "swap";
+ case OpCode::kPEA: return "pea";
+ case OpCode::kILLEGAL: return "illegal";
+ case OpCode::kTAS: return "tas";
+ case OpCode::kTST: return "tst";
+ case OpCode::kTRAP: return "trap";
+ case OpCode::kLINK: return "link";
+ case OpCode::kUNLK: return "unlk";
+ case OpCode::kRESET: return "reset";
+ case OpCode::kNOP: return "nop";
+ case OpCode::kSTOP: return "stop";
+ case OpCode::kRTE: return "rte";
+ case OpCode::kRTS: return "rts";
+ case OpCode::kTRAPV: return "trapv";
+ case OpCode::kRTR: return "rtr";
+ case OpCode::kJSR: return "jsr";
+ case OpCode::kJMP: return "jmp";
+ case OpCode::kMOVEM: return "movem";
+ case OpCode::kLEA: return "lea";
+ case OpCode::kCHK: return "chk";
+ case OpCode::kADDQ: return "addq";
+ case OpCode::kSUBQ: return "subq";
+ case OpCode::kScc:
+ switch(condition) {
+ case Condition::kT : return "st";
+ case Condition::kF: return "sf";
+ case Condition::kHI: return "shi";
+ case Condition::kLS: return "sls";
+ case Condition::kCC: return "scc";
+ case Condition::kCS: return "scs";
+ case Condition::kNE: return "sne";
+ case Condition::kEQ: return "seq";
+ case Condition::kVC: return "svc";
+ case Condition::kVS: return "svs";
+ case Condition::kPL: return "spl";
+ case Condition::kMI: return "smi";
+ case Condition::kGE: return "sge";
+ case Condition::kLT: return "slt";
+ case Condition::kGT: return "sgt";
+ case Condition::kLE: return "sle";
+ }
+ assert(false);
+ break;
+ case OpCode::kDBcc:
+ switch (condition) {
+ case Condition::kT: return "dbt";
+ case Condition::kF: return "dbf";
+ case Condition::kHI: return "dbhi";
+ case Condition::kLS: return "dbls";
+ case Condition::kCC: return "dbcc";
+ case Condition::kCS: return "dbcs";
+ case Condition::kNE: return "dbne";
+ case Condition::kEQ: return "dbeq";
+ case Condition::kVC: return "dbvc";
+ case Condition::kVS: return "dbvs";
+ case Condition::kPL: return "dbpl";
+ case Condition::kMI: return "dbmi";
+ case Condition::kGE: return "dbge";
+ case Condition::kLT: return "dblt";
+ case Condition::kGT: return "dbgt";
+ case Condition::kLE: return "dble";
+ }
+ assert(false);
+ break;
+ case OpCode::kBcc:
+ switch (condition) {
+ case Condition::kT: return "bra";
+ case Condition::kF: return "bsr";
+ case Condition::kHI: return "bhi";
+ case Condition::kLS: return "bls";
+ case Condition::kCC: return "bcc";
+ case Condition::kCS: return "bcs";
+ case Condition::kNE: return "bne";
+ case Condition::kEQ: return "beq";
+ case Condition::kVC: return "bvc";
+ case Condition::kVS: return "bvs";
+ case Condition::kPL: return "bpl";
+ case Condition::kMI: return "bmi";
+ case Condition::kGE: return "bge";
+ case Condition::kLT: return "blt";
+ case Condition::kGT: return "bgt";
+ case Condition::kLE: return "ble";
+ }
+ assert(false);
+ break;
+ case OpCode::kMOVEQ: return "moveq";
+ case OpCode::kDIVU: return "divu";
+ case OpCode::kDIVS: return "divs";
+ case OpCode::kSBCD: return "sbcd";
+ case OpCode::kOR: return "or";
+ case OpCode::kSUB: return "sub";
+ case OpCode::kSUBX: return "subx";
+ case OpCode::kSUBA: return "suba";
+ case OpCode::kEOR: return "eor";
+ case OpCode::kCMPM: return "cmpm";
+ case OpCode::kCMP: return "cmp";
+ case OpCode::kCMPA: return "cmpa";
+ case OpCode::kMULU: return "mulu";
+ case OpCode::kMULS: return "muls";
+ case OpCode::kABCD: return "abcd";
+ case OpCode::kEXG: return "exg";
+ case OpCode::kAND: return "and";
+ case OpCode::kADD: return "add";
+ case OpCode::kADDX: return "addx";
+ case OpCode::kADDA: return "adda";
+ case OpCode::kASR: return "asr";
+ case OpCode::kASL: return "asl";
+ case OpCode::kLSR: return "lsr";
+ case OpCode::kLSL: return "lsl";
+ case OpCode::kROXR: return "roxr";
+ case OpCode::kROXL: return "roxl";
+ case OpCode::kROR: return "ror";
+ case OpCode::kROL: return "rol";
+ }
+ assert(false);
+ return "?";
+}
+
+static const char *ToString(const OpSize s)
+{
+ switch (s) {
+ case OpSize::kNone: return "";
+ case OpSize::kByte: return "b";
+ case OpSize::kShort: return "s";
+ case OpSize::kWord: return "w";
+ case OpSize::kLong: return "l";
+ }
+ assert(false);
+ return "";
+}
+
+static int OpcodeSNPrintf(
+ char *const buf,
+ const size_t bufsz,
+ const OpCode opcode,
+ const Condition condition,
+ const OpSize size_spec)
+{
+ return snprintf(buf, bufsz, "%s%s", ToString(opcode, condition), ToString(size_spec));
+}
+
+static char RegChar(const uint8_t xi)
+{
+ return (xi & 0x08) ? 'a' : 'd';
+}
+
+static char SizeSpecChar(const uint8_t xi)
+{
+ return (xi & 0x10) ? 'l' : 'w';
+}
+
+static unsigned RegNum(const uint8_t xi)
+{
+ return xi & 0x7;
+}
+
+static size_t snprint_reg_mask(
+ char *const buf, const size_t bufsz, const uint32_t regmask_arg, const ArgType arg_type)
+{
+ const uint32_t regmask = regmask_arg & 0xffff;
+ size_t written = 0;
+ bool first_printed = 0;
+ size_t span = 0;
+ // 17-th bit used to close the span with 0 value unconditionally
+ for (int i = 0; i < 17; i++) {
+ const uint32_t mask = (i <= 15)
+ ? (1 << ((arg_type == ArgType::kRegMaskPredecrement) ? (15 - i) : i))
+ : 0;
+ const bool hit = regmask & mask;
+ const bool span_open = hit && span == 0;
+ const bool span_closed = !hit && span > 1;
+ const int printable_i = i - (span_closed ? 1 : 0);
+ const int id = printable_i % 8;
+ const char regtype = (printable_i >= 8) ? 'a' : 'd';
+ if (span_open || span_closed) {
+ const char *const delimiter = span_open ? (first_printed ? "/" : "") : "-";
+ const size_t remaining = bufsz - written;
+ const int ret = snprintf(buf + written, remaining, "%s%%%c%d", delimiter, regtype, id);
+ assert(ret > 0);
+ assert(static_cast<unsigned>(ret) >= sizeof("%d0")-1);
+ assert(static_cast<unsigned>(ret) <= sizeof("-%d0")-1);
+ written += Min(remaining, ret);
+ first_printed = true;
+ }
+ span = hit ? span + 1 : 0;
+ }
+ assert(written < bufsz); // Output must not be truncated
+ return written;
+}
+
+int Arg::SNPrint(
+ char *const buf,
+ const size_t bufsz,
+ const bool imm_as_hex,
+ const RefKindMask ref_kinds,
+ const char *const label,
+ const uint32_t self_addr,
+ const uint32_t ref_addr) const
+{
+ switch (type) {
+ case ArgType::kNone:
+ assert(false);
+ break;
+ case ArgType::kRaw:
+ return snprintf(buf, bufsz, "0x%04x", uword);
+ case ArgType::kDn:
+ return snprintf(buf, bufsz, "%%d%d", xn);
+ case ArgType::kAn:
+ return snprintf(buf, bufsz, "%%a%u", xn);
+ case ArgType::kAnAddr:
+ return snprintf(buf, bufsz, "%%a%u@", xn);
+ case ArgType::kAnAddrIncr:
+ return snprintf(buf, bufsz, "%%a%u@+", xn);
+ case ArgType::kAnAddrDecr:
+ return snprintf(buf, bufsz, "%%a%u@-", xn);
+ case ArgType::kD16AnAddr:
+ return snprintf(buf, bufsz, "%%a%u@(%d:w)", d16_an.an, d16_an.d16);
+ case ArgType::kD8AnXiAddr:
+ return snprintf(
+ buf, bufsz, "%%a%u@(%d,%%%c%u:%c)",
+ d8_an_xi.an,
+ d8_an_xi.d8,
+ RegChar(d8_an_xi.xi),
+ RegNum(d8_an_xi.xi),
+ SizeSpecChar(d8_an_xi.xi));
+ case ArgType::kWord:
+ case ArgType::kLong:
+ {
+ const char c = type == ArgType::kLong ? 'l' : 'w';
+ if (ref_kinds & kRefAbsMask) {
+ if (static_cast<uint32_t>(lword) == ref_addr) {
+ return snprintf(buf, bufsz, "%s:%c", label, c);
+ } else {
+ // It has to be AFTER the label we are gonna reference here
+ assert(static_cast<uint32_t>(lword) > ref_addr);
+ return snprintf(buf, bufsz, "%s+%d:%c", label, lword - ref_addr, c);
+ }
+ } else {
+ return snprintf(buf, bufsz, "0x%x:%c", lword, c);
+ }
+ }
+ case ArgType::kD16PCAddr:
+ if (ref_kinds & kRefRelMask) {
+ // XXX: Most of instructions with PC relative values have 2 bytes
+ // added to the offset, some does not. Still figuring that out.
+ const bool has_fix = ref_kinds & kRefPcRelFix2Bytes;
+ const uint32_t arg_addr = self_addr + d16_pc.d16 + kInstructionSizeStepBytes + (has_fix ? kInstructionSizeStepBytes : 0);
+ if (arg_addr == ref_addr) {
+ return snprintf(buf, bufsz, "%%pc@(%s:w)", label);
+ } else {
+ assert(arg_addr > ref_addr);
+ return snprintf(buf, bufsz, "%%pc@(%s+%d:w)", label, arg_addr - ref_addr);
+ }
+ } else {
+ return snprintf(buf, bufsz, "%%pc@(%d:w)", d16_pc.d16);
+ }
+ case ArgType::kD8PCXiAddr:
+ return snprintf(
+ buf, bufsz, "%%pc@(%d,%%%c%u:%c)",
+ d8_pc_xi.d8,
+ RegChar(d8_pc_xi.xi),
+ RegNum(d8_pc_xi.xi),
+ SizeSpecChar(d8_pc_xi.xi));
+ case ArgType::kImmediate:
+ if (ref_kinds & kRef1ImmMask) {
+ if (static_cast<uint32_t>(lword) == ref_addr) {
+ return snprintf(buf, bufsz, "#%s", label);
+ } else {
+ // It has to be AFTER the label we are gonna reference here
+ assert(static_cast<uint32_t>(lword) > ref_addr);
+ return snprintf(buf, bufsz, "#%s+%d", label, lword - ref_addr);
+ }
+ } else if (imm_as_hex) {
+ return snprintf(buf, bufsz, "#0x%x", lword);
+ } else {
+ return snprintf(buf, bufsz, "#%d", lword);
+ }
+ case ArgType::kRegMask:
+ case ArgType::kRegMaskPredecrement:
+ return snprint_reg_mask(buf, bufsz, uword, type);
+ case ArgType::kDisplacement:
+ if (ref_kinds & kRefRelMask) {
+ if (static_cast<uint32_t>(self_addr + lword) == ref_addr) {
+ return snprintf(buf, bufsz, "%s", label);
+ } else {
+ assert(static_cast<uint32_t>(self_addr + lword) > ref_addr);
+ return snprintf(buf, bufsz, "%s+%d", label, (self_addr + lword) - ref_addr);
+ }
+ } else {
+ return snprintf(buf, bufsz, ".%s%d", lword >= 0 ? "+" : "", lword);
+ }
+ case ArgType::kCCR:
+ return snprintf(buf, bufsz, "%%ccr");
+ case ArgType::kSR:
+ return snprintf(buf, bufsz, "%%sr");
+ case ArgType::kUSP:
+ return snprintf(buf, bufsz, "%%usp");
+ }
+ assert(false);
+ return -1;
+}
+
+int Op::FPrint(
+ FILE *const stream,
+ const char *const indent,
+ const bool imm_as_hex,
+ const RefKindMask ref_kinds,
+ const char *const ref1_label,
+ const char *const ref2_label,
+ const uint32_t self_addr,
+ const uint32_t ref1_addr,
+ const uint32_t ref2_addr) const
+{
+ assert(opcode != OpCode::kNone);
+ char mnemonic_str[kMnemonicBufferSize]{};
+ OpcodeSNPrintf(mnemonic_str, kMnemonicBufferSize, opcode, condition, size_spec);
+ if (arg1.type != ArgType::kNone) {
+ char arg1_str[kArgsBufferSize]{};
+ const RefKindMask ref1_kinds = ref_kinds & (kRef1Mask | kRefPcRelFix2Bytes);
+ // It is useful to have immediate value printed as hex if destination
+ // argument is plain address register, status register or condition code
+ // register. USP is not the case because it's value may be moved only to
+ // or from An register.
+ const bool imm_as_hex_2 = imm_as_hex ||
+ arg2.type == ArgType::kAn ||
+ arg2.type == ArgType::kCCR ||
+ arg2.type == ArgType::kSR;
+ arg1.SNPrint(
+ arg1_str,
+ kArgsBufferSize,
+ imm_as_hex_2,
+ ref1_kinds,
+ ref1_label,
+ self_addr,
+ ref1_addr);
+ if (arg2.type != ArgType::kNone) {
+ char arg2_str[kArgsBufferSize]{};
+ const RefKindMask ref2_kinds = ref_kinds & (kRef2Mask | kRefPcRelFix2Bytes);
+ arg2.SNPrint(
+ arg2_str,
+ kArgsBufferSize,
+ false,
+ ref2_kinds,
+ ref2_label,
+ self_addr,
+ ref2_addr);
+ return fprintf(stream, "%s%s %s,%s", indent, mnemonic_str, arg1_str, arg2_str);
+ } else {
+ return fprintf(stream, "%s%s %s", indent, mnemonic_str, arg1_str);
+ }
+ } else {
+ return fprintf(stream, "%s%s", indent, mnemonic_str);
+ }
+}
+
+void DisasmNode::AddReferencedBy(const uint32_t address_from, const ReferenceType ref_type)
+{
+ ReferenceNode *node{};
+ if (this->last_ref_by) {
+ node = this->last_ref_by;
+ } else {
+ node = new ReferenceNode{};
+ assert(node);
+ this->ref_by = this->last_ref_by = node;
+ }
+ node->refs[node->refs_count] = ReferenceRecord{ref_type, address_from};
+ node->refs_count++;
+ if (node->refs_count >= kRefsCountPerBuffer) {
+ ReferenceNode *new_node = new ReferenceNode{};
+ assert(new_node);
+ node->next = new_node;
+ this->last_ref_by = new_node;
+ }
+}
+
+DisasmNode::~DisasmNode()
+{
+ ReferenceNode *ref{this->ref_by};
+ while (ref) {
+ ReferenceNode *prev = ref;
+ ref = ref->next;
+ delete prev;
+ }
+}