// SwatResident.bcpl - Companion to OS module SwatResident.asm
// Copyright Xerox Corporation 1979, 1982
// Last modified April 11, 1982  4:11 PM by Boggs

get "Swat.decl"
get "AltoDefs.d"

external
[
// outgoing procedures
InitResident
Go; Call; UserScreen
ResidentSwapOut; ResidentSwapIn
ResidentSysOut; ResidentSysIn
StoreVec; PrintSwateeString; DisplayState

// incoming procedures
MapStack; PrintFrame; IsBreak; CheckAllBreaks
AddrToSym; BuildSI; TeleSwatServer
VMStore; VMFetch; VMSwap; VMPrint
UserPrintError; ParityError; SymPrint
ReportFail; Fail; Confirm

Allocate; Free; Zero; Usc
Endofs; Gets; Puts; Resets
ReadBlock; WriteBlock
Ws; Wss; PutTemplate
Enqueue; Dequeue; Unqueue

// outgoing statics
stackDubious

// incoming statics
sysZone
dsp; state
vm
]

static
[
rsQ			// -> queue of RS blocks
swateeDubious		// true if Swatee not runable
stackDubious		// true if Stack unsuitable for ↑c
scm			// -> SCM in Swatee
cvPtr			// free words remaining in codeVec
]

compileif lenSI ne 6 then
   [ Error("Change size of block for SI in Go() and Call()") ]

//----------------------------------------------------------------------------
structure RS:		// Resident's State
//----------------------------------------------------------------------------
[
link word		// must be first
state word lenState	// 700-707
trapPC word		// trapPC is disturbed by Swat mechanism
scm word lenSCM		// the whole SCM
]
manifest lenRS = size RS/16

//----------------------------------------------------------------------------
let InitResident() be [ rsQ = Allocate(sysZone, 2); rsQ!0 = 0 ]
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and Go(addr) = valof
//----------------------------------------------------------------------------
// Returns a vector of code which resumes execution of the user program,
//  with the first instruction executed being that at addr.
// If addr is broken, we simulate the missing instruction and resume
//  execution at its next address, otherwise we simulate a nop at
//  addr-1 and resume execution at addr.
[
let cv = table
   [
   9		//number of words that follow
   4407b	//jsr .+7
   0		//space for SI -- *** known to be 6 words long ***
   0
   0
   0
   0
   0
   171000b	//mov 3 2
   64767b	//jsrii .-11 (SimIns)
   ]
test IsBreak(addr) % (VMFetch(addr) & 177400b) eq 77400b
   ifso
      [
      BuildSI(cv+2, VMFetch(addr))  //simulate broken instr
      VMStore(userPC, addr)
      ]
   ifnot
      [
      BuildSI(cv+2, 101000b)  //yes. 101000b = Mov 0 0
      VMStore(userPC, addr-1)  //simMA increments userPC
      ]
resultis cv
]

//----------------------------------------------------------------------------
and UserScreen() = valof
//----------------------------------------------------------------------------
// Display the user screen
[
SaveResidentState()
resultis table
   [
        7	//0 number of words that follow 
    24406B	//1 lda 1 .+6
    22404B	//2 lda 0 @.+4
   107414B	//3 and# 0 1 szr
      776B	//4  jmp .-2
   screenTrap	//5 swat
   177037B	//6 kbdAd+3
        4	//7 swat key mask
   ]
]

//----------------------------------------------------------------------------
and Call(numArgs, argVec) = valof
//----------------------------------------------------------------------------
// Note that the following only works for BCPL procedures.
// Compose a call by filling AC0, AC1, perhaps putting entries in AC2!3.
// This is a little dangerous because a poor soul might get a trap before
// GetFrame had fully finished the frame, and given another procedure call.
// The documentation says "expect occasional anomolies after ↑C".
[
if stackDubious then
   unless Confirm("The stack is dubious.  Do you still want to call? ") do
      Fail()

SaveResidentState()
let ac2 = VMFetch(userAC2)
let procArgs = numArgs-1  //number of args to pass
switchon procArgs into
   [
   default:
      [
      let r = StoreVec(argVec+3, procArgs-2)
      argVec!3 = r-ac2-3
      ]
   case 3: VMStore(ac2+3, argVec!3)
   case 2: VMStore(userAC1, argVec!2)
   case 1: VMStore(userAC0, argVec!1)
   case 0: endcase
   ]

let cv = table
   [
   13		//number of words that follow
   4407b	//jsr .+7
   0		//space for SI -- *** known to be 6 words long ***
   0 
   0
   0
   0
   0
   171000b	//mov 3 2
   64767b	//jsrii .-11 (SimIns)

   6403b	//jsr @.+3
   0		//numArgs
   callReturnTrap
   0		//procedure address
   ]
BuildSI(cv+2, 101000b)  //simulate a Mov 0 0
VMStore(userPC, scm+codeVectorOffset+9-1)  //simMA increments it
cv!11 = procArgs
cv!13 = argVec!0
resultis cv
]

