*-----------------------------------------------------------
Title[CpaProcess.mc...May 23, 1986 10:49:38 am PDT...Willie-Sue];
* Process/monitor opcodes, interrupt handler, and process scheduler.
IfE[AltoMode, 0, , ER[Pilot-only.module--will.not.work.with.Alto, 1]];
*-----------------------------------------------------------

%

 CONTENTS, by order of occurence

ME  Monitor Enter
MRE  Monitor Reenter
MXD  Monitor Exit and Depart
MXW  Monitor Exit and Wait
NOTIFY  Notify
BCAST  Broadcast
REQUEUE  Requeue
Pop2ShortOrLong Pop 2 short or long pointers and load base registers
Pop1ShortOrLong Pop 1 short or long pointer and load base register
FetchCPFlags Fetch current process's flags
FetchCPLink Fetch current process's link
EnterFailed Put current process on ML queue
ExitMonitor Unlock monitor and awaken process at head of ML queue
NotifyWakeup Naked Notify of CV (awaken process or set wakeup waiting bit)
WakeHead Awaken process at head of CV queue
Requeue  Move a process from a source queue to a destination queue
CleanUpCVQueue Clean up CV queue that may have been left in a mess by Requeue
IWDC  Increment WDC, disable interrupts
DWDC  Decrement WDC, reenable interrupts as appropriate
MesaFault Suspend current process and notify fault handler
MesaReschedTrap Mesa Reschedule trap
MesaInterrupt Notify naked CVs according to wakeups pending in NWW
CheckForTimeouts Scan PSBs and awaken any that have timed out
MesaReschedule Suspend current process and begin new one from Ready Queue

%
TopLevel;
KnowRBase[RTemp0];
*-----------------------------------------------------------
* Base registers.
*-----------------------------------------------------------

* PDA    * Process data area -- permanently assigned

* Following two registers must be IFU-addressable and mutually xor 1.
BR[MLBR, 34];   * Monitor lock
BR[CVBR, 35];   * Condition variable

BR[RQBR, IP[PDA]];  * Ready queue -- know it is word 0 of the PDA
*-----------------------------------------------------------
* R register usage.
*-----------------------------------------------------------
* Permanently assigned:
* CurrentPSB PsbHandle of Current PSB (accessible via RR/WR)
*  Note: PrincOps says this should be a PsbIndex, but
*  the D0 implementation says otherwise!
* CurrentTime Current time, in ~50 ms ticks
* TickCount Counter to divide down vertical field interrupts to make ticks

* RTemp0-3 are used for general scratch purposes.
* The remaining RTemps are assigned for specific purposes:
RME[Process, RTemp4];  * -> PSB being worked on
RME[DQBRReg, RTemp5];  * MemBase value for Requeue destination queue
    * B0=1 => Requeue has been done
RME[SQBRReg, RTemp6];  * MemBase value for Requeue source queue
    * B15=1 => source queue is NIL
RME[IndexMask, XferFlags]; * Constant mask for extracting Q.tail words

