-- Fsp.Mesa Edited by Sandman on August 1, 1980 10:08 AM -- Copyright Xerox Corporation 1979, 1980 DIRECTORY AltoDefs USING [PageSize], FSPDefs USING [ BlockSize, Deallocator, FreeNodePointer, NodeHeader, NodePointer, ZoneHeader, ZoneOverhead, ZonePointer], InlineDefs USING [COPY], NucleusOps USING [], ProcessDefs USING [InitializeMonitor], SegmentDefs USING [ DataSegmentAddress, DataSegmentHandle, DefaultMDSBase, HardDown, HeapDS, MakeDataSegment, VMtoDataSegment], Storage USING [FreePages, PagesForWords], StringDefs USING [WordsForString], SystemDefs USING []; FSP: MONITOR LOCKS z.lock USING z: ZonePointer IMPORTS InlineDefs, ProcessDefs, SegmentDefs, StringDefs, Storage EXPORTS FSPDefs, NucleusOps, Storage, 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; ZoneTooLarge: 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]; IF CARDINAL[length] > CARDINAL[LAST[BlockSize]] THEN ERROR ZoneTooLarge[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; ValidateZone[z ! UNWIND => NULL]; 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; IF z.checking THEN CheckZone[z ! UNWIND => NULL]; RETURN END; InvalidZone: ERROR [POINTER] = CODE; -- zone header looks fishy ValidateZone: PRIVATE PROCEDURE [z: ZonePointer] = BEGIN SELECT TRUE FROM (z.node.length # 0), ((@z.node + z.length - UsedNodeSize).length # UsedNodeSize), (z.node.fwdp.backp # @z.node OR z.node.backp.fwdp # @z.node) => ERROR InvalidZone[z]; ENDCASE; RETURN END; NodeLoop: ERROR [ZonePointer] = CODE; CheckZone: PRIVATE PROCEDURE [z: ZonePointer] = BEGIN node: FreeNodePointer; count: INTEGER; ValidateZone[z]; count ← (LAST[BlockSize] - FIRST[BlockSize])/FreeNodeSize + 1; node ← @z.node; DO CheckNode[z, node, FALSE]; IF (count ← count - 1) < 0 THEN ERROR NodeLoop[z]; IF (node ← node.fwdp) = @z.node THEN EXIT; ENDLOOP; RETURN END; InvalidNode: ERROR [POINTER] = CODE; -- node appears damaged CheckNode: PRIVATE PROCEDURE [ z: ZonePointer, node: NodePointer, free: BOOLEAN] = BEGIN OP: TYPE = ORDERED POINTER; p: OP; zone: ZonePointer; FOR zone ← z, zone.restOfZone UNTIL zone = NIL DO p ← LOOPHOLE[zone, OP]; IF LOOPHOLE[node, OP] IN [p..p + zone.length) THEN EXIT; REPEAT FINISHED => GOTO error; ENDLOOP; WITH node SELECT FROM free => IF free THEN ERROR; ENDCASE; DO WITH node SELECT FROM inuse => IF length = UsedNodeSize THEN EXIT; -- end of zone free => BEGIN IF fwdp.backp # node OR backp.fwdp # node THEN GO TO error; IF length = 0 AND node # @z.node THEN GO TO error; END; ENDCASE; node ← node + node.length; IF node.state # inuse THEN EXIT; ENDLOOP; RETURN EXITS error => BEGIN z.checking ← FALSE; ERROR InvalidNode[node + UsedNodeSize]; END; 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; IF z.checking THEN CheckZone[z ! UNWIND => NULL]; 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 rover.length ← nl; node ← rover + nl; nodelength ← n; z.rover ← rover; node↑ ← NodeHeader[nodelength, inuse[]]; RETURN[node + UsedNodeSize] END ELSE BEGIN rover.fwdp.backp ← rover.backp; z.rover ← rover.backp.fwdp ← rover.fwdp; node ← rover; node↑ ← NodeHeader[nodelength, inuse[]]; RETURN[node + UsedNodeSize] END; END; rover.length ← nodelength; IF (rover ← rover.fwdp) = z.rover THEN EXIT; ENDLOOP; RETURN[NIL]; END; FreeNode: ENTRY PROCEDURE [z: ZonePointer, p: POINTER] = BEGIN IF z.checking THEN BEGIN CheckZone[z ! UNWIND => NULL]; CheckNode[z, p - UsedNodeSize, TRUE ! UNWIND => NULL]; END; FreeThisNode[z, p ! UNWIND => NULL]; RETURN END; 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; SplitNode: ENTRY PROCEDURE [z: ZonePointer, p: POINTER, n: BlockSize] = BEGIN node: NodePointer = p - UsedNodeSize; lastpart: NodePointer; t: BlockSize; IF z.checking THEN BEGIN CheckZone[z ! UNWIND => NULL]; CheckNode[z, node, TRUE ! UNWIND => NULL]; END; n ← MAX[n + UsedNodeSize, FreeNodeSize]; WITH node SELECT FROM free => RETURN WITH ERROR InvalidNode[p]; ENDCASE => IF (t ← node.length - n) >= MAX[FreeNodeSize, z.threshold] THEN BEGIN lastpart ← node + n; lastpart↑ ← NodeHeader[t, inuse[]]; FreeThisNode[z, lastpart + UsedNodeSize ! UNWIND => NULL]; node.length ← n; END; 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; LargeNode: CARDINAL = 150; HeapZone: PROCEDURE RETURNS [ZonePointer] = BEGIN RETURN[TheHeap] END; Node, AllocateHeapNode: PROCEDURE [nwords: CARDINAL] RETURNS [p: POINTER] = BEGIN OPEN Storage; IF INTEGER[nwords] < 0 THEN ERROR ZoneTooSmall[TheHeap]; IF nwords + UsedNodeSize > LargeNode THEN BEGIN p ← GetMorePages[PagesForWords[nwords + UsedNodeSize]]; p↑ ← NodeHeader[length: nwords + UsedNodeSize, extension: inuse[]]; RETURN[p + 1] END; p ← MakeNode[ TheHeap, nwords ! NoRoomInZone => BEGIN Expand[PagesForWords[nwords + ZoneOverhead + UsedNodeSize]]; RESUME END]; RETURN END; Expand: PROCEDURE [pages: CARDINAL] = BEGIN OPEN Storage; AddToNewZone[ z: TheHeap, base: GetMorePages[pages], length: pages*AltoDefs.PageSize, deallocate: FreePages]; END; GetMorePages: PROCEDURE [pages: CARDINAL] RETURNS [POINTER] = BEGIN OPEN SegmentDefs; seg: DataSegmentHandle ← MakeDataSegment[DefaultMDSBase, pages, HardDown]; seg.type ← HeapDS; RETURN[DataSegmentAddress[seg]]; END; Free, FreeHeapNode: PROCEDURE [p: POINTER] = BEGIN OPEN SegmentDefs; pp: POINTER; IF p = NIL THEN RETURN; IF LOOPHOLE[(pp ← p - 1), POINTER TO inuse NodeHeader].length > LargeNode AND DataSegmentAddress[VMtoDataSegment[pp]] = pp THEN { Storage.FreePages[pp]; RETURN}; FreeNode[TheHeap, p]; RETURN END; String, AllocateHeapString: PROCEDURE [nchars: CARDINAL] RETURNS [s: STRING] = BEGIN s ← AllocateHeapNode[StringDefs.WordsForString[nchars]]; s↑ ← StringBody[length: 0, maxlength: nchars, text:]; RETURN[s] END; FreeString, FreeHeapString: PROCEDURE [s: STRING] = LOOPHOLE[Free]; CopyString: PROCEDURE [s: STRING, longer: CARDINAL ← 0] RETURNS [STRING] = BEGIN ns: STRING; IF s = NIL THEN RETURN[IF longer = 0 THEN NIL ELSE String[longer]]; ns ← AllocateHeapString[s.length + longer]; InlineDefs.COPY[from: @s.text, to: @ns.text, nwords: (s.length + 1)/2]; ns.length ← s.length; RETURN[ns]; END; ExpandString: PROCEDURE [s: POINTER TO STRING, longer: CARDINAL ← 0] = BEGIN ns: STRING; IF s↑ = NIL THEN {IF longer # 0 THEN s↑ ← String[longer]; RETURN}; ns ← AllocateHeapString[s.maxlength + longer]; InlineDefs.COPY[from: @s.text, to: @ns.text, nwords: (s.length + 1)/2]; ns.length ← s.length; FreeHeapString[s↑]; s↑ ← ns; END; PruneHeap, Prune: PROCEDURE RETURNS [BOOLEAN] = BEGIN RETURN[PruneZone[TheHeap]] END; -- initialization code TheHeap ← MakeNewZone[GetMorePages[2], 2*AltoDefs.PageSize, Storage.FreePages]; END.