//----------------------------------------------------------------------------
and ResidentSwapIn() = valof 
//----------------------------------------------------------------------------
// Returns true iff ok to go on reading from command file
[
let ok = true
swateeDubious = false //assume Swatee is ok
Ws("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*n")

test vm>>VM.type eq vmTypeDisk
   ifso if VMFetch(dumperFlg) then
      [
      Ws("The Swatee was created by a Dumper-boot.*n")
      Ws("Page 0 is invalid and the display is off.*n")
      swateeDubious = true
      ok = false
      ]
   ifnot swateeDubious = true

scm = VMFetch(567B) //Swat Communication table base address
if VMFetch(scm+versionOffset) ls residentVersion then
   [
   Ws("Swat's resident is lost or incompatible.*n")
   swateeDubious = true
   ok = false
   ]

let pc = VMFetch(userPC)  //where to resume user
let trapAddr = VMFetch(trapPC)-1  //address of trapping instruction
let trapInstr = VMFetch(trapAddr)  //trapping instruction
CheckAllBreaks(trapAddr)
switchon trapInstr into
   [
   default:
   case generalTrap:	//general "Get me to swat" instruction
      [
      unless IsBreak(trapAddr) do  //CheckAllBreaks already did it
         [
         PutTemplate(dsp, "Trap instruction $UO at $P*N",
          trapInstr, AddrToSym, pc)
         ok = false
         ]
      endcase
      ]
   case parityTrap:	//a parity error happened
      [
      ParityError(pc, VMFetch)
      endcase
      ]
   case callSwatTrap:	//user called CallSwat()
   case teleSwatTrap:
      [
      ok = false
      PutTemplate(dsp, "CallSwat from $P*N", AddrToSym, VMFetch(userAC3)-1)
      let numArgs = VMFetch(VMFetch(userAC3))
      if numArgs gr 0 then
         if PrintSwateeString(VMFetch(userAC0)) then Puts(dsp, $*N)
      if numArgs gr 1 then
         if PrintSwateeString(VMFetch(userAC1)) then Puts(dsp, $*N)
      if trapInstr eq teleSwatTrap then TeleSwatServer()
      endcase
      ]
   case printErrorTrap:	//print an error message from a file
      [
      UserPrintError()
      endcase
      ]
   case screenTrap:	//return from UserScreen ↑U
      [
      RestoreResidentState()
      endcase
      ]
   case callReturnTrap:	//return from proc called by Swat
      [
      let value = VMFetch(userAC0)
      PutTemplate(dsp, "Call returns $UOb = $D.*n", value, value)
      RestoreResidentState()
      endcase
      ]
   case kbdTrap:	//<Left-Shift><Control><Swat>
   case abortTrap:	//<Left-Shift><Swat> (should never be seen in Swat)
      [
      PutTemplate(dsp, "Keyboard trap at $P*N", AddrToSym, pc)
      ok = false
      endcase
      ]
   ]

cvPtr = lenCodeVector-1	//the code vector is empty
DisplayState()
resultis ok
]

//----------------------------------------------------------------------------
and ResidentSwapOut(sm) be
//----------------------------------------------------------------------------
[
if swateeDubious then
   unless Confirm("The Swatee is dubious. Do you still want to swap? ") do
      Fail()

// signal Swat resident to jump into the code vector
VMStore(scm+resumeOffset, -1)

// put the code in the code vector
if sm!0 gr cvPtr then ReportFail("Code vector overflow")
for i = 1 to sm!0 do VMStore(scm+codeVectorOffset+i-1, sm!i)
]

