-- VMMgr>SpaceImplA.mesa  (last edited by Levin on October 26, 1982 9:44 am)

-- Note: There is another version of SpaceImpl for UtilityPilot which should
-- track changes to this module.

-- Implementation Notes:
-- All client processes which wish to enter the Space Monitor MUST enter it via
-- EnterSpace[].  There must be no other client-callable ENTRY procedures.
-- The routines herein  must have this organization:  first, check for all client
-- errors, and raise an ERROR if any;  then, make changes to the VM databases, etc.
-- The MONITORLOCK of SpaceImplA/B "spaceLock" protects all VMMgr data, including
-- the Hierarchy and Projection data bases and the MapLog.  That is, those
-- facilities are only accessed from within the Space monitor, and so are not
-- themselves MONITORs, but rely on the serialization provided by spaceLock.
-- This makes the calls to those facilities faster.
-- In doing arithmetic involving subtraction of page numbers, the programmer must
-- take care that no result is ever less than zero, since they are all CARDINALs.


-- Things to Consider:
-- GetAttributes could be augmented by operations for accessing individual space
-- attributes, avoiding unnecessary disk accesses, if performance requirements so
-- dictate.

DIRECTORY
  CachedRegion USING [
    activate, Apply, createSwapUnits, deactivate, Desc, flush, startForceOut,
    kill, Mapped, Operation, Outcome, pageTop, pin, unpin, wait],
  CachedSpace USING [Desc, Handle, handleNull, handleVM, Level, SizeSwapUnit],
  Environment USING [Long, maxPagesInMDS, wordsPerPage],
  File USING [Capability, Error, ErrorType, Permissions, read, Unknown, write],
  Hierarchy USING [
    Delete, FindFirstWithin, GetDescriptor, GetInterval, Insert, Touch, Update,
    ValidSpaceOrSwapUnit],
  Inline USING [BITAND, BITNOT, LongMult],
  PrincOps USING [BytePC, ControlLink, EPRange, GlobalFrameHandle, PrefixHandle],
  PrincOpsRuntime USING [GFT],
  Process USING [GetPriority, Priority, SetPriority],
  ProcessPriorities USING [priorityPageFaultLow],
  Projection USING [
    DeleteSwapUnits, ForceOut, Get, Merge, Split, Touch, TranslateLevel],
  Runtime USING [GlobalFrame],
  RuntimeInternal USING [Codebase],
  Space USING [
    defaultBase, defaultWindow, ErrorType, Handle, PageCount, PageNumber,
    PageOffset, WindowOrigin, wordsPerPage],
  SpaceExtras USING [],
  SpaceImplInternal USING [
    InitializeSpaceImplB, Interval, Level, RegionD, SpaceD, spaceLock,
    UnmapInternal],
  SpecialSpace USING [],
  Transaction USING [Handle, InvalidHandle, nullHandle],
  TransactionState USING [WithdrawFromTransaction],
  Utilities USING [PageFromLongPointer],
  VM USING [Interval, PageCount],
  VMMPrograms USING [],
  Volume USING [ID, InsufficientSpace, Unknown];

