summaryrefslogtreecommitdiff
path: root/src/m68k.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/m68k.h')
-rw-r--r--src/m68k.h401
1 files changed, 401 insertions, 0 deletions
diff --git a/src/m68k.h b/src/m68k.h
new file mode 100644
index 0000000..65429dc
--- /dev/null
+++ b/src/m68k.h
@@ -0,0 +1,401 @@
+#pragma once
+
+/* SPDX-License-Identifier: Unlicense
+ */
+
+#include "data_buffer.h"
+#include "common.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+
+enum class OpSize: int {
+ kByte = 0,
+ kWord = 1,
+ kLong = 2,
+ kInvalid = 3,
+ kNone = kInvalid,
+ kShort, ///< Semantically is the same as kByte, pseudosize, used for Bcc
+};
+
+enum class OpCode: uint8_t {
+ kNone,
+ kRaw, ///< Emits ".short"
+ kORI,
+ kANDI,
+ kSUBI,
+ kADDI,
+ kEORI,
+ kCMPI,
+ kBTST,
+ kBCHG,
+ kBCLR,
+ kBSET,
+ kMOVEP,
+ kMOVEA,
+ kMOVE,
+ kNEGX,
+ kCLR,
+ kNEG,
+ kNOT,
+ kEXT,
+ kNBCD,
+ kSWAP,
+ kPEA,
+ kILLEGAL,
+ kTAS,
+ kTST,
+ kTRAP,
+ kLINK,
+ kUNLK,
+ kRESET,
+ kNOP,
+ kSTOP,
+ kRTE,
+ kRTS,
+ kTRAPV,
+ kRTR,
+ kJSR,
+ kJMP,
+ kMOVEM,
+ kLEA,
+ kCHK,
+ kADDQ,
+ kSUBQ,
+ kScc,
+ kDBcc,
+ kBcc,
+ kMOVEQ,
+ kDIVU,
+ kDIVS,
+ kSBCD,
+ kOR,
+ kSUB,
+ kSUBX,
+ kSUBA,
+ kEOR,
+ kCMPM,
+ kCMP,
+ kCMPA,
+ kMULU,
+ kMULS,
+ kABCD,
+ kEXG,
+ kAND,
+ kADD,
+ kADDX,
+ kADDA,
+ kASR,
+ kASL,
+ kLSR,
+ kLSL,
+ kROXR,
+ kROXL,
+ kROR,
+ kROL,
+};
+
+enum class Condition: uint8_t {
+ kT = 0,
+ kF = 1,
+ kHI = 2,
+ kLS = 3,
+ kCC = 4,
+ kCS = 5,
+ kNE = 6,
+ kEQ = 7,
+ kVC = 8,
+ kVS = 9,
+ kPL = 10,
+ kMI = 11,
+ kGE = 12,
+ kLT = 13,
+ kGT = 14,
+ kLE = 15,
+};
+
+enum class AddrMode: uint8_t {
+ kInvalid = 0,
+ kDn = 1,
+ kAn = 2,
+ kAnAddr = 3,
+ kAnAddrIncr = 4,
+ kAnAddrDecr = 5,
+ kD16AnAddr = 6,
+ kD8AnXiAddr = 7,
+ kWord = 8,
+ kLong = 9,
+ kD16PCAddr = 10,
+ kD8PCXiAddr = 11,
+ kImmediate = 12,
+};
+
+enum class ArgType: uint8_t {
+ kNone = 0,
+ kDn = 1, ///< Dn
+ kAn = 2, ///< An
+ kAnAddr = 3, ///< (An)
+ kAnAddrIncr = 4, ///< (An)+
+ kAnAddrDecr = 5, ///< -(An)
+ kD16AnAddr = 6, ///< (d16,An)
+ kD8AnXiAddr = 7, ///< (d8,An,Xi)
+ kWord = 8, ///< (xxx).W
+ kLong = 9, ///< (xxx).L
+ kD16PCAddr = 10, ///< (d16,PC)
+ kD8PCXiAddr = 11, ///< (d8,PC,Xn)
+ kImmediate = 12, ///< #imm
+ kRegMask,
+ kRegMaskPredecrement,
+ kDisplacement, ///< For BRA, BSR, Bcc and DBcc
+ kCCR,
+ kSR,
+ kUSP,
+ kRaw, ///< Emits "0xXXXX" for ".short"
+};
+
+struct D8AnPCXiAddr {
+ uint8_t an; ///< ID number of An reg, for kD8AnXiAddr only
+ /*! ID number of Xi reg (3 lower bits), for kD8AnXiAddr and kD8PCXiAddr.
+ * Bit 3 (mask 0x8) means 0 == Dn, 1 == An.
+ * Bit 4 (mask 0x10) means 0 == Word, 1 == Long.
+ */
+ uint8_t xi;
+ int8_t d8; ///< Displacement, for kD8AnXiAddr and kD8PCXiAddr
+};
+
+struct D16AnPCAddr {
+ uint8_t an; ///< ID number of An reg, for kD16AnAddr only
+ int16_t d16; ///< Displacement, for D16AnAddr and kD16PCAddr
+};
+
+static_assert(sizeof(D8AnPCXiAddr) <= sizeof(uint32_t), "");
+static_assert(sizeof(D16AnPCAddr) <= sizeof(uint32_t), "");
+
+struct Arg {
+ union {
+ ArgType type{ArgType::kNone};
+ AddrMode mode;
+ };
+ union {
+ int32_t lword{}; ///< kLong, kWord, kDisplacement, kImmediate
+ uint16_t uword; ///< kRegMask, kRaw
+ uint8_t xn; ///< kDn, kAn, kAnAddr, kAnAddrIncr, kAnAddrDecr
+ D16AnPCAddr d16_an; ///< kD16AnAddr
+ D16AnPCAddr d16_pc; ///< kD16PCAddr
+ D8AnPCXiAddr d8_an_xi; ///< kD8AnXiAddr
+ D8AnPCXiAddr d8_pc_xi; ///< kD8PCXiAddr
+ };
+ /// Size of the instruction extension: 0, 2 or 4 bytes
+ constexpr size_t Size(const OpSize s) const
+ {
+ switch (mode) {
+ case AddrMode::kInvalid:
+ case AddrMode::kDn:
+ case AddrMode::kAn:
+ case AddrMode::kAnAddr:
+ case AddrMode::kAnAddrIncr:
+ case AddrMode::kAnAddrDecr:
+ return 0;
+ case AddrMode::kD16AnAddr:
+ case AddrMode::kD8AnXiAddr:
+ case AddrMode::kWord:
+ return 2;
+ case AddrMode::kLong:
+ return 4;
+ case AddrMode::kD16PCAddr:
+ case AddrMode::kD8PCXiAddr:
+ return 2;
+ case AddrMode::kImmediate:
+ // Byte and Word immediate are of 2 bytes length
+ return s == OpSize::kLong ? 4 : 2;
+ }
+ return 0;
+ }
+ static constexpr auto AddrModeXn(const ArgType type, const uint8_t xn) {
+ Arg a{{type}, {0}};
+ a.xn = xn;
+ return a;
+ }
+ static constexpr auto Dn(const uint8_t xn) { return AddrModeXn(ArgType::kDn, xn); }
+ static constexpr auto An(const uint8_t xn) { return AddrModeXn(ArgType::kAn, xn); }
+ static constexpr auto AnAddr(const uint8_t xn) { return AddrModeXn(ArgType::kAnAddr, xn); }
+ static constexpr auto AnAddrIncr(const uint8_t xn)
+ {
+ return AddrModeXn(ArgType::kAnAddrIncr, xn);
+ }
+ static constexpr auto AnAddrDecr(const uint8_t xn)
+ {
+ return AddrModeXn(ArgType::kAnAddrDecr, xn);
+ }
+ static constexpr auto D16AnAddr(const uint8_t xn, const int16_t d16)
+ {
+ Arg a{{ArgType::kD16AnAddr}, {0}};
+ a.d16_an = D16AnPCAddr{xn, d16};
+ return a;
+ }
+ static constexpr auto D16PCAddr(const int16_t d16)
+ {
+ Arg a{{ArgType::kD16PCAddr}, {0}};
+ a.d16_pc = D16AnPCAddr{0, d16};
+ return a;
+ }
+ static constexpr auto Word(const int16_t w)
+ {
+ Arg a{{ArgType::kWord}, {0}};
+ a.lword = w;
+ return a;
+ }
+ static constexpr auto Long(const int32_t l)
+ {
+ Arg a{{ArgType::kLong}, {0}};
+ a.lword = l;
+ return a;
+ }
+ static constexpr auto D8AnXiAddr(
+ const uint8_t xn, const uint8_t xi, const OpSize s, const int8_t d8)
+ {
+ Arg a{{ArgType::kD8AnXiAddr}, {0}};
+ a.d8_an_xi = D8AnPCXiAddr{xn, uint8_t(xi | (s == OpSize::kLong ? 0x10u : 0u)), d8};
+ return a;
+ }
+ static constexpr auto D8PCXiAddr(
+ const uint8_t xn, const uint8_t xi, const OpSize s, const int8_t d8)
+ {
+ Arg a{{ArgType::kD8PCXiAddr}, {0}};
+ a.d8_pc_xi = D8AnPCXiAddr{xn, uint8_t(xi | (s == OpSize::kLong ? 0x10u : 0u)), d8};
+ return a;
+ }
+ static constexpr auto Immediate(const int32_t value) {
+ Arg a{{ArgType::kImmediate}, {0}};
+ a.lword = value;
+ return a;
+ }
+ static constexpr auto RegMask(const uint16_t regmask) {
+ Arg a{{ArgType::kRegMask}, {0}};
+ a.uword = regmask;
+ return a;
+ }
+ static constexpr auto RegMaskPredecrement(const uint16_t regmask) {
+ Arg a{{ArgType::kRegMaskPredecrement}, {0}};
+ a.uword = regmask;
+ return a;
+ }
+ static constexpr auto Displacement(const int32_t displacement) {
+ Arg a{{ArgType::kDisplacement}, {0}};
+ a.lword = displacement;
+ return a;
+ }
+ static constexpr auto CCR() { return Arg{{ArgType::kCCR}, {0}}; }
+ static constexpr auto SR() { return Arg{{ArgType::kSR}, {0}}; }
+ static constexpr auto USP() { return Arg{{ArgType::kUSP}, {0}}; }
+ static constexpr auto Raw(const uint16_t instr) {
+ Arg a{{ArgType::kRaw}, {0}};
+ a.uword = instr;
+ return a;
+ }
+ int SNPrint(
+ char *buf,
+ size_t bufsz,
+ bool imm_as_hex = false,
+ RefKindMask ref_kinds = 0,
+ const char *label = nullptr,
+ uint32_t self_addr = 0,
+ uint32_t ref_addr = 0) const;
+};
+
+enum class NodeType {
+ kTracedInstruction,
+ kRefInstruction,
+ kData,
+};
+
+constexpr size_t kRefsCountPerBuffer = 10;
+
+constexpr size_t kMnemonicBufferSize = 10;
+constexpr size_t kArgsBufferSize = 80;
+
+enum class ReferenceType {
+ kUnknown = 0,
+ kCall,
+ kBranch,
+ kRead,
+ kWrite,
+};
+
+struct ReferenceRecord {
+ ReferenceType type{};
+ uint32_t address{};
+};
+
+struct ReferenceNode {
+ ReferenceNode *next{};
+ ReferenceRecord refs[kRefsCountPerBuffer];
+ uint32_t refs_count{};
+};
+
+struct Op {
+ OpCode opcode{OpCode::kNone}; ///< Identifies instruction (mnemonic)
+ /// Size specifier, the suffix `b`, `w` or `l`
+ OpSize size_spec{OpSize::kNone};
+ Condition condition{Condition::kT}; ///< For Scc, Bcc and Dbcc
+ Arg arg1{}; ///< First argument, optional
+ Arg arg2{}; ///< Second argument, optional, cannot be set if arg1 is not set
+ static constexpr auto Typical(
+ const OpCode opcode = OpCode::kNone,
+ const OpSize opsize = OpSize::kNone,
+ const Arg arg1 = Arg{},
+ const Arg arg2 = Arg{})
+ {
+ return Op{opcode, opsize, Condition::kT, arg1, arg2};
+ }
+ static constexpr auto Raw(const uint16_t instr)
+ {
+ return Op::Typical(OpCode::kRaw, OpSize::kNone, Arg::Raw(instr));
+ }
+ int FPrint(
+ FILE *,
+ const char *indent,
+ bool imm_as_hex,
+ RefKindMask ref_kinds = 0,
+ const char *ref1_label = nullptr,
+ const char *ref2_label = nullptr,
+ uint32_t self_addr = 0,
+ uint32_t ref1_addr = 0,
+ uint32_t ref2_addr = 0) const;
+};
+
+struct DisasmNode {
+ const NodeType type{};
+ /// Address of the instruction (PC value basically)
+ const uint32_t address{};
+ /// Instruction size in bytes
+ size_t size{kInstructionSizeStepBytes};
+ /// Indicates whether `ref_addr` should be interpreted and how
+ RefKindMask ref_kinds{};
+ /// Address of first argument reference
+ uint32_t ref1_addr{};
+ /// Address of second argument reference
+ uint32_t ref2_addr{};
+ ReferenceNode *ref_by{};
+ ReferenceNode *last_ref_by{};
+ Op op{};
+
+ /*! Disassembles instruction with arguments
+ * returns size of whole instruction with arguments in bytes
+ */
+ size_t Disasm(const DataView &code);
+ size_t DisasmAsRaw(const DataView &code);
+ void AddReferencedBy(uint32_t address, ReferenceType);
+ ~DisasmNode();
+};
+
+static constexpr inline bool IsInstruction(NodeType t)
+{
+ return t == NodeType::kTracedInstruction || t == NodeType::kRefInstruction;
+}
+
+static constexpr inline bool IsBRA(Op op)
+{
+ return op.opcode == OpCode::kBcc && op.condition == Condition::kT;
+}