//----------------------------------------------------------------------------
and DisplayState() be
//----------------------------------------------------------------------------
// Display state of machine from user's viewpoint
[
Resets(state)
PutTemplate(state, "AC0:$UO  AC1:$UO  AC2:$UO  AC3:$UO  CRY:$UO",
 VMFetch(userAC0), VMFetch(userAC1), VMFetch(userAC2), VMFetch(userAC3),
 VMFetch(userCry)? 1, 0)
PutTemplate(state, "  PC:$P", AddrToSym, VMFetch(userPC))
PutTemplate(state, "  INT:$S", VMFetch(userInt)? "ON", "OFF")
PutTemplate(state, "*N*N$P   $P*N*N", VMPrint, false, SymPrint, false)
stackDubious = true  //assume the worst
MapStack(2, PrintFrame, state, VMFetch(userAC2))
]

//----------------------------------------------------------------------------
and PrintSwateeString(addr, Fetch; numargs na) = valof
//----------------------------------------------------------------------------
// See if addr could be a pointer to a string in Swatee and print it if so.
// return true if anything was printed
[
if na ls 2 then Fetch = VMFetch
if Usc(addr, 176777B) gr 0 resultis false  //can't be in IO area
let string = vec 127
string!0 = Fetch(addr)
for i = 1 to string>>String.length rshift 1 do
   string!i = Fetch(addr+i)
for i = 1 to string>>String.length do
   if string>>String.char↑i ge 200b resultis false
Ws(string)
resultis true
]

//----------------------------------------------------------------------------
and StoreVec(vector, length) = valof
//----------------------------------------------------------------------------
// Store vector in the code vector, and return the address of its first word
// Vectors are packed in from the end working toward the front.
// Code is packed in from the front working toward the end.
// cvPtr is the index of the first free word, working back from the end.
[
if swateeDubious then ReportFail("Can't store vector -- swatee is dubious.")
test cvPtr-length ls 0
   ifso ReportFail("Code vector overflow")
   ifnot cvPtr = cvPtr-length
let cv = scm + codeVectorOffset + cvPtr +1
for i = 0 to length-1 do VMStore(cv+i, vector!i)
resultis cv
]

//----------------------------------------------------------------------------
and ResidentSysOut(sysOut) be
//----------------------------------------------------------------------------
[
// count up the number of state frames
let count, rs = 0, rsQ!0
while rs ne 0 do
   [
   count = count +1
   rs = rs>>RS.link
   ]
Puts(sysOut, count)

// write them out
rs = rsQ!0; while rs ne 0 do
   [
   WriteBlock(sysOut, rs, lenRS)
   rs = rs>>RS.link
   ]
]

//----------------------------------------------------------------------------
and ResidentSysIn(sysIn) be
//----------------------------------------------------------------------------
[
swateeDubious = true

// reset resident state to empty
while rsQ!0 ne 0 do Free(sysZone, Dequeue(rsQ))

for i = 1 to (Endofs(sysIn)? 0, Gets(sysIn)) do
   [
   let rs = Allocate(sysZone, lenRS)
   ReadBlock(sysIn, rs, lenRS)
   Enqueue(rsQ, rs)
   ]
]

//----------------------------------------------------------------------------
and SaveResidentState() be
//----------------------------------------------------------------------------
// note that we manage this queue LIFO rather than FIFO
[
let rs = Allocate(sysZone, lenRS)
let state = lv rs>>RS.state
for i = 0 to lenState-1 do state!i = VMFetch(userPC+i)
rs>>RS.trapPC = VMFetch(trapPC)
let scm = lv rs>>RS.scm
for i = 0 to lenSCM-1 do scm!i = VMFetch(scm+i)
Enqueue(rsQ, rs)  //add to tail of queue
]

//----------------------------------------------------------------------------
and RestoreResidentState() be
//----------------------------------------------------------------------------
// note that we manage this queue LIFO rather than FIFO
[
if rsQ!0 eq 0 then ReportFail("Registers lost")
let rs = rsQ!1; Unqueue(rsQ, rs)  //remove most recently queued item (TAIL)
let state = lv rs>>RS.state
for i = 0 to lenState-1 do VMStore(userPC+i, state!i)
VMStore(trapPC, rs>>RS.trapPC)
let scm = lv rs>>RS.scm
for i = 0 to lenSCM-1 do VMStore(scm+i, scm!i)
Free(sysZone, rs)
]