*-----------------------------------------------------------
Title[Xfer.mc...November 23, 1982  4:37 PM...Taft];
* Control transfers (PrincOps, chapter 9)
*-----------------------------------------------------------
%

	CONTENTS, by order of occurence

Control links
	D0		Descriptor Zero
	DB		Descriptor Byte
	DBS		Descriptor Byte Stack

Frame allocation
	AllocFrame	Allocate frame subroutine
	FreeFrame	Free frame subroutine
	AF		Allocate Frame
	FF		Free Frame

Control transfer primitive
	Xfer		Control transfer primitive
	XferProcCont	Xfer to procedure given starting PC (used by LFC)

Control transfer instructions
	LFCn		Local Function Call n
	LFCB		Local Function Call Byte
	EFCn		External Function Call n
	EFCB		External Function Call Byte
	SFC		Stack Function Call
	KFCB		Kernel Function Call Byte
	LKB		Link Byte
	RET		Return
	PO		Port Out
	POR		Port Out Responding
	PI		Port In
	LLKB		Load Link Byte
	RKIB		Read Link Indirect Byte
	RKDIB		Read Link Double Indirect Byte

Traps
	TrapParamDLink	TrapOne with DLink as trap parameter
	TrapParamSLink	TrapOne with SLink as trap parameter
	SavePCAndTrap	Save PC in frame before trapping
	MTrap		Trap with no additional actions
	SaveStack	Save stack in StateVector subroutine
	LoadStack	Load stack from StateVector subroutine
	DSTK		Dump Stack
	LSTF		Load State and Free
	LSTE		Load State and Enable
	BRK		Breakpoint

Subroutines
	SavePCInFrameIL	Save PCX+IL in local frame
	SavePCInFrame	Save arbitrary PC in local frame
	FetchLink	Fetch external link given index in ID
	LoadGFCB	Load Global Frame and Code Base registers


Additional possible improvements:

1. Dynamically replace instruction at XferExitEven with one at XferSetLFNextOp
when XferTraps are disabled (XTS=0).  Saves 1 cycle on every Xfer.

2. At XferProcCont, play shifting and masking tricks to compute AV+fsi rather than
just fsi, and arrange AllocFrame to take AV+fsi as argument.  Saves 1 cycle on
every call and also benefits FF; requires a major rearrangement in register usage.

2. Keep a count of the depth of nesting of "simple" calls (LFC, EFC, SFC to proc),
and perform the corresponding "simple" RETs in-line without any dispatches and without
concern for any possibility of traps or faults.  Non-simple Xfers and certain other
operations (e.g., stores into overhead words) have to zero the simple call count.
Saves 6 or 7 cycles on every simple RET, at the expense of 1 cycle during every
simple call.
%

*-----------------------------------------------------------
IFUR[D0, 1, MemBase[MDS], N[0]];		* Descriptor Zero
* ProcDesc: TYPE = [gfi (0..9), epi (10..14), tag (15)];
* word: GlobalWord ← FetchMds[@GlobalBase[GF].word]↑;
* Push[ProcDesc[gfi: 0, epi: 0, tag: 1]];

IFUR[DB, 2, MemBase[MDS]];			* Descriptor Byte
* ProcDesc: TYPE = [gfi (0..9), epi (10..14), tag (15)];
* evi: EVIndex = GetCodeByte[]/2;
* word: GlobalWord ← FetchMds[@GlobalBase[GF].word]↑;
* Push[ProcDesc[gfi: word.gfi+(evi/EPRange), epi: evi MOD EPRange, tag: 1]];
*-----------------------------------------------------------

	T← (GFShadow)+(GF.word), Branch[DBCommon];


*-----------------------------------------------------------
NewOp; ESCEntry[DBS];				* Descriptor Byte Stack
* ProcDesc: TYPE = [gfi (0..9), epi (10..14), tag (15)];
* evi: EVIndex = GetCodeByte[]/2; frame: GlobalFrameHandle ← Pop[];
* word: GlobalWord ← FetchMds[@GlobalBase[frame].word]↑;
* Push[ProcDesc[gfi: word.gfi+(evi/EPRange), epi: evi MOD EPRange, tag: 1]];
*-----------------------------------------------------------

	T← (Stack&-1)+(GF.word);

* Note: the PrincOps implies but does not state explicitly that the alpha byte
* must always be even.  This implementation depends on that.
DBCommon:
	Fetch← T, T← GW.gfi, StkP+1;	* Fetch @GlobalBase[frame].word
	Stack← T AND MD;		* Extract gfi
	Stack← (ID)+(Stack)+1, NextOpcode; * Merge gfi, alpha, tag

*-----------------------------------------------------------
AllocFrame:					* Allocate frame
* Enter: T = fsi
*	MemBase = MDS
* Normal exit: T = frame
* Failure exit: does not return but executes FrameFault[fsi]
* Clobbers T, Q, RTemp2, RTemp3; specifically, preserves RTemp0 and RTemp1
* Timing: 8 cycles normally
*-----------------------------------------------------------
Subroutine;

	RTemp2← T← T+(AV);
