UNPKG

m68kdecode

Version:

Port of deplinenoise/m68kdecode to Typescript

340 lines 9.38 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.instructionToString = void 0; const types_1 = require("./types"); const lib_1 = require("./lib"); // Map bytes to size qualifier const sizes = { 1: ".b", 2: ".w", 4: ".l" }; // Map float formats to size qualifier const floatSizes = { FPF_BYTE_INT: ".b", FPF_DOUBLE: ".d", FPF_EXTENDED_REAL: ".x", FPF_LONG_INT: ".l", FPF_PACKED_DECIMAL_REAL_STATIC: ".p", FPF_PACKED_DECIMAL_REAL_DYNAMIC: ".p", FPF_SINGLE: ".s", FPF_WORD_INT: ".w", }; // These operations only have a single size (at least per operand type) // so we can omit the qualifier text. const singleSize = [ "ABCD", "BCHG", "BCLR", "BSET", "BTST", "EXG", "EXG", "JMP", "JSR", "LEA", "MOVEFROMCCR", "MOVEFROMSR", "MOVEFROMUSP", "MOVEQ", "MOVETOCCR", "MOVETOSR", "MOVETOUSP", "NBCD", "PEA", "SBCD", "SCC", "SWAP", "TAS", "FMOVECR", "FMOVEM", "FSCC", ]; // Only output a single operand for these operations where src/dest are the same register const unaryFloat = [ "FABS", "FSABS", "FDABS", "FACOS", "FASIN", "FATAN", "FATANH", "FCOS", "FCOSH", "FETOX", "FETOXM1", "FGETEXP", "FGETMAN", "FINT", "FINTRZ", "FLOG10", "FLOG2", "FLOGN", "FLOGNP1", "FNEG", "FSNEG", "FDNEG", "FSIN", "FSINCOS", "FSINH", "FSQRT", "FSSQRT", "FDSQRT", "FTAN", "FTANH", "FTENTOX", "FTWOTOX", ]; // Map control registers codes to names const controlRegs = { 0x000: "sfc", 0x001: "dfc", 0x002: "cacr", 0x003: "tc", 0x004: "itt0", 0x005: "itt1", 0x006: "dtt0", 0x007: "dtt1", 0x008: "buscr", 0x800: "usp", 0x801: "vbr", 0x802: "caar", 0x803: "msp", 0x804: "isp", 0x805: "mmusr", 0x806: "urp", 0x807: "srp", 0x808: "pcr", 0xc00: "rombar", 0xc04: "rambar0", 0xc05: "rambar1", 0xc0f: "mbar0", }; /** * Convert decoded instruction object to source text * * @param {Instruction} instruction Decoded instruction * @returns {string} Source instruction text */ function instructionToString(instruction) { const { extra, operation, operands, size } = instruction; let out = " " + operationToString(instruction); // No size qualifier needed when src/dest are both FP registers const allFloat = operands[0]?.kind === "FR" && (!operands[1] || operands[1]?.kind === "FR" || operands[1]?.kind === "FPAIR"); if (!singleSize.includes(operation) && !allFloat) { const qualifier = extra?.kind === "FloatFormat" ? floatSizes[extra.format.kind] : sizes[size]; if (qualifier) { out += qualifier; } } const ops = operands .map((op) => operandToString(op, instruction)) .filter(Boolean); if (extra?.kind === "PackAdjustment") { ops.push("#" + extra.value); } if (extra?.kind === "Bitfield") { const offset = bitfieldDataToString(extra.offset); const width = bitfieldDataToString(extra.width); // Append to operand containing indirect address const i = ops.findIndex((v) => v?.includes("(a")); ops[i] += `{${offset}:${width}}`; } if (unaryFloat.includes(operation) && ops[0] === ops[1]) { ops.pop(); } if (ops.length) { out += " " + ops.join(","); } if (extra?.kind === "FloatFormat") { const format = extra.format; if (format.kind === "FPF_PACKED_DECIMAL_REAL_DYNAMIC") { out += `{d${format.fmove}}`; } else if (format.kind === "FPF_PACKED_DECIMAL_REAL_STATIC" && format.fmove !== 0) { out += `{#${format.fmove}}`; } } return out; } exports.instructionToString = instructionToString; function bitfieldDataToString(data) { return data.kind === "DYNAMIC" ? "d" + data.reg : data.value.toString(); } function operationToString(instruction) { const op = instruction.operation.toLowerCase(); const impliedMatch = op.match(/^(.+)(to|from)(sr|ccr|usp)$/); if (impliedMatch) { return impliedMatch[1]; } if (instruction.extra?.kind === "Condition") { return op.replace(/cc$/, types_1.ConditionCode[instruction.extra.code].substring(3).toLowerCase()); } if (instruction.extra?.kind === "FPCondition") { return op.replace(/cc$/, types_1.FPConditionCode[instruction.extra.code].substring(5).toLowerCase()); } switch (op) { case "divull": return "divul"; case "divsll": return "divsl"; case "divul": return "divu"; case "divsl": return "divs"; case "extbl": return "extb"; case "extw": case "extl": return "ext"; default: return op; } } function operandToString(op, inst) { switch (op?.kind) { case "ABS16": return (0, lib_1.i16)(op.value) + ".w"; case "ABS32": return op.value.toString(); case "AR": return "a" + op.reg; case "ARDEC": return `-(a${op.reg})`; case "ARDISP": return dispToString(op); case "ARINC": return `(a${op.reg})+`; case "ARIND": return `(a${op.reg})`; case "CONTROLREG": return controlRegs[op.reg]; case "DISP": return dispToString(op); case "DPAIR": return `d${op.reg2}:d${op.reg1}`; case "DR": return "d" + op.reg; case "FPAIR": return `fp${op.reg2}:fp${op.reg1}`; case "FR": return "fp" + op.reg; case "IMM16": case "IMM32": return "#" + op.value.toString(); case "IMM8": { const v = inst.operation.endsWith("Q") ? (0, lib_1.i8)(op.value) : op.value; return "#" + v.toString(); } case "Implied": { const match = inst.operation.match(/(TO|FROM)(SR|CCR|USP)$/); if (!match) { return null; // Omit implied shift } return match[2].toLowerCase(); } case "PCDISP": return dispToString(op); case "REGLIST": return inst.operation === "FMOVEM" ? floatRegListToString(op, inst.operands.indexOf(op) === 0) : regListToString(op, inst.operands.indexOf(op) === 0); } return null; } function regListToString(op, source) { if (op.kind !== "REGLIST") { throw new Error("Not a reglist operand"); } const dRegs = []; const aRegs = []; // Order of bits is reversed for source register const mask = source ? reverseBits(op.bitmask) : op.bitmask; for (let i = 0; i < 8; i++) { if (mask & (1 << i)) { dRegs.push(i); } if (mask & (1 << (i + 8))) { aRegs.push(i); } } return [...groupRegs(dRegs, "d"), ...groupRegs(aRegs, "a")].join("/"); } function floatRegListToString(op, source) { if (op.kind !== "REGLIST") { throw new Error("Not a reglist operand"); } const regs = []; const mask = source ? op.bitmask : reverseBits(op.bitmask); for (let i = 0; i < 8; i++) { if (mask & (1 << i)) { regs.push(i); } } return groupRegs(regs, "fp").join("/"); } // Combines sequential registers to range e.g. d1-d4 // Non sequential registers / groups separated with '/' function groupRegs(regs, t) { const outStrs = []; let start; let last; for (const r of regs) { if (last === undefined) { start = r; } else if (r !== 1 + last) { outStrs.push(start === last ? t + start : t + start + "-" + t + last); start = r; } last = r; } if (start !== undefined) { outStrs.push(start === last ? t + start : t + start + "-" + t + last); } return outStrs; } function reverseBits(num) { const reversed = num.toString(2).padStart(16); return parseInt(reversed.split("").reverse().join(""), 2); } function dispToString(op) { if (op.kind !== "ARDISP" && op.kind !== "PCDISP" && op.kind !== "DISP") { throw new Error("Not a displacement operand"); } let r; let out = ""; const args = []; if (op.disp.baseDisplacement) { out += op.disp.baseDisplacement; } if (op.kind === "ARDISP") { args.push("a" + op.reg); } if (op.kind === "PCDISP") { args.push("pc"); } if (op.disp.indexer) { let arg = op.disp.indexer.kind === "AR" ? "a" : "d"; arg += op.disp.indexer.reg; if (op.disp.indexer.scale) { arg += "*" + (1 << op.disp.indexer.scale); } // TODO: size? args.push(arg); } if (op.disp.outerDisplacement) { args.push(op.disp.outerDisplacement.toString()); } if (op.disp.indirection === "IndirectPreIndexed") { out += "-"; } out += `(${args.join(",")})`; if (op.disp.indirection === "IndirectPostIndexed") { out += "+"; } return out; } //# sourceMappingURL=to_string.js.map