-- VMMgr>MapLogImpl.mesa  (last edited by Levin on 12-Feb-82 18:13:11)

DIRECTORY
  CachedRegion USING [activate, Apply, --deactivate,-- Outcome],
  CachedSpace USING [Desc, Get],
  Environment USING [wordsPerPage],
  File USING [Capability, PageCount],
  Inline USING [LowHalf],
  KernelFile USING [GetFilePoint],
  MapLog USING [],
  PilotSwitches USING [switches --.m--],
  SimpleSpace USING [Create, Handle, Map, Page],
  Space USING [WindowOrigin],
  Utilities USING [LongPointerFromPage],
  VM USING [Interval, PageCount, PageNumber],
  VMMapLog USING [--Descriptor,-- Entry, EntryBasePointer, PatchTable],
  VMMgrStore USING [AllocateMapLogFile],
  VMMPrograms USING [];

MapLogImpl: PROGRAM [pMapLogDesc: LONG POINTER]
  -- logically, this should be a monitor, but it is only called from
  -- SpaceImpl, and is protected by its monitor lock.
  IMPORTS CachedRegion, CachedSpace, Inline, KernelFile, PilotSwitches,
    SimpleSpace, Utilities, VMMgrStore
  EXPORTS MapLog, VMMPrograms =
BEGIN OPEN VMMapLog;

maxPagesPerEntry: CARDINAL = 4096; -- should be in VMMapLog!


-- Note:  the following declaration must match VMMapLog (which we can't yet
-- afford to recompile.)  The only difference is that the EntryPointers are
-- now ORDERED.

Descriptor: TYPE = MACHINE DEPENDENT RECORD [
  self: Entry, -- description of virtual memory used by Pilot to access log
  writer: EntryPointer,
  reader: EntryPointer,
  limit: EntryPointer,
  patchTable: LONG POINTER TO PatchTable];
  
EntryPointer: TYPE = EntryBasePointer RELATIVE ORDERED POINTER [0..177777B] TO Entry;

Grain: TYPE = MACHINE DEPENDENT RECORD [
  SELECT OVERLAID * FROM
    pointer => [p: EntryPointer],
    offset => [n: CARDINAL],
    ENDCASE];

mapLogging: PUBLIC BOOLEAN = PilotSwitches.switches.m = up;

-- Performance parameters:
grainPages: VM.PageCount = 1;  -- this should evenly divide countLog
threshold: CARDINAL = 6*SIZE[Entry];  -- distance from grain boundary that triggers page-in of adjacent grain

pageLog: VM.PageNumber;
countLog: VM.PageCount;
bLog: EntryBasePointer;

-- Invariant for the following pointers:
--  They point at the first and last words, respectively, of the grain of the log that
--  surrounds the last actual entry of the log (i.e., the one pointed to by
--  pM.writer-SIZE[Entry]).  Note that they typically will not point to the start of
--  an entry.
grainStart, grainEnd: Grain;

MapLogFull: ERROR = CODE;

Bug: ERROR [type: BugType] = CODE;
BugType: TYPE = {
  bogusVariant, unmapIntervalDoesntMatchMap, cantFindEntry, activateFailed, smashedEntry};

-- Statistics:

statistics: BOOLEAN = TRUE;

totalMaps, totalUnmaps: LONG CARDINAL ← 0;
multiEntryMaps: LONG CARDINAL ← 0;
topOfStackHits: LONG CARDINAL ← 0;
simpleCompressions: LONG CARDINAL ← 0;
fullCompressions: LONG CARDINAL ← 0;
uselessCompressions: LONG CARDINAL ← 0;
crossGrainUnmaps: LONG CARDINAL ← 0;


