// SwatSymB.bcpl -- symbol table -- companion file is SwatSymA.asm
// Copyright Xerox Corporation 1979, 1980, 1982
// Last modified May 9, 1982  11:36 AM by Taft

// All you do is just...09/05/73   (ALB)

get "Swat.decl"
get "Streams.d"
get "AltoFileSys.d"
get "SwatSym.decl"

external
[
// outgoing Procedures
SymToAddr; AddrToSym; MapSym; ReadSymsFile
SymPrint; SymRead; SymReset; StaticValue; SymBank
SymSysOut; SymSysIn; SymSwapIn; ResetSymCache

// incoming procedures from Swat
VMFetch; VMCache; ReadString; Confirm; DisplayState
Fail; ReportFail; SetFailPt; UnSetFailPt; ReportBug
ScanSymBuffer; FindSym; ClosestPL; ClosestV

// incoming procedures from OS
OpenFile; CreateDiskStream; ReadLeaderPage
InitScanStream; GetScanStreamBuffer; FinishScanStream
GetCurrentFa; JumpToFa; GetCompleteFa
ReadBlock; WriteBlock; FilePos; SetFilePos
Closes; Endofs; Gets; Puts; PutTemplate; Ws; Wss
Zero; MoveBlock; SetBlock; DoubleAdd; Allocate; Free; Noop
Dequeue; Enqueue; Unqueue; QueueLength; DefaultArgs
ExtractSubstring; ConcatenateStrings; StringCompare; CopyString

// outgoing statics
cfaSym; symFileName; writeDate; faSymStrings
stfQ; userStrings; builtInStrings
s; a; p

// incoming statics
sysZone; dsp; xmFlag
]

static
[
stfQ			// -> queue of STFs
builtInStrings		// -> queue of built-in names
userStrings		// -> queue of cached names
symCacheHits		// # of hits in the user symbol cache
symCacheMisses		// # of misses in the user symbol cache

cfaSym			// -> cfa of start of .Syms file
symFileName		// -> file name of .Syms file
writeDate		// -> write date of .Syms file
faSymStrings		// -> fa of string area of .Syms file

s; a; p			// MapSym temporaries
]

//Each static has a Sym structure in an STF structure on stfQ.
//Strings for built-in statics are kept in CE structures on the
// builtInStrings queue.  Sym.builtIn is true for these.
//Strings for recently referenced user statics are cached in CE structures
// on the userStrings queue.  Sym.inCache is true for these.
//If the static is built-in or cached, then Sym.namePos -> the CE
// containing the name, which is either on the builtInStrings queue
// or the userStrings queue.  Otherwise Sym.namePos is the word offset
// in the string area of the .SYMS file for the string.
//SysOut appends a copy of the .SYMS file to the SysOut file.
//SysIn sets up all of the FPs and FAs so that it can treat that segment
// of the file as if it were simply a .SYMS file.

structure FSS:		// File Scan State
[
buffer word		// -> current buffer (0 => hit end-of-file)
pos word		// word position in buffer of current sym name
endPos word		// word position of first word not in buffer
basePos word		// offset of buffer base relative to faSymStrings
stopPos word		// length of string area = limit value of basePos
ssd word		// -> Scan Stream Descriptor
]

