-- XDUserSegments.Mesa
-- Edited by:
--             Sandman on July 22, 1980  5:07 PM
--             Johnsson on August 30, 1978  12:02 PM
--             Barbara on April 6, 1979  12:22 PM
--             Bruce on October 3, 1980  5:04 PM

DIRECTORY
  AltoDefs USING [BYTE, BytesPerPage, PageNumber],
  AltoFileDefs USING [eofDA, FP],
  BcdDefs USING [Base, CodeDesc, FTNull, FTSelf, GFTIndex, MTIndex],
  BcdOps USING [BcdBase, FTHandle, MTHandle, NameString, ProcessModules, SGHandle],
  ControlDefs USING [InstWord],
  DebugFormat USING [CodeObject],
  DebugOps USING [CacheNewFile, LongREAD, LongWRITE, ShortCopyREAD, ShortCopyWRITE],
  DLoadState USING [Acquire, AcquireBcd, MapRealToConfig, ReleaseBcd, Release],
  Drum USING [DrumItem, Handle],
  Frames USING [Invalid],
  Gf USING [Check, GFI, Handle, Original],
  Init USING [bootLoaded],
  Inline USING [LongNumber],
  MachineDefs USING [BYTE, BytePC, FHandle, GFTIndex, GlobalFrame, GFHandle, NullConfig],
  Mopcodes USING [zRBL],
  SegmentDefs USING [
    AddressFromPage, BankIndex, DefaultAccess, DeleteFileSegment, FileHandle, FileHint,
    FileObject, FileSegmentAddress, FileSegmentHandle, FileSegmentObject,
    InsertFile, InvalidFP, LongAddressFromPage, LongVMtoFileSegment, NewFileSegment,
    PageFromAddress, PageNumber, Read, ReleaseFile, SegmentFault, SetEndOfFile,
    SwapIn, SwapOut, Unlock],
  State USING [GetGS, GSHandle],
  Storage USING [Free, Node],
  String USING [AppendString],
  Strings USING [AppendSubString, SubStringDescriptor];

XDUserSegments: PROGRAM
  IMPORTS BcdOps, DebugOps, DLoadState, Frames, Gf, Init,
    SegmentDefs, State, Storage, String, Strings
  EXPORTS DebugOps, Drum, Gf =

BEGIN OPEN DebugOps, DebugFormat, Gf, Drum, MachineDefs;

FrameHandle: TYPE = MachineDefs.FHandle;
GlobalFrameHandle: TYPE = MachineDefs.GFHandle;
FileSegmentHandle: TYPE = SegmentDefs.FileSegmentHandle;

NoCode: ERROR = CODE;

data: State.GSHandle ← State.GetGS[];

-- Utilities

UserSegment: SegmentDefs.FileSegmentObject;

ReadUserSegment: PROCEDURE [s: FileSegmentHandle] RETURNS [FileSegmentHandle] =
  BEGIN
  ShortCopyREAD[to: @UserSegment, from: s,
    nwords: SIZE[SegmentDefs.FileSegmentObject]];
  RETURN [@UserSegment]
  END;

WriteUserSegment: PROCEDURE [s: FileSegmentHandle] =
  BEGIN
  ShortCopyWRITE[ to: s, from: @UserSegment,
    nwords: SIZE[SegmentDefs.FileSegmentObject]];
  END;

