Switched interrupt management to a new set of Trigger functions and static Trigger objects, eliminating the old InterruptQueue of constantly created/destroyed InterruptEvents

This commit is contained in:
Jeff Parsons 2016-10-13 10:20:11 -07:00 committed by Jeff Parsons
commit 686396d726
6 changed files with 255 additions and 254 deletions

View file

@ -898,6 +898,11 @@ CPUPDP11.prototype.calcRemainingTime = function()
*
* Why not use JavaScript's setTimeout() instead? Good question. For a good answer, see setTimer() below.
*
* TODO: Consider making the addTimer() and setTimer() interfaces more like the addTrigger() and setTrigger()
* interfaces (which return the underlying object instead of an array index) and maintaining a separate list
* of active timers, in order of highest to lowest cycle countdown values, as this could speed up getBurstCycles()
* and updateTimers() functions ever so slightly.
*
* @this {CPUPDP11}
* @param {function()} callBack
* @return {number} timer index

View file

@ -1704,8 +1704,8 @@ PDP11.opWAIT = function(opCode)
*
* 1) opWAIT() sets a new opFlags bit (OPFLAG.WAIT)
* 2) Rewind the PC back to the WAIT instruction
* 3) Whenever stepCPU() detects OPFLAG.WAIT, call checkInterruptQueue()
* 4) If checkInterruptQueue() detects an interrupt, advance PC past the WAIT and then dispatch the interrupt
* 3) Whenever stepCPU() detects OPFLAG.WAIT, call checkInterrupts()
* 4) If checkInterrupts() detects an interrupt, advance PC past the WAIT and then dispatch the interrupt
*
* Technically, the PC is already exactly where it's supposed to be, so why are we wasting time with steps
* 2 and 4? It's largely for the Debugger's sake, so that as long as execution is "blocked" by a WAIT, that's

View file

