"use strict"; /* Register types */ const VB_PROGRAM = 0; const VB_SYSTEM = 1; const VB_OTHER = 2; /* System registers */ const VB_ADTRE = 25; const VB_CHCW = 24; const VB_ECR = 4; const VB_EIPC = 0; const VB_EIPSW = 1; const VB_FEPC = 2; const VB_FEPSW = 3; const VB_PIR = 6; const VB_PSW = 5; const VB_TKCW = 7; /* Other registers */ const VB_PC = 0; // 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.vbGetRegister(msg.sim, 0, x); system [x] = this.vbGetRegister(msg.sim, 1, x); } return { pc : this.vbGetRegister(msg.sim, 2, 0) >>> 0, program : program, system : system, transfers: [ program.buffer, system.buffer ] }; } // Retrieve the value of a register getRegister(msg) { let value = this.vbGetRegister(msg.sim, msg.type, msg.id); if (msg.type != VB_PROGRAM) value >>>= 0; return { value: value }; } // 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; } // Send the response to the main thread if (subscriptions.length == 0) return; this.main.postMessage({ subscriptions: subscriptions.sort(CoreThread.REFRESH_ORDER) }, transfers); } // Simulate a hardware reset reset(msg) { this.vbReset(msg.sim); } // Execute until the next current instruction runToNext(msg) { let sims = this.malloc(msg.sims.length, true); for (let x = 0; x < msg.sims.length; x++) sims[x] = msg.sims[x]; this.RunToNext(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.vbGetRegister(msg.sims[x], 2, 0) >>> 0; return { pcs: pcs }; } // Specify a value for a register setRegister(msg) { let value = this.vbSetRegister(msg.sim, msg.type, msg.id, msg.value); if (msg.type != VB_PROGRAM) value >>>= 0; return { value: 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 }; } // 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.vbGetRegister(msg.sims[x], 2, 0) >>> 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();