pvbemu/app/windows/Disassembler.js

389 lines
14 KiB
JavaScript

"use strict";
// Decode and format instruction code
(globalThis.Disassembler = class Disassembler {
// Static initializer
static initializer() {
// Bcond conditions
this.BCONDS = [
"BV" , "BL" , "BZ" , "BNH", "BN", "BR" , "BLT", "BLE",
"BNV", "BNL", "BNZ", "BH" , "BP", "NOP", "BGE", "BGT"
];
// Mapping for bit string instruction IDs
this.BITSTRING = [
"SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD",
null , null , null , null ,
"ORBSU" , "ANDBSU" , "XORBSU" , "MOVBSU" ,
"ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU"
];
// Mapping for floating-point/Nintendo instruction IDs
this.FLOATENDO = [
"CMPF.S" , null , "CVT.WS" , "CVT.SW" ,
"ADDF.S" , "SUBF.S" , "MULF.S" , "DIVF.S" ,
"XB" , "XH" , "REV" , "TRNC.SW",
"MPYHW"
];
// Opcode definitions
this.OPDEFS = [
{ format: 1, mnemonic: "MOV" },
{ format: 1, mnemonic: "ADD" },
{ format: 1, mnemonic: "SUB" },
{ format: 1, mnemonic: "CMP" },
{ format: 1, mnemonic: "SHL" },
{ format: 1, mnemonic: "SHR" },
{ format: 1, mnemonic: "JMP" },
{ format: 1, mnemonic: "SAR" },
{ format: 1, mnemonic: "MUL" },
{ format: 1, mnemonic: "DIV" },
{ format: 1, mnemonic: "MULU" },
{ format: 1, mnemonic: "DIVU" },
{ format: 1, mnemonic: "OR" },
{ format: 1, mnemonic: "AND" },
{ format: 1, mnemonic: "XOR" },
{ format: 1, mnemonic: "NOT" },
{ format: 2, mnemonic: "MOV" },
{ format: 2, mnemonic: "ADD" },
{ format: 2, mnemonic: "SETF" },
{ format: 2, mnemonic: "CMP" },
{ format: 2, mnemonic: "SHL" },
{ format: 2, mnemonic: "SHR" },
{ format: 2, mnemonic: "CLI" },
{ format: 2, mnemonic: "SAR" },
{ format: 2, mnemonic: "TRAP" },
{ format: 2, mnemonic: "RETI" },
{ format: 2, mnemonic: "HALT" },
{ format: 0, mnemonic: null },
{ format: 2, mnemonic: "LDSR" },
{ format: 2, mnemonic: "STSR" },
{ format: 2, mnemonic: "SEI" },
{ format: 2, mnemonic: null },
{ format: 3, mnemonic: null },
{ format: 3, mnemonic: null },
{ format: 3, mnemonic: null },
{ format: 3, mnemonic: null },
{ format: 3, mnemonic: null },
{ format: 3, mnemonic: null },
{ format: 3, mnemonic: null },
{ format: 3, mnemonic: null },
{ format: 5, mnemonic: "MOVEA" },
{ format: 5, mnemonic: "ADDI" },
{ format: 4, mnemonic: "JR" },
{ format: 4, mnemonic: "JAL" },
{ format: 5, mnemonic: "ORI" },
{ format: 5, mnemonic: "ANDI" },
{ format: 5, mnemonic: "XORI" },
{ format: 5, mnemonic: "MOVHI" },
{ format: 6, mnemonic: "LD.B" },
{ format: 6, mnemonic: "LD.H" },
{ format: 0, mnemonic: null },
{ format: 6, mnemonic: "LD.W" },
{ format: 6, mnemonic: "ST.B" },
{ format: 6, mnemonic: "ST.H" },
{ format: 0, mnemonic: null },
{ format: 6, mnemonic: "ST.W" },
{ format: 6, mnemonic: "IN.B" },
{ format: 6, mnemonic: "IN.H" },
{ format: 6, mnemonic: "CAXI" },
{ format: 6, mnemonic: "IN.W" },
{ format: 6, mnemonic: "OUT.B" },
{ format: 6, mnemonic: "OUT.H" },
{ format: 7, mnemonic: null },
{ format: 6, mnemonic: "OUT.W" }
];
// Program register names
this.PROREGNAMES = [
"r0" , "r1" , "hp" , "sp" , "gp" , "tp" , "r6" , "r7" ,
"r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15",
"r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
"r24", "r25", "r26", "r27", "r28", "r29", "r30", "lp"
];
// SETF conditions
this.SETFS = [
"V" , "L" , "Z" , "NH", "N", "T", "LT", "LE",
"NV", "NL", "NZ", "H" , "P", "F", "GE", "GT"
];
// System register names
this.SYSREGNAMES = [
"EIPC", "EIPSW", "FEPC", "FEPSW", "ECR", "PSW", "PIR", "TKCW",
"8" , "9" , "10" , "11" , "12" , "13" , "14" , "15" ,
"16" , "17" , "18" , "19" , "20" , "21" , "22" , "23" ,
"CHCW", "ADTRE", "26" , "27" , "28" , "29" , "30" , "31"
];
}
/**************************** Static Methods *****************************/
// Disassemble instructions as lines of text
static disassemble(buffer, doffset, address, target, pc, line, lines) {
let history, hIndex;
// Two bytes before PC to ensure PC isn't skipped
let prePC = (pc - 2 & 0xFFFFFFFF) >>> 0;
// Prepare history buffer
if (line > 0) {
history = new Array(line);
hIndex = 0;
}
// Locate the line containing the target address
for (;;) {
// Emergency error checking
if (doffset >= buffer.length)
throw "Error: Target address not in disassembly buffer";
// Determine the size of the current line of output
let size = address == prePC ||
this.OPDEFS[buffer[doffset + 1] >>> 2].format < 4 ? 2 : 4;
// The line contians the target address
if ((target - address & 0xFFFFFFFF) >>> 0 < size)
break;
// Record the current line in the history
if (line > 0) {
let item = history[hIndex] = history[hIndex] || {};
hIndex = hIndex < history.length - 1 ? hIndex + 1 : 0;
item.address = address;
item.doffset = doffset;
}
// Advance to the next line
doffset += size;
address = (address + size & 0xFFFFFFFF) >>> 0;
}
// The target address is before the first line of output
for (; line < 0; line++) {
let size = address == prePC ||
this.OPDEFS[buffer[doffset + 1] >>> 2].format < 4 ? 2 : 4;
doffset += size;
address = (address + size & 0xFFFFFFFF) >>> 0;
}
// The target address is after the first line of output
if (line > 0) {
let item = history[hIndex];
// Emergency error checking
if (!item)
throw "Error: First output not in disassembly history";
// Inherit the address of the first history item
address = item.address;
doffset = item.doffset;
}
// Decode the lines of the output
let ret = new Array(lines);
for (let x = 0; x < lines; x++) {
let inst = ret[x] = this.decode(buffer, doffset, address);
let size = address == prePC ? 2 : inst.size;
doffset += size;
address = (address + size & 0xFFFFFFFF) >>> 0;
}
return ret;
}
/**************************** Private Methods ****************************/
// Retrieve the bits for an instruction
static bits(inst) {
return inst.size == 2 ?
inst.bytes[1] << 8 | inst.bytes[0] : (
inst.bytes[1] << 24 | inst.bytes[0] << 16 |
inst.bytes[3] << 8 | inst.bytes[2]
) >>> 0
;
}
// Decode one line of output
static decode(buffer, doffset, address) {
let opcode = buffer[doffset + 1] >>> 2;
let opdef = this.OPDEFS[opcode];
let size = opdef.format < 4 ? 2 : 4;
// Emergency error checking
if (doffset + size > buffer.length)
throw "Error: Insufficient disassembly data";
// Produce output line object
let inst = {
address : address,
mnemonic: opdef.mnemonic,
opcode : opcode,
operands: null,
size : size,
bytes : new Uint8Array(
buffer.buffer.slice(doffset, doffset + size))
};
// Processing by instruction format
switch (opdef.format) {
case 1: this.decodeFormat1(inst ); break;
case 2: this.decodeFormat2(inst ); break;
case 3: this.decodeFormat3(inst, address); break;
case 4: this.decodeFormat4(inst, address); break;
case 5: this.decodeFormat5(inst ); break;
case 6: this.decodeFormat6(inst ); break;
case 7: this.decodeFormat7(inst ); break;
}
// Illegal opcode
if (inst.mnemonic == null)
inst.mnemonic = "---";
return inst;
}
// Format I
static decodeFormat1(inst) {
let bits = this.bits(inst);
let reg1 = this.PROREGNAMES[bits & 31];
switch (inst.opcode) {
case 0b000110: // JMP
inst.operands = "[" + reg1 + "]";
break;
default: // All others
inst.operands = reg1 + ", " + this.PROREGNAMES[bits >> 5 & 31];
}
}
// Format II
static decodeFormat2(inst) {
let bits = this.bits(inst);
let reg2 = this.PROREGNAMES[bits >> 5 & 31];
let other = bits & 31;
switch (inst.opcode) {
case 0b010010: // SETF
inst.operands = this.SETFS[other & 15] + ", " + reg2;
break;
case 0b011000: // TRAP
inst.operands = other;
break;
case 0b011100: // LDSR
inst.operands = reg2 + ", " + this.SYSREGNAMES[other];
break;
case 0b011101: // STSR
inst.operands = this.SYSREGNAMES[other] + ", " + reg2;
break;
case 0b011111: // Bit string
inst.mnemonic = this.BITSTRING[other];
break;
case 0b010110: // CLI
case 0b011001: // RETI
case 0b011010: // HALT
case 0b011110: // SEI
break;
case 0b010000: // MOV
case 0b010001: // ADD
case 0b010011: // CMP
inst.operands = this.signExtend(other, 5) + ", " + reg2;
break;
default: // SHL, SHR, SAR
inst.operands = other + ", " + reg2;
}
}
// Format III
static decodeFormat3(inst, address) {
let bits = this.bits(inst);
let disp = this.signExtend(bits & 0x1FF, 9);
let cond = bits >> 9 & 15;
inst.mnemonic = this.BCONDS[cond];
if (cond == 13)
return; // NOP
inst.operands = ("0000000" + ((address + disp & 0xFFFFFFFF) >>> 0)
.toString(16).toUpperCase()).slice(-8);
}
// Format IV
static decodeFormat4(inst, address) {
let bits = this.bits(inst);
let disp = this.signExtend(bits & 0x3FFFFFF, 26);
inst.operands = ("0000000" + ((address + disp & 0xFFFFFFFF) >>> 0)
.toString(16).toUpperCase()).slice(-8);
}
// Format V
static decodeFormat5(inst) {
let bits = this.bits(inst);
let reg2 = this.PROREGNAMES[bits >> 21 & 31];
let reg1 = this.PROREGNAMES[bits >> 16 & 31];
let imm = bits & 0xFFFF;
switch (inst.opcode) {
case 0b101001: // ADDI
inst.operands =
this.signExtend(imm, 16) + ", " + reg1 + ", " + reg2;
break;
default: // All others
inst.operands = "0x" + ("000" + imm.toString(16).toUpperCase())
.slice(-4) + ", " + reg1 + ", " + reg2;
}
}
// Format VI
static decodeFormat6(inst) {
let bits = this.bits(inst);
let reg2 = this.PROREGNAMES[bits >> 21 & 31];
let reg1 = this.PROREGNAMES[bits >> 16 & 31];
let disp = this.signExtend(bits & 0xFFFF, 16);
disp = disp == 0 ? "" : (disp < -255 || disp > 255) ? "0x" +
("000" + (disp & 0xFFFF).toString(16).toUpperCase()).slice(-4) :
(disp < 0 ? "-" : "") + "0x" +
Math.abs(disp).toString(16).toUpperCase();
switch (inst.opcode) {
case 0b110000: // LD.B
case 0b110001: // LD.H
case 0b110011: // LD.W
case 0b111000: // IN.B
case 0b111001: // IN.H
case 0b111010: // CAXI
case 0b111011: // IN.W
inst.operands = disp + "[" + reg1 + "], " + reg2;
break;
default: // Output and store
inst.operands = reg2 + ", " + disp + "[" + reg1 + "]";
}
}
// Format VII
static decodeFormat7(inst) {
let bits = this.bits(inst);
let reg2 = this.PROREGNAMES[bits >> 21 & 31];
let reg1 = this.PROREGNAMES[bits >> 16 & 31];
let subop = bits >> 10 & 63;
inst.mnemonic = this.FLOATENDO[subop];
if (inst.mnemonic == null)
return;
switch (subop) {
case 0b001000: // XB
case 0b001001: // XH
inst.operands = reg2;
break;
default: // All others
inst.operands = reg1 + ", " + reg2;
}
}
// Sign extend a value
static signExtend(value, bits) {
return value & 1 << bits - 1 ? value | -1 << bits : value;
}
}).initializer();