959 lines
29 KiB
JavaScript
959 lines
29 KiB
JavaScript
import { Util } from /**/"../app/Util.js";
|
|
|
|
|
|
|
|
// Opcode definition
|
|
class Opdef {
|
|
constructor(format, mnemonic, signExtend) {
|
|
this.format = format;
|
|
this.mnemonic = mnemonic;
|
|
this.signExtend = !!signExtend;
|
|
}
|
|
}
|
|
|
|
// Top-level opcode definition lookup table by opcode
|
|
let OPDEFS = [
|
|
new Opdef(1, "MOV" ), new Opdef(1, "ADD" ), new Opdef(1, "SUB" ),
|
|
new Opdef(1, "CMP" ), new Opdef(1, "SHL" ), new Opdef(1, "SHR" ),
|
|
new Opdef(1, "JMP" ), new Opdef(1, "SAR" ), new Opdef(1, "MUL" ),
|
|
new Opdef(1, "DIV" ), new Opdef(1, "MULU" ), new Opdef(1, "DIVU" ),
|
|
new Opdef(1, "OR" ), new Opdef(1, "AND" ), new Opdef(1, "XOR" ),
|
|
new Opdef(1, "NOT" ), new Opdef(2, "MOV" ,1), new Opdef(2, "ADD",1),
|
|
new Opdef(2, "SETF" ), new Opdef(2, "CMP" ,1), new Opdef(2, "SHL" ),
|
|
new Opdef(2, "SHR" ), new Opdef(2, "CLI" ), new Opdef(2, "SAR" ),
|
|
new Opdef(2, "TRAP" ), new Opdef(2, "RETI" ), new Opdef(2, "HALT" ),
|
|
new Opdef(0, null ), new Opdef(2, "LDSR" ), new Opdef(2, "STSR" ),
|
|
new Opdef(2, "SEI" ), new Opdef(2, null ), new Opdef(3, "Bcond"),
|
|
new Opdef(3, "Bcond"), new Opdef(3, "Bcond" ), new Opdef(3, "Bcond"),
|
|
new Opdef(3, "Bcond"), new Opdef(3, "Bcond" ), new Opdef(3, "Bcond"),
|
|
new Opdef(3, "Bcond"), new Opdef(5,"MOVEA",1), new Opdef(5,"ADDI",1),
|
|
new Opdef(4, "JR" ), new Opdef(4, "JAL" ), new Opdef(5, "ORI" ),
|
|
new Opdef(5, "ANDI" ), new Opdef(5, "XORI" ), new Opdef(5, "MOVHI"),
|
|
new Opdef(6, "LD.B" ), new Opdef(6, "LD.H" ), new Opdef(0, null ),
|
|
new Opdef(6, "LD.W" ), new Opdef(6, "ST.B" ), new Opdef(6, "ST.H" ),
|
|
new Opdef(0, null ), new Opdef(6, "ST.W" ), new Opdef(6, "IN.B" ),
|
|
new Opdef(6, "IN.H" ), new Opdef(6, "CAXI" ), new Opdef(6, "IN.W" ),
|
|
new Opdef(6, "OUT.B"), new Opdef(6, "OUT.H" ), new Opdef(7, null ),
|
|
new Opdef(6, "OUT.W")
|
|
];
|
|
|
|
// Bit string mnemonic lookup table by sub-opcode
|
|
let BITSTRINGS = [
|
|
"SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD",
|
|
null , null , null , null ,
|
|
"ORBSU" , "ANDBSU" , "XORBSU" , "MOVBSU" ,
|
|
"ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU"
|
|
];
|
|
|
|
// Floating-point/Nintendo mnemonic lookup table by sub-opcode
|
|
let FLOATENDOS = [
|
|
"CMPF.S", null , "CVT.WS", "CVT.SW" ,
|
|
"ADDF.S", "SUBF.S", "MULF.S", "DIVF.S" ,
|
|
"XB" , "XH" , "REV" , "TRNC.SW",
|
|
"MPYHW"
|
|
];
|
|
|
|
// Program register names
|
|
let PROREGS = { 2: "hp", 3: "sp", 4: "gp", 5: "tp", 31: "lp" };
|
|
|
|
// System register names
|
|
let SYSREGS = [
|
|
"EIPC", "EIPSW", "FEPC", "FEPSW",
|
|
"ECR" , "PSW" , "PIR" , "TKCW" ,
|
|
null , null , null , null ,
|
|
null , null , null , null ,
|
|
null , null , null , null ,
|
|
null , null , null , null ,
|
|
"CHCW", "ADTRE", null , null ,
|
|
null , null , null , null
|
|
];
|
|
|
|
// Condition mnemonics
|
|
let CONDS = [
|
|
"V" , ["C" , "L" ], ["E" , "Z" ], "NH",
|
|
"N" , "T" , "LT" , "LE",
|
|
"NV", ["NC", "NL"], ["NE", "NZ"], "H" ,
|
|
"P" , "F" , "GE" , "GT"
|
|
];
|
|
|
|
// Output setting keys
|
|
const SETTINGS = [
|
|
"bcondMerged", "branchAddress", "condCase", "condCL", "condEZ",
|
|
"condNames", "hexCaps", "hexDollar", "hexSuffix", "imm5OtherHex",
|
|
"imm5ShiftHex", "imm5TrapHex", "imm16AddiLargeHex", "imm16AddiSmallHex",
|
|
"imm16MoveHex", "imm16OtherHex", "jmpBrackets", "memoryLargeHex",
|
|
"memorySmallHex", "memoryInside", "mnemonicCaps", "operandReverse",
|
|
"proregCaps", "proregNames", "setfMerged", "sysregCaps", "sysregNames"
|
|
];
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Line //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// One line of output
|
|
class Line {
|
|
|
|
///////////////////////// Initialization Methods //////////////////////////
|
|
|
|
constructor(parent, first) {
|
|
|
|
// Configure instance fields
|
|
this.first = first;
|
|
this.parent = parent;
|
|
|
|
// Configure labels
|
|
this.lblAddress = this.label("tk-address" , first);
|
|
this.lblBytes = this.label("tk-bytes" , first);
|
|
this.lblMnemonic = this.label("tk-mnemonic", first);
|
|
this.lblOperands = this.label("tk-operands", false);
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////// Package Methods /////////////////////////////
|
|
|
|
// Update the elements' display
|
|
refresh(row, isPC) {
|
|
|
|
// The row is not available
|
|
if (!row) {
|
|
this.lblAddress .innerText = "--------";
|
|
this.lblBytes .innerText = "";
|
|
this.lblMnemonic.innerText = "---";
|
|
this.lblOperands.innerText = "";
|
|
}
|
|
|
|
// Update labels with the disassembled row's contents
|
|
else {
|
|
this.lblAddress .innerText = row.address;
|
|
this.lblBytes .innerText = row.bytes;
|
|
this.lblMnemonic.innerText = row.mnemonic;
|
|
this.lblOperands.innerText = row.operands;
|
|
}
|
|
|
|
// Update style according to selection
|
|
let method = row && isPC ? "add" : "remove";
|
|
this.lblAddress .classList[method]("tk-selected");
|
|
this.lblBytes .classList[method]("tk-selected");
|
|
this.lblMnemonic.classList[method]("tk-selected");
|
|
this.lblOperands.classList[method]("tk-selected");
|
|
}
|
|
|
|
// Specify whether the elements on this line are visible
|
|
setVisible(visible) {
|
|
|
|
// Column elements
|
|
let columns = [
|
|
this.lblAddress,
|
|
this.lblBytes,
|
|
this.lblMnemonic,
|
|
this.lblOperands
|
|
];
|
|
|
|
// Column elements on the first row
|
|
if (this.first) {
|
|
columns[0] = columns[0].parentNode; // Address
|
|
columns[1] = columns[1].parentNode; // Bytes
|
|
columns[2] = columns[2].parentNode; // Mnemonic
|
|
}
|
|
|
|
// Column visibility
|
|
visible = [
|
|
visible, // Address
|
|
visible && this.parent.hasBytes, // Bytes
|
|
visible, // Mnemonic
|
|
visible // Operands
|
|
];
|
|
|
|
// Configure elements
|
|
for (let x = 0; x < 4; x++)
|
|
columns[x].style.display = visible[x] ? "block" : "none";
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////// Private Methods /////////////////////////////
|
|
|
|
// Create a display label
|
|
label(className, first) {
|
|
|
|
// Create the label element
|
|
let label = document.createElement("div");
|
|
label.className = "tk " + className;
|
|
|
|
// The label is part of the first row of output
|
|
let element = label;
|
|
if (first) {
|
|
|
|
// Create a container element
|
|
element = document.createElement("div");
|
|
element.append(label);
|
|
element.max = 0;
|
|
|
|
// Ensure the container can always fit the column contents
|
|
Toolkit.addResizeListener(element, ()=>{
|
|
let width = Math.ceil(label.getBoundingClientRect().width);
|
|
if (width <= element.max)
|
|
return;
|
|
element.max = width;
|
|
element.style.minWidth = width + "px";
|
|
});
|
|
|
|
}
|
|
|
|
// Configure elements
|
|
this.parent.view.append(element);
|
|
return label;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Disassembler //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Text disassembler for NVC
|
|
class Disassembler extends Toolkit.ScrollPane {
|
|
|
|
///////////////////////// Initialization Methods //////////////////////////
|
|
|
|
constructor(debug) {
|
|
super(debug.app, {
|
|
className : "tk tk-scrollpane tk-disassembler",
|
|
horizontal: Toolkit.ScrollPane.AS_NEEDED,
|
|
focusable : true,
|
|
tabStop : true,
|
|
tagName : "div",
|
|
vertical : Toolkit.ScrollPane.NEVER
|
|
});
|
|
|
|
// Configure instance fields
|
|
this.address = Util.u32(0xFFFFFFF0);
|
|
this.app = debug.app;
|
|
this.columns = [ 0, 0, 0, 0 ];
|
|
this.data = [];
|
|
this.debug = debug;
|
|
this.hasBytes = true;
|
|
this.isSubscribed = false;
|
|
this.lines = null;
|
|
this.pc = this.address;
|
|
this.pending = [];
|
|
this.rows = [];
|
|
this.scroll = 0;
|
|
this.sim = debug.sim;
|
|
|
|
// Default output settings
|
|
this.setConfig({
|
|
bcondMerged : true,
|
|
branchAddress : true,
|
|
condCase : false,
|
|
condCL : 1,
|
|
condEZ : 1,
|
|
condNames : true,
|
|
hexCaps : true,
|
|
hexDollar : false,
|
|
hexSuffix : false,
|
|
imm5OtherHex : false,
|
|
imm5ShiftHex : false,
|
|
imm5TrapHex : false,
|
|
imm16AddiLargeHex: true,
|
|
imm16AddiSmallHex: false,
|
|
imm16MoveHex : true,
|
|
imm16OtherHex : true,
|
|
jmpBrackets : true,
|
|
memoryLargeHex : true,
|
|
memorySmallHex : false,
|
|
memoryInside : false,
|
|
mnemonicCaps : true,
|
|
operandReverse : false,
|
|
proregCaps : false,
|
|
proregNames : true,
|
|
setfMerged : false,
|
|
sysregCaps : true,
|
|
sysregNames : true
|
|
});
|
|
|
|
// Configure viewport
|
|
this.viewport.classList.add("tk-mono");
|
|
|
|
// Configure view
|
|
let view = document.createElement("div");
|
|
view.className = "tk tk-view";
|
|
Object.assign(view.style, {
|
|
display : "grid",
|
|
gridTemplateColumns: "repeat(3, max-content) auto"
|
|
});
|
|
this.setView(view);
|
|
|
|
// Font-measuring element
|
|
this.metrics = new Toolkit.Component(this.app, {
|
|
className: "tk tk-metrics tk-mono",
|
|
tagName : "div",
|
|
style : {
|
|
position : "absolute",
|
|
visibility: "hidden"
|
|
}
|
|
});
|
|
this.metrics.element.innerText = "X";
|
|
this.append(this.metrics.element);
|
|
|
|
// First row always exists
|
|
this.lines = [ new Line(this, true) ];
|
|
|
|
// Configure event handlers
|
|
Toolkit.addResizeListener(this.viewport, e=>this.onResize(e));
|
|
this.addEventListener("keydown", e=>this.onKeyDown (e));
|
|
this.addEventListener("wheel" , e=>this.onMouseWheel(e));
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////// Event Handlers //////////////////////////////
|
|
|
|
// Key press
|
|
onKeyDown(e) {
|
|
let tall = this.tall(false);
|
|
|
|
|
|
// Ctrl key is pressed
|
|
if (e.ctrlKey) switch (e.key) {
|
|
|
|
// Toggle bytes column
|
|
case "b": case "B":
|
|
this.showBytes(!this.hasBytes);
|
|
break;
|
|
|
|
// Fit columns
|
|
case "f": case "F":
|
|
this.fitColumns();
|
|
break;
|
|
|
|
// Goto
|
|
case "g": case "G":
|
|
this.promptGoto();
|
|
break;
|
|
|
|
default: return;
|
|
}
|
|
|
|
// Ctrl key is not pressed
|
|
else switch (e.key) {
|
|
|
|
// Navigation
|
|
case "ArrowDown" : this.fetch(+1 , true); break;
|
|
case "ArrowUp" : this.fetch(-1 , true); break;
|
|
case "PageDown" : this.fetch(+tall, true); break;
|
|
case "PageUp" : this.fetch(-tall, true); break;
|
|
|
|
// View control
|
|
case "ArrowLeft" : this.horizontal.setValue(
|
|
this.horizontal.value - this.horizontal.increment); break;
|
|
case "ArrowRight": this.horizontal.setValue(
|
|
this.horizontal.value + this.horizontal.increment); break;
|
|
|
|
// Single step
|
|
case "F10":
|
|
this.debug.runNext();
|
|
break;
|
|
|
|
// Single step
|
|
case "F11":
|
|
this.debug.singleStep();
|
|
break;
|
|
|
|
default: return;
|
|
}
|
|
|
|
// Configure event
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}
|
|
|
|
// Mouse wheel
|
|
onMouseWheel(e) {
|
|
|
|
// User agent scaling action
|
|
if (e.ctrlKey)
|
|
return;
|
|
|
|
// No rotation has occurred
|
|
let offset = Math.sign(e.deltaY) * 3;
|
|
if (offset == 0)
|
|
return;
|
|
|
|
// Update the display address
|
|
this.fetch(offset, true);
|
|
}
|
|
|
|
// Viewport resized
|
|
onResize(e) {
|
|
let fetch = false;
|
|
let tall = this.tall(true);
|
|
|
|
// Add additional lines to the output
|
|
for (let x = 0; x < tall; x++) {
|
|
if (x >= this.lines.length) {
|
|
fetch = true;
|
|
this.lines.push(new Line(this));
|
|
}
|
|
this.lines[x].setVisible(true);
|
|
}
|
|
|
|
// Remove extra lines from the output
|
|
for (let x = tall; x < this.lines.length; x++)
|
|
this.lines[x].setVisible(false);
|
|
|
|
// Configure horizontal scroll bar
|
|
if (this.metrics)
|
|
this.horizontal.setIncrement(this.metrics.getBounds().width);
|
|
|
|
// Update the display
|
|
if (fetch)
|
|
this.fetch(0, true);
|
|
else this.refresh();
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////// Public Methods //////////////////////////////
|
|
|
|
// Produce disassembly text
|
|
disassemble(rows) {
|
|
|
|
// Produce a deep copy of the input list
|
|
let copy = new Array(rows.length);
|
|
for (let x = 0; x < rows.length; x++) {
|
|
copy[x] = {};
|
|
Object.assign(copy[x], rows[x]);
|
|
}
|
|
rows = copy;
|
|
|
|
// Process all rows
|
|
for (let row of rows) {
|
|
row.operands = [];
|
|
|
|
// Read instruction bits from the bus
|
|
let bits0 = row.bytes[1] << 8 | row.bytes[0];
|
|
let bits1;
|
|
if (row.bytes.length == 4)
|
|
bits1 = row.bytes[3] << 8 | row.bytes[2];
|
|
|
|
// Working variables
|
|
let opcode = bits0 >> 10;
|
|
let opdef = OPDEFS[opcode];
|
|
|
|
// Sub-opcode mnemonics
|
|
if (row.opcode == 0b011111)
|
|
row.mnemonic = BITSTRINGS[bits0 & 31] || "---";
|
|
else if (row.opcode == 0b111110)
|
|
row.mnemonic = FLOATENDOS[bits1 >> 10 & 63] || "---";
|
|
else row.mnemonic = opdef.mnemonic;
|
|
|
|
// Processing by format
|
|
switch (opdef.format) {
|
|
case 1: this.formatI (row, bits0 ); break;
|
|
case 3: this.formatIII(row, bits0 ); break;
|
|
case 4: this.formatIV (row, bits0, bits1); break;
|
|
case 6: this.formatVI (row, bits0, bits1); break;
|
|
case 7: this.formatVII(row, bits0 ); break;
|
|
case 2:
|
|
this.formatII(row, bits0, opdef.signExtend); break;
|
|
case 5:
|
|
this.formatV (row, bits0, bits1, opdef.signExtend);
|
|
}
|
|
|
|
// Format bytes
|
|
let text = [];
|
|
for (let x = 0; x < row.bytes.length; x++)
|
|
text.push(row.bytes[x].toString(16).padStart(2, "0"));
|
|
row.bytes = text.join(" ");
|
|
|
|
// Post-processing
|
|
row.address = row.address.toString(16).padStart(8, "0");
|
|
if (this.hexCaps) {
|
|
row.address = row.address.toUpperCase();
|
|
row.bytes = row.bytes .toUpperCase();
|
|
}
|
|
if (!this.mnemonicCaps)
|
|
row.mnemonic = row.mnemonic.toLowerCase();
|
|
if (this.operandReverse)
|
|
row.operands.reverse();
|
|
row.operands = row.operands.join(", ");
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
// Retrieve all output settings in an object
|
|
getConfig() {
|
|
let ret = {};
|
|
for (let key of SETTINGS)
|
|
ret[key] = this[key];
|
|
return ret;
|
|
}
|
|
|
|
// Update with disassembly state from the core
|
|
refresh(data = 0) {
|
|
let bias;
|
|
|
|
// Scrolling prefresh
|
|
if (typeof data == "number")
|
|
bias = 16 + data;
|
|
|
|
// Received data from the core thread
|
|
else {
|
|
this.data = data.rows;
|
|
this.pc = data.pc;
|
|
if (this.data.length == 0)
|
|
return;
|
|
this.address = this.data[0].address;
|
|
this.rows = this.disassemble(this.data);
|
|
bias = 16 +
|
|
(data.scroll === null ? 0 : this.scroll - data.scroll);
|
|
}
|
|
|
|
// Update elements
|
|
let count = Math.min(this.tall(true), this.data.length);
|
|
for (let y = 0; y < count; y++) {
|
|
let index = bias + y;
|
|
let line = this.data[index];
|
|
let row = this.rows[index];
|
|
this.lines[y].refresh(row, line && line.address == this.pc);
|
|
}
|
|
|
|
// Refesh scroll pane
|
|
this.update();
|
|
}
|
|
|
|
// Bring an address into view
|
|
seek(address, force) {
|
|
|
|
// Check if the address is already in the view
|
|
if (!force) {
|
|
let bias = 16;
|
|
let tall = this.tall(false);
|
|
let count = Math.min(tall, this.data.length);
|
|
|
|
// The address is currently visible in the output
|
|
for (let y = 0; y < count; y++) {
|
|
let row = this.data[bias + y];
|
|
if (!row || Util.u32(address - row.address) >= row.size)
|
|
continue;
|
|
|
|
// The address is on this row
|
|
this.refresh();
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
// Place the address at a particular position in the view
|
|
this.address = address;
|
|
this.fetch(null);
|
|
}
|
|
|
|
// Update output settings
|
|
setConfig(config) {
|
|
|
|
// Update settings
|
|
for (let key of SETTINGS)
|
|
if (key in config)
|
|
this[key] = config[key];
|
|
|
|
// Regenerate output
|
|
this.refresh({
|
|
pc : this.pc,
|
|
rows : this.data,
|
|
scroll: null
|
|
});
|
|
}
|
|
|
|
// Subscribe to or unsubscribe from core updates
|
|
setSubscribed(subscribed) {
|
|
subscribed = !!subscribed;
|
|
|
|
// Nothing to change
|
|
if (subscribed == this.isSubscribed)
|
|
return;
|
|
|
|
// Configure instance fields
|
|
this.isSubscribed = subscribed;
|
|
|
|
// Subscribe to core updates
|
|
if (subscribed)
|
|
this.fetch(0);
|
|
|
|
// Unsubscribe from core updates
|
|
else this.sim.unsubscribe("dasm");
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////// Private Methods /////////////////////////////
|
|
|
|
// Select a condition's name
|
|
cond(cond) {
|
|
let ret = CONDS[cond];
|
|
switch (cond) {
|
|
case 1: case 9: return CONDS[cond][this.condCL];
|
|
case 2: case 10: return CONDS[cond][this.condEZ];
|
|
}
|
|
return CONDS[cond];
|
|
}
|
|
|
|
// Retrieve disassembly data from the core
|
|
async fetch(scroll, prefresh) {
|
|
let row;
|
|
|
|
// Scrolling relative to the current view
|
|
if (scroll) {
|
|
if (prefresh)
|
|
this.refresh(scroll);
|
|
this.scroll = Util.s32(this.scroll + scroll);
|
|
row = -scroll;
|
|
}
|
|
|
|
// Jumping to an address directly
|
|
else row = scroll === null ? Math.floor(this.tall(false) / 3) + 16 : 0;
|
|
|
|
// Retrieve data from the core
|
|
this.refresh(
|
|
await this.sim.disassemble(
|
|
this.address,
|
|
row,
|
|
this.tall(true) + 32,
|
|
scroll === null ? null : this.scroll, {
|
|
subscribe: this.isSubscribed && "dasm"
|
|
})
|
|
);
|
|
}
|
|
|
|
// Shrink all columns to their minimum size
|
|
fitColumns() {
|
|
let line = this.lines[0];
|
|
for (let column of [ "lblAddress", "lblBytes", "lblMnemonic" ] ) {
|
|
let element = line[column].parentNode;
|
|
element.max = 0;
|
|
element.style.removeProperty("min-width");
|
|
}
|
|
}
|
|
|
|
// Represent a hexadecimal value
|
|
hex(value, digits) {
|
|
let sign = Util.s32(value) < 0 ? "-" : "";
|
|
let ret = Math.abs(Util.u32(value)).toString(16).padStart(digits,"0");
|
|
if (this.hexCaps)
|
|
ret = ret.toUpperCase();
|
|
if (this.hexSuffix)
|
|
ret = ("abcdefABCDEF".indexOf(ret[0]) == -1 ? "" : "0") +
|
|
ret + "h";
|
|
else ret = (this.hexDollar ? "$" : "0x") + ret;
|
|
return sign + ret;
|
|
}
|
|
|
|
// Prompt the user to specify a new address
|
|
promptGoto() {
|
|
|
|
// Receive input from the user
|
|
let address = prompt(this.app.translate("common.gotoPrompt"));
|
|
if (address == null)
|
|
return;
|
|
|
|
// Process the input as an address in hexadecimal
|
|
address = parseInt(address, 16);
|
|
if (isNaN(address))
|
|
return;
|
|
|
|
// Move the selection and refresh the display
|
|
this.seek(Util.u32(address));
|
|
}
|
|
|
|
// Select a program register name
|
|
proreg(index) {
|
|
let ret = this.proregNames && PROREGS[index] || "r" + index;
|
|
return this.proregCaps ? ret.toUpperCase() : ret;
|
|
}
|
|
|
|
// Specify whether or not to show the bytes column
|
|
showBytes(show) {
|
|
let tall = this.tall(true);
|
|
|
|
// Configure instance fields
|
|
this.hasBytes = show;
|
|
|
|
// Configure elements
|
|
this.view.style.gridTemplateColumns =
|
|
"repeat(" + (show ? 3 : 2) + ", max-content) auto";
|
|
for (let x = 0; x < tall; x++)
|
|
this.lines[x].setVisible(true);
|
|
|
|
// Measure scroll pane
|
|
this.update();
|
|
}
|
|
|
|
// Measure how many rows of output are visible
|
|
tall(partial) {
|
|
let lineHeight = !this.metrics ? 0 :
|
|
Math.ceil(this.metrics.getBounds().height);
|
|
return lineHeight <= 0 ? 1 : Math.max(1, Math[partial?"ceil":"floor"](
|
|
this.getBounds().height / lineHeight));
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////// Decoding Methods /////////////////////////////
|
|
|
|
// Disassemble a Format I instruction
|
|
formatI(row, bits0) {
|
|
let reg1 = this.proreg(bits0 & 31);
|
|
|
|
// JMP
|
|
if (row.mnemonic == "JMP") {
|
|
if (this.jmpBrackets)
|
|
reg1 = "[" + reg1 + "]";
|
|
row.operands.push(reg1);
|
|
}
|
|
|
|
// Other instructions
|
|
else {
|
|
let reg2 = this.proreg(bits0 >> 5 & 31);
|
|
row.operands.push(reg1, reg2);
|
|
}
|
|
|
|
}
|
|
|
|
// Disassemble a Format II instruction
|
|
formatII(row, bits0, signExtend) {
|
|
|
|
// Bit-string instructions are zero-operand
|
|
if (bits0 >> 10 == 0b011111)
|
|
return;
|
|
|
|
// Processing by mnemonic
|
|
switch (row.mnemonic) {
|
|
|
|
// Zero-operand
|
|
case "---" : // Fallthrough
|
|
case "CLI" : // Fallthrough
|
|
case "HALT": // Fallthrough
|
|
case "RETI": // Fallthrough
|
|
case "SEI" : return;
|
|
|
|
// Distinct notation
|
|
case "LDSR": return this.ldstsr(row, bits0, true );
|
|
case "SETF": return this.setf (row, bits0 );
|
|
case "STSR": return this.ldstsr(row, bits0, false);
|
|
}
|
|
|
|
// Retrieve immediate operand
|
|
let imm = bits0 & 31;
|
|
if (signExtend)
|
|
imm = Util.signExtend(bits0, 5);
|
|
|
|
// TRAP instruction is one-operand
|
|
if (row.mnemonic == "TRAP") {
|
|
row.operands.push(this.trapHex ?
|
|
this.hex(imm, 1) : imm.toString());
|
|
return;
|
|
}
|
|
|
|
// Processing by mnemonic
|
|
let hex = this.imm5OtherHex;
|
|
switch (row.mnemonic) {
|
|
case "SAR": // Fallthrough
|
|
case "SHL": // Fallthrough
|
|
case "SHR": hex = this.imm5ShiftHex;
|
|
}
|
|
imm = hex ? this.hex(imm, 1) : imm.toString();
|
|
|
|
// Two-operand instruction
|
|
let reg2 = this.proreg(bits0 >> 5 & 31);
|
|
row.operands.push(imm, reg2);
|
|
}
|
|
|
|
// Disassemble a Format III instruction
|
|
formatIII(row, bits0) {
|
|
let cond = this.cond(bits0 >> 9 & 15);
|
|
let disp = Util.signExtend(bits0 & 0x1FF, 9);
|
|
|
|
// Condition merged with mnemonic
|
|
if (this.bcondMerged) {
|
|
switch (cond) {
|
|
case "F": row.mnemonic = "NOP"; return;
|
|
case "T": row.mnemonic = "BR" ; break;
|
|
default : row.mnemonic = "B" + cond;
|
|
}
|
|
}
|
|
|
|
// Condition as operand
|
|
else {
|
|
if (!this.condCaps)
|
|
cond = cond.toLowerCase();
|
|
row.operands.push(cond);
|
|
}
|
|
|
|
// Operand as destination address
|
|
if (this.branchAddress) {
|
|
disp = Util.u32(row.address + disp & 0xFFFFFFFE)
|
|
.toString(16).padStart(8, "0");
|
|
if (this.hexCaps)
|
|
disp = disp.toUpperCase();
|
|
row.operands.push(disp);
|
|
}
|
|
|
|
// Operand as displacement
|
|
else {
|
|
let sign = disp < 0 ? "-" : disp > 0 ? "+" : "";
|
|
let rel = this.hex(Math.abs(disp), 1);
|
|
row.operands.push(sign + rel);
|
|
}
|
|
|
|
}
|
|
|
|
// Disassemble a Format IV instruction
|
|
formatIV(row, bits0, bits1) {
|
|
let disp = Util.signExtend(bits0 << 16 | bits1, 26);
|
|
|
|
// Operand as destination address
|
|
if (this.branchAddress) {
|
|
disp = Util.u32(row.address + disp & 0xFFFFFFFE)
|
|
.toString(16).padStart(8, "0");
|
|
if (this.hexCaps)
|
|
disp = disp.toUpperCase();
|
|
row.operands.push(disp);
|
|
}
|
|
|
|
// Operand as displacement
|
|
else {
|
|
let sign = disp < 0 ? "-" : disp > 0 ? "+" : "";
|
|
let rel = this.hex(Math.abs(disp), 1);
|
|
row.operands.push(sign + rel);
|
|
}
|
|
|
|
}
|
|
|
|
// Disassemble a Format V instruction
|
|
formatV(row, bits0, bits1, signExtend) {
|
|
let imm = signExtend ? Util.signExtend(bits1) : bits1;
|
|
let reg1 = this.proreg(bits0 & 31);
|
|
let reg2 = this.proreg(bits0 >> 5 & 31);
|
|
|
|
if (
|
|
row.mnemonic == "ADDI" ?
|
|
Math.abs(imm) <= 256 ?
|
|
this.imm16AddiSmallHex :
|
|
this.imm16AddiLargeHex
|
|
: row.mnemonic == "MOVEA" || row.mnemonic == "MOVHI" ?
|
|
this.imm16MoveHex
|
|
:
|
|
this.imm16OtherHex
|
|
) imm = this.hex(imm, 4);
|
|
|
|
row.operands.push(imm, reg1, reg2);
|
|
}
|
|
|
|
// Disassemble a Format VI instruction
|
|
formatVI(row, bits0, bits1) {
|
|
let disp = Util.signExtend(bits1);
|
|
let reg1 = this.proreg(bits0 & 31);
|
|
let reg2 = this.proreg(bits0 >> 5 & 31);
|
|
let sign =
|
|
disp < 0 ? "-" :
|
|
disp == 0 || !this.memoryInside ? "" :
|
|
"+";
|
|
|
|
// Displacement is hexadecimal
|
|
disp = Math.abs(disp);
|
|
if (disp == 0)
|
|
disp = ""
|
|
else if (disp <= 256 ? this.memorySmallHex : this.memoryLargeHex)
|
|
disp = this.hex(disp, 1);
|
|
|
|
// Format the displacement figure according to its presentation
|
|
disp = this.memoryInside ?
|
|
sign == "" ? "" : " " + sign + " " + disp :
|
|
sign + disp
|
|
;
|
|
|
|
// Apply operands
|
|
row.operands.push(this.memoryInside ?
|
|
"[" + reg1 + disp + "]" :
|
|
disp + "[" + reg1 + "]",
|
|
reg2);
|
|
|
|
// Swap operands for output and store instructions
|
|
switch (row.mnemonic) {
|
|
case "OUT.B": case "OUT.H": case "OUT.W":
|
|
case "ST.B" : case "ST.H" : case "ST.W" :
|
|
row.operands.reverse();
|
|
}
|
|
|
|
}
|
|
|
|
// Disassemble a Format VII instruction
|
|
formatVII(row, bits0) {
|
|
let reg1 = this.proreg(bits0 & 31);
|
|
let reg2 = this.proreg(bits0 >> 5 & 31);
|
|
|
|
// Invalid sub-opcode is zero-operand
|
|
if (row.mnemonic == "---")
|
|
return;
|
|
|
|
// Processing by mnemonic
|
|
switch (row.mnemonic) {
|
|
case "XB": // Fallthrough
|
|
case "XH": break;
|
|
default : row.operands.push(reg1);
|
|
}
|
|
|
|
row.operands.push(reg2);
|
|
}
|
|
|
|
// Format an LDSR or STSR instruction
|
|
ldstsr(row, bits0, reverse) {
|
|
|
|
// System register
|
|
let sysreg = bits0 & 31;
|
|
sysreg = this.sysregNames && SYSREGS[sysreg] || sysreg.toString();
|
|
if (!this.sysregCaps)
|
|
sysreg = sysreg.toLowerCase();
|
|
|
|
// Program register
|
|
let reg2 = this.proreg(bits0 >> 5 & 31);
|
|
|
|
// Operands
|
|
row.operands.push(sysreg, reg2);
|
|
if (reverse)
|
|
row.operands.reverse();
|
|
}
|
|
|
|
// Format a SETF instruction
|
|
setf(row, bits0) {
|
|
let cond = this.cond (bits0 & 15);
|
|
let reg2 = this.proreg(bits0 >> 5 & 31);
|
|
|
|
// Condition merged with mnemonic
|
|
if (!this.bcondMerged) {
|
|
row.mnemonic += cond;
|
|
}
|
|
|
|
// Condition as operand
|
|
else {
|
|
if (!this.condCaps)
|
|
cond = cond.toLowerCase();
|
|
row.operands.push(cond);
|
|
}
|
|
|
|
row.operands.push(reg2);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export { Disassembler };
|