@ -87,29 +87,30 @@ Component.subclass(CPUStatePDP11, CPUPDP11);
* Overview of Device Interrupt Support
*
* Originally, the CPU maintained a queue of requested interrupts. Entries in this queue recorded a device's
* priority, vector, and delay (ie, a number of instructions to process before issuing the interrupt). This queue
* would constantly grow and shrink as requests were issued and dispatched, and as long as there was something
* priority, vector, and delay (ie, a number of instructions to execute before dispatching the interrupt). This
* queue would constantly grow and shrink as requests were issued and dispatched, and as long as there was something
* in the queue, the CPU was constantly examining it.
*
* Now we are trying something simpler. First, the CPU offers timer services to any device that wants a callback
* after a specified delay, which are much more efficient than requiring the CPU to dive into an interrupt queue and
* look for (and decrement) delay counts on every instruction.
* Now we are trying something more efficient. First, for devices that require delays (like a serial port's receiver
* and transmitter buffer registers that are supposed to "clock" the data in and out at a specific baud rate), the
* CPU offers timer services that will "fire" a callback after a specified delay, which are much more efficient than
* requiring the CPU to dive into an interrupt queue and decrement delay counts on every instruction.
*
* Second, when a device decides it's time to interrupt (either at the end of some operation or when its delay timer
* has fired), it will simply request the interrupt, and CPU priority permitting, the interrupt will be dispatched.
* If CPU priority is NOT permitting, then the interrupt will remain pending until the priority drops. This means
* that the CPU need not check for any pending interrupts until/unless the CPU priority changes.
* Second, devices that generate interrupts will allocate a trigger object during initialization; we will no longer
* be creating and destroying interrupt event objects and inserting/deleting them in a constantly changing queue. Each
* trigger contains properties that never change (eg, the vector and priority), along with a "next" pointer that's
* only used when the trigger is active.
*
* All this will be managed with "triggers". Every device that wants to interrupt must register via addTrigger().
* This will add a Trigger object to an array, and each object will contain the (self) assigned vector, the priority,
* the interrupt request status, and a next pointer.
* When a device decides it's time to interrupt (either at the end of some I/O operation or when a timer has "fired"),
* it will simply "pull the trigger", which basically means that its trigger will be linked onto a list of active
* triggers, and in priority order, so that when the CPU is ready to acknowledge interrupts, it need only check the
* top of the active trigger list.
*/
/**
* @typedef {{
* vector: number,
* priority: number,
* request: number,
* next: (Trigger|null)
* }} Trigger
*/
@ -125,10 +126,10 @@ CPUStatePDP11.prototype.initProcessor = function()
this.decode = PDP11.op1170.bind(this);
/** @type {Array.<Trigger>} */
this.aTriggers = [];
this.aTriggers = []; // list of all triggers, active or not (TODO: DEBUG-only?)
/** @type {Trigger|null} */
this.triggerNext = null;
this.triggerNext = null; // the head of the active triggers list, in priority order
this.initRegs();
@ -599,23 +600,11 @@ CPUStatePDP11.prototype.setSP = function(addr)
*/
CPUStatePDP11.prototype.addTrigger = function(vector, priority)
{
var trigger = {vector: vector, priority: priority, request: -1, next: null};
var trigger = {vector: vector, priority: priority, next: null};
this.aTriggers.push(trigger);
return trigger;
};
/**
* checkTriggers()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.checkTriggers = function()
{
if (this.triggerNext && this.setTrigger(this.triggerNext)) {
this.removeTrigger(this.triggerNext);
}
};
/**
* insertTrigger(trigger)
*
@ -638,7 +627,7 @@ CPUStatePDP11.prototype.insertTrigger = function(trigger)
break;
}
triggerPrev = triggerNext;
} while (true);
} while (triggerPrev);
}
}
};
@ -655,14 +644,14 @@ CPUStatePDP11.prototype.removeTrigger = function(trigger)
if (triggerPrev == trigger) {
this.triggerNext = trigger.next;
} else {
do {
while (triggerPrev) {
var triggerNext = triggerPrev.next;
if (triggerNext == trigger) {
triggerPrev.next = triggerNext.next;
break;
}
triggerPrev = triggerNext;
} while (true);
}
}
};
@ -670,16 +659,65 @@ CPUStatePDP11.prototype.removeTrigger = function(trigger)
* setTrigger(trigger)
*
* @this {CPUStatePDP11}
* @param {Trigger|null} trigger
* @param {Trigger} trigger
* @return {boolean} (true if interrupt dispatched, false if not)
*/
CPUStatePDP11.prototype.setTrigger = function(trigger)
{
if (!this.dispatchInterrupt(trigger.vector, trigger.priority)) {
this.insertTrigger(trigger);
return false;
/*
* For now, we will not dispatch interrupts immediately, but always defer to checkInterrupts() instead.
*
if (this.dispatchInterrupt(trigger.vector, trigger.priority)) {
return true;
}
*/
this.insertTrigger(trigger);
this.opFlags |= PDP11.OPFLAG.INTQ;
return false;
};
/**
* checkTriggers(priority)
*
* @this {CPUStatePDP11}
* @param {number} priority
* @return {Trigger|null}
*/
CPUStatePDP11.prototype.checkTriggers = function(priority)
{
return (this.triggerNext && this.triggerNext.priority > priority)? this.triggerNext : null;
};
/**
* checkInterrupts()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.checkInterrupts = function()
{
if (this.opFlags & PDP11.OPFLAG.INTQ) {
this.opFlags &= ~PDP11.OPFLAG.INTQ;
var vector = PDP11.TRAP.PIRQ;
var priority = (this.regPIR & PDP11.PSW.PRI) >> PDP11.PSW.SHIFT.PRI;
var trigger = this.checkTriggers(priority);
if (trigger) {
vector = trigger.vector;
priority = trigger.priority;
}
if (this.dispatchInterrupt(vector, priority)) {
if (trigger) this.removeTrigger(trigger);
}
}
else if (this.opFlags & PDP11.OPFLAG.INTQ_SPL) {
/*
* We know that INTQ (bit 1) is clear, so since INTQ_SPL (bit 0) is set, incrementing opFlags
* will transform INTQ_SPL into INTQ, without affecting any other (higher) bits.
*/
this.opFlags++;
}
return true;
};
/**
@ -704,61 +742,6 @@ CPUStatePDP11.prototype.dispatchInterrupt = function(vector, priority)
return false;
};
/**
* checkInterrupts()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.checkInterrupts = function()
{
if (this.opFlags & PDP11.OPFLAG.INTQ) {
this.opFlags &= ~PDP11.OPFLAG.INTQ;
this.checkTriggers();
/*
var interruptEvent = null;
var savePSW = this.regPIR & 0xe0;
for (var i = this.interruptQueue.length; --i >= 0;) {
if (this.interruptQueue[i].delay > 0) {
this.interruptQueue[i].delay--;
this.opFlags |= PDP11.OPFLAG.INTQ;
break; // Decrement only one delay 'difference' per cycle
}
if (this.interruptQueue[i].callback) {
if (!this.interruptQueue[i].callback()) {
this.interruptQueue.splice(i, 1);
continue;
}
//delete
this.interruptQueue[i].callback = null;
}
if (this.interruptQueue[i].priority > savePSW) {
savePSW = this.interruptQueue[i].priority;
interruptEvent = this.interruptQueue[i];
this.interruptQueue.splice(i, 1);
}
}
if (savePSW > (this.regPSW & 0xe0)) {
if (this.opFlags & PDP11.OPFLAG.WAIT) {
this.advancePC(2);
this.opFlags &= ~PDP11.OPFLAG.WAIT;
}
if (!interruptEvent) {
this.trap(PDP11.TRAP.PIRQ, PDP11.REASON.INTERRUPT);
} else {
this.trap(interruptEvent.vector, PDP11.REASON.INTERRUPT);
}
}
*/
}
else if (this.opFlags & PDP11.OPFLAG.INTQ_SPL) {
/*
* We know that INTQ (bit 1) is clear, so since INTQ_SPL (bit 0) is set, incrementing opFlags
* will transform INTQ_SPL into INTQ, without affecting any other (higher) bits.
*/
this.opFlags++;
}
};
/**
* getPSW()
*
@ -768,11 +751,11 @@ CPUStatePDP11.prototype.checkInterrupts = function()
CPUStatePDP11.prototype.getPSW = function()
{
/*
* I'm not sure why this function can't simply be written as:
* TODO: I'm not sure why this function can't simply be written as:
*
* return (this.regPSW & ~PDP11.PSW.FLAGS) | (this.getNF() | this.getZF() | this.getVF() | this.getCF());
*
* but for now, I'm keeping the same masking logic as pdp11.js.
* but for now, I'm keeping the same masking logic as the original pdp11.js.
*/
var mask = PDP11.PSW.CMODE | PDP11.PSW.PMODE | PDP11.PSW.REGSET | PDP11.PSW.PRI | PDP11.PSW.TF;
return this.regPSW = (this.regPSW & mask) | this.getNF() | this.getZF() | this.getVF() | this.getCF();
@ -817,11 +800,23 @@ CPUStatePDP11.prototype.setPSW = function(newPSW)
this.regsAltStack[oldMode] = this.regsGen[6];
this.regsGen[6] = this.regsAltStack[this.mmuMode];
}
this.regPSW = newPSW;
/*
* Trigger a call to checkInterrupts()
* Trigger a call to checkInterrupts(), just in case.
*
* TODO: I think this is overdone; if you set a breakpoint on checkInterrupts(), you'll see that a significant
* percentage of calls do nothing. For example, you'll usually see a spurious checkInterrupts() immediately after
* an interrupt has been dispatched, because it's dispatched via trap(), and trap() calls setPSW().
*
* I mean, sure, it's POSSIBLE that the new PSW loaded by trap() actually set a lower priority, allowing a lower
* priority interrupt to immediately be acknowledged. But perhaps we should a bit more rigorous here.
*
* For example, we could avoid setting INTQ unless 1) there's actually an active interrupt trigger (ie, triggerNext
* is not null) or 2) an optional fCheckInterrupts flag is passed to us, because the caller has some knowledge
* that priority could be changing. Just throwing out some ideas....
*/
this.opFlags |= PDP11.OPFLAG.INTQ;
this.regPSW = newPSW;
};
/**

View file

@ -186,7 +186,7 @@ var PDP11 = {
*/
OPFLAG: {
INTQ_SPL: 0x01, // INTQ triggered by SPL
INTQ: 0x02, // call checkInterruptQueue()
INTQ: 0x02, // call checkInterrupts()
WAIT: 0x04, // WAIT operation in progress
TRAP_TF: 0x10, // aka PDP11.PSW.TF
TRAP_MMU: 0x20,