pvbemu/app/core/CoreWorker.js

379 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
//size : Math.min(u32(pc - address), 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);
}
}();