//----------------------------------------------------------------------------
let SymToAddr(symbol) = valof
//----------------------------------------------------------------------------
[
// Hack: the value of a symbol that begins with ↑ is its address
let forceAddr, fixedSymbol = false, vec 127
if symbol>>String.char↑1 eq $↑ then
   [
   for i = 2 to symbol>>String.length do
      fixedSymbol>>String.char↑(i-1) = symbol>>String.char↑i
   fixedSymbol>>String.length = symbol>>String.length-1
   symbol = fixedSymbol
   forceAddr = true
   ]

// Search the built-in string queue
let ce = builtInStrings!0; while ce ne 0 do
   [
   if StringCompare(ce>>CE.string, symbol) eq 0 then
      resultis StaticValue(ce>>CE.sym, forceAddr)
   ce = ce>>CE.link
   ]

// Search the user string cache
ce = userStrings!0; while ce ne 0 do
   [
   if StringCompare(ce>>CE.string, symbol) eq 0 then
      [
      Unqueue(userStrings, ce)
      Enqueue(userStrings, ce)  //make it most recently referenced
      symCacheHits = symCacheHits +1
      resultis StaticValue(ce>>CE.sym, forceAddr)
      ]
   ce = ce>>CE.link
   ]

// Search string area in .Syms file
let stream = CreateDiskStream(lv cfaSym>>CFA.fp, ksTypeReadOnly, wordItem)
if stream ne 0 then
   [
   JumpToFa(stream, faSymStrings)
   let numChars = symbol>>String.length
   fixedSymbol>>String.length = numChars
   for i = 1 to numChars do
      fixedSymbol>>String.char↑i = symbol>>String.char↑i & 337b
   if (numChars & 1) eq 0 then
      fixedSymbol>>String.char↑(numChars+1) = 0

   // The following must be declared in the order defined in the FSS structure
   let buffer, pos, endPos, basePos, stopPos, ssd = 0, nil, 0, nil, nil, nil
   let fss = lv buffer
   pos = ((faSymStrings>>FA.charPos+2) rshift 1) & 377b
   basePos = 1 - pos
   stopPos = Gets(stream)
   manifest nBufs = 2
   let bufTable = vec nBufs
   for i = 0 to nBufs-1 do bufTable!i = Allocate(sysZone, 256)
   ssd = InitScanStream(stream, bufTable, nBufs)
   AdvanceBuffer(fss)
   s = 0

   // SymToAddr (cont'd)

   while buffer ne 0 & pos ls endPos do
      [
      // Advance the pointer to the first "interesting" symbol.
      // The call to ScanSymBuffer accelerates the search, but if the call is
      //  omitted the search still works, only more slowly.
      pos = ScanSymBuffer(buffer+pos, buffer+endPos, fixedSymbol) - buffer

      // Now inpect the entry carefully.
      p = basePos + pos
      let thisSymbol, tempSymbol = buffer+pos, vec 128  //NOT 127!
      let len = thisSymbol>>String.length rshift 1 +1
      MoveBlock(tempSymbol, thisSymbol, len)
      pos = pos + len; if pos ge endPos then
         [ AdvanceBuffer(fss); MoveBlock(tempSymbol+len-pos, buffer, pos) ]
      tempSymbol!len = 0  //stop ScanSymBuffer
      unless ScanSymBuffer(tempSymbol, tempSymbol+len+1, fixedSymbol) eq tempSymbol loop

      // Found it
      MapSym(FindSym, FindSym, FindSym)
      if s ne 0 then AddToSymCache(tempSymbol, s)
      break
      ]

   FinishScanStream(ssd)
   for i = 0 to nBufs-1 do Free(sysZone, bufTable!i)
   Closes(stream)
   if s ne 0 resultis StaticValue(s, forceAddr)
   ]

PutTemplate(dsp, "$S not found*n", symbol)
Fail()
]

//----------------------------------------------------------------------------
and AdvanceBuffer(fss) be
//----------------------------------------------------------------------------
[
fss>>FSS.basePos = fss>>FSS.basePos + fss>>FSS.endPos
test fss>>FSS.basePos ge fss>>FSS.stopPos
   ifso
      [
      fss>>FSS.buffer = 0
      fss>>FSS.pos = 0
      fss>>FSS.endPos = 0
      ]
    ifnot
      [
      fss>>FSS.buffer = GetScanStreamBuffer(fss>>FSS.ssd)
      fss>>FSS.pos = fss>>FSS.pos - fss>>FSS.endPos
      fss>>FSS.endPos = fss>>FSS.ssd>>SSD.numChars rshift 1
      if fss>>FSS.basePos + fss>>FSS.endPos gr fss>>FSS.stopPos then
         fss>>FSS.endPos = fss>>FSS.stopPos - fss>>FSS.basePos
      ]
]

//----------------------------------------------------------------------------
// and FindSym(sym) be  //asm coded for speed
//----------------------------------------------------------------------------
//    if sym>>Sym.builtIn eq 0 & sym>>Sym.inCache eq 0 &
//     sym>>Sym.namePos eq p then s = sym

