GarbageCollector.Mod 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. (* Automatic memory management *)
  2. (* Copyright (C) Florian Negele *)
  3. (** The GarbageCollector module provides an automatic memory management for pointers allocated using the NEW statement. *)
  4. (** It implements a concurrent and interruptible mark and sweep garbage collection. *)
  5. MODULE GarbageCollector;
  6. IMPORT Activities, BaseTypes, Counters, CPU;
  7. (** Notifies interested parties before collecting. *)
  8. VAR finalize*: PROCEDURE (cycle: LONGWORD);
  9. VAR tracings: Counters.AlignedCounter;
  10. VAR currentCycle, oldestCycle: LONGWORD;
  11. VAR firstMarked {UNTRACED}, firstWatched {UNTRACED}: BaseTypes.Pointer;
  12. VAR markedSentinel {UNTRACED}, watchedSentinel {UNTRACED}: BaseTypes.Pointer;
  13. TYPE Delegate = RECORD proc: PROCEDURE; pointer: BaseTypes.Pointer END;
  14. (** Registers the specified pointer for automatic memory management. *)
  15. (** This procedure is called by the compiler when a pointer or object type is allocated using the NEW statement. *)
  16. PROCEDURE Watch- (pointer {UNTRACED}: BaseTypes.Pointer);
  17. VAR value {UNTRACED}: BaseTypes.Pointer;
  18. BEGIN {UNCOOPERATIVE, UNCHECKED}
  19. (* check for valid arguments *)
  20. ASSERT (pointer # NIL);
  21. ASSERT (pointer.nextWatched = NIL);
  22. (* initialize garbage collection cycle *)
  23. pointer.cycle := CAS (oldestCycle, 0, 0);
  24. (* append pointer to linked list of watched pointers *)
  25. LOOP
  26. value := CAS (firstWatched, NIL, NIL);
  27. pointer.nextWatched := value;
  28. IF CAS (firstWatched, value, pointer) = value THEN EXIT END;
  29. CPU.Backoff;
  30. END;
  31. END Watch;
  32. (** Performs the assignment of a pointer variable. *)
  33. (** This procedure is called by the compiler when assigning variables of pointer type. *)
  34. PROCEDURE Assign- (VAR pointer {UNTRACED}: BaseTypes.Pointer; value {UNTRACED}: BaseTypes.Pointer);
  35. VAR previousValue {UNTRACED}: BaseTypes.Pointer;
  36. BEGIN {UNCOOPERATIVE, UNCHECKED}
  37. previousValue := pointer; pointer := value; IF value # NIL THEN Mark (value) END;
  38. IF Activities.IsLocalVariable (ADDRESS OF pointer) THEN
  39. IF value # NIL THEN Counters.Inc (value.references) END;
  40. IF previousValue # NIL THEN Counters.Dec (previousValue.references) END;
  41. END;
  42. END Assign;
  43. (** Performs the assignment of a procedure delegate. *)
  44. (** This procedure is called by the compiler when assigning variables of procedure delegate type. *)
  45. PROCEDURE AssignDelegate-(target {UNTRACED}, source {UNTRACED}: POINTER {UNSAFE} TO Delegate);
  46. BEGIN {UNCOOPERATIVE, UNCHECKED}
  47. Assign(target.pointer, source.pointer);
  48. target.proc := source.proc;
  49. END AssignDelegate;
  50. (** Performs the assignment of an array containing pointers. *)
  51. (** This procedure is called by the compiler when assigning arrays of pointer type. *)
  52. PROCEDURE AssignPointerArray- (VAR target {UNTRACED}: ARRAY OF BaseTypes.Pointer; CONST source {UNTRACED}: ARRAY OF BaseTypes.Pointer);
  53. VAR i: SIZE;
  54. BEGIN {UNCOOPERATIVE, UNCHECKED}
  55. ASSERT (LEN (target) = LEN (source));
  56. FOR i := 0 TO LEN (target) - 1 DO
  57. Assign (target[i], source[i]);
  58. END;
  59. END AssignPointerArray;
  60. (** Performs the assignment of an array containing delegates. *)
  61. (** This procedure is called by the compiler when assigning arrays of procedure delegate type. *)
  62. PROCEDURE AssignDelegateArray-(VAR target {UNTRACED}: ARRAY OF Delegate; CONST source {UNTRACED}: ARRAY OF Delegate);
  63. VAR i: SIZE;
  64. BEGIN{UNCOOPERATIVE, UNCHECKED}
  65. FOR i := 0 TO LEN(target) DO
  66. AssignDelegate(ADDRESS OF target[i], ADDRESS OF source[i]);
  67. END;
  68. END AssignDelegateArray;
  69. (** Executes an atomic compare-and-swap operation on a pointer variable. *)
  70. (** This procedure is called by the compiler when executing CAS expressions. *)
  71. PROCEDURE CompareAndSwap- (VAR pointer {UNTRACED}: BaseTypes.Pointer; previousValue {UNTRACED}, value {UNTRACED}: BaseTypes.Pointer): ADDRESS;
  72. VAR result {UNTRACED}: BaseTypes.Pointer;
  73. BEGIN {UNCOOPERATIVE, UNCHECKED}
  74. result := CAS (pointer, previousValue, value); IF value # NIL THEN Mark (value) END;
  75. IF (result = previousValue) & Activities.IsLocalVariable (ADDRESS OF pointer) THEN
  76. IF value # NIL THEN Counters.Inc (value.references) END;
  77. IF previousValue # NIL THEN Counters.Dec (value.references) END;
  78. END;
  79. RETURN result;
  80. END CompareAndSwap;
  81. (** Resets a pointer variable. *)
  82. (** This procedure is called by the compiler when assigning NIL to variables of pointer type. *)
  83. PROCEDURE Reset- (VAR pointer {UNTRACED}: BaseTypes.Pointer);
  84. BEGIN {UNCOOPERATIVE, UNCHECKED}
  85. IF pointer = NIL THEN RETURN END;
  86. IF Activities.IsLocalVariable (ADDRESS OF pointer) THEN Counters.Dec (pointer.references) END;
  87. pointer := NIL;
  88. END Reset;
  89. (** Resets an array of pointers. *)
  90. (** This procedure is called by the compiler for resetting all elements of array variables containing pointers. *)
  91. PROCEDURE ResetArray- (VAR pointers {UNTRACED}: ARRAY OF BaseTypes.Pointer);
  92. VAR i: SIZE;
  93. BEGIN {UNCOOPERATIVE, UNCHECKED}
  94. FOR i := 0 TO LEN (pointers) - 1 DO
  95. Reset (pointers[i]);
  96. END;
  97. END ResetArray;
  98. (** Reset a delegate. *)
  99. (** This procedure is called by the compiler when resetting variables of procedure delegate type. *)
  100. PROCEDURE ResetDelegate-(target {UNTRACED}: POINTER {UNSAFE} TO Delegate);
  101. BEGIN {UNCOOPERATIVE, UNCHECKED}
  102. Reset(target.pointer);
  103. END ResetDelegate;
  104. (** Resets an array of delegates. *)
  105. (** This procedure is called by the compiler for resetting all elements of array variables containing procedure delegates. *)
  106. PROCEDURE ResetDelegateArray-(VAR delegates {UNTRACED}: ARRAY OF Delegate);
  107. VAR i: SIZE;
  108. BEGIN{UNCOOPERATIVE, UNCHECKED}
  109. FOR i := 0 TO LEN(delegates) DO
  110. Reset (delegates[i].pointer);
  111. END;
  112. END ResetDelegateArray;
  113. (* Increments a garbage collection cycle and returns its previous value. *)
  114. (* Collection cycles should be able to overflow and are therefore represented using a signed type. *)
  115. PROCEDURE Increment (VAR cycle: LONGWORD): LONGWORD;
  116. VAR value: LONGWORD;
  117. BEGIN {UNCOOPERATIVE, UNCHECKED}
  118. LOOP
  119. value := CAS (cycle, 0, 0);
  120. IF CAS (cycle, value, value + 1) = value THEN EXIT END;
  121. CPU.Backoff;
  122. END;
  123. RETURN value;
  124. END Increment;
  125. (** Performs a complete garbage collection cycle by marking the object graph and disposing all unreachable objects. *)
  126. (** Garbage can be collected concurrently if necessary. *)
  127. PROCEDURE Collect*;
  128. VAR root EXTERN "Modules.root": BaseTypes.Pointer;
  129. VAR cycle: LONGWORD; first {UNTRACED}: BaseTypes.Pointer;
  130. BEGIN {UNCHECKED}
  131. (* ensure list of marked pointers is empty *)
  132. TraceMarkedPointers;
  133. (* get unique garbage collection cycle *)
  134. cycle := Increment (currentCycle);
  135. (* help older collection cycles to mark all remaining pointers *)
  136. WHILE CAS (oldestCycle, 0, 0) # cycle DO TraceMarkedPointers END;
  137. (* acquire ownership of watched pointers *)
  138. first := AcquireList (firstWatched, watchedSentinel);
  139. (* mark global referenced pointers *)
  140. Mark (root); TraceMarkedPointers;
  141. (* mark pointers changed by assignments *)
  142. WHILE Activities.AssignmentsInProgress () DO TraceMarkedPointers END;
  143. (* mark local referenced pointers *)
  144. MarkReferenced (first); TraceMarkedPointers;
  145. (* mark all remaining pointers *)
  146. WHILE Counters.Read (tracings) # 0 DO TraceMarkedPointers END; TraceMarkedPointers;
  147. ASSERT (CAS (oldestCycle, cycle, cycle + 1) = cycle);
  148. (* notify interested parties *)
  149. IF finalize # NIL THEN finalize (cycle) END;
  150. (* collect garbage *)
  151. Sweep (first, cycle);
  152. END Collect;
  153. (** Marks the specified pointer as reachable. *)
  154. (** This procedure is called by the compiler while tracing outgoing pointers of marked objects. *)
  155. PROCEDURE Mark- (pointer {UNTRACED}: BaseTypes.Pointer);
  156. VAR cycle, current: LONGWORD; first {UNTRACED}: BaseTypes.Pointer;
  157. BEGIN {UNCOOPERATIVE, UNCHECKED}
  158. (* ignore nil pointers *)
  159. IF pointer = NIL THEN RETURN END;
  160. (* check whether the pointer has to be marked *)
  161. LOOP
  162. cycle := CAS (pointer.cycle, 0, 0);
  163. (* ignore already marked pointers *)
  164. current := CAS (currentCycle, 0, 0);
  165. IF cycle - current >= 0 THEN RETURN END;
  166. (* try to mark pointer *)
  167. IF CAS (pointer.cycle, cycle, current) = cycle THEN EXIT END;
  168. CPU.Backoff;
  169. END;
  170. (* append pointer to linked list of marked pointers *)
  171. LOOP
  172. first := CAS (firstMarked, NIL, NIL);
  173. IF CAS (pointer.nextMarked, NIL, first) # NIL THEN RETURN END;
  174. IF CAS (firstMarked, first, pointer) = first THEN EXIT END;
  175. ASSERT (CAS (pointer.nextMarked, first, NIL) = first);
  176. CPU.Backoff;
  177. END;
  178. END Mark;
  179. (** Marks an array of pointers. *)
  180. (** This procedure is called by the compiler while tracing arrays of pointer type. *)
  181. PROCEDURE MarkPointerArray- (CONST pointers {UNTRACED} : ARRAY OF BaseTypes.Pointer);
  182. VAR i: SIZE;
  183. BEGIN {UNCOOPERATIVE, UNCHECKED}
  184. FOR i := 0 TO LEN (pointers) - 1 DO
  185. Mark (pointers[i]);
  186. END;
  187. END MarkPointerArray;
  188. (** Marks an array of records. *)
  189. (** This procedure is called by the compiler while tracing arrays of record type. *)
  190. PROCEDURE MarkRecordArray- (address: ADDRESS; length: SIZE; descriptor {UNTRACED}: BaseTypes.Descriptor);
  191. VAR i: SIZE;
  192. BEGIN {UNCOOPERATIVE, UNCHECKED}
  193. FOR i := 0 TO length - 1 DO
  194. descriptor.trace (address);
  195. INC (address, descriptor.size);
  196. END;
  197. END MarkRecordArray;
  198. PROCEDURE MarkDelegateArray-(CONST delegates {UNTRACED}: ARRAY OF Delegate);
  199. VAR i: SIZE;
  200. BEGIN{UNCOOPERATIVE, UNCHECKED}
  201. FOR i := 0 TO LEN(delegates) DO
  202. Mark (delegates[i].pointer);
  203. END;
  204. END MarkDelegateArray;
  205. (* Acquires ownership of the specified linked list by exchanging its first item with its sentinel. *)
  206. PROCEDURE AcquireList (VAR first {UNTRACED}: BaseTypes.Pointer; sentinel {UNTRACED}: BaseTypes.Pointer): {UNTRACED} BaseTypes.Pointer;
  207. VAR value {UNTRACED}: BaseTypes.Pointer;
  208. BEGIN {UNCOOPERATIVE, UNCHECKED}
  209. LOOP
  210. value := CAS (first, NIL, NIL);
  211. IF value = sentinel THEN EXIT END;
  212. IF CAS (first, value, sentinel) = value THEN EXIT END;
  213. CPU.Backoff;
  214. END;
  215. RETURN value;
  216. END AcquireList;
  217. (* Traverses the linked list of marked pointers and traces outgoing pointers. *)
  218. PROCEDURE TraceMarkedPointers;
  219. VAR current {UNTRACED}: BaseTypes.Pointer;
  220. BEGIN {UNCHECKED}
  221. (* notify older collections cycles that the list is traversed *)
  222. IF CAS (firstMarked, NIL, NIL) = markedSentinel THEN RETURN END;
  223. Counters.Inc (tracings);
  224. LOOP
  225. (* acquire ownership of marked pointers *)
  226. current := AcquireList (firstMarked, markedSentinel);
  227. IF current = markedSentinel THEN EXIT END;
  228. (* traverse linked list and trace pointers *)
  229. REPEAT
  230. current.Trace;
  231. current := CAS (current.nextMarked, current.nextMarked, NIL);
  232. UNTIL current = markedSentinel;
  233. END;
  234. (* allow older cycles to continue *)
  235. Counters.Dec (tracings);
  236. END TraceMarkedPointers;
  237. (* Traverses a list of watched pointers and marks local references. *)
  238. PROCEDURE MarkReferenced (VAR first {UNTRACED}: BaseTypes.Pointer);
  239. VAR current {UNTRACED}, previous {UNTRACED}, next {UNTRACED}, front {UNTRACED}, back {UNTRACED}: BaseTypes.Pointer;
  240. BEGIN {UNCHECKED}
  241. (* traverse linked list and remove unreferenced items *)
  242. current := first; previous := NIL; front := NIL; back := NIL;
  243. WHILE current # watchedSentinel DO
  244. next := current.nextWatched;
  245. IF Counters.Read (current.references) # 0 THEN
  246. IF previous # NIL THEN previous.nextWatched := next ELSE first := next END;
  247. IF front # NIL THEN current.nextWatched := front ELSE back := current END;
  248. front := current; Mark (current);
  249. ELSE
  250. previous := current;
  251. END;
  252. current := next;
  253. END;
  254. (* reinsert referenced items *)
  255. InsertWatched (front, back);
  256. END MarkReferenced;
  257. (* Insert specified items to linked list of watched pointers. *)
  258. PROCEDURE InsertWatched (first {UNTRACED}, last {UNTRACED}: BaseTypes.Pointer);
  259. VAR value {UNTRACED}: BaseTypes.Pointer;
  260. BEGIN {UNCOOPERATIVE, UNCHECKED}
  261. IF last = NIL THEN RETURN END;
  262. LOOP
  263. value := CAS (firstWatched, NIL, NIL);
  264. last.nextWatched := value;
  265. IF CAS (firstWatched, value, first) = value THEN EXIT END;
  266. CPU.Backoff;
  267. END;
  268. END InsertWatched;
  269. (** Disposes all unmarked pointers up to the specified garbage collection cycle. *)
  270. PROCEDURE Sweep (first {UNTRACED}: BaseTypes.Pointer; cycle: LONGWORD);
  271. VAR current {UNTRACED}, previous {UNTRACED}, next {UNTRACED}: BaseTypes.Pointer;
  272. BEGIN {UNCHECKED}
  273. (* traverse linked list and remove unmarked items *)
  274. current := first; previous := NIL;
  275. WHILE current # watchedSentinel DO
  276. next := current.nextWatched;
  277. ASSERT (next # NIL);
  278. IF current.cycle - cycle <= 0 THEN
  279. IF previous # NIL THEN previous.nextWatched := next ELSE first := next END;
  280. DISPOSE (current);
  281. ELSE
  282. previous := current;
  283. END;
  284. current := next;
  285. END;
  286. (* reinsert marked items *)
  287. InsertWatched (first, previous);
  288. END Sweep;
  289. (** Initializes the module and its resources. *)
  290. PROCEDURE Initialize-;
  291. BEGIN {UNCOOPERATIVE, UNCHECKED}
  292. NEW (markedSentinel); markedSentinel.nextWatched := markedSentinel; firstMarked := markedSentinel;
  293. NEW (watchedSentinel); watchedSentinel.nextWatched := watchedSentinel; firstWatched := watchedSentinel;
  294. END Initialize;
  295. (** Terminates the module and disposes its resources and all remaining pointers that have been registered using the GarbageCollector.Watch procedure. *)
  296. (** @topic Runtime Call *)
  297. PROCEDURE Terminate-;
  298. VAR current {UNTRACED}, next {UNTRACED}: BaseTypes.Pointer;
  299. BEGIN {UNCOOPERATIVE, UNCHECKED}
  300. WHILE firstWatched # watchedSentinel DO
  301. current := firstWatched;
  302. firstWatched := watchedSentinel;
  303. REPEAT
  304. next := current.nextWatched;
  305. ASSERT (next # NIL);
  306. DISPOSE (current);
  307. current := next;
  308. UNTIL current = watchedSentinel;
  309. END;
  310. DISPOSE (markedSentinel);
  311. DISPOSE (watchedSentinel);
  312. END Terminate;
  313. END GarbageCollector.