SpaceImplA: MONITOR LOCKS SpaceImplInternal.spaceLock
  IMPORTS
    CachedRegion, File, Hierarchy, Inline, Process, Projection, Runtime,
    RuntimeInternal, SpaceImplInternal, Transaction, TransactionState, Utilities,
    Volume
  EXPORTS Space, SpaceExtras, SpaceImplInternal, SpecialSpace, VMMPrograms
  SHARES File --USING [Capability.permissions]-- =

  BEGIN OPEN Space, SpaceImplInternal;

  Handle: PUBLIC TYPE = CachedSpace.Handle;

  AlignType: TYPE = {code, powerOf2, none};

  ChildrenSelf: TYPE = {children, self};

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Space monitor data:
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  -- Public Data:

  Error: PUBLIC ERROR [type: ErrorType] = CODE;
  InsufficientSpace: PUBLIC ERROR [available: PageCount] = CODE;
  mds: PUBLIC Handle;
  nullHandle: PUBLIC Handle ← CachedSpace.handleNull;
  nullTransactionHandle: PUBLIC Transaction.Handle ← Transaction.nullHandle;
  virtualMemory: PUBLIC Handle ← CachedSpace.handleVM;

  -- Private Data:

  countVM: VM.PageCount;
  handleMDS: CachedSpace.Handle;
  intervalMDS: Interval;
  --SpaceImplInternal.-- spaceLock: PUBLIC MONITORLOCK;  -- (private to SpaceImpl#)

  largestPowerOf2Cardinal: CARDINAL = (LAST[CARDINAL]/2) + 1;  -- assumes binary representation for CARDINALs.
  maxPagesInCodeBlock: PageCount = Environment.maxPagesInMDS;

  Bug: PRIVATE --PROGRAMMING-- ERROR [type: BugType] = CODE;  -- not to be caught by client;
  BugType: TYPE = {
    deleteBadSpace, funnyAlignment, funnySignal, funnyErrorState, funnyOutcome,
    noCreateDecision};


  --~~~~~~~~~~~~~~~~~~~~
  -- Initialization
  --~~~~~~~~~~~~~~~~~~~~

  InitializeSpace: PUBLIC PROCEDURE [countVM: VM.PageCount, handleMDS: Handle] = {
    InitializeInternal[countVM, handleMDS]};  -- (just to change the parameter names.)

  InitializeInternal: PROCEDURE [ctVM: VM.PageCount, handMDS: Handle] =
    BEGIN
    countVM ← ctVM;  -- copy params into globals..
    mds ← (handleMDS ← handMDS);
    intervalMDS ← Hierarchy.GetInterval[handleMDS].interval;
    InitializeSpaceImplB[countVM];
    END;

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- SpecialSpace implementation
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  --~~~~~~~~~~~~~~~~~~~~
  -- Monitor externals

  --~~~~~~~~~~~~~~~~~~~~

  ActivateProc: PUBLIC --EXTERNAL-- PROCEDURE [
    proc: --PrincOps.ControlLink-- UNSPECIFIED] = {
    Activate[GetHandle[Utilities.PageFromLongPointer[Code[proc]]]]};

  ActivateSwapUnit: PUBLIC --EXTERNAL-- PROCEDURE [page: Space.PageNumber] = {
    Activate[GetHandle[page]]};

  Code: --EXTERNAL-- PROCEDURE [proc: PrincOps.ControlLink]
    RETURNS [code: LONG POINTER --TO code-- ] =
    -- Returns pointer to first word of procedure's code.
    BEGIN
    OutermostProcOf: PROCEDURE [link: PrincOps.ControlLink]
      RETURNS [PrincOps.ControlLink] = INLINE
      BEGIN
      WHILE link.indirect AND ~link.proc DO link ← link.link↑; ENDLOOP;
      IF ~link.proc THEN Error[invalidParameters];
      RETURN[link]
      END;
    codeBase: LONG PrincOps.PrefixHandle =
      RuntimeInternal.Codebase[Runtime.GlobalFrame[proc ← OutermostProcOf[proc]]];
    evi: CARDINAL = PrincOpsRuntime.GFT[proc.gfi].epbias*PrincOps.EPRange + proc.ep;
    startPC: PrincOps.BytePC = codeBase.entry[evi].initialpc;
    RETURN[codeBase + LOOPHOLE[startPC, CARDINAL]];
    END;

  CreateAligned: PUBLIC --EXTERNAL-- PROCEDURE [
    size: Space.PageCount, parent: Space.Handle, alignment: Space.PageCount ← 0]
    RETURNS [newSpace: Space.Handle] =
    -- Create a space which begins at a page within virtual memory which is an
    -- integral multiple of the smallest power of two not less than the given
    -- size.  (Thus if size is already a power of two, the beginning page number
    -- will have at least as many low-order zero bits as does size.)
    {RETURN[CreateAny[size, parent, defaultBase, --alignType:-- powerOf2, alignment]]};

  CreateForCode: PUBLIC --EXTERNAL-- PROCEDURE [
    size: PageCount, parent: Handle, base: PageOffset]
    RETURNS [newSpace: Space.Handle] =
    -- Creates a space guaranteed not to cross a 64KW boundary.
    {RETURN[CreateAny[size, parent, base, --alignType:-- code]]};

  DeactivateProc: PUBLIC --EXTERNAL-- PROCEDURE [
    proc: --PrincOps.ControlLink-- UNSPECIFIED] = {
    Deactivate[GetHandle[Utilities.PageFromLongPointer[Code[proc]]]]};

  DeactivateSwapUnit: PUBLIC --EXTERNAL-- PROCEDURE [page: Space.PageNumber] = {
    Deactivate[GetHandle[page]]};

  MakeCodeResident: PUBLIC --EXTERNAL-- PROCEDURE [frame: PROGRAM] = {
    MakeResident[
      GetHandle[Utilities.PageFromLongPointer[RuntimeInternal.Codebase[frame]]]]};

  MakeCodeSwappable: PUBLIC --EXTERNAL-- PROCEDURE [frame: PROGRAM] = {
    MakeSwappable[
      GetHandle[Utilities.PageFromLongPointer[RuntimeInternal.Codebase[frame]]]]};

  MakeGlobalFrameResident: PUBLIC --EXTERNAL-- PROCEDURE [frame: PROGRAM] = {
    gf: PrincOps.GlobalFrameHandle = LOOPHOLE[frame];
    IF gf.alloced THEN RETURN;
    MakeResident[
      GetHandle[Utilities.PageFromLongPointer[gf]]]};

  MakeGlobalFrameSwappable: PUBLIC --EXTERNAL-- PROCEDURE [frame: PROGRAM] = {
    gf: PrincOps.GlobalFrameHandle = LOOPHOLE[frame];
    IF gf.alloced THEN RETURN;
    MakeSwappable[
      GetHandle[Utilities.PageFromLongPointer[gf]]]};

  MakeProcedureResident: PUBLIC --EXTERNAL-- PROCEDURE [
    proc: --PrincOps.ControlLink-- UNSPECIFIED] = {
    MakeResident[GetHandle[Utilities.PageFromLongPointer[Code[proc]]]]};

  MakeProcedureSwappable: PUBLIC --EXTERNAL-- PROCEDURE [
    proc: --PrincOps.ControlLink-- UNSPECIFIED] = {
    MakeSwappable[GetHandle[Utilities.PageFromLongPointer[Code[proc]]]]};

  MakeResident: PUBLIC --EXTERNAL-- PROCEDURE [space: Handle] =
    -- OK to make an already-resident space resident.
    BEGIN
    MakeResidentInternal: INTERNAL PROCEDURE[] =
      BEGIN
      Pinnable: PROCEDURE [regionD: RegionD] RETURNS [BOOLEAN] = {
        RETURN[
          regionD.state IN CachedRegion.Mapped
            AND regionD.levelMapped <= space.level]};
      spaceD: SpaceD;
      validSpace, validSwapUnit: BOOLEAN;
      [validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[@spaceD, space];
      IF validSwapUnit THEN Error[notApplicableToSwapUnit]
      ELSE IF ~validSpace THEN Error[invalidHandle];
      IF ~ForAllRegions[spaceD.interval, Pinnable] THEN
        Error[invalidMappingOperation];
      spaceD.pinned ← TRUE;
      Hierarchy.Update[@spaceD];
      ApplyToInterval[spaceD.interval, CachedRegion.pin]
      END;
    EnterSpace[MakeResidentInternal];
    END;

  MakeSwappable: PUBLIC --EXTERNAL-- PROCEDURE [space: Handle] =
    BEGIN
    MakeSwappableInternal: INTERNAL PROCEDURE[] =
      BEGIN
      spaceD: SpaceD;
      validSpace, validSwapUnit: BOOLEAN;
      [validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[@spaceD, space];
      IF validSwapUnit THEN Error[notApplicableToSwapUnit]
      ELSE IF ~validSpace THEN Error[invalidHandle];
      IF ~spaceD.pinned THEN Error[invalidMappingOperation];
      spaceD.pinned ← FALSE;
      Hierarchy.Update[@spaceD];
      ApplyToInterval[spaceD.interval, CachedRegion.unpin]
      END;
    EnterSpace[MakeSwappableInternal];
    END;


  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Space implementation:
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  --~~~~~~~~~~~~~~~~~~~~
  -- Monitor externals
  --~~~~~~~~~~~~~~~~~~~~

  Activate: PUBLIC --EXTERNAL-- SAFE PROCEDURE [space: Handle] = TRUSTED
    BEGIN
    ActivateInternal: INTERNAL PROCEDURE[] = {
      IF ~ApplyToSpace[space, CachedRegion.activate].validHandle THEN
        Error[invalidHandle]};
    EnterSpace[ActivateInternal];
    END;

  Create: PUBLIC --EXTERNAL-- PROCEDURE [
    size: PageCount, parent: Handle, base: PageOffset] RETURNS [Handle] = {
    RETURN[CreateAny[size, parent, base, --alignType:-- none]]};

  CreateAny: --EXTERNAL-- PROCEDURE [
    size: PageCount, parent: Handle, base: PageOffset, alignType: AlignType, alignSize: PageCount ← 0]
    RETURNS [newSpace: Handle] =
    BEGIN
    CreateAnyInternal: INTERNAL PROCEDURE[] =
      BEGIN
      validSpace, validSwapUnit: BOOLEAN;
      page: PageNumber;  -- the start of the new space.
      regionD: RegionD;
      spaceDParent: SpaceD;
      [validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[
        @spaceDParent, parent];
      IF validSwapUnit THEN Error[notApplicableToSwapUnit]
      ELSE IF ~validSpace THEN Error[invalidHandle];
      IF parent.level >= LAST[Level] THEN Error[spaceTreeTooDeep];
      IF spaceDParent.hasSwapUnits THEN Error[notApplicableToSwapUnit];
      IF size = 0 OR (alignType = code AND size > maxPagesInCodeBlock)
        OR (alignType = powerOf2 AND MAX[size, alignSize] > largestPowerOf2Cardinal) THEN
        Error[invalidParameters];
      IF base ~= defaultBase THEN  --explicit base given--
        BEGIN
        regionD ← Projection.Get[page ← parent.page + base];  -- so page IN regionD.interval
        IF size > (regionD.interval.page + regionD.interval.count) - page
          OR base >= spaceDParent.interval.count OR regionD.level ~= parent.level
          OR
            (alignType = code
              AND page/maxPagesInCodeBlock ~=
                (page + size - 1)/maxPagesInCodeBlock) THEN
          Error[invalidParameters];
        END
      ELSE  --create anywhere--
        BEGIN
        alignment: CARDINAL;  -- for alignType=powerOf2.
        pwr2BdyMask: WORD;  -- for alignType=powerOf2.
        countMax: PageCount ← 0;  -- largest suitable interval in space (in case none found).
        codeBdyMask: WORD = LOOPHOLE[-(maxPagesInCodeBlock - 1) - 1, WORD];  -- (compile-time constant)

        HoleNotFound: INTERNAL PROCEDURE [
          reg: RegionD --, alignType, alignment-- ]
          RETURNS [notFound: BOOLEAN --, page, countmax-- ] =
          -- Looks for a hole in reg of a suitable size and alignment.  If found,
	  -- notFound=FALSE and page is set;  If notFound=TRUE, countmax is set
	  -- to the size of the largest suitable interval.
          BEGIN
          IF reg.level ~= parent.level THEN RETURN[notFound: TRUE]
          ELSE  --can not have swap units--
            BEGIN OPEN r: reg.interval;
            endRegion: PageNumber = r.page + r.count;
            SELECT alignType FROM
              code =>
                BEGIN
                firstBdy: PageNumber = Inline.BITAND[
                  r.page + maxPagesInCodeBlock, codeBdyMask];  --overflow is ok, yields 0
                IF endRegion <= firstBdy OR firstBdy = 0 THEN
                  -- region ends before first boundary..
                  {
                  IF size <= r.count THEN {page ← r.page; RETURN[notFound: FALSE]}
                  ELSE {
                    countMax ← MAX[countMax, r.count]; RETURN[notFound: TRUE]}}
                ELSE  --first opportunity ends at first code boundary--
                  {
                  IF size <= firstBdy - r.page THEN {
                    page ← r.page; RETURN[notFound: FALSE]}
                  ELSE {countMax ← MAX[countMax, firstBdy - r.page]}};
                -- Try after first code boundary:
                IF Inline.BITAND[endRegion, codeBdyMask] ~= firstBdy THEN
                  -- region contains a whole CodeBlock..
                  {page ← firstBdy; RETURN[notFound: FALSE]}
                ELSE  --region contains exactly one code boundary--
                  IF firstBdy - r.page >= endRegion - firstBdy THEN
                    -- first chunk is larger..
                    {
                    IF size <= firstBdy - r.page THEN {
                      page ← r.page; RETURN[notFound: FALSE]}
                    ELSE {
                      countMax ← MAX[countMax, firstBdy - r.page];
                      RETURN[notFound: TRUE]}}
                  ELSE  -- second chunk is larger..
                    {
                    IF size <= endRegion - firstBdy THEN {
                      page ← firstBdy; RETURN[notFound: FALSE]}
                    ELSE {
                      countMax ← MAX[countMax, endRegion - firstBdy];
                      RETURN[notFound: TRUE]}};
                END;
              powerOf2 =>
                BEGIN
                page ← Inline.BITAND[r.page + (alignment-1), pwr2BdyMask];  --overflow is ok, yields 0.
                IF endRegion <= page OR page = 0 THEN RETURN[notFound: TRUE]  -- region ends before first boundary..
                ELSE  --starting page is in region--
                  {
                  IF size <= endRegion - page THEN RETURN[notFound: FALSE]
                  ELSE {
                    countMax ← MAX[countMax, endRegion - page];
                    RETURN[notFound: TRUE]}}
                END;
              none => {
                IF size <= r.count THEN {page ← r.page; RETURN[notFound: FALSE]}
                ELSE {countMax ← MAX[countMax, r.count]; RETURN[notFound: TRUE]}}
              ENDCASE => ERROR Bug[funnyAlignment];
            END;
          -- ERROR Bug[noCreateDecision]; ++ (generates compiler warning)
          END --HoleNotFound-- ;

        IF alignType = powerOf2 THEN {
          IF alignSize = 0 THEN alignSize ← size;
          FOR alignment ← 1, alignment*2 UNTIL alignment >= alignSize DO NULL ENDLOOP;
          pwr2BdyMask ← Inline.BITNOT[alignment - 1]};
        IF ForAllRegions[
          [
          spaceDParent.pageRover,
          spaceDParent.interval.count -
            (spaceDParent.pageRover - spaceDParent.interval.page)], HoleNotFound]
          THEN
          IF ForAllRegions[
            [
            spaceDParent.interval.page,
            spaceDParent.pageRover - spaceDParent.interval.page], HoleNotFound]
            THEN RETURN WITH ERROR InsufficientSpace[available: countMax];
        spaceDParent.pageRover ← page + size;  -- pageRover=interval.page+interval.count is ok
        Hierarchy.Update[@spaceDParent];  -- (update pageRover.)
        END --create anywhere-- ;
      regionD ← Projection.Get[page];  -- so page IN regionD.interval
      ApplyToInterval[regionD.interval, CachedRegion.flush];  -- assure that the Projection is current (and disable client refs while changing).
      newSpace ← [SUCC[parent.level], page];
      Hierarchy.Insert[newSpace, size];
      Projection.Split[page];
      Projection.Split[page + size];
      Projection.TranslateLevel[pageMember: page, delta: 1];
      END;
    EnterSpace[CreateAnyInternal];
    END;

  CreateUniformSwapUnits: PUBLIC --EXTERNAL-- PROCEDURE [
    size: PageCount, parent: Handle] =
    BEGIN
    CreateUniformSwapUnitsInternal: INTERNAL PROCEDURE[] =
      BEGIN
      spaceDParent: SpaceD;
      validSpace, validSwapUnit: BOOLEAN;
      [validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[
        @spaceDParent, parent];
      IF validSwapUnit THEN Error[notApplicableToSwapUnit]
      ELSE IF ~validSpace THEN Error[invalidHandle];
      IF parent.level < LAST[Level] THEN
        IF Hierarchy.FindFirstWithin[
          [parent.level + 1, parent.page], spaceDParent.interval.count] ~=
          CachedSpace.handleNull THEN Error[spaceNotUnitary];  -- no children allowed.
      IF spaceDParent.hasSwapUnits THEN Error[spaceNotUnitary];
      IF size = 0 OR ~(size IN CachedSpace.SizeSwapUnit) THEN
        Error[invalidParameters];
      spaceDParent.hasSwapUnits ← TRUE;
      spaceDParent.sizeSwapUnit ← size;
      Hierarchy.Update[@spaceDParent];
      ApplyToInterval[spaceDParent.interval, CachedRegion.createSwapUnits];
      END;
    EnterSpace[CreateUniformSwapUnitsInternal];
    END;

  Deactivate: PUBLIC --EXTERNAL-- SAFE PROCEDURE [space: Handle] = TRUSTED
    BEGIN
    DeactivateInternal: INTERNAL PROCEDURE[] = {
      IF ~ApplyToSpace[space, CachedRegion.deactivate].validHandle THEN
        Error[invalidHandle]};
    EnterSpace[DeactivateInternal];
    END;

  Delete: PUBLIC --EXTERNAL-- PROCEDURE [space: Handle] = {
    DeleteAny[space, self]};

  DeleteAny: --EXTERNAL-- PROCEDURE [space: Handle, which: ChildrenSelf] =
    BEGIN
    DeleteAnyInternal: INTERNAL PROCEDURE[] =
      BEGIN
      level, topLevel: Level;
      DeleteIfLeaf: INTERNAL PROCEDURE [regionD: RegionD] RETURNS [BOOLEAN] =
        -- Implicit parameter: level.
        -- Assume level>0 (i.e. space~=virtualMemory)
        BEGIN
        IF regionD.level = level THEN
          -- Region described by regionD corresponds to a leaf space.
          BEGIN
          leafSpaceD: SpaceD;
          mergeLeft, mergeRight: BOOLEAN;
          descParent: SpaceD;
          intervalParent: Interval;
          pageNext: PageNumber = regionD.interval.page + regionD.interval.count;
          flushInterval: Interval ← regionD.interval;  -- assume no merging
          IF ~Hierarchy.GetDescriptor[
            @leafSpaceD, [level, regionD.interval.page]].validSpace THEN
            ERROR Bug[deleteBadSpace];
          [] ← Hierarchy.GetDescriptor[
            @descParent, [level - 1, regionD.interval.page]];
          intervalParent ← descParent.interval;
          IF leafSpaceD.transaction ~= nullTransactionHandle THEN
            TransactionState.WithdrawFromTransaction[
              leafSpaceD.transaction, Handle[level, regionD.interval.page] !
              Transaction.InvalidHandle => CONTINUE];
          IF regionD.interval.page > FIRST[PageNumber] THEN  -- left neighbor exists.
            BEGIN
            leftNeighbor: RegionD = Projection.Get[regionD.interval.page - 1];
            mergeLeft ←
              (regionD.interval.page ~= intervalParent.page
                AND leftNeighbor.level < regionD.level);
            IF mergeLeft THEN
              flushInterval ← [
                flushInterval.page - leftNeighbor.interval.count,
                flushInterval.count + leftNeighbor.interval.count];
            IF descParent.pageRover > regionD.interval.page THEN  -- pageRover should be updated.
              BEGIN
              IF descParent.level = leftNeighbor.level THEN
                descParent.pageRover ← MAX[
                  leftNeighbor.interval.page, intervalParent.page]
              ELSE descParent.pageRover ← regionD.interval.page;
              Hierarchy.Update[@descParent];
              END;
            END
          ELSE
            BEGIN  -- no left neighbor.
            mergeLeft ← FALSE;
            descParent.pageRover ← regionD.interval.page;
            Hierarchy.Update[@descParent];
            END;
          IF pageNext < CachedRegion.pageTop THEN
            BEGIN
            rightNeighbor: RegionD = Projection.Get[pageNext];
            mergeRight ←
              (pageNext ~= intervalParent.page + intervalParent.count
                AND rightNeighbor.level < regionD.level);
            IF mergeRight THEN
              flushInterval.count ←
                flushInterval.count + rightNeighbor.interval.count;
            END
          ELSE mergeRight ← FALSE;  -- no right neighbor.
          IF regionD.state IN CachedRegion.Mapped
            AND regionD.levelMapped = regionD.level THEN
            -- BEGIN
            -- spaceD: SpaceD;
            -- [] ← Hierarchy.GetDescriptor[@spaceD, [regionD.level, regionD.interval.page]];
            -- spaceD should be equal to leafSpaceD, unnecessary procedure call (GetDescriptor) is commented out.
            UnmapInternal[@leafSpaceD];
          -- END;
          -- make sure Projection is current (and prevent client refs
	  -- while changing).  (Flush this region and any required neighbors.)
          ApplyToInterval[flushInterval, CachedRegion.flush];
          IF regionD.hasSwapUnits THEN
            Projection.DeleteSwapUnits[regionD.interval.page];
          Hierarchy.Delete[[regionD.level, regionD.interval.page]];
          -- At this point, the properties of the current region must match
	  -- the properties of any adjacent regions with which it will coalesce.
          Projection.TranslateLevel[pageMember: regionD.interval.page, delta: -1];
          IF mergeLeft THEN Projection.Merge[regionD.interval.page];
          IF mergeRight THEN Projection.Merge[pageNext];
          END;
        RETURN[TRUE];  -- "keep going"
        END;
      levelLeaves: Level ← space.level;
      MaxLevel: PROCEDURE [regionD: RegionD] RETURNS [BOOLEAN] = {
        levelLeaves ← MAX[levelLeaves, regionD.level]; RETURN[TRUE]};  -- "keep going"
      validSpace, validSwapUnit: BOOLEAN;
      spaceD: SpaceD;
      interval: Interval;
      [validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[@spaceD, space];
      IF validSwapUnit THEN Error[notApplicableToSwapUnit]
      ELSE IF ~validSpace THEN Error[invalidHandle];
      interval ← spaceD.interval;
      IF space.level <= handleMDS.level
        AND handleMDS.page IN [interval.page..interval.page + interval.count) THEN
        Error[invalidParameters];  -- don't delete the MDS!
      [] ← ForAllRegions[interval, MaxLevel];  -- levelLeaves ← max region level, region in interval
      topLevel ←
        IF which = children THEN space.level + 1 --delete only children--
        ELSE space.level --delete children and self-- ;
      FOR level DECREASING IN [topLevel..levelLeaves] DO
        [] ← ForAllRegions[interval, DeleteIfLeaf]; ENDLOOP;
      IF which = children THEN  -- delete uniform swap units of self:
        BEGIN
        ApplyToInterval[interval, CachedRegion.flush];
        Projection.DeleteSwapUnits[interval.page];
        spaceD.hasSwapUnits ← FALSE;
        Hierarchy.Update[@spaceD];
        END;
      END;
    EnterSpace[DeleteAnyInternal];
    END;

  DeleteSwapUnits: PUBLIC --EXTERNAL-- PROCEDURE [space: Handle] = {
    DeleteAny[space, children]};

  ForceOut: PUBLIC --EXTERNAL-- PROCEDURE [space: Handle] =
    BEGIN
    ForceOutInternal: INTERNAL PROCEDURE[] = {
      IF ~ApplyToSpace[space, CachedRegion.startForceOut].validHandle THEN
        Error[invalidHandle];
      [] ← ApplyToSpace[space, CachedRegion.wait]};
    EnterSpace[ForceOutInternal];
    END;

  GetAttributes: PUBLIC --EXTERNAL-- SAFE PROCEDURE [space: Handle]
    RETURNS [
      parent, lowestChild, nextSibling: Handle, base: PageOffset, size: PageCount,
      mapped: BOOLEAN] = TRUSTED
    BEGIN
    GetAttributesInternal: INTERNAL PROCEDURE[] =
      BEGIN
      spaceD: CachedSpace.Desc;
      validSpace, validSwapUnit: BOOLEAN;
      interval: Interval;
      [validSpace, validSwapUnit, interval] ← Hierarchy.GetInterval[space];
      IF ~(validSpace OR validSwapUnit) THEN Error[invalidHandle];
      IF space.level = 0 THEN {parent ← nextSibling ← nullHandle; base ← 0}
      ELSE
        BEGIN
        intervalParent: Interval = Hierarchy.GetInterval[
          [space.level - 1, interval.page]].interval;
        pageNext: PageNumber = interval.page + interval.count;
        countNext: PageCount =
          intervalParent.page + intervalParent.count - pageNext;
        parent ← [space.level - 1, intervalParent.page];
        nextSibling ←
          IF validSwapUnit THEN
          IF pageNext < intervalParent.page + intervalParent.count THEN [
          space.level, pageNext]  -- next swap unit
          ELSE nullHandle
          ELSE --validSpace-- Hierarchy.FindFirstWithin[
            [space.level, pageNext], countNext];
        base ← interval.page - intervalParent.page;
        END;
      [] ← Hierarchy.GetDescriptor[@spaceD, space];  -- (gets desc of parent space if this is a swap unit handle)
      lowestChild ←
        IF validSwapUnit THEN nullHandle
        ELSE  --validSpace--
          IF spaceD.hasSwapUnits THEN [space.level + 1, space.page]  -- first swap unit
          ELSE  --~hasSwapUnits--
            IF space.level < LAST[Level] THEN Hierarchy.FindFirstWithin[
            [space.level + 1, space.page], interval.count]
            ELSE --handle.level=LAST[Level]-- nullHandle;
      size ← interval.count;
      mapped ← IF validSwapUnit THEN FALSE ELSE spaceD.state = mapped;
      END;
    EnterSpace[GetAttributesInternal];
    END;

  GetHandle: PUBLIC --EXTERNAL-- SAFE PROCEDURE [page: PageNumber]
    RETURNS [space: Handle] = TRUSTED
    BEGIN
    GetHandleInternal: INTERNAL PROCEDURE[] =
      BEGIN
      regionD: CachedRegion.Desc;
      handleLevel: CARDINAL;  -- (not a CachedSpace.Level, since a swap unit may have handle.level = LAST[Level]+1)
      IF page >= countVM THEN Error[invalidParameters];
      regionD ← Projection.Get[page];  -- (gets level of parent space if swap units)
      handleLevel ← regionD.level + (IF regionD.hasSwapUnits THEN 1 ELSE 0);
      space ← [
        handleLevel, Hierarchy.GetInterval[[handleLevel, page]].interval.page];
      END;
    EnterSpace[GetHandleInternal];
    END;

  GetWindow: PUBLIC --EXTERNAL-- SAFE PROCEDURE [space: Handle]
    RETURNS [window: WindowOrigin] = TRUSTED
    BEGIN
    GetWindowInternal: INTERNAL PROCEDURE[] =
      BEGIN
      mappingSpaceD: SpaceD;
      regionD: RegionD;
      validSpace, validSwapUnit: BOOLEAN;
      [validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[@mappingSpaceD, space];
      IF ~validSpace AND ~validSwapUnit THEN Error[invalidHandle];
      regionD ← Projection.Get[space.page];  -- works even if swap units
      IF ~(regionD.state IN CachedRegion.Mapped) THEN Error[noWindow];
      IF regionD.levelMapped ~= space.level THEN
        [] ← Hierarchy.GetDescriptor[
          @mappingSpaceD, Handle[level: regionD.levelMapped, page: space.page]];
      window ←
        IF mappingSpaceD.dataOrFile = data THEN defaultWindow
        ELSE [
          [
          mappingSpaceD.window.file.fID,
          IF mappingSpaceD.writeProtected THEN File.read ELSE File.read + File.write],
          mappingSpaceD.window.base+space.page-mappingSpaceD.interval.page];
      END;
    EnterSpace[GetWindowInternal];
    END;

  Kill: PUBLIC --EXTERNAL-- PROCEDURE [space: Handle] =
    BEGIN
    KillInternal: INTERNAL PROCEDURE[] =
      BEGIN
      validSpace, validSwapUnit: BOOLEAN;
      interval: Interval;
      [validSpace, validSwapUnit, interval] ← Hierarchy.GetInterval[space];
      IF ~validSpace AND ~validSwapUnit THEN Error[invalidHandle];
      ApplyToInterval[interval, CachedRegion.kill];
      END;
    EnterSpace[KillInternal];
    END;

  LongPointer: PUBLIC --EXTERNAL-- SAFE PROCEDURE [space: Handle]
    RETURNS [pointer: LONG POINTER] = TRUSTED
    BEGIN
    LongPointerInternal: INTERNAL PROCEDURE[] = {
      IF ~Hierarchy.ValidSpaceOrSwapUnit[space] THEN Error[invalidHandle];
      pointer ← LongPointerFromPage[space.page]};
    EnterSpace[LongPointerInternal];
    END;

  LongPointerFromPage: PUBLIC --EXTERNAL-- SAFE PROCEDURE [page: PageNumber]
    RETURNS [LONG POINTER] = TRUSTED {
    RETURN[LOOPHOLE[Inline.LongMult[page, Environment.wordsPerPage]]]};

  PageFromLongPointer: PUBLIC --EXTERNAL-- SAFE PROCEDURE [lp: LONG POINTER]
    RETURNS [page: PageNumber] = TRUSTED {
    OPEN LOOPHOLE[lp, num Environment.Long];
    IF lp = NIL THEN ERROR Error[invalidParameters]
    ELSE RETURN[highbits*256 + lowbits/256]};

  Pointer: PUBLIC --EXTERNAL-- SAFE PROCEDURE [space: Handle]
    RETURNS [pointer: POINTER] = TRUSTED
    BEGIN
    PointerInternal: INTERNAL PROCEDURE[] =
      BEGIN
      IF ~Hierarchy.ValidSpaceOrSwapUnit[space] THEN Error[invalidHandle];
      IF space.level <= handleMDS.level
        OR ~(space.page IN [intervalMDS.page..intervalMDS.page + intervalMDS.count))
        THEN Error[invalidParameters];
      pointer ← LOOPHOLE[(space.page - intervalMDS.page)*wordsPerPage];
      END;
    EnterSpace[PointerInternal];
    END;

  VMPageNumber: PUBLIC --EXTERNAL-- SAFE PROCEDURE [space: Handle]
    RETURNS [page: PageNumber] = TRUSTED
    BEGIN
    VMPageNumberInternal: INTERNAL PROCEDURE[] = {
      IF ~Hierarchy.ValidSpaceOrSwapUnit[space] THEN Error[invalidHandle];
      page ← space.page};
    EnterSpace[VMPageNumberInternal];
    END;

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- The Only VMMgr ENTRY Procedure:
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  --SpaceImplInternal.--
  EnterSpace: PUBLIC --EXTERNAL-- PROCEDURE [internalProc: PROCEDURE[]] =
    -- Enters the Space monitor and calls internalProc.  internalProc may freely
    -- signal or not catch signals from below (but should catch UNWIND from
    -- EnterSpace if appropriate).
    -- All client processes which wish to enter the Space Monitor MUST enter
    -- via this procedure.
    -- This procedure, internalProc, and all procedures that all internalProc's
    -- call must be Cached Descriptor.
    -- In order to avoid being deadlocked on a stateVector, we must transiently
    -- change the caller's priority to a reserved level and have a state vector
    -- allocated for it (in the bootmesa file).  The order of operations is
    -- critical and non-intuitively different for entering and leaving.  On
    -- entering, the priority must be changed after we begin executing
    -- CachedDescriptor code (so the VMHelper can swap us in if necessary)
    -- and after we have entered the Space monitor (so only one process can
    -- claim the reserved state vector), and before we Wait on CONDITION or
    -- attempt to enter any lower-level monitor (note that Process.SetPriority
    -- is not a monitor entry!).  On leaving, the priority must be changed after
    -- we have exited the Space monitor (since Process.SetPriority may voluntarily
    -- give up our state vector which some other process could snarf up by page
    -- faulting), and while we are still standing on cached descriptor code (so
    -- the VMHelper can swap us in if necessary).
    BEGIN

    EnterSpaceEntry: ENTRY PROCEDURE [internalProc: PROCEDURE[]] = INLINE
      BEGIN
      SignalType: TYPE = {
        fileError, fileUnknown, spaceError, spaceInsufficientSpace,
        transactionInvalidHandle, volumeUnknown, volumeInsufficientSpace};
      signalType: SignalType;
      unknownFile: File.Capability;
      fileErrorType: File.ErrorType;
      spaceAvailable: Space.PageCount;
      spaceErrorType: Space.ErrorType;
      unknownVolume: Volume.ID;
      Process.SetPriority[ProcessPriorities.priorityPageFaultLow];  -- SetPriority is *not* a monitor entry.
      --scope of SomeError--
      BEGIN
      internalProc[
        !
        File.Error --[type]-- => {
          fileErrorType ← type; signalType ← fileError; GO TO SomeError};
        File.Unknown --[file]-- => {
          unknownFile ← file; signalType ← fileUnknown; GO TO SomeError};
        --Space.--
        Error --[type]-- => {
          spaceErrorType ← type; signalType ← spaceError; GO TO SomeError};
        --Space.--
        InsufficientSpace --[available]-- => {
          spaceAvailable ← available;
          signalType ← spaceInsufficientSpace;
          GO TO SomeError};
        Transaction.InvalidHandle => {
          signalType ← transactionInvalidHandle; GO TO SomeError};
        Volume.Unknown --[volume]-- => {
          unknownVolume ← volume; signalType ← volumeUnknown; GO TO SomeError};
        Volume.InsufficientSpace => {
          signalType ← volumeInsufficientSpace; GO TO SomeError}];
      EXITS
        SomeError =>
          BEGIN
          Process.SetPriority[priorityPrev];
          SELECT signalType FROM
            fileError => RETURN WITH ERROR File.Error[fileErrorType];
            fileUnknown => RETURN WITH ERROR File.Unknown[unknownFile];
            spaceError => RETURN WITH ERROR --Space.-- Error[spaceErrorType];
            spaceInsufficientSpace =>
              RETURN WITH ERROR --Space.-- InsufficientSpace[spaceAvailable];
            transactionInvalidHandle =>
              RETURN WITH ERROR Transaction.InvalidHandle;
            volumeUnknown => RETURN WITH ERROR Volume.Unknown[unknownVolume];
            volumeInsufficientSpace => RETURN WITH ERROR Volume.InsufficientSpace;
            ENDCASE => Bug[funnySignal];
          END;
      END;  --scope of SomeError--
      END;  --EnterSpaceEntry--

    priorityPrev: Process.Priority = Process.GetPriority[];
    EnterSpaceEntry[internalProc];  -- (priority changed inside.)
    Process.SetPriority[priorityPrev];  -- now that we've exited the monitor.
    END;  --EnterSpace--

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Monitor internal procedures:
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  --SpaceImplInternal.--
  NotePinned: PUBLIC SIGNAL [levelMax: Level, page: PageNumber] = CODE;

  --SpaceImplInternal.--
  ApplyToInterval: PUBLIC INTERNAL PROCEDURE [
    interval: Interval, operation: CachedRegion.Operation] =
    -- Performs, in ascending order, operation (successfully) for each
    -- swap unit of each region of interval (exactly as required by CachedRegion.Apply).
    -- May raise NotePinned (only if operation.action=unmap).
    -- for operation=writeProtect, flush, remap, and unmap, interval must
    -- start and end on a region boundary!  (restriction of CachedRegion.Apply)
    BEGIN
    page, pageNext: PageNumber;
    outcome: CachedRegion.Outcome;
    FOR page ← interval.page, pageNext WHILE page < interval.page + interval.count
      DO  -- for each swap unit in interval..
      DO  --REPEAT operation UNTIL outcome=ok or notePinned--
        [outcome, pageNext] ← CachedRegion.Apply[page, operation];
        WITH outcome SELECT FROM
          ok => EXIT;
          notePinned -- [levelMax] -- =>
            BEGIN SIGNAL NotePinned[levelMax, page]; EXIT END;
          regionDMissing => Projection.Touch[page];
          regionDDirty => Projection.ForceOut[page];
          retry => NULL;  -- go around again
          spaceDMissing -- [level] -- => Hierarchy.Touch[[level, page]];
          -- error -- ENDCASE => ERROR Bug[funnyOutcome];
        ENDLOOP;
      ENDLOOP;
    END;

  --SpaceImplInternal.--
  ApplyToSpace: PUBLIC INTERNAL PROCEDURE [
    handle: Handle, operation: CachedRegion.Operation]
    RETURNS [validHandle: BOOLEAN] =
    BEGIN
    validSpace, validSwapUnit: BOOLEAN;
    interval: Interval;
    [validSpace, validSwapUnit, interval] ← Hierarchy.GetInterval[handle];
    validHandle ← validSpace OR validSwapUnit;
    IF validHandle THEN ApplyToInterval[interval, operation];
    END;

  --SpaceImplInternal.--
  ForAllRegions: PUBLIC INTERNAL PROCEDURE [
    interval: Interval, Predicate: PROCEDURE [RegionD] RETURNS [true: BOOLEAN]]
    RETURNS [BOOLEAN] =
    -- Returns as soon as Predicate returns FALSE.
    BEGIN
    page: PageNumber;
    regionD: RegionD;
    FOR page ← interval.page, regionD.interval.page + regionD.interval.count WHILE
      page < interval.page + interval.count DO
      regionD ← Projection.Get[page];
      IF ~Predicate[regionD] THEN RETURN[FALSE]
      ENDLOOP;
    RETURN[TRUE]
    END;

  END.


LOG

(For earlier log entries see Pilot 3.0 archive version.)
January 25, 1980  1:50 PM	Knutsen	Use new ErrorType's.
January 28, 1980  10:54 AM	Forrest Action: Made SpaceImpl take starting parameters and
				eliminated InitSpace; copied in (most) of LongPtr<=>Page from UtilitiesImpl.
February 25, 1980  6:04 PM	Knutsen	AR3594: CreateUSU[spaceWithSU] raised wrong signal.
				AR1935: use Projection.DeleteSwapUnits.  Renumbered the Internal errors.
April 16, 1980  10:09 AM	Knutsen	Remap must always wait for operation complete to avoid
				having the file deleted out from under the swapping operation.
				DeleteIfLeaf must avoid walking off the ends of VM.  Converted
				SpaceImpl[] into InitializeSpace[] AGAIN!
April 17, 1980  1:03 PM		Gobbel	Added transaction handles.
April 21, 1980  6:01 PM		Gobbel	Implemented Space.Copy.
May 19, 1980  5:42 PM		Gobbel	FrameOps=>Frame, ControlDefs => PrincOps.
May 21, 1980  2:31 PM		Gobbel	Mesa 6 change: f.code.longbase replaced by RuntimeInternal.CodeBase[frame]
				in SpaceForCode.
June 16, 1980  5:12 PM		Gobbel	Transaction operations added.
June 19, 1980  2:27 PM		Gobbel	Created SpaceImplA from parts of old SpaceImpl.
August 7, 1980  1:06 PM		Knutsen	Moved initialization relevant to SpaceImplB to there.  Moved
				ApplyToInterval, etc. here.  GetWindow returns current
				writeProtected-ness.  Implement MakeGlobalFrame*, MakeProcedure*, etc.
September 8, 1980  4:14 PM	Knutsen	Delete now does WithdrawFromTransaction.
September 11, 1980  1:15 PM	Gobbel	Imported TransactionState.
October 10, 1980  9:09 PM	Fay	Fixed DeleteIfLeaf to correctly recognize a leaf space
				with no right neighbor.
January 23, 1981  9:54 AM	Knutsen	Implemented CreateAligned.  Fixed MakeProcedure*.  Process priority.
January 27, 1981  1:47 PM	Knutsen	Use EnterSpace (moved here).  Helper moved to SpaceImplB.
January 30, 1981  11:25 AM	Knutsen	Code[] wrongly thought startPC was a byte PC.
February 13, 1981  12:24 PM	Knutsen	EnterSpace must release lock before resetting priority.
February 18, 1981  12:20 PM	Knutsen	Pass countVM to InitializeSpaceImplB.
March 4, 1981  11:08 AM		Yokota	Added four procedures defined in SpaceExtras - Activate(Deactivate)Proc(SwapUnit).
March 9, 1981  11:31 AM		Yokota	pageRover in the parent space descriptor is updated for a child space deletion.
 4-Feb-82 13:17:13		Levin Fix bug in CreateAny in alignment logic for powerOf2.
16-Feb-82 17:44:12		Levin Fix Code[] to handle nested procedures properly. Also
 				fix MakeGlobalFrame{Resident|Swappable} to ignore frames
				allocated in the frame heap.
June 4, 1982 9:56 am	Levin Fix another bug in CreateAny in alignment logic for powerOf2
August 26, 1982 11:15 am	Levin	Make things SAFE.
August 26, 1982 3:55 pm	Levin	Add alignSize logic to CreateAny; exploit in CreateAligned.
October 26, 1982 9:44 am	Levin	Change GetWindow to work reasonably on subspaces of mapped spaces.