WriteLog1: PUBLIC PROCEDURE [
  interval: VM.Interval, pSpaceD: POINTER TO CachedSpace.Desc] =
  BEGIN
  pM: LONG POINTER TO Descriptor = LOOPHOLE[pMapLogDesc];
  Compress: PROCEDURE [howMuch: {oneGrain, wholeLog}] =
    BEGIN
    start, end, out: EntryPointer;
    SELECT howMuch FROM
      oneGrain =>
        BEGIN
        start ← LOOPHOLE[((grainStart.n + SIZE[Entry] - 1)/SIZE[Entry])*SIZE[Entry]];
        end ← pM.writer;
        IF statistics THEN simpleCompressions ← simpleCompressions + 1;
        END;
      wholeLog =>
        BEGIN
        start ← pM.reader;
        end ← pM.limit;
        IF statistics THEN fullCompressions ← fullCompressions + 1;
        END;
      ENDCASE;
    out ← start;
    FOR in: EntryPointer ← start, in + SIZE[Entry] UNTIL in >= end DO
      IF bLog[in].kind ~= nil THEN
        BEGIN
        IF in ~= out THEN
	  BEGIN
	  -- We want to be careful that, if we pagefault while copying this entry,
	  -- the debugger won't get confused.  The logic below assumes it is OK for
	  -- the debugger to see two entries with identical contents.  We'd like to
	  -- write this as follows:
	  -- temp: Entry ← bLog[in];
	  -- IF temp.kind ~= disk THEN ERROR Bug[smashedEntry];
	  -- bLog[out].kind ← temp.kind ← nil;
	  -- bLog[out] ← temp;
	  -- bLog[out].kind ← disk;
	  -- However, the compiler won't let us.  As the result, we have to do
	  -- something ugly...
	  EntryHack: TYPE = MACHINE DEPENDENT RECORD [
	    body(0): SELECT OVERLAID * FROM
	      real => [entry(0): Entry],
	      hack => [fill1(0): CARDINAL, fill2(1:0..13): [0..37777B], tag(1: 14..15): [0..3]],
	      ENDCASE];
	  nilEntry: Entry = [page: , count: , writeProtected: , fill: , filePoint:  nil[]];
	  outPtr: LONG POINTER TO EntryHack ← LOOPHOLE[@bLog[out]];
	  temp: EntryHack;
	  IF (temp.entry ← bLog[in]).kind ~= disk THEN ERROR Bug[smashedEntry];
	  outPtr.tag ← temp.tag ← LOOPHOLE[nilEntry.kind];  -- now the destination looks empty to the debugger
	  outPtr.entry ← temp.entry;  -- throughout this copy, it still looks empty
	  outPtr.tag ← LOOPHOLE[bLog[in].kind];
	  END;
        out ← out + SIZE[Entry];
        END;
      ENDLOOP;
    IF statistics AND end = out THEN uselessCompressions ← uselessCompressions + 1;
    pM.writer ← out;
    END;
  Touch: PROCEDURE [where: {above, below}] =
    BEGIN
    grainOffset: CARDINAL ← grainStart.n/(grainPages*Environment.wordsPerPage);
    SELECT where FROM
      above => IF (grainOffset ← grainOffset + grainPages) >= countLog THEN RETURN;
      below => IF grainOffset = 0 THEN RETURN ELSE grainOffset ← grainOffset - grainPages;
      ENDCASE;
    IF CachedRegion.Apply[pageLog + grainOffset, CachedRegion.activate].outcome ~= [ok[]] THEN
      ERROR Bug[activateFailed];
    END;
  fileOffset: File.PageCount ← 0;
  IF ~mapLogging THEN RETURN;
  IF pSpaceD ~= NIL THEN
    BEGIN
    IF statistics THEN totalMaps ← totalMaps + 1;
    WHILE interval.count > 0 DO
      count: VM.PageCount ← MIN[interval.count, maxPagesPerEntry];
      pEntry: LONG POINTER TO Entry;
      IF pM.writer > grainEnd.p THEN
        BEGIN
        Compress[oneGrain];
        IF pM.writer >= pM.limit THEN
	  BEGIN
	  -- compression accomplished nothing and log is full
	  Compress[wholeLog];
	  IF pM.writer >= pM.limit THEN ERROR MapLogFull;
	  ResetGrain[pM.writer];
	  END
        ELSE
	  IF pM.writer > grainEnd.p THEN ResetGrain[pM.writer];  -- no space acquired; move to next grain
        END;
      pEntry ← @bLog[pM.writer];
      KernelFile.GetFilePoint[pEntry, @pSpaceD.window.file, pSpaceD.window.base + fileOffset];
      pEntry.page ← interval.page;
      count ← pEntry.count ← MIN[pEntry.count, count];  -- describes a physical run
      pEntry.writeProtected ← pSpaceD.writeProtected;
      IF statistics AND fileOffset = 0 --i.e. first time-- AND count ~= interval.count THEN
        multiEntryMaps ← multiEntryMaps + 1;
      fileOffset ← fileOffset + count;
      IF grainEnd.p - pM.writer < threshold THEN Touch[above];
      pM.writer ← pM.writer + SIZE[Entry];
      interval ← [interval.page + count, interval.count - count];
      ENDLOOP;
    END
  ELSE
    BEGIN
    totalCount: VM.PageCount ← interval.count;
    entry: EntryPointer ← pM.writer;
    IF statistics THEN totalUnmaps ← totalUnmaps + 1;
    DO
      IF totalCount = 0 THEN EXIT;
      IF entry = pM.reader THEN ERROR Bug[cantFindEntry];
      entry ← entry - SIZE[Entry];
      WITH e: bLog[entry] SELECT FROM
	disk =>
	  BEGIN
	  upperLimit: VM.PageNumber = interval.page + interval.count;
	  IF e.page IN [interval.page..upperLimit) THEN
	    BEGIN
	    IF e.page + e.count > upperLimit OR e.count > totalCount THEN ERROR Bug[unmapIntervalDoesntMatchMap];
	    totalCount ← totalCount - e.count;
	    bLog[entry].filePoint ← nil[];
	    IF entry = pM.writer - SIZE[Entry] THEN
	      BEGIN  -- unmapping the most recently mapped space.
	      IF statistics AND totalCount = 0 -- don't count each piece -- THEN
	        topOfStackHits ← topOfStackHits + 1;
	      pM.writer ← entry;
	      IF pM.writer < grainStart.p THEN ResetGrain[pM.writer - SIZE[Entry]];
	      END;
	    END;
	  END;
	nil => NULL;
	ENDCASE => ERROR Bug[bogusVariant];
      IF statistics AND entry = grainStart.p THEN crossGrainUnmaps ← crossGrainUnmaps + 1;
      ENDLOOP;
    IF pM.writer - grainStart.p < threshold THEN Touch[below];
    END;
  END;

