-- UserSegments.Mesa
-- Edited by:
--             Sandman on May 5, 1980  6:06 PM
--             Johnsson on August 30, 1978  12:02 PM
--             Barbara on April 6, 1979  12:22 PM
--             Bruce on July 2, 1980  5:39 PM

DIRECTORY
  AltoDefs USING [BYTE, BytesPerPage, PageNumber],
  AltoFileDefs USING [eofDA, FP],
  BcdDefs USING [GFTIndex, MTIndex],
  BcdOps USING [BcdBase, MTHandle, ProcessModules],
  ControlDefs USING [BYTE, BytePC, FrameHandle, GFTIndex, GlobalFrame, GlobalFrameHandle, InstWord],
  DebugFormat USING [CodeObject],
  DebugOps USING [LongREAD, LongWRITE, ShortCopyREAD, ShortCopyWRITE],
  Drum USING [DrumItem, Handle],
  Frames USING [Invalid],
  Gf USING [Check, GFI, Handle, Original],
  InlineDefs USING [LongNumber],
  LoadStateOps USING [AcquireBcd, InputLoadState, MapRealToConfig, NullConfig, ReleaseBcd, ReleaseLoadState],
  Mopcodes USING [zRBL],
  SegmentDefs USING [AddressFromPage, BankIndex, DeleteFileSegment, FileHandle, FileHint, FileObject, FileSegmentAddress, FileSegmentHandle, FileSegmentObject, InsertFile, InvalidFP, NewFileSegment, PageFromAddress, PageNumber, Read, ReleaseFile, SegmentFault, SetEndOfFile, SwapIn, SwapOut, Unlock],
  State USING [GetGS, GSHandle],
  Storage USING [Free, Node];

UserSegments: PROGRAM
  IMPORTS BcdOps, DebugOps, Frames, Gf, LoadStateOps,
    SegmentDefs, State, Storage
  EXPORTS DebugOps, Drum, Gf =

BEGIN OPEN DebugOps, DebugFormat, Gf, Drum;

BytePC: TYPE = ControlDefs.BytePC;
BYTE: TYPE = AltoDefs.BYTE;
FrameHandle: TYPE = ControlDefs.FrameHandle;
GlobalFrameHandle: TYPE = ControlDefs.GlobalFrameHandle;
FileHandle: TYPE = SegmentDefs.FileHandle;
FileSegmentHandle: TYPE = SegmentDefs.FileSegmentHandle;
InstWord: TYPE = ControlDefs.InstWord;

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

-- Utilities

UserSegment: SegmentDefs.FileSegmentObject;

