// SwatBreak.bcpl - break points
// Copyright Xerox Corporation 1979, 1982
// Last modified April 11, 1982  12:22 AM by Boggs

get "Swat.decl"

external
[
// outgoing procedures
InitBreak; IsBreak; CheckAllBreaks
BreakSwapOut; BreakSwapIn
BreakSysOut; BreakSysIn
SetBreak; DelBreak; DelAllBreaks
PrintBreak; PrintAllBreaks

// incoming procedures
AddrToSym; BuildSI
ReportFail; VMFetch; VMStore

PutTemplate; Ws
Endofs; Gets; Puts
ReadBlock; WriteBlock
Allocate; Free; Zero
Enqueue; Unqueue

// incoming statics
sysZone; dsp
]

static
[
bpQ			// -> queue of active breakpoints
nextBrkNum
]

//----------------------------------------------------------------------------
structure BP:		// BreakPoint
//----------------------------------------------------------------------------
[
link word		// must be first
oneShot word		// true if it should be deleted when hit
mpbp word		// -> mpbp in user space (zero => not mpbp)
brkNum word		// breakpoint number
brkAddr word		// address of this breakpoint
brokenIns word		// instruction which was replaced by bp trap
]
manifest lenBP = size BP/16

//----------------------------------------------------------------------------
structure MPBP:		// Multiple Proceed Break Point
//----------------------------------------------------------------------------
[
si word lenSI
brkAddr word		// break address for this MPBP (0 => mpbp is free)
brkCnt word		// proceed count
]
compileif size MPBP/16 ne lenSI+2 then
   [ Error("Change lenMPBP declaration in Swat.decl") ]

//----------------------------------------------------------------------------
let InitBreak() be [ bpQ = Allocate(sysZone, 2); bpQ!0 = 0 ]
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and IsBreak(addr) = valof
//----------------------------------------------------------------------------
[
let bp = bpQ!0; while bp ne 0 do
   [
   if bp>>BP.brkAddr eq addr resultis true
   bp = bp>>BP.link
   ]
resultis false
]

//----------------------------------------------------------------------------
and CheckAllBreaks(addr) = valof
//----------------------------------------------------------------------------
[
let bp = bpQ!0; while bp ne 0 do
   [
   let link = bp>>BP.link
   test VMFetch(bp>>BP.brkAddr) eq bp>>BP.brokenIns  //did it vanish?
      ifnot DelBreak(bp>>BP.brkAddr, false)
      ifso if bp>>BP.brkAddr eq addr test bp>>BP.oneShot
         ifso DelBreak(bp>>BP.brkAddr, false)
         ifnot
            [
            if bp>>BP.mpbp ne 0 &
             VMFetch(bp>>BP.mpbp+offset MPBP.brkCnt/16) eq 0 then
               [  // downgrade to vanilla breakpoint
               bp>>BP.mpbp = 0
               VMStore(bp>>BP.mpbp+offset MPBP.brkAddr/16, 0)
               ]
            PrintBreak(bp)
            ]
   bp = link
   ]
]

//----------------------------------------------------------------------------
and PrintBreak(bp, noCR; numargs na) be 
//----------------------------------------------------------------------------
[
PutTemplate(dsp, "BreakPoint $UO at $P",
 bp>>BP.brkNum, AddrToSym, bp>>BP.brkAddr)
if bp>>BP.mpbp ne 0 then PutTemplate(dsp, ", $UO proceeds left",
 VMFetch(bp>>BP.mpbp+offset MPBP.brkCnt/16))
unless na gr 1 & noCR do Puts(dsp, $*N)
]

//----------------------------------------------------------------------------
and PrintAllBreaks() be
//----------------------------------------------------------------------------
[
let bp = bpQ!0; while bp ne 0 do
   [
   PrintBreak(bp)
   bp = bp>>BP.link
   ]
]

//----------------------------------------------------------------------------
and BreakSwapOut() be
//----------------------------------------------------------------------------
// User is about to get control.
[
let bp = bpQ!0; while bp ne 0 do
   [
   if VMFetch(bp>>BP.brkAddr) eq bp>>BP.brokenIns then
      VMStore(bp>>BP.brkAddr, (bp>>BP.mpbp? mpBrkPtTrap, breakPtTrap))
   bp = bp>>BP.link
   ]
]

