378 lines
12 KiB
JavaScript
378 lines
12 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
|
||
|
|
||
|
// Un-sign a 32-bit integer
|
||
|
// Emscripten is sign-extending uint32_t and Firefox can't import in Workers
|
||
|
let u32 = (()=>{
|
||
|
let U32 = new Uint32Array(1);
|
||
|
return x=>{ U32[0] = x; return U32[0]; };
|
||
|
})();
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
// CoreWorker //
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Thread manager for Core commands
|
||
|
new class CoreWorker {
|
||
|
|
||
|
///////////////////////// Initialization Methods //////////////////////////
|
||
|
|
||
|
// Stub constructor
|
||
|
constructor() {
|
||
|
onmessage = async e=>{
|
||
|
await this.init(e.data.wasm);
|
||
|
onmessage = e=>this.onCommand(e.data, false);
|
||
|
onmessage(e);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Substitute constructor
|
||
|
async init(wasm) {
|
||
|
|
||
|
// Load the WebAssembly module
|
||
|
let imports = {
|
||
|
env: { emscripten_notify_memory_growth: ()=>this.onMemory() }
|
||
|
};
|
||
|
this.wasm = await (typeof wasm == "string" ?
|
||
|
WebAssembly.instantiateStreaming(fetch(wasm), imports) :
|
||
|
WebAssembly.instantiate ( wasm , imports)
|
||
|
);
|
||
|
|
||
|
// Configure instance fields
|
||
|
this.api = this.wasm.instance.exports;
|
||
|
this.frameData = null;
|
||
|
this.isRunning = false;
|
||
|
this.memory = this.api.memory.buffer;
|
||
|
this.ptrSize = this.api.PointerSize();
|
||
|
this.ptrType = this.ptrSize == 4 ? Uint32Array : Uint64Array;
|
||
|
this.subscriptions = {};
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Event Handlers //////////////////////////////
|
||
|
|
||
|
// Message from audio thread
|
||
|
onAudio(frames) {
|
||
|
}
|
||
|
|
||
|
// Message from main thread
|
||
|
onCommand(data) {
|
||
|
|
||
|
// Subscribe to the command
|
||
|
if (data.subscribe) {
|
||
|
let sub = data.sim || 0;
|
||
|
sub = this.subscriptions[sub] || (this.subscriptions[sub] = {});
|
||
|
sub = sub[data.subscribe] = {};
|
||
|
Object.assign(sub, data);
|
||
|
delete sub.promised;
|
||
|
delete sub.run;
|
||
|
delete sub.subscribe;
|
||
|
}
|
||
|
|
||
|
// Execute the command
|
||
|
if (data.run)
|
||
|
this[data.command](data);
|
||
|
|
||
|
// Process all subscriptions to refresh any debugging interfaces
|
||
|
if (data.refresh)
|
||
|
this.doSubscriptions(data.sim ? [ data.sim ] : data.sims);
|
||
|
|
||
|
// Reply to the main thread
|
||
|
if (data.respond) {
|
||
|
postMessage({
|
||
|
response: data.response
|
||
|
}, data.transfers);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Memory growth
|
||
|
onMemory() {
|
||
|
this.memory = this.api.memory.buffer;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//////////////////////////////// Commands /////////////////////////////////
|
||
|
|
||
|
// Associate two simulations as peers, or remove an association
|
||
|
connect(data) {
|
||
|
this.api.vbConnect(data.sims[0], data.sims[1]);
|
||
|
}
|
||
|
|
||
|
// Allocate and initialize a new simulation
|
||
|
create(data) {
|
||
|
let ptr = this.api.Create(data.sims);
|
||
|
data.response = new this.ptrType(this.memory, ptr, data.sims).slice();
|
||
|
data.transfers = [ data.response.buffer ];
|
||
|
this.api.Free(ptr);
|
||
|
}
|
||
|
|
||
|
// Delete a simulation
|
||
|
destroy(data) {
|
||
|
this.api.Destroy(data.sim);
|
||
|
}
|
||
|
|
||
|
// Locate instructions for disassembly
|
||
|
disassemble(data) {
|
||
|
let decode; // Address of next row
|
||
|
let index; // Index in list of next row
|
||
|
let rows = new Array(data.rows);
|
||
|
let pc = u32(this.api.vbGetProgramCounter(data.sim));
|
||
|
let row; // Located output row
|
||
|
|
||
|
// The target address is before or on the first row of output
|
||
|
if (data.row <= 0) {
|
||
|
decode = u32(data.target - 4 * Math.max(0, data.row + 10));
|
||
|
|
||
|
// Locate the target row
|
||
|
for (;;) {
|
||
|
row = this.dasmRow(data.sim, decode, pc);
|
||
|
if (u32(data.target - decode) < row.size)
|
||
|
break;
|
||
|
decode = u32(decode + row.size);
|
||
|
}
|
||
|
|
||
|
// Locate the first row of output
|
||
|
for (index = data.row; index < 0; index++) {
|
||
|
decode = u32(decode + row.size);
|
||
|
row = this.dasmRow(data.sim, decode, pc);
|
||
|
}
|
||
|
|
||
|
// Prepare to process remaining rows
|
||
|
decode = u32(decode + row.size);
|
||
|
rows[0] = row;
|
||
|
index = 1;
|
||
|
}
|
||
|
|
||
|
// The target address is after the first row of output
|
||
|
else {
|
||
|
let circle = new Array(data.row + 1);
|
||
|
let count = Math.min(data.row + 1, data.rows);
|
||
|
let src = 0;
|
||
|
decode = u32(data.target - 4 * (data.row + 10));
|
||
|
|
||
|
// Locate the target row
|
||
|
for (;;) {
|
||
|
row = circle[src] = this.dasmRow(data.sim, decode, pc);
|
||
|
decode = u32(decode + row.size);
|
||
|
if (u32(data.target - row.address) < row.size)
|
||
|
break;
|
||
|
src = (src + 1) % circle.length;
|
||
|
}
|
||
|
|
||
|
// Copy entries from the circular buffer to the output list
|
||
|
for (index = 0; index < count; index++) {
|
||
|
src = (src + 1) % circle.length;
|
||
|
rows[index] = circle[src];
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Locate any remaining rows
|
||
|
for (; index < data.rows; index++) {
|
||
|
let row = rows[index] = this.dasmRow(data.sim, decode, pc);
|
||
|
decode = u32(decode + row.size);
|
||
|
}
|
||
|
|
||
|
// Respond to main thread
|
||
|
data.response = {
|
||
|
pc : pc,
|
||
|
rows : rows,
|
||
|
scroll: data.scroll
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Retrieve all CPU program registers
|
||
|
getProgramRegisters(data) {
|
||
|
let ret = data.response = new Uint32Array(32);
|
||
|
for (let x = 0; x < 32; x++)
|
||
|
ret[x] = this.api.vbGetProgramRegister(data.sim, x);
|
||
|
data.transfers = [ ret.buffer ];
|
||
|
}
|
||
|
|
||
|
// Retrieve the value of a system register
|
||
|
getSystemRegister(data) {
|
||
|
data.response = u32(this.api.vbGetSystemRegister(data.sim, data.id));
|
||
|
}
|
||
|
|
||
|
// Retrieve all CPU system registers (including PC)
|
||
|
getSystemRegisters(data) {
|
||
|
data.response = {
|
||
|
adtre: u32(this.api.vbGetSystemRegister(data.sim, 25)),
|
||
|
chcw : u32(this.api.vbGetSystemRegister(data.sim, 24)),
|
||
|
ecr : u32(this.api.vbGetSystemRegister(data.sim, 4)),
|
||
|
eipc : u32(this.api.vbGetSystemRegister(data.sim, 0)),
|
||
|
eipsw: u32(this.api.vbGetSystemRegister(data.sim, 1)),
|
||
|
fepc : u32(this.api.vbGetSystemRegister(data.sim, 2)),
|
||
|
fepsw: u32(this.api.vbGetSystemRegister(data.sim, 3)),
|
||
|
pc : u32(this.api.vbGetProgramCounter(data.sim )),
|
||
|
pir : u32(this.api.vbGetSystemRegister(data.sim, 6)),
|
||
|
psw : u32(this.api.vbGetSystemRegister(data.sim, 5)),
|
||
|
tkcw : u32(this.api.vbGetSystemRegister(data.sim, 7)),
|
||
|
[29] : u32(this.api.vbGetSystemRegister(data.sim, 29)),
|
||
|
[30] : u32(this.api.vbGetSystemRegister(data.sim, 30)),
|
||
|
[31] : u32(this.api.vbGetSystemRegister(data.sim, 31))
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Read bytes from the simulation
|
||
|
read(data) {
|
||
|
let ptr = this.api.Malloc(data.length);
|
||
|
this.api.ReadBuffer(data.sim, ptr, data.address, data.length);
|
||
|
let buffer = new Uint8Array(this.memory, ptr, data.length).slice();
|
||
|
this.api.Free(ptr);
|
||
|
data.response = {
|
||
|
address: data.address,
|
||
|
bytes : buffer
|
||
|
};
|
||
|
data.transfers = [ buffer.buffer ];
|
||
|
}
|
||
|
|
||
|
// Attempt to execute until the following instruction
|
||
|
runNext(data) {
|
||
|
this.api.RunNext(data.sims[0], data.sims[1]);
|
||
|
let pc = [ u32(this.api.vbGetProgramCounter(data.sims[0])) ];
|
||
|
if (data.sims[1])
|
||
|
pc.push(u32(this.api.vbGetProgramCounter(data.sims[1])));
|
||
|
data.response = {
|
||
|
pc: pc
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Specify a new value for PC
|
||
|
setProgramCounter(data) {
|
||
|
data.response =
|
||
|
u32(this.api.vbSetProgramCounter(data.sim, data.value));
|
||
|
}
|
||
|
|
||
|
// Specify a new value for a program register
|
||
|
setProgramRegister(data) {
|
||
|
data.response = this.api.vbSetProgramRegister
|
||
|
(data.sim, data.id, data.value);
|
||
|
}
|
||
|
|
||
|
// Specify a ROM buffer
|
||
|
setROM(data) {
|
||
|
let ptr = this.api.Malloc(data.rom.length);
|
||
|
let buffer = new Uint8Array(this.memory, ptr, data.rom.length);
|
||
|
for (let x = 0; x < data.rom.length; x++)
|
||
|
buffer[x] = data.rom[x];
|
||
|
data.response = !!this.api.SetROM(data.sim, ptr, data.rom.length);
|
||
|
}
|
||
|
|
||
|
// Specify a new value for a system register
|
||
|
setSystemRegister(data) {
|
||
|
data.response = u32(this.api.vbSetSystemRegister
|
||
|
(data.sim, data.id, data.value));
|
||
|
}
|
||
|
|
||
|
// Execute one instruction
|
||
|
singleStep(data) {
|
||
|
this.api.SingleStep(data.sims[0], data.sims[1]);
|
||
|
let pc = [ u32(this.api.vbGetProgramCounter(data.sims[0])) ];
|
||
|
if (data.sims[1])
|
||
|
pc.push(u32(this.api.vbGetProgramCounter(data.sims[1])));
|
||
|
data.response = {
|
||
|
pc: pc
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Unsubscribe from frame data
|
||
|
unsubscribe(data) {
|
||
|
let sim = data.sim || 0;
|
||
|
if (sim in this.subscriptions) {
|
||
|
let subs = this.subscriptions[sim];
|
||
|
delete subs[data.key];
|
||
|
if (Object.keys(subs).length == 0)
|
||
|
delete this.subscriptions[sim];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Write bytes to the simulation
|
||
|
write(data) {
|
||
|
let ptr = this.api.Malloc(data.bytes.length);
|
||
|
let buffer = new Uint8Array(this.memory, ptr, data.bytes.length);
|
||
|
for (let x = 0; x < data.bytes.length; x++)
|
||
|
buffer[x] = data.bytes[x];
|
||
|
this.api.WriteBuffer(data.sim, ptr, data.address, data.bytes.length);
|
||
|
this.api.Free(ptr);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Private Methods /////////////////////////////
|
||
|
|
||
|
// Retrieve basic information for a row of disassembly
|
||
|
dasmRow(sim, address, pc) {
|
||
|
let bits = this.api.vbRead(sim, address, 3 /* VB_U16 */);
|
||
|
let opcode = bits >> 10 & 63;
|
||
|
let size = (
|
||
|
opcode < 0b101000 || // Formats I through III
|
||
|
opcode == 0b110010 || // Illegal
|
||
|
opcode == 0b110110 // Illegal
|
||
|
) ? 2 : 4;
|
||
|
|
||
|
// Establish row information
|
||
|
let row = {
|
||
|
address: address,
|
||
|
bytes : [ bits & 0xFF, bits >> 8 ],
|
||
|
size : u32(address + 2) == pc ? 2 : size
|
||
|
};
|
||
|
|
||
|
// Read additional bytes
|
||
|
if (size == 4) {
|
||
|
bits = this.api.vbRead(sim, address + 2, 3 /* VB_U16 */);
|
||
|
row.bytes.push(bits & 0xFF, bits >> 8);
|
||
|
}
|
||
|
|
||
|
return row;
|
||
|
}
|
||
|
|
||
|
// Process subscriptions and send a message to the main thread
|
||
|
doSubscriptions(sims) {
|
||
|
let message = { subscriptions: {} };
|
||
|
let transfers = [];
|
||
|
|
||
|
// Process all simulations
|
||
|
for (let sim of sims) {
|
||
|
|
||
|
// There are no subscriptions for this sim
|
||
|
if (!(sim in this.subscriptions))
|
||
|
continue;
|
||
|
|
||
|
// Working variables
|
||
|
let subs = message.subscriptions[sim] = {};
|
||
|
|
||
|
// Process all subscriptions
|
||
|
for (let sub of Object.entries(this.subscriptions[sim])) {
|
||
|
|
||
|
// Run the command
|
||
|
this[sub[1].command](sub[1]);
|
||
|
|
||
|
// Add the response to the message
|
||
|
if (sub[1].response) {
|
||
|
subs[sub[0]] = sub[1].response;
|
||
|
delete sub[1].response;
|
||
|
}
|
||
|
|
||
|
// Add the transferable objects to the message
|
||
|
if (sub[1].transfers) {
|
||
|
transfers.push(... sub[1].transfers);
|
||
|
delete sub[1].transfers;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Send the message to the main thread
|
||
|
if (Object.keys(message).length != 0)
|
||
|
postMessage(message, transfers);
|
||
|
}
|
||
|
|
||
|
}();
|