UserFileSegmentAddress: PROCEDURE[useg: FileSegmentHandle]
  RETURNS[POINTER] =
  BEGIN
  RETURN[SegmentDefs.AddressFromPage[ReadUserSegment[useg].VMpage]]
  END;

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: 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;

  ReadCodeByte: PUBLIC PROCEDURE [gf: GlobalFrameHandle, pc: BytePC]
    RETURNS [BYTE] =
    BEGIN OPEN SegmentDefs;
    iword: InstWord;
    lpc: LONG POINTER;
    patched: BOOLEAN ← FALSE;
    LocateCode[gf];
    lpc ← gfCache.p+pc/2;
    IF (gfCache.in OR gfCache.seg = NIL) AND ~patched THEN
      iword ← LongREAD[lpc]
    ELSE
      IF gfCache.seg # NIL THEN
	BEGIN ENABLE InvalidFP => BEGIN iword ← LOOPHOLE[-1]; CONTINUE END;
	useg: FileSegmentHandle = ReadUserSegment[gfCache.seg];
	WITH useg SELECT FROM
	  remote => ERROR RemoteSeg[gfCache.seg];
	  ENDCASE;
	SwapIn[gfCache.dseg];
	iword ← (FileSegmentAddress[gfCache.dseg]+gfCache.offset+pc/2)↑;
	Unlock[gfCache.dseg];
	END;
    RETURN[IF pc MOD 2 = 0 THEN iword.evenbyte ELSE iword.oddbyte]
    END;

  ReadCodeWord: PUBLIC PROCEDURE [gf: GlobalFrameHandle, offset: INTEGER]
    RETURNS [iword: UNSPECIFIED] =
    BEGIN OPEN SegmentDefs;
    lpc: LONG POINTER;
    LocateCode[gf];
    lpc ← gfCache.p+offset;
    IF (gfCache.in OR gfCache.seg = NIL) THEN iword ← LongREAD[lpc]
    ELSE
      IF gfCache.seg # NIL THEN
	BEGIN ENABLE InvalidFP => BEGIN iword ← 0; CONTINUE END;
	useg: FileSegmentHandle = ReadUserSegment[gfCache.seg];
	WITH useg SELECT FROM
	  remote => ERROR RemoteSeg[gfCache.seg];
	  ENDCASE;
	SwapIn[gfCache.dseg];
	iword ← (FileSegmentAddress[gfCache.dseg]+gfCache.offset+offset)↑;
	Unlock[gfCache.dseg];
	END;
    RETURN
    END;


  WriteCodeByte: PUBLIC PROCEDURE [gf: GlobalFrameHandle, pc: BytePC, inst: BYTE] =
    BEGIN
    iword: InstWord;
    even: BOOLEAN;
    pi: POINTER TO InstWord;
    co: CodeObject;
    even ← pc MOD 2 = 0;
    co ← Code[gf];
    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 LoadStateOps, coCache;
    cgfi: ControlDefs.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
	BEGIN code.seg ← mth.code.sgi; RETURN[TRUE]; END;
      RETURN[FALSE];
      END;
    IF coCache.gf = f THEN RETURN[code];
    [] ← InputLoadState[];
    [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];
    ReleaseLoadState[];
    coCache.gf ← f;
    RETURN[code]
    END;


  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;

  -- copied from GlobalFrameDefs.mesa

  GlobalFrame: TYPE = MACHINE DEPENDENT RECORD [
    gfi: [0..777B],
    unused: [0..1], -- reserved for future gfi expansion
    copied, alloced, shared, started: BOOLEAN,
    trapxfers, codelinks: BOOLEAN,
    code: FrameCodeBase,
    global: ARRAY [0..0) OF UNSPECIFIED];

  LN: TYPE = InlineDefs.LongNumber;

  FrameCodeBase: TYPE = MACHINE DEPENDENT RECORD [
    SELECT OVERLAID * FROM
      in => [
	SELECT OVERLAID * FROM
	  codebase => [
	    codebase: LONG POINTER],
	  shortCodebase => [
	    shortCodebase: UNSPECIFIED,
	    highHalf: CARDINAL],
	  ENDCASE],
      out => [
	offset: CARDINAL,
	handle: POINTER],
      either => [
	fill1: [0..77777B],
	swappedout: BOOLEAN,
	highByte, topByteOfLongPointer: [0..377B]],
      ENDCASE];

  File: PUBLIC PROCEDURE [f: GlobalFrameHandle] RETURNS [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];
    gfCache.gf ← f;
    in ← TRUE;
    p ← NIL;
    seg ← NIL;
    offset ← 0;
    ShortCopyREAD[from: f, to: @gf, nwords: SIZE[GlobalFrame]];
    IF gf.code.swappedout THEN gf.code.swappedout ← in ← FALSE;
    IF gf.code.highByte # 0 THEN
      BEGIN
      seg ← gf.code.handle;
      IF in THEN
	BEGIN
	LOOPHOLE[p, LN].lowbits ← gf.code.shortCodebase;
	offset ← gf.code.shortCodebase -
	  LOOPHOLE[AddressFromPage[ReadUserSegment[seg].VMpage], CARDINAL];
	END
      ELSE offset ← gf.code.offset;
      IF ReadUserSegment[seg].swappedin THEN
	BEGIN
	in ← TRUE;
	LOOPHOLE[p, LN].lowbits ← offset +
	  LOOPHOLE[AddressFromPage[ReadUserSegment[seg].VMpage], CARDINAL];
	END;
      END
    ELSE
      BEGIN
      p ← gf.code.codebase;
      IF gf.code.topByteOfLongPointer <= LAST[BankIndex] THEN
	BEGIN
	LREAD: PROCEDURE [page: CARDINAL, bank: CARDINAL] RETURNS [POINTER] =
	  MACHINE CODE BEGIN Mopcodes.zRBL, 0 END;
	page: CARDINAL = PageFromAddress[gf.code.shortCodebase];
	seg ← LREAD[page: page, bank: gf.code.topByteOfLongPointer];
	in ← TRUE;
	offset ← gf.code.shortCodebase -
	  LOOPHOLE[AddressFromPage[ReadUserSegment[seg].VMpage], CARDINAL];
	END;
      END;
    dseg ← IF seg # NIL THEN MapUSeg[seg] ELSE NIL;
    END;


END.