Web re-rewrite

This commit is contained in:
2023-03-08 11:10:33 -06:00
parent 7d31c55391
commit 17277bb354
78 changed files with 9571 additions and 9496 deletions
+88
View File
@@ -0,0 +1,88 @@
"use strict";
// Dedicated audio output thread
class AudioThread extends AudioWorkletProcessor {
///////////////////////// Initialization Methods //////////////////////////
constructor() {
super();
// Configure instance fields
this.buffers = []; // Input sample buffer queue
this.offset = 0; // Offset into oldest buffer
// Wait for initializer message from parent thread
this.port.onmessage = m=>this.init(m.data);
}
async init(main) {
// Configure message ports
this.core = this.port;
this.core.onmessage = m=>this.onCore(m.data);
this.main = main;
this.main.onmessage = m=>this.onMain(m.data);
// Notify main thread
this.port.postMessage(0);
}
///////////////////////////// Public Methods //////////////////////////////
// Produce output samples (called by the user agent)
process(inputs, outputs, parameters) {
let output = outputs[0];
let length = output [0].length;
let empty = null;
// Process all samples
for (let x = 0; x < length;) {
// No bufferfed samples are available
if (this.buffers.length == 0) {
for (; x < length; x++)
output[0] = output[1] = 0;
break;
}
// Transfer samples from the oldest buffer
let y, buffer = this.buffers[0];
for (y = this.offset; x < length && y < buffer.length; x++, y+=2) {
output[0][x] = buffer[y ];
output[1][x] = buffer[y + 1];
}
// Advance to the next buffer
if ((this.offset = y) == buffer.length) {
if (empty == null)
empty = [];
empty.push(this.buffers.shift());
this.offset = 0;
}
}
// Return emptied sample buffers to the core thread
if (empty != null)
this.core.postMessage(empty, empty.map(e=>e.buffer));
return true;
}
///////////////////////////// Message Methods /////////////////////////////
// Message received from core thread
onCore(msg) {
}
// Message received from main thread
onMain(msg) {
}
}
registerProcessor("AudioThread", AudioThread);
+293
View File
@@ -0,0 +1,293 @@
// Interface between application and WebAssembly worker thread
class Core {
///////////////////////// Initialization Methods //////////////////////////
constructor() {
// Configure instance fields
this.promises = [];
}
async init(coreUrl, wasmUrl, audioUrl) {
// Open audio output stream
this.audio = new AudioContext({
latencyHint: "interactive",
sampleRate : 41700
});
await this.audio.suspend();
// Launch the audio thread
await this.audio.audioWorklet.addModule(
Core.url(audioUrl, "AudioThread.js", /***/"./AudioThread.js"));
let node = new AudioWorkletNode(this.audio, "AudioThread", {
numberOfInputs : 0,
numberOfOutputs : 1,
outputChannelCount: [2]
});
node.connect(this.audio.destination);
// Attach a second MessagePort to the audio thread
let channel = new MessageChannel();
this.audio.port = channel.port1;
await new Promise(resolve=>{
node.port.onmessage = resolve;
node.port.postMessage(channel.port2, [channel.port2]);
});
this.audio.port.onmessage = m=>this.onAudio(m.data);
// Launch the core thread
this.core = new Worker(
Core.url(wasmUrl, "CoreThread.js", /***/"./CoreThread.js"));
await new Promise(resolve=>{
this.core.onmessage = resolve;
this.core.postMessage({
audio : node.port,
wasmUrl: Core.url(wasmUrl, "core.wasm", /***/"./core.wasm")
}, [node.port]);
});
this.core.onmessage = m=>this.onCore(m.data);
return this;
}
///////////////////////////// Static Methods //////////////////////////////
// Select a URL in the same path as the current script
static url(arg, name, bundled) {
// The input argument was provided
if (arg)
return arg;
// Running from a bundle distribution
if (bundled.startsWith("blob:") || bundled.startsWith("data:"))
return bundled;
// Compute the URL for the given filename
let url = new URL(import.meta.url).pathname;
return url.substring(0, url.lastIndexOf("/") + 1) + name;
}
///////////////////////////// Event Handlers //////////////////////////////
// Message received from audio thread
onAudio(msg) {
}
// Message received from core thread
onCore(msg) {
// Process subscriptions
if (msg.subscriptions && this.onsubscription instanceof Function) {
for (let sub of msg.subscriptions) {
let key = sub.subscription;
delete sub.subscription;
this.onsubscription(key, sub, this);
}
delete msg.subscriptions;
}
// The main thread is waiting on a reply
if (msg.isReply) {
delete msg.isReply;
// For "create", produce sim objects
if (msg.isCreate) {
delete msg.isCreate;
msg.sims = msg.sims.map(s=>({ pointer: s }));
}
// Notify the caller
this.promises.shift()(msg);
}
}
///////////////////////////// Public Methods //////////////////////////////
// Create and initialize simulations
create(count, options) {
return this.message({
command: "create",
count : count
}, [], options);
}
// Delete a simulation
delete(sim, options) {
return this.message({
command: "delete",
sim : sim.pointer
}, [], options);
}
// Retrieve the value of all CPU registers
getAllRegisters(sim, options) {
return this.message({
command: "getAllRegisters",
sim : sim.pointer
}, [], options);
}
// Retrieve the value of PC
getProgramCounter(sim, options) {
return this.message({
command: "getProgramCounter",
sim : sim.pointer
}, [], options);
}
// Retrieve the value of a system register
getSystemRegister(sim, id, options) {
return this.message({
command: "getSystemRegister",
id : id,
sim : sim.pointer
}, [], options);
}
// Read multiple bytes from memory
read(sim, address, length, options) {
return this.message({
command: "read",
address: address,
length : length,
sim : sim.pointer
}, [], options);
}
// Refresh subscriptions
refresh(subscriptions = null, options) {
return this.message({
command : "refresh",
subscriptions: subscriptions
}, [], options);
}
// Simulate a hardware reset
reset(sim, options) {
return this.message({
command: "reset",
sim : sim.pointer
}, [], options);
}
// Execute until the next current instruction
runNext(sims, options) {
return this.message({
command: "runNext",
sims : Array.isArray(sims) ?
sims.map(s=>s.pointer) : [ sims.pointer ]
}, [], options);
}
// Specify a value for the program counter
setProgramCounter(sim, value, options) {
return this.message({
command: "setProgramCounter",
sim : sim.pointer,
value : value
}, [], options);
}
// Specify a value for a program register
setProgramRegister(sim, index, value, options) {
return this.message({
command: "setProgramRegister",
index : index,
sim : sim.pointer,
value : value
}, [], options);
}
// Specify a cartridge ROM buffer
setROM(sim, data, options = {}) {
data = data.slice();
return this.message({
command: "setROM",
data : data,
reset : !("reset" in options) || !!options.reset,
sim : sim.pointer
}, [data.buffer], options);
}
// Specify a value for a system register
setSystemRegister(sim, id, value, options) {
return this.message({
command: "setSystemRegister",
id : id,
sim : sim.pointer,
value : value
}, [], options);
}
// Execute the current instruction
singleStep(sims, options) {
return this.message({
command: "singleStep",
sims : Array.isArray(sims) ?
sims.map(s=>s.pointer) : [ sims.pointer ]
}, [], options);
}
// Cancel a subscription
unsubscribe(subscription, options) {
return this.message({
command : "unsubscribe",
subscription: subscription
}, [], options);
}
// Write multiple bytes to memory
write(sim, address, data, options) {
data = data.slice();
return this.message({
address: address,
command: "write",
data : data,
sim : sim.pointer
}, [data.buffer], options);
}
///////////////////////////// Private Methods /////////////////////////////
// Send a message to the core thread
message(msg, transfers, options = {}) {
// Configure options
if (!(options instanceof Object))
options = { reply: options };
if (!("reply" in options) || options.reply)
msg.reply = true;
if ("refresh" in options)
msg.refresh = options.refresh;
if ("subscription" in options)
msg.subscription = options.subscription;
if ("tag" in options)
msg.tag = options.tag;
// Send the command to the core thread
return msg.reply ?
new Promise(resolve=>{
this.promises.push(resolve);
this.core.postMessage(msg, transfers);
}) :
this.core.postMessage(msg, transfers);
;
}
}
export { Core };
+330
View File
@@ -0,0 +1,330 @@
"use strict";
// Dedicated emulation thread
class CoreThread {
///////////////////////// Initialization Methods //////////////////////////
constructor() {
// Configure instance fields
this.subscriptions = new Map();
// Wait for initializer message from parent thread
onmessage = m=>this.init(m.data.audio, m.data.wasmUrl);
}
async init(audio, wasmUrl) {
// Configure message ports
this.audio = audio;
this.audio.onmessage = m=>this.onAudio (m.data);
this.main = globalThis;
this.main .onmessage = m=>this.onMessage(m.data);
// Load and instantiate the WebAssembly module
this.wasm = (await WebAssembly.instantiateStreaming(
fetch(wasmUrl), {
env: { emscripten_notify_memory_growth: ()=>this.onGrowth() }
})).instance;
this.onGrowth();
this.pointerSize = this.PointerSize();
this.pointerType = this.pointerSize == 8 ? Uint64Array : Uint32Array;
// Notify main thread
this.main.postMessage(0);
}
///////////////////////////// Event Handlers //////////////////////////////
// Message received from audio thread
onAudio(frames) {
// Audio processing was suspended
if (frames == 0) {
return;
}
// Wait for more frames
this.audio.postMessage(0);
}
// Emscripten has grown the linear memory
onGrowth() {
Object.assign(this, this.wasm.exports);
}
// Message received from main thread
onMessage(msg) {
// Subscribe to the command
if (msg.subscription && msg.command != "refresh")
this.subscriptions.set(CoreThread.key(msg.subscription), msg);
// Process the command
let rep = this[msg.command](msg);
// Do not send a reply
if (!msg.reply)
return;
// Configure the reply
if (!rep)
rep = {};
if (msg.reply)
rep.isReply = true;
if ("tag" in msg)
rep.tag = msg.tag;
// Send the reply to the main thread
let transfers = rep.transfers;
if (transfers)
delete rep.transfers;
this.main.postMessage(rep, transfers || []);
// Refresh subscriptions
if (msg.refresh && msg.command != "refresh") {
let subs = {};
if (Array.isArray(msg.refresh))
subs.subscriptions = msg.refresh;
this.refresh(subs);
}
}
//////////////////////////////// Commands /////////////////////////////////
// Create and initialize a new simulation
create(msg) {
let sims = new Array(msg.count);
for (let x = 0; x < msg.count; x++)
sims[x] = this.Create();
return {
isCreate: true,
sims : sims
};
}
// Delete all memory used by a simulation
delete(msg) {
this.Delete(msg.sim);
}
// Retrieve the values of all CPU registers
getAllRegisters(msg) {
let program = new Int32Array (32);
let system = new Uint32Array(32);
for (let x = 0; x < 32; x++) {
program[x] = this.vbGetProgramRegister(msg.sim, x);
system [x] = this.vbGetSystemRegister (msg.sim, x);
}
return {
pc : this.vbGetProgramCounter(msg.sim) >>> 0,
program : program,
system : system,
transfers: [ program.buffer, system.buffer ]
};
}
// Retrieve the value of PC
getProgramCounter(msg) {
return { value: this.vbGetProgramCounter(msg.sim) >>> 0 };
}
// Retrieve the value of a system register
getSystemRegister(msg) {
return { value: this.vbGetSystemRegister(msg.sim, msg.id) >>> 0 };
}
// Read multiple bytes from memory
read(msg) {
let buffer = this.malloc(msg.length);
this.vbReadEx(msg.sim, msg.address, buffer.pointer, msg.length);
let data = buffer.slice();
this.free(buffer);
return {
address : msg.address,
data : data,
transfers: [data.buffer]
};
}
// Process subscriptions
refresh(msg) {
let subscriptions = [];
let transfers = [];
// Select the key set to refresh
let keys = Array.isArray(msg.subscriptions) ?
msg.subscriptions.map(s=>CoreThread.key(s)) :
this.subscriptions.keys()
;
// Process all subscriptions
for (let key of keys) {
// Process the subscription
let sub = this.subscriptions.get(key);
let rep = this[sub.command](sub);
// There is no result
if (!rep)
continue;
// Add the result to the response
rep.subscription = sub.subscription;
if ("tag" in sub)
rep.tag = sub.tag;
subscriptions.push(rep);
// Add the transfers to the response
if (!rep.transfers)
continue;
transfers = transfers.concat(rep.transfers);
delete rep.transfers;
}
// Do not send a reply
if (subscriptions.length == 0 && !msg.reply)
return;
// Send the response to the main thread
this.main.postMessage({
isReply : !!msg.reply,
subscriptions: subscriptions.sort(CoreThread.REFRESH_ORDER)
}, transfers);
}
// Simulate a hardware reset
reset(msg) {
this.vbReset(msg.sim);
}
// Execute until the next current instruction
runNext(msg) {
let sims = this.malloc(msg.sims.length, true);
for (let x = 0; x < msg.sims.length; x++)
sims[x] = msg.sims[x];
this.RunNext(sims.pointer, msg.sims.length);
this.free(sims);
let pcs = new Array(msg.sims.length);
for (let x = 0; x < msg.sims.length; x++)
pcs[x] = this.vbGetProgramCounter(msg.sims[x]) >>> 0;
return { pcs: pcs };
}
// Specify a value for the program counter
setProgramCounter(msg) {
return { value: this.vbSetProgramCounter(msg.sim, msg.value) >>> 0 };
}
// Specify a value for a program register
setProgramRegister(msg) {
return {value:this.vbSetProgramRegister(msg.sim,msg.index,msg.value)};
}
// Specify a cartridge ROM buffer
setROM(msg) {
let prev = this.vbGetROM(msg.sim, 0);
let success = true;
// Specify a new ROM
if (msg.data != null) {
let data = this.malloc(msg.data.length);
for (let x = 0; x < data.length; x++)
data[x] = msg.data[x];
success = !this.vbSetROM(msg.sim, data.pointer, data.length);
}
// Operation was successful
if (success) {
// Delete the previous ROM
this.Free(prev);
// Reset the simulation
if (msg.reset)
this.vbReset(msg.sim);
}
return { success: success };
}
// Specify a value for a system register
setSystemRegister(msg) {
return {value:this.vbSetSystemRegister(msg.sim,msg.id,msg.value)>>>0};
}
// Execute the current instruction
singleStep(msg) {
let sims = this.malloc(msg.sims.length, true);
for (let x = 0; x < msg.sims.length; x++)
sims[x] = msg.sims[x];
this.SingleStep(sims.pointer, msg.sims.length);
this.free(sims);
let pcs = new Array(msg.sims.length);
for (let x = 0; x < msg.sims.length; x++)
pcs[x] = this.vbGetProgramCounter(msg.sims[x]) >>> 0;
return { pcs: pcs };
}
// Delete a subscription
unsubscribe(msg) {
this.subscriptions.delete(CoreThread.key(msg.subscription));
}
// Write multiple bytes to memory
write(msg) {
let data = this.malloc(msg.data.length);
for (let x = 0; x < data.length; x++)
data[x] = msg.data[x];
this.vbWriteEx(msg.sim, msg.address, data.pointer, data.length);
this.free(data);
}
///////////////////////////// Private Methods /////////////////////////////
// Delete a byte array in WebAssembly memory
free(buffer) {
this.Free(buffer.pointer);
}
// Format a subscription key as a string
static key(subscription) {
return subscription.map(k=>k.toString()).join("\n");
}
// Allocate a byte array in WebAssembly memory
malloc(length, pointers = false) {
let size = pointers ? length * this.pointerSize : length;
return this.map(this.Malloc(size), length, pointers);
}
// Map a typed array into WebAssembly memory
map(address, length, pointers = false) {
let ret = new (pointers ? this.pointerType : Uint8Array)
(this.memory.buffer, address, length);
ret.pointer = address;
return ret;
}
// Comparator for subscriptions within the refresh command
static REFRESH_ORDER(a, b) {
a = a.subscription[0];
b = b.subscription[0];
return a < b ? -1 : a > b ? 1 : 0;
}
}
new CoreThread();
+542
View File
@@ -0,0 +1,542 @@
// Machine code to human readable text converter
class Disassembler {
//////////////////////////////// Constants ////////////////////////////////
// Default settings
static DEFAULTS = {
condCL : "L", // Use C/NC or L/NL for conditions
condEZ : "E", // Use E/NE or Z/NZ for conditions
condNames : true, // Use condition names
condUppercase: false, // Condition names uppercase
hexPrefix : "0x", // Hexadecimal prefix
hexSuffix : "", // Hexadecimal suffix
hexUppercase : true, // Hexadecimal uppercase
instUppercase: true, // Mnemonics uppercase
jumpAddress : true, // Jump/branch shows target address
memInside : false, // Use [reg1 + disp] notation
opDestFirst : false, // Destination operand first
proNames : true, // Use program register names
proUppercase : false, // Program register names uppercase
splitBcond : false, // BCOND condition as an operand
splitSetf : true, // SETF condition as an operand
sysNames : true, // Use system register names
sysUppercase : false // System register names uppercase
};
/////////////////////////// Disassembly Lookup ////////////////////////////
// Opcode descriptors
static OPDEFS = [
[ "MOV" , [ "opReg1" , "opReg2" ] ], // 000000
[ "ADD" , [ "opReg1" , "opReg2" ] ],
[ "SUB" , [ "opReg1" , "opReg2" ] ],
[ "CMP" , [ "opReg1" , "opReg2" ] ],
[ "SHL" , [ "opReg1" , "opReg2" ] ],
[ "SHR" , [ "opReg1" , "opReg2" ] ],
[ "JMP" , [ "opReg1Ind" ] ],
[ "SAR" , [ "opReg1" , "opReg2" ] ],
[ "MUL" , [ "opReg1" , "opReg2" ] ],
[ "DIV" , [ "opReg1" , "opReg2" ] ],
[ "MULU" , [ "opReg1" , "opReg2" ] ],
[ "DIVU" , [ "opReg1" , "opReg2" ] ],
[ "OR" , [ "opReg1" , "opReg2" ] ],
[ "AND" , [ "opReg1" , "opReg2" ] ],
[ "XOR" , [ "opReg1" , "opReg2" ] ],
[ "NOT" , [ "opReg1" , "opReg2" ] ],
[ "MOV" , [ "opImm5S", "opReg2" ] ], // 010000
[ "ADD" , [ "opImm5S", "opReg2" ] ],
null, // SETF: special
[ "CMP" , [ "opImm5S", "opReg2" ] ],
[ "SHL" , [ "opImm5U", "opReg2" ] ],
[ "SHR" , [ "opImm5U", "opReg2" ] ],
[ "CLI" , [ ] ],
[ "SAR" , [ "opImm5U", "opReg2" ] ],
[ "TRAP" , [ "opImm5U" ] ],
[ "RETI" , [ ] ],
[ "HALT" , [ ] ],
null, // Invalid
[ "LDSR" , [ "opReg2" , "opSys" ] ],
[ "STSR" , [ "opSys" , "opReg2" ] ],
[ "SEI" , [ ] ],
null, // Bit string: special
null, // BCOND: special // 100000
null, // BCOND: special
null, // BCOND: special
null, // BCOND: special
null, // BCOND: special
null, // BCOND: special
null, // BCOND: special
null, // BCOND: special
[ "MOVEA", [ "opImm16U" , "opReg1", "opReg2" ] ],
[ "ADDI" , [ "opImm16S" , "opReg1", "opReg2" ] ],
[ "JR" , [ "opDisp26" ] ],
[ "JAL" , [ "opDisp26" ] ],
[ "ORI" , [ "opImm16U" , "opReg1", "opReg2" ] ],
[ "ANDI" , [ "opImm16U" , "opReg1", "opReg2" ] ],
[ "XORI" , [ "opImm16U" , "opReg1", "opReg2" ] ],
[ "MOVHI", [ "opImm16U" , "opReg1", "opReg2" ] ],
[ "LD.B" , [ "opReg1Disp", "opReg2" ] ], // 110000
[ "LD.H" , [ "opReg1Disp", "opReg2" ] ],
null, // Invalid
[ "LD.W" , [ "opReg1Disp", "opReg2" ] ],
[ "ST.B" , [ "opReg2" , "opReg1Disp" ] ],
[ "ST.H" , [ "opReg2" , "opReg1Disp" ] ],
null, // Invalid
[ "ST.W" , [ "opReg2" , "opReg1Disp" ] ],
[ "IN.B" , [ "opReg1Disp", "opReg2" ] ],
[ "IN.H" , [ "opReg1Disp", "opReg2" ] ],
[ "CAXI" , [ "opReg1Disp", "opReg2" ] ],
[ "IN.W" , [ "opReg1Disp", "opReg2" ] ],
[ "OUT.B", [ "opReg2" , "opReg1Disp" ] ],
[ "OUT.H", [ "opReg2" , "opReg1Disp" ] ],
null, // Floating-point/Nintendo: special
[ "OUT.W", [ "opReg2" , "opReg1Disp" ] ]
];
// Bit string sub-opcode descriptors
static BITSTRING = [
"SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD",
null , null , null , null ,
"ORBSU" , "ANDBSU" , "XORBSU" , "MOVBSU" ,
"ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU" ,
null , null , null , null ,
null , null , null , null ,
null , null , null , null ,
null , null , null , null
];
// Floating-point/Nintendo sub-opcode descriptors
static FLOATENDO = [
[ "CMPF.S" , [ "opReg1", "opReg2" ] ],
null, // Invalid
[ "CVT.WS" , [ "opReg1", "opReg2" ] ],
[ "CVT.SW" , [ "opReg1", "opReg2" ] ],
[ "ADDF.S" , [ "opReg1", "opReg2" ] ],
[ "SUBF.S" , [ "opReg1", "opReg2" ] ],
[ "MULF.S" , [ "opReg1", "opReg2" ] ],
[ "DIVF.S" , [ "opReg1", "opReg2" ] ],
[ "XB" , [ "opReg2" ] ],
[ "XH" , [ "opReg2" ] ],
[ "REV" , [ "opReg1", "opReg2" ] ],
[ "TRNC.SW", [ "opReg1", "opReg2" ] ],
[ "MPYHW" , [ "opReg1", "opReg2" ] ],
null, null, null,
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null
];
// Condition mnemonics
static CONDITIONS = [
"V" , "C" , "E" , "NH", "N", "T", "LT", "LE",
"NV", "NC", "NE", "H" , "P", "F", "GE", "GT"
];
// Program register names
static PRONAMES = [
"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"
];
// System register names
static SYSNAMES = [
"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 //////////////////////////////
// Determine the bounds of a data buffer to represent all lines of output
static dataBounds(address, line, length) {
let before = 10; // Number of lines before the first line of output
let max = 4; // Maximum number of bytes that can appear on a line
// The reference line is before the preferred earliest line
if (line < -before) {
length = (length - line) * max;
}
// The reference line is before the first line
else if (line < 0) {
address -= (line + before) * max;
length = (length + before) * max;
}
// The reference line is at or after the first line
else {
address -= (line + before) * max;
length = (Math.max(length, line) + before) * max;
}
return {
address: (address & ~1) >>> 0,
length : length
};
}
///////////////////////// Initialization Methods //////////////////////////
constructor() {
Object.assign(this, Disassembler.DEFAULTS);
}
///////////////////////////// Public Methods //////////////////////////////
// Disassemble a region of memory
disassemble(data, dataAddress, refAddress, refLine, length, pc = null) {
let pcOffset = pc === null ? -1 : pc - dataAddress >>> 0;
// Locate the offset of the first line of output in the buffer
let offset = 0;
for (let
addr = dataAddress,
circle = refLine > 0 ? new Array(refLine) : null,
index = 0,
more = [],
remain = null
;;) {
// Determine the size of the current line
if (more.length == 0)
this.more(more, data, offset);
let size = more.shift();
// The current line contains the reference address
if (refAddress - addr >>> 0 < size) {
// The next item in the buffer is the first line of output
if (refLine > 0) {
offset = circle[index];
break;
}
// This line is the first line of output
if (refLine == 0)
break;
// Count more lines for the first line of output
remain = refLine;
}
// Record the offset of the current instruction
if (refLine > 0) {
circle[index] = offset;
index = (index + 1) % circle.length;
}
// Advance to the next line
let sizeToPC = pcOffset - offset >>> 0;
if (offset != pcOffset && sizeToPC < size) {
size = sizeToPC;
more.splice();
}
addr = addr + size >>> 0;
offset += size;
if (remain !== null && ++remain == 0)
break; // The next line is the first line of output
}
// Process all lines of output
let lines = new Array(length);
for (let
addr = dataAddress + offset,
more = [],
x = 0;
x < length; x++
) {
// Determine the size of the current line
if (more.length == 0)
this.more(more, data, offset, pcOffset);
let size = more.shift();
// Add the line to the response
lines[x] = this.format({
rawAddress: addr,
rawBytes : data.slice(offset, offset + size)
});
// Advance to the next line
let sizeToPC = pcOffset - offset >>> 0;
if (offset != pcOffset && sizeToPC < size) {
size = sizeToPC;
more.splice();
}
addr = addr + size >>> 0;
offset += size;
}
return lines;
}
/////////////////////////// Formatting Methods ////////////////////////////
// Format a line as human-readable text
format(line) {
let canReverse = true;
let opcode = line.rawBytes[1] >>> 2;
let opdef;
let code = [
line.rawBytes[1] << 8 | line.rawBytes[0],
line.rawBytes.length == 2 ? null :
line.rawBytes[3] << 8 | line.rawBytes[2]
];
// BCOND
if ((opcode & 0b111000) == 0b100000) {
let cond = code[0] >>> 9 & 15;
opdef =
cond == 13 ? [ "NOP", [ ] ] :
this.splitBcond ? [ "BCOND", [ "opBCond", "opDisp9" ] ] :
[
cond == 5 ? "BR" : "B" + this.condition(cond, true),
[ "opDisp9" ]
]
;
canReverse = false;
}
// Processing by opcode
else switch (opcode) {
// SETF
case 0b010010:
opdef = !this.splitSetf ?
[
"SETF" + Disassembler.CONDITIONS[code[0] & 15],
[ "opReg2" ]
] :
[ "SETF", [ "opCond", "opReg2" ] ]
;
break;
// Bit string
case 0b011111:
opdef = Disassembler.BITSTRING[code[0] & 31];
if (opdef != null)
opdef = [ opdef, [] ];
break;
// Floating-point/Nintendo
case 0b111110:
opdef = Disassembler.FLOATENDO[code[1] >>> 10];
break;
// All others
default: opdef = Disassembler.OPDEFS[opcode];
}
// The opcode is undefined
if (opdef == null)
opdef = [ "---", [] ];
// Format the line's display text
line.address = this.hex(line.rawAddress, 8, false);
line.bytes = new Array(line.rawBytes.length);
line.mnemonic = this.instUppercase ? opdef[0] : opdef[0].toLowerCase();
line.operands = new Array(opdef[1].length);
for (let x = 0; x < line.bytes.length; x++)
line.bytes[x] = this.hex(line.rawBytes[x], 2, false);
for (let x = 0; x < line.operands.length; x++)
line.operands[x] = this[opdef[1][x]](line, code);
if (this.opDestFirst && canReverse)
line.operands.reverse();
return line;
}
// Format a condition operand in a BCOND instruction
opBCond(line, code) {
return this.condition(code[0] >>> 9 & 15);
}
// Format a condition operand in a SETF instruction
opCond(line, code) {
return this.condition(code[0] & 15);
}
// Format a 9-bit displacement operand
opDisp9(line, code) {
let disp = code[0] << 23 >> 23;
return this.jump(line.rawAddress, disp);
}
// Format a 26-bit displacement operand
opDisp26(line, code) {
let disp = (code[0] << 16 | code[1]) << 6 >> 6;
return this.jump(line.rawAddress, disp);
}
// Format a 5-bit signed immediate operand
opImm5S(line, code) {
return (code[0] & 31) << 27 >> 27;
}
// Format a 5-bit unsigned immediate operand
opImm5U(line, code) {
return code[0] & 31;
}
// Format a 16-bit signed immediate operand
opImm16S(line, code) {
let ret = code[1] << 16 >> 16;
return (
ret < -256 ? "-" + this.hex(-ret) :
ret > 256 ? this.hex( ret) :
ret
);
}
// Format a 16-bit unsigned immediate operand
opImm16U(line, code) {
return this.hex(code[1], 4);
}
// Format a Reg1 operand
opReg1(line, code) {
return this.programRegister(code[0] & 31);
}
// Format a disp[reg1] operand
opReg1Disp(line, code) {
let disp = code[1] << 16 >> 16;
let reg1 = this.programRegister(code[0] & 31);
// Do not print the displacement
if (disp == 0)
return "[" + reg1 + "]";
// Format the displacement amount
disp =
disp < -256 ? "-" + this.hex(-disp) :
disp > 256 ? this.hex( disp) :
disp.toString()
;
// [reg1 + disp] notation
if (this.memInside) {
return "[" + reg1 + (disp.startsWith("-") ?
" - " + disp.substring(1) :
" + " + disp
) + "]";
}
// disp[reg1] notation
return disp + "[" + reg1 + "]";
}
// Format a [Reg1] operand
opReg1Ind(line, code) {
return "[" + this.programRegister(code[0] & 31) + "]";
}
// Format a Reg2 operand
opReg2(line, code) {
return this.programRegister(code[0] >> 5 & 31);
}
// Format a system register operand
opSys(line, code) {
return this.systemRegister(code[0] & 31);
}
///////////////////////////// Private Methods /////////////////////////////
// Select the mnemonic for a condition
condition(index, forceUppercase = false) {
if (!this.condNames)
return index.toString();
let ret =
index == 1 ? this.condCL :
index == 2 ? this.condEZ :
index == 9 ? "N" + this.condCL :
index == 10 ? "N" + this.condEZ :
Disassembler.CONDITIONS[index]
;
if (!forceUppercase && !this.condUppercase)
ret = ret.toLowerCase();
return ret;
}
// Format a number as a hexadecimal string
hex(value, digits = null, decorated = true) {
value = value.toString(16);
if (this.hexUppercase)
value = value.toUpperCase();
if (digits != null)
value = value.padStart(digits, "0");
if (decorated) {
value = this.hexPrefix + value + this.hexSuffix;
if (this.hexPrefix == "" && "0123456789".indexOf(value[0]) == -1)
value = "0" + value;
}
return value;
}
// Format a jump or branch destination
jump(address, disp) {
return (
this.jumpAddress ?
this.hex(address + disp >>> 0, 8, false) :
disp < -256 ? "-" + this.hex(-disp) :
disp > 256 ? "+" + this.hex( disp) :
disp.toString()
);
}
// Determine the number of bytes in the next line(s) of disassembly
more(more, data, offset) {
// Error checking
if (offset + 1 >= data.length)
throw new Error("Disassembly error: Unexpected EoF");
// Determine the instruction's size from its opcode
let opcode = data[offset + 1] >>> 2;
more.push(
opcode < 0b101000 || // 16-bit instruction
opcode == 0b110010 || // Illegal opcode
opcode == 0b110110 // Illegal opcode
? 2 : 4);
}
// Format a program register
programRegister(index) {
let ret = this.proNames ? Disassembler.PRONAMES[index] : "r" + index;
if (this.proUppercase)
ret = ret.toUpperCase();
return ret;
}
// Format a system register
systemRegister(index) {
let ret = this.sysNames ?
Disassembler.SYSNAMES[index] : index.toString();
if (!this.sysUppercase && this.sysNames)
ret = ret.toLowerCase();
return ret;
}
}
export { Disassembler };
+78
View File
@@ -0,0 +1,78 @@
#undef VBAPI
#include <stdlib.h>
#include <emscripten/emscripten.h>
#include <vb.h>
/////////////////////////////// Module Commands ///////////////////////////////
// Create and initialize a new simulation
EMSCRIPTEN_KEEPALIVE VB* Create() {
VB *vb = malloc(sizeof (VB));
vbInit(vb);
return vb;
}
// Delete all memory used by a simulation
EMSCRIPTEN_KEEPALIVE void Delete(VB *vb) {
free(vb->cart.ram);
free(vb->cart.rom);
free(vb);
}
// Proxy for free()
EMSCRIPTEN_KEEPALIVE void Free(void *ptr) {
free(ptr);
}
// Proxy for malloc()
EMSCRIPTEN_KEEPALIVE void* Malloc(int size) {
return malloc(size);
}
// Size in bytes of a pointer
EMSCRIPTEN_KEEPALIVE int PointerSize() {
return sizeof (void *);
}
////////////////////////////// Debugger Commands //////////////////////////////
// Execute until the following instruction
uint32_t RunNextAddress;
static int RunNextFetch(VB *vb, int fetch, VBAccess *access) {
return access->address == RunNextAddress;
}
static int RunNextExecute(VB *vb, VBInstruction *inst) {
RunNextAddress = inst->address + inst->size;
vbSetOnExecute(vb, NULL);
vbSetOnFetch(vb, &RunNextFetch);
return 0;
}
EMSCRIPTEN_KEEPALIVE void RunNext(VB **vbs, int count) {
uint32_t clocks = 20000000; // 1s
vbSetOnExecute(vbs[0], &RunNextExecute);
vbEmulateEx (vbs, count, &clocks);
vbSetOnExecute(vbs[0], NULL);
vbSetOnFetch (vbs[0], NULL);
}
// Execute the current instruction
static int SingleStepBreak;
static int SingleStepFetch(VB *vb, int fetch, VBAccess *access) {
if (fetch != 0)
return 0;
if (SingleStepBreak == 1)
return 1;
SingleStepBreak = 1;
return 0;
}
EMSCRIPTEN_KEEPALIVE void SingleStep(VB **vbs, int count) {
uint32_t clocks = 20000000; // 1s
SingleStepBreak = vbs[0]->cpu.stage == 0 ? 0 : 1;
vbSetOnFetch(vbs[0], &SingleStepFetch);
vbEmulateEx (vbs, count, &clocks);
vbSetOnFetch(vbs[0], NULL);
}