ResetGrain: PROCEDURE [entry: EntryPointer] =
  BEGIN
  -- resets the grain limit pointers to enclose 'entry'.
  pM: LONG POINTER TO Descriptor = LOOPHOLE[pMapLogDesc];
  grainWords: CARDINAL = grainPages*Environment.wordsPerPage;
  grainStart.n ← (LOOPHOLE[entry, CARDINAL]/grainWords)*grainWords;
  grainEnd.p ← MIN[grainStart.p + grainWords, pM.limit] - 1;
  END;

Initialize: PROCEDURE =
  BEGIN
  pM: LONG POINTER TO Descriptor = LOOPHOLE[pMapLogDesc];
  handle: SimpleSpace.Handle;
  window: Space.WindowOrigin;
  desc: CachedSpace.Desc;

  IF ~mapLogging THEN RETURN;
  countLog ← Inline.LowHalf[VMMgrStore.AllocateMapLogFile[pWindowResult: @window].count];
  KernelFile.GetFilePoint[@pM.self, @window.file, window.base];
  handle ← SimpleSpace.Create[count: countLog, location: hyperspace, sizeSwapUnit: grainPages];
  SimpleSpace.Map[handle: handle, window: window, andPin: FALSE];
  pM.self.page ← pageLog ← SimpleSpace.Page[handle];
  pM.self.count ← MIN[pM.self.count, countLog];
  pM.writer ← pM.reader ← FIRST[EntryPointer];
  pM.limit ← LOOPHOLE[countLog*Environment.wordsPerPage/SIZE[Entry]*SIZE[Entry], EntryPointer];
  ResetGrain[pM.writer];
  bLog ← LOOPHOLE[Utilities.LongPointerFromPage[pM.self.page]];
  -- map log space is set up, now map log it`
  CachedSpace.Get[@desc, LOOPHOLE[handle]];
  WriteLog1[interval: [pM.self.page, countLog], pSpaceD: @desc];
  END;

Initialize[];


END.


LOG

Time: August 1, 1978  10:11 AM	By: McJones
	Action: Create file
Time: August 7, 1978  4:51 PM	By: McJones
	Action: pDesc.self.page wasn't initialized
Time: August 8, 1978  9:10 AM	By: McJones
	Action: WriteLog didn't set entry page field in case of non-nil
	pWindow
Time: August 8, 1978  3:25 PM	By: McJones
	Action: limit initialization didn't convert pages to words
Time: August 29, 1978  4:44 PM	By: McJones
	Action: Add VMMode
Time: September 5, 1978  6:48 PM	By: McJones
	Action: Replace signal with CleanMapLog[], GetFilePoint moved
	to SpecialFile
Time: September 15, 1978  4:47 PM	By: McJones
	Action: Getting ready for "uniform" swap unit management
Time: September 29, 1978  11:05 AM	By: McJones
	CR20.42: Replace PutRootFile with MakePermanent
Time: July 31, 1979  1:12 PM	By: McJones
	Action: Prepared to add writeProtected to map log entry
Time: August 16, 1979  8:56 PM	By: McJones
	Action: Add writeProtected to map log entry; SpecialFile =>
	KernelFile; VMMode => PilotSwitches
Time: September 4, 1979  10:00 AM	By: Forrest
	Action: Change to use PilotFileTypes
Time: November 7, 1979  2:39 PM	By: McJones
	AR2744: Create backing file even if map log disabled
Time: November 21, 1979  9:18 AM	By: Knutsen
	Action: Use backing file now provided by STLeafImpl.
Time: June 3, 1980  12:19 PM	By: Knutsen
	Action: Use Uniform Swap Units. Activate/deactivate as appropriate.
	Named the errors.
Time: December 19, 1980  11:10 AM	By: Gobbel
	Action: Remove requirement that map log file be contiguous.
Time: January 9, 1981  3:22 PM	By: Gobbel
	Action: Fix bug introduced by previous change: make count of "self"
	entry be MIN of self.count and countLog, instead of size of whole
	page group.
Time: January 13, 1981  12:03 PM	By: Gobbel
	Action: Make mapLogging be PUBLIC, chane WriteLog to WriteLog1.
Time: 23-Nov-81 10:28:37		By: Levin
	Action: Implementation completely changed.  The map log is now a
	complete history rather than a chronological log of recent events.
	Although in principle this would require an unreasonbly large
	number of entries, empirical observation shows that, in practice,
	a plateau is reach quite quickly and that most Map/Unmap activity is
	essentially stack-like.  The algorithm takes care that the
	non-stack-like cases do not significantly perturb the working set.
	Specifically, a Map operation never touches more than one stack "grain",
	unless the grain is completely full.  An Unmap operation only touches
	multiple grains when the corresponding map entry is not near the top
	of the stack.  We take care to avoid thrashing at grain boundaries and
	unnecessary compression of dead entries in the stack.
Time:  4-Feb-82 11:47:42		By: Levin
	Action: Compression is now careful to avoid an incomplete entry that
	might confuse the debugger.
Time: 12-Feb-82 18:13:11		By: Levin
	Action: Fix bug in compression that loses first entry in log; rename
	BugTypes to be more intelligible.