123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- (* Automatic memory management *)
- (* Copyright (C) Florian Negele *)
- (** The GarbageCollector module provides an automatic memory management for pointers allocated using the NEW statement. *)
- (** It implements a concurrent and interruptible mark and sweep garbage collection. *)
- MODULE GarbageCollector;
- IMPORT Activities, BaseTypes, Counters, CPU;
- (** Notifies interested parties before collecting. *)
- VAR finalize*: PROCEDURE (cycle: LONGWORD);
- VAR tracings: Counters.AlignedCounter;
- VAR currentCycle, oldestCycle: LONGWORD;
- VAR firstMarked {UNTRACED}, firstWatched {UNTRACED}: BaseTypes.Pointer;
- VAR markedSentinel {UNTRACED}, watchedSentinel {UNTRACED}: BaseTypes.Pointer;
- TYPE Delegate = RECORD proc: PROCEDURE; pointer: BaseTypes.Pointer END;
- (** Registers the specified pointer for automatic memory management. *)
- (** This procedure is called by the compiler when a pointer or object type is allocated using the NEW statement. *)
- PROCEDURE Watch- (pointer {UNTRACED}: BaseTypes.Pointer);
- VAR value {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- (* check for valid arguments *)
- ASSERT (pointer # NIL);
- ASSERT (pointer.nextWatched = NIL);
- (* initialize garbage collection cycle *)
- pointer.cycle := CAS (oldestCycle, 0, 0);
- (* append pointer to linked list of watched pointers *)
- LOOP
- value := CAS (firstWatched, NIL, NIL);
- pointer.nextWatched := value;
- IF CAS (firstWatched, value, pointer) = value THEN EXIT END;
- CPU.Backoff;
- END;
- END Watch;
- (** Performs the assignment of a pointer variable. *)
- (** This procedure is called by the compiler when assigning variables of pointer type. *)
- PROCEDURE Assign- (VAR pointer {UNTRACED}: BaseTypes.Pointer; value {UNTRACED}: BaseTypes.Pointer);
- VAR previousValue {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- previousValue := pointer; pointer := value; IF value # NIL THEN Mark (value) END;
- IF Activities.IsLocalVariable (ADDRESS OF pointer) THEN
- IF value # NIL THEN Counters.Inc (value.references) END;
- IF previousValue # NIL THEN Counters.Dec (previousValue.references) END;
- END;
- END Assign;
- (** Performs the assignment of a procedure delegate. *)
- (** This procedure is called by the compiler when assigning variables of procedure delegate type. *)
- PROCEDURE AssignDelegate-(target {UNTRACED}, source {UNTRACED}: POINTER {UNSAFE} TO Delegate);
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- Assign(target.pointer, source.pointer);
- target.proc := source.proc;
- END AssignDelegate;
- (** Performs the assignment of an array containing pointers. *)
- (** This procedure is called by the compiler when assigning arrays of pointer type. *)
- PROCEDURE AssignPointerArray- (VAR target {UNTRACED}: ARRAY OF BaseTypes.Pointer; CONST source {UNTRACED}: ARRAY OF BaseTypes.Pointer);
- VAR i: SIZE;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- ASSERT (LEN (target) = LEN (source));
- FOR i := 0 TO LEN (target) - 1 DO
- Assign (target[i], source[i]);
- END;
- END AssignPointerArray;
- (** Performs the assignment of an array containing delegates. *)
- (** This procedure is called by the compiler when assigning arrays of procedure delegate type. *)
- PROCEDURE AssignDelegateArray-(VAR target {UNTRACED}: ARRAY OF Delegate; CONST source {UNTRACED}: ARRAY OF Delegate);
- VAR i: SIZE;
- BEGIN{UNCOOPERATIVE, UNCHECKED}
- FOR i := 0 TO LEN(target) DO
- AssignDelegate(ADDRESS OF target[i], ADDRESS OF source[i]);
- END;
- END AssignDelegateArray;
- (** Executes an atomic compare-and-swap operation on a pointer variable. *)
- (** This procedure is called by the compiler when executing CAS expressions. *)
- PROCEDURE CompareAndSwap- (VAR pointer {UNTRACED}: BaseTypes.Pointer; previousValue {UNTRACED}, value {UNTRACED}: BaseTypes.Pointer): ADDRESS;
- VAR result {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- result := CAS (pointer, previousValue, value); IF value # NIL THEN Mark (value) END;
- IF (result = previousValue) & Activities.IsLocalVariable (ADDRESS OF pointer) THEN
- IF value # NIL THEN Counters.Inc (value.references) END;
- IF previousValue # NIL THEN Counters.Dec (value.references) END;
- END;
- RETURN result;
- END CompareAndSwap;
- (** Resets a pointer variable. *)
- (** This procedure is called by the compiler when assigning NIL to variables of pointer type. *)
- PROCEDURE Reset- (VAR pointer {UNTRACED}: BaseTypes.Pointer);
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- IF pointer = NIL THEN RETURN END;
- IF Activities.IsLocalVariable (ADDRESS OF pointer) THEN Counters.Dec (pointer.references) END;
- pointer := NIL;
- END Reset;
- (** Resets an array of pointers. *)
- (** This procedure is called by the compiler for resetting all elements of array variables containing pointers. *)
- PROCEDURE ResetArray- (VAR pointers {UNTRACED}: ARRAY OF BaseTypes.Pointer);
- VAR i: SIZE;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- FOR i := 0 TO LEN (pointers) - 1 DO
- Reset (pointers[i]);
- END;
- END ResetArray;
- (** Reset a delegate. *)
- (** This procedure is called by the compiler when resetting variables of procedure delegate type. *)
- PROCEDURE ResetDelegate-(target {UNTRACED}: POINTER {UNSAFE} TO Delegate);
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- Reset(target.pointer);
- END ResetDelegate;
- (** Resets an array of delegates. *)
- (** This procedure is called by the compiler for resetting all elements of array variables containing procedure delegates. *)
- PROCEDURE ResetDelegateArray-(VAR delegates {UNTRACED}: ARRAY OF Delegate);
- VAR i: SIZE;
- BEGIN{UNCOOPERATIVE, UNCHECKED}
- FOR i := 0 TO LEN(delegates) DO
- Reset (delegates[i].pointer);
- END;
- END ResetDelegateArray;
- (* Increments a garbage collection cycle and returns its previous value. *)
- (* Collection cycles should be able to overflow and are therefore represented using a signed type. *)
- PROCEDURE Increment (VAR cycle: LONGWORD): LONGWORD;
- VAR value: LONGWORD;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- LOOP
- value := CAS (cycle, 0, 0);
- IF CAS (cycle, value, value + 1) = value THEN EXIT END;
- CPU.Backoff;
- END;
- RETURN value;
- END Increment;
- (** Performs a complete garbage collection cycle by marking the object graph and disposing all unreachable objects. *)
- (** Garbage can be collected concurrently if necessary. *)
- PROCEDURE Collect*;
- VAR root EXTERN "Modules.root": BaseTypes.Pointer;
- VAR cycle: LONGWORD; first {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCHECKED}
- (* ensure list of marked pointers is empty *)
- TraceMarkedPointers;
- (* get unique garbage collection cycle *)
- cycle := Increment (currentCycle);
- (* help older collection cycles to mark all remaining pointers *)
- WHILE CAS (oldestCycle, 0, 0) # cycle DO TraceMarkedPointers END;
- (* acquire ownership of watched pointers *)
- first := AcquireList (firstWatched, watchedSentinel);
- (* mark global referenced pointers *)
- Mark (root); TraceMarkedPointers;
- (* mark pointers changed by assignments *)
- WHILE Activities.AssignmentsInProgress () DO TraceMarkedPointers END;
- (* mark local referenced pointers *)
- MarkReferenced (first); TraceMarkedPointers;
- (* mark all remaining pointers *)
- WHILE Counters.Read (tracings) # 0 DO TraceMarkedPointers END; TraceMarkedPointers;
- ASSERT (CAS (oldestCycle, cycle, cycle + 1) = cycle);
- (* notify interested parties *)
- IF finalize # NIL THEN finalize (cycle) END;
- (* collect garbage *)
- Sweep (first, cycle);
- END Collect;
- (** Marks the specified pointer as reachable. *)
- (** This procedure is called by the compiler while tracing outgoing pointers of marked objects. *)
- PROCEDURE Mark- (pointer {UNTRACED}: BaseTypes.Pointer);
- VAR cycle, current: LONGWORD; first {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- (* ignore nil pointers *)
- IF pointer = NIL THEN RETURN END;
- (* check whether the pointer has to be marked *)
- LOOP
- cycle := CAS (pointer.cycle, 0, 0);
- (* ignore already marked pointers *)
- current := CAS (currentCycle, 0, 0);
- IF cycle - current >= 0 THEN RETURN END;
- (* try to mark pointer *)
- IF CAS (pointer.cycle, cycle, current) = cycle THEN EXIT END;
- CPU.Backoff;
- END;
- (* append pointer to linked list of marked pointers *)
- LOOP
- first := CAS (firstMarked, NIL, NIL);
- IF CAS (pointer.nextMarked, NIL, first) # NIL THEN RETURN END;
- IF CAS (firstMarked, first, pointer) = first THEN EXIT END;
- ASSERT (CAS (pointer.nextMarked, first, NIL) = first);
- CPU.Backoff;
- END;
- END Mark;
- (** Marks an array of pointers. *)
- (** This procedure is called by the compiler while tracing arrays of pointer type. *)
- PROCEDURE MarkPointerArray- (CONST pointers {UNTRACED} : ARRAY OF BaseTypes.Pointer);
- VAR i: SIZE;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- FOR i := 0 TO LEN (pointers) - 1 DO
- Mark (pointers[i]);
- END;
- END MarkPointerArray;
- (** Marks an array of records. *)
- (** This procedure is called by the compiler while tracing arrays of record type. *)
- PROCEDURE MarkRecordArray- (address: ADDRESS; length: SIZE; descriptor {UNTRACED}: BaseTypes.Descriptor);
- VAR i: SIZE;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- FOR i := 0 TO length - 1 DO
- descriptor.trace (address);
- INC (address, descriptor.size);
- END;
- END MarkRecordArray;
- PROCEDURE MarkDelegateArray-(CONST delegates {UNTRACED}: ARRAY OF Delegate);
- VAR i: SIZE;
- BEGIN{UNCOOPERATIVE, UNCHECKED}
- FOR i := 0 TO LEN(delegates) DO
- Mark (delegates[i].pointer);
- END;
- END MarkDelegateArray;
- (* Acquires ownership of the specified linked list by exchanging its first item with its sentinel. *)
- PROCEDURE AcquireList (VAR first {UNTRACED}: BaseTypes.Pointer; sentinel {UNTRACED}: BaseTypes.Pointer): {UNTRACED} BaseTypes.Pointer;
- VAR value {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- LOOP
- value := CAS (first, NIL, NIL);
- IF value = sentinel THEN EXIT END;
- IF CAS (first, value, sentinel) = value THEN EXIT END;
- CPU.Backoff;
- END;
- RETURN value;
- END AcquireList;
- (* Traverses the linked list of marked pointers and traces outgoing pointers. *)
- PROCEDURE TraceMarkedPointers;
- VAR current {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCHECKED}
- (* notify older collections cycles that the list is traversed *)
- IF CAS (firstMarked, NIL, NIL) = markedSentinel THEN RETURN END;
- Counters.Inc (tracings);
- LOOP
- (* acquire ownership of marked pointers *)
- current := AcquireList (firstMarked, markedSentinel);
- IF current = markedSentinel THEN EXIT END;
- (* traverse linked list and trace pointers *)
- REPEAT
- current.Trace;
- current := CAS (current.nextMarked, current.nextMarked, NIL);
- UNTIL current = markedSentinel;
- END;
- (* allow older cycles to continue *)
- Counters.Dec (tracings);
- END TraceMarkedPointers;
- (* Traverses a list of watched pointers and marks local references. *)
- PROCEDURE MarkReferenced (VAR first {UNTRACED}: BaseTypes.Pointer);
- VAR current {UNTRACED}, previous {UNTRACED}, next {UNTRACED}, front {UNTRACED}, back {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCHECKED}
- (* traverse linked list and remove unreferenced items *)
- current := first; previous := NIL; front := NIL; back := NIL;
- WHILE current # watchedSentinel DO
- next := current.nextWatched;
- IF Counters.Read (current.references) # 0 THEN
- IF previous # NIL THEN previous.nextWatched := next ELSE first := next END;
- IF front # NIL THEN current.nextWatched := front ELSE back := current END;
- front := current; Mark (current);
- ELSE
- previous := current;
- END;
- current := next;
- END;
- (* reinsert referenced items *)
- InsertWatched (front, back);
- END MarkReferenced;
- (* Insert specified items to linked list of watched pointers. *)
- PROCEDURE InsertWatched (first {UNTRACED}, last {UNTRACED}: BaseTypes.Pointer);
- VAR value {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- IF last = NIL THEN RETURN END;
- LOOP
- value := CAS (firstWatched, NIL, NIL);
- last.nextWatched := value;
- IF CAS (firstWatched, value, first) = value THEN EXIT END;
- CPU.Backoff;
- END;
- END InsertWatched;
- (** Disposes all unmarked pointers up to the specified garbage collection cycle. *)
- PROCEDURE Sweep (first {UNTRACED}: BaseTypes.Pointer; cycle: LONGWORD);
- VAR current {UNTRACED}, previous {UNTRACED}, next {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCHECKED}
- (* traverse linked list and remove unmarked items *)
- current := first; previous := NIL;
- WHILE current # watchedSentinel DO
- next := current.nextWatched;
- ASSERT (next # NIL);
- IF current.cycle - cycle <= 0 THEN
- IF previous # NIL THEN previous.nextWatched := next ELSE first := next END;
- DISPOSE (current);
- ELSE
- previous := current;
- END;
- current := next;
- END;
- (* reinsert marked items *)
- InsertWatched (first, previous);
- END Sweep;
- (** Initializes the module and its resources. *)
- PROCEDURE Initialize-;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- NEW (markedSentinel); markedSentinel.nextWatched := markedSentinel; firstMarked := markedSentinel;
- NEW (watchedSentinel); watchedSentinel.nextWatched := watchedSentinel; firstWatched := watchedSentinel;
- END Initialize;
- (** Terminates the module and disposes its resources and all remaining pointers that have been registered using the GarbageCollector.Watch procedure. *)
- (** @topic Runtime Call *)
- PROCEDURE Terminate-;
- VAR current {UNTRACED}, next {UNTRACED}: BaseTypes.Pointer;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- WHILE firstWatched # watchedSentinel DO
- current := firstWatched;
- firstWatched := watchedSentinel;
- REPEAT
- next := current.nextWatched;
- ASSERT (next # NIL);
- DISPOSE (current);
- current := next;
- UNTIL current = watchedSentinel;
- END;
- DISPOSE (markedSentinel);
- DISPOSE (watchedSentinel);
- END Terminate;
- END GarbageCollector.
|