-- "Swapping Drum" and user code manipulation

  diHead: Handle ← NIL;
  endHint: SegmentDefs.FileHint;
  endPage: AltoDefs.PageNumber;
  drumFile: SegmentDefs.FileHandle;

  MoveToDrum: PROCEDURE [f: GlobalFrameHandle, co: CodeObject] =
    BEGIN
    LocateCode[f];
    IF gfCache.seg # NIL THEN Alloc[gfCache.seg].di.co ← co;
    FlushCodeCache[];
    RETURN
    END;

  Alloc: PUBLIC PROCEDURE [useg: FileSegmentHandle]
    RETURNS [di: Handle] =
    BEGIN OPEN SegmentDefs;
    p: Handle;
    lfo: FileObject;
    tfile: FileHandle = @lfo; -- copy of user file object
    tseg: FileSegmentHandle; -- copy of user segment
    dseg: FileSegmentHandle = MapUSeg[useg];
    old: FileHandle = dseg.file;
    di ← Storage.Node[SIZE[DrumItem]];
    di.next ← NIL;
    di.dseg ← dseg;
    -- copy values from user segment
    tseg ← ReadUserSegment[di.useg ← useg];
    di.oldBase ← tseg.base;
    di.oldFile ← tseg.file;
    WITH t: tseg SELECT FROM
      disk => di.oldHint ← t.hint;
      ENDCASE => ERROR RemoteSeg[useg
	! UNWIND => Storage.Free[di]];
    -- remove segment from user's file object
    ShortCopyREAD[to: tfile, from: tseg.file, nwords: SIZE[FileObject]];
    tfile.lock ← tfile.lock + 1;
    tfile.segcount ← tfile.segcount - 1;
    IF tseg.swappedin THEN tfile.swapcount ← tfile.swapcount - 1;
    ShortCopyWRITE[from: tfile, to: tseg.file, nwords: SIZE[FileObject]];
    -- move user segment to drum file
    tseg.file ← data.ESV.drumFile;
    tseg.base ← endPage;
    -- reflect new seg and swap counts in users drum file object
    ShortCopyREAD[
      to: tfile, from: data.ESV.drumFile, nwords: SIZE[FileObject]];
    tfile.segcount ← tfile.segcount + 1;
    IF tseg.swappedin THEN tfile.swapcount ← tfile.swapcount + 1;
    ShortCopyWRITE[
      from: tfile, to: data.ESV.drumFile, nwords: SIZE[FileObject]];
    SwapIn[dseg];
    dseg.write ← TRUE;
    -- update seg and swap counts for debugger's files
    old.swapcount ← old.swapcount - 1;
    IF (old.segcount ← old.segcount - 1) = 0 THEN
      ReleaseFile[old];
    drumFile.segcount ← drumFile.segcount + 1;
    drumFile.swapcount ← drumFile.swapcount + 1;
    -- move drum segment to drum file
    dseg.file ← drumFile;
    dseg.base ← endPage;
    WITH d: dseg SELECT FROM
      disk => d.hint ← endHint;
      ENDCASE;
    endPage ← endPage + dseg.pages;
    Unlock[dseg];
    SwapOut[dseg !
      SegmentFault =>
	BEGIN
	SetEndOfFile[drumFile,endPage-1,AltoDefs.BytesPerPage];
	RETRY
	END];
    WITH d: dseg SELECT FROM
      disk => endHint ← d.hint;
      ENDCASE;
    WITH t: tseg SELECT FROM
      disk => t.hint ← endHint;
      ENDCASE;
    WriteUserSegment[useg];
    dseg.write ← FALSE;
    -- add new item to end of list
    IF diHead = NIL THEN diHead ← di
    ELSE FOR p ← diHead, p.next UNTIL p.next = NIL DO
      NULL;
      REPEAT FINISHED => p.next ← di;
      ENDLOOP;
    RETURN
    END;

  Free: PUBLIC PROCEDURE [f: GlobalFrameHandle] =
    BEGIN
    LocateCode[f];
    IF gfCache.seg # NIL THEN Remove[gfCache.seg];
    FlushCodeCache[];
    RETURN
    END;

  Remove: PUBLIC PROCEDURE [useg: FileSegmentHandle] =
    BEGIN OPEN SegmentDefs;
    lfo: FileObject;
    tfile: FileHandle = @lfo; -- copy of user file object
    tseg: FileSegmentHandle; -- copy of user segment
    prev, di: Handle;
    -- find item on the list
    prev ← NIL;
    FOR di ← diHead, di.next UNTIL di = NIL DO
      IF di.useg = useg THEN EXIT;
      prev ← di;
      REPEAT FINISHED => RETURN
      ENDLOOP;
    IF prev = NIL THEN diHead ← di.next
    ELSE prev.next ← di.next;
    -- put old values back into user segment
    tseg ← ReadUserSegment[useg];
    tseg.file ← di.oldFile;
    tseg.base ← di.oldBase;
    WITH t: tseg SELECT FROM
      disk => t.hint ← di.oldHint;
      ENDCASE;
    -- add segment to original file
    ShortCopyREAD[to: tfile, from: tseg.file, nwords: SIZE[FileObject]];
    tfile.lock ← tfile.lock - 1;
    tfile.segcount ← tfile.segcount + 1;
    IF tseg.swappedin THEN tfile.swapcount ← tfile.swapcount + 1;
    ShortCopyWRITE[from: tfile, to: tseg.file, nwords: SIZE[FileObject]];
    -- remove segment from drum file
    ShortCopyREAD[
      to: tfile, from: data.ESV.drumFile, nwords: SIZE[FileObject]];
    tfile.segcount ← tfile.segcount - 1;
    IF tseg.swappedin THEN tfile.swapcount ← tfile.swapcount - 1;
    ShortCopyWRITE[
      from: tfile, to: data.ESV.drumFile, nwords: SIZE[FileObject]];
    WriteUserSegment[useg];
    -- update end values and shuffle
    WITH s: di.dseg SELECT FROM
      disk => endHint ← s.hint;
      ENDCASE;
    endPage ← di.dseg.base;
    DeleteFileSegment[di.dseg]; -- delete the real debugger segment
    ShuffleDrum[di.next];
    Storage.Free[di];
    RETURN
    END;

  CodeOnDrum: PROCEDURE [co: CodeObject] RETURNS [BOOLEAN] =
    BEGIN
    di: Handle;
    FOR di ← diHead, di.next UNTIL di = NIL DO
      IF di.co = co THEN RETURN[TRUE];
      ENDLOOP;
    RETURN[FALSE];
    END;

  ShuffleDrum: PROCEDURE [di: Handle] =
    -- Starting with di, shuffle segments to lower addresses on the drum
    -- and update the user's copies
    BEGIN OPEN SegmentDefs;
    seg: FileSegmentHandle;
    useg: FileSegmentHandle;
    UNTIL di = NIL DO
      SwapIn[seg ← di.dseg];
      useg ← ReadUserSegment[di.useg];
      useg.base ← seg.base ← endPage;
      WITH s: seg SELECT FROM
	disk => s.hint ← endHint;
	ENDCASE;
      WITH u: useg SELECT FROM
	disk => u.hint ← endHint;
	ENDCASE;
      WriteUserSegment[di.useg];
      endPage ← endPage + seg.pages;
      Unlock[seg];
      SwapOut[seg];
      WITH s: seg SELECT FROM
	disk => endHint ← s.hint;
	ENDCASE;
      di ← di.next;
      ENDLOOP;
    END;

  RemoteSeg: PUBLIC SIGNAL [seg: FileSegmentHandle] = CODE;

  MapUSeg: PUBLIC PROCEDURE [useg: FileSegmentHandle] RETURNS [seg: FileSegmentHandle]=
    -- Return a segment in the debugger space for the given user segment
    BEGIN OPEN SegmentDefs;
    tempseg:  FileSegmentHandle;
    localfp: AltoFileDefs.FP;
    tempseg ← ReadUserSegment[useg];
    IF tempseg = NIL OR tempseg.file = NIL THEN RETURN[NIL];
    ShortCopyREAD[
      from: @tempseg.file.fp,
      to: @localfp,
      nwords: SIZE[AltoFileDefs.FP]];
    seg ← NewFileSegment[
      InsertFile[@localfp, Read],tempseg.base,tempseg.pages,Read];
    WITH s: seg SELECT FROM
      disk =>
	s.hint ← WITH t: tempseg SELECT FROM
	  disk => t.hint,
	  ENDCASE => FileHint[AltoFileDefs.eofDA, 0];
      ENDCASE;
    RETURN
    END;

  Initialize: PUBLIC PROCEDURE =
    BEGIN
    next: Handle;
    UNTIL diHead = NIL DO
      next ← diHead.next;
      SegmentDefs.DeleteFileSegment[diHead.dseg];
      Storage.Free[diHead];
      diHead ← next;
      ENDLOOP;
    drumFile ← data.debuggeeFH;
    endHint ← [AltoFileDefs.eofDA, 0];
    endPage ← 256;  -- after core image
    SegmentDefs.SetEndOfFile[drumFile,endPage+19,AltoDefs.BytesPerPage];
    RETURN
    END;

  GetInst: PROC [offset: INTEGER] RETURNS [iword: ControlDefs.InstWord] = {
    SegmentDefs.SwapIn[gfCache.dseg];
    iword ← (SegmentDefs.FileSegmentAddress[gfCache.dseg]+gfCache.offset+offset)↑;
    SegmentDefs.Unlock[gfCache.dseg]};

  ReadCodeByte: PUBLIC PROCEDURE [gf: GFHandle, pc: BytePC] RETURNS [BYTE] = {
    iword: ControlDefs.InstWord ← ReadCodeWord[gf,pc/2];
    RETURN[IF pc MOD 2 = 0 THEN iword.evenbyte ELSE iword.oddbyte]};

  ReadCodeWord: PUBLIC PROCEDURE [gf: GFHandle, offset: INTEGER] RETURNS [u: UNSPECIFIED] =
    BEGIN OPEN SegmentDefs;
    LocateCode[gf];
    SELECT TRUE FROM
      Init.bootLoaded => RETURN[GetInst[offset]];
      gfCache.in OR gfCache.seg = NIL => {
	lpc: LONG POINTER ← gfCache.p+offset;
	RETURN[LongREAD[lpc]]};
      gfCache.seg # NIL =>
	BEGIN ENABLE InvalidFP => GOTO bad;
	useg: FileSegmentHandle = ReadUserSegment[gfCache.seg];
	WITH useg SELECT FROM
	  remote => ERROR RemoteSeg[gfCache.seg];
	  ENDCASE;
	RETURN[GetInst[offset]];
	EXITS bad => RETURN[0];
	END;
      Init.bootLoaded => RETURN[GetInst[offset]];
      ENDCASE;
    ERROR NoCode;
    END;

  WriteCodeByte: PUBLIC PROCEDURE [gf: GlobalFrameHandle, pc: BytePC, inst: BYTE] =
    BEGIN
    iword: ControlDefs.InstWord;
    even: BOOLEAN;
    pi: POINTER TO ControlDefs.InstWord;
    co: CodeObject;
    even ← pc MOD 2 = 0;
    co ← Code[gf];
    IF Init.bootLoaded THEN RETURN;
    IF ~CodeOnDrum[co] THEN MoveToDrum[gf, co];
    LocateCode[gf];
    IF gfCache.in OR gfCache.seg = NIL THEN
      BEGIN
      iword ← LongREAD[gfCache.p+pc/2];
      IF even THEN iword.evenbyte ← inst ELSE iword.oddbyte ← inst;
      LongWRITE[gfCache.p+pc/2, iword];
      END;
    IF gfCache.seg # NIL THEN
      BEGIN OPEN SegmentDefs;
      useg: FileSegmentHandle = ReadUserSegment[gfCache.seg];
      WITH useg SELECT FROM
	remote => ERROR RemoteSeg[gfCache.seg];
	ENDCASE;
      gfCache.dseg.write ← TRUE;
      SwapIn[gfCache.dseg];
      pi ← FileSegmentAddress[gfCache.dseg]+gfCache.offset+pc/2;
      IF even THEN pi.evenbyte ← inst ELSE pi.oddbyte ← inst;
      Unlock[gfCache.dseg];
      END;
    RETURN
    END;

  COCacheObject: TYPE = RECORD [
    gf: GlobalFrameHandle,
    code: CodeObject];

  coCache: COCacheObject ← [NIL,];

  Code: PUBLIC PROCEDURE [f: GlobalFrameHandle] RETURNS [CodeObject] =
    BEGIN OPEN DLoadState, coCache;
    cgfi: MachineDefs.GFTIndex;
    bcd: BcdOps.BcdBase;
    FindModuleSeg: PROCEDURE [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
      RETURNS [BOOLEAN] =
      BEGIN
      IF cgfi IN[mth.gfi..mth.gfi+mth.ngfi) THEN {
	IF Init.bootLoaded THEN FindSeg[bcd, mth.code, f];
	code.seg ← mth.code.sgi; RETURN[TRUE]};
      RETURN[FALSE];
      END;
    IF coCache.gf = f THEN RETURN[code];
    [] ← Acquire[];
    [cgfi,code.config] ← MapRealToConfig[GFI[Original[f]]];
    IF code.config = NullConfig THEN ERROR Frames.Invalid[f];
    bcd ← AcquireBcd[code.config];
    [] ← BcdOps.ProcessModules[bcd, FindModuleSeg];
    ReleaseBcd[bcd];
    Release[];
    coCache.gf ← f;
    RETURN[code]
    END;


  FindSeg: PROC [bcd: BcdOps.BcdBase, code: BcdDefs.CodeDesc, gf: GlobalFrameHandle] =
    BEGIN OPEN SegmentDefs;
    sgb: BcdDefs.Base = LOOPHOLE[bcd+bcd.sgOffset];
    sgh: BcdOps.SGHandle = @sgb[code.sgi];
    f: BcdOps.FTHandle = @LOOPHOLE[bcd+bcd.ftOffset, BcdDefs.Base][sgh.file];
    IF gfCache.gf = gf THEN RETURN;
    FlushCodeCache[];
    SELECT sgh.file FROM
      BcdDefs.FTNull => ERROR NoCode;
      BcdDefs.FTSelf => 
	BEGIN
	bcdseg: FileSegmentHandle ← LongVMtoFileSegment[bcd];
	gfCache.dseg ← NewFileSegment[bcdseg.file, sgh.base, sgh.pages, Read];
	END;
      ENDCASE => gfCache.dseg ← NewFileSegment[OpenFile[bcd,f], sgh.base, sgh.pages, Read];
    gfCache.gf ← gf; gfCache.seg ← NIL; gfCache.p ← NIL;
    gfCache.in ← FALSE; gfCache.offset ← code.offset;
    END;

  OpenFile: PROC [bcd: BcdOps.BcdBase, f: BcdOps.FTHandle]
    RETURNS [fh: SegmentDefs.FileHandle] = {
    ssb: BcdOps.NameString = LOOPHOLE[bcd+bcd.ssOffset];
    ss: Strings.SubStringDescriptor ← [@ssb.string, f.name, ssb.size[f.name]];
    name: STRING ← [40];
    Strings.AppendSubString[name, @ss];
    CheckExtension[name];
    fh ← DebugOps.CacheNewFile[name, SegmentDefs.DefaultAccess] };

  CheckExtension: PROCEDURE [s: STRING] = {
    FOR i: CARDINAL DECREASING IN [1..s.length) DO
      IF s[i] = '. THEN RETURN;
      ENDLOOP;
    String.AppendString[s,".bcd"L]};
  
  FrameCacheObject: TYPE = RECORD [
    gf: GlobalFrameHandle,
    seg: FileSegmentHandle,
    p: LONG POINTER,
    in: BOOLEAN,
    offset: CARDINAL,
    dseg: FileSegmentHandle];

  gfCache: FrameCacheObject ← [NIL,,,,,];

  FlushCodeCache: PROCEDURE =
    BEGIN
    IF gfCache.gf # NIL AND gfCache.dseg # NIL THEN
      SegmentDefs.DeleteFileSegment[gfCache.dseg];
    gfCache.gf ← NIL;
    RETURN;
    END;

  FlushCodeSegmentCache: PUBLIC PROCEDURE =
    BEGIN
    FlushCodeCache[];
    coCache.gf ← NIL;
    RETURN;
    END;

  LN: TYPE = Inline.LongNumber;

  File: PUBLIC PROCEDURE [f: GlobalFrameHandle] RETURNS [SegmentDefs.FileHandle] =
    BEGIN
    co: CodeObject ← Code[f];
    di: Handle;
    fp: AltoFileDefs.FP;
    LocateCode[f];
    IF gfCache.dseg = NIL THEN RETURN[NIL];
    FOR di ← diHead, di.next UNTIL di = NIL DO
      IF di.co = co THEN
	BEGIN OPEN SegmentDefs;
	IF di.oldFile = NIL THEN RETURN[NIL];
	ShortCopyREAD[from: @di.oldFile.fp, to: @fp, nwords: SIZE[AltoFileDefs.FP]];
	RETURN[InsertFile[@fp, Read]]
	END;
      ENDLOOP;
    RETURN[gfCache.dseg.file]
    END;

  LocateCode: PROCEDURE [f: GlobalFrameHandle] =
    BEGIN OPEN SegmentDefs, gfCache;
    gf: GlobalFrame;
    IF gfCache.gf = f THEN RETURN;
    FlushCodeCache[];
    Gf.Check[f];
    IF Init.bootLoaded THEN {[] ← Code[f]; RETURN};
    gfCache.gf ← f;
    in ← TRUE;
    p ← NIL;
    seg ← NIL;
    offset ← 0;
    ShortCopyREAD[from: f, to: @gf, nwords: SIZE[GlobalFrame]];
    IF gf.code.out THEN gf.code.out ← in ← FALSE;
    IF gf.code.highByte # 0 THEN
      BEGIN
      seg ← gf.code.handle;
      IF in THEN
	BEGIN
	LOOPHOLE[p, LN].lowbits ← LOOPHOLE[gf.code.shortbase];
	offset ← LOOPHOLE[gf.code.shortbase, CARDINAL] -
	  LOOPHOLE[AddressFromPage[ReadUserSegment[seg].VMpage], CARDINAL];
	END
      ELSE offset ← gf.code.offset;
      IF ReadUserSegment[seg].swappedin THEN {
	in ← TRUE;
	p ← offset + LongAddressFromPage[ReadUserSegment[seg].VMpage]};
      END
    ELSE
      BEGIN
      p ← gf.code.longbase;
      IF gf.code.otherByte <= LAST[BankIndex] THEN
	BEGIN
	LREAD: PROCEDURE [page: CARDINAL, bank: CARDINAL] RETURNS [POINTER] =
	  MACHINE CODE BEGIN Mopcodes.zRBL, 0 END;
	page: CARDINAL = PageFromAddress[gf.code.shortbase];
	seg ← LREAD[page: page, bank: gf.code.otherByte];
	in ← TRUE;
	offset ← LOOPHOLE[gf.code.shortbase, CARDINAL] -
	  LOOPHOLE[AddressFromPage[ReadUserSegment[seg].VMpage], CARDINAL];
	END;
      END;
    dseg ← IF seg # NIL THEN MapUSeg[seg] ELSE NIL;
    END;


END.