-- ZoneCleanupImpl.Mesa
-- last edited February 6, 1981 9:13 AM by Willie-Sue Haugeland
-- last edit by Russ Atkinson, 17-Jul-81 14:12:24
-- last edited February 25, 1983 10:19 am by Paul Rovner
DIRECTORY
Inline, -- also for implicit imports
RTBases USING[BaseOverhead],
RTBasic USING[Pointer],
RTFlags USING[checking, clearing],
RTMicrocode USING[LONGZERO],
RTQuanta USING[QuantumSizeMOD, LASTAddress, QuantumIndex, QuantumSize,
QuantumCount],
RTStorageOps USING[NotImplemented],
Runs USING[Run],
SafeStorage USING[], -- EXPORTs only
UnsafeStorage USING[], -- EXPORTs only
RTZones;
ZoneCleanupImpl: MONITOR -- protects zones
LOCKS zn.LOCK USING zn: PZone
IMPORTS Inline, RTMicrocode, RTQuanta, RTStorageOps, RTZones
EXPORTS SafeStorage, UnsafeStorage
= BEGIN
OPEN RTBasic, RTMicrocode, RTQuanta, RTZones;
checking: BOOLEAN = FALSE; -- RTFlags.checking;
-- Exported procedures (public)
EraseZone: PUBLIC PROC[zone: ZONE] =
BEGIN
ERROR RTStorageOps.NotImplemented["EraseZone"];
-- zn: Zone = zone;
-- WITH zn: zn SELECT FROM
-- quantum => EraseQuantumZone[LOOPHOLE[@zn, QuantumZone]];
-- mixed => EraseMixedZone[LOOPHOLE[@zn, MixedZone]];
-- ENDCASE;
END;
TrimZone: PUBLIC SAFE PROC[zone: ZONE] =
TRUSTED { WITH zn: LOOPHOLE[zone, PZone] SELECT FROM
quantized => {TrimQuantizedZone[@zn]};
prefixed => {MergePrefixedZone[@zn]; TrimPrefixedZone[@zn]};
ENDCASE};
MergeUZone: PUBLIC PROC[zone: UNCOUNTED ZONE] =
{ WITH zn: LOOPHOLE[zone, LONG POINTER TO PZone]^ SELECT FROM
prefixed => MergePrefixedZone[@zn];
ENDCASE};
TrimUZone: PUBLIC PROC[zone: UNCOUNTED ZONE] =
{ WITH zn: LOOPHOLE[zone, LONG POINTER TO PZone]^ SELECT FROM
quantized => {TrimQuantizedZone[@zn]};
prefixed => {MergePrefixedZone[@zn]; TrimPrefixedZone[@zn]};
ENDCASE};
MergeAllPrefixedZones: PUBLIC SAFE PROC =
TRUSTED { maxZi: ZoneIndex = GetZiNext[] - 1;
FOR i: ZoneIndex IN [1..maxZi] DO
zn: Zone = MapZiZn[i];
IF zn # NIL AND zn.sr = prefixed THEN MergePrefixedZone[LOOPHOLE[zn]];
ENDLOOP};
TrimAllZones: PUBLIC SAFE PROC =
TRUSTED { FOR i: ZoneIndex IN [1..GetZiNext[] - 1] DO
IF MapZiZn[i] # NIL THEN TrimZone[LOOPHOLE[MapZiZn[i]]]; ENDLOOP};
-- Internal procedures for quantum zones
EraseQuantizedZone: PROC[zn: QuantizedZone] = -- *** dummy for now
{NULL};
TrimQuantizedZone: ENTRY PROC[zn: PZone] =
BEGIN ENABLE UNWIND => NULL;
qzn: PQuantizedZone = LOOPHOLE[zn];
FOR i: CARDINAL IN [0..qzn.mAsz] DO -- for each subzone
sz: SubZone = @qzn.pAsz[i];
size: LONG CARDINAL = sz.size;
pSzFl: FreeList = @sz.fl;
NodeProc: TYPE = PROC[ ptr: LONG POINTER TO LONG UNSPECIFIED,
q: QuantumIndex, size: LONG CARDINAL,
unlinkProc: PROC];
MapFl: PROC[procNode: NodeProc] =
BEGIN
-- Apply procNode to each node on a free list.
flPrev: FreeList ← pSzFl;
fl: FreeList;
Unlink: PROC = { flPrev^ ← fl^; fl ← flPrev};
UNTIL (fl ← flPrev^) = NIL DO
procNode[fl, MapPtrQ[fl], size, Unlink];
flPrev ← fl;
ENDLOOP;
END;
-- Count the number of occupied words in each quantum as follows.
-- Quanta with only one free node so far are linked on flOne.
-- Quanta with more than one free node are linked on flMore, where
-- the first node in the quantum is a QCounter and its successor
-- is the link to the next such quantum's QCounter.
QCounter: TYPE = MACHINE DEPENDENT RECORD
[
empty: BOOLEAN ← FALSE, -- true iff this quantum is known empty
count: [0..LAST[CARDINAL]/2], -- so empty + count fits in 1 word.
-- limit should be QuantumSize. count of occupied words
link: INTEGER -- should be (-QuantumSize..QuantumSize)
-- self-relative link: points to a cell which contains a PQCounter
];
PQCounter: TYPE = LONG POINTER TO QCounter;
PPQCounter: TYPE = LONG POINTER TO PQCounter;
flOne: FreeList ← NIL;
flMore: PQCounter ← NIL;
FindOnOne: PROC[q: QuantumIndex] RETURNS[fl: FreeList] =
BEGIN -- always deletes from flOne if successful
flPrev: FreeList ← @flOne;
UNTIL (fl ← flPrev^) = NIL DO
IF MapPtrQ[fl] = q THEN
BEGIN
flPrev^ ← fl^;
RETURN[fl];
END;
flPrev ← fl;
ENDLOOP;
RETURN[NIL];
END;
FindOnMore: PROC[q: QuantumIndex] RETURNS[flNext: PPQCounter] =
BEGIN -- moves to head of flMore if successful
flPrev: PPQCounter ← @flMore;
pqc: PQCounter;
UNTIL (pqc ← flPrev^) = NIL DO
flNext ← LOOPHOLE[pqc+pqc.link];
IF MapPtrQ[pqc] = q THEN
BEGIN
IF pqc # flMore THEN
BEGIN
flPrev^ ← flNext^; -- delink here
flNext^ ← flMore; -- insert at head
flMore ← pqc;
END;
RETURN[flNext];
END;
flPrev ← flNext;
ENDLOOP;
RETURN[NIL];
END;
-- First pass, accumulate amount of occupied space in each quantum
AccumNode: NodeProc = -- binds ptr, q, size, unlinkProc
BEGIN
fl: FreeList;
qsize: CARDINAL =
QuantumSize - (IF q = zn.qFirst THEN RTBases.BaseOverhead ELSE 0);
IF size > qsize/2 THEN -- only one (free) node in this quantum
BEGIN
nq: QuantumCount = MapSizeNq[size];
unlinkProc[];
ReturnQuanta[zn, q, nq];
RETURN;
END;
IF FindOnMore[q] # NIL THEN -- found node is now head of flMore
BEGIN
flMore.count ← flMore.count - LOOPHOLE[size, Inline.LongNumber].lowbits;
RETURN;
END;
unlinkProc[];
IF (fl ← FindOnOne[q]) # NIL THEN
BEGIN
IF size > qsize/3 THEN -- only two (free) nodes in this quantum
BEGIN ReturnQuanta[zn, q, 1]; RETURN; END;
fl^ ← LOOPHOLE[flMore]; -- move node from flOne to flMore
flMore ← LOOPHOLE[ptr];
flMore^ ←
[count: QuantumSize-LOOPHOLE[size, Inline.LongNumber].lowbits*2,
link: Inline.LowHalf[fl]-Inline.LowHalf[ptr]];
RETURN;
END;
ptr^ ← flOne; -- first free node encountered in quantum
flOne ← ptr;
END;
-- Second pass, free vacant quanta
ReturnNode: NodeProc = -- binds ptr, q, size, unlinkProc
BEGIN
flNext: PPQCounter;
IF (flNext ← FindOnMore[q]) # NIL THEN -- moves found node to head of flMore
BEGIN
empty: BOOLEAN ← flMore.empty;
IF flMore.count < size THEN {empty ← flMore.empty ← TRUE}; -- quantum is returnable
IF empty THEN unlinkProc[]; -- remove node from the free list
flMore.count ← flMore.count+LOOPHOLE[size, Inline.LongNumber].lowbits;
IF flMore.count = QuantumSize-(size + size) THEN -- last visit to this quantum
BEGIN
fl: FreeList ← LOOPHOLE[flMore];
flMore ← flNext^; -- removes node from flMore
IF empty THEN ReturnQuanta[zn, q, 1] -- free the quantum
ELSE -- return the flMore entry to the freelist
BEGIN
LOOPHOLE[flNext, FreeList]^ ← ptr^;
ptr^ ← fl;
fl^ ← LOOPHOLE[flNext, FreeList];
END;
END;
END;
END;
-- Finally, the driver
IF SIZE[FreeList] < SIZE[QCounter] THEN ERROR; -- minimum free block size constraint
IF IsSubZoneVacant[sz] THEN LOOP;
MapFl[AccumNode];
MapFl[ReturnNode];
WHILE flMore # NIL DO -- return the flMore list to the free list
fl1: FreeList ← LOOPHOLE[flMore];
fl2: FreeList ← LOOPHOLE[flMore+flMore.link];
flMore ← LOOPHOLE[fl2^];
fl1^ ← fl2; fl2^ ← pSzFl^; pSzFl^ ← fl1;
ENDLOOP;
IF flOne # NIL THEN -- return flOne list to freelist
BEGIN
fl: FreeList ← flOne;
UNTIL fl^ = NIL DO fl ← fl^ ENDLOOP;
fl^ ← pSzFl^;
pSzFl^ ← flOne;
END;
ENDLOOP; -- for each subzone
IF qzn.qNext # 0 THEN {ReturnQuanta[zn, qzn.qNext, qzn.qLast - qzn.qNext]; qzn.qNext ← qzn.qLast ← 0};
END;
-- Internal procedures for heap zones
ErasePrefixedZone: PROC[zn: PPrefixedZone] =
{NULL}; -- *** dummy for now
-- merge adjacent free blocks by scanning the free list
MergePrefixedZone: ENTRY PROC[zn: PZone] =
{ ENABLE UNWIND => NULL;
pzn: PPrefixedZone = LOOPHOLE[zn];
pfnFirst: PFreeNode ← @pzn.fnd;
pfn: PFreeNode ← pfnFirst;
UNTIL (pfn ← pfn.pfnNext) = pfnFirst DO
pfnLength: LongNodeSize = NodeLength[pfn];
pfnEndpfn: PNode ← pfn + pfnLength;
IF LOOPHOLE[pfnEndpfn, LONG CARDINAL] > LASTAddress THEN LOOP;
IF MapPtrZf[LOOPHOLE[pfnEndpfn, Pointer]] = [prefixed[zi: zn.zi]] THEN
{ WITH pfnEnd: pfnEndpfn SELECT FROM
free => -- next guy is free, merge it with pfn
{ newLength: LongNodeSize = pfnLength + NodeLength[@pfnEnd];
IF checking THEN
{IF pfn.pfnNext.pfnPrev # pfn
OR pfn.pfnPrev.pfnNext # pfn
OR pfnEnd.pfnNext.pfnPrev # @pfnEnd
OR pfnEnd.pfnPrev.pfnNext # @pfnEnd
OR pfnLength < MinBlockSize
OR NodeLength[@pfnEnd] < MinBlockSize
THEN ERROR};
pfnEnd.pfnNext.pfnPrev ← pfnEnd.pfnPrev;
pfnEnd.pfnPrev.pfnNext ← pfnEnd.pfnNext;
IF RTFlags.clearing -- keep free objects clear
THEN [] ← LONGZERO[@pfnEnd, SIZE[free NodeHeader]];
pfn.SizeLo ← LOOPHOLE[newLength, MDRLongNodeSize].lnsLo;
pfn.SizeHi ← LOOPHOLE[newLength, MDRLongNodeSize].lnsHi;
IF checking THEN CheckFreeChain[pfn];
pfn ← pfn.pfnPrev;
};
ENDCASE;
};
ENDLOOP;
pzn.pfn ← pzn.fnd.pfnNext;
};
CheckFreeChain: PROC [head: PFreeNode] = {
p: PFreeNode ← head;
IF p.pfnPrev.pfnNext # p THEN ERROR;
DO
next: PFreeNode ← p.pfnNext;
IF next.pfnPrev # p THEN ERROR;
p ← next;
IF p = head THEN RETURN;
ENDLOOP;
};
EnumerateAllObjects: PROC[visit: PROC[REF]] = {
-- this proc visits all non-free objects in a prefixed zone
FOR zi: CARDINAL IN [0..MapZiZn.length)
DO
zone: PZone ← LOOPHOLE[MapZiZn[zi]];
IF zone = NIL THEN LOOP;
IF zone.linkage.tag # collectible THEN LOOP;
ObjectsInPrefixedZone[zone, visit];
ENDLOOP;
};
ObjectsInPrefixedZone: ENTRY PROC [zn: PZone, visit: PROC[REF]] = {
-- this proc visits all non-free objects in a prefixed zone
ENABLE UNWIND => NULL;
WITH z: zn SELECT FROM
prefixed =>
-- look at all of the objects, both allocated and freed
FOR r: Runs.Run ← z.runs, r.rnNext UNTIL r = NIL DO
lim: PNode = LOOPHOLE[LONG[r.iTo] * RTQuanta.QuantumSize]; -- iTo not included
ptr: PNode ← LOOPHOLE[LONG[r.iFrom] * RTQuanta.QuantumSize];
WHILE ptr # lim DO -- look at each object in the run
size: LONG CARDINAL = NodeLength[ptr];
ref: REF ← LOOPHOLE[ptr + sizeNd];
IF RTFlags.checking AND size = 0 THEN ERROR;
IF ptr.state # free THEN visit[ref];
IF RTFlags.checking AND size # NodeLength[ptr]
THEN ERROR; -- oops, something changed!
ptr ← ptr + size;
ENDLOOP
ENDLOOP;
ENDCASE => ERROR;
};
-- return all quanta lying entirely within a free block
TrimPrefixedZone: ENTRY PROC[zn: PZone] =
{ ENABLE UNWIND => NULL;
pzn: PPrefixedZone = LOOPHOLE[zn];
pfn: PFreeNode ← @pzn.fnd;
UNTIL (pfn ← pfn.pfnNext) = @pzn.fnd DO -- foreach free block
IF NodeLength[pfn] >= QuantumSize THEN
{ -- quick rejection if there can't be at least one wholly contained quantum
qFirst: QuantumIndex ← MapPtrQ[pfn+(QuantumSize-1)];
qNext: QuantumIndex ← MapPtrQ[pfn+NodeLength[pfn]];
IF qNext > qFirst THEN -- if there is at least one wholly contained quantum
{ n: CARDINAL =
QuantumSizeMOD[LOOPHOLE[pfn, LONG CARDINAL]];
-- nonsense because compiler can't know this MOD yields a short result
prefixLn: CARDINAL ← (IF n = 0 THEN 0 ELSE QuantumSize-n);
pfnEnd: PFreeNode = LOOPHOLE[pfn+NodeLength[pfn]]; -- not a free node
suffixLn: CARDINAL ←
QuantumSizeMOD[LOOPHOLE[pfnEnd, LONG CARDINAL]];
-- nonsense ditto
tpfn: PFreeNode;
-- Retain residual blocks before and after entire quanta
IF (prefixLn > 0) AND (prefixLn < MinBlockSize)
THEN {IF (qFirst ← qFirst + 1) = qNext THEN LOOP;
prefixLn ← prefixLn + QuantumSize};
IF (suffixLn > 0) AND (suffixLn < MinBlockSize)
THEN {IF (qNext ← qNext - 1) = qFirst THEN LOOP;
suffixLn ← suffixLn + QuantumSize};
-- forget it if fragments would be created
tpfn ← DelinkFreeNode[pfn];
IF prefixLn # 0 THEN AddBlock[pfn, prefixLn, @pzn.fnd];
IF suffixLn # 0 THEN AddBlock[pfnEnd-suffixLn, suffixLn, @pzn.fnd];
-- Finally, release the quanta
ReturnQuanta[zn, qFirst, qNext-qFirst];
pfn ← tpfn; -- prepare for next iteration (new blocks may or may not be processed)
}; -- if there is at least one wholly contained quantum
};
ENDLOOP;
pzn.pfn ← pzn.fnd.pfnNext;
};
DelinkFreeNode: PROC[pfn: PFreeNode] RETURNS[pfnPrev: PFreeNode] =
INLINE BEGIN
pfnNext: PFreeNode = pfn.pfnNext;
pfnNext.pfnPrev ← pfnPrev ← pfn.pfnPrev;
pfnPrev.pfnNext ← pfnNext;
END;
END.