diff --git a/modules/pcjs/lib/bus.js b/modules/pcjs/lib/bus.js index 892b2011c..d900b048d 100644 --- a/modules/pcjs/lib/bus.js +++ b/modules/pcjs/lib/bus.js @@ -61,9 +61,10 @@ if (NODE) { * addMemory(). If the component needs something more than simple read/write storage, * it must provide a controller with getMemoryBuffer() and getMemoryAccess() methods. * - * By contrast, all port (I/O) operations are defined by external handlers; they register - * with us, and we manage those registrations, as well as support for I/O breakpoints, - * but unlike memory accesses, we're not involved with port data accesses. + * All port (I/O) operations are defined by external handlers; they register with us, + * and we manage those registrations and provide support for I/O breakpoints, but the + * only default I/O behavior we provide is ignoring writes to any unregistered output + * ports and returning 0xff from any unregistered input ports. * * @constructor * @extends Component @@ -1266,7 +1267,7 @@ Bus.prototype.getSymbol = function(addr, fNearest) }; /** - * saveMemory() + * saveMemory(fAll) * * The only memory blocks we save are those marked as dirty, but most likely all of RAM will have been marked dirty, * and even if our dirty-memory flags were as smart as our dirty-sector flags (ie, were set only when a write changed @@ -1291,17 +1292,17 @@ Bus.prototype.getSymbol = function(addr, fNearest) * helper methods compress() and decompress() to create and expand the compressed data arrays. * * @this {Bus} + * @param {boolean} [fAll] (true to save all non-ROM memory blocks, regardless of their dirty flags) * @return {Array} a */ -Bus.prototype.saveMemory = function() +Bus.prototype.saveMemory = function(fAll) { var i = 0; var a = []; /* * A quick-and-dirty work-around for 32-bit bus machines, to ensure that all blocks in the 2nd Mb are - * mapped in before we save. We do this by forcing A20 on, and then turning it back off again before we - * leave. + * mapped in before we save. We do this by forcing A20 on, and then turning it off again before we leave. */ var fA20 = this.getA20(); if (!fA20) this.setA20(true); @@ -1313,7 +1314,7 @@ Bus.prototype.saveMemory = function() * the memory blocks (eg, video memory), and while cleanMemory() will clear a dirty block's fDirty flag, * it also sets the dirty block's fDirtyEver flag, which is left set for the lifetime of the machine. */ - if (block.fDirty || block.fDirtyEver) { + if (fAll && block.type != Memory.TYPE.ROM || block.fDirty || block.fDirtyEver) { a[i++] = iBlock; a[i++] = State.compress(block.save()); } @@ -1336,7 +1337,7 @@ Bus.prototype.saveMemory = function() * it was using when it's restored. And since the CPU is guaranteed to be the last * component to be restored, all those blocks (and their attributes) should be in place now. * - * See saveMemory() for a description of how the memory block contents are saved. + * See saveMemory() for more information on how the memory block contents are saved. * * @this {Bus} * @param {Array} a @@ -1466,12 +1467,11 @@ Bus.prototype.checkPortInputNotify = function(port, size, addrLIP) var dataPort = maskPort; /* - * TODO: We need to decide what to do about 8-bit I/O to a 16-bit port - * (ditto for 16-bit I/O to a 32-bit port). We probably should pass the - * size through to the aNotify[0] handler, and let it decide what to do, - * but I don't feel like changing all the I/O handlers right now. The - * good news, at least, is that the 8-bit handlers would not have to do - * anything special. This assert will warn us if this is a pressing need. + * TODO: We need to decide what to do about 8-bit I/O to a 16-bit port (ditto for 16-bit I/O + * to a 32-bit port). We probably should pass the size through to the aNotify[0] handler, + * and let it decide what to do, but I don't feel like changing all the I/O handlers right now. + * The good news, at least, is that the 8-bit handlers would not have to do anything special. + * This assert will warn us if this is a pressing need. */ this.assert(size >= sizePort); @@ -1625,12 +1625,11 @@ Bus.prototype.checkPortOutputNotify = function(port, size, data, addrLIP) var dataPort = (data >>>= shift) & maskPort; /* - * TODO: We need to decide what to do about 8-bit I/O to a 16-bit port - * (ditto for 16-bit I/O to a 32-bit port). We probably should pass the - * size through to the aNotify[0] handler, and let it decide what to do, - * but I don't feel like changing all the I/O handlers right now. The - * good news, at least, is that the 8-bit handlers would not have to do - * anything special. This assert will warn us if this is a pressing need. + * TODO: We need to decide what to do about 8-bit I/O to a 16-bit port (ditto for 16-bit I/O + * to a 32-bit port). We probably should pass the size through to the aNotify[0] handler, + * and let it decide what to do, but I don't feel like changing all the I/O handlers right now. + * The good news, at least, is that the 8-bit handlers would not have to do anything special. + * This assert will warn us if this is a pressing need. */ this.assert(size >= sizePort); diff --git a/modules/pcjs/lib/memory.js b/modules/pcjs/lib/memory.js index f88f41f2d..4134001da 100644 --- a/modules/pcjs/lib/memory.js +++ b/modules/pcjs/lib/memory.js @@ -256,6 +256,17 @@ Memory.adjustEndian = function(dw) { Memory.prototype = { constructor: Memory, parent: null, + /** + * init(addr) + * + * Quick reinitializer when reusing a Memory block. + * + * @this {Memory} + * @param {number} addr + */ + init: function(addr) { + this.addr = addr; + }, /** * clone(mem, type) * @@ -501,6 +512,7 @@ Memory.prototype = { * @param {number} offPTE */ setPhysBlock: function(blockPhys, blockPDE, offPDE, blockPTE, offPTE) { + this.blockPhys = blockPhys; this.blockPDE = blockPDE; this.iPDE = offPDE >> 2; // convert offPDE into iPDE (an adw index) this.blockPTE = blockPTE; @@ -516,9 +528,9 @@ Memory.prototype = { this.adw = blockPhys.adw; this.setAccess(Memory.afnPagedLE); } else { - this.blockPhys = blockPhys; this.bitPTEAccessed = blockPhys? Memory.adjustEndian(X86.PTE.ACCESSED) : 0; this.bitPTEDirty = blockPhys? Memory.adjustEndian(X86.PTE.ACCESSED | X86.PTE.DIRTY) : 0; + this.setAccess(Memory.afnPaged); } }, /** @@ -1231,7 +1243,14 @@ Memory.prototype = { this.ab[off] = b; this.blockPDE.adw[this.iPDE] |= X86.PTE.ACCESSED; this.blockPTE.adw[this.iPTE] |= X86.PTE.ACCESSED | X86.PTE.DIRTY; - this.fDirty = true; + /* + * NOTE: Technically, we should be setting the fDirty flag on blockPDE and blockPTE as well, but let's + * consider the two sole uses of fDirty. First, we have cleanMemory(), which is currently used only by + * the Video component, and video memory should never contain page directories or page tables, so no + * worries there. Second, we have saveMemory(), but the CPU now asks that function to save all physical + * memory blocks whenever paging is enabled, so no worries there either. + */ + this.blockPhys.fDirty = true; }, /** * writeShortBE(off, w, addr) @@ -1287,7 +1306,14 @@ Memory.prototype = { } this.blockPDE.adw[this.iPDE] |= X86.PTE.ACCESSED; this.blockPTE.adw[this.iPTE] |= X86.PTE.ACCESSED | X86.PTE.DIRTY; - this.fDirty = true; + /* + * NOTE: Technically, we should be setting the fDirty flag on blockPDE and blockPTE as well, but let's + * consider the two sole uses of fDirty. First, we have cleanMemory(), which is currently used only by + * the Video component, and video memory should never contain page directories or page tables, so no + * worries there. Second, we have saveMemory(), but the CPU now asks that function to save all physical + * memory blocks whenever paging is enabled, so no worries there either. + */ + this.blockPhys.fDirty = true; }, /** * writeLongBE(off, l, addr) @@ -1347,7 +1373,14 @@ Memory.prototype = { } this.blockPDE.adw[this.iPDE] |= X86.PTE.ACCESSED; this.blockPTE.adw[this.iPTE] |= X86.PTE.ACCESSED | X86.PTE.DIRTY; - this.fDirty = true; + /* + * NOTE: Technically, we should be setting the fDirty flag on blockPDE and blockPTE as well, but let's + * consider the two sole uses of fDirty. First, we have cleanMemory(), which is currently used only by + * the Video component, and video memory should never contain page directories or page tables, so no + * worries there. Second, we have saveMemory(), but the CPU now asks that function to save all physical + * memory blocks whenever paging is enabled, so no worries there either. + */ + this.blockPhys.fDirty = true; }, /** * readBackTrackNone(off) diff --git a/modules/pcjs/lib/state.js b/modules/pcjs/lib/state.js index a30af0bd3..3bf0a7644 100644 --- a/modules/pcjs/lib/state.js +++ b/modules/pcjs/lib/state.js @@ -278,9 +278,7 @@ State.prototype = { if (s) { this[this.id] = s; this.fLoaded = true; - if (DEBUG && this.messageEnabled()) { - this.printMessage("localStorage(" + this.key + "): " + s.length + " bytes loaded"); - } + if (DEBUG) this.printString("localStorage(" + this.key + "): " + s.length + " bytes loaded"); return true; } } @@ -317,9 +315,7 @@ State.prototype = { if (web.hasLocalStorage()) { var s = JSON.stringify(this[this.id]); if (web.setLocalStorageItem(this.key, s)) { - if (DEBUG && this.messageEnabled()) { - this.printMessage("localStorage(" + this.key + "): " + s.length + " bytes stored"); - } + if (DEBUG) this.printString("localStorage(" + this.key + "): " + s.length + " bytes stored"); } else { /* * WARNING: Because browsers tend to disable all alerts() during an "unload" operation, @@ -377,40 +373,24 @@ State.prototype = { var sKey = aKeys[i]; if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) { web.removeLocalStorageItem(sKey); - if (DEBUG && this.messageEnabled()) { - this.printMessage("localStorage(" + sKey + ") removed"); - } + if (DEBUG) this.printString("localStorage(" + sKey + ") removed"); aKeys.splice(i, 1); i = 0; } } }, /** - * messageEnabled(bitsMessage) + * printString(s) * * @this {State} - * @param {number} [bitsMessage] is one or more Messages category flag(s) - * @return {boolean} + * @param {string} s is any caller-defined string */ - messageEnabled: function(bitsMessage) { - if (DEBUGGER && this.dbg) { - if (bitsMessage == null) { - bitsMessage = Messages.STATE; - } else { - bitsMessage |= Messages.STATE; + printString: function(s) { + if (DEBUG && DEBUGGER && this.dbg) { + if (this.dbg.messageEnabled(Messages.STATE)) { + this.dbg.message(s); } - return this.dbg.messageEnabled(bitsMessage); } - return false; - }, - /** - * printMessage(sMessage) - * - * @this {State} - * @param {string} sMessage is any caller-defined message string - */ - printMessage: function(sMessage) { - if (DEBUGGER && this.dbg) this.dbg.message(sMessage); } }; diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index c4f0ef66b..e21aecfdd 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -218,6 +218,8 @@ if (PREFETCH) { X86CPU.PFINFO.IP_MASK = ((X86CPU.PFINFO.LENGTH - 1) & ~0x3); } +X86CPU.PAGEBLOCKS_CACHE = 512; // TODO: This seems adequate for 4Mb of RAM, but it should be dynamically reconfigured + /** * initMemory(aMemBlocks, nBlockShift) * @@ -432,6 +434,12 @@ X86CPU.prototype.enablePageBlocks = function() * if the Debugger is suppressing faults or calling probeAddr(), returning memEmpty is helpful. */ this.memEmpty = new Memory(); + + /* + * Initialize our PAGEBLOCKS cache (see acquirePageBlock() and releasePageBlock()). + */ + this.aCacheBlocks = new Array(X86CPU.PAGEBLOCKS_CACHE); + this.iCacheBlocks = 0; } else { /* * Our equivalent of a TLB flush. NOTE: We do not attempt to simulate an actual TLB; our @@ -440,12 +448,62 @@ X86CPU.prototype.enablePageBlocks = function() * a constrained TLB -- at least not from the 80386 era, which is all we're emulating. */ for (var i = 0; i < this.aBlocksPaged.length; i++) { - this.aMemBlocks[this.aBlocksPaged[i]] = this.blockUnpaged; + var iBlock = this.aBlocksPaged[i]; + this.releasePageBlock(this.aMemBlocks[iBlock]); + this.aMemBlocks[iBlock] = this.blockUnpaged; } } this.aBlocksPaged = []; }; +/** + * acquirePageBlock(addr) + * + * This implements a simple paged memory block cache. Candidates for caching must be released via + * releasePageBlock(). + * + * After acquiring a block from this cache, the caller MUST use setPhysBlock() to properly reinitialize + * it for the new given linear address. + * + * @this {X86CPU} + * @param {number} addr + * @return {Memory} + */ +X86CPU.prototype.acquirePageBlock = function(addr) +{ + var block; + if (this.iCacheBlocks > 0) { + block = this.aCacheBlocks[--this.iCacheBlocks]; + /* + * Paged memory blocks are all very generic and contain no memory of their own, so the fact + * that we're not calling the Memory constructor to reinitialize it is OK. setPhysBlock() is + * what's critical, and the caller will take care of that. However, to avoid any confusion, + * especially when debugging, there are a few properties we should reinitialize, hence init(). + */ + block.init(addr); + } else { + block = new Memory(addr, 0, 0, Memory.TYPE.PAGED); + } + return block; +}; + +/** + * releasePageBlock(block) + * + * Instead of simply tossing Memory blocks onto the garbage collector's heap, we'll retain a maximum + * number (X86CPU.PAGEBLOCKS_CACHE) in aCacheBlocks, with iCacheBlocks pointing to the next free element. + * + * @this {X86CPU} + * @param {Memory} block + */ +X86CPU.prototype.releasePageBlock = function(block) +{ + this.assert(block && block.type === Memory.TYPE.PAGED); + if (this.iCacheBlocks < X86CPU.PAGEBLOCKS_CACHE) { + this.aCacheBlocks[this.iCacheBlocks++] = block; + } +}; + /** * mapPageBlock(addr, fWrite, fSuppress) * @@ -535,14 +593,13 @@ X86CPU.prototype.mapPageBlock = function(addr, fWrite, fSuppress) * * Now we can create a new PAGED Memory block and record the physical block info using setPhysBlock(). */ - var addrPage = addr & ~X86.LADDR.OFFSET; - var blockPage = new Memory(addrPage, 0, 0, Memory.TYPE.PAGED); + var blockPage = this.acquirePageBlock(addr & ~X86.LADDR.OFFSET); blockPage.setPhysBlock(blockPhys, blockPDE, offPDE, blockPTE, offPTE); blockPage.copyBreakpoints(this.dbg, block); this.aMemBlocks[iBlock] = blockPage; - this.aBlocksPaged.push(iBlock); + return blockPage; }; @@ -563,6 +620,19 @@ X86CPU.prototype.disablePageBlocks = function() } }; +/** + * isPagingEnabled() + * + * @this {X86CPU} + * @return {boolean} + */ +X86CPU.prototype.isPagingEnabled = function() +{ + var fPaging = !!(this.regCR0 & X86.CR0.PG); + this.assert((this.aMemBlocks !== this.aBusBlocks) === fPaging); + return fPaging; +}; + /** * initProcessor() * @@ -1717,7 +1787,7 @@ X86CPU.prototype.save = function() state.set(1, a); state.set(2, [this.segData.sName, this.segStack.sName, this.opFlags, this.opPrefixes, this.intFlags, this.regEA, this.regEAWrite]); state.set(3, [0, this.nTotalCycles, this.getSpeed()]); - state.set(4, this.bus.saveMemory()); + state.set(4, this.bus.saveMemory(this.isPagingEnabled())); return state.data(); }; diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index c1959c4bb..721123ef2 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -4116,7 +4116,8 @@ X86.fnFaultMessage = function(nFault, nError, fHalt) var sMessage = "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode " + str.toHexByte(bOpcode); if (fHalt && fRunning) sMessage += " (blocked by PCjs Debugger)"; - if (this.printMessage(sMessage, fHalt || bitsMessage, true)) { + if (DEBUGGER && this.dbg) { + this.printMessage(sMessage, fHalt || bitsMessage, true); if (fHalt) { /* * By setting fHalt to fRunning (which is true while running but false while single-stepping), @@ -4131,8 +4132,8 @@ X86.fnFaultMessage = function(nFault, nError, fHalt) } } else { /* - * If printMessage() returned false, then there's no Debugger, which means that messageEnabled() must have - * returned false as well, which means that fHalt must be true. Which means we should shut the machine down. + * If there's no Debugger, then messageEnabled() must have returned false, which means that fHalt must + * be true. Which means we should shut the machine down. */ this.assert(fHalt); this.notice(sMessage); diff --git a/modules/shared/lib/component.js b/modules/shared/lib/component.js index f5039366f..1172fb1e3 100644 --- a/modules/shared/lib/component.js +++ b/modules/shared/lib/component.js @@ -960,16 +960,13 @@ Component.prototype = { * @param {string} sMessage is any caller-defined message string * @param {number|boolean} [bitsMessage] is zero or more MESSAGE_* category flag(s) * @param {boolean} [fAddress] is true to display the current address - * @return {boolean} true if Debugger available, false if not */ printMessage: function(sMessage, bitsMessage, fAddress) { if (DEBUGGER && this.dbg) { if (bitsMessage === true || this.messageEnabled(bitsMessage | 0)) { this.dbg.message(sMessage, fAddress); } - return true; } - return false; }, /** * printMessageIO(port, bOut, addrFrom, name, bIn, bitsMessage)