-- SegmentsA.Mesa  Edited by Sandman on August 18, 1980  3:33 PM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [MaxMDSPage, PageSize],
  AltoFileDefs USING [CFA, eofDA, FA, FP, vDA],
  DiskDefs USING [DiskCheckError, DiskPageDesc, DiskRequest, nSectors, SwapPages],
  InlineDefs USING [BITAND, DIVMOD, HighHalf, LongDiv, LowHalf],
  NucleusOps USING [],
  ProcessDefs USING [DisableInterrupts, EnableInterrupts],
  Region USING [Index, Page, PagesPerRegion],
  SegmentDefs USING [
    AccessOptions, AddressFromPage, AllocInfo, Append, DataSegmentAddress,
    DataSegmentHandle, DefaultAccess, DefaultBase, DefaultPages,
    DeleteDataSegment, EasyDown, EnumerateFileSegments, FileAccessError,
    FileError, FileHandle, FileHint, FileSegmentClass, FileSegmentHandle,
    GetEndOfFile, MakeDataSegment, MaxRefs, MaxSegs, NewDataSegment, Object,
    ObjectHandle, ObjectType, OpenFile, PageFromAddress, PageCount, PageNumber,
    Read, RefCount, ReleaseFile, SegCount, SegLockCount, SegmentHandle,
    SegmentLocation, SegmentType, SetFileAccess, SwapError, SwapOut, Write],
  SwapperOps USING [AllocateObject, LiberateObject, regions, UpdateVM];

