"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();