331 lines
9.1 KiB
JavaScript
331 lines
9.1 KiB
JavaScript
|
"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();
|