*----------------------------------------------------------- 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];