//----------------------------------------------------------------------------
// and ScanSymBuffer(ptr, endPtr, symbol) = valof  //asm coded for speed
//----------------------------------------------------------------------------
// [
// let len = ptr>>String.length rshift 1 +1
// if ptr+len uge endPtr resultis ptr
// if StringCompare(ptr, symbol) eq 0 resultis ptr
// ptr = ptr + len
// ] repeat

//----------------------------------------------------------------------------
and AddrToSym(stream, addr, bank, epsilon; numargs na) be
//----------------------------------------------------------------------------
[
DefaultArgs(lv na, -2, 0, 0)
let dummySym = vec lenSym; Zero(dummySym, lenSym)
dummySym>>Sym.bank = bank
s, a = dummySym, addr
MapSym(ClosestPL, ClosestPL, ClosestV)
test s>>Sym.inCache ne 0
   ifso symCacheHits = symCacheHits +1
   ifnot if s>>Sym.builtIn eq 0 then  //Aw shit
      unless s eq dummySym do  //skip if no symbol close to this addr
         [
         let sym = CreateDiskStream(lv cfaSym>>CFA.fp,
          ksTypeReadOnly, wordItem)
         JumpToFa(sym, faSymStrings)
         let fPos = vec 1; FilePos(sym, fPos)
         SymSetPos(sym, fPos, s>>Sym.namePos)
         let string = vec 127
         string!0 = Gets(sym)
         for i = 1 to string>>String.length rshift 1 do string!i = Gets(sym)
         Closes(sym)
         AddToSymCache(string, s)
         ]
let string = s>>Sym.namePos>>CE.string
let value = StaticValue(s, false)
test s ne dummySym & addr-value uls 2000B
   ifso test addr-value ule epsilon
      ifso Wss(stream, string)  // exact match
      ifnot PutTemplate(stream, "$S+$O", string, addr-value)  // close
   ifnot test bank eq 0  // too far away
      ifso PutTemplate(stream, "$UO", addr)
      ifnot PutTemplate(stream, "$O.$UO", bank, addr)
]

//----------------------------------------------------------------------------
// and ClosestPL(sym) be  //asm coded for speed
//----------------------------------------------------------------------------
//    if s>>Sym.value uls sym>>Sym.value & sym>>Sym.value ule a &
//     s>>Sym.bank eq sym>>Sym.bank then s = sym

//----------------------------------------------------------------------------
// and ClosestV(sym) be if sym>>Sym.addr eq a then s = sym  //asm coded
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and SymRead() be	//↑Y command
//----------------------------------------------------------------------------
[
let fn = ReadString("Symbol file name: "); if fn eq 0 return
let stream = OpenFile(fn, ksTypeReadOnly, wordItem)
if stream eq 0 then
   [
   fn = ConcatenateStrings(fn, ".syms", true)
   stream = OpenFile(fn, ksTypeReadOnly, wordItem)
   if stream eq 0 then [ Free(sysZone, fn); ReportFail("File not found") ]
   ]
SetFailPt(sr)
ReadSymsFile(stream, fn)
UnSetFailPt()
sr:Closes(stream)
Free(sysZone, fn)
]

//----------------------------------------------------------------------------
and SymSysIn(sysIn, name) be
//----------------------------------------------------------------------------
[
SymReset()
let length = Endofs(sysIn)? 0, Gets(sysIn)
if length ne 0 then  //a .syms file follows
   [
   let fPos = vec 1; FilePos(sysIn, fPos)
   SetFailPt(ssi)
   ReadSymsFile(sysIn, name)
   UnSetFailPt()
   ssi: SymSetPos(sysIn, fPos, length)
   ]
]

//----------------------------------------------------------------------------
and SymSysOut(sysOut) be
//----------------------------------------------------------------------------
[
let sym = CreateDiskStream(lv cfaSym>>CFA.fp, ksTypeReadOnly, wordItem)
test sym eq 0
   ifso Puts(sysOut, 0)
   ifnot  //append .syms file to sysout file
      [
      let buf, bufLen = 0, 77777b
      SetFailPt(sso)
      buf = Allocate(sysZone, bufLen, lv bufLen) repeatuntil buf ne 0
      JumpToFa(sym, lv cfaSym>>CFA.fa)
      ReadBlock(sym, buf, 16)
      JumpToFa(sym, lv cfaSym>>CFA.fa)
      Puts(sysOut, buf>>SymHead.fileLength)
      until Endofs(sym) do
         WriteBlock(sysOut, buf, ReadBlock(sym, buf, bufLen))
      UnSetFailPt()

      sso:
      if buf ne 0 then Free(sysZone, buf)
      Closes(sym)
      ]
]