AllocRepeat:
	Fetch← T, RTemp3← LShift[AV!, 2]C;
	Q← Link;
TopLevel;
	RTemp3← (RTemp3)+(BDispatch← MD);
Subroutine;
	Link← Q;

DispTable[4, 7, 4];
	T← Fetch← MD, Q← T, Branch[AllocRet];	* 0 good frame
	T← (RTemp2)-(AV), Branch[AllocFail];	* 1 empty; recover original fsi
	T← RSH[RTemp3, 2], Branch[AllocRepeat];	* 2 indirect
	T← (RTemp2)-(AV), Branch[AllocFail];	* 3 undefined; treat same as empty

* Found good frame.  Remove it from the head of its AV chain.
AllocRet:
	Store← T, DBuf← T, RTemp3← MD;	* Dirty new frame in case WP fault
	Store← Q, DBuf← RTemp3, Return;	* Make successor be new head of chain
TopLevel;

AllocFail:
	FaultParam0← T;			* Fault parameter = fsi
	T← qFrameFault, Branch[MesaFault];


*-----------------------------------------------------------
FreeFrame:					* Free frame
* Enter: T = @LocalBase[frame].word = frame-4
*	MemBase = MDS
* Clobbers T, RTemp0
* Timing: 6 cycles
*-----------------------------------------------------------
Subroutine;

	RTemp0← (Fetch← T)-(LF.word);	* Fetch LocalBase[frame].word (containing fsi);
					* RTemp0← frame
	T← RShift[AV!, 10]C;
	T← DPF[T, 10, 10, MD];		* AV+word.fsi (depends on AV being page-aligned)
	Fetch← T;			* Fetch AV[fsi]
	Store← T, T← DBuf← RTemp0, RTemp0← MD; * Store old frame in AV[fsi]
	Store← T, DBuf← RTemp0,	Return;	* Store previous head as old frame's successor


*-----------------------------------------------------------
NewOp; ESCEntry[AF];				* Allocate Frame
* Push[Alloc[Pop[]]];	
*-----------------------------------------------------------

	T← Stack, Call[AllocFrame];
	Stack← T, NextOpcode;


*-----------------------------------------------------------
NewOp; ESCEntry[FF];				* Free Frame
* Free[Pop[]];	
*-----------------------------------------------------------

	T← (Stack&-1)+(LF.word), Call[FreeFrame];
	NextOpcode;

*-----------------------------------------------------------
Xfer:				* Control transfer primitive
* Entry conditions:
*	MemBase = MDS
* 	SLink = source link
*	DLink = dest link
*	XferFlags = free, trap, and storeTrapParam flags as appropriate; push = 0
* Performs complete PrincOps XFER operation, or else traps or faults appropriately.
* Minimum timings for non-trap Xfer (cycles):
* If dest is frame link: 11, plus 7 if dest's GF is different from source's
* If dest is procedure descriptor: 38
* Plus one or more of the following:
*	10 for first indirect link, 5 for each additional one
*	8 if free
*-----------------------------------------------------------

	T← BDispatch← DLink,		* DLink = initial destination link
		DblBranch[XferTagOdd, XferTagEven, R odd], Global;

