summaryrefslogtreecommitdiff
path: root/static/mips.js
diff options
context:
space:
mode:
Diffstat (limited to 'static/mips.js')
-rw-r--r--static/mips.js627
1 files changed, 627 insertions, 0 deletions
diff --git a/static/mips.js b/static/mips.js
new file mode 100644
index 0000000..c839482
--- /dev/null
+++ b/static/mips.js
@@ -0,0 +1,627 @@
+'use strict';
+
+/*
+ * Copyright (c) 2022, lemon
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+const $ = x => document.getElementById(x);
+const nullish = x => x === null || x === undefined || Number.isNaN(x);
+
+const registerNames = [
+ 'zero', 'at', 'v0', 'v1', 'a0', 'a1', 'a2', 'a3',
+ 't0', 't1', 't2', 't3', 't4', 't5', 't6', 't7',
+ 's0', 's1', 's2', 's3', 's4', 's5', 's6', 's7',
+ 't8', 't9', 'k0', 'k1', 'gp', 'sp', 's8', 'ra'
+];
+const regIdx2Name = i => registerNames[i];
+function regName2Idx(name) {
+ const asIdx = name.substring(0,1) == '$' ? parseInt(name.substring(1), 10) : null;
+ if (!nullish(asIdx) && asIdx >= 0 && asIdx < 32) return asIdx;
+ const asName = registerNames.indexOf(name.replace(/^\$/, "").replace(/^fp$/, "s8"));
+ if (asName >= 0) return asName;
+};
+
+function leftpad(s, count, pad=' ') {
+ return s.length >= count ? s : s + pad.repeat(count - s.length);
+}
+function rightpad(s, count, pad=' ') {
+ return s.length >= count ? s : pad.repeat(count - s.length) + s;
+}
+
+// int -> 4-byte hex str (8 characters)
+function int2hex(n) {
+ return rightpad(((n >> 16) & 0xFFFF).toString(16), 4, '0') + rightpad((n & 0xFFFF).toString(16), 4, '0');
+}
+
+function bswap32(n) {
+ return ((n >> 24) & 0xFF) << 0
+ | ((n >> 16) & 0xFF) << 8
+ | ((n >> 8) & 0xFF) << 16
+ | ((n >> 0) & 0xFF) << 24;
+}
+
+ ///////////////////////////////////
+//// ////
+//// INSTRUCTION DEFINITIONS ////
+//// ////
+ ///////////////////////////////////
+
+// some of the most common syntaxes are given a name here to facilitate reading comprehension
+const syntax = { 'regArith' : ['rd', 'rs', 'rt'],
+ 'shiftImm' : ['rd', 'rt', 'sa'],
+ 'shiftReg' : ['rd', 'rt', 'rs'],
+ 'arithImm' : ['rt', 'rs', 'imm16'],
+ 'arithImmU': ['rt', 'rs', 'imm16.u'],
+ 'memory' : ['rt', ['imm16', 'rs']]
+ };
+const instructionTable = {
+ 'add' : { 'op': 0x00, 'funct': 32, 'syntax': syntax.regArith },
+ 'addu' : { 'op': 0x00, 'funct': 33, 'syntax': syntax.regArith },
+ 'sub' : { 'op': 0x00, 'funct': 34, 'syntax': syntax.regArith },
+ 'subu' : { 'op': 0x00, 'funct': 35, 'syntax': syntax.regArith },
+ 'and' : { 'op': 0x00, 'funct': 36, 'syntax': syntax.regArith },
+ 'or' : { 'op': 0x00, 'funct': 37, 'syntax': syntax.regArith },
+ 'xor' : { 'op': 0x00, 'funct': 38, 'syntax': syntax.regArith },
+ 'nor' : { 'op': 0x00, 'funct': 39, 'syntax': syntax.regArith },
+ 'sll' : { 'op': 0x00, 'funct': 0, 'syntax': syntax.shiftImm },
+ 'srl' : { 'op': 0x00, 'funct': 2, 'syntax': syntax.shiftImm },
+ 'sra' : { 'op': 0x00, 'funct': 3, 'syntax': syntax.shiftImm },
+ 'sllv' : { 'op': 0x00, 'funct': 4, 'syntax': syntax.shiftReg },
+ 'srlv' : { 'op': 0x00, 'funct': 6, 'syntax': syntax.shiftReg },
+ 'srav' : { 'op': 0x00, 'funct': 7, 'syntax': syntax.shiftReg },
+ 'mfhi' : { 'op': 0x00, 'funct': 16, 'syntax': ['rd'] },
+ 'mflo' : { 'op': 0x00, 'funct': 18, 'syntax': ['rd'] },
+ 'mthi' : { 'op': 0x00, 'funct': 17, 'syntax': ['rs'] },
+ 'mtlo' : { 'op': 0x00, 'funct': 19, 'syntax': ['rs'] },
+ 'mult' : { 'op': 0x00, 'funct': 24, 'syntax': ['rs', 'rt'] },
+ 'multu' : { 'op': 0x00, 'funct': 25, 'syntax': ['rs', 'rt'] },
+ 'div' : { 'op': 0x00, 'funct': 26, 'syntax': ['rs', 'rt'] },
+ 'divu' : { 'op': 0x00, 'funct': 27, 'syntax': ['rs', 'rt'] },
+ 'slt' : { 'op': 0x00, 'funct': 42, 'syntax': syntax.regArith },
+ 'sltu' : { 'op': 0x00, 'funct': 43, 'syntax': syntax.regArith },
+ 'jr' : { 'op': 0x00, 'funct': 8, 'syntax': ['rs'] },
+ 'jalr' : { 'op': 0x00, 'funct': 9, 'syntax': ['rs'] },
+ 'j' : { 'op': 0x02, 'syntax': ['imm26.j'] },
+ 'jal' : { 'op': 0x03, 'syntax': ['imm26.j'] },
+ 'beq' : { 'op': 0x04, 'syntax': ['rs', 'rt', 'imm16.rel'] },
+ 'bne' : { 'op': 0x05, 'syntax': ['rs', 'rt', 'imm16.rel'] },
+ 'blez' : { 'op': 0x06, 'syntax': ['rs', 'imm16.rel'] },
+ 'bgtz' : { 'op': 0x07, 'syntax': ['rs', 'imm16.rel'] },
+ 'beql' : { 'op': 0x14, 'syntax': ['rs', 'rt', 'imm16.rel'] },
+ 'bnel' : { 'op': 0x15, 'syntax': ['rs', 'rt', 'imm16.rel'] },
+ 'blezl' : { 'op': 0x16, 'syntax': ['rs', 'imm16.rel'] },
+ 'bgtzl' : { 'op': 0x17, 'syntax': ['rs', 'imm16.rel'] },
+ 'addi' : { 'op': 0x08, 'syntax': syntax.arithImm },
+ 'addiu' : { 'op': 0x09, 'syntax': syntax.arithImm },
+ 'slti' : { 'op': 0x0A, 'syntax': syntax.arithImm },
+ 'sltiu' : { 'op': 0x0B, 'syntax': syntax.arithImm },
+ 'andi' : { 'op': 0x0C, 'syntax': syntax.arithImmU },
+ 'ori' : { 'op': 0x0D, 'syntax': syntax.arithImmU },
+ 'xori' : { 'op': 0x0E, 'syntax': syntax.arithImmU },
+ 'lui' : { 'op': 0x0F, 'syntax': ['rt', 'imm16.u'] },
+ 'lb' : { 'op': 0x20, 'syntax': syntax.memory },
+ 'lbu' : { 'op': 0x24, 'syntax': syntax.memory },
+ 'lh' : { 'op': 0x21, 'syntax': syntax.memory },
+ 'lhu' : { 'op': 0x25, 'syntax': syntax.memory },
+ 'lw' : { 'op': 0x23, 'syntax': syntax.memory },
+ 'lwl' : { 'op': 0x22, 'syntax': syntax.memory },
+ 'lwr' : { 'op': 0x26, 'syntax': syntax.memory },
+ 'sb' : { 'op': 0x28, 'syntax': syntax.memory },
+ 'sh' : { 'op': 0x29, 'syntax': syntax.memory },
+ 'sw' : { 'op': 0x2B, 'syntax': syntax.memory },
+ 'swl' : { 'op': 0x2A, 'syntax': syntax.memory },
+ 'swr' : { 'op': 0x2E, 'syntax': syntax.memory },
+ 'cache' : { 'op': 0x2F, 'syntax': ['cacheop', ['imm16', 'rs']] },
+ 'mfc0' : { 'op': 0x10, 'rs': 0, 'syntax': ['rt', 'fs'] },
+ 'mtc0' : { 'op': 0x10, 'rs': 4, 'syntax': ['rt', 'fs'] },
+ 'mfc1' : { 'op': 0x11, 'rs': 0, 'syntax': ['rt', 'fs'] },
+ 'mtc1' : { 'op': 0x11, 'rs': 4, 'syntax': ['rt', 'fs'] },
+ 'cfc1' : { 'op': 0x11, 'rs': 2, 'syntax': ['rt', 'fs'] },
+ 'ctc1' : { 'op': 0x11, 'rs': 6, 'syntax': ['rt', 'fs'] },
+ 'bc1f' : { 'op': 0x11, 'rs': 8, 'ndtf': 0, 'syntax': ['cc', 'imm16.rel'] },
+ 'bc1t' : { 'op': 0x11, 'rs': 8, 'ndtf': 1, 'syntax': ['cc', 'imm16.rel'] },
+ 'bc1fl' : { 'op': 0x11, 'rs': 8, 'ndtf': 2, 'syntax': ['cc', 'imm16.rel'] },
+ 'bc1ft' : { 'op': 0x11, 'rs': 8, 'ndtf': 3, 'syntax': ['cc', 'imm16.rel'] },
+ 'syscall': { 'op': 0x00, 'funct': 12, 'optsyntax': ['syscode'] },
+ 'break' : { 'op': 0x00, 'funct': 13, 'optsyntax': ['brkcode'] },
+ 'nop' : { 'op': 0x00, 'rs': 0, 'rt': 0, 'rd': 0, 'sa': 0, 'funct': 0 }
+}; Object.freeze(instructionTable);
+
+function _decodeEncode(f) {
+ // f: (field,offset,size) -> { ... }
+ f('op', 26, 6)
+ f('rs', 21, 5)
+ f('rt', 16, 5)
+ f('rd', 11, 5)
+ f('sa', 6, 5)
+ f('funct', 0, 6)
+ f('imm16', 0, 16)
+ f('imm26', 0, 26)
+ f('fs', 11, 5)
+ f('ndtf', 16, 2)
+ f('cc', 18, 3)
+ f('brkcode', 16, 10)
+ f('syscode', 6, 20)
+ f('cacheop', 16, 5)
+};
+
+// bit masks containing the bits that each instruction makes use of,
+// to detect if an instruction is setting unused bits to a non-zero value
+let instrUsedBits = {};
+for (const i in instructionTable) {
+ instrUsedBits[i] = 0;
+
+ // gather used fields
+ let usedFields = [];
+ for (const k in instructionTable[i])
+ usedFields.push(k)
+ const visit = x => Array.isArray(x) ? x.forEach(visit) : usedFields.push(x.replace(/\..*$/,""));
+ if (instructionTable[i].syntax) visit(instructionTable[i].syntax);
+ if (instructionTable[i].optsyntax) visit(instructionTable[i].optsyntax);
+
+ // build bitmask from fields
+ _decodeEncode((name, offst, siz) => {
+ if (usedFields.includes(name))
+ instrUsedBits[i] |= ((1 << siz) - 1) << offst;
+ });
+};
+
+
+ /////////////////////////////////////////
+//// ////
+//// INSTRUCTION ENCODING/DECODING ////
+//// ////
+ /////////////////////////////////////////
+
+function encode(ins) {
+ let word = 0;
+ let setbits = 0;
+ _decodeEncode((field, offset, siz) => {
+ const mask = (1 << siz) - 1;
+ if (!nullish(ins[field])) {
+ if ((setbits & (mask << offset)) != 0)
+ throw "! Internal error: overwriting instruction fields during encoding! " + field + " instr " + ins;
+ word = (word & ~(mask << offset)) | ((ins[field] & mask) << offset);
+ setbits |= mask << offset;
+ }
+ });
+ //console.log(int2hex(setbits), ins);
+ //if (setbits != (0xFFFFFFFF|0)) throw "! Not all bits set during encoding!"; // hmm....
+ return word;
+};
+
+function decode(word) {
+ let ins = {};
+ _decodeEncode((field, offset, siz) => {
+ ins[field] = (word >> offset) & ((1 << siz) - 1);
+ });
+ return ins;
+};
+
+ ///////////////////////////////////////////////////////
+//// ////
+//// INDIVIDUAL INSTRUCTION ASSEMBLY/DISASSEMBLY ////
+//// ////
+ ///////////////////////////////////////////////////////
+
+function assemble(labels, line, address) {
+ const insName = line.split(' ')[0];
+ if (insName == '.org' || (insName.substring(insName.length - 1) == ':' && !nullish(labels[insName.substring(0, insName.length - 1)]))) return 'continue';
+ const insFmt = instructionTable[insName];
+ const args = line.indexOf(' ') < 0 ? null : line.substring(line.indexOf(' ')).replace(/\s*/g, "").split(',');
+ if (insName == '.word') {
+ if (!args || args.length != 1) throw "! Illegal syntax for .word: " + line;
+ const arg = args[0];
+ let word;
+ if (arg.match(/^[0-9]+$/))
+ word = parseInt(arg, 10);
+ else if (arg.match(/^0x[0-9a-f]+$/))
+ word = parseInt(arg.substring(2), 16);
+ if (nullish(word)) throw "! Illegal syntax for .word: " + line;
+ if (word > 0xFFFFFFFF) throw "! .word: " + arg + " too large";
+ return word;
+ }
+
+ if (!insFmt) throw "! Unknown instruction " + insName;
+ for (const i in args) {
+ // turn "x(y)" into [x, y]
+ const matches = args[i].match(/^([^)]+)\(([^)]+)\)$/);
+ if (matches && matches.length == 3) args[i] = [matches[1], matches[2]];
+ }
+
+ let ins = {};
+ for (const k in insFmt) ins[k] = insFmt[k];
+ const syntax = insFmt.syntax || insFmt.optsyntax;
+ if (!(!args && insFmt.optsyntax) && (args || syntax)) {
+ if (!syntax || !args)
+ throw "! Invalid syntax for " + insName;
+
+ function match(fmt, arg, dest) {
+ if (Array.isArray(fmt)) {
+ if (arg.length != fmt.length) throw "! Invalid syntax for " + insName;
+ for (let i = 0; i < arg.length; i++) {
+ const field = fmt[i];
+ match(field, arg[i], dest);
+ }
+ } else {
+ //assert(typeof(fmt) == 'string');
+ if (['rt','rs','rd'].includes(fmt)) { // base registers
+ const regIdx = regName2Idx(arg);
+ //console.log(fmt);
+ if (nullish(regIdx)) throw "! Invalid register name " + arg;
+ dest[fmt] = regIdx;
+ } else if (fmt == 'sa') { // shift amount
+ let shamt;
+ if (arg.match(/^[0-9]+$/))
+ shamt = parseInt(arg, 10);
+ else if (arg.match(/^0x[0-9a-f]+$/))
+ shamt = parseInt(arg.substring(2), 16);
+ if (nullish(shamt)) throw "! Invalid shift amount " + arg;
+ if (shamt < 0 || shamt > 31) throw "! Shift amount out of range";
+ dest.sa = shamt;
+ } else if (['fs'].includes(fmt)) { // floating point registers
+ try {
+ const regIdx = parseInt(arg.match(/^\$?f([0-9]+)$/)[1], 10);
+ if (regIdx < 0 || regIdx > 31) throw "";
+ dest[fmt] = regIdx;
+ } catch {
+ throw "! Invalid floating-point register name " + arg;
+ }
+ } else if (fmt == 'imm16' || fmt == 'imm16.u') { // 16-bit immediates
+ let negate = false;
+ if (Array.isArray(arg) && arg.length == 2 && ['%hi','%lo'].includes(arg[0])) {
+ if (!nullish(labels[arg[1]]))
+ dest.imm16 = (labels[arg[1]] >> (arg[0] == '%hi' ? 16 : 0)) & 0xFFFF;
+ else
+ throw "! No such label: " + arg[1];
+ } else if (typeof arg === 'string') {
+ let str = arg;
+ if (str.substring(0,1) == '-') {
+ negate = true;
+ str = str.substring(1);
+ }
+ let n;
+ let hex = false;
+ if (str.match(/^[0-9]+$/)) n = parseInt(str, 10);
+ else if (str.match(/^0x[0-9a-fA-F]+$/)) {
+ n = parseInt(str.substring(2), 16);
+ hex = true;
+ }
+ if (nullish(n)) throw "! Invalid syntax for immediate value " + arg;
+ if (negate) n = -n;
+
+ let min, max;
+ if (hex) { min = -0x7FFF - 1, max = 0xFFFF; }
+ else if (fmt == 'imm16') { min = -0x7FFF - 1, max = 0x7FFF; }
+ else /*fmt == 'imm16.u'*/{ min = 0, max = 0xFFFF; }
+ if (n > max || n < min) throw "! Immediate value out of range " + arg;
+ dest.imm16 = n & 0xFFFF;
+ } else throw "! Bad arg: " + arg;
+ } else if (fmt == 'imm16.rel') { // 16 (18)-bit address/relative branch offset
+ const baseAddr = address + 4;
+ let absolute = true;
+ let negate = false;
+ let str = arg;
+ if (str.substring(0,1) == '+') {
+ absolute = false;
+ str = str.substring(1);
+ } else if (str.substring(0,1) == '-') {
+ absolute = false;
+ negate = true;
+ str = str.substring(1);
+ }
+ let target;
+ if (!absolute && str.match(/^[0-9]+$/))
+ target = parseInt(str, 10);
+ else if (str.match(absolute ? /^(0x)?[0-9a-f]+$/ : /^0x[0-9a-f]+$/))
+ target = parseInt(str.replace(/^0x/, ""), 16);
+ else if (absolute) {
+ target = labels[str];
+ if (nullish(target)) throw "! No such label: " + str;
+ }
+
+ if (nullish(target)) throw "! Invalid syntax for branch target " + arg;
+ if (negate) target = -target;
+ if (!absolute) target = baseAddr + target * 4;
+ if (target & 3) throw "! Unaligned branch target " + line;
+ const disp = (target - baseAddr) >> 2;
+ if (disp < -0x8000 || disp > 0x7FFF) throw "! Jump target " + int2hex(target) + " out of range";
+ dest.imm16 = disp & 0xFFFF;
+ } else if (fmt == 'imm26.j') { // j and jal
+ const baseAddr = address + 4;
+ let target;
+ if (arg.match(/^(0x)?[0-9a-f]+$/))
+ target = parseInt(arg.replace(/^0x/, ""), 16);
+ else {
+ target = labels[arg];
+ if (nullish(target)) throw "! No such label: " + arg;
+ }
+
+ if (nullish(target)) throw "! Invalid syntax for branch target " + arg;
+ if (target & 3) throw "! Unaligned branch target";
+ if ((baseAddr & 0xF0000000) != (target & 0xF0000000)) throw "! Branch target " + int2hex(target) + " out of range";
+ dest.imm26 = (target >> 2) & 0x03FFFFFF;
+ } else if (fmt == 'brkcode' || fmt == 'syscode') { // break/syscall code
+ let str = arg;
+ let n;
+ if (str.match(/^[0-9]+$/)) n = parseInt(str, 10);
+ else if (str.match(/^0x[0-9a-fA-F]+$/))
+ n = parseInt(str.substring(2), 16);
+ else
+ throw "! Invalid syntax for immediate value " + arg;
+
+ const max = (fmt == 'brkcode') ? 0x3FF : 0xFFFFF;
+ if (n < 0 || n > max) throw "! Immediate value out of range " + arg;
+ dest[fmt] = n;
+ } else if (fmt == 'cacheop') {
+ let str = arg;
+ let n;
+ if (str.match(/^[0-9]+$/)) n = parseInt(str, 10);
+ else if (str.match(/^0x[0-9a-fA-F]+$/))
+ n = parseInt(str.substring(2), 16);
+ if (nullish(n)) throw "! Invalid syntax for cache op " + arg;
+
+ if (n < 0 || n > 31) throw "! Cache op out of range " + arg;
+ dest.cacheop = n;
+ } else throw "! Unimplemented syntax " + fmt;
+ }
+ }
+
+ match(syntax, args, ins);
+ }
+
+ return encode(ins);
+};
+
+function disassemble(word, address, showNote) {
+ if (word === 0) return 'nop';
+ const ins = decode(word);
+ let name;
+ for (const insName in instructionTable) {
+ const insFmt = instructionTable[insName];
+ let matches = true;
+ for (const k in insFmt) {
+ if (!k.match(/syntax/) && insFmt[k] != ins[k]) { matches = false; break; }
+ }
+ if (matches) { name = insName; break; }
+ }
+
+ if (nullish(name)) return '??';
+
+ let note = '';
+ function display(fmt, ins, depth=0) {
+ if (!fmt) return "";
+ if (Array.isArray(fmt)) {
+ if (depth == 0 || fmt.length != 2) {
+ // arg1,arg2,...
+ let s = '';
+ for (const i in fmt) {
+ s += display(fmt[i], ins, depth + 1);
+ if (i != fmt.length - 1) s += ',';
+ }
+ return s;
+ } else {
+ // arg1(arg2)
+ return display(fmt[0], ins, depth + 1) + '('+display(fmt[1], ins, depth + 1)+')';
+ }
+ } else {
+ if (['rs','rt','rd'].includes(fmt)) // basic register
+ return regIdx2Name(ins[fmt]);
+ else if (fmt == 'sa') // shift amount
+ return ins.sa+'';
+ else if (['fs'].includes(fmt)) // float regiser
+ return '$f'+ins[fmt];
+ else if (fmt == 'imm16') { // signed 16-bit immediate
+ const n = (ins.imm16 << 16) >> 16; // sign extension
+ if (n > 9 || n < -9)
+ note = leftpad(n+'', 6) + ' = '+n.toString(16).replace(/^-/, "-0x").replace(/^([^-])/, "0x$1")
+ return ''+n;
+ } else if (fmt == 'imm16.u') { // unsigned 16-bit immediate
+ const n = ins.imm16;
+ const paddedHex = '0x'+rightpad(n.toString(16), 4, '0');
+ if (n > 9 || n < -9)
+ note = paddedHex + ' = ' + n;
+ return paddedHex;
+ } else if (fmt == 'imm16.rel') { // relative branch offset
+ const offst = (ins.imm16 << 16) >> 16;
+ const dispStr = offst >= 0 ? '+0x'+offst.toString(16)
+ : '-0x'+(-offst).toString(16);
+ if (nullish(address)) {
+ return dispStr;
+ } else {
+ const target = address + 4 + (offst << 2);
+ note = dispStr;
+ return int2hex(target);
+ }
+ } else if (fmt == 'imm26.j') { // j and jal
+ if (nullish(address))
+ return int2hex(ins.imm26 << 2);
+ return ((address >> 28) & 0xF).toString(16)+int2hex(ins.imm26 << 2).substring(1);
+ } else if (fmt == 'brkcode' || fmt == 'syscode') { // break/syscall code
+ return ins[fmt] ? '0x'+ins[fmt].toString(16) : '';
+ } else if (fmt == 'cacheop') {
+ return '0x'+ins.cacheop.toString(16);
+ } else throw "! Unimplemented syntax " + fmt;
+
+ }
+ };
+ const syntax = instructionTable[name].syntax || instructionTable[name].optsyntax;
+ const base = leftpad(name, 8) + display(syntax, ins);
+ if ((word & ~instrUsedBits[name]) != 0) note = "* non-zero unused bits";
+ if (note && showNote) return leftpad(base, 27) + ' ; ' + note;
+ return base;
+};
+
+ ////////////////////////////////////////////////
+//// ////
+//// "WHOLE-PROGRAM" ASSEMBLY/DISASSEMBLY ////
+//// ////
+ ////////////////////////////////////////////////
+
+function assembleListing(input) {
+ let lines = input.split('\n');
+ for (const i in lines) {
+ lines[i] = lines[i]
+ .toLowerCase() // normalize case
+ .replace(/^\s+/, "") // trim leading whitespace
+ .replace(/\s*(;.*)?$/,"") // trim trailing whitespace and comments
+ .replace(/\s+/, " "); // simplify whitespace
+ }
+ lines = lines.filter(s => s.length != 0); // remove empty lines
+
+ // map lines -> addresses, also find labels
+ let addresses = [];
+ let lastAddress = -4;
+ let labels = {};
+ for (const line of lines) {
+ if (line.match(/^\.org.*/)) {
+ try {
+ const matches = [...line.matchAll(/^\.org\s+(0x)?([a-zA-Z0-9]+)$/g)];
+ const addr = parseInt(matches[0][2], 16);
+ addresses.push(addr);
+ lastAddress = addr - 4;
+ } catch {
+ throw "! Invalid .org syntax (should be ex: .org 800ABCD0)"
+ }
+ if (lastAddress+4 > 0xFFFFFFFF) throw "! .org: address out of range"
+ if (lastAddress & 3) throw "! Unaligned address is disallowed"
+ } else if (line.match(/^[_a-zA-Z$][_a-zA-Z0-9$]*:$/)) { // label
+ addresses.push(lastAddress+4);
+ const label = line.replace(/:$/, "");
+ if (labels[label] !== undefined) throw "! Redefining label " + label;
+ labels[label] = lastAddress+4;
+ } else {
+ lastAddress += 4;
+ addresses.push(lastAddress);
+ }
+ }
+
+ let output = [];
+ for (const i in lines) {
+ const res = assemble(labels, lines[i], addresses[i]);
+ if (res === 'continue') continue;
+ output.push([addresses[i], res]);
+ }
+
+ return output;
+};
+
+function disassembleListing(input, orgAddr, littleEndian, showNote) {
+ // remove punctuation and whitespace
+ input = input.replace(/([-.,]|\^|\s|[\n])/g, "");
+ if (input.length % 8 != 0) throw "! Hex words expected (incorrect bit length in input)";
+
+ let addr = orgAddr;
+ let output = [];
+ for (let i = 0; i < input.length; i += 8) {
+ const hex = input.substring(i, i + 8);
+ const word = hex.match(/^[0-9A-Fa-f]+$/) ? parseInt(hex, 16) : null;
+ if (nullish(word)) throw "! Invalid hex word: " + hex;
+ output.push([addr, word, disassemble(littleEndian ? bswap32(word) : word, addr, showNote)]);
+ addr += 4;
+ }
+
+ return output;
+};
+
+ ////////////////////////////////////////
+//// ////
+//// 'DOM' INTERFACE INPUT/OUTPUT ////
+//// ////
+ ////////////////////////////////////////
+
+function assembleAndPresent() {
+ let out = $('assembler-output');
+ try {
+ const insns = assembleListing($('assembler-input').value);
+ const hideAddr = $('assembler-hideaddr').checked;
+ const hideMnemonic = $('assembler-hidemnemonic').checked;
+ const hideNote = $('assembler-hidenote').checked;
+ const bigEndian = $('assembler-endian').value === 'big';
+
+ out.value = '';
+ insns.sort((x, y) => y[0] < x[0]);
+ let lastAddr;
+ for (const insn of insns) {
+ if (lastAddr !== undefined) {
+ if (lastAddr >= insn[0]) throw "! Overwriting instructions at address " + int2hex(insn[0]);
+ if (lastAddr < insn[0] - 4) out.value += "........\n";
+ }
+
+ const [addr, word] = insn;
+ if (!hideAddr) out.value += int2hex(addr) + ': ';
+ out.value += int2hex(bigEndian ? word : bswap32(word));
+ if (!hideMnemonic) out.value += ' ' + disassemble(word, addr, !hideNote);
+ out.value += '\n';
+
+ lastAddr = addr;
+ };
+ } catch (e) {
+ console.error(e);
+ out.value = "! Error!!\n" + e;
+ }
+}
+
+function disassembleAndPresent() {
+ let out = $('disassembler-output');
+ try {
+ const hideAddr = $('disassembler-hideaddr').checked;
+ const hideHex = $('disassembler-hidehex').checked;
+ const hideNote = $('disassembler-hidenote').checked;
+ const bigEndian = $('disassembler-endian').value === 'big';
+ const org = parseInt($('disassembler-org').value, 16) || 0;
+ if (org > 0xFFFFFFFF || org < 0)
+ throw "! Invalid starting address (out of range) " + rightpad(org.toString(16), 8, "0");
+ else if ((org & 3) != 0)
+ throw "! Invalid starting address (unaligned) " + rightpad(org.toString(16), 8, "0");
+
+ const insns = disassembleListing($('disassembler-input').value, org, !bigEndian, !hideNote);
+
+ out.value = '';
+ for (const insn of insns) {
+ const [addr, word, mnemonic] = insn;
+
+ if (!hideAddr) out.value += int2hex(addr) + ': ';
+ if (!hideHex) out.value += int2hex(word) + ' ';
+ out.value += mnemonic;
+ out.value += '\n';
+ }
+ } catch (e) {
+ console.error(e);
+ out.value = "! Error!!\n" + e;
+ }
+}
+
+window.onload = () => {
+ let disasmOrgPrevVal = $('disassembler-org').value || 0;
+ $('disassembler-org').oninput = (e) => {
+ // revert edit if not a valid hex address
+ const newval = e.target.value;
+ if (newval.length && !newval.match(/^[0-9A-Fa-f]+$/))
+ e.target.value = disasmOrgPrevVal;
+ else
+ disasmOrgPrevVal = e.target.value;
+ };
+};