1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543 |
- MODULE Heaps; (** AUTHOR "pjm/Luc Bläser/U. Glavitsch (ug)"; PURPOSE "Heap management and garbage collector"; *)
- (*
- This module contains lots of low-level memory manipulations, which are best
- read together with the memory management data structure documentation.
- Garbage collector using a marking stack with overflow handling,
- References:
- Jones, Lins, Garbage Collection, Section 4.2, Algorithm 4.1
- Knuth, The Art of Computer Programming, Volume 1, Section 2.3.5, Algorithm C
- *)
- IMPORT SYSTEM, Trace, Machine;
- CONST
- DebugValue = LONGINT(0DEADDEADH); (* set non-0 to clear free storage to this value *)
- Stats* = TRUE; (* maintain statistical counters *)
- AddressSize = SIZEOF(ADDRESS);
- MaxTries = 16; (* max number of times to try and allocate memory, before trapping *)
- Unmarked = -1; (* mark value of free blocks *)
- BlockSize* = 32; (* power of two, <= 32 for RegisterCandidates *)
- ArrayAlignment = 8; (* first array element of ArrayBlock and first data element of SystemBlock must be aligned to 0 MOD ArrayAlignment *)
- BlockHeaderSize* = 2 * AddressSize;
- HeapBlockOffset* = - 2 * AddressSize;
- TypeDescOffset* = - AddressSize;
- MaxCandidates = 1024;
- ProtTypeBit* = 31; (** flags in TypeDesc, RoundUp(log2(MaxTags)) low bits reserved for extLevel *)
-
- FlagsOfs = AddressSize * 3; (* flags offset in TypeDesc *)
- ModOfs* = AddressSize * 4; (* moduleAdr offset in TypeDesc *)
- TypeNameOfs = AddressSize * 5; (* type name offset in TypeDesc *)
- ModNameOfs = AddressSize * 2; (* module name offset in ModuleDesc *)
- MinPtrOfs = -40000000H; (* sentinel offset for ptrOfs *)
- MethodEndMarker* = MinPtrOfs; (* marks the end of the method addresses, used in Info.ModuleDetails *)
- NilVal* = 0;
- NumPriorities* = 6;
- HeuristicStackInspectionGC* = 0;
- MetaDataForStackGC* = 1;
- TYPE
- RootObject* = OBJECT (* ref. Linker0 *)
- VAR nextRoot: RootObject; (* for linking root objects during GC *)
- PROCEDURE FindRoots*; (** abstract *)
- BEGIN HALT(301) END FindRoots;
- END RootObject;
- ProcessLink* = OBJECT (RootObject)
- VAR next*, prev*: ProcessLink
- END ProcessLink;
- ProcessQueue* = RECORD
- head*, tail*: ProcessLink
- END;
- Finalizer* = PROCEDURE {DELEGATE} (obj: ANY);
- FinalizerNode* = POINTER TO RECORD
- objWeak* {UNTRACED}: ANY; (* weak reference to checked object *)
- nextFin: FinalizerNode; (* in finalization list *)
- objStrong*: ANY; (* strong reference to object to be finalized *)
- finalizer* {UNTRACED} : Finalizer;(* finalizer, if any. Untraced for the case that a finalizer points to objWeak *)
- finalizerStrong: Finalizer; (* strong reference to the object that is referenced by the finalizer, if any *)
- END;
- HeapBlock* = POINTER TO HeapBlockDesc; (* base object of all heap blocks *)
- HeapBlockU = POINTER {UNSAFE} TO HeapBlockDesc; (* base object of all heap blocks *)
- HeapBlockDesc* = RECORD
- heapBlock {FICTIVE =HeapBlockOffset}: ADDRESS;
- typeDesc {FICTIVE =TypeDescOffset}: POINTER {UNSAFE} TO StaticTypeDesc;
- mark: LONGINT;
- dataAdr-: ADDRESS;
- size-: SIZE;
- nextMark {UNTRACED}: HeapBlock;
- END;
- FreeBlock* = POINTER TO FreeBlockDesc;
- FreeBlockU = POINTER {UNSAFE} TO FreeBlockDesc;
- FreeBlockDesc* = RECORD (HeapBlockDesc)
- next: FreeBlock;
- END;
- SystemBlock* = POINTER TO SystemBlockDesc;
- SystemBlockDesc = RECORD (HeapBlockDesc)
- END;
- RecordBlock* = POINTER TO RecordBlockDesc;
- RecordBlockU = POINTER {UNSAFE} TO RecordBlockDesc;
- RecordBlockDesc = RECORD (HeapBlockDesc)
- END;
- ProtRecBlock* = POINTER TO ProtRecBlockDesc;
- ProtRecBlockU = POINTER {UNSAFE} TO ProtRecBlockDesc;
- ProtRecBlockDesc* = RECORD (RecordBlockDesc)
- count*: LONGINT;
- locked*: BOOLEAN;
- awaitingLock*, awaitingCond*: ProcessQueue;
- lockedBy*: ANY;
- waitingPriorities*: ARRAY NumPriorities OF LONGINT;
- lock*: ANY; (* generic implementation slot -- used by LinuxAos *)
- END;
- ArrayBlock* = POINTER TO ArrayBlockDesc;
- ArrayBlockU = POINTER {UNSAFE} TO ArrayBlockDesc;
- ArrayBlockDesc = RECORD (HeapBlockDesc)
- END;
- TypeInfo*= POINTER{UNSAFE} TO TypeInfoDesc;
- TypeInfoDesc = RECORD
- descSize: LONGINT;
- sentinel: LONGINT; (* = MPO-4 *)
- tag: ADDRESS; (* pointer to static type descriptor, only used by linker and loader *)
- flags: SET;
- mod: ADDRESS; (* module *)
- name*: ARRAY 32 OF CHAR;
- END;
- StaticTypeBlock*= POINTER TO StaticTypeDesc;
- StaticTypeBlockU= POINTER {UNSAFE} TO StaticTypeDesc;
- StaticTypeDesc = RECORD
- info {FICTIVE =TypeDescOffset}: TypeInfo;
- recSize: SIZE;
- pointerOffsets* {UNTRACED}: PointerOffsets;
- END;
- PointerOffsets = POINTER TO ARRAY OF SIZE;
-
- Block*= POINTER {UNSAFE} TO RECORD
- heapBlock {FICTIVE =HeapBlockOffset}: HeapBlock;
- typeBlock {FICTIVE =TypeDescOffset}: StaticTypeBlock;
- END;
- DataBlockU = POINTER {UNSAFE} TO DataBlockDesc;
- DataBlockDesc*= RECORD
- heapBlock {FICTIVE =HeapBlockOffset}: POINTER {UNSAFE} TO HeapBlockDesc;
- typeDesc {FICTIVE =TypeDescOffset}: POINTER {UNSAFE} TO StaticTypeDesc;
- END;
-
- ArrayDataBlockU = POINTER {UNSAFE} TO ArrayDataBlockDesc;
- ArrayDataBlockDesc*= RECORD (DataBlockDesc)
- numElems: SIZE;
- current: ADDRESS; (* unused *)
- first: ADDRESS;
- END;
-
- (*StackBlock = POINTER{UNSAFE} TO StackBlockDesc;
- StackBlockDesc= RECORD
- link: StackBlock;
- pc: ADDRESS;
- END;
- *)
-
- (* a single pointer -- required as base type TD for array of pointer
- Don't rename this. Compiler refers to this TD by name
- *)
- AnyPtr = RECORD a: ANY END;
- TYPE
- GCStatus* = OBJECT
- (* the following procedures are overridden in Objects.GCStatusExt. The reason is that shared objects can only be implemented in modules Objects or higher *)
- PROCEDURE SetgcOngoing*(value: BOOLEAN);
- BEGIN
- HALT(2000);
- END SetgcOngoing;
- PROCEDURE GetgcOngoing*(): BOOLEAN;
- BEGIN
- HALT(2001); RETURN FALSE
- END GetgcOngoing;
- PROCEDURE WaitForGCEnd*;
- BEGIN
- HALT(2002)
- END WaitForGCEnd;
- END GCStatus;
- CONST
- MaxFreeLists = 20;
- FreeListBarrier = 7;
- TYPE
- FreeList= RECORD minSize: SIZE; first {UNTRACED}, last{UNTRACED}: FreeBlock END;
- FreeLists = ARRAY MaxFreeLists+1 OF FreeList;
-
- MarkList = RECORD first{UNTRACED}, last{UNTRACED}: HeapBlock END;
- VAR
- markList: MarkList;
- freeLists: FreeLists;
- GC*: PROCEDURE; (** activate the garbage collector *)
- initBlock {UNTRACED}: ANY; (* anchor for init calls *)
- currentMarkValue: LONGINT; (* all objects that have this value in their mark field are still used - initial value filled in by linker *)
- sweepMarkValue: LONGINT; (* most recent mark value *)
- sweepBlockAdr: ADDRESS; (* where to resume sweeping *)
- sweepMemBlock {UNTRACED}: Machine.MemoryBlock; (* where to resume sweeping *)
- candidates: ARRAY MaxCandidates OF ADDRESS; (* procedure stack pointer candidates *)
- numCandidates: LONGINT;
- rootList {UNTRACED}: RootObject; (* list of root objects during GC - tracing does not harm but is unnecessary *)
- realtimeList {UNTRACED}: HeapBlock; (* list of realtime objects - tracing does not harm but is unnecessary *)
- newSum: SIZE;
- checkRoot: FinalizerNode; (* list of checked objects (contains weak references to the checked objects) *)
- finalizeRoot: FinalizerNode; (* objects scheduled for finalization (contains references to scheduled objects) *)
- freeBlockTag, systemBlockTag, recordBlockTag, protRecBlockTag, arrayBlockTag: ADDRESS; (* same values of type ADDRESS *)
- (** Statistics. Will only be maintained if Stats = TRUE *)
- (** Memory allocation statistics *)
- Nnew- : LONGINT; (** Number of times NewBlock has been called since system startup *)
- NnewBytes- : HUGEINT; (** Number of bytes allocated by NewBlock since system startup *)
- (** Garbage collection statistics *)
- Ngc- : LONGINT; (** Number of GC cycles since system startup *)
- (** Statistics considering the last GC cyle *)
- Nmark-, Nmarked-, NfinalizeAlive-, NfinalizeDead-: LONGINT;
- NgcCyclesMark-, NgcCyclesLastRun-, NgcCyclesMax-, NgcCyclesAllRuns- : HUGEINT;
- NgcSweepTime-, NgcSweepMax-: HUGEINT;
- gcStatus*: GCStatus;
- GCType*: LONGINT;
- freeBlockFound-, freeBlockNotFound-: LONGINT;
- EnableFreeLists, EnableReturnBlocks, trace-: BOOLEAN;
- allocationLogger-: PROCEDURE(p: ANY);
- (* for low level debugging of allocation -- beware: errors or traps in allocation logger can produce catastrophy - loggers may not allocate memory *)
- PROCEDURE SetAllocationLogger*(a: PROCEDURE (p:ANY));
- BEGIN
- allocationLogger := a
- END SetAllocationLogger;
- (* check validity of p *)
- PROCEDURE CheckPointer(p: ADDRESS): BOOLEAN;
- VAR ret: BOOLEAN; heapBlockAdr, tdAdr: ADDRESS;
- BEGIN
- ret := FALSE;
- IF Machine.ValidHeapAddress(p+HeapBlockOffset)THEN
- SYSTEM.GET(p + HeapBlockOffset, heapBlockAdr);
- IF Machine.ValidHeapAddress(heapBlockAdr + TypeDescOffset) THEN
- SYSTEM.GET(heapBlockAdr + TypeDescOffset, tdAdr);
- IF (tdAdr = systemBlockTag) OR (tdAdr = recordBlockTag) OR (tdAdr = protRecBlockTag) OR (tdAdr = arrayBlockTag) THEN
- ret := TRUE
- END
- END
- END;
- RETURN ret
- END CheckPointer;
- PROCEDURE AppendToMarkList(heapBlock: HeapBlock);
- BEGIN
- IF markList.first = NIL THEN
- markList.first := heapBlock
- ELSE
- markList.last.nextMark := heapBlock
- END;
- markList.last := heapBlock;
- heapBlock.nextMark := NIL; (* sanity of the list *)
- END AppendToMarkList;
- PROCEDURE ExtractFromMarkList(): HeapBlock;
- VAR heapBlock: HeapBlock;
- BEGIN
- heapBlock := markList.first;
- IF heapBlock # NIL THEN
- markList.first := heapBlock.nextMark;
- (* by the construction of AppendToMarkList, it is not necessary to update the last pointer *)
- END;
- RETURN heapBlock;
- END ExtractFromMarkList;
- PROCEDURE Inspect(block {UNTRACED}: ANY);
- VAR
- heapBlock {UNTRACED}: HeapBlock;
- rootObj{UNTRACED}: RootObject;
- blockMeta : Block;
- BEGIN
- (* ug: check for validity of block is necessary since users may assign values to pointer variables that are not real heap blocks, e.g. by using SYSTEM.VAL or ADDRESSOF *)
- IF (block = NIL) OR ~CheckPointer(block) THEN RETURN END;
- blockMeta := block;
- heapBlock := blockMeta.heapBlock;
- IF (heapBlock = NIL) OR (heapBlock.mark >= currentMarkValue) THEN RETURN END;
- heapBlock.mark := currentMarkValue;
- IF Stats THEN INC(Nmarked) END;
- IF (heapBlock IS RecordBlock) OR (heapBlock IS ProtRecBlock) OR (heapBlock IS ArrayBlock) THEN
- IF block IS RootObject THEN
- rootObj := block(RootObject);
- rootObj.nextRoot := rootList; rootList := rootObj; (* link root list *)
- END;
- IF (LEN(blockMeta.typeBlock.pointerOffsets) > 0) OR (heapBlock IS ProtRecBlock) THEN (* not atomic or heapBlock is ProtRecBlock containing awaiting queues *)
- AppendToMarkList(heapBlock);
- END
- END
- END Inspect;
- (** Mark - Mark an object and its decendents. Used by findRoots. *)
- PROCEDURE Mark*(p {UNTRACED}: ANY);
- VAR orgBlock: ADDRESS; staticTypeBlock {UNTRACED}: StaticTypeBlock;
- orgHeapBlock {UNTRACED}: HeapBlock;
- currentArrayElemAdr, lastArrayElemAdr: ADDRESS; i: LONGINT;
- protected: ProtRecBlock;
- b : POINTER {UNSAFE} TO RECORD p: ANY END;
- meta: POINTER {UNSAFE} TO RECORD staticTypeBlock {FICTIVE=TypeDescOffset}: StaticTypeBlock; last, current, first: ADDRESS END;
- BEGIN
- IF Stats THEN INC(Nmark) END;
- Inspect(p);
- orgHeapBlock := ExtractFromMarkList();
- WHILE orgHeapBlock # NIL DO
- orgBlock := orgHeapBlock.dataAdr;
- meta := orgBlock;
- staticTypeBlock := meta.staticTypeBlock;
- IF ~(orgHeapBlock IS ArrayBlock) THEN
- FOR i := 0 TO LEN(staticTypeBlock.pointerOffsets) - 1 DO
- b := orgBlock + staticTypeBlock.pointerOffsets[i];
- Inspect(b.p)
- END
- ELSE
- currentArrayElemAdr := meta.first;
-
- lastArrayElemAdr := meta.first + meta.last * staticTypeBlock.recSize;
- IF currentArrayElemAdr > lastArrayElemAdr THEN HALT(100) END;
- WHILE currentArrayElemAdr < lastArrayElemAdr DO
- FOR i := 0 TO LEN(staticTypeBlock.pointerOffsets) - 1 DO
- b := currentArrayElemAdr + staticTypeBlock.pointerOffsets[i];
- Inspect(b.p)
- END;
- INC(currentArrayElemAdr, staticTypeBlock.recSize);
- END
- END;
- IF orgHeapBlock IS ProtRecBlock THEN
- protected := orgHeapBlock(ProtRecBlock);
- Inspect(protected.awaitingLock.head);
- Inspect(protected.awaitingCond.head);
- Inspect(protected.lockedBy);
- Inspect(protected.lock);
- END;
- orgHeapBlock := ExtractFromMarkList();
- END;
- END Mark;
- PROCEDURE MarkRealtimeObjects;
- VAR heapBlock {UNTRACED}: HeapBlock;
- BEGIN
- (*! disable realtime block handling for the time being
- first, we have to check that objects cannot move between mark list and realtime list
-
- heapBlock := realtimeList;
- WHILE heapBlock # NIL DO
- Mark(SYSTEM.VAL(ANY, heapBlock.dataAdr));
- heapBlock := heapBlock.nextRealtime;
- END;
- *)
- END MarkRealtimeObjects;
- (** WriteType - Write a type name (for tracing only). *)
- PROCEDURE WriteType*(t: ADDRESS); (* t is static type descriptor *)
- VAR m: ADDRESS; i: LONGINT; ch: CHAR;
- typeDesc: StaticTypeBlockU;
- BEGIN
- typeDesc := t;
- m := typeDesc.info.mod;
- IF m # NilVal THEN (* could be a type without module, e.g. a Java class *)
- i := 0; SYSTEM.GET (m + ModNameOfs + i, ch);
- WHILE (ch >= "0") & (ch <= "z") & (i # 32) DO
- Trace.Char(ch);
- INC(i); SYSTEM.GET (m + ModNameOfs + i, ch)
- END
- ELSE
- Trace.String("NIL")
- END;
- Trace.Char(".");
- IF typeDesc.info.name = "" THEN
- Trace.String("-")
- ELSE
- Trace.String(typeDesc.info.name);
- END;
- END WriteType;
- (** free list handling **)
- PROCEDURE ClearFreeLists;
- VAR i: LONGINT;
- BEGIN
- FOR i := 0 TO MaxFreeLists DO
- freeLists[i].first := NIL;
- freeLists[i].last := NIL
- END;
- END ClearFreeLists;
- (* insert element in fifo, first = freeList.first; last = freeList.last *)
- PROCEDURE AppendFree(VAR freeList: FreeList; block: FreeBlock);
- BEGIN
- ASSERT(block.size >= freeList.minSize);
- IF freeList.first = NIL THEN
- freeList.first := block; freeList.last := block
- ELSE
- freeList.last.next := block;
- freeList.last := block;
- END;
- block.next := NIL
- END AppendFree;
- (* get last element from fifo *)
- PROCEDURE GetFree(VAR freeList: FreeList): FreeBlock;
- VAR block: FreeBlock;
- BEGIN
- IF freeList.first = NIL THEN block := NIL;
- ELSIF freeList.first = freeList.last THEN block := freeList.first; freeList.first := NIL; freeList.last := NIL
- ELSE block := freeList.first; freeList.first := block.next; block.next := NIL
- END;
- RETURN block
- END GetFree;
- (** insert sorted into queue, no handling of last queue element *)
- PROCEDURE InsertSorted(VAR freeList: FreeList; block: FreeBlock);
- VAR x: FreeBlock;
- BEGIN
- (* keep them ordered to avoid unnecessary splits *)
- x := freeList.first;
- WHILE x # NIL DO
- ASSERT(x # block);
- x := x.next;
- END;
- x := freeList.first;
- IF (x = NIL) OR (block.size <= x.size) THEN
- block.next := x;
- freeList.first := block;
- ELSE
- WHILE (x.next # NIL) & (block.size > x.next.size) DO x := x.next END;
- block.next := x.next;
- x.next := block;
- END;
- END InsertSorted;
- PROCEDURE AppendFreeBlock(block: FreeBlock);
- VAR i: LONGINT;
- BEGIN
- i := MaxFreeLists;
- WHILE (i > 0) & (freeLists[i].minSize > block.size) DO DEC( i ) END;
- IF i < FreeListBarrier THEN
- AppendFree(freeLists[i], block);
- ELSE
- AppendFree(freeLists[i], block);
- (*
- keeping lists sorted has some positive impact on heap utilization
- but it slows down heap allocation speed:
-
- InsertSorted(freeLists[i], block);
- *)
- END;
- END AppendFreeBlock;
- PROCEDURE FindFreeBlock( size: SIZE ): FreeBlock;
- VAR prev, block: FreeBlock; i: LONGINT;
- BEGIN
- i := MaxFreeLists;
- WHILE (i > 0) & (freeLists[i].minSize > size) DO DEC( i ) END;
- REPEAT
- IF i < FreeListBarrier THEN
- block := GetFree(freeLists[i]);
- ELSE
- block := freeLists[i].first;
- prev := NIL;
- WHILE (block # NIL) & (block.size < size) DO
- prev := block;
- block := block.next;
- END;
- IF block # NIL THEN (* blockize >= size *)
- IF prev = NIL THEN
- freeLists[i].first := block.next;
- ELSE prev.next := block.next
- END;
- IF block = freeLists[i].last THEN
- freeLists[i].last := prev
- END;
- block.next := NIL;
- END;
- (*
- prev := freeLists[i].first;
- WHILE prev # NIL DO
- ASSERT(prev # block);
- prev := prev.next;
- END;
- *)
- END;
- INC( i )
- UNTIL (block # NIL) OR (i > MaxFreeLists);
- RETURN block
- END FindFreeBlock;
- PROCEDURE GetFreeBlockAndSplit(size: SIZE): FreeBlock;
- VAR p,remainder: FreeBlockU; adr: ADDRESS;
- BEGIN
- p := FindFreeBlock(size);
- IF (p # NIL) & (ADDRESS(p.size) > ADDRESS (size)) THEN (* block too big - divide block into two parts: block with required size and remaining free block *)
- ASSERT(ADDRESS(p.size - size) >= BlockHeaderSize + SIZEOF(FreeBlockDesc));
- adr := p;
- remainder := adr + size;
- InitFreeBlock(remainder, Unmarked, NilVal, p.size - size);
- AppendFreeBlock(remainder);
- p.size := size;
- END;
- IF p # NIL THEN INC(freeBlockFound) ELSE INC(freeBlockNotFound) END;
- RETURN p
- END GetFreeBlockAndSplit;
- PROCEDURE GetFreeBlock(size: SIZE; VAR p: FreeBlock);
- BEGIN
- IF EnableFreeLists THEN
- IF sweepMarkValue < currentMarkValue THEN
- (*Trace.String("clear free lists and lazy sweep"); Trace.Ln;*)
- ClearFreeLists;
- LazySweep(MAX(LONGINT), p)
- END;
- p := GetFreeBlockAndSplit(size)
- ELSE
- LazySweep(size, p)
- END;
- IF size # MAX(LONGINT) THEN
- INC(throughput, size);
- END;
- END GetFreeBlock;
- (* Sweep phase *)
- PROCEDURE LazySweep(size: SIZE; VAR p: FreeBlock);
- VAR
- lastFreeBlockAdr: ADDRESS; found : BOOLEAN;
- block {UNTRACED}: HeapBlock ; freeBlock{UNTRACED}, lastFreeBlock{UNTRACED}: FreeBlock;
- blockMark: LONGINT; blockSize: SIZE;
- time1, time2: HUGEINT;
- CONST FreeBlockHeaderSize = SIZEOF(FreeBlockDesc) + BlockHeaderSize;
- BEGIN
- time1 := Machine.GetTimer();
- ASSERT(~EnableFreeLists OR (size = MAX(LONGINT)));
- found := FALSE;
- lastFreeBlockAdr := NilVal;
- lastFreeBlock := NIL;
- IF (sweepMemBlock = NIL) OR (sweepMarkValue < currentMarkValue) THEN (* restart lazy sweep including clearance of lists *)
- (* note that the order of the blocks does not necessarily represent the historical order of insertion
- as they are potentially provided by the underlying host system in with non-increasing address ranges
- blocks are sorted by Machine.Mod in an increased address range order
- *)
- sweepMemBlock := Machine.memBlockHead;
- sweepBlockAdr := Machine.memBlockHead.beginBlockAdr;
- sweepMarkValue := currentMarkValue;
- END;
- WHILE ~found & (sweepMemBlock # NIL) DO
- WHILE ~found & (sweepBlockAdr < sweepMemBlock.endBlockAdr) DO
- block := SYSTEM.VAL(HeapBlock, sweepBlockAdr + BlockHeaderSize); (* get heap block *)
- blockMark := block.mark; (* cache these values since they may be overwritten during concatenation *)
- blockSize := block.size;
- IF (blockMark < sweepMarkValue) THEN
- IF (block IS SystemBlock) OR (block IS RecordBlock) OR (block IS ProtRecBlock) OR (block IS ArrayBlock) THEN
- freeBlock := SYSTEM.VAL(FreeBlock, block);
- InitFreeBlock(freeBlock, Unmarked, NilVal, blockSize); (* convert this block into a free heap block and clear its data *)
- Machine.Fill32(sweepBlockAdr + FreeBlockHeaderSize, blockSize - FreeBlockHeaderSize, DebugValue);
- ELSE
- ASSERT(block IS FreeBlock);
- freeBlock := block(FreeBlock); (* free block has data cleared by definition *)
- END;
- IF lastFreeBlockAdr = NilVal THEN
- lastFreeBlockAdr := sweepBlockAdr;
- lastFreeBlock := freeBlock;
- ELSIF lastFreeBlockAdr + lastFreeBlock.size = sweepBlockAdr THEN
- (* there are two contiguous free blocks - merge them *)
- INC(lastFreeBlock.size,blockSize);
- (* clear header fields of concatenated block *)
- Machine.Fill32(sweepBlockAdr, FreeBlockHeaderSize, DebugValue);
- END
- ELSE
- ASSERT(~(block IS FreeBlock));
- END;
- IF (blockMark >= sweepMarkValue) OR (sweepBlockAdr + blockSize = sweepMemBlock.endBlockAdr) THEN (* no further merging is possible *)
- ASSERT(sweepBlockAdr + blockSize <= sweepMemBlock.endBlockAdr);
- IF lastFreeBlockAdr # NilVal THEN
- IF ADDRESS(lastFreeBlock.size) >= ADDRESS (size) THEN (* block found - may be too big *)
- p := lastFreeBlock;
- IF ADDRESS(p.size) > ADDRESS (size) THEN (* block too big - divide block into two parts: block with required size and remaining free block *)
- ASSERT(ADDRESS(p.size - size) >= FreeBlockHeaderSize);
- freeBlock := SYSTEM.VAL(FreeBlock, SYSTEM.VAL(ADDRESS, p) + size);
- InitFreeBlock(freeBlock, Unmarked, NilVal, p.size - size);
- p.size := size;
- END;
- sweepBlockAdr := lastFreeBlockAdr + size; (* make sure next lazy sweep continues after block p *)
- found := TRUE;
- ELSIF EnableFreeLists THEN AppendFreeBlock(lastFreeBlock);
- END;
- lastFreeBlockAdr := NilVal;
- lastFreeBlock := NIL;
- END
- END;
- IF ~found THEN sweepBlockAdr := sweepBlockAdr + blockSize END
- END;
- IF ~found THEN
- sweepMemBlock := sweepMemBlock.next;
- IF sweepMemBlock # NIL THEN
- sweepBlockAdr := sweepMemBlock.beginBlockAdr
- ELSE
- sweepBlockAdr := NilVal
- END
- END
- END;
- time2 := Machine.GetTimer()-time1;
- INC(NgcSweepTime, time2);
- IF time2 > NgcSweepMax THEN NgcSweepMax := time2 END;
- END LazySweep;
- (* -- useful for debugging --
- PROCEDURE CheckHeap;
- VAR memBlock {UNTRACED}: Machine.MemoryBlock; p, refBlock, currentArrayElemAdr, lastArrayElemAdr: ADDRESS;
- heapBlock {UNTRACED}: HeapBlock; staticTypeBlock {UNTRACED}: StaticTypeBlock; i: LONGINT;
- PROCEDURE CheckBlock(block: ADDRESS): BOOLEAN;
- VAR heapBlockAdr: ADDRESS;
- BEGIN
- IF block = NilVal THEN
- RETURN TRUE
- ELSE
- IF (block >= Machine.memBlockHead.beginBlockAdr) & (block < Machine.memBlockTail.endBlockAdr) THEN
- SYSTEM.GET(block + HeapBlockOffset, heapBlockAdr);
- IF (heapBlockAdr >= Machine.memBlockHead.beginBlockAdr) & (heapBlockAdr < Machine.memBlockTail.endBlockAdr) THEN
- RETURN TRUE
- ELSE
- RETURN FALSE
- END
- ELSE
- RETURN FALSE
- END
- END
- END CheckBlock;
- BEGIN
- memBlock := Machine.memBlockHead;
- WHILE memBlock # NIL DO
- p := memBlock.beginBlockAdr;
- WHILE p < memBlock.endBlockAdr DO
- heapBlock := SYSTEM.VAL(HeapBlock, p + BlockHeaderSize);
- IF heapBlock IS SystemBlock THEN
- ELSIF heapBlock IS RecordBlock THEN
- IF heapBlock.dataAdr # NilVal THEN
- SYSTEM.GET(heapBlock.dataAdr + TypeDescOffset, staticTypeBlock); ASSERT(staticTypeBlock # NIL);
- FOR i := 0 TO LEN(staticTypeBlock.pointerOffsets) - 1 DO
- SYSTEM.GET(heapBlock.dataAdr + staticTypeBlock.pointerOffsets[i], refBlock);
- IF ~CheckBlock(refBlock) THEN
- Trace.String("SEVERE ERROR: RecordBlock = "); Trace.Hex(heapBlock.dataAdr, 8);
- Trace.String(" invalid reference at pointer offset = "); Trace.Hex(staticTypeBlock.pointerOffsets[i], 0); Trace.Ln
- END
- END;
- IF heapBlock IS ProtRecBlock THEN
- IF CheckBlock(heapBlock(ProtRecBlock).awaitingLock.head) &
- CheckBlock(heapBlock(ProtRecBlock).awaitingLock.tail) &
- CheckBlock(heapBlock(ProtRecBlock).awaitingCond.head) &
- CheckBlock(heapBlock(ProtRecBlock).awaitingCond.tail) &
- CheckBlock(heapBlock(ProtRecBlock).lockedBy) THEN
- ELSE
- Trace.String("SEVERE ERROR in awaiting queues of block = "); Trace.Hex(heapBlock.dataAdr, 8); Trace.Ln
- END
- END
- ELSE
- Trace.StringLn("SEVERE ERROR: heapBlock.dataAdr = NilVal for RecordBlock or ProtRecBlock")
- END;
- ELSIF heapBlock IS ArrayBlock THEN
- IF heapBlock.dataAdr # NilVal THEN
- SYSTEM.GET(heapBlock.dataAdr + TypeDescOffset, staticTypeBlock); ASSERT(staticTypeBlock # NIL);
- SYSTEM.GET(heapBlock.dataAdr + 2 * AddressSize, currentArrayElemAdr);
- SYSTEM.GET(heapBlock.dataAdr, lastArrayElemAdr);
- WHILE currentArrayElemAdr <= lastArrayElemAdr DO
- FOR i := 0 TO LEN(staticTypeBlock.pointerOffsets) - 1 DO
- SYSTEM.GET(currentArrayElemAdr + staticTypeBlock.pointerOffsets[i], refBlock);
- IF ~CheckBlock(refBlock) THEN
- Trace.String("SEVERE ERROR in ArrayBlock = "); Trace.Hex(currentArrayElemAdr, 8);
- Trace.String(" invalid reference at pointer offset = "); Trace.Hex(staticTypeBlock.pointerOffsets[i], 0); Trace.Ln
- END
- END;
- INC(currentArrayElemAdr, staticTypeBlock.recSize)
- END
- ELSE
- Trace.StringLn("SEVERE ERROR: heapBlock.dataAdr = NilVal for ArrayBlock")
- END
- ELSIF heapBlock IS FreeBlock THEN
- ELSE
- Trace.StringLn("Invalid heap block type")
- END;
- p := p + heapBlock.size;
- END;
- memBlock := memBlock.next
- END
- END CheckHeap;
- *)
- (* CheckCandidates - Check which candidates could be pointers, and mark them. (exported for debugging only) *)
- PROCEDURE CheckCandidates*;
- CONST MinDataOffset = BlockHeaderSize + SIZEOF(HeapBlockDesc) + BlockHeaderSize; (* minimal offset of data address with respect to block start address *)
- VAR i, j, h: LONGINT; p, blockStart: ADDRESS; memBlock {UNTRACED}: Machine.MemoryBlock;
- heapBlock {UNTRACED}: HeapBlock;
- BEGIN
- (* {numCandidates > 0} *)
- (* first sort them in increasing order using shellsort *)
- h := 1; REPEAT h := h*3 + 1 UNTIL h > numCandidates;
- REPEAT
- h := h DIV 3; i := h;
- WHILE i < numCandidates DO
- p := candidates[i]; j := i;
- WHILE (j >= h) & (candidates[j-h] > p) DO
- candidates[j] := candidates[j-h]; j := j-h;
- END;
- candidates[j] := p; INC(i)
- END
- UNTIL h = 1;
- (* sweep phase *)
- i := 0;
- p := candidates[i];
- memBlock := Machine.memBlockHead;
- WHILE memBlock # NIL DO
- blockStart := memBlock.beginBlockAdr;
- WHILE (i < numCandidates) & (blockStart < memBlock.endBlockAdr) DO
- IF p < blockStart + MinDataOffset THEN (* candidate missed *)
- INC(i);
- IF i < numCandidates THEN
- p := candidates[i]
- END
- ELSE
- heapBlock := SYSTEM.VAL(HeapBlock, blockStart + BlockHeaderSize);
- IF (p = heapBlock.dataAdr) & ~(heapBlock IS FreeBlock) THEN (* heap block must not be a free block but any other heap block type *)
- Mark(SYSTEM.VAL(ANY, p))
- END;
- blockStart := blockStart + heapBlock.size;
- END
- END;
- memBlock := memBlock.next
- END;
- numCandidates := 0
- END CheckCandidates;
- (* Check validity of single pointer candidate and enter it into the list of candidates *)
- PROCEDURE Candidate*(p: ADDRESS);
- VAR memBlock, memBlockX {UNTRACED}: Machine.MemoryBlock;
- tdAdr, heapBlockAdr: ADDRESS;
- tdPtr: POINTER {UNSAFE} TO RECORD typeAdr: ADDRESS END;
- hbPtr: POINTER {UNSAFE} TO RECORD heapBlock: HeapBlock END;
- heapBlock {UNTRACED}: HeapBlock;
- BEGIN
- IF p MOD SIZEOF(ADDRESS) # 0 THEN RETURN END;
- IF (p >= Machine.memBlockHead.beginBlockAdr) & (p < Machine.memBlockTail.endBlockAdr) THEN
- memBlock := Machine.memBlockHead;
- WHILE memBlock # NIL DO
- IF (p + HeapBlockOffset >= memBlock.beginBlockAdr) & (p + HeapBlockOffset < memBlock.endBlockAdr) THEN
- hbPtr := p + HeapBlockOffset;
- heapBlock := hbPtr.heapBlock;
- heapBlockAdr := heapBlock ;
- IF heapBlockAdr MOD SIZEOF(ADDRESS) # 0 THEN RETURN END;
- tdAdr :=heapBlockAdr + TypeDescOffset;
- (* check if tdAdr is a valid pointer in the heap *)
- memBlockX := Machine.memBlockHead;
- WHILE memBlockX # NIL DO
- IF (tdAdr >= memBlockX.beginBlockAdr) & (tdAdr < memBlockX.endBlockAdr) THEN
- (* IF (heapBlock.mark >= currentMarkValue) THEN RETURN END;*)
- tdPtr := tdAdr;
- tdAdr := tdPtr.typeAdr;
- (* check whether tdAdr is a valid type descriptor address *)
- IF (tdAdr = systemBlockTag) OR (tdAdr = recordBlockTag) OR (tdAdr = protRecBlockTag) OR (tdAdr = arrayBlockTag) THEN
- candidates[numCandidates] := p;
- INC(numCandidates);
- IF numCandidates = LEN(candidates) THEN CheckCandidates END
- END;
- RETURN; (* found *)
- END;
- memBlockX := memBlockX.next
- END;
- RETURN; (* not found *)
- END;
- memBlock := memBlock.next
- END
- END
- END Candidate;
- (** RegisterCandidates - Register a block of pointer candidates *)
- PROCEDURE RegisterCandidates*(adr: ADDRESS; size: SIZE);
- VAR end, p: ADDRESS;
- BEGIN
- (* current processor must hold Heaps lock *)
- end := adr + size;
- WHILE adr # end DO
- SYSTEM.GET(adr, p);
- Candidate(p);
- INC(adr, AddressSize)
- END
- END RegisterCandidates;
- (* Check reachability of finalized objects. *)
- PROCEDURE CheckFinalizedObjects;
- VAR n, p, t: FinalizerNode; heapBlock {UNTRACED}: HeapBlock;
- PROCEDURE MarkDelegate(p: Finalizer);
- VAR pointer: ANY;
- BEGIN
- SYSTEM.GET(ADDRESSOF(p)+SIZEOF(ADDRESS),pointer);
- IF pointer # NIL THEN Mark(pointer) END;
- END MarkDelegate;
- BEGIN
- n := checkRoot;
- WHILE n # NIL DO (* move unmarked checked objects to finalize list *)
- SYSTEM.GET(SYSTEM.VAL(ADDRESS, n.objWeak) + HeapBlockOffset, heapBlock);
- IF heapBlock.mark < currentMarkValue THEN
- IF n = checkRoot THEN checkRoot := n.nextFin ELSE p.nextFin := n.nextFin END;
- n.objStrong := n.objWeak; (* anchor the object for finalization *)
- n.finalizerStrong := n.finalizer; (* anchor the finalizer for finalization *)
- t := n.nextFin; n.nextFin := finalizeRoot; finalizeRoot := n; n := t;
- IF Stats THEN DEC(NfinalizeAlive); INC(NfinalizeDead) END
- ELSE
- p := n; n := n.nextFin
- END
- END;
- (* now trace the weak references to keep finalized objects alive during this collection *)
- n := finalizeRoot;
- WHILE n # NIL DO
- MarkDelegate(n.finalizerStrong);
- Mark(n.objStrong); n := n.nextFin
- END;
- n := checkRoot;
- WHILE n # NIL DO (* list of objects that had been marked before entering CheckFinalizedObjects *)
- (* we still have to mark the weak finalizers, as they might have not been marked before *)
- MarkDelegate(n.finalizer); n := n.nextFin
- END;
- END CheckFinalizedObjects;
- (** Return the next scheduled finalizer or NIL if none available. Called by finalizer object in Kernel. *)
- PROCEDURE GetFinalizer* (): FinalizerNode;
- VAR n: FinalizerNode;
- BEGIN
- n := NIL;
- IF finalizeRoot # NIL THEN
- Machine.Acquire(Machine.Heaps);
- n := finalizeRoot; (* take one finalizer *)
- IF n # NIL THEN
- finalizeRoot := n.nextFin; n.nextFin := NIL;
- IF Stats THEN DEC(NfinalizeDead) END;
- END;
- Machine.Release(Machine.Heaps);
- END;
- RETURN n
- END GetFinalizer;
- (** Check finalizers registered in the specified module, which is about to be freed or shut down. Remove all finalizer procedures in this module from the finalizer lists so they won't be called any more. *)
- PROCEDURE CleanupModuleFinalizers*(codeAdr: ADDRESS; codeLen: SIZE; CONST name: ARRAY OF CHAR);
- VAR n, p, t: FinalizerNode; codeEnd: ADDRESS; N1, N2: LONGINT;
- BEGIN
- codeEnd := codeAdr + codeLen; N1 := 0; N2 := 0;
- Machine.Acquire(Machine.Heaps);
- n := checkRoot;
- WHILE n # NIL DO (* iterate over checked list *)
- t := n; n := n.nextFin;
- IF (codeAdr <= SYSTEM.VAL (ADDRESS, t.finalizer)) & (SYSTEM.VAL (ADDRESS, t.finalizer) <= codeEnd) THEN
- IF t = checkRoot THEN checkRoot := t.nextFin ELSE p.nextFin := t.nextFin END; (* remove from list *)
- IF Stats THEN DEC(NfinalizeAlive) END;
- INC(N1)
- ELSE
- p := t
- END
- END;
- (* also remove finalizers from list, so they won't be called *)
- n := finalizeRoot;
- WHILE n # NIL DO (* iterate over finalized list *)
- t := n; n := n.nextFin;
- IF (codeAdr <= SYSTEM.VAL (ADDRESS, t.finalizer)) & (SYSTEM.VAL (ADDRESS, t.finalizer) <= codeEnd) THEN
- IF t = finalizeRoot THEN finalizeRoot := t.nextFin ELSE p.nextFin := t.nextFin END; (* remove from list *)
- IF Stats THEN DEC(NfinalizeDead) END;
- INC(N2)
- ELSE
- p := t
- END
- END;
- Machine.Release(Machine.Heaps);
- IF (N1 # 0) OR (N2 # 0) THEN
- Machine.Acquire (Machine.TraceOutput);
- Trace.String(name); Trace.Char(" ");
- Trace.Int(N1, 1); Trace.String(" discarded finalizers, ");
- Trace.Int(N2, 1); Trace.StringLn (" pending finalizers");
- Machine.Release (Machine.TraceOutput);
- END
- END CleanupModuleFinalizers;
- (* Add a root object to the set of traversable objects. If in allocated heap then mark and traverse, if in Module Heap (Bootfile) then only traverse. *)
- PROCEDURE AddRootObject*(rootObject: RootObject);
- BEGIN
- IF rootObject = NIL THEN (* nothing *)
- ELSIF CheckPointer(rootObject) THEN
- (* object in heap, must be fully marked and traversed *)
- Mark(rootObject)
- ELSE
- (* object in bootfile, traverse as root object only *)
- rootObject.nextRoot := rootList; rootList := rootObject; (* link root list *)
- END;
- END AddRootObject;
- (* interruptible garbage collector for native A2 *)
- PROCEDURE CollectGarbage*(root : RootObject);
- VAR
- obj: RootObject;
- time1, time2: HUGEINT;
- f: FreeBlock;
- BEGIN
- (* do never use any low level locks as the garbage collector process has a very high priority and may thus be blocked by lower level processes -> potential deadlock *)
- (*!
- Do not use windows functionality such as trace here in general -- can lead to deadlock when stopped processes are in writing to a file
- *)
- (* GC may run only if and only if sweep phase has been completed *)
- IF ~EnableFreeLists OR (sweepMemBlock = NIL) & (sweepMarkValue = currentMarkValue) THEN
- IF Stats THEN
- Nmark := 0; Nmarked := 0;
- INC(Ngc);
- time1 := Machine.GetTimer ();
- END;
- numCandidates := 0;
- rootList := NIL;
- INC(currentMarkValue);
- AddRootObject(root);
- IF GCType = HeuristicStackInspectionGC THEN
- REPEAT
- REPEAT
- IF rootList # NIL THEN (* check root objects *)
- REPEAT
- obj := rootList; (* get head object *)
- rootList := rootList.nextRoot; (* link to next *)
- obj.FindRoots; (* Mark called via AddRootObject, but not for objects in static heap *)
- UNTIL rootList = NIL
- END;
- IF numCandidates # 0 THEN CheckCandidates END
- UNTIL (numCandidates = 0) & (rootList = NIL);
- MarkRealtimeObjects;
- CheckFinalizedObjects;
- UNTIL rootList = NIL;
- ELSIF GCType = MetaDataForStackGC THEN
- REPEAT
- IF rootList # NIL THEN (* check root objects *)
- REPEAT
- obj := rootList; (* get head object *)
- rootList := rootList.nextRoot; (* link to next *)
- obj.FindRoots; (* Mark called via AddRootObject, but not for objects in static heap *)
- UNTIL rootList = NIL
- END;
- MarkRealtimeObjects;
- CheckFinalizedObjects
- UNTIL rootList = NIL;
- ELSE
- HALT(901) (* wrong GCType constant *)
- END;
- IF Stats THEN
- time2 := Machine.GetTimer ();
- NgcCyclesLastRun := time2 - time1;
- IF NgcCyclesLastRun > NgcCyclesMax THEN NgcCyclesMax := NgcCyclesLastRun; END;
- INC(NgcCyclesAllRuns, NgcCyclesLastRun);
- NgcCyclesMark := NgcCyclesLastRun
- END;
- END;
- IF EnableFreeLists THEN GetFreeBlock(MAX(LONGINT), f) END;
- END CollectGarbage;
- PROCEDURE InvokeGC*;
- BEGIN
- ASSERT(gcStatus # NIL);
- gcStatus.SetgcOngoing(TRUE);
- END InvokeGC;
- PROCEDURE ReturnBlocks;
- VAR memBlock {UNTRACED}, free{UNTRACED}: Machine.MemoryBlock; p: ADDRESS; heapBlock {UNTRACED}: HeapBlock; f: FreeBlock;
- BEGIN
- GetFreeBlock(MAX(LONGINT), f);
- memBlock := Machine.memBlockHead;
- WHILE memBlock # NIL DO
- free := NIL;
- p := memBlock.beginBlockAdr;
- heapBlock := SYSTEM.VAL(HeapBlock, p + BlockHeaderSize);
- IF (heapBlock IS FreeBlock) & (p + heapBlock.size = memBlock.endBlockAdr) THEN
- free := memBlock;
- END;
- memBlock := memBlock.next;
- IF free # NIL THEN
- Machine.FreeMemBlock(free)
- END;
- END;
- sweepMemBlock := NIL; (* restart LazySweep *)
- ClearFreeLists;
- END ReturnBlocks;
- PROCEDURE LazySweepGC*;
- VAR p {UNTRACED}: FreeBlock;
- BEGIN
- (* invoke mark phase, mark phase starts at next scheduler interrupt *)
- GC;
- (* return blocks now *)
- Machine.Acquire(Machine.Heaps);
- (* trying to satisfy a request of MAX(LONGINT) bytes will never succeed - lazy sweep runs until end of heap *)
- GetFreeBlock(MAX(LONGINT), p);
- IF EnableReturnBlocks THEN ReturnBlocks END;
- Machine.Release(Machine.Heaps);
- END LazySweepGC;
- (* initialize a free heap block *)
- PROCEDURE InitFreeBlock(freeBlock: FreeBlock; mark: LONGINT; dataAdr: ADDRESS; size: SIZE);
- BEGIN
- freeBlock.mark := mark;
- freeBlock.dataAdr := dataAdr;
- freeBlock.size := size;
- freeBlock.next := NIL;
- (* initialize heap block header *)
- freeBlock.typeDesc := freeBlockTag;
- freeBlock.heapBlock := NIL;
- END InitFreeBlock;
- VAR throughput := 0 : SIZE;
- (* NewBlock - Allocate a heap block. {(size MOD BlockSize = 0)}. Caller must hold Heap lock. *)
- PROCEDURE NewBlock(size: SIZE): ADDRESS;
- VAR try: LONGINT; p: FreeBlock; freeBlock : FreeBlockU; memBlock {UNTRACED}: Machine.MemoryBlock;
- beginHeapBlockAdr, endHeapBlockAdr: ADDRESS;
- PROCEDURE CheckPostGC;
- BEGIN
- IF (sweepMarkValue < currentMarkValue) & EnableReturnBlocks THEN (* GC has run but no Sweep yet -- time to do post-gc cleanup *)
- ReturnBlocks
- END;
- END CheckPostGC;
- BEGIN
- CheckPostGC;
- try := 1;
- p := NIL;
- IF (GC = NilGC) OR (throughput < 64*1024*1024) THEN
- GetFreeBlock(size, p);
- ELSE
- throughput := 0;
- END;
- WHILE (p = NIL) & (try <= MaxTries) DO
- Machine.Release(Machine.Heaps); (* give up control *)
- GC; (* try to free memory (other processes may also steal memory now) *)
- Machine.Acquire(Machine.Heaps);
- CheckPostGC;
- sweepMemBlock := NIL;
- GetFreeBlock(size, p);
- IF p = NIL THEN
- Machine.ExpandHeap(try, size, memBlock, beginHeapBlockAdr, endHeapBlockAdr); (* try to extend the heap *)
- IF endHeapBlockAdr > beginHeapBlockAdr THEN
- freeBlock := beginHeapBlockAdr + BlockHeaderSize;
- InitFreeBlock(freeBlock, Unmarked, NilVal, endHeapBlockAdr - beginHeapBlockAdr);
- Machine.SetMemoryBlockEndAddress(memBlock, endHeapBlockAdr); (* end address of expanded block must set after free block is fit in memory block *)
- IF EnableFreeLists THEN AppendFreeBlock(freeBlock)
- ELSE
- sweepMemBlock := memBlock;
- sweepBlockAdr := beginHeapBlockAdr;
- END;
- GetFreeBlock(size, p);
- sweepMemBlock := NIL; (* restart sweep from beginning after having taken big block in order to avoid fragmentation *)
- END;
- INC(try)
- END;
- END;
- IF p # NIL THEN
- IF Stats THEN INC(Nnew); INC(NnewBytes, size) END;
- ASSERT(p.size >= size);
- RETURN p;
- ELSE (* try = MaxTries *)
- SYSTEM.HALT(14) (* out of memory *)
- END;
- END NewBlock;
- PROCEDURE CheckBP(bp: ADDRESS): ADDRESS;
- VAR n: ADDRESS;
- BEGIN
- SYSTEM.GET(bp,n);
- IF ODD(n) THEN bp := bp + SIZEOF(ADDRESS) END;
- RETURN bp;
- END CheckBP;
- PROCEDURE SetPC(p: DataBlockU);
- VAR bp: ADDRESS;
- BEGIN
- IF p # NIL THEN
- bp := CheckBP(Machine.CurrentBP());
- SYSTEM.GET(bp, bp);
- bp := CheckBP(bp);
- SYSTEM.GET(bp+SIZEOF(ADDRESS), p.heapBlock.heapBlock);
- END;
- END SetPC;
- (** NewSys - Implementation of SYSTEM.NEW. *)
- PROCEDURE NewSys*(VAR p: ANY; size: SIZE; isRealtime: BOOLEAN);
- VAR
- blockSize, systemBlockSize: SIZE; systemBlockAdr, dataBlockAdr: ADDRESS;
- systemBlock: HeapBlockU;
- dataBlock: DataBlockU;
- BEGIN
- systemBlockSize := BlockHeaderSize + SIZEOF(SystemBlockDesc);
- INC(systemBlockSize, (-systemBlockSize) MOD ArrayAlignment); (* round up to multiple of ArrayAlignment to ensure alignment of first data element to 0 MOD ArrayAlignment *)
- blockSize := systemBlockSize + BlockHeaderSize + size;
- INC(blockSize, (-blockSize) MOD BlockSize); (* round up to multiple of BlockSize *)
- Machine.Acquire(Machine.Heaps);
- systemBlockAdr:= NewBlock(blockSize);
- IF systemBlockAdr # 0 THEN
- systemBlock := systemBlockAdr;
- dataBlockAdr := systemBlockAdr + systemBlockSize;
- dataBlock := dataBlockAdr;
- systemBlock.typeDesc := systemBlockTag;
- dataBlock.typeDesc := NilVal;
- dataBlock.heapBlock := systemBlock;
- systemBlock.mark := currentMarkValue;
- systemBlock.dataAdr := dataBlockAdr;
- systemBlock.size := blockSize;
- (*! disable realtime block handling for the time being
- IF isRealtime THEN
- systemBlock.nextRealtime := realtimeList;
- realtimeList := systemBlock
- ELSE
- systemBlock.nextRealtime := NIL
- END;
- *)
- SetPC(dataBlock);
- p := dataBlock;
- (* clear could be done outside lock because SysBlks are not traced, but for conformity it is done inside the lock *)
- Machine.Fill32(dataBlockAdr, blockSize - systemBlockSize - BlockHeaderSize, 0); (* clear everything from dataBlockAdr until end of block *)
- ELSE
- p := NIL
- END;
- IF allocationLogger # NIL THEN allocationLogger(p) END;
- Machine.Release(Machine.Heaps)
- END NewSys;
- (** NewRec - Implementation of NEW with a record. *)
- PROCEDURE NewRec*(VAR p: ANY; tag: ADDRESS; isRealtime: BOOLEAN);
- VAR
- size, blockSize: SIZE; recordBlockAdr, dataBlockAdr : ADDRESS;
- recordBlock: RecordBlockU;
- dataBlock: DataBlockU;
- typeDesc: StaticTypeBlockU;
- BEGIN
- typeDesc := tag;
- IF ProtTypeBit IN typeDesc.info.flags THEN
- NewProtRec(p, tag, isRealtime);
- SetPC(p);
- ELSE
- size := typeDesc.recSize;
- (* the block size is the sum of the size of the RecordBlock and the DataBlock.
- Two extra fields per subblock contain the tag and the reference to the heap block *)
- blockSize := BlockHeaderSize + SIZEOF(RecordBlockDesc) + BlockHeaderSize + size;
- INC(blockSize, (-blockSize) MOD BlockSize); (* round up to multiple of BlockSize *)
- Machine.Acquire(Machine.Heaps);
- recordBlockAdr := NewBlock(blockSize);
- IF recordBlockAdr # 0 THEN
- recordBlock := recordBlockAdr;
- dataBlockAdr := recordBlockAdr + SIZEOF(RecordBlockDesc) + BlockHeaderSize;
- dataBlock := dataBlockAdr;
- recordBlock.typeDesc := recordBlockTag;
- dataBlock.typeDesc := tag;
- dataBlock.heapBlock := recordBlockAdr;
- recordBlock.mark := currentMarkValue;
- recordBlock.dataAdr := dataBlockAdr;
- recordBlock.size := blockSize;
-
- (*! disable realtime block handling for the time being
- IF isRealtime THEN
- recordBlock.nextRealtime := realtimeList;
- realtimeList := recordBlock
- ELSE
- recordBlock.nextRealtime := NIL
- END;
- *)
-
- SetPC(dataBlock);
- p := dataBlock;
-
- (* clear must be done inside lock to ensure all traced pointer fields are initialized to NIL *)
- Machine.Fill32(dataBlockAdr, blockSize - SIZEOF(RecordBlockDesc) - 2 * BlockHeaderSize, 0); (* clear everything from dataBlockAdr until end of block *)
- ELSE
- p := NIL
- END;
- IF allocationLogger # NIL THEN allocationLogger(p) END;
- Machine.Release(Machine.Heaps)
- END;
- END NewRec;
- (** NewProtRec - Implementation of NEW with a protected record. *)
- PROCEDURE NewProtRec*(VAR p: ANY; tag: ADDRESS; isRealtime: BOOLEAN);
- VAR size, blockSize: SIZE; protRecBlockAdr, dataBlockAdr: ADDRESS;
- protRecBlock: ProtRecBlockU;
- dataBlock: DataBlockU;
- i: LONGINT;
- typeDesc: StaticTypeBlockU;
- BEGIN
- typeDesc := tag;
- size := typeDesc.recSize;
- blockSize := BlockHeaderSize + SIZEOF(ProtRecBlockDesc) + BlockHeaderSize + size;
- INC(blockSize, (-blockSize) MOD BlockSize); (* round up to multiple of BlockSize *)
- Machine.Acquire(Machine.Heaps);
- protRecBlockAdr := NewBlock(blockSize);
- IF protRecBlockAdr # 0 THEN
- protRecBlock := protRecBlockAdr;
- dataBlockAdr := protRecBlockAdr + SIZEOF(ProtRecBlockDesc) + BlockHeaderSize;
- dataBlock := dataBlockAdr;
- protRecBlock.typeDesc := protRecBlockTag;
- dataBlock.typeDesc := tag;
- dataBlock.heapBlock := protRecBlockAdr;
- protRecBlock.mark := currentMarkValue;
- protRecBlock.dataAdr := dataBlockAdr;
- protRecBlock.size := blockSize;
- (*! disable realtime block handling for the time being
- IF isRealtime THEN
- protRecBlock.nextRealtime := realtimeList;
- realtimeList := protRecBlock
- ELSE
- protRecBlock.nextRealtime := NIL
- END;
- *)
- protRecBlock.count := 0;
- protRecBlock.awaitingLock.head := NIL;
- protRecBlock.awaitingLock.tail := NIL;
- protRecBlock.awaitingCond.head := NIL;
- protRecBlock.awaitingCond.tail := NIL;
- protRecBlock.lockedBy := NIL;
- protRecBlock.locked := FALSE;
- protRecBlock.lock := NIL;
- FOR i := 0 TO NumPriorities - 1 DO
- protRecBlock.waitingPriorities[i] := 0
- END;
- INC(protRecBlock.waitingPriorities[0]); (* set sentinel value: assume that idle process with priority 0 waits on this resource *)
-
- SetPC(dataBlock);
- p := dataBlock;
- (* clear must be done inside lock to ensure all traced pointer fields are initialized to NIL *)
- Machine.Fill32(dataBlockAdr, blockSize - SIZEOF(ProtRecBlockDesc) - 2 * BlockHeaderSize, 0); (* clear everything from dataBlockAdr to end of block *)
- ELSE
- p := NIL
- END;
- IF allocationLogger # NIL THEN allocationLogger(p) END;
- Machine.Release(Machine.Heaps)
- END NewProtRec;
- (** NewArr - Implementation of NEW with an array containing pointers. *)
- PROCEDURE NewArr*(VAR p: ANY; elemTag: ADDRESS; numElems, numDims: SIZE; isRealtime: BOOLEAN);
- VAR arrayBlockAdr, dataBlockAdr: ADDRESS;
- elemSize, arrSize, blockSize, arrayBlockSize, fillSize, size, arrayDataOffset: SIZE;
- firstElem: ADDRESS;
- ptrOfs: ADDRESS;
- elemType: StaticTypeBlockU;
- arrayBlock: ArrayBlockU;
- dataBlock: ArrayDataBlockU;
- BEGIN
- elemType := elemTag;
- elemSize := elemType.recSize;
- arrSize := numElems * elemSize;
-
- IF arrSize = 0 THEN
- NewSys(p, numDims * AddressSize + 3 * AddressSize, isRealtime); (* no data, thus no specific alignment *)
- SetPC(p);
- ELSE
- ASSERT(BlockHeaderSize MOD ArrayAlignment = 0);
- arrayDataOffset := numDims * AddressSize + 3 * AddressSize;
- INC(arrayDataOffset, (-arrayDataOffset) MOD ArrayAlignment); (* round up to multiple of ArrayAlignment to ensure that first array element is aligned at 0 MOD ArrayAlignment *)
- ptrOfs := elemType.pointerOffsets;
- IF ptrOfs = MinPtrOfs - AddressSize THEN (* no pointers in element type *)
- size := arrayDataOffset + arrSize;
- NewSys(p, size, isRealtime);
- SetPC(p);
- ELSE
- arrayBlockSize := BlockHeaderSize + SIZEOF(ArrayBlockDesc);
- INC(arrayBlockSize, (-arrayBlockSize) MOD ArrayAlignment); (* do. *)
- blockSize := arrayBlockSize + BlockHeaderSize + (arrayDataOffset + arrSize);
- INC(blockSize, (-blockSize) MOD BlockSize); (* round up to multiple of BlockSize *)
- Machine.Acquire(Machine.Heaps);
- arrayBlockAdr := NewBlock(blockSize);
- IF arrayBlockAdr # 0 THEN
- arrayBlock := arrayBlockAdr;
- dataBlockAdr := arrayBlockAdr + arrayBlockSize (* - BlockHeaderSize + BlockHeaderSize *);
- dataBlock := dataBlockAdr;
- arrayBlock.typeDesc := arrayBlockTag;
- dataBlock.typeDesc := elemType;
- dataBlock.heapBlock := arrayBlock;
- arrayBlock.mark := currentMarkValue;
- arrayBlock.dataAdr := dataBlockAdr;
- arrayBlock.size := blockSize;
- (*! disable realtime block handling for the time being
- IF isRealtime THEN
- arrayBlock.nextRealtime := realtimeList;
- realtimeList := arrayBlock
- ELSE
- arrayBlock.nextRealtime := NIL
- END;
- *)
- (* clear data part of array here, since size parameter of Machine.Fill32 must be a multiple of 4. Some fields of the data part are filled below for GC. , *)
- fillSize := blockSize - arrayBlockSize - BlockHeaderSize;
- Machine.Fill32(dataBlockAdr, fillSize, 0); (* clear everything from dataBlockAdr until end of block *)
-
- firstElem := dataBlockAdr + arrayDataOffset;
- dataBlock.numElems := numElems;
- dataBlock.current := NIL;
- dataBlock.first := firstElem;
- SetPC(dataBlock);
- p := dataBlock;
- ELSE
- p := NIL
- END;
- IF allocationLogger # NIL THEN allocationLogger(p) END;
- Machine.Release(Machine.Heaps)
- END
- END
- END NewArr;
- (* obsolete for generic object file / required only for old loader *)
- PROCEDURE FillStaticType*(VAR staticTypeAddr: ADDRESS; startAddr, typeInfoAdr: ADDRESS; size, recSize: SIZE;
- numPtrs, numSlots: LONGINT);
- VAR p, offset: ADDRESS; staticTypeBlock {UNTRACED}: StaticTypeBlock;
- BEGIN
- Machine.Acquire(Machine.Heaps);
- Machine.Fill32(startAddr, size, 0); (* clear whole static type, size MOD AddressSize = 0 implicitly, see WriteType in PCOF.Mod *)
- SYSTEM.PUT(startAddr, MethodEndMarker); (* sentinel *)
- (* methods and tags filled in later *)
- offset := AddressSize * (numSlots + 1 + 1); (* #methods, max. no. of tags, method end marker (sentinel), pointer to type information*)
- p := startAddr + offset;
- SYSTEM.PUT(p + TypeDescOffset, typeInfoAdr); (* pointer to typeInfo *)
- staticTypeBlock := SYSTEM.VAL(StaticTypeBlock, p);
- staticTypeBlock.recSize := recSize;
- staticTypeAddr := p;
- (* create the pointer for the dynamic array of pointer offsets, the dynamic array of pointer offsets is stored in the static type
- descriptor, it has no header part *)
- INC(p, SIZEOF(StaticTypeDesc));
- IF p MOD (2 * AddressSize) # 0 THEN INC(p, AddressSize) END;
- SYSTEM.PUT(p + 3 * AddressSize, numPtrs); (* internal structure of dynamic array without pointers: the first 3 fields are unused *)
- staticTypeBlock.pointerOffsets := SYSTEM.VAL(PointerOffsets, p); (* the fourth field contains the dimension of the array *)
- (* pointer offsets filled in later *)
- Machine.Release(Machine.Heaps)
- END FillStaticType;
- PROCEDURE AddFinalizer*(obj: ANY; n: FinalizerNode);
- BEGIN
- n.objWeak := obj; n.objStrong := NIL; n.finalizerStrong := NIL;
- Machine.Acquire(Machine.Heaps);
- n.nextFin := checkRoot; checkRoot := n;
- IF Stats THEN INC(NfinalizeAlive) END;
- Machine.Release(Machine.Heaps)
- END AddFinalizer;
- (** Compute total heap size, free space and largest free block size in bytes. This is a slow operation. *)
- PROCEDURE GetHeapInfo*(VAR total, free, largest: SIZE);
- VAR memBlock {UNTRACED}: Machine.MemoryBlock; blockAdr: ADDRESS;
- block {UNTRACED}: HeapBlock;
- BEGIN
- Machine.Acquire(Machine.Heaps);
- memBlock := Machine.memBlockHead;
- total := 0; free := 0; largest := 0;
- WHILE memBlock # NIL DO
- total := total + memBlock.endBlockAdr - memBlock.beginBlockAdr;
- blockAdr := memBlock.beginBlockAdr;
- WHILE blockAdr < memBlock.endBlockAdr DO
- block := SYSTEM.VAL(HeapBlock, blockAdr + BlockHeaderSize); (* get heap block *)
- IF (block.mark < currentMarkValue) THEN (* free/unused block encountered *)
- free := free + block.size;
- IF ADDRESS(block.size) > ADDRESS(largest) THEN largest := block.size END
- END;
- blockAdr := blockAdr + block.size;
- END;
- memBlock := memBlock.next
- END;
- Machine.Release(Machine.Heaps)
- END GetHeapInfo;
- (* NilGC - Default garbage collector. *)
- PROCEDURE NilGC;
- BEGIN
- HALT(301) (* garbage collector not available yet *)
- END NilGC;
- (* Init - Initialize the heap. *)
- PROCEDURE Init;
- VAR beginBlockAdr, endBlockAdr, freeBlockAdr, p: ADDRESS;
- heapBlock: HeapBlockU; freeBlock: FreeBlockU; memBlock {UNTRACED}: Machine.MemoryBlock;
- s: ARRAY 32 OF CHAR; minSize,i: LONGINT;
- BEGIN
- Machine.GetConfig("EnableFreeLists", s);
- EnableFreeLists := (s[0] = "1");
- Machine.GetConfig("EnableReturnBlocks", s);
- EnableReturnBlocks := (s[0] = "1");
- IF EnableReturnBlocks THEN Trace.String("Heaps:ReturnBlocks enabled"); Trace.Ln END;
- Machine.GetConfig("TraceHeaps",s);
- trace := (s[0] = "1");
- minSize := 32;
- FOR i := 0 TO MaxFreeLists DO
- freeLists[i].minSize := minSize;
- freeLists[i].first := NIL; freeLists[i].last := NIL;
- IF i < FreeListBarrier THEN INC( minSize, BlockSize ) ELSE minSize := 2 * minSize END
- END;
- GC := NilGC;
- newSum := 0;
- checkRoot := NIL; finalizeRoot := NIL; rootList := NIL; realtimeList := NIL;
- gcStatus := NIL;
- Machine.SetGCParams;
- Machine.GetStaticHeap(beginBlockAdr, endBlockAdr, freeBlockAdr);
- (* the Type desciptor is generated by the compiler, therefore the linker does not have to patch anything any more *)
- freeBlockTag := SYSTEM.TYPECODE (FreeBlockDesc);
- systemBlockTag := SYSTEM.TYPECODE (SystemBlockDesc);
- recordBlockTag := SYSTEM.TYPECODE (RecordBlockDesc);
- protRecBlockTag := SYSTEM.TYPECODE (ProtRecBlockDesc);
- arrayBlockTag := SYSTEM.TYPECODE (ArrayBlockDesc);
- (* find last block in static heap *)
- p := beginBlockAdr;
- heapBlock := SYSTEM.VAL(HeapBlock, p + BlockHeaderSize);
- WHILE p < freeBlockAdr DO
- initBlock := SYSTEM.VAL(ANY, heapBlock.dataAdr);
- p := p + heapBlock.size;
- heapBlock := SYSTEM.VAL(HeapBlock, p + BlockHeaderSize)
- END;
- ASSERT(p = freeBlockAdr);
- IF endBlockAdr - freeBlockAdr > 0 THEN
- (* initialization of free heap block done here since boot file is only written up to freeBlockAdr *)
- freeBlock := freeBlockAdr + BlockHeaderSize;
- InitFreeBlock(freeBlock, Unmarked, NilVal, endBlockAdr - freeBlockAdr);
- IF EnableFreeLists THEN AppendFreeBlock(freeBlock) END;
- ASSERT(freeBlock.size MOD BlockSize = 0)
- END;
- currentMarkValue := 1;
- (* extend the heap for one block such that module initialization can continue as long as Heaps.GC is not set validly *)
- Machine.ExpandHeap(1, 1, memBlock, beginBlockAdr, endBlockAdr); (* try = 1, size = 1 -> the minimal heap block expansion is performed *)
- IF endBlockAdr > beginBlockAdr THEN
- freeBlock := beginBlockAdr + BlockHeaderSize;
- InitFreeBlock(freeBlock, Unmarked, NilVal, endBlockAdr - beginBlockAdr);
- Machine.SetMemoryBlockEndAddress(memBlock, endBlockAdr);
- IF EnableFreeLists THEN AppendFreeBlock(freeBlock) END;
- sweepMarkValue := currentMarkValue;
- sweepMemBlock := memBlock;
- sweepBlockAdr := beginBlockAdr
- END;
- END Init;
- PROCEDURE SetHeuristic*;
- BEGIN
- GCType := HeuristicStackInspectionGC;
- Trace.String("GC mode : heuristic"); Trace.Ln;
- END SetHeuristic;
- PROCEDURE SetMetaData*;
- BEGIN
- GCType := MetaDataForStackGC;
- Trace.String("GC mode : metadata"); Trace.Ln;
- END SetMetaData;
- BEGIN
- (* The meta data stack inspection is more efficient than the heuristics *)
- GCType := HeuristicStackInspectionGC;
- Init;
- END Heaps.
- (*
- TraceHeap:
- 0 1 NR NEW record
- 1 2 NA/NV NEW array
- 2 4 NS SYSTEM.NEW
- 3 8 DR deallocate record #
- 4 16 DA deallocate array #
- 5 32 DS deallocate sysblk #
- 6 64 NT NewType
- 7 128
- 8 256 FB show free blocks #
- 9 512 DP deallocate protrec #
- 10 1024 finalizers
- 11 2048 live/dead #
- 12 4096 trace mark stack overflows #
- # influences timing
- *)
- (*
- 20.03.1998 pjm Started
- 17.08.1998 pjm FindRoots method
- 18.08.1998 pjm findPossibleRoots removed, use FindRoots method
- 09.10.1998 pjm NewRec with page alignment
- 21.01.1999 pjm Mark adapted for AosBuffers
- 26.01.1999 pjm Incorporated changes for new compiler
- 10.11.2000 pjm Finalizers
- 26.01.2001 pjm Removed trapReserve, reimplemented NewBlock
- 11.11.2004 lb Garbage collector with marking stack
- 19.06.2007 ug Garbage collector using meta data for stack inspection (cf. Objects)
- 11.07.2008 ug new heap data structures and adaption to GC
- *)
- Compiler.Compile -p=Win32G --traceModule=Trace Heaps.Mod ~
- StaticLinker.Link --fileFormat=PE32 --fileName=A2M.exe --extension=GofW --displacement=401000H Runtime Trace Kernel32 Machine Heaps Modules Objects Kernel KernelLog Streams Commands FIles WinFS Clock Dates Reals Strings Diagnostics BitSets StringPool ObjectFile GenericLinker Reflection GenericLoader BootConsole ~
- FSTools.CloseFiles A2M.exe ~
- FoxBinarySymbolFile.Test /temp/obj/Heaps ~
|