summaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
authorlemon <lsof@mailbox.org>2022-02-19 20:55:07 +0100
committerlemon <lsof@mailbox.org>2022-02-19 20:55:07 +0100
commit8b3f0aa590b51cfd7696ed212adf94fa43a0e858 (patch)
treedaefae18ee470d4f2f1622779d3cbf52d2bdeb61 /static
initial
Diffstat (limited to 'static')
-rw-r--r--static/asset/link.pngbin0 -> 972 bytes
-rw-r--r--static/asset/linux.pngbin0 -> 1224 bytes
-rw-r--r--static/asset/lynx.pngbin0 -> 2193 bytes
-rw-r--r--static/asset/mailto.pngbin0 -> 735 bytes
-rw-r--r--static/asset/nvim.pngbin0 -> 12359 bytes
-rw-r--r--static/mips.html158
-rw-r--r--static/mips.js627
-rw-r--r--static/style.css106
8 files changed, 891 insertions, 0 deletions
diff --git a/static/asset/link.png b/static/asset/link.png
new file mode 100644
index 0000000..6f2867f
--- /dev/null
+++ b/static/asset/link.png
Binary files differ
diff --git a/static/asset/linux.png b/static/asset/linux.png
new file mode 100644
index 0000000..e9397bd
--- /dev/null
+++ b/static/asset/linux.png
Binary files differ
diff --git a/static/asset/lynx.png b/static/asset/lynx.png
new file mode 100644
index 0000000..2b53dfa
--- /dev/null
+++ b/static/asset/lynx.png
Binary files differ
diff --git a/static/asset/mailto.png b/static/asset/mailto.png
new file mode 100644
index 0000000..20d9c9e
--- /dev/null
+++ b/static/asset/mailto.png
Binary files differ
diff --git a/static/asset/nvim.png b/static/asset/nvim.png
new file mode 100644
index 0000000..1559e3c
--- /dev/null
+++ b/static/asset/nvim.png
Binary files differ
diff --git a/static/mips.html b/static/mips.html
new file mode 100644
index 0000000..219034c
--- /dev/null
+++ b/static/mips.html
@@ -0,0 +1,158 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>MIPS (dis)assembler</title>
+ <link rel="stylesheet" href="/style.css"/>
+
+ <style>
+body {
+ max-width: 90%;
+}
+
+textarea {
+ resize: none;
+ width: 45%;
+ font-size: 110%;
+ font-family: monospace;
+ font-variant-ligatures: none;
+ color: var(--pre-fg);
+ background: var(--pre-bg);
+ border: 1px solid var(--border1);
+ border-radius: 5px;
+}
+
+.option {
+ display: inline-block;
+ color: var(--code-fg);
+ background: var(--code-bg);
+ border: 1px solid var(--border1);
+ border-radius: 5px;
+ margin-right: 10px;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-bottom: 5px;
+}
+
+label {
+ font-family: monospace;
+ font-size: 14px;
+ position: relative;
+ margin-bottom: 10px;
+}
+input[type="text"] {
+ margin: 0;
+ font-family: monospace;
+ font-variant-ligatures: none;
+ background: var(--code-bg);
+ border: 1px solid var(--border2);
+ color: var(--fg);
+ font-size: 16px;
+ appearance: none;
+ box-shadow: none;
+ border-radius: 3px;
+}
+input[type="checkbox"] {
+ margin: 0;
+ position: relative;
+ top: 2px;
+}
+select {
+ font-family: monospace;
+ background: var(--code-bg);
+ border: 1px solid var(--border2);
+ border-radius: 3px;
+ color: var(--fg);
+ font-size: 15px;
+ margin-left: 2px;
+}
+
+button {
+}
+
+hr {
+ position: absolute;
+ width: 78%;
+ left: 12.2%;
+}
+
+ </style>
+
+</head>
+
+<body>
+ <div style="width: 85%; margin: 0 auto; padding-left: 10%;">
+ <noscript>JavaScript is required</noscript>
+ <h2>MIPS Assembler</h2>
+ <p>Input MIPS assembly code below. Labels, .org and simple .word directives are also recognized. Comments start with ';' and extend to the end of the line.</p>
+ <textarea wrap='off' rows="15" id="assembler-input" placeholder="MIPS assembly input" spellcheck="false"></textarea>
+ <textarea wrap='off' rows="15" readonly id="assembler-output" placeholder="Assembler output"></textarea>
+ <br>
+ <div class="option">
+ <input type='checkbox' id="assembler-hideaddr">
+ <label for='assembler-hideaddr'>Hide Addresses</label>
+ </div>
+ <div class="option">
+ <input type='checkbox' id="assembler-hidemnemonic">
+ <label for="assembler-hidemnemonic">Hide Mnemonics</label>
+ </div>
+ <div class="option">
+ <input type='checkbox' id="assembler-hidenote">
+ <label for="assembler-hidenote">Hide Annotations</label>
+ </div>
+ <div class="option">
+ <select id="assembler-endian">
+ <option value="big" selected>Big</option>
+ <option value="little">Little</option>
+ </select>
+ <label for="assembler-endian">Endianness</label>
+ </div>
+ <button class="option" onclick="assembleAndPresent();">ASSEMBLE</button>
+ <br><br><hr><br>
+
+ <h2>MIPS Disassembler</h2>
+ <p>Input MIPS machine code in hexadecimal below.</p>
+ <div class="option">
+ <label>.org (Start Address)</label>
+ <input type='text' id="disassembler-org" size="8" placeholder="00000000" spellcheck="false">
+ </div>
+ <br>
+ <textarea wrap='off' rows="15" id="disassembler-input" placeholder="MIPS machine code in hex" spellcheck="false"></textarea>
+ <textarea wrap='off' rows="15" readonly id="disassembler-output" placeholder="Disassembler output"></textarea>
+ <br>
+ <div class="option">
+ <input type='checkbox' id="disassembler-hideaddr">
+ <label for='disassembler-hideaddr'>Hide Addresses</label>
+ </div>
+ <div class="option">
+ <input type='checkbox' id="disassembler-hidehex">
+ <label for="disassembler-hidehex">Hide Hex</label>
+ </div>
+ <div class="option">
+ <input type='checkbox' id="disassembler-hidenote">
+ <label for="disassembler-hidenote">Hide Annotations</label>
+ </div>
+ <div class="option">
+ <select id="disassembler-endian">
+ <option value="big" selected>Big</option>
+ <option value="little">Little</option>
+ </select>
+ <label for="disassembler-endian">Endianness</label>
+ </div>
+
+ <button class="option" onclick="disassembleAndPresent();">DISASSEMBLE</button>
+
+ <br><br><hr><br>
+ <a href="mips.js">source</a> <br>
+ <a href="/">go back</a>
+
+ </div>
+
+ <script type="text/javascript" src='./mips.js'></script>
+</body>
+
+</html>
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;
+ };
+};
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..72a7b1c
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,106 @@
+:root { /* default theme (light) */
+ --fg: #333;
+ --bg: #f4f0f0;
+ --subtitle: #777;
+ --pre-fg: #111;
+ --pre-bg: #eaeaea;
+ --border1: #ccc;
+ --code-bg: #e8e8e8;
+ --border2: #999;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --fg: #fff;
+ --bg: #262222;
+ --subtitle: #888;
+ --pre-fg: #eaeaea;
+ --pre-bg: #262327;
+ --border1: #444;
+ --code-bg: #222;
+ --border2: #555;
+ }
+}
+
+body {
+ margin: auto;
+ max-width: 820px;
+ line-height: 1.6;
+ font-family: Serif;
+ padding: 0 10px;
+ font-size: 18px;
+ color: var(--fg);
+ background: var(--bg);
+}
+
+p {
+ text-align: justify;
+}
+
+h1.title {
+ line-height: 1.2;
+ font-size: 40px;
+}
+
+h1 {
+ line-height: 1.2;
+ font-size: 26px;
+}
+
+h2 {
+ line-height: 1.2;
+ font-size: 21px;
+}
+
+p.subtitle {
+ font-size: 16px;
+ color: var(--subtitle);
+}
+
+pre {
+ padding: 8px 8px 8px 8px;
+ border-radius: 3px;
+ border: 1px solid var(--border1);
+ font-size: 14px;
+ overflow-x: auto;
+ background: var(--pre-bg);
+ color: var(--pre-fg);
+}
+
+pre > code {
+ background: var(--pre-bg);
+ font-size: 14px;
+ border: none;
+ padding: 0px;
+}
+
+code {
+ background: var(--code-bg);
+ border: 1px solid var(--border1);
+ font-size: 14px;
+ border-radius: 3px;
+ padding: 2px 2px;
+}
+
+a {
+ color: #d53c54;/*#e77850;*/
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+ text-decoration-style: dotted;
+}
+
+td,th {
+ vertical-align:top;
+ padding: 5px 5px 5px;
+ text-align:center;
+ border: solid 1px var(--border2);
+}
+
+table {
+ border: solid 1px var(--border1);
+ background: var(--code-bg);
+ font-size: 15px;
+}