//----------------------------------------------------------------------------
and ReadSymsFile(stream, fn) be
//----------------------------------------------------------------------------
[
VMCache(vmFlush); SymReset()

// Remember some things about .Syms file to make subsequent accesses faster
//  and to detect if file has changed so that this info can be recomputed.
GetCompleteFa(stream, cfaSym)
symFileName = ExtractSubstring(fn)
let fPos = vec 1; FilePos(stream, fPos)
let ld = Allocate(sysZone, 256)
ReadLeaderPage(stream, ld)
MoveBlock(writeDate, lv ld>>LD.written, 2)
Free(sysZone, ld)
JumpToFa(stream, lv cfaSym>>CFA.fa)
let freeSlop = Allocate(sysZone, 1000)
let header, numUserSyms = vec 16, nil
SetFailPt(rsf)

// read file header
ReadBlock(stream, header, 16)
if (header>>SymHead.version & 177400b) ne 1000b then  //major vers = 2
   unless Confirm("Are you sure this is a symbol file?") do Fail()

// save FA of string area
SymSetPos(stream, fPos, header>>SymHead.namesAddr)
GetCurrentFa(stream, faSymStrings)

// build user symbol table
SymSetPos(stream, fPos, header>>SymHead.symsAddr)
numUserSyms = Gets(stream)
// There seems to be a bug in Bldr that causes the number of user symbols
//  for Sys.syms to be 1 too big (not surprising: Sys.syms is special).
while header>>SymHead.symsAddr+lenSym*numUserSyms+1 gr
 header>>SymHead.brFilesAddr do numUserSyms = numUserSyms -1

while numUserSyms ne 0 do
   [
   let stf, symsInThisSTF = nil, numUserSyms
      [
      let maxSize = nil
      stf = Allocate(sysZone, lenSTFHeader+symsInThisSTF*lenSym, lv maxSize)
      if stf eq 0 then
         [
         if maxSize ls 100 then Fail("Your symbol table is too big!*N")
         symsInThisSTF = (maxSize-lenSTFHeader)/lenSym
         ]
      ] repeatuntil stf ne 0

   // install user's symbols
   ReadBlock(stream, lv stf>>STF.firstSym, symsInThisSTF*lenSym)
   stf>>STF.builtIn = false
   stf>>STF.numSyms = symsInThisSTF
   let sym = lv stf>>STF.firstSym
   for i = 1 to symsInThisSTF do
      [
      sym>>Sym.builtIn = false
      sym>>Sym.inCache = false
      sym = sym + lenSym
      ]
   Enqueue(stfQ, stf)
   numUserSyms = numUserSyms - symsInThisSTF
   ]
Free(sysZone, freeSlop)

BindAllSyms()
DisplayState()
UnSetFailPt()
return

rsf: SymReset()
Free(sysZone, freeSlop)
]

//----------------------------------------------------------------------------
and BindAllSyms() be MapSym(BindSym, BindSym, Noop)
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and BindSym(sym) be
//----------------------------------------------------------------------------
[
sym>>Sym.bank = 0
test sym>>Sym.reloc
   ifso UpdateReloc(sym)
   ifnot sym>>Sym.value = VMFetch(sym>>Sym.addr)
]

//----------------------------------------------------------------------------
and StaticValue(sym, forceAddr) =
//----------------------------------------------------------------------------
   ((sym>>Sym.type eq variable) % forceAddr)? sym>>Sym.addr, sym>>Sym.value

//----------------------------------------------------------------------------
and SymBank(sym, forceAddr) = sym>>Sym.bank
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and MapSym(Procedure, Label, Variable) be
//----------------------------------------------------------------------------
[
let stf = stfQ!0; while stf ne 0 do
   [
   let sym = lv stf>>STF.firstSym
   for i = 1 to stf>>STF.numSyms do
      [
      (selecton sym>>Sym.type into
         [
         case procedure: Procedure
         case label: Label
         case variable: Variable
         default: Noop
         ])(sym)
      sym = sym + lenSym
      ]
   stf = stf>>STF.link
   ]
]

