-- MMFsp.Mesa  Edited by Sandman on May 17, 1979  8:44 AM

DIRECTORY
  AltoDefs: FROM "altodefs" USING [PageSize],
  FSPDefs: FROM "fspdefs" USING [
    BlockSize, Deallocator, FreeNodePointer, NodeHeader, NodePointer,
    ZoneHeader, ZoneOverhead, ZonePointer],
  ProcessDefs: FROM "processdefs" USING [InitializeMonitor],
  StringDefs: FROM "stringdefs" USING [WordsForString],
  SystemDefs: FROM "systemdefs" USING [
    AllocateResidentPages, FreePages, PagesForWords];

MMFSP: MONITOR LOCKS z.lock USING z: ZonePointer
  IMPORTS ProcessDefs, StringDefs, SystemDefs EXPORTS FSPDefs, SystemDefs
  SHARES FSPDefs = PUBLIC
  BEGIN OPEN FSPDefs;  -- Mesa Free Storage Package --

    -- A set of procedures to manage allocation within a zone.
    -- Coalescing of free nodes occurs during allocation; all
    -- free nodes following a candidate node are merged before
    -- any space is allocated.  The logic is derived from a
    -- BCPL program by E. M. McCreight and was suggested by an
    -- exercise in Knuth Volume I, p. 453 #19

  UsedNodeSize: PRIVATE BlockSize = SIZE [inuse NodeHeader];
  FreeNodeSize: PRIVATE BlockSize = SIZE [free NodeHeader];
  ZoneHeaderSize: PRIVATE BlockSize = SIZE [ZoneHeader];

  ZoneTooSmall: ERROR [POINTER] = CODE;

  DoNothingDeallocate: Deallocator = BEGIN NULL END;

  MakeZone: PROCEDURE [base: POINTER, length: BlockSize] RETURNS [z: ZonePointer] =
    BEGIN
    RETURN[MakeNewZone[base, length, DoNothingDeallocate]];
    END;

  MakeNewZone: PROCEDURE [base: POINTER, length: BlockSize, deallocate: Deallocator]
  RETURNS [z: ZonePointer] =
    BEGIN
    fn: FreeNodePointer;
    an: POINTER TO inuse NodeHeader;
    IF length < ZoneHeaderSize+FreeNodeSize+UsedNodeSize
      THEN ERROR ZoneTooSmall[base];
    z ← base; 
    -- set up the bulk of the zone as a large free block.
    fn ← base + ZoneHeaderSize;
    fn↑ ← NodeHeader[length: length-(ZoneHeaderSize+UsedNodeSize),
      extension: free[fwdp: @z.node, backp: @z.node]];
    -- set up allocated node (smallest possible) at end of block.
    an ← base + (length-UsedNodeSize);
    an↑ ← NodeHeader[length: UsedNodeSize, extension: inuse[]];
    -- set up the zone header
    z↑ ← ZoneHeader[rover: fn, lock:, restOfZone: NIL, length: length,
      deallocate: deallocate, threshold: FreeNodeSize, checking: FALSE,
      node: NodeHeader[length: 0, extension: free[fwdp: fn, backp: fn]]];
    ProcessDefs.InitializeMonitor[@z.lock];
    RETURN
    END;

  AddToZone: PROCEDURE [z: ZonePointer, base: POINTER, length: BlockSize] =
    BEGIN
    AddToNewZone[z, base, length, DoNothingDeallocate];
    END;

  AddToNewZone: ENTRY PROCEDURE [z: ZonePointer, base: POINTER,
    length: BlockSize, deallocate: Deallocator] =
    BEGIN
    newz: ZonePointer;
    firstnew, lastnew: FreeNodePointer;
    newz ← MakeNewZone[base, length, deallocate];
    -- splice the zones together
    firstnew ← newz.node.fwdp;  lastnew ← newz.node.backp;
    z.node.backp.fwdp ← firstnew;
    firstnew.backp ← z.node.backp;
    lastnew.fwdp ← @z.node;
    z.node.backp ← lastnew;
    -- make newz head an empty list
    newz.node.fwdp ← newz.node.backp ← @newz.node;
    newz.restOfZone ← z.restOfZone;
    z.restOfZone ← newz;
    RETURN
    END;

  NoRoomInZone: SIGNAL [ZonePointer] = CODE;

  MakeNode: PROCEDURE [z: ZonePointer, n: BlockSize] RETURNS [POINTER] =
    BEGIN
    node: NodePointer;
    IF INTEGER[n] < 0 THEN ERROR ZoneTooSmall[z];
    n ← MAX[n+UsedNodeSize, FreeNodeSize];
    WHILE (node ← GetNode[z, n]) = NIL DO
      SIGNAL NoRoomInZone[z];
      ENDLOOP;  -- try again if RESUMEd from the signal
    RETURN[node]
    END;

  GetNode: PRIVATE ENTRY PROCEDURE [z: ZonePointer, n: BlockSize] RETURNS [POINTER] =
    BEGIN
    rover: FreeNodePointer ← z.rover;
    node, neighbour: NodePointer;
    nodelength, nl: BlockSize;
    DO
      nodelength ← rover.length;
      FOR neighbour ← rover+nodelength, neighbour+nl DO 
	WITH neighbour SELECT FROM
	  inuse => EXIT;
	  free =>
	    BEGIN        -- coalesce
	    IF (nl ← length) = 0 THEN EXIT;        -- end of zone
	    fwdp.backp ← backp;  backp.fwdp ← fwdp;
	    z.rover ← rover;        -- in case neighbor was z.rover
	    nodelength ← nodelength+nl;
	    END;
	  ENDCASE;
	ENDLOOP;
      IF nodelength >= n THEN
	BEGIN
	IF (nl ← (nodelength-n)) > MAX[FreeNodeSize, z.threshold] THEN
	  BEGIN        -- split the block
	  z.rover ← rover;  rover.length ← nl;
	  node ← rover+nl;  nodelength ← n;
	  END
	ELSE
	  BEGIN
	  rover.fwdp.backp ← rover.backp;
	  z.rover ← rover.backp.fwdp ← rover.fwdp;
	  node ← rover;
	  END;
	node↑ ← NodeHeader[nodelength, inuse[]];
	RETURN [node + UsedNodeSize]
	END
      ELSE rover.length ← nodelength;
      IF (rover ← rover.fwdp) = z.rover THEN EXIT;
      ENDLOOP;
    RETURN[NIL];
    END;

  FreeNode: ENTRY PROCEDURE [z: ZonePointer, p: POINTER] =
    BEGIN
    FreeThisNode[z, p ! UNWIND => NULL];
    RETURN
    END;

  InvalidNode: ERROR [POINTER] = CODE;

  FreeThisNode: PRIVATE PROCEDURE [z: ZonePointer, p: POINTER] =
    BEGIN
    node: NodePointer = p-UsedNodeSize;
    WITH node SELECT FROM
      free => ERROR InvalidNode[p];
      ENDCASE =>
	node↑ ← NodeHeader[node.length, free[@z.node, z.node.backp]];
    WITH n:node SELECT FROM
      free => z.node.backp ← n.backp.fwdp ← @n;
      ENDCASE;
    RETURN
    END;

  NodeSize: PROCEDURE [p: POINTER] RETURNS [BlockSize] =
    BEGIN
    node: NodePointer = p-UsedNodeSize;
    WITH node SELECT FROM
      free => ERROR InvalidNode[p];
      ENDCASE => RETURN [length-UsedNodeSize];
    END;

  PruneZone: ENTRY PROCEDURE [z: ZonePointer] RETURNS [BOOLEAN] =
    BEGIN
    didit: BOOLEAN ← FALSE;
    rest: ZonePointer;
    zone: ZonePointer ← z;
    prev: ZonePointer ← z;
    node: NodePointer;
    nl: BlockSize;
    FOR zone ← z.restOfZone, rest UNTIL zone = NIL DO
      rest ← zone.restOfZone;
      IF zone.deallocate # DoNothingDeallocate THEN
	FOR node ← LOOPHOLE[zone+ZoneHeaderSize, NodePointer], node+nl DO
	  WITH node SELECT FROM
	    inuse =>
	      BEGIN
	      IF length = UsedNodeSize THEN
		BEGIN  -- end of zone
		FreeZone[zone ! UNWIND => NULL];
		didit ← TRUE;
		prev.restOfZone ← rest;
		END
	      ELSE prev ← zone;
	      EXIT
	      END;
	    free => nl ← length;
	    ENDCASE;
	  ENDLOOP;
      ENDLOOP;
    z.rover ← z.node.fwdp; -- reset rover incase in a freed zone
    RETURN[didit];
    END;

  FreeZone: PRIVATE PROCEDURE [zone: ZonePointer] =
    BEGIN
    node: NodePointer;
    nl: BlockSize;
    FOR node ← LOOPHOLE[zone+ZoneHeaderSize, NodePointer], node+nl DO
      WITH node SELECT FROM
	inuse => EXIT;  -- end of zone
	free =>
	  BEGIN
	  nl ← length;
	  backp.fwdp ← fwdp;
	  fwdp.backp ← backp;
	  END;
	ENDCASE;
      ENDLOOP;
    zone.deallocate[zone];
    END;


  -- DestroyZone is not an entry procedure since the monitorlock will be
  --   gone when we try to exit the procedure

  DestroyZone: PROCEDURE [z: ZonePointer] =
    BEGIN
    rest: ZonePointer;
    IF z = TheHeap THEN RETURN;
    FOR z ← z, rest UNTIL z = NIL DO
      rest ← z.restOfZone;
      z.deallocate[z];
      ENDLOOP;
    END;

  -- management of the heap

  TheHeap: PRIVATE ZonePointer;

  HeapZone: PROCEDURE RETURNS [ZonePointer] =
    BEGIN
    RETURN [TheHeap]
    END;

  AllocateHeapNode: PROCEDURE [nwords: CARDINAL] RETURNS [p: POINTER] =
    BEGIN OPEN SystemDefs;
    np: CARDINAL;
    IF INTEGER[nwords] < 0 THEN ERROR ZoneTooSmall[TheHeap];
    p ← MakeNode[TheHeap, nwords ! NoRoomInZone =>
      BEGIN
      np ← PagesForWords[nwords + ZoneOverhead + UsedNodeSize];
      AddToNewZone[z: TheHeap, base: AllocateResidentPages[np],
	length: np*AltoDefs.PageSize, deallocate: FreePages];
      RESUME
      END];
    RETURN
    END;

  FreeHeapNode: PROCEDURE [p: POINTER] =
    BEGIN
    FreeNode[TheHeap, p];  RETURN
    END;


  AllocateHeapString: PROCEDURE [nchars: CARDINAL] RETURNS [STRING] =
    BEGIN
    OPEN StringDefs;
    p: POINTER TO MACHINE DEPENDENT RECORD[	-- faked string header
      length: CARDINAL,
      maxlength: CARDINAL];
    p ← AllocateHeapNode[WordsForString[nchars]];
    p.length ← 0;  p.maxlength ← nchars;
    RETURN [LOOPHOLE[p, STRING]]
    END;

  FreeHeapString: PROCEDURE [s: STRING] =
  LOOPHOLE[FreeHeapNode];

  PruneHeap: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    RETURN[PruneZone[TheHeap]]
    END;

  -- initialization code

    TheHeap ← MakeNewZone[SystemDefs.AllocateResidentPages[1],
      1*AltoDefs.PageSize, SystemDefs.FreePages];

END.