389 lines
14 KiB
JavaScript
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();
|