//----------------------------------------------------------------------------
and SymSetPos(stream, pos, inc) be
//----------------------------------------------------------------------------
[
let byteInc = vec 1
byteInc!0 = inc rshift 15
byteInc!1 = inc lshift 1
DoubleAdd(byteInc, pos)
SetFilePos(stream, byteInc)
]

//----------------------------------------------------------------------------
and SymSwapIn() be
//----------------------------------------------------------------------------
[
if symFileName ne 0 then  //We may have a symbol file...
   [
   let stream = OpenFile(symFileName, ksTypeReadOnly, wordItem, 0,
    lv cfaSym>>CFA.fp)
   test stream eq 0
      ifso SymReset()  //Nope, it disappeared; forget about it.
      ifnot
         [
         let ld = vec 256
         SetFailPt(ssi1)
         ReadLeaderPage(stream, ld)
         if writeDate>>TIME.h ne ld>>LD.written.h %
          writeDate>>TIME.l ne ld>>LD.written.l then
            [
            let fn = symFileName; symFileName = 0
            ReadSymsFile(stream, fn)  //It changed. Reinstall it.
            ]
         UnSetFailPt()
         ssi1: Closes(stream)
         ]
   ]
MapSym(UpdateReloc, UpdateReloc, Noop)
]

//----------------------------------------------------------------------------
and UpdateReloc(sym) be
//----------------------------------------------------------------------------
[
if sym>>Sym.reloc then
   [
   let value = VMFetch(sym>>Sym.addr)
   if xmFlag then
      [
      let firstWord = VMFetch(value)
      if (firstWord & xJmpInstMask) eq xJmp0 then
         [
         sym>>Sym.bank = firstWord & xJmpBankMask
         value = VMFetch(value+1)  //i.e. xPC
         ]
      ]
   sym>>Sym.value = value
   ]
]

//----------------------------------------------------------------------------
and SymReset() be
//----------------------------------------------------------------------------
[
ResetSymCache()
// reset user symbols to empty but keep built-in symbols
let stf = stfQ!0; while stf ne 0 do
   [
   let nextSTF = stf>>STF.link
   if stf>>STF.builtIn eq 0 then
      [ Unqueue(stfQ, stf); Free(sysZone, stf) ]
   stf = nextSTF
   ]
symCacheHits, symCacheMisses = 0, 0
if symFileName ne 0 then Free(sysZone, symFileName); symFileName = 0
SetBlock(cfaSym, 125252b, lCFA)
]

//----------------------------------------------------------------------------
and AddToSymCache(string, sym) be
//----------------------------------------------------------------------------
[
if QueueLength(userStrings) gr maxCacheLength then RemoveFromSymCache()
let ce = Allocate(sysZone, lenCE+string>>String.length rshift 1 +1, true)
if ce eq 0 return
ce>>CE.string = ce+lenCE
CopyString(ce>>CE.string, string)
ce>>CE.sym = sym
ce>>CE.namePos = sym>>Sym.namePos
sym>>Sym.namePos = ce
sym>>Sym.inCache = true
Enqueue(userStrings, ce)
symCacheMisses = symCacheMisses +1
]

//----------------------------------------------------------------------------
and RemoveFromSymCache() be
//----------------------------------------------------------------------------
[
let ce = Dequeue(userStrings)
ce>>CE.sym>>Sym.namePos = ce>>CE.namePos
ce>>CE.sym>>Sym.inCache = false
Free(sysZone, ce)
]

//----------------------------------------------------------------------------
and ResetSymCache() be while userStrings!0 ne 0 do RemoveFromSymCache()
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and SymPrint(stream, verbose) be
//----------------------------------------------------------------------------
[
if symFileName eq 0 then [ Wss(stream, "No symbol file"); return ]
PutTemplate(stream, "Symbol file: $S", symFileName)
if verbose then
   [
   PutTemplate(stream, ", hits: $UD misses: $UD*N*N",
    symCacheHits, symCacheMisses)
   let ce = userStrings!0; while ce ne 0 do
      [
      PutTemplate(stream, "$S*N", ce>>CE.string)
      ce = ce>>CE.link
      ]
   ]
]