SegmentsA: PROGRAM
  IMPORTS SwapperOps, DiskDefs, InlineDefs, ProcessDefs, SegmentDefs
  EXPORTS SwapperOps, SegmentDefs, NucleusOps
  SHARES SegmentDefs =
  BEGIN OPEN AltoFileDefs, SwapperOps, SegmentDefs;

  InvalidSegmentSize: PUBLIC SIGNAL [pages: PageCount] = CODE;

  NewFileSegment: PUBLIC PROCEDURE [
    file: FileHandle, base: PageNumber, pages: PageCount, access: AccessOptions]
    RETURNS [seg: FileSegmentHandle] =
    BEGIN OPEN InlineDefs;
    IF access = DefaultAccess THEN access ← Read;
    IF file.segcount = MaxSegs THEN ERROR FileError[file];
    IF BITAND[access, Append] # 0 THEN ERROR FileAccessError[file];
    IF base = DefaultBase THEN base ← 1;
    IF pages = DefaultPages THEN pages ← GetEndOfFile[file].page - base + 1;
    IF pages ~IN (0..AltoDefs.MaxMDSPage + 1] THEN
      ERROR InvalidSegmentSize[pages];
    SetFileAccess[file, access];
    seg ← LOOPHOLE[AllocateObject[SIZE[file segment Object]]];
    seg↑ ← Object[
      busy: FALSE,
      body: segment[
      VMpage: 0,
      info: file[
      swappedin: FALSE, write: BITAND[access, Write] # 0, class: other,
      inuse: FALSE, lock: 0, file: file, base: base, pages: pages,
      location: disk[FileHint[eofDA, 0]]]]];
    file.segcount ← file.segcount + 1;
    RETURN
    END;

  DeleteFileSegment: PUBLIC PROCEDURE [seg: FileSegmentHandle] =
    BEGIN
    file: FileHandle ← seg.file;
    SwapOut[seg];
    LiberateObject[seg];
    file.segcount ← file.segcount - 1;
    IF file.segcount = 0 THEN ReleaseFile[file];
    RETURN
    END;

  FileSegmentAddress: PUBLIC PROCEDURE [seg: FileSegmentHandle]
    RETURNS [POINTER] =
    BEGIN
    IF ~seg.swappedin THEN ERROR SwapError[seg];
    RETURN[AddressFromPage[seg.VMpage]]
    END;

  -- Segment Positioning


  PositionSeg: PUBLIC PROCEDURE [seg: FileSegmentHandle, useseg: BOOLEAN]
    RETURNS [BOOLEAN] =
    BEGIN -- returns TRUE if it read a non-null page into the segment.
    cfa: CFA;
    buf: DataSegmentHandle;
    buffer: POINTER;
    WITH s: seg SELECT FROM
      disk =>
	BEGIN
	IF s.hint.da = eofDA AND s.base > 8 AND s.file.segcount > 1 THEN
	  FindSegHint[@s];
	IF s.hint.da = eofDA OR s.hint.page # s.base THEN
	  BEGIN
	  buffer ←
	    IF useseg THEN AddressFromPage[s.VMpage]
	    ELSE DataSegmentAddress[buf ← NewDataSegment[DefaultBase, 1]];
	  cfa.fp ← s.file.fp;
	  cfa.fa ← FA[s.hint.da, s.hint.page, 0];
	  [] ← JumpToPage[
	    @cfa, s.base, buffer !
	    UNWIND => IF ~useseg THEN DeleteDataSegment[buf]];
	  IF ~useseg THEN DeleteDataSegment[buf];
	  IF cfa.fa.page # s.base THEN ERROR SwapError[@s];
	  s.hint ← FileHint[cfa.fa.da, cfa.fa.page];
	  RETURN[useseg AND cfa.fa.byte # 0];
	  END;
	END;
      ENDCASE;
    RETURN[FALSE]
    END;

  FindSegHint: PUBLIC PROCEDURE [seg: FileSegmentHandle] =
    BEGIN
    CheckHint: PROCEDURE [other: FileSegmentHandle] RETURNS [BOOLEAN] =
      BEGIN
      WITH o: other SELECT FROM
	disk =>
	  BEGIN
	  IF o.file = seg.file AND o.hint.da # eofDA AND o.hint.page IN
	    (hint.page..seg.base] THEN hint ← o.hint;
	  RETURN[hint.page = seg.base]
	  END;
	ENDCASE;
      RETURN[FALSE]
      END;
    hint: FileHint;
    WITH s: seg SELECT FROM
      disk =>
	BEGIN
	hint ← s.hint;
	[] ← EnumerateFileSegments[CheckHint];
	s.hint ← hint;
	END;
      ENDCASE;
    RETURN
    END;

  GetFileSegmentDA: PUBLIC PROCEDURE [seg: FileSegmentHandle] RETURNS [vDA] =
    BEGIN
    WITH s: seg SELECT FROM
      disk => BEGIN [] ← PositionSeg[seg, FALSE]; RETURN[s.hint.da]; END;
      ENDCASE;
    RETURN[AltoFileDefs.eofDA]
    END;

  SetFileSegmentDA: PUBLIC PROCEDURE [seg: FileSegmentHandle, da: vDA] =
    BEGIN
    WITH s: seg SELECT FROM disk => s.hint ← FileHint[da, s.base]; ENDCASE;
    RETURN
    END;

  -- File Positioning

  jump: INTEGER = 10*DiskDefs.nSectors;

  InvalidFP: PUBLIC SIGNAL [fp: POINTER TO FP] = CODE;

  JumpToPage: PUBLIC PROCEDURE [
    cfa: POINTER TO CFA, page: PageNumber, buf: POINTER]
    RETURNS [prev, next: vDA] =
    BEGIN OPEN DiskDefs;
    desc: DiskPageDesc;
    da: vDA ← cfa.fa.da;
    startpage: PageNumber;
    direction: INTEGER ← 1;
    firstpage: PageNumber ← cfa.fa.page;
    arg: swap DiskRequest ← DiskRequest[
      buf, @da, , , @cfa.fp, TRUE, ReadD, ReadD, TRUE, swap[@desc]];
    BEGIN
    IF da = eofDA THEN GO TO reset;
    SELECT firstpage - page FROM
      <= 0 => NULL;
      = 1, < firstpage/10 => direction ← -1;
      ENDCASE => GO TO reset;
    EXITS reset => BEGIN firstpage ← 0; da ← cfa.fp.leaderDA; END;
    END;
    BEGIN
    ENABLE
      DiskCheckError --[page]-- =>
	BEGIN
	IF page # startpage THEN RESUME
	;
	IF startpage = 0 THEN GO TO failed;
	firstpage ← 0;
	da ← cfa.fp.leaderDA;
	direction ← 1;
	RETRY;
	END;
    IF da = eofDA THEN GO TO failed;
    startpage ← firstpage;
    UNTIL da = eofDA DO
      arg.firstPage ← firstpage;
      arg.lastPage ←
	IF direction < 0 THEN firstpage ELSE MIN[page, firstpage + jump - 1];
      [] ← SwapPages[@arg];
      IF desc.page = page THEN EXIT;
      da ← IF direction < 0 THEN desc.prev ELSE desc.next;
      firstpage ← desc.page + direction;
      ENDLOOP;
    cfa.fa ← FA[desc.this, desc.page, desc.bytes];
    RETURN[desc.prev, desc.next];
    EXITS failed => ERROR InvalidFP[@cfa.fp];
    END;
    END;

  ChangeDataToFileSegment: PUBLIC PROCEDURE [
    dataseg: DataSegmentHandle, fileseg: FileSegmentHandle] =
    BEGIN
    IF dataseg.pages # fileseg.pages OR ~fileseg.write OR fileseg.swappedin OR
      fileseg.file.swapcount = MaxRefs THEN SIGNAL SwapError[fileseg];
    IF ~fileseg.file.open THEN OpenFile[fileseg.file];
    ProcessDefs.DisableInterrupts[];
    fileseg.swappedin ← TRUE;
    fileseg.lock ← fileseg.lock + 1;
    fileseg.file.swapcount ← fileseg.file.swapcount + 1;
    SwapperOps.UpdateVM[
      base: fileseg.VMpage ← dataseg.VMpage, pages: fileseg.pages, seg: fileseg];
    ProcessDefs.EnableInterrupts[];
    SwapperOps.LiberateObject[dataseg];
    END;

  InvalidLongPointer: PUBLIC ERROR [lp: LONG UNSPECIFIED] = CODE;

  ValidateLongPointer: PUBLIC PROCEDURE [a: LONG UNSPECIFIED] =
    BEGIN
    page: PageNumber;
    index: Region.Index;
    IF InlineDefs.HighHalf[a] > LAST[Region.Index] THEN
      ERROR InvalidLongPointer[a];
    page ← InlineDefs.LongDiv[a, AltoDefs.PageSize];
    [quotient: index] ← InlineDefs.DIVMOD[page, Region.PagesPerRegion];
    IF regions[index] = NIL THEN ERROR InvalidLongPointer[a];
    END;

  LongVMtoSegment: PUBLIC PROCEDURE [a: LONG POINTER] RETURNS [SegmentHandle] =
    BEGIN
    relPage: Region.Page;
    ValidateLongPointer[a];
    relPage ← PageFromAddress[InlineDefs.LowHalf[a]];
    RETURN[regions[InlineDefs.HighHalf[a]].status[relPage].seg]
    END;

  LongVMtoFileSegment: PUBLIC PROCEDURE [a: LONG POINTER]
    RETURNS [FileSegmentHandle] =
    BEGIN
    seg: SegmentHandle ← LongVMtoSegment[a];
    IF seg = NIL THEN RETURN[NIL];
    WITH s: seg SELECT FROM file => RETURN[@s]; ENDCASE;
    RETURN[NIL];
    END;


  END.