-- Copyright (C) 1981, 1982, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- VMIO.mesa, HGM, 16-Sep-85 23:08:19

-- Last edited by Wobber:  2-Nov-82 10:38:19
-- Last edited by Gobbel: 18-May-81 12:18:20

DIRECTORY
  FileDefs USING [ComparePositions, Completer, FileHandle, PageNumber],
  Inline USING [LowHalf],
  LogDefs USING [DisplayNumber, Percentage],
  VMDefs USING [
    CantReadBackingStore, Error, LookAheadCount, Page, PageAddress, Position,
    Problem],
  VMPrivate USING [
    AcquireCache, AcquirePage, AllocateCacheIndex, CacheIndex, EnterInHashTable,
    EnterInPageTable, FileHandle, FileObject, IndexToHandle, LookupInHashTable,
    nilCacheIndex, PageHandle,
    ReleaseCache, ReleasePage, ValidateFile, ValidatePageNumber, WaitUntilStable];

VMIO: PROGRAM
  IMPORTS FileDefs, Inline, LogDefs, VMDefs, VMPrivate EXPORTS VMDefs, VMPrivate =

  BEGIN OPEN VMDefs, VMPrivate;


  -- Statistics Logging --

  readPageCalls, readPageCacheHits: LONG CARDINAL;

  cacheHitPercent: LogDefs.Percentage;


  -- Miscellaneous Declarations --

  BadAddress: ERROR = CODE;
  WaitUntilStableFailed: ERROR = CODE;


  -- Procedures, Signals, and Types Exported to VMDefs --

  FileObject: PUBLIC TYPE = VMPrivate.FileObject;

  ReadPage: PUBLIC PROCEDURE [addr: PageAddress, lookAhead: LookAheadCount ← 0]
    RETURNS [Page] =
    BEGIN
    page: PageHandle;

    FixUseCount: PROCEDURE = INLINE
      -- decrements page.useCount after an error.
      BEGIN
      AcquirePage[page];  -- needn't remap 'addr' because useCount > 0
      page.useCount ← page.useCount - 1;
      ReleasePage[page];
      END;

    ValidateAddressAndAcquireCache[addr];
    DoReads[addr, lookAhead, TRUE];
    ReleaseCache[];
    page ← IndexToHandle[LookupInHashTable[addr]];  -- Lookup can't fail; useCount > 0
    readPageCalls ← readPageCalls + 1;
    IF page.state = stable THEN readPageCacheHits ← readPageCacheHits + 1;
    cacheHitPercent ← Inline.LowHalf[(readPageCacheHits * 100) / readPageCalls];
    IF WaitUntilStable[page, reading ! CantReadBackingStore => FixUseCount[]] #
      stable THEN ERROR WaitUntilStableFailed;
    RETURN[page.pointer];
    END;

  StartReading: PUBLIC PROCEDURE [
    addr: PageAddress, lookAhead: LookAheadCount ← 0] =
    BEGIN
    ValidateAddressAndAcquireCache[addr];
    DoReads[addr, lookAhead, FALSE];
    ReleaseCache[];
    END;



  -- Procedures and Signals Exported to VMPrivate --

  -- Page I/O --

  WritePageToFS: PUBLIC PROCEDURE [page: PageHandle, wait: BOOLEAN] =
    BEGIN
    vmFile: FileHandle = page.file;
    fFile: FileDefs.FileHandle = vmFile.fh;
    DO
      -- loops only in the case that the write is never started (which can't
      -- happen if extending)
      extending: BOOLEAN;
      newLength: Position;
      [extending, newLength] ← ValidatePageAddress[page];
      page.errorStatus ← ok;
      IF extending THEN
        BEGIN
        vmFile.fs.ops.extend[
          fFile, newLength, page.pointer !
          VMDefs.Error => {page.errorStatus ← reason; CONTINUE}];
        IF page.errorStatus = ok THEN page.dirty ← FALSE;
        ReleasePage[page];
        END
      ELSE
        vmFile.fs.ops.startWrite[
          fFile, page.page, page.pointer, FSWriteComplete, page];
      IF ~wait OR WaitUntilStable[page, writing] = stable THEN EXIT;
      ENDLOOP;
    END;

  -- Start/Stop --

  InitializeVMIO: PUBLIC PROCEDURE =
    BEGIN
    readPageCalls ← readPageCacheHits ← cacheHitPercent ← 0;
    LogDefs.DisplayNumber["VM Cache Hits"L, [percent[@cacheHitPercent]]];
    END;

  FinalizeVMIO: PUBLIC PROCEDURE = {NULL};

  -- Miscellaneous --

  ValidatePageAddress: PUBLIC PROCEDURE [page: PageHandle]
    RETURNS [extending: BOOLEAN, newLength: Position] =
    BEGIN
    vmFile: FileHandle = page.file;
    length: Position = vmFile.fs.ops.getLength[vmFile.fh];
    newLength ← [page.page + 1, 0];
    IF ~(extending ← FileDefs.ComparePositions[newLength, length] = greater) THEN
      RETURN;
    IF newLength.page ~= length.page + 1 THEN ERROR BadAddress;
    END;



  -- Internal Procedures --

  -- Page Input --

  DoReads: PROCEDURE [
    addr: PageAddress, lookAhead: LookAheadCount, bump: BOOLEAN] =
    -- does the common part of ReadPage and StartReading.  If 'bump' is TRUE, the
    -- useCount for the page corresponding to 'addr' will be incremented (this prevents
    -- it from being stolen by any look-ahead).
    BEGIN
    vmFile: FileHandle = addr.file;
    fileLength: Position = vmFile.fs.ops.getLength[vmFile.fh];
    pg: FileDefs.PageNumber;
    actualReads: CARDINAL;

    IF (pg ← addr.page + (IF fileLength.byte = 0 THEN 1 ELSE 0)) > fileLength.page
      THEN ERROR BadAddress;
    actualReads ← MIN[lookAhead, fileLength.page - pg] + 1;
    THROUGH [0..actualReads) DO
      page: PageHandle;
      oldindex, newindex: CacheIndex;
      BEGIN
      IF (oldindex ← LookupInHashTable[addr]) # nilCacheIndex THEN
        GO TO AlreadyIn;
      ReleaseCache[];
      newindex ← AllocateCacheIndex[];
      AcquireCache[];
      page ← IndexToHandle[newindex];
      IF (oldindex ← LookupInHashTable[addr]) # nilCacheIndex THEN {
        page.state ← stable; GO TO AlreadyIn};
      page.file ← addr.file;
      page.page ← addr.page;
      page.dirty ← FALSE;
      EnterInPageTable[page, newindex];
      EnterInHashTable[page];
      vmFile.fs.ops.startRead[
        vmFile.fh, page.page, page.pointer, FSReadComplete,
        page];
      EXITS AlreadyIn => {page ← IndexToHandle[oldindex]};
      END;
      page.age ← new;
      IF bump THEN {page.useCount ← page.useCount + 1; bump ← FALSE};
      addr.page ← addr.page + 1;
      ENDLOOP;
    END;

  FSReadComplete: FileDefs.Completer =
    -- handles termination of remote file read operations initiated by DoReads.
    BEGIN
    page: PageHandle = LOOPHOLE[arg];
    page.errorStatus ← outcome;
    ReleasePage[page];
    END;

  -- Page Output --

  FSWriteComplete: FileDefs.Completer =
    -- handles termination of remote file write operations initiated by WritePageToFS.
    BEGIN
    page: PageHandle = LOOPHOLE[arg];
    IF (page.errorStatus ← outcome) = ok THEN page.dirty ← FALSE;
    ReleasePage[page];
    END;

  -- Miscellaneous --

  ValidateAddressAndAcquireCache: PROCEDURE [addr: PageAddress] =
    BEGIN
    ValidateFile[addr.file];
    ValidatePageNumber[addr.page];
    AcquireCache[];
    END;

  END.