* BRConst[XX] generates a constant to select base register XX, and with
* bit 0 = 1 to set the Requeue flag in DQBRReg (if relevant).
M[BRConst, Add[100000, LShift[IP[#1], 10]]C];
*-----------------------------------------------------------
* Process/monitor data structures
*-----------------------------------------------------------

* Priority: TYPE = [0..7];

* Note: QueueHandles are either short or long pointers; the process/monitor
* opcodes must distinguish between them by testing the stack depth.
* QueueHandle: LONG POINTER TO Queue;

* Note: a Queue has the same structure as the words containing
* CV.tail, ML.tail, PSBL.next, and PSBF.cleanup.
* Queue: TYPE = MACHINE DEPENDENT RECORD [
   * reserved: [0..17B] ← 0,
MC[Q.tail, 7774]; * tail: PsbIndex,
   * reserved: [0..3];
MC[shcQ.tail, 44]; * ShC for making Q.tail: LMask = 4, RMask = 2

* PsbHandle: TYPE = POINTER TO PSB; -- PDA-relative
* PsbIndex: TYPE = [0..1024);
* Invariant: for any given PSB, PsbHandle = 4*PsbIndex.

* PSB: TYPE = MACHINE DEPENDENT RECORD [
MC[PSB.link, 0]; * Link: PsbLink,
MC[PSB.flags, 1]; * flags: PsbFlags,
MC[PSB.context, 2]; * context: POINTER, -- to Frame or StateVector
MC[PSB.timeout, 3]; * timeout: CARDINAL ];

MC[sizePSB, 4];

* PsbLink: TYPE = MACHINE DEPENDENT RECORD [
MC[PSBL.failed, 100000]; * failed: BOOLEAN, -- monitor enter failed
MC[PSBL.priority, 70000]; * priority: Priority,
MC[PSBL.next, 7774]; * next: PsbIndex,
   * reserved: BIT ← 0,
MC[PSBL.vector, 1]; * vector: BOOLEAN ]; -- context is StateVector

* PsbFlags: TYPE = MACHINE DEPENDENT RECORD [
   * available: [0..17B], -- software use
MC[PSBF.cleanup, 7774]; * cleanup: PsbIndex, -- cleanup link
MC[PSBF.waiting, 2]; * waiting: BOOLEAN, -- waiting on CV
MC[PSBF.abort, 1]; * abort: BOOLEAN ]; -- abort at next wait

* Condition: TYPE = MACHINE DEPENDENT RECORD [
   * reserved: [0..17B] ← 0,
MC[CV.tail, 7774], * tail: PsbIndex,
MC[CV.abortable, 2]; * abortable: BOOLEAN,
MC[CV.wakeup, 1]; * wakeup: BOOLEAN ]; -- wakeup waiting

* MonitorLock: TYPE = MACHINE DEPENDENT RECORD [
   * reserved: [0..17B] ← 0,
MC[ML.tail, 7774]; * tail: PsbIndex,
   * available: BIT, -- currently unused
MC[ML.locked, 1]; * locked: BOOLEAN ];
* StateVector: TYPE = MACHINE DEPENDENT RECORD [
MC[SV.stack, 0]; * stack: ARRAY[0..7] OF UNSPECIFIED,
MC[SV.stkp, 16]; * breakbyte, stkp: BYTE,
MC[SV.frame, 17]; * frame: ControlLink,
MC[SV.data, 20]; * data: ARRAY[0..1] OF UNSPECIFIED ];

* FaultIndex: TYPE = [0..7];

* Process Data Area (PDA) format, relative to PDA base register
* PDA: TYPE = MACHINE DEPENDENT RECORD [
   * block: ARRAY [0..0) OF PSB, -- zero length,
   * -- enables PDA-relative addressing of PSBs
MSC[PDA.ready, 0]; * ready: Queue,
MSC[PDA.count, 1]; * count: CARDINAL, -- number of PSBs in PDA
* MSC[PDA.timeout, 2]; * timeout: POINTER TO TimeoutVector, -- unused
   * available: ARRAY [3..7] OF UNSPECIFIED,
MC[PDA.state, 10]; * state: ARRAY Priority OF POINTER TO StateVector,
MC[PDA.interrupt, 20]; * interrupt: ARRAY [0..15] OF RECORD [
   * Condition, UNSPECIFIED],
MC[PDA.fault, 60]; * fault: ARRAY FaultIndex OF RECORD [
   * Queue, Condition],
MC[PDA.psbs, 100]; * psbs: ARRAY [0..count) OF PSB ];

* Other stuff
* MC[TimerChanMask, 100000]; * This channel is used to run the 60-Hz timer
   * Must be sign bit
*-----------------------------------------------------------
IFUR[ME, 1, MLBR];  * MonitorEnter[m];
*-----------------------------------------------------------
:IfMEP;
 StkP-1, Branch[.+2];
 Stack&-1← MD, Branch[.+1];
 StkP+1, Call[Pop1ShortOrLong]; * MLBR← m
:Else;
 Call[Pop1ShortOrLong];  * MLBR← m
:EndIf;
* ml ← Fetch[m]^; IF ~ml.locked THEN
* BEGIN ml.locked ← TRUE; Store[m]^ ← ml; Push[TRUE]; END
* ELSE EnterFailed[m];
Call[MeStats];
Fetch← 0S, Call[TestMonitorLock];
LockMonitorAndExit:
Stack← (Store← 0S)+1, DBuf← Q, IFUNext0;
Stack← (Store← 0S)+1, DBuf← Q;
Call[MesStats];
IFUNext0;
*-----------------------------------------------------------
IFUR[MRE, 1, CVBR];  * MonitorReEnter[m, c];
*-----------------------------------------------------------
:IfMEP;
 StkP-1, Branch[.+2];
 Stack&-1← MD, Branch[.+1];
 StkP+1, Call[Pop2ShortOrLong]; * CVBR← c, MLBR← m
:Else;
 Call[Pop2ShortOrLong];  * CVBR← c, MLBR← m
:EndIf;
* ml ← Fetch[m]^;
* IF ml.locked THEN EnterFailed[m] ELSE ...
Call[MreStats];
Fetch← 0S, Call[TestMonitorLock];
* BEGIN CleanUpCVQueue[c];
* flags ← Fetch[@PDA.block[PSB].flags]^; flags.cleanup ← PsbNull;
* Store[@PDA.block[PSB].flags]^ ← flags;

 T← A0, MemBase← CVBR, Call[CleanUpCVQueue];
 T← (CurrentPSB)+1, MemBase← PDA;
 Fetch← T, RTemp2← NOT (IndexMask);
 RTemp2← (RTemp2) AND MD;
 Store← T, DBuf← RTemp2, Branch[MRENoAbort, R even]; * abort is bit 15

* IF flags.abort THEN
* BEGIN cond ← Fetch[c]^; IF cond.abortable THEN ProcessTrap[]; END;
* ml.locked ← TRUE; Store[m]^ ← ml; Push[TRUE]; END;
* RTemp0 contains cond (left over from CleanUpCVQueue).
PD← (RTemp0) AND (CV.abortable);
Branch[.+2, ALU=0];
T← sProcessTrap, Branch[SavePCAndTrap];
Nop;
MRENoAbort:
MemBase← MLBR, Branch[LockMonitorAndExit];
MemBase← MLBR;
Stack← (Store← 0S)+1, DBuf← Q;
Call[MresStats];
IFUNext0;
*-----------------------------------------------------------
TestMonitorLock:
* Test and set monitor lock; shared by ME and MRE.
* Enter: MD = monitor lock
* Exit: if monitor is locked, goes to EnterFailed; otherwise:
* RTemp1 = Q = monitor lock value, with ML.locked = TRUE.
* StkP incremented (in preparation for pushing TRUE result)
* Clobbers T
*-----------------------------------------------------------
Subroutine;
 T← A0, RTemp1← MD;
 T← RTemp1← (Store← T)+(DBuf← RTemp1)+1, * Dirty monitor, set ML.locked
  Branch[EnterFailed, R odd]; * Branch if already locked
 Q← T, StkP+1, Return;
*-----------------------------------------------------------
IFUR[MXD, 1, MLBR];  * MonitorExitAndDepart[m];
*-----------------------------------------------------------
:IfMEP;
 StkP-1, Branch[.+2];
 Stack&-1← MD, Branch[.+1];
 StkP+1, Call[Pop1ShortOrLong]; * MLBR← m
:Else;
 Call[Pop1ShortOrLong];  * MLBR← m
:EndIf;
* IF ExitMonitor[m] THEN Reschedule[];
T← A0, Call[ExitMonitor];
RTemp1← ID, Branch[MesaReschedule1]; * IL=1
RTemp1← ID;
Call[MxdStats];
Branch[MesaReschedule1]; * IL=1
*-----------------------------------------------------------
IFUR[MXW, 1, CVBR];  * MonitorExitAndWait[m, c, t];
*-----------------------------------------------------------
 T← Stack&-1, Branch[MXWM2];
:IfMEP;
 T← Stack&-1← MD, Branch[.+1];
:EndIf;
MXWM2: RTemp3← T, Call[Pop2ShortOrLong]; * RTemp3← t, CVBR← c, MLBR← m

* CleanUpCVQueue[c]; ExitMonitor[m];

 T← A0, MemBase← CVBR, Call[CleanUpCVQueue];
 T← A0, MemBase← MLBR, Call[ExitMonitor];

* flags ← Fetch[@PDA.block[PSB].flags]^; cond ← Fetch[c]^;
* IF ~(flags.abort AND cond.abortable) THEN ...

 MemBase← PDA, Call[FetchCPFlags]; * Returns RTemp1=flags, T=@PSB.flags
 RTemp2← T, MemBase← CVBR; * Save @PSB.flags
 PD← Fetch← 0S, RTemp1, Branch[.+2, R even]; * Fetch[c], test flags.abort
 PD← (Add[CV.abortable!]S) AND MD;
 T← A0, RTemp0← MD,  * RTemp0← cond
  Branch[.+2, ALU=0]; * Branch if PSB not aborting or CV not abortable
 RTemp1← ID, Branch[MesaReschedule1]; * Aborting, don't wait
* IF cond.wakeup THEN
* BEGIN cond.wakeupWaiting ← FALSE; Store[c]^ ← cond; END ...
RTemp0← (RTemp0) AND NOT (CV.wakeup),
Branch[.+2, R even]; * cond.wakeup is bit 15
Store← T, DBuf← RTemp0, Branch[MesaReschedule];
RTemp0← (RTemp0) AND NOT (CV.wakeup),
Branch[mxw3, R even]; * cond.wakeup is bit 15
Store← T, DBuf← RTemp0;
Call[MxwStats];
Branch[MesaReschedule];
mxw3:
* ... ELSE BEGIN
* Store[@PDA.block[PSB].timeout]^ ← IF t=0 THEN 0 ELSE MAX[1, ticks+t];
* flags.waiting ← TRUE; Store[@PDA.block[PSB].flags]^ ← flags;
* Requeue[src: @PDA.ready, dst: c, psb: PSB]; END;

 T← RTemp3, MemBase← PDA; * RTemp3 = time to wait (0 = infinity)
 T← (CurrentTime)+T, Branch[.+3, ALU=0];
 PD← T-1;   * Generates carry unless T=0
 RTemp3← T+1, XorSavedCarry;
 T← (RTemp1) OR (PSBF.waiting);
 Store← RTemp2, DBuf← T;  * Store updated flags
 T← (CurrentPSB)+(PSB.timeout);
 DQBRReg← BRConst[CVBR];  * dst = c
 SQBRReg← BRConst[RQBR];  * src = ready
 Store← T, DBuf← RTemp3, Branch[Requeue&Resched];
*-----------------------------------------------------------
IFUR[NOTIFY, 1, CVBR, N[1]]; * Notify[c];
IFUR[BCAST, 1, CVBR, N[0]];  * Broadcast[c];
*-----------------------------------------------------------
:IfMEP;
 StkP-1, Branch[.+2];
 Stack&-1← MD, Branch[.+1];
 RTemp3← ID-1, StkP+1, Call[Pop1ShortOrLong]; * CVBR← c
:Else;
 RTemp3← ID-1, Call[Pop1ShortOrLong]; * CVBR← c
:EndIf;

* This code is shared by NOTIFY and BCAST.
* RTemp3 = 0 if NOTIFY, -1 if BCAST.
* CleanUpCVQueue[c];
* WHILE (cond ← Fetch[c]).tail # PsbNull DO
* WakeHead[c]; requeue ← TRUE; ENDLOOP;
* IF requeue THEN Reschedule[];
* Note that we actually call CleanUpCVQueue every iteration.
* This is ok since it is an idempotent operation.
Nop;
Branch[bcs, R < 0], RTemp3;
Nop;
Call[NotifyStats];
Branch[BroadcastLoop];
bcs:
Call[BCastStats];
Nop;
BroadcastLoop:
T← A0, Call[CleanUpCVQueue]; * Returns Process = ALU = c.tail
RTemp3← (RTemp3)+1, Branch[.+2, ALU=0];
RTemp3← (RTemp3)-1, Call[WakeHead];
MemBase← CVBR, RTemp3, Branch[BroadcastLoop, R<0];
RTemp1← ID, Branch[MesaReschedule1];
*-----------------------------------------------------------
IFUR[REQUEUE, 1, CVBR];  * Requeue[sq, dq, psb];
*-----------------------------------------------------------
 T← Stack&-1, Branch[REQUEUEM2]; * PSB handle
:IfMEP;
 T← Stack&-1← MD, Branch[.+1];
:EndIf;
REQUEUEM2:
 Process← T, Call[Pop2ShortOrLong]; * CVBR← dq, MLBR← sq
* Dirty both queues so that if a write-protect fault is to occur,
* it will occur now.
SQBRReg← T-T-1, Branch[ReQSQNil, ALU=0]; * Branch if sq=NIL
T← Fetch← 0S;
SQBRReg← BRConst[MLBR];  * sq in MLBR
Store← T, DBuf← MD, FlipMemBase, Branch[.+2];
ReQSQNil:
T← A0, FlipMemBase;
Fetch← T, DQBRReg← BRConst[CVBR]; * dq in CVBR
Store← T, DBuf← MD, Branch[Requeue&Resched];
Store← T, DBuf← MD;
Call[ReqStats];
Branch[Requeue&Resched];
*-----------------------------------------------------------
Pop2ShortOrLong:
* Pop two short or long pointers and load base registers.
* Enter: MemBase, MemBase xor 1 = first and second BRs to be loaded
* TIOA=0
* Exit: MemBase = entering MemBase xor 1
* Both BRs loaded
* T = ALU = 0 iff second pointer is NIL
* DQBRReg >= 0 (i.e., Requeue flag initialized to 0, if relevant)
* IndexMask initialized
* Clobbers RTemp0, DQBRReg
*-----------------------------------------------------------
Subroutine;

 DQBRReg← TIOA&StkP;  * Assume emulator's TIOA=0 !
 DQBRReg← (DQBRReg)-(2C);
 IndexMask← And[Q.tail!, 177400]C, Branch[.+2, ALU=0];

* StkP=4: long pointer, pop high part off stack.
 T← A0, BRHi← Stack&-1, Branch[.+2];

* StkP=2: short pointer, lengthen it with high part of MDS.
 T← (BRHi← MDSHi)-(MDSHi)-1;

 BRLo← Stack&-1;

* Now ready to pop second pointer.
* DQBRReg=2 and T=0 if first pointer was long; DQBRReg=0 and T=-1 if short.
* Adjust DQBRReg to 1 in latter case.
 DQBRReg← (DQBRReg)-T, FlipMemBase, Branch[Pop1Tail];
*-----------------------------------------------------------
Pop1ShortOrLong:
* Pop one short or long pointer and load base register.
* Enter: MemBase = BR to be loaded
* TIOA=0
* Exit: MemBase unchanged
* BR loaded
* T = ALU = 0 iff pointer is NIL
* DQBRReg >= 0 (i.e., Requeue flag initialized to 0, if relevant)
* IndexMask initialized
* Clobbers RTemp0, DQBRReg
*-----------------------------------------------------------
Subroutine;

 IndexMask← And[Q.tail!, 177400]C;
 DQBRReg← TIOA&StkP;  * Assume emulator's TIOA=0 !
Pop1Tail:
 PD← (DQBRReg)-(2C);
 IndexMask← (IndexMask) OR (And[Q.tail!, 377]C), Branch[.+2, ALU#0];

* StkP=2: long pointer, pop high part off stack.
 T← BRHi← Stack&-1, Branch[.+2];

* StkP=1: short pointer, lengthen it with high part of MDS.
 T← A0, BRHi← MDSHi, Branch[Pop1StackErr, Carry];

 T← (BRLo← Stack&-1) OR T, Return;

* Opcode was not called with minimal stack; generate stack error trap.
TopLevel;
Pop1StackErr:
 Branch[StackError];
*-----------------------------------------------------------
FetchCPFlags:
* Fetches current process's flags.
* Enter: MemBase = PDA
* CurrentPSB set up
* Exit: RTemp1 = CurrentPSB^.flags
* T = pointer to CurrentPSB^.flags
* Process = CurrentPSB
*-----------------------------------------------------------
Subroutine;

 T← (CurrentPSB)+1;  * Know PSB.flags is word 1
 Process← (Fetch← T)-1, Branch[.+2];
*-----------------------------------------------------------
FetchCPLink:
* Fetches current process's link
* Enter: MemBase = PDA
* CurrentPSB set up
* Exit: RTemp1 = CurrentPSB^.link
* T = pointer to CurrentPSB^.link
* Process = CurrentPSB
*-----------------------------------------------------------
Subroutine;

 T← Process← Fetch← CurrentPSB; * Know that link is word 0
 RTemp1← MD, Return;
*-----------------------------------------------------------
EnterFailed:
* Enter: MLBR points to monitor lock
* Exit: Does not return but rather goes directly to MesaReschedule
*-----------------------------------------------------------
TopLevel;

* link ← Fetch[@PDA.block[PSB].link]^; link.failed ← TRUE;
* Store[@PDA.block[PSB].link]^ ← link;
* Requeue[src: @PSB.ready, dst: m, psb: PSB];

 MemBase← PDA, Call[FetchCPLink]; * RTemp1← PSB.link, T← @PSB.link
 RTemp1← (RTemp1) OR (PSBL.failed);
 Store← T, DBuf← RTemp1;
 DQBRReg← BRConst[MLBR];  * dst = ML
 SQBRReg← BRConst[RQBR];  * src = ReadyQ
 Process← T, Branch[Requeue&Resched]; * T = CurrentPSB
*-----------------------------------------------------------
ExitMonitor:
* Unlocks monitor, and moves process at head of monitor lock queue to
* ready queue, if there is one.
* Enter: T = 0
* MemBase = MLBR
* MLBR = QueueHandle for MonitorLock
* Clobbers T, RTemp0, RTemp1, RTemp2, Process, MemBase
*-----------------------------------------------------------
Subroutine;

* ml ← Fetch[m]^; ml.locked ← FALSE; Store[m]^ ← ml;
* IF ml.tail # PsbNull THEN ...

 Fetch← T;
 RTemp1← MD, T← (IndexMask) AND MD;
 RTemp1← (RTemp1) AND NOT (ML.locked), Branch[.+2, ALU#0];
 Store← T, DBuf← RTemp1, Return;

* BEGIN link ← Fetch[@PDA.block[mon.tail].link]^;
* Requeue[src: m, dst: @PDA.ready, psb: link.next]; END;

 Store← 0S, DBuf← RTemp1;
 MemBase← PDA;
 Fetch← T, DQBRReg← BRConst[RQBR]; * dst = ReadyQ
 SQBRReg← BRConst[MLBR];  * src = ML
 T← (IndexMask) AND MD;
 Process← Fetch← T, Branch[Requeue1];
*-----------------------------------------------------------
NotifyWakeup:
* Wakes the process at the head of the CV queue if there is one;
* sets the wakeup waiting bit in the CV if there isn't.
* Enter: CVBR = pointer to CV
* IndexMask initialized
* Exit: ALU#0 (for the benefit of MesaInterrupt)
* Clobbers T, RTemp0, RTemp1, RTemp2, Process, SQBRReg, DQBRReg, MemBase
*-----------------------------------------------------------
Subroutine;

 RTemp2← Link;
TopLevel;

* CleanupCondition[c];
* IF (cond ← Fetch[c]^).tail = PsbNull THEN
* BEGIN cond.wakeup ← TRUE; Store[c]^ ← cond; END
* ELSE WakeHead[c];

 T← A0, MemBase← CVBR, Call[CleanUpCVQueue];
 T← (RTemp0) OR (CV.wakeup), Branch[.+2, ALU=0];
 Link← RTemp2, Branch[WakeHead]; * CV queue non-empty, awaken head
 Link← RTemp2;   * CV queue empty, set wakeup waiting
Subroutine;
 Store← 0S, PD← DBuf← T, Return;
*-----------------------------------------------------------
WakeHead:
* Wakes the process at the head of the CV queue (there must be one).
* Enter: CVBR = pointer to CV
* Process = CV.tail
* IndexMask initialized
* Exit: MemBase = PDA
* ALU#0 (for the benefit of MesaInterrupt)
* Clobbers T, RTemp0, RTemp1, RTemp2, Process, SQBRReg, DQBRReg
*-----------------------------------------------------------
Subroutine;

 T← Process, MemBase← PDA;

* link ← Fetch[@PDA.block[cond.tail].link]^;

 Fetch← T,   * PSB.link is word 0
  DQBRReg← BRConst[RQBR]; * dst = ReadyQ
 Process← (IndexMask) AND MD;

* flags ← Fetch[@PDA.block[link.next].flags]^; flags.waiting ← FALSE;
* Store[@PDA.block[link.next].flags]^ ← flags;
* Store[@PDA.block[link.next].timeout]^ ← 0;

 T← (Process)+(PSB.flags);
 RTemp0← (Fetch← T)+(Sub[PSB.timeout!, PSB.flags!]C);
 Store← RTemp0, DBuf← 0C, RTemp0← MD;
 RTemp0← (RTemp0) AND NOT (PSBF.waiting);
 Store← T, DBuf← RTemp0;

* Requeue[src: c, dst: ready, psb: link.next];

 SQBRReg← BRConst[CVBR];  * src = CV
 Fetch← Process, Branch[Requeue1];
*-----------------------------------------------------------
Requeue:
* Moves a process from a source queue to a destination queue.
* Enter: SQBRReg = MemBase value for BR containing source QueueHandle;
*  bit 15 = 1 iff source QueueHandle is NIL, meaning the caller
*  doesn't know what queue the process is on.
* DQBRReg = MemBase value for BR containing destination QueueHandle
* Process = PSB to be requeued
* MemBase = PDA
* IndexMask initialized
* Exit: MemBase = PDA
* ALU#0 (for benefit of MesaInterrupt)
* Clobbers T, RTemp0, RTemp1, RTemp2, ShC
*-----------------------------------------------------------
Subroutine;

* Here we compute "prev", the predecessor of process, and put it in RTemp1.
* If psb.link points to itself, queue will be empty after psb is removed.
* IF src#NIL THEN queue ← Fetch[src]^;
* link ← Fetch[@PDA.block[psb].link]^;
* IF link.next = psb THEN prev ← PsbNull ...

 Fetch← Process, Global;
Requeue1:
 RTemp1← shcQ.tail;  * Set shifter for Q.tail masking
 RTemp0← T← (IndexMask) AND MD;
 RTemp2← MD, PD← (Process)#T; * RTemp2 = link, will be used later
 RTemp1← A0, ShC← RTemp1, Branch[RequeueGotPrev, ALU=0];

* Search for predecessor. Start at psb if sq is NIL, else at sq.tail.
* ELSE BEGIN prev ← IF sq=NIL THEN psb ELSE queue.tail;
* DO temp ← Fetch[@PDA.block[prev].link]^; IF temp.next = psb THEN EXIT;
* prev ← temp.next; ENDLOOP;
* temp.next ← link.next; Store[@PDA.block[prev].link]^ ← temp; END;

 MemBase← SQBRReg, Branch[.+2, R odd]; * Branch if src=NIL
 Fetch← 0S;   * queue ← Fetch[src]^
 T← (IndexMask) AND MD, MemBase← PDA; * T = psb.next or queue.tail
RequeueSearchSQ:
 RTemp1← Fetch← T;  * T = potential predecessor
 T← (IndexMask) AND MD;
 PD← (Process)#T;
 Branch[RequeueSearchSQ, ALU#0];

* Now RTemp1 has predecessor of process, and MD has its link word.
* Bypass process in queue structure.
 T← ShMDBothMasks[RTemp0]; * Insert RTemp0 = psb^.link.next
 Store← RTemp1, DBuf← T;

* RTemp1 = predecessor of process, or 0 if there wasn't one.
* RTemp0 = psb^.link.next.
* IF src=NIL THEN
* BEGIN flags ← Fetch[@PDA.block[psb].flags]^; flags.cleanup ← link.next;
* Store[@PDA.block[psb].flags]^ ← flags; END;
* ELSE IF queue.tail = psb THEN
* BEGIN queue.tail ← prev; Store[src]^ ← queue; END;

RequeueGotPrev:
 T← A0, MemBase← SQBRReg, Branch[.+3, R even]; * Branch if src#NIL
 T← (Process)+1, MemBase← PDA; * Know flags is word 1
 RTemp1← Fetch← T, Branch[StoreQueueOrCleanup];

 Fetch← T, RTemp0← RTemp1; * Fetch[src]^; RTemp0← prev
 T← (IndexMask) AND (MD);
 PD← (Process)#T;
 RTemp1← A0, Branch[BeginInsertDQ, ALU#0];

* In the following multi-purpose code, either:
* MemBase = PDA, RTemp1 = @psb.flags, MD = psb.flags, and RTemp0 = link.next;
* or: MemBase = SQBR, RTemp1 = 0, MD = src^, and RTemp0 = prev.
StoreQueueOrCleanup:
 T← ShMDBothMasks[RTemp0]; * Insert into Q.tail or flags.next
 Store← RTemp1, DBuf← T;

* Now begin to insert process into dst queue. First, test for the easy case
* in which the dst queue is initially empty. RTemp2 still contains psb^.link.
* queue ← Fetch[dst]^; link ← Fetch[@PDA.block[psb].link]^;
* IF queue.tail = PsbNull THEN
* BEGIN link.next ← psb; Store[@PDA.block[psb].link]^ ← link;
* queue.tail ← psb; Store[dst]^ ← queue; END;

BeginInsertDQ:
 T← A0, MemBase← DQBRReg;
 RTemp0← Fetch← T;  * Fetch[dst]^
 RTemp1← (IndexMask) AND MD;
 T← ShMDBothMasks[Process], Branch[CheckDQTail, ALU#0];

 Store← RTemp0, DBuf← T;  * Queue empty, make it point to psb
 T← Process, MemBase← PDA, * Link psb to itself
  Branch[RequeueStorePSBLink];

* dst is non-empty. Begin by testing whether process's priority is <=
* the priority of the current tail process, and if so just append process.
* RTemp1 points to current tail process; RTemp2 = psb^.link.
* ELSE BEGIN prev ← queue.tail; temp ← Fetch[@PDA.block[prev].link]^;
* IF temp.priority >= link.priority THEN
* BEGIN queue.tail ← psb; Store[dst]^ ← queue; END;

CheckDQTail:
 RTemp0← T, MemBase← PDA; * In case psb becomes new tail (below)
 Fetch← RTemp1, T← PSBL.priority;
 RTemp2← (RTemp2) AND T;  * Extract psb^.link.priority
 T← T AND MD;   * Extract prev^.link.priority
 PD← T-(RTemp2);
 T← (IndexMask) AND MD, Branch[SearchDQLoop, ALU<0];

* Process priority <= tail priority. Just make process be new tail.
 T← A0, MemBase← DQBRReg;
 Store← T, DBuf← RTemp0;  * Make queue point to psb
 MemBase← PDA, Branch[RequeueStorePrevLink]; * Link it into the queue

* Must search queue to find proper place to insert process.
* Find prev = last process whose priority is >= that of psb.
* At this point, RTemp1 points to tail process, and MD = its link word,
* which is a pointer to the head process. T = temp.next.
* ELSE DO next ← Fetch[@PDA.block[temp.next].link]^;
* IF link.priority > next.priority THEN EXIT;
* prev ← temp.next; temp ← next; ENDLOOP;

SearchDQLoop:
 RTemp0← Fetch← T;
 T← PSBL.priority;
 T← T AND MD;
 PD← (RTemp2)-T-1;
 T← (IndexMask) AND MD, Branch[RequeueStorePrevLink, ALU>=0];
 RTemp1← RTemp0, Branch[SearchDQLoop];

* Now RTemp1 = prev = the process after which we are to insert.
* link.next ← temp.next; Store[@PDA.block[psb].link]^ ← link;
* temp.next ← psb; Store[@PDA.block[prev].link]^ ← temp; END;

RequeueStorePrevLink:
 Fetch← RTemp1;   * Insert Process into prev^.link.next
 T← ShMDBothMasks[Process];
 Store← RTemp1, DBuf← T, T← MD; * T← former prev^.link

* Insert T into psb^.link.next and return.
RequeueStorePSBLink:
 RTemp0← T, Fetch← Process; * Know link is word 0
 T← ShMDBothMasks[RTemp0];
 PD← Store← Process, DBuf← T, Return; * ALU#0
*-----------------------------------------------------------
CleanUpCVQueue:
* Cleans up CV queue that may have been left in a mess by Requeue.
* Enter: MemBase = CVBR
* CVBR points to condition variable c
* T = 0
* IndexMask initialized
* Exit: MemBase = CVBR
* ALU = Process = c^.tail (as modified by subroutine, if necessary)
* RTemp0 = c^ (modified, if necessary; tail field may not be valid)
* Clobbers T, RTemp0, RTemp1
*-----------------------------------------------------------
Subroutine;

* If CV queue is empty then do nothing.
* cond ← Fetch[c]^; psb ← cond.tail; IF psb # PsbNull THEN ...
 T← IndexMask, Fetch← T, Global;
* Force write-protect fault to occur here if it's going to occur at all.
 Store← 0S, Process← DBuf← MD;
 Process← T← (Process) AND T, MemBase← PDA;
 T← T+1, RTemp0← MD, Branch[.+2, ALU#0]; * RTemp0← cond
CleanupCVReturn:
 PD← Process, MemBase← CVBR, Return; * Queue is empty, nothing to do

* If head PSB's cleanup link is nil then do nothing.
* BEGIN flags ← Fetch[@PDA.block[psb].flags]^;
* IF flags.cleanup # PsbNull THEN ...
 T← IndexMask, Fetch← T;  * psb^.flags -- know flags are word 1
 RTemp0← (RTemp0) AND NOT T; * cond.tail ← PsbNull (for later)
 PD← (IndexMask) AND MD;
SearchForCVQHead:
 T← (IndexMask) AND MD, Branch[.+2, ALU#0]; * T← psb^.flags.cleanup
 PD← Process, MemBase← CVBR, Return; * No cleanup queue, nothing to do

* Process has a psb to check, and ALU = T = psb^.flags.cleanup.
* Follow cleanup links until we either hit NIL or
* find a PSB with a cleanup link pointing to itself.
* BEGIN DO IF flags.cleanup = psb then
* BEGIN cond.wakeup ← FALSE; cond.tail ← PsbNull; Store[c]^ ← cond;
* RETURN; END;
* psb ← flags.cleanup; flags ← Fetch[@PDA.block[psb].flags]^;
* IF flags.cleanup = PsbNull THEN EXIT;
* ENDLOOP;
 PD← (Process)#T, Branch[FoundCVQHead, ALU=0];
 T← T+1, Branch[MakeCVQEmpty, ALU=0];
 Process← (Fetch← T)-1, Branch[SearchForCVQHead]; * ALU#0 guaranteed

* Found a PSB with a cleanup link pointing to itself. This can happen
* if a PSB is Requeued when it is the only PSB on the CV queue.
* Reset the CV queue to empty.
MakeCVQEmpty:
 RTemp0← (RTemp0) AND NOT (CV.wakeup);
 Process← T← A0, Branch[StoreCVQueue];

* Found a PSB with a NIL cleanUp link, and not the first in the cleanUp queue.
* Put it at the head of the CV queue. Since queues are circular and rooted
* through their tail, this requires chasing down the queue to find the tail.
* head ← psb;
* DO link ← Fetch[@PDA.block[psb].link]^; IF link.next = head THEN EXIT;
* psb ← link.next; ENDLOOP;
FoundCVQHead:
 RTemp1← T← Fetch← Process; * Remember head, fetch psb^.link
SearchForCVQTail:
 Process← T;
 T← (IndexMask) AND MD;  * T← psb^.link.next
 PD← (RTemp1)#T;
 Fetch← T, Branch[SearchForCVQTail, ALU#0];

* Process = new tail PSB; RTemp0 = cond with tail field zeroed out.
* cond.tail ← psb; Store[c]^ ← cond; END; END;
 T← Process;
StoreCVQueue:
 RTemp0← (RTemp0) OR T, MemBase← CVBR;
 Store← 0S, DBuf← RTemp0, Branch[CleanupCVReturn];
*-----------------------------------------------------------
IFUR[IWDC, 1, L]; * Increment Wakeup Disable Counter: wdc ← wdc+1;
*-----------------------------------------------------------

 T← (WDC)+1, Branch[CheckValidWDC];
:IfMEP;
 Stack← MD, T← (WDC)+1, Branch[CheckValidWDC];
 StkP+1, Branch[.-2];
:EndIf;
*-----------------------------------------------------------
IFUR[DWDC, 1, L]; * Decrement Wakeup Disable Counter: wdc ← wdc-1;
* Note: one instruction must be executed after DWDC before an interrupt may occur.
*-----------------------------------------------------------

 PD← NWW, NoReschedule, DblBranch[DWDCRescPend, DWDCNoRescPend, Reschedule];
:IfMEP;
 Stack← MD, Branch[.-1];
 StkP+1, Branch[.-2];
:EndIf;

* If a Reschedule was already pending, we must restart the IFU in order to
* prevent a reschedule trap from occurring on the next IFUJump, even though
* we have already done a NoReschedule.
DWDCRescPend:
 T← (ID)-(PCX')-1;
 PD← NWW, PCF← T;
DWDCNoRescPend:
 T← (WDC)-1, Branch[.+2, ALU=0]; * Any interrupt requests in?

* Actually, should Reschedule only if WDC=0 now, but incrementing WDC
* more than once is uncommon, and MesaReschedTrap will sort this out anyway.
* Note that Reschedule doesn't take effect until the second successful IFUJump.
 PD← T, Reschedule;
CheckValidWDC:
 Branch[.+2, ALU<0];
 WDC← T, IFUNext0;  * Normal exit
 T← sWakeupError, Branch[SavePCAndTrap]; * Over-incremented/decremented
*-----------------------------------------------------------
MesaFault: * Suspend current process and invoke fault handler
* Enter: T = FaultVector offset = 2*FaultIndex for fault handler
* FaultParam0, FaultParam1 = fault parameters, if any
* Exit: Puts current process on fault queue, awakens handler, and
* performs a Reschedule.
*-----------------------------------------------------------
TopLevel;

 Q← XferFlags,   * Preserve XferFlags (=IndexMask)
  Branch[.+2, R<0]; * Branch if xf.invalidContext
 RestoreStkP;

* Requeue[dst: @PDA.fault[fi].queue, src: @PDA.ready, psb: PSB];

 MemBase← MLBR;
 RTemp0← T+(PDA.fault);
 RTemp1← PDAHi;
 RTemp0← (BRLo← RTemp0)+1; * MLBR← @PDA.fault[fi].queue
 BRHi← RTemp1;
 T← CurrentPSB, MemBase← CVBR; * Process← PSB to requeue
 BRLo← RTemp0;   * CVBR← @PDA.fault[fi].condition
 BRHi← RTemp1;
 DQBRReg← BRConst[MLBR];  * Destination is fault queue
 SQBRReg← BRConst[RQBR];  * Source is ready queue
 IndexMask← And[Q.tail!, 177400]C;
 IndexMask← (IndexMask) OR (And[Q.tail!, 377]C);
 Process← T, MemBase← PDA,
  Call[Requeue];  * Move CurrentPSB to fault queue

* NotifyWakeup[@PDA.fault[fi].condition];
* PC ← savedPC; -- done by hardware
* SP ← savedSP; -- done by RestoreStkP (above)
* Reschedule[preemption: TRUE];

 Call[NotifyWakeup];  * CVBR points to CV already
 XferFlags← Q, Branch[NoMoreInts]; * Recover XferFlags, go preempt current process

*-----------------------------------------------------------
* These are exception entries for the Mesa instruction set
*-----------------------------------------------------------
Set[MesaTrapBase, Sub[300, LShift[MesaInsSet, 6]]];
DontKnowRBase;
TopLevel;

MesaIFUNotReady:
 At[MesaTrapBase, 34], IFUNext0;
:IfMEP;
 At[MesaTrapBase, 35], T← Stack&-1← MD, IFUNext2;
 At[MesaTrapBase, 36], IFUNext2;
:EndIf;
*-----------------------------------------------------------
* Reschedule trap and interrupt processing
*-----------------------------------------------------------

MesaReschedTrap:
 At[MesaTrapBase, 14], RBase← RBase[WDC], Branch[MesaResched1];
:IfMEP;
 At[MesaTrapBase, 15], Stack← MD, RBase← RBase[WDC], Branch[MesaResched1];
 At[MesaTrapBase, 16], StkP+1, RBase← RBase[WDC], Branch[MesaResched1];
:EndIf;

* Immediately reset the Reschedule trap condition and restart the IFU, so
* that if we subsequently decide there is nothing to do (either here or
* later), we can simply exit with an IFUNext0.
MesaResched1:
 PD← WDC, NoReschedule;
 T← NOT (PCX'), Branch[MesaIntDisabled, ALU#0];
 T← NWW, PCF← T;   * NWW#0 means interrupt pending
MesaReschedTest:
 NWW← (NWW) AND NOT T,  * Clear interrupts we will process
  Branch[MesaInterrupt, ALU#0];
 IFUNext0;   * No interrupt, resume execution

MesaIntDisabled:
 T← A0, PCF← T, Branch[MesaReschedTest];
*-----------------------------------------------------------
MesaInterrupt:
* Enter: T = interrupt request mask
* CurrentPSB#0 if interrupted from normal emulation; CurrentPSB=0 if from idle loop.
* WDC=0
*-----------------------------------------------------------

* Make a special test for an interrupt occurring on the timer channel
* (60 hz), and do CheckForTimeouts when one occurs.
 IndexMask← And[Q.tail!, 177400]C;
 IndexMask← (IndexMask) OR (And[Q.tail!, 377]C);
 WDC← T;
 DQBRReg← A0, MemBase← PDA, Branch[CheckForTimeouts, ALU<0];

* For each interrupt that is pending, check the naked CV in the corresponding
* slot in the PDA.interrupt array.
* RTemp3 keeps the PDA-relative pointer, and WDC is used to shift the
* interrupt request bits.
* WW: ARRAY InterruptLevel OF BIT;
* FOR level DECREASING IN InterruptLevel DO
* IF WW[level]#0 THEN
* requeue ← requeue OR NotifyWakeup[@PDA.interrupt[level].condition];

CheckNakedCVs:
 RTemp3← Add[PDA.interrupt!, 40]C, Branch[CheckNakedCVLoop];

NakedCVWakeup:
 BRLo← RTemp3, Call[NotifyWakeup]; * Returns with ALU#0
CheckNakedCVLoop:
 RTemp3← (RTemp3)-(2C), Branch[NoMoreInts, ALU=0];
 WDC← (WDC) RSH 1, Branch[.-1, R even];

 MemBase← CVBR;   * Interrupt for this CV
 T← PDAhi;
 BRHi← T, Branch[NakedCVWakeup];

* All interrupt requests have been processed, and WDC=0 again.
* If we were interrupted from normal emulation (CurrentPSB#0), do a Reschedule iff
* the interrupt actually caused any processes to be requeued.
* If we were interrupted from the idle loop (CurrentPSB=0), go reconsider the ReadyQ.
NoMoreInts:
 PD← CurrentPSB, MemBase← PDA;
 RTemp1← A0, DblBranch[MesaReschedule1, IdleReschedule, ALU#0];
*-----------------------------------------------------------
CheckForTimeouts:
* This is invoked every time an interrupt is requested on the
* 60 hz timer channel. This microcode "knows" which channel the
* Mesa system enables for that purpose!
* Enter: MemBase = PDA
* Exit: MemBase = PDA
* Clobbers T, RTemp0-3, Process, DQBRReg, SQBRReg
*-----------------------------------------------------------

* Scan PSBs only every third tick
 RBase← RBase[TickCount];
 TickCount← (TickCount)-1, RBase← RBase[RTemp0];
 T← Fetch← PDA.count, Branch[.+2, ALU=0];
 Branch[CheckNakedCVs];

* count ← Fetch[@PDA.count]^;
* FOR psb IN [StartPsb..StartPsb+count) DO
* timeout ← Fetch[@PDA.block[psb].timeout]^;
* IF timeout#0 AND timeout=CurrentTime THEN
* BEGIN flags ← Fetch[@PSB.block[psb].flags]^;
* flags.waiting ← FALSE; Store[@PDA.block[psb].flags]^ ← flags;
* Store[@PDA.block[psb].timeout]^ ← 0;
* Requeue[src: 0, dst: @PDA.ready, psb:psb];
* END; ENDLOOP

 TickCount← T+T+1;  * = 3 (know PDA.count=1)
 CurrentTime← (CurrentTime)+1;
 T← Add[PDA.psbs!, PSB.timeout!]C, * Timeout word of first PSB
  RTemp3← MD;  * Use this to count PSBs

* T points to PSB timeout word
CheckTimerLoop:
 Process← (Fetch← T)-(PSB.timeout);
 PD← MD;
 PD← (CurrentTime)-MD, Branch[CanTimeout, ALU#0];
 RTemp3← (RTemp3)-1;
NoTimeout:
 T← (Process)+(Add[PSB.timeout!, sizePSB!]C),
  Branch[CheckTimerLoop, ALU#0];
 Branch[CheckNakedCVs];

CanTimeout:
 RTemp3← (RTemp3)-1, Branch[NoTimeout, ALU#0];

* This process has timed out. T = @psb.timeout
 RTemp0← T+(Sub[PSB.flags!, PSB.timeout!]C);
 Fetch← RTemp0;   * Fetch psb.flags
 Store← T, DBuf← 0C, T← MD; * psb.timeout ← 0
 T← T AND (Not[PSBF.waiting!]C);
 Store← RTemp0, DBuf← T;
 DQBRReg← BRConst[RQBR];  * dst = ReadyQ
 SQBRReg← T-T-1, Call[Requeue]; * src = NIL
 PD← RTemp3, Branch[NoTimeout];
*-----------------------------------------------------------
Requeue&Resched:
* Do a Requeue followed by a Reschedule.
* Arguments as for Requeue.
*-----------------------------------------------------------
 MemBase← PDA, Call[Requeue];
*-----------------------------------------------------------
MesaReschedule: * PrincOps Reschedule operation
* Also includes SaveProcess, LoadProcess, AllocState, FreeState, and
* part of Fault, suitably flattened out into in-line code.
* Process/monitor opcodes normally branch here when they are done.
* Can get here at the end of interrupt processing also.
* Enter: DQBRReg[0] = 1 iff a Requeue has been done.
* Exit: Resumes execution of this or some other process.
*-----------------------------------------------------------
TopLevel;

 RTemp1← ID;   * RTemp1← IL

* Here RTemp1 = 0 if came from MesaInterrupt or MesaFault; = IL if came from
* process/monitor opcode. Note that IL = 1 for all process/monitor opcodes;
* therefore "RTemp1 even" is synonymous with "preemption".
* IF ~reschedulePending THEN RETURN;

MesaReschedule1:
 MemBase← PDA, DQBRReg, Branch[.+2, R<0];
 IFUNext0;   * No Requeue, resume normal execution
*-----------------------------------------------------------
* Save state of current process.
* Can get here due to a Fault, after requeueing CurrentPSB onto a fault queue.
*-----------------------------------------------------------
* link ← Fetch[@PDA.block[PSB].link]^;
* IF ValidContext[] THEN StoreMDS[@LocalBase[L].pc]^ ← PC;
* IF (link.vector ← preemption) THEN
* BEGIN state ← AllocState[link.priority]; SaveStack[state];
* Store[@state.frame]^ ← L; Store[@PDA.block[PSB].context]^ ← state; END
* ELSE Store[@PDA.block[psb].context]^ ← L;
* Store[@PDA.block[PSB].link]^ ← link;
* Validity of context is indicated by XferFlags[xf.invalidContext], which
* is checked by SavePCInFrame.
Call[SwitchStats];
Fetch← CurrentPSB;  * Know PSB.link is word 0
T← MD, RTemp1← (RTemp1)-(PCX')-1, * PCX or PCX+IL
Branch[NotPreemption, R odd];
* Here to preempt current process (possibly due to a fault).
* AllocState: need to allocate a StateVector.
* state ← Fetch[@PDA.state[priority]]^; IF state=NIL THEN ERROR;
* Store[@PDA.state[priority]]^ ← Fetch[state]^;
* Careful not to clobber RTemp3 (=FaultParam1) until it is stored.
* Must call SaveState before removing StateVector from list, because SaveState
* might generate a StackError trap (during interrupts only, never during faults).
 RTemp2← T OR (PSBL.vector);
 RTemp4← LDF[T, 3, 14];  * Extract link.priority
 RTemp4← (RTemp4)+(PDA.state);
 Fetch← RTemp4,   * Fetch head of state list
  Call[SavePCInFrame]; * Returns L in T
 RTemp0← PD← MD, MemBase← PDA; * RTemp0← head StateVector
 DLink← T, Branch[NoStateVector, ALU=0]; * Branch if list empty
 Fetch← RTemp0;   * Fetch successor
 Call[SaveState];  * Save stack, StkP, DLink; doesn't clobber MD
 Store← RTemp4, DBuf← MD; * Store successor as head

* The following is really part of FaultTwo rather than SaveProcess:
* Store[@state.data[0]]^ ← LowHalf[parameter]; -- = FaultParam0
* Store[@state.data[1]]^ ← HighHalf[parameter]; -- = FaultParam1
 T← (Store← T)+1, DBuf← FaultParam0;
 Store← T, DBuf← FaultParam1,
  Branch[EndSaveProcess]; * Use RTemp0 = state as context

NoStateVector:
 Breakpoint, Branch[.];  * No state vector for preempted process

* Here if preemption not in progress: just save PC and use L as context.
NotPreemption:
 RTemp2← T AND NOT (PSBL.vector),
  Call[SavePCInFrame]; * Returns L in T
 RTemp0← T, MemBase← PDA;

* Together again here.
* Now RTemp0 = context (frame or state vector); RTemp2 = updated PSB.link.
EndSaveProcess:
 T← CurrentPSB;
 Store← T, DBuf← RTemp2;
 T← T+(PSB.context);
 Store← T, DBuf← RTemp0;
*-----------------------------------------------------------
* Check ready queue for new process to run.
* Can get here after saving current process (above), or after
* an interrupt from BusyWait (below).
* A process is runnable if either it already has a StateVector (i.e., the
* process was preempted) or a free StateVector is available at the
* process's priority level.
*-----------------------------------------------------------
* queue ← Fetch[@PDA.ready]^; IF queue.tail = PsbNull THEN GOTO BusyWait;
* link ← Fetch[@PDA.block[queue.tail].link]^;
* DO psb ← link.next; link ← Fetch[@PDA.block[psb].link]^;
* IF link.vector OR ~EmptyState[link.priority] THEN EXIT;
* IF psb = queue.tail THEN GOTO BusyWait;
* ENDLOOP;
* EXITS BusyWait =>
* BEGIN IF WDC#0 THEN RescheduleError[]; running ← FALSE; RETURN; END;

IdleReschedule:
 Fetch← PDA.ready;  * Fetch queue
 IndexMask← And[Q.tail!, 177400]C;
 IndexMask← (IndexMask) OR (And[Q.tail!, 377]C), T← MD;
 RTemp0← Fetch← T;  * Fetch link word of tail PSB
 T← (IndexMask) AND MD, FreezeBC;
FindRunnableProcess:
 CurrentPSB← Fetch← T, Q← T, * Fetch link word of next PSB
  Branch[BusyWait, ALU=0]; * Branch if queue empty or hit tail
 RTemp2← MD;
 T← LDF[RTemp2, 3, 14];  * T← link.priority
 RTemp2← T+(PDA.state),  * See if StateVector is available
  Branch[LoadPreempted, R odd]; * Branch if link.vector=TRUE
 Fetch← RTemp2, RTemp2← T← B← MD;
 T← (IndexMask) AND T;  * T← next PSB
 PD← MD;
 PD← (RTemp0)-Q,   * See if CurrentPSB = queue.tail
  DblBranch[LoadNonPreempted, FindRunnableProcess, ALU#0];

* Ready queue is empty, or there are no runnable processes.
* The only way out of this state is to get a naked notify (or a timeout)
* on a CV that some process is waiting on. Therefore, spin here until
* an interrupt request is raised. (This is our implementation of setting
* the "running" flag to FALSE and returning.)
BusyWait:
 PD← WDC;
 T← sWakeupError, Branch[.+2, ALU=0];
 Branch[MTrap];   * It's an error to be idle with WDC#0

 CurrentPSB← A0;   * Indicate we are idle
 T← NWW, NoReschedule, Branch[., ALU=0];
 NWW← (NWW) AND NOT T,  * Clear interrupts about to be caused
  Branch[MesaInterrupt]; * CurrentPSB=0 => will return to IdleReschedule
*-----------------------------------------------------------
* Run preempted process.
* CurrentPSB = process to run; RTemp2 = @PDA.state[link.priority].
*-----------------------------------------------------------
* state ← Fetch[@PDA.block[PSB].context]^
* LoadStack[state]; L ← Fetch[@state.frame];
* FreeState[link.priority, state];

LoadPreempted:
 T← (CurrentPSB)+(PSB.context);
 Fetch← T,   * PSB.context = StateVector
  XferFlags← xf.invalidContext; * xf.free← 0, xf.push← 0
 RTemp0← MD, Call[LoadStack]; * Load stack, StkP, DLink

* FreeState:
* Store[state]^ ← Fetch[@PDA.state[priority]]^;
* Store[@PDA.state[priority]]^ ← state;

 T← Fetch← RTemp2;
 Store← T, DBuf← RTemp0, T← MD;
 Store← RTemp0, DBuf← T, Branch[XferToNewProcess];
*-----------------------------------------------------------
* Run non-preempted process.
* CurrentPSB = Q = process to run; RTemp2 = PSB.link.
*-----------------------------------------------------------
* L ← Fetch[@PDA.block[PSB].context];
* IF link.failed THEN
* BEGIN Push[FALSE]; link.failed ← FALSE;
* Store[@PDA.block[psb].link]^ ← link; END;

LoadNonPreempted:
 T← (CurrentPSB)+(PSB.context);
 Fetch← T,   * PSB.context = frame
  XferFlags← xf.invalidContext; * xf.free← 0, xf.push← 0
 DLink← MD, T← RTemp2, Branch[XferToNewProcess, R>=0];
 T← T AND NOT (PSBL.failed), StkP+1;
 Store← Q, DBuf← T, Stack← A0; * Store updated link; Push[FALSE]

* Preempted and non-preempted cases together here. DLink = new local frame.
* Set the new local frame now so that if a fault occurs during the
* subsequent Xfer, the correct context is saved.
XferToNewProcess:
 MemBase← L;
 BRLo← DLink;
 SLink← A0, MemBase← MDS, Branch[Xfer];