m68kdecode
Version:
Port of deplinenoise/m68kdecode to Typescript
592 lines (572 loc) • 14.9 kB
text/typescript
/* eslint-disable @typescript-eslint/ban-types */
// Used to replace Rust enums with args
type DiscriminatedUnion<K extends PropertyKey, T extends object> = {
[P in keyof T]: { [Q in K]: P } & T[P] extends infer U
? { [Q in keyof U]: U[Q] }
: never;
}[keyof T];
export type Operation =
| "ANDITOCCR"
| "ANDITOSR"
| "EORITOCCR"
| "EORITOSR"
| "ORITOCCR"
| "ORITOSR"
| "MOVEP"
| "BTST"
| "BCHG"
| "BCLR"
| "BSET"
| "RTM"
| "CALLM"
| "ADDI"
| "SUBI"
| "ANDI"
| "ORI"
| "CMP2"
| "CHK2"
| "EORI"
| "CMPI"
| "MOVES"
| "MOVE"
| "MOVEA"
| "BGND"
| "ILLEGAL"
| "NOP"
| "RESET"
| "RTD"
| "RTE"
| "RTR"
| "RTS"
| "STOP"
| "TRAPV"
| "MOVEC"
| "SWAP"
| "BKPT"
| "EXTW"
| "EXTL"
| "EXTBL"
| "LEA"
| "LINK"
| "UNLK"
| "TRAP"
| "DIVSL"
| "DIVSLL"
| "DIVUL"
| "DIVULL"
| "JMP"
| "JSR"
| "MULS"
| "MULU"
| "NBCD"
| "MOVEFROMSR"
| "MOVETOSR"
| "MOVETOUSP"
| "MOVEFROMUSP"
| "MOVEFROMCCR"
| "MOVETOCCR"
| "PEA"
| "TAS"
| "MOVEM"
| "CLR"
| "NEG"
| "NEGX"
| "NOT"
| "TST"
| "CHK"
| "DBCC"
| "ADDQ"
| "SUBQ"
| "TRAPCC"
| "SCC"
| "BRA"
| "BSR"
| "BCC"
| "MOVEQ"
| "PACK"
| "UNPK"
| "SBCD"
| "DIVS"
| "DIVU"
| "OR"
| "SUBX"
| "SUB"
| "SUBA"
| "CMPA"
| "CMPM"
| "CMP"
| "EOR"
| "ABCD"
| "EXG"
| "AND"
| "ADDX"
| "ADD"
| "ADDA"
| "BFCHG"
| "BFCLR"
| "BFEXTS"
| "BFEXTU"
| "BFFFO"
| "BFINS"
| "BFSET"
| "BFTST"
| "ASL"
| "ASR"
| "LSL"
| "LSR"
| "ROXL"
| "ROXR"
| "ROL"
| "ROR"
| "FMOVECR"
| "FABS"
| "FSABS"
| "FDABS"
| "FACOS"
| "FADD"
| "FSADD"
| "FDADD"
| "FASIN"
| "FATAN"
| "FATANH"
| "FNOP"
| "FBCC"
| "FCMP"
| "FCOS"
| "FCOSH"
| "FDBCC"
| "FDIV"
| "FSDIV"
| "FDDIV"
| "FETOX"
| "FETOXM1"
| "FGETEXP"
| "FGETMAN"
| "FINT"
| "FINTRZ"
| "FLOG10"
| "FLOG2"
| "FLOGN"
| "FLOGNP1"
| "FMOD"
| "FMOVE"
| "FSMOVE"
| "FDMOVE"
| "FMOVEM"
| "FMUL"
| "FSMUL"
| "FDMUL"
| "FNEG"
| "FSNEG"
| "FDNEG"
| "FREM"
| "FSCALE"
| "FTRAPCC"
| "FSCC"
| "FSGLDIV"
| "FSGLMUL"
| "FSIN"
| "FSINCOS"
| "FSINH"
| "FSQRT"
| "FSSQRT"
| "FDSQRT"
| "FSUB"
| "FSSUB"
| "FDSUB"
| "FTAN"
| "FTANH"
| "FTENTOX"
| "FTST"
| "FTWOTOX";
/// Indicates how memory indexing is to be performed for 68020+ double memory operands.
export type MemoryIndirection =
/// Regular memory indirection.
| "Indirect"
/// Memory pre-indexed indirection (indexer applies to inner array).
| "IndirectPreIndexed"
/// Memory post-indexed indirection (indexer applies to outer array).
| "IndirectPostIndexed";
/// Indicates how indexing is to be performed.
export type Indexer = DiscriminatedUnion<
"kind",
{
/// Index using data register
DR: { reg: number; scale: number };
/// Index using address register
AR: { reg: number; scale: number };
}
>;
// Indexer factories
export const DRIndexer = (reg: number, scale: number): Indexer => ({
kind: "DR",
reg,
scale,
});
export const ARIndexer = (reg: number, scale: number): Indexer => ({
kind: "AR",
reg,
scale,
});
/// Indicates how a memory operand effective address is to be computed using displacement.
export interface Displacement {
/// Base displacement. This is always sign-extended to 32-bit by the instruction decoder.
baseDisplacement: number;
/// Outer displacement (only used for 68020+ double indirection modes)
outerDisplacement: number;
/// Indicates how indexing is to be performed.
indexer: Indexer | null;
/// Indicates what type of memory indirection is to be performed.
indirection: MemoryIndirection | null;
}
/// Describes one of the two operands in an instruction
export type Operand = DiscriminatedUnion<
"kind",
{
/// The operand is implied, for example when the CCR is being moved to or from.
///
/// When the operand is implied, the instruction documentation must be consulted to see what
/// data is being affected.
Implied: {};
/// An immediate 8-bit value.
///
/// Stored as unsigned, but it often needs to be interpreted as signed.
IMM8: { value: number };
/// An immediate 16-bit value.
///
/// Stored as unsigned, but it often needs to be interpreted as signed.
IMM16: { value: number };
/// An immediate 32-bit value.
///
/// Stored as unsigned, but it often needs to be interpreted as signed.
IMM32: { value: number };
/// An absolute address, given as a sign-extended 16-bit value.
ABS16: { value: number };
/// An absolute address, given as a full 32-bit value.
ABS32: { value: number };
/// A data register
DR: { reg: number };
/// An address register
AR: { reg: number };
/// A floating point register
FR: { reg: number };
/// A vanilla indrection without displacement, e.g. `(a0)`
ARIND: { reg: number };
/// Address register indirect with post-increment, e.g. `(a0)+`
ARINC: { reg: number };
/// Address register indirect with pre-decrement, e.g. `-(a0)`
ARDEC: { reg: number };
/// Address register indirect with displacement, e.g. `123(pc,d0)`
ARDISP: { reg: number; disp: Displacement };
/// Program counter indirect with displacement, e.g. `123(pc,d0)`
PCDISP: { offset: number; disp: Displacement };
/// Just a displacement (skipping the base register), e.g. `123(d0)`
DISP: { disp: Displacement };
/// A data register pair, used for 64-bit multiply/divide results.
DPAIR: { reg1: number; reg2: number };
/// A floating point register pair, used for `FSINCOS`. First register receives the sine, the
/// other the cosine.
FPAIR: { reg1: number; reg2: number };
/// A register bitmask for `MOVEM` or `FMOVEM`. The order of registers is reversed depending on
/// whether the address register is pre-decremented or post-incremented.
REGLIST: { bitmask: number };
/// A control register
CONTROLREG: { reg: number };
}
>;
// Operand factories
export const Implied = (): Operand => ({ kind: "Implied" });
export const IMM8 = (value: number): Operand => ({ kind: "IMM8", value });
export const IMM16 = (value: number): Operand => ({ kind: "IMM16", value });
export const IMM32 = (value: number): Operand => ({ kind: "IMM32", value });
export const ABS16 = (value: number): Operand => ({ kind: "ABS16", value });
export const ABS32 = (value: number): Operand => ({ kind: "ABS32", value });
export const DR = (reg: number): Operand => ({ kind: "DR", reg });
export const AR = (reg: number): Operand => ({ kind: "AR", reg });
export const FR = (reg: number): Operand => ({ kind: "FR", reg });
export const ARIND = (reg: number): Operand => ({
kind: "ARIND",
reg,
});
export const ARINC = (reg: number): Operand => ({
kind: "ARINC",
reg,
});
export const ARDEC = (reg: number): Operand => ({
kind: "ARDEC",
reg,
});
export const ARDISP = (reg: number, disp: Displacement): Operand => ({
kind: "ARDISP",
reg,
disp,
});
export const PCDISP = (offset: number, disp: Displacement): Operand => ({
kind: "PCDISP",
offset,
disp,
});
export const DISP = (disp: Displacement): Operand => ({ kind: "DISP", disp });
export const DPAIR = (reg1: number, reg2: number): Operand => ({
kind: "DPAIR",
reg1,
reg2,
});
export const FPAIR = (reg1: number, reg2: number): Operand => ({
kind: "FPAIR",
reg1,
reg2,
});
export const REGLIST = (bitmask: number): Operand => ({
kind: "REGLIST",
bitmask,
});
export const CONTROLREG = (reg: number): Operand => ({
kind: "CONTROLREG",
reg,
});
/// Describes one leg of a 68020+ bitfield specification
export type BitfieldData = DiscriminatedUnion<
"kind",
{
/// The offset or width is static.
STATIC: { value: number };
/// The offset or width is dynamic and specified via a data register.
DYNAMIC: { reg: number };
}
>;
// BitfieldData factories:
export const STATIC = (value: number): BitfieldData => ({
kind: "STATIC",
value,
});
export const DYNAMIC = (reg: number): BitfieldData => ({
kind: "DYNAMIC",
reg,
});
/// A CPU condition code from the CCR
export enum ConditionCode {
/// Always True
CC_T = 0b0000,
/// Always False
CC_F = 0b0001,
/// High
CC_HI = 0b0010,
/// Low or Same
CC_LS = 0b0011,
/// Carry Clear
CC_CC = 0b0100,
/// Carry Set
CC_CS = 0b0101,
/// Not Equal
CC_NE = 0b0110,
/// Equal
CC_EQ = 0b0111,
/// Overflow Clear
CC_VC = 0b1000,
/// Overflow Set
CC_VS = 0b1001,
/// Plus
CC_PL = 0b1010,
/// Negative
CC_MI = 0b1011,
/// Greater or Equal
CC_GE = 0b1100,
/// Less
CC_LT = 0b1101,
/// Greater
CC_GT = 0b1110,
/// Less or Equal
CC_LE = 0b1111,
}
/// A FPU condition code
export enum FPConditionCode {
/// False
FPCC_F = 0b000000,
/// Equal
FPCC_EQ = 0b000001,
/// Ordered Greater Than
FPCC_OGT = 0b000010,
/// Ordered Greater Than or Equal
FPCC_OGE = 0b000011,
/// Ordered Less Than
FPCC_OLT = 0b000100,
/// Ordered Less Than or Equal
FPCC_OLE = 0b000101,
/// Ordered Greater Than or Less Than
FPCC_OGL = 0b000110,
/// Ordered
FPCC_OR = 0b000111,
/// Unordered
FPCC_UN = 0b001000,
/// Unordered or Equal
FPCC_UEQ = 0b001001,
/// Unordered or Greater Than
FPCC_UGT = 0b001010,
/// Unordered or Greater Than or Equal
FPCC_UGE = 0b001011,
/// Unordered or Less Than
FPCC_ULT = 0b001100,
/// Unordered or Less Than or Equal
FPCC_ULE = 0b001101,
/// Not Equal
FPCC_NE = 0b001110,
/// True
FPCC_T = 0b001111,
/// Signaling False
FPCC_SF = 0b010000,
/// Signaling Equal
FPCC_SEQ = 0b010001,
/// Greater Than
FPCC_GT = 0b010010,
/// Greater Than or Equal
FPCC_GE = 0b010011,
/// Less Than
FPCC_LT = 0b010100,
/// Less Than or Equal
FPCC_LE = 0b010101,
/// Greater Than or Less Than
FPCC_GL = 0b010110,
/// Greater Than or Less Than or Equal
FPCC_GLE = 0b010111,
/// Not (Greater Than or Less Than or Equal)
FPCC_NGLE = 0b011000,
/// Not (Greater Than or Less Than)
FPCC_NGL = 0b011001,
/// Not (Less Than or Equal)
FPCC_NLE = 0b011010,
/// Not (Less Than)
FPCC_NLT = 0b011011,
/// Not (Greater Than or Equal)
FPCC_NGE = 0b011100,
/// Not (Greater Than)
FPCC_NGT = 0b011101,
/// Signaling Not Equal
FPCC_SNE = 0b011110,
/// Signaling True
FPCC_ST = 0b011111,
}
/// Indicates the floating point format in memory for a FPU operation
export type FPFormat = DiscriminatedUnion<
"kind",
{
/// The memory operand is a 32-bit integer.
FPF_LONG_INT: {};
/// The memory operand is a IEEE 32-bit single.
FPF_SINGLE: {};
/// The memory operand is a 68k extended-precision real (10 bytes).
FPF_EXTENDED_REAL: {};
/// The memory operand is a 68k packed decimal real (BCD encoded) (12 bytes).
/// For `FMOVE`, this also includes a static _K-factor_ to be applied to the result.
FPF_PACKED_DECIMAL_REAL_STATIC: { fmove: number }; // Includes fmove K-factor
/// The memory operand is a 68k packed decimal real (BCD encoded) (12 bytes).
/// For `FMOVE`, this also includes a dynamic _K-factor_ to be applied to the result.
FPF_PACKED_DECIMAL_REAL_DYNAMIC: { fmove: number }; // Includes fmove K-factor
/// The memory operand is a 16-bit integer.
FPF_WORD_INT: {};
/// The memory operand is a IEEE 64-bit double.
FPF_DOUBLE: {};
/// The memory operand is an 8-bit integer.
FPF_BYTE_INT: {};
}
>;
// FPFormat factories
export const FPF_LONG_INT = (): FPFormat => ({
kind: "FPF_LONG_INT",
});
export const FPF_SINGLE = (): FPFormat => ({
kind: "FPF_SINGLE",
});
export const FPF_EXTENDED_REAL = (): FPFormat => ({
kind: "FPF_EXTENDED_REAL",
});
export const FPF_PACKED_DECIMAL_REAL_STATIC = (fmove: number): FPFormat => ({
kind: "FPF_PACKED_DECIMAL_REAL_STATIC",
fmove,
});
export const FPF_PACKED_DECIMAL_REAL_DYNAMIC = (fmove: number): FPFormat => ({
kind: "FPF_PACKED_DECIMAL_REAL_DYNAMIC",
fmove,
});
export const FPF_WORD_INT = (): FPFormat => ({
kind: "FPF_WORD_INT",
});
export const FPF_DOUBLE = (): FPFormat => ({
kind: "FPF_DOUBLE",
});
export const FPF_BYTE_INT = (): FPFormat => ({
kind: "FPF_BYTE_INT",
});
/// Additional attributes for an instruction that don't fit anywhere else.
export type InstructionExtra = DiscriminatedUnion<
"kind",
{
/// For 68020+ bitfield instructions, specifies the bitfield offset and width of the EA.
Bitfield: { offset: BitfieldData; width: BitfieldData };
/// For instructions involving a CPU condition code (e.g. `DBcc`), specifies the condition code
/// tested.
Condition: { code: ConditionCode };
/// For instructions involving a FPU condition code (e.g. `FDBcc`), specifies the condition code
/// tested.
FPCondition: { code: FPConditionCode };
/// Specifies the pack adjustment for BCD packing.
PackAdjustment: { value: number };
/// Specifies the float format of a memory operand for FPU instructions that use a memory EA.
FloatFormat: { format: FPFormat };
}
>;
// InstructionExtra factories
export const Bitfield = (
offset: BitfieldData,
width: BitfieldData
): InstructionExtra => ({
kind: "Bitfield",
offset,
width,
});
export const Condition = (code: ConditionCode): InstructionExtra => ({
kind: "Condition",
code,
});
export const FPCondition = (code: FPConditionCode): InstructionExtra => ({
kind: "FPCondition",
code,
});
export const PackAdjustment = (value: number): InstructionExtra => ({
kind: "PackAdjustment",
value,
});
export const FloatFormat = (format: FPFormat): InstructionExtra => ({
kind: "FloatFormat",
format,
});
/// Represents an instruction.
export interface Instruction {
/// The size of any data movement (the number of bytes read or written).
size: number;
/// The instruction itself.
operation: Operation;
/// The two operands involved, source and destination.
operands: [Operand | null, Operand | null];
/// Any additional data required to make sense of the operation.
extra: InstructionExtra | null;
}
/// Represents the result of decoding an instruction from a byte stream.
export interface DecodedInstruction {
/// The number of bytes that were consumed decoding the instruction.
bytesUsed: number;
/// The decoded instruction.
instruction: Instruction;
}
/// export Enumerates errors that can happen while decoding instructions.
export type DecodingError =
/// The instruction is not implemented in the decoder.
| "NotImplemented"
/// The code stream doesn't contain enough data to decode the instruction.
| "OutOfSpace"
/// An illegal register was specified in the instruction encoding.
| "BadRegister"
/// An illegal size was specified in the instruction encoding.
| "BadSize"
/// A reserved case was hit in the instruction encoding.
| "Reserved";