//----------------------------------------------------------------------------
and BreakSwapIn() be
//----------------------------------------------------------------------------
// We just got control.
[
let bp = bpQ!0; while bp ne 0 do
   [
   test VMFetch(bp>>BP.brkAddr) eq (bp>>BP.mpbp? mpBrkPtTrap, breakPtTrap)
      ifso VMStore(bp>>BP.brkAddr, bp>>BP.brokenIns)
      ifnot bp>>BP.brokenIns = 0
   bp = bp>>BP.link
   ]
]

//----------------------------------------------------------------------------
and BreakSysOut(sysOut) be
//----------------------------------------------------------------------------
[
// count the breakpoints
let count, bp = 0, bpQ!0
while bp ne 0 do
   [
   count = count +1
   bp = bp>>BP.link
   ]

// write them on the SysOut file
Puts(sysOut, count) 
bp = bpQ!0; while bp ne 0 do
   [
   WriteBlock(sysOut, bp, lenBP)
   bp = bp>>BP.link
   ]
]

//----------------------------------------------------------------------------
and BreakSysIn(sysIn) be
//----------------------------------------------------------------------------
[
DelAllBreaks()

// read SysIn file's breakpoint list
let count = Endofs(sysIn)? 0, Gets(sysIn) 
for i = 1 to count do
   [
   let bp = Allocate(sysZone, lenBP)
   ReadBlock(sysIn, bp, lenBP)
   bp>>BP.mpbp = 0  // ahem
   Enqueue(bpQ, bp)
   ]
]

//----------------------------------------------------------------------------
and SetBreak(addr, oneShot, proceedCnt; numargs na) be
//----------------------------------------------------------------------------
[
if IsBreak(addr) then ReportFail("Already broken")

let mpbp = valof
   [
   if na ls 3 resultis 0
   let p = VMFetch(567B) + mpbpOffset
   for i = 1 to numMPBP do
      [
      if VMFetch(p+offset MPBP.brkAddr/16) eq 0 then
         [
         let si = vec lenSI; BuildSI(si, VMFetch(addr))
         for j = 0 to lenSI-1 do VMStore(p+j, si!j)
         VMStore(p+offset MPBP.brkAddr/16, addr)
         VMStore(p+offset MPBP.brkCnt/16, proceedCnt)
         resultis p
         ]
      p = p + lenMPBP
      ]
   ReportFail("Multiple proceed break point table full")
   ]

let bp = Allocate(sysZone, lenBP)
bp>>BP.brkNum = nextBrkNum
nextBrkNum = nextBrkNum +1
bp>>BP.oneShot = na ls 2? false, oneShot
bp>>BP.brkAddr = addr
bp>>BP.brokenIns = VMFetch(addr)
bp>>BP.mpbp = mpbp
PrintBreak(bp)
Enqueue(bpQ, bp)
]

//----------------------------------------------------------------------------
and DelBreak(arg, num) be
//----------------------------------------------------------------------------
// arg is a breakpoint number if num is true, else an addr
[
let bp = bpQ!0; while bp ne 0 do
   [
   if arg eq (num? bp>>BP.brkNum, bp>>BP.brkAddr) then
      [
      Unqueue(bpQ, bp)
      PrintBreak(bp, true)
      Ws(" deleted*N")
      if bp>>BP.mpbp ne 0 then VMStore(bp>>BP.mpbp+offset MPBP.brkAddr/16, 0)
      Free(sysZone, bp)
      return
      ]
   bp = bp>>BP.link
   ]
ReportFail("Break not found")
]

//----------------------------------------------------------------------------
and DelAllBreaks() be
//----------------------------------------------------------------------------
[
// The following code is redundant if the breakpoint machinery is working
//  correctly, and causes a deadlock when detaching from a non-responding
//  network virtual address space.
// let mpbp = VMFetch(567B) + mpbpOffset
// for i = 1 to numMPBP do
//    [
//    VMStore(mpbp + offset MPBP.brkAddr/16, 0)
//    mpbp = mpbp + lenMPBP
//    ]
until bpQ!0 eq 0 do DelBreak((bpQ!0)>>BP.brkAddr, false)
nextBrkNum = 0
]