* T = ALU = control link being dispatched on (from DLink or indirect link).
* Here if tag is 00 (frame) or 10 (indirect).
XferTagEven:
	RTemp0← T+(LF.globallink),	* RTemp0← @LocalBase[LF].globallink
		DblBranch[XferFrame, ZeroDest, ALU#0];

* The following 3 instructions form a dispatch table in which only entries
* 0 (frame) and 2 (indirect) can be reached during the Xfer tag dispatch.
* Entry 1 of the table is reached only by the above conditional branch.
DispTable[3, 7, 4];


*-----------------------------------------------------------
XferFrame:
* dest[14:15]=00: dest link is frame
*-----------------------------------------------------------

	T← (Fetch← RTemp0)+1,		* Fetch @LocalBase[LF].globallink
		Branch[XferFrameCont];

* Destination link = 0 => control trap.  Trap parameter is SLink.
ZeroDest:
	T← TrapWithParam[sControlTrap], Branch[TrapParamSLink];


*-----------------------------------------------------------
XferIndirect:
* dest[14:15]=10: dest link is indirect; interpret as pointer to dest link.
*-----------------------------------------------------------

	XferFlags← (XferFlags) OR (xf.push), * Cause SLink and DLink to be pushed
		Branch[.+2, R odd];	* Branch if xf.free=1
	Fetch← T, Branch[.+2];		* Fetch target
	Fetch← T, SLink← A0;		* Called from RET: make SLink be correct
	RTemp0← MD;
	T← BDispatch← RTemp0, DblBranch[XferTagOdd, XferTagEven, R odd];

* Continuation of frame transfer.
* RTemp0 = @LocalBase[LF].globallink; T = @LocalBase[LF].pc; MD = globallink
XferFrameCont:
	LFShadow← (Fetch← T)+1,		* Fetch new PC; LFShadow← new LF
		T← MD, Q← LFShadow;	* T← new GF; Q← old LF
	RTemp1← MD, PD← (GFShadow)#T;	* RTemp1← new PC; see if global frames are the same
	Store← RTemp0, DBuf← T,		* Dirty local frame to force WP fault if any
		Branch[.+2, ALU=0];	* Skip loading GF and CB if GF is unchanged
	GFShadow← T, MemBase← GF,	* Load GF and CB registers
		Call[LoadGFCB];
	PCF← RTemp1, T← MD,		* Start IFU at new PC; force fault from Store←
		Branch[XferExitDispatch];

*-----------------------------------------------------------
XferTagOdd:
* dest[14:15]=01 or 11: dest link is proc descriptor. 
* dest[0:9] = gfi, dest[10:14] = epi
* Note that the Xfer tag dispatch is still pending and must be squashed.
*-----------------------------------------------------------

	GFShadow← RSH[T, 6];		* Extract gfi
	GFShadow← (GFShadow)+(GFT), DispTable[1, 7, 7];
	Fetch← GFShadow, GFShadow← 177774C; * Fetch gfti from GFT
	RTemp0← LDF[T, 5, 1], T← MD;	* Extract epi
	GFShadow ← (GFShadow) AND T, MemBase← GF; * gfti[0..13],,00 = global frame
	T← DPF[T, 2, 5],		* T← 32*gfti[14..15] = ep bias
		Branch[NullGF, ALU=0];	* global frame = 0 => unbound
	RTemp0← (RTemp0)+(2C);
	RTemp0← (RTemp0)+T,		* RTemp0← bias+epi+2
		Call[LoadGFCB];		* Load GF and CB registers; returns MemBase=CB
	Fetch← RTemp0;			* Fetch PC from entry vector


*-----------------------------------------------------------
XferProcCont:			* Xfer to procedure given starting PC
* Used both by Local Function Calls and by the Procedure case of Xfer.
* Allocates new frame and patches links
* Entry conditions:
*	MD holds starting PC
*	MemBase = CB
*-----------------------------------------------------------

	RTemp1← MD, T← (B← MD) RSH 1;	* Puts unshifted PC thru ALU
	Fetch← T, FlipMemBase,		* Fetch word containing fsi byte; MemBase← MDS
		Branch[ProcUnbound, ALU=0]; * Branch if PC was zero
	RTemp1← (RTemp1)+1, T← MD, Branch[.+2, R odd];
	T← RSH[T, 10], Branch[.+2];	* Extract fsi from CB byte-indexed by PC
	T← T AND (377C);
	PCF← RTemp1, Call[AllocFrame];	* Start IFU at PC+1; allocate new frame

* No page faults are possible after here, so it's ok to load LFShadow now.
	LFShadow← T, Q← LFShadow;	* Set newly-allocated LF, preserve old LF
	T← T+(LF.returnlink);		* T← @LocalBase[LF].returnlink
	T← (Store← T)+1, DBuf← SLink;	* returnlink ← caller's frame
	Store← T, DBuf← GFShadow,	* globallink ← GF
		Branch[XferExitDispatch];

*-----------------------------------------------------------
XferExitDispatch:
* Tail of all Xfer cases.
* Here LFShadow = new frame, Q = old frame, RTemp1 = new PC (already loaded into PCF).
*-----------------------------------------------------------

	BDispatch← XferFlags, Branch[ExitOddDisp, R odd]; * Dispatch on exit actions
	T← LFShadow, MemBase← LF;

* Exit dispatch table for XferFlags even (free=FALSE).  T=LFShadow, MemBase=LF.
* The combination (trap AND push) is impossible, so only cases 0, 2, 4 need be considered.
XferExitEven: DispTable[5];

* No actions: just exit.
	XTS← (XTS) RSH 1,		* [0]
		DblBranch[XferSetLFNextOp, XferTrap, R even];

* push=TRUE: push the links onto the stack
XferExitPush:
	Q← SLink, Branch[.+2];		* [1] can't get here during dispatch
	StkP+2, Branch[.-1];		* [2] Push[dest]; Push[source]; SP ← SP-2
	Stack&-1← Q, Branch[XferPushCont]; * [3] can't get here during dispatch

* trap=TRUE: special handling for trap Xfer.
* If trapping to a frame (as opposed to a procedure), save the source context
* in the returnlinks field (since it otherwise would be lost) and disable interrupts.
	MemBase← MDS, DLink,		* [4] Testing the original DLink is correct,
		Branch[TrapToProc, R odd]; * since trapping thru indirect links is illegal
	RBase← RBase[WDC];
	WDC← (WDC)+1, RBase← RBase[MesaRegsMain];
	T← (LFShadow)+(LF.returnlink);
	Store← T, DBuf← SLink;

* If directed to store a trap parameter, do so.
TrapToProc:
	PD← (XferFlags) AND (xf.storeTrapParam);
	T← LFShadow, Branch[.+2, ALU=0];
	Store← T, DBuf← TrapParam;

* Ensure that if an interrupt is pending, it will not occur between the trap XFER and
* the instruction dispatched to.  This is no longer required by the PrincOps (though
* it was at one time).  Rather, it ensures that if the instruction which trapped was
* dispatched from a Break byte, the Break byte will not be cleared by the 
* interrupt handler until the trap handler has had a chance to save it (with DSTK).
	NoReschedule, Branch[.+2, Reschedule'];
	Reschedule;
	T← LFShadow, MemBase← LF, Branch[XferExitEven];

* Nothing left to do: set LF and dispatch to next opcode.  T=LFShadow, MemBase=LF.
XferSetLFNextOp:
	XferFlags← A0, BRLo← T, NextOpcode;

* Continuation of push=TRUE case.
XferPushCont:
	Q← DLink;
	Stack&-1← Q, Branch[XferExitEven];

* XferExitDispatch (cont'd)

ExitOddDisp:
	T← Q, MemBase← MDS;

* Exit dispatch table for XferFlags odd (free=TRUE).  T=old LF, MemBase=MDS
* The combination (trap AND free) is impossible, so only cases 1 and 3 need be considered.
XferExitOdd: DispTable[3, 3, 1];

* free=TRUE: free the old frame and exit
	T← T+(LF.word), Call[FreeFrame]; * [1]
	T← LFShadow, MemBase← LF,	* [2] can't get here during dispatch
		Branch[XferExitEven];

* push=TRUE, free=TRUE: both free the frame and push the links
	T← T+(LF.word), StkP+2,		* [3]
		Call[FreeFrame];
	T← LFShadow, MemBase← LF,
		Branch[XferExitPush];


*-----------------------------------------------------------
* Traps from Xfer
*-----------------------------------------------------------

* Null GFT entry => unbound trap.  Trap parameter is DLink.
NullGF:
	T← TrapWithParam[sUnboundTrap], Branch[TrapParamDLink];

* Entry PC = 0 => unbound trap.  Trap parameter is DLink.
ProcUnbound:
	T← TrapWithParam[sUnboundTrap], Branch[TrapParamDLink];

* Xfer trap
* Get here at end of Xfer when the low-order bit of XTS is one.
* IF XTS MOD 2 # 0 THEN {
*   word: GlobalWord ← FetchMds[@GlobalBase[GF].word]↑;
*   IF ~word.disablexfertraps THEN {
*     XTS ← XTS/2; Trap[@SD[sXferTrap]]; StoreMds[LF]↑ ← dst}
*     ELSE XTS ← XTS/2};
* DLink = destination link of Xfer (0 if LFC)
* RTemp1 = PC of destination context
* MemBase = LF; LFShadow = new contents of LF (not loaded yet).
* Initiates the trap AFTER the Xfer takes place, so it appears that the trap
* occurred in the first instruction of the destination context.
XferTrap:
	BRLo← LFShadow;
	MemBase← MDS;
	T← (GFShadow)+(GF.word);	* T← @GlobalBase[GF].word
	Fetch← T, T← GW.disablexfertraps;
	PD← T AND MD;
	TrapParam← DLink, Branch[.+2, ALU#0];

* Xfer trap takes.  Note that we are in the new LF context at this point.
	T← TrapWithParam[sXferTrap], Branch[SaveRTemp1AndTrap];

* Xfer trap does not take.  Note that XTS has already been right-shifted one;
* this must be undone before we complete the Xfer.
	XTS← (XTS)+(XTS)+1;
	XferFlags← A0, NextOpcode;

*-----------------------------------------------------------
IFUP[LFC1, 1, MemBase[CB], N[3]];		* Local Function Call n
IFUP[LFC2, 1, MemBase[CB], N[4]];
IFUP[LFC3, 1, MemBase[CB], N[5]];
IFUP[LFC4, 1, MemBase[CB], N[6]];
IFUP[LFC5, 1, MemBase[CB], N[7]];
* LFC[n];

* LFC: PROCEDURE[epi] = {
*   word: BytePair; nPC: CARDINAL; nLF: LocalFrameHandle;
*   StoreMds[@LocalBase[L].pc]↑ ← PC;
*   nPC ← Fetch[@CB.entry[epi].pc]↑; IF nPC=0 THEN UnboundTrap[0];
*   word ← ReadCode[nPC/2]; nLF ← Alloc[IF nPC MOD 2 = 0 THEN word.even ELSE word.odd];
*   StoreMds[@LocalBase[nLF].globallink]↑ ← GF;
*   StoreMds[@LocalBase[nLF].returnlink]↑ ← LF;
*   LF ← nLF; PC ← nPC+1; CheckForXferTraps[] };
* Minimum timing: 26 cycles
*-----------------------------------------------------------

	Fetch← ID, Call[SavePCInFrameIL]; * @CB.entry[epi].pc = CB+epi+2
	DLink← A0, MemBase← CB, Branch[XferProcCont]; * XferFlags = 0


*-----------------------------------------------------------
IFUP[LFCB, 2, MemBase[CB]];			* Local Function Call Byte
* LFC[GetCodeByte[]];
* Minimum timing: 27 cycles
*-----------------------------------------------------------

	T← (ID)+(2C);			* epi+2
	Fetch← T, Call[SavePCInFrameIL];
	DLink← A0, MemBase← CB, Branch[XferProcCont]; * XferFlags = 0


*-----------------------------------------------------------
IFUP[EFC0, 1, MemBase[MDS], N[0]];		* External Function Call n
IFUP[EFC1, 1, MemBase[MDS], N[1]];
IFUP[EFC2, 1, MemBase[MDS], N[2]];
IFUP[EFC3, 1, MemBase[MDS], N[3]];
IFUP[EFC4, 1, MemBase[MDS], N[4]];
IFUP[EFC5, 1, MemBase[MDS], N[5]];
IFUP[EFC6, 1, MemBase[MDS], N[6]];
IFUP[EFC7, 1, MemBase[MDS], N[7]];
IFUP[EFC8, 1, MemBase[MDS], N[10]];
IFUP[EFC9, 1, MemBase[MDS], N[11]];
IFUP[EFC10, 1, MemBase[MDS], N[12]];
IFUP[EFC11, 1, MemBase[MDS], N[13]];
IFUP[EFC12, 1, MemBase[MDS], N[14]];
* StoreMds[@LocalBase[LF].pc]↑ ← PC;
* XFER[dst: FetchLink[n], src: LF];

IFUP[EFCB, 2, MemBase[MDS]];			* External Function Call Byte
* StoreMds[@LocalBase[LF].pc]↑ ← PC;
* XFER[dst: FetchLink[GetCodeByte[]], src: LF];
* Minimum timing: 47 cycles
*-----------------------------------------------------------

	T← (GFShadow)+(GF.word), Call[FetchLink];
	DLink← MD, Call[SavePCInFrameIL]; * Returns with MemBase=MDS
	T← BDispatch← DLink, DblBranch[XferTagOdd, XferTagEven, R odd];

*-----------------------------------------------------------
IFUP[SFC, 1, MemBase[MDS]];			* Stack Function Call
* link: ControlLink ← Pop[]; StoreMds[@LocalBase[LF].pc]↑ ← PC;
* XFER[dst: link, src: LF];
* Minimum timing: 42 cycles if link is procedure descriptor,
* 15 if to frame with same global frame, 22 if to frame with different global frame
*-----------------------------------------------------------

	DLink← Stack&-1, Call[SavePCInFrameIL]; * Returns with MemBase=MDS
	T← BDispatch← DLink, DblBranch[XferTagOdd, XferTagEven, R odd];


*-----------------------------------------------------------
IFUP[KFCB, 2, MemBase[MDS]];			* Kernel Function Call Byte
* StoreMds[@LocalBase[LF].pc]↑ ← PC;
* XFER[dst: FetchMds[@SD[GetCodeByte[]]]↑, src: LF];
* Minimum timing: 44 cycles if link is procedure descriptor,
* 17 if to frame with same global frame, 24 if to frame with different global frame
*-----------------------------------------------------------

	T← (ID)+(SD);
	Fetch← T, Call[SavePCInFrameIL]; * Fetch SD[alpha]
XferMD:
	DLink← MD, MemBase← MDS, Branch[Xfer];


*-----------------------------------------------------------
IFUR[LKB, 2, MemBase[LF]];			* Link Byte
* alpha: BYTE ← GetCodeByte[]; link: ControlLink;
* Recover[]; link ← Pop[];
* StoreMds[LF]↑ ← link-alpha;  -- store in Local 0
* Timing: 3 cycles
*-----------------------------------------------------------

	T← ID, StkP+1;
	T← (Stack&-1)-T;

* This can't fault, because the store is into word 0 of the frame we are running in.
* Therefore can exit with NextOpcode instead of NextOpcodeCF.
	Store← 0S, DBuf← T, NextOpcode;


*-----------------------------------------------------------
IFUP[RET, 1, MemBase[MDS]];			* Return:
* dst: ControlLink ← FetchMds[@LocalBase[LF].returnlink]↑;
* XFER[dst: dst, src: NIL, free: TRUE];
* Minimum timing: 22 cycles if returning to same global frame, 29 if different
*-----------------------------------------------------------

	T← (LFShadow)+(LF.returnlink);
	Fetch← T, XferFlags← xf.free, Branch[XferMD];

* Logically, must set SLink← 0 before Xfer.  However, SLink is not ordinarily needed;
* it is used only if the Xfer goes through an indirect link or generates a
* control fault.  We'll set SLink← 0 in those cases by testing the type of Xfer:
* RET is the only instruction with xf.free=1.  (Actually, that is not strictly true:
* LSTF also sets xf.free=1.  However, a LSTF whose destination is an indirect link
* would be rather bizzarre.  A LSTF which generates a control fault is somewhat
* more likely and is handled correctly at TrapParamSLink.)

*-----------------------------------------------------------
NewOp; ESCEntry[PO];				* Port Out
* port: PortLink ← Pop[];
* StoreMds[@LocalBase[LF].pc]↑ ← PC; StoreMds[@port.inport]↑ ← LF;
* XFER[dst: FetchMds[@port.outport]↑, src: port];
*-----------------------------------------------------------

POTail:
	XferFlags← A0, Call[SavePCInFrameIL]; * Returns with T=LF, MemBase=MDS
	T← (Store← Stack&-1)+1, DBuf← T;
	SLink← (Fetch← T)-1, Branch[XferMD];


*-----------------------------------------------------------
NewOp; ESCEntry[POR],				* Port Out Responding
* Same as PO
*-----------------------------------------------------------

	Branch[POTail];


*-----------------------------------------------------------
NewOp; ESCEntry[PI],				* Port In
* port: PortLink; src: ControlLink;
* Recover[]; Recover[]; src ← Pop[]; port ← Pop[];
* StoreMds[@port.inport]↑ ← 0;
* IF src#0 THEN StoreMds[@port.outport]↑ ← src;
*-----------------------------------------------------------
	
	StkP+2;
	PD← Stack&-1;			* PD← src
	T← Stack&+1, Branch[.+2, ALU=0]; * T← port

* if src#0 then the first instruction stores 0 in port.inport and the second
* instruction stores src in port.outport.  If src=0 then the first instruction
* is not executed and the second stores src (=0) in inport.
	T← (Store← T)+1, DBuf← 0C;
	Store← T, DBuf← Stack&-2, NextOpcodeCF;


*-----------------------------------------------------------
IFUR[LLKB, 2, MemBase[MDS]];			* Load Link Byte
* Push[FetchLink[GetCodeByte[]]];
*-----------------------------------------------------------
	
	T← (GFShadow)+(GF.word), Call[FetchLink];
	Stack+1← MD, NextOpcode;


*-----------------------------------------------------------
IFUR[RKIB, 2, MemBase[MDS]];			* Read Link Indirect Byte
* ptr: POINTER ← FetchLink[GetCodeByte[]]; Push[FetchMds[ptr]↑];
*-----------------------------------------------------------
	
	T← (GFShadow)+(GF.word), Call[FetchLink];
	T← MD, MemBase← MDS, Branch[ExitReadStackT];


*-----------------------------------------------------------
IFUR[RKDIB, 2, MemBase[MDS]];			* Read Link Double Indirect Byte
* ptr: POINTER ← FetchLink[GetCodeByte[]];
* Push[FetchMds[ptr]↑]; Push[FetchMds[ptr+1]↑];
*-----------------------------------------------------------
	
	T← (GFShadow)+(GF.word), Call[FetchLink];
	T← MD, MemBase← MDS;
	T← (Fetch← T)+1, StkP+1, Branch[ExitStackMDReadStackT];

*-----------------------------------------------------------
* Trap sequences:
* Entry conditions:
*	T: trapParamFlag + index in SD through which to trap
*	   trapParamFlag = TrapParamFlag if a trap parameter is to be passed, 0 if not
*	TrapParam = trap parameter to be passed, if any
* Entry points:
*	SavePCAndTrap	saves PC in frame before trapping
*	TrapParamDLink	traps with DLink as trap parameter
*	TrapParamSLink	traps with SLink as trap parameter
*	MTrap		no additional actions
*-----------------------------------------------------------

TrapParamDLink:
	TrapParam← DLink, Branch[SavePCAndTrap];

TrapParamSLink:
	PD← (XferFlags)-1;		* Xfer called from RET? (know xf.free=1)
	Branch[.+2, ALU=0];
	TrapParam← SLink, Branch[SavePCAndTrap]; * No, pass SLink as parameter
	TrapParam← A0, Branch[SavePCAndTrap]; * Yes, SLink was really zero

SavePCAndTrap:
	XferFlags, Branch[.+2, R<0], Global;
	RestoreStkP;			* Do this only if context is valid

SavePCTrapNRStkP:
	RTemp1← NOT (PCX');

SaveRTemp1AndTrap:
	RTemp0← T, Call[SavePCInFrame];	* Save contents of RTemp1 as PC
	T← RTemp0;

MTrap:
	T← T+(SD);
	MemBase← MDS, Branch[DoTrapOne, ALU<0];

DoTrapZero:
	Fetch← T, XferFlags← xf.trap,
		DblBranch[XferMD, TrapCtxInvalid, R>=0];

DoTrapOne:
* Note: the TrapParamFlag is removed by subtracting rather than by ANDing.
* This is because ESC opcode traps are handled by passing a fake SD index
* greater than 377B which actually indexes into ETT rather than SD.
	T← T-(TrapParamFlag);
	Fetch← T, XferFlags← Add[xf.trap!, xf.storeTrapParam!]C,
		DblBranch[XferMD, TrapCtxInvalid, R>=0];

* If current context is invalid, leave it that way so recursive traps work.
TrapCtxInvalid:
	XferFlags← (XferFlags) OR (xf.invalidContext), Branch[XferMD];

*-----------------------------------------------------------
SaveStack:			* Save stack in StateVector
* Enter: RTemp0 holds the address of the StateVector
*	MemBase = whatever is appropriate
*	BreakByte = whatever
* Exit:	State saved
*	StkP = 0
*	BreakByte = 0
*	T = @state.frame = RTemp0+StackDepth+1
* Clobbers Q, RTemp1
*-----------------------------------------------------------
Subroutine;

	RTemp1← T← TIOA&StkP;		* Read StkP -- know TIOA=0 !
	PD← T-(Add[StackDepth, 1]C), StkP+1; * See if valid stack pointer
	T← T+1, StkP+1, Branch[SaveStackBad, ALU>=0];

* Note: must save 2 words beyond TOS.  Save StkP after storing stack so that
* it isn't clobbered if too many stack words are stored.
* PrincOps deviation: this may clobber the State.frame word (one after state.word).
* This is deemed to be harmless.
	T← (RTemp0)+(Cnt← T);		* state[0..SP+1] ← stack[1..SP+2]
	T← (Store← T)-1, DBuf← Stack&-1, Branch[., Cnt#0&-1];

* Note: StkP=0 now.  Save Break and original StkP.
	RBase← RBase[MesaRegsAlt];
	Break← A0, Q← Break;
	RBase← RBase[MesaRegsMain];
	T← (RTemp0)+(Add[StackDepth]C);
	RTemp1← (RTemp1) OR Q;
	T← (Store← T)+1, DBuf← RTemp1, Return; * state.word ← Break ,, SP
TopLevel;

SaveStackBad:
	Branch[StackError];


*-----------------------------------------------------------
LoadStack:			* Load stack from StateVector
* Enter: RTemp0 points to block from which state is to be loaded
*	MemBase = whatever is appropriate
* Exit:	Stack, StkP, and Break loaded
* Clobbers T, RTemp1, Cnt
*-----------------------------------------------------------
Subroutine;

	T← (RTemp0)+(Add[StackDepth]C);	* T← @state.word (containing break,,stkptr)
	Fetch← T, T← A0;
	T← DPF[T, 10, 0, MD], RTemp1← MD; * Break← state.word.break,,0
	Break← T;
	T← (RTemp1) XOR (StkP← T);	* T← 0,,state.word.stkptr; StkP← B[8:15]← 0
	T← RTemp0, Cnt← T;

* Note: must load 2 words beyond TOS.  Stack[1..StkP+2] ← State[0..StkP+1]
	T← (Fetch← T)+1, StkP+1;
	T← (Fetch← T)+1, Stack&+1← MD, Branch[., Cnt#0&-1];
	Stack&-2← MD, Return;		* Leave 2 words above TOS

TopLevel;

*-----------------------------------------------------------
NewOp; ESCEntry[DSTK];				* Dump Stack
* SaveStack[LF+alpha];
*-----------------------------------------------------------

	RTemp0← (ID)+(LFShadow), Call[SaveStack];
	T← MD, NextOpcode;


*-----------------------------------------------------------
NewOp; ESCEntry[LSTF];				* Load State and Free
* LoadState[free: TRUE];
*-----------------------------------------------------------

	XferFlags← Add[xf.lstf!, xf.free!]C, Branch[LoadState];


*-----------------------------------------------------------
NewOp; ESCEntry[LSTE],				* Load State and Enable
* LoadState[free: FALSE]; EnableInterrupts[];
*-----------------------------------------------------------

	XferFlags← A0, Branch[LoadState];


*-----------------------------------------------------------
LoadState:			* Load state from StateVector, and Xfer to new context
* Enter: ID = LF-relative offset of StateVector
*	XferFlags = whatever is appropriate
* Exit:	goes to Xfer after loading stack, StkP, Break, DLink, and SLink.
* state: POINTER TO StateVector ← LOOPHOLE[LF+GetCodeByte[]];
* LoadStack[LengthenPointer[state]];
* IF ~free THEN StoreMDS[@LocalBase[LF].pc]↑ ← PC;
* XFER[dst: FetchMds[@state.frame]↑, src: FetchMds[@state.data[0]]↑, free: free];
* PrincOps deviation: we store the PC unconditionally.  This is harmless.
*-----------------------------------------------------------

	RTemp0← ID, Call[SavePCInFrameIL]; * Returns with T=LF, MemBase=MDS
	RTemp0← (RTemp0)+T, Call[LoadStack];
	T← (RTemp0)+(Add[StackDepth, 2]C);
	T← (Fetch← T)-1,		* @state.data[0] = source link
		XferFlags, Branch[.+3, R odd]; * R even => xf.free=0 => LSTE

* This is the tail of LSTE: EnableInterrupts[];
	RBase← RBase[WDC];
	WDC← (WDC)-1, RBase← RBase[MesaRegsMain];

* Ensure that if an interrupt is pending, it will not occur between the LSTx and
* the instruction dispatched to.  This is no longer required by the PrincOps (though
* it was at one time).  Rather, it ensures that if a Break byte was set by the LSTx,
* it will not be cleared by the interrupt handler until it has had an opportunity
* to be used.  (Presumably the target instruction is a BRK in this case.)
	NoReschedule, Branch[.+2, Reschedule'];
	Reschedule;

	SLink← MD, Fetch← T, Branch[XferMD]; * @state.frame = destination link

*-----------------------------------------------------------
IFUP[BRK, 1, RBase[MesaRegsAlt]];			* Breakpoint
* IF Break=0 THEN BreakTrap[]
* ELSE {Dispatch[Break]; Break ← 0};
*-----------------------------------------------------------

	T← Break, RBase← RBase[MesaRegsMain]; * Does a Break byte exist?
	Branch[.+2, ALU#0];

* No Break byte exists.  This is a breakpoint being encountered during the
* normal interpretation of instructions.  Simply cause a breakpoint trap.
	T← sBreakTrap, Branch[SavePCAndTrap];

* Break byte exists.  We are proceeding from this breakpoint, and should substitute
* the Break byte for the opcode executed at this PC.  Cause an interrupt to occur
* after execution of that opcode; the interrupt handler will zero the Break byte.
* Note that if the instruction does not complete due to a fault or trap, the Break
* byte will not be cleared but rather will be saved as part of the process state.
	NoReschedule;
	Reschedule;
	T← A0, BrkIns← T, Branch[JumpRelativeT];


*-----------------------------------------------------------
SavePCInFrameIL:		* Saves PCX+IL in local frame
* Entry conditions:
*	All ID code bytes must have been consumed already
* Exit conditions:
*	MemBase = MDS
*	SLink and T loaded from LF
* Clobbers T, RTemp1; does not clobber MD
* Timing: 3 cycles
*-----------------------------------------------------------
Subroutine;

	RTemp1← (ID)-(PCX')-1,		* ID=instruction length
		Branch[DoSavePC], Global;


*-----------------------------------------------------------
SavePCInFrame:			* Saves arbitrary PC in local frame
* Entry conditions:
*	RTemp1 = PC to store
* Exit conditions:
*	MemBase = MDS
*	SLink and T loaded from LF
* XferFlags[xf.invalidContext] indicates that the context is
* invalid and the PC should not be stored in the local frame.
* Clobbers T; does not clobber MD
* Timing: 3 cycles normally
*-----------------------------------------------------------
Subroutine;

	MemBase← MDS, XferFlags,	* xf.invalidContext = bit 0
		DblBranch[NoSavePC, DoSavePC, R<0];
DoSavePC:
	T← (LFShadow)-1, MemBase← MDS;	* T← @LocalBase[LF].pc
	SLink← T← (Store← T)+1, DBuf← RTemp1, Return;
NoSavePC:
	SLink← T← LFShadow, Return;

*-----------------------------------------------------------
FetchLink:			* Fetches external link given index in ID
* Entry conditions:
*	MemBase = MDS
*	ID = index of the link
*	T = @GlobalBase[GF].word = GF-3
* Exit:
*	MD = requested control link
*	MemBase = GF or CB, depending on where the links are
* word: GlobalWord ← FetchMds[@GlobalBase[GF].word]↑;
* RETURN[IF word.codelinks THEN Fetch[CB-LONG[index]-1]↑
*   ELSE FetchMds[GlobalBase[GF]-index-1]↑];
* Clobbers T, RTemp0, RTemp1
* Timing: 4 cycles if links are in code, 5 if in global frame
*-----------------------------------------------------------
Subroutine;

	RTemp1← (Fetch← T)-T-1, Global;	* Fetch GF.word
	T← NOT (ID), RTemp0← MD, MemBase← CB; * T← -index-1
	RTemp0← GF.word, DblBranch[GetLinkCode, GetLinkFrame, R odd];

* Links are in frame
GetLinkFrame:
	T← T+(RTemp0), MemBase← GF;	* Skip overhead words

* Links are in code segment.  This case is more common and is accordingly
* favored in this implementation for minimum execution time.
GetLinkCode:
	LongFetch← T, B← RTemp1, Return; * Negative reference: -1,,-index-1


*-----------------------------------------------------------
LoadGFCB:			* Loads global frame and code base registers
* Entry conditions:
*	MemBase = GF
*	GFShadow = new global frame
* Exit conditions:
*	MemBase = CB
* If the code base is odd then does not return but rather generates a CodeTrap.
* Note: may be called with LF and LFShadow out of sync (see XferFrameCont).
* If a CodeTrap is generated, it reloads LFShadow from LF.
* Clobbers T, RTemp2
* Timing: 6 cycles
*-----------------------------------------------------------
Subroutine;

	T← (BRLo← GFShadow)-1;		* Load GF
	T← T-1, MemBase← MDS;		* T← @GlobalBase[GF].codebase
	T← (Fetch← T)+1;		* Low word
	RTemp2← MD, Fetch← T, FlipMemBase; * High word; MemBase← CB
	RTemp2← MD, BRLo← RTemp2,
		Branch[DoCodeTrap, R odd]; * Code base odd => CodeTrap
	BRHi← RTemp2, Return;

TopLevel;

* Generate code trap (i.e., "start trap") if code base is odd.
DoCodeTrap:
	T← A0, MemBase← LF, Call[FetchGetsT]; * Restore correct LFShadow before trapping
	LFShadow← VALo;
	T← TrapWithParam[sCodeTrap], Branch[TrapParamDLink];