-- File: DBStorageVecImpl.mesa
-- Last edited by:
--   MBrown on August 7, 1982 9:55 pm
-- Last Edited by: Cattell, January 16, 1983 11:40 am

  DIRECTORY
    DBCommon USING[WordsPerPage],
    DBEnvironment USING[InternalError],
    DBStats,
    DBHeapStorage USING[Node, Free],
    DBStorageVec,
    DBStorageTID USING[TIDSlotMask],
    Inline;

DBStorageVecImpl: PROGRAM
  IMPORTS
    DBEnvironment,
    DBHeapStorage,
    DBStats,
    DBStorageVec,
    I: Inline
  EXPORTS
    DBStorageVec
  SHARES DBStorageVec
  = BEGIN OPEN DBEnvironment;

  VecPage: TYPE = DBStorageVec.VecPage;
  Slot: TYPE = DBStorageVec.Slot;
  VecHeader: TYPE = DBStorageVec.VecHeader;

  -- This module implements all of DBStorageVec.


  --    Constants relating to persistent structure (some others are in DBStorageVec).

  -- Distinguished values for VecHeader.slotIndex:
  FreeSlotIndex: DBStorageVec.SlotIndexField = 0;
    -- In a VecHeader, this means that the vec is free.  This slot points to freeVec.
  NonsenseSlotIndex: CARDINAL = LAST[CARDINAL];
    -- Returned by failing AllocVec as the slot result; should trap if used later.


  --    Switches to control the amount of redundant checking compiled in.
  ValidateInput: BOOL = TRUE;
    -- Compile code to check the nWords input to AllocVec, slotIndex to FreeVec, etc.
    --(Don't exhaustively check input pages).
  CheckPage: BOOL = TRUE;
    -- Compile code to check properties of the page structure during AllocVec, FreeVec, etc.
    --(Don't exhaustively check input pages).
  ExhaustivelyCheckPage: BOOL = FALSE;
    -- Compile code to exhaustively check input pages, and recheck before returning,
    --for AllocVec, FreeVec, ModifyVec.


  --    Inlines


  VecOfOffset: PROC[p: LONG POINTER TO VecPage, offset: CARDINAL]
   RETURNS[LONG POINTER TO VecHeader] = INLINE BEGIN
    RETURN[LOOPHOLE[p+offset, LONG POINTER TO VecHeader]];
  END;--VecOfOffset

  FreeVecOffset: PROC[p: LONG POINTER TO VecPage] RETURNS[CARDINAL] = INLINE BEGIN
    -- Returns the offset of freeVec.
    RETURN[LOOPHOLE[p + (DBCommon.WordsPerPage-SIZE[Slot]), LONG POINTER TO Slot].vecOffset];
  END;--FreeVecOffset 

  FreeVecPtr: PROC[p: LONG POINTER TO VecPage] RETURNS[LONG POINTER TO VecHeader] = INLINE BEGIN
    -- Returns a pointer to freeVec.
    RETURN[VecOfOffset[p, FreeVecOffset[p]]];
  END;--FreeVecPtr 

  InitializeVecPage: PUBLIC PROC[p: LONG POINTER TO VecPage, pageTag: CARDINAL] = BEGIN
    -- Creates an empty page in page p of the cache, ready to store vecs using the
    --procedures below.  The new page has tag = pageTag.
    WordsFree: CARDINAL = DBCommon.WordsPerPage - SIZE[VecPage] - SIZE[Slot];
      -- Number of words free after the page header and free slot are created.
    DBStats.Inc[StorageInitVecPage];
    p↑ ← VecPage[tag: pageTag, highSlot: 0, nFreeSlots: 0, nWordsInFreeVecs: WordsFree];
    LOOPHOLE[p+SIZE[VecPage], LONG POINTER TO VecHeader]↑ ← 
      VecHeader[slotIndex: FreeSlotIndex, length: WordsFree];
    LOOPHOLE[p+DBCommon.WordsPerPage-SIZE[Slot], LONG POINTER TO Slot]↑ ← 
      Slot[type: DBStorageVec.UnFreeType, vecOffset: SIZE[VecPage]];
  END;--InitializeVecPage


  CheckVecPage: PUBLIC PROC[p: LONG POINTER TO VecPage, pageTag: CARDINAL] = BEGIN
    -- Verifies that the internal structure of the page is consistent, and that
    --it has tag = pageTag.  This proc clearly cannot check that the data stored
    --in the vecs themselves is correct.  May die horribly if the page is really bad.

    OPEN pageHdr: p;

    Assert: PROC[condition: BOOL] = BEGIN
      IF ~condition THEN ERROR InternalError; -- CheckVecPageFailed
    END;--Assert

    -- tag is correct.
    DBStats.Inc[StorageCheckVecPage];
    Assert[pageTag = DBStorageVec.TagOfPage[p]];
    -- nFreeSlots is correct.
    BEGIN nFreeSlots: CARDINAL ← 0;  curSlot: CARDINAL;
    FOR curSlot IN [1..DBStorageVec.HighSlotIndexOfPage[p]] DO
      IF DBStorageVec.TypeOfSlot[p, curSlot] = DBStorageVec.FreeType THEN
        nFreeSlots  ← nFreeSlots + 1;
    ENDLOOP;
    Assert[nFreeSlots = pageHdr.nFreeSlots];
    END;
    -- FreeSlot is UnFree, freeVec is free and immediately above highSlot.
    BEGIN freeVecPtr: LONG POINTER TO VecHeader ← FreeVecPtr[p];
    Assert[LOOPHOLE[p+DBCommon.WordsPerPage-SIZE[Slot],LONG POINTER TO Slot].type =
            DBStorageVec.UnFreeType];
    Assert[freeVecPtr.slotIndex = FreeSlotIndex];
    Assert[LOOPHOLE[freeVecPtr + freeVecPtr.length, LONG POINTER TO Slot] =
            DBStorageVec.IndexToSlot[p, DBStorageVec.HighSlotIndexOfPage[p]]];
    END;
    -- The non-slot area is full of vecs.  Non-free vecs point to (unique) non-free slots, that 
    --point back.  (We use the pigeonhole principle to test uniqueness by counting non-free vecs).
    BEGIN freeWords: CARDINAL ← FreeVecPtr[p].length;  nonFreeVecs: CARDINAL ← 0;
    curVecPtr: LONG POINTER TO VecHeader ← VecOfOffset[p, SIZE[VecPage]];
    WHILE curVecPtr # FreeVecPtr[p] DO
      IF curVecPtr.slotIndex = FreeSlotIndex THEN freeWords ← freeWords + curVecPtr.length
      ELSE BEGIN
        Assert[DBStorageVec.TypeOfSlot[p, curVecPtr.slotIndex] # DBStorageVec.FreeType];
        Assert[DBStorageVec.VecOfSlot[p, curVecPtr.slotIndex] = curVecPtr];
        nonFreeVecs ← nonFreeVecs + 1;
      END;--IF
      curVecPtr ← curVecPtr + curVecPtr.length;
    ENDLOOP;
    Assert[freeWords = pageHdr.nWordsInFreeVecs];
    Assert[nonFreeVecs + pageHdr.nFreeSlots = pageHdr.highSlot];
    END;
  END;--CheckVecPage

  AllocVec: PUBLIC PROC[p: LONG POINTER TO VecPage, nWords: CARDINAL]
   RETURNS[--slotIndex-- CARDINAL, --success-- BOOL] = BEGIN
    -- Makes a new vec of total length nWords (including vec header), a slot to hold it,
    --and returns the index of the slot.  If ~success, then the call failed for lack
    --of space, and slotIndex is garbage.

    OPEN pageHdr: p;
    newSlotIndex: CARDINAL;
    BEGIN--block for exit GOTOs
    newSlotPtr: LONG POINTER TO Slot;
    freeVecPtr: LONG POINTER TO VecHeader ← FreeVecPtr[p];
    oldFreeVecLength: CARDINAL;
    DBStats.Inc[StorageAllocVec];
    IF ExhaustivelyCheckPage THEN DBStorageVec.CheckVecPage[p, pageHdr.tag];
    IF ValidateInput AND (nWords < SIZE[VecHeader]) THEN
      ERROR InternalError; -- NWordsTooSmall
    IF pageHdr.nFreeSlots = 0 THEN BEGIN
      IF pageHdr.nWordsInFreeVecs < nWords+SIZE[VecHeader]+SIZE[Slot] THEN
        GOTO Failure;
      IF pageHdr.highSlot = DBStorageTID.TIDSlotMask THEN GOTO Failure;
      IF freeVecPtr.length = SIZE[VecHeader] THEN BEGIN
        CompactPage[p];  freeVecPtr ← FreeVecPtr[p];
      END;--IF
      -- Create a new slot numbered one higher than the highest existing slot.
      newSlotIndex ← pageHdr.highSlot ← pageHdr.highSlot + 1;
      newSlotPtr ← DBStorageVec.IndexToSlot[p, newSlotIndex];
      IF CheckPage AND (freeVecPtr + freeVecPtr.length-1 #
       LOOPHOLE[newSlotPtr, LONG POINTER TO VecHeader]) THEN
        ERROR InternalError; -- FreeVecSmashed
      -- Shrink freeVec by one Slot's worth of words.
      freeVecPtr.length ← freeVecPtr.length - SIZE[Slot];
      IF CheckPage AND (freeVecPtr.length < SIZE[VecHeader]) THEN
        ERROR InternalError; -- FreeVecSmashed
      pageHdr.nWordsInFreeVecs ← pageHdr.nWordsInFreeVecs - SIZE[Slot];  END
    ELSE BEGIN--pageHdr.nFreeSlots > 0
      IF pageHdr.nWordsInFreeVecs < nWords+SIZE[VecHeader] THEN GOTO Failure;
      -- If it weren't for paranoia, we could just use a WHILE loop here; there must be a free slot.
      FOR newSlotIndex IN [1..pageHdr.highSlot] DO
        IF DBStorageVec.TypeOfSlot[p, newSlotIndex] = DBStorageVec.FreeType THEN EXIT;
      REPEAT
        FINISHED => ERROR InternalError; -- FreeSlotsSmashed
      ENDLOOP;
      newSlotPtr ← DBStorageVec.IndexToSlot[p, newSlotIndex];
      pageHdr.nFreeSlots ← pageHdr.nFreeSlots - 1;
    END;--IF
    -- If freeVec is too small, compact it now.
    IF freeVecPtr.length < nWords+SIZE[VecHeader] THEN BEGIN
      CompactPage[p];  freeVecPtr ← FreeVecPtr[p];
    END;--IF
    -- It is large enough now, so do it.
    oldFreeVecLength ← freeVecPtr.length;
    IF CheckPage AND (oldFreeVecLength < nWords+SIZE[VecHeader]) THEN
      ERROR InternalError; -- FreeVecSmashed
    freeVecPtr↑ ← [slotIndex: newSlotIndex, length: nWords];
    newSlotPtr↑ ← [type: DBStorageVec.UnFreeType, vecOffset: FreeVecOffset[p]];
    freeVecPtr ← freeVecPtr + nWords;
    freeVecPtr↑ ← [slotIndex: FreeSlotIndex, length: oldFreeVecLength-nWords];
    newSlotPtr ← LOOPHOLE[p + (DBCommon.WordsPerPage - SIZE[Slot]),
                          LONG POINTER TO Slot]; -- point to freeSlot
    newSlotPtr.vecOffset ← newSlotPtr.vecOffset + nWords;
    pageHdr.nWordsInFreeVecs ← pageHdr.nWordsInFreeVecs - nWords;
    GOTO Success;
    EXITS
      Success => BEGIN
        IF ExhaustivelyCheckPage THEN DBStorageVec.CheckVecPage[p, pageHdr.tag];
        RETURN[newSlotIndex, TRUE];
      END;--Success
      Failure => BEGIN
        IF ExhaustivelyCheckPage THEN DBStorageVec.CheckVecPage[p, pageHdr.tag];
        RETURN[NonsenseSlotIndex, FALSE];
      END;--Failure
    END;
  END;--AllocVec

  WordsInLargestAllocableVec: PUBLIC PROC[p: LONG POINTER TO VecPage]
   RETURNS[--nWords-- CARDINAL] = {
    OPEN pageHdr: p;
    wordsOfVecSpace: CARDINAL = pageHdr.nWordsInFreeVecs - SIZE[VecHeader];
    wordsNeededForSlots: CARDINAL = IF pageHdr.nFreeSlots = 0 THEN SIZE[Slot] ELSE 0;
    RETURN[IF wordsOfVecSpace <= wordsNeededForSlots THEN 0 ELSE wordsOfVecSpace-wordsNeededForSlots];
  };--WordsInLargestAllocableVec


  FreeVec: PUBLIC PROC[p: LONG POINTER TO VecPage, slotIndex: CARDINAL] = BEGIN
    -- Frees the vec held in the slot at slotIndex, and the slot also.

    OPEN pageHdr: p;
    slotPtr: LONG POINTER TO Slot;
    DBStats.Inc[StorageFreeVec];
    IF ExhaustivelyCheckPage THEN DBStorageVec.CheckVecPage[p, pageHdr.tag];
    IF ValidateInput AND (slotIndex = 0 OR slotIndex > pageHdr.highSlot) THEN
      ERROR InternalError; -- NotASlot
    slotPtr ← DBStorageVec.IndexToSlot[p, slotIndex];
    IF ValidateInput AND (slotPtr.type = DBStorageVec.FreeType)
      THEN ERROR InternalError; -- SlotIsFree
    BEGIN
      vecPtr: LONG POINTER TO VecHeader ←
        LOOPHOLE[p + slotPtr.vecOffset, LONG POINTER TO VecHeader];
      vecPtr.slotIndex ← FreeSlotIndex;
      pageHdr.nWordsInFreeVecs ← pageHdr.nWordsInFreeVecs + vecPtr.length;
    END;
    IF slotIndex # pageHdr.highSlot THEN BEGIN
      slotPtr↑ ← Slot[type: DBStorageVec.FreeType, vecOffset: 0];
      pageHdr.nFreeSlots ← pageHdr.nFreeSlots + 1;  END
    ELSE BEGIN
      freeWordsReclaimed: CARDINAL ← 1;
      -- Check integrity of sentinel (FreeSlot).
      IF CheckPage AND
        (DBStorageVec.IndexToSlot[p, FreeSlotIndex].type # DBStorageVec.UnFreeType) THEN
        ERROR InternalError; -- FreeVecSmashed
      DO
        IF (slotPtr+freeWordsReclaimed).type # DBStorageVec.FreeType THEN EXIT;
        freeWordsReclaimed ← freeWordsReclaimed + 1;
      ENDLOOP;
      pageHdr.highSlot ← pageHdr.highSlot - freeWordsReclaimed;
      pageHdr.nFreeSlots ← pageHdr.nFreeSlots - (freeWordsReclaimed-1);
      BEGIN
        freeVecPtr: LONG POINTER TO VecHeader ← FreeVecPtr[p];
        freeVecPtr.length ← freeVecPtr.length + freeWordsReclaimed;
        pageHdr.nWordsInFreeVecs ← pageHdr.nWordsInFreeVecs + freeWordsReclaimed;
      END;
    END;--IF
    IF ExhaustivelyCheckPage THEN DBStorageVec.CheckVecPage[p, pageHdr.tag];
    RETURN;
  END;--FreeVec

  ModifyVec: PUBLIC PROC
   [p: LONG POINTER TO VecPage, slotIndex: CARDINAL, deltaWords: INTEGER, preserveContents: BOOL]
   RETURNS[--success-- BOOL] = BEGIN
    -- Changes the length of the vec at slotIndex by deltaWords.  If deltaWords<0, the
    --final deltaWords words of data in the vec are lost forever.  If deltaWords>0 and
    --preserveContents, then the old contents of the vec will be found in the initial
    --words of the new vec; the new words are not initialized.  If ~success, then the call
    --failed for lack of space.  (The call cannot fail if deltaWords<0).

    OPEN pageHdr: LOOPHOLE[p, LONG POINTER TO VecPage];
    BEGIN--block for EXITS GOTOs
    slotPtr: LONG POINTER TO Slot; --points to slot whose vec we're modifying
    vecPtr: LONG POINTER TO VecHeader; --points to vec we're modifying
    vecLen: CARDINAL; --original length of vec we're modifying
    newVecLen: CARDINAL; -- length after modification
    DBStats.Inc[StorageModifyVec];
    IF ExhaustivelyCheckPage THEN DBStorageVec.CheckVecPage[p, pageHdr.tag];
    IF ValidateInput AND (slotIndex = 0 OR slotIndex > pageHdr.highSlot) THEN
      ERROR InternalError; -- NotASlot
    slotPtr ← DBStorageVec.IndexToSlot[p, slotIndex];
    IF ValidateInput AND (slotPtr.type = DBStorageVec.FreeType) THEN
      ERROR InternalError; -- SlotIsFree
    vecPtr ← LOOPHOLE[p + slotPtr.vecOffset, LONG POINTER TO VecHeader];
    vecLen ← vecPtr.length;
    IF deltaWords > 0 THEN BEGIN
      valPtr: LONG POINTER;  shortValPtr: POINTER;
      valWasAllocated: BOOL ← FALSE; -- TRUE later if valPtr points to heap storage
      IF deltaWords + SIZE[VecHeader] > pageHdr.nWordsInFreeVecs THEN--it just won't fit
        GOTO Failure;
      -- We use the following "simplified" strategy:  If the page does not contain enough free words
      --to hold the entire expanded vec without deleting the old copy, we save the old value in heap
      --storage (if it needs to be preserved), delete the old vec, and compact.  Else if the page has
      --enough space but freeVec doesn't, we compact.  If the old value needn't be preserved, we free
      --it before compaction; otherwise it is freed after the value is copied out (and hence does not
      --join freeVec).
      -- Possible elaborations: extending the vec by looking for a free vec behind it.  (Not too hard,
      --but may require coalescing several free vecs).  In-place permutation during compaction (MUCH
      --harder).
      -- Possible simplifications: always preserveContents (not so good for long strings).
      --Allocate fixed storage in global frame for a buffer area, to eliminate heap node
      --allocation.  With a suitable algorithm, this area needs only contain as much 
      --storage as the maximum number of slots, or maybe less...
      newVecLen ← vecLen + deltaWords;
      IF newVecLen + SIZE[VecHeader] > pageHdr.nWordsInFreeVecs THEN BEGIN
        --it will fit only after the original vec's storage has been reclaimed
        IF preserveContents THEN BEGIN-- copy current value to temp storage
          DBStats.Inc[StorageModifyDifficultVec];
          valPtr ← shortValPtr ← DBHeapStorage.Node[vecLen-SIZE[VecHeader]];
            --note: we assume that a request for a node of size 0 is ok.
          I.LongCOPY[from: vecPtr+SIZE[VecHeader], nwords:vecLen-SIZE[VecHeader],
                              to: valPtr];
          valWasAllocated ← TRUE;
        END;--IF
        vecPtr.slotIndex ← FreeSlotIndex;
        pageHdr.nWordsInFreeVecs ← pageHdr.nWordsInFreeVecs + vecLen;
        CompactPage[p];  END
      ELSE BEGIN
        --it will fit in the current free storage, but compaction may still be needed...
        IF ~preserveContents THEN BEGIN --free old vec now, before possible compaction
          vecPtr.slotIndex ← FreeSlotIndex;
          pageHdr.nWordsInFreeVecs ← pageHdr.nWordsInFreeVecs + vecLen;
        END;--IF
        IF newVecLen + SIZE[VecHeader] > FreeVecPtr[p].length THEN BEGIN
          CompactPage[p];
          vecPtr ← LOOPHOLE[p + slotPtr.vecOffset, LONG POINTER TO VecHeader];
        END;--IF
        IF preserveContents THEN BEGIN
          valPtr ← vecPtr + SIZE[VecHeader]; -- safe because no compaction can happen now.
          vecPtr.slotIndex ← FreeSlotIndex;
          pageHdr.nWordsInFreeVecs ← pageHdr.nWordsInFreeVecs + vecLen;
        END;--IF
      END;--IF
      -- At this point, the following is true: vecLen, newVecLen,and slotPtr
      --have values as described in their respective declarations above.  The slot
      --at slotPtr may point to garbage, but its type is ok.  If preserveContents,
      --then valPtr points to the old contents (which may go away if CompactPage
      --is called).  If valWasAllocated, then valPtr points to a vector gotten from
      --AllocFieldValue.  FreeVec is long enough to hold newVecLen words for the new
      --vec, plus a minimum freeVec.
      IF CheckPage AND newVecLen + SIZE[VecHeader] > FreeVecPtr[p].length THEN
        ERROR InternalError; -- Unknown
      slotPtr.vecOffset ← FreeVecOffset[p];
      vecPtr ← LOOPHOLE[p + slotPtr.vecOffset, LONG POINTER TO VecHeader];
      IF preserveContents THEN BEGIN-- copy preserved value back
        I.LongCOPY[from: valPtr, nwords: vecLen-SIZE[VecHeader],
                            to: vecPtr+SIZE[VecHeader]];
        IF valWasAllocated THEN DBHeapStorage.Free[shortValPtr];
      END;--IF
      vecLen ← vecPtr.length; --length of freeVec before allocation
      vecPtr↑ ← VecHeader[slotIndex: slotIndex, length: newVecLen];
      -- fixup freeVec
      vecPtr ← vecPtr + newVecLen; -- point to freeVec
      vecPtr↑ ← VecHeader[slotIndex: FreeSlotIndex, length: vecLen - newVecLen];
      slotPtr ← LOOPHOLE[p + (DBCommon.WordsPerPage - SIZE[Slot]),
                         LONG POINTER TO Slot]; -- point to FreeSlot
      slotPtr.vecOffset ← slotPtr.vecOffset + newVecLen;
      pageHdr.nWordsInFreeVecs ← pageHdr.nWordsInFreeVecs - newVecLen;  END
    ELSE BEGIN--deltaWords<=0
      IF deltaWords = 0 THEN GOTO Success;
      -- we depend on SIZE[VecHeader] = 1 here; otherwise we must do something else when a piece
      --smaller than a VecHeader is freed.  (Compacting would NOT suffice).
      deltaWords ← -deltaWords;
      IF ValidateInput AND LOOPHOLE[deltaWords, CARDINAL] > vecLen THEN
        InternalError; -- DeltaTooSmall
      newVecLen ← LOOPHOLE[vecLen - deltaWords, CARDINAL]; 
      vecPtr.length ← newVecLen;
      vecPtr ← vecPtr + newVecLen; --point to the fragment we're freeing
      vecPtr↑ ← VecHeader[slotIndex: FreeSlotIndex, length: deltaWords];
      pageHdr.nWordsInFreeVecs ← pageHdr.nWordsInFreeVecs + deltaWords;
    END;--IF
    GOTO Success;
    EXITS
      Success => BEGIN
        IF ExhaustivelyCheckPage THEN DBStorageVec.CheckVecPage[p, pageHdr.tag];
        RETURN[TRUE];
      END;--Success
      Failure => BEGIN
        IF ExhaustivelyCheckPage THEN DBStorageVec.CheckVecPage[p, pageHdr.tag];
        RETURN[FALSE];
      END;--Failure
    END;
  END;--ModifyVec

  CompactPage: PROC[p: LONG POINTER TO VecPage] = BEGIN
    -- Collects all free storage on page p into a single free vec, and returns the size
    --of the vec.  p is a tuple page in the cache, open for writing.
    -- Called from: AllocVec, ModifyVec.

    OPEN pageHdr: p;
    highSlotOff: CARDINAL ← DBStorageVec.IndexToOffset[DBStorageVec.HighSlotIndexOfPage[p]];
    curSrc: CARDINAL ← SIZE[VecPage];
    curDst: CARDINAL;
      -- all three quantities are p-relative
    s, l: CARDINAL;
    nAllocVecs: CARDINAL ← 0;

    BEGIN-- block for exit GOTOs
    DBStats.Inc[StorageCompactPage];
    DO
      -- loop to find the first free vec, if any
      IF VecOfOffset[p, curSrc].slotIndex = FreeSlotIndex THEN EXIT;
      nAllocVecs ← nAllocVecs + 1;
      curSrc ← curSrc + VecOfOffset[p, curSrc].length;
      IF CheckPage AND (curSrc >= highSlotOff) THEN GOTO NoFreeVecs;
    REPEAT
      NoFreeVecs => ERROR InternalError; -- FreeVecSmashed, freeVec should be present
    ENDLOOP;
    -- move past the first free vec
    curDst ← curSrc;
    curSrc ← curSrc + VecOfOffset[p, curSrc].length;
    IF curSrc >= highSlotOff THEN BEGIN
      IF ~CheckPage OR (curSrc = highSlotOff) THEN GOTO Compacted
      ELSE ERROR InternalError; -- Unknown
    END;--IF
    DO
      -- loop to move each allocated vec down
      DO
        -- loop to find the next allocated vec, if any
        IF VecOfOffset[p, curSrc].slotIndex # FreeSlotIndex THEN EXIT;
        curSrc ← curSrc + VecOfOffset[p, curSrc].length;
        IF curSrc >= highSlotOff THEN GOTO SeenAllVecs;
      ENDLOOP;
      -- found an allocated vec; move it
      nAllocVecs ← nAllocVecs + 1;
      s ← VecOfOffset[p, curSrc].slotIndex;  l ← VecOfOffset[p, curSrc].length;
      I.LongCOPY[from: p+curSrc, nwords: l, to: p+curDst];
      DBStorageVec.IndexToSlot[p, s].vecOffset ← curDst;
      curDst ← curDst + l;
      curSrc ← curSrc + l;
      IF curSrc >= highSlotOff THEN GOTO SeenAllVecs;
    REPEAT
      SeenAllVecs => BEGIN
        IF ~CheckPage OR (curSrc=highSlotOff) THEN BEGIN
          DBStorageVec.IndexToSlot[p, FreeSlotIndex].vecOffset ← curDst;
          VecOfOffset[p, curDst]↑ ← VecHeader[slotIndex: FreeSlotIndex, length: curSrc - curDst];
          GOTO Compacted;
        END
        ELSE ERROR InternalError;
      END;--SeenAllVecs
    ENDLOOP;
    EXITS
      Compacted => BEGIN
        IF CheckPage AND (VecOfOffset[p, curDst].length # pageHdr.nWordsInFreeVecs) THEN
          ERROR InternalError;
        RETURN;
      END;--Compacted
    END;--EXITS
  END;--CompactPage

END.--StorageVecImpl

--  Module History

Created by MBrown on February 15, 1980  10:39 PM
-- AllocVec and CompactPage copied from preliminary versions done on February 2
--(before design was "debugged" and documented, interface formalized).

Changed by MBrown on February 17, 1980  4:58 PM
-- Recoded AllocVec to match new interface (it now allocates slots also).  Simplified it by
--forcing freeVec to always exist, and by having redundant information in header.

Changed by MBrown on February 17, 1980  10:25 PM
-- Coded CheckVecPage.  Converted CompactPage to new primitives and made it compact upward.

Changed by MBrown on February 18, 1980  10:46 AM
-- Coded FreeVec.

Changed by MBrown on February 18, 1980  6:30 PM
-- Bug fixes: CompactPage set freeVec length to the negative of its true value.
--AllocVec didn't update FreeSlot after allocating a vec from freeVec.  First test
--program runs to completion.  (This took 1 hr of debugging; all other bugs were in
--testing code).

Changed by MBrown on February 19, 1980  3:41 PM
-- Coded ModifyVec.

Changed by MBrown on 20-Feb-80 11:52
-- Two bugs found in ModifyVec. First was a logic bug in case ~preserveContents
--and ~compact. {This was due to insufficient analysis in coding a complex problem;
--a symptom was the deeply nested IF-THEN-ELSE structure.  The fix caused a considerable
--simplification of this.}  Second was in the case preserveContents and ~valWasAllocated,
--the length of freeVec was wrong. {This was due to coding one of two cases, noting
--that the tail of the second could be unified with the first, but using the already
--written code in the unification without reexamining it for hidden assumptions.}

Changed by MBrown on February 20, 1980  3:07 PM
-- A bug in one of the redundant checks.  Expanded VecTest now runs to completion.

Changed by MBrown on February 24, 1980  11:55 AM
-- Minor changes to conform to changes in inteface.

Changed by MBrown on June 11, 1980  4:29 PM
-- TIDSlotMask now comes from DBStorageTID.

Changed by MBrown on August 22, 1980  4:37 PM
-- Implemented WordsInLargestAllocableVec.

Changed by MBrown on August 24, 1980  10:51 AM
-- WordsInLargestAllocableVec returned 177777B when called on an empty page.  This was another
--CARDINAL subtraction bug.  We really need true CARDINAL arithmetic!

Changed by MBrown on September 26, 1980  4:18 PM
-- Converted to new DBException.

Changed by MBrown on December 6, 1980  11:28 PM
-- Added DBStats counter events StorageInitVecPage, StorageCheckVecPage, StorageAllocVec,
--StorageFreeVec, StorageModifyVec, StorageModifyDifficultVec, StorageCompactPage.

Changed by MBrown on February 27, 1981  5:28 PM
-- Pre-Pilot changes.

Changed by MBrown on August 7, 1982 9:56 pm
-- Set ExhaustivelyCheckPage = FALSE.