"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); } }();