On this page:
3.7.1 Writing your Garbage Collectors
3.7.2 Testing your Garbage Collectors
3.7.3 Sample Code
3.7.3.1 Trivial Collector
3.7.4 Notes

3.7 Garbage Collectors

In this assignment, you will implement two garbage collectors: mark & sweep, and stop & copy. As we have seen, garbage collection involves starting from a root set of references, which reside in local variables and stack frames, and searching for reachable values on the heap.

3.7.1 Writing your Garbage Collectors

You must complete this assignment using a specific language. Choose Determine language from source in the DrRacket menu, then write
as the first line of your program, overwritting the default #lang at the top of the file.

This language defines an interface to a program’s stack and heap that you will use to implement garbage collection.

If you look at the documentation for plai/gc2/collector, it gives you a list of the functions that are available to you as a collector author (Garbage Collector Interface) and a list of the functions that you must write (Garbage Collector Exports). It is essential that you understand what these functions do, since it is the entirety of the assignment.

3.7.2 Testing your Garbage Collectors

You must complete this assignment using a specific language. Choose Determine language from source in the DrRacket menu, then write
as the first line of your program, overwritting the default #lang at the top of the file.

This language is a subset of Racket that uses a garbage collector that you specify. The first line of a test program must be:

(allocator-setup "collector.rkt" heap-size)

where "collector.rkt" must be the name of your collector’s file. heap-size is the size of the heap your collector will use.

The remainder of the program is in a subset of Racket with numbers, symbols, lists, etc. The primitives of the language map directly to the procedures you define in your garbage collector. Thus, you can just write normal Racket programs without thinking about the collector and expect them to work.

Make sure you read the documentation for plai/gc2/mutator.

3.7.3 Sample Code

To get you started, we’ve provided a trivial collector that signals an error when the heap fills up.

3.7.3.1 Trivial Collector

"collector.rkt":

#lang plai/gc2/collector
(define heap-ptr 'uninitialized-heap-ptr)
 
(define (init-allocator)
 (set! heap-ptr 0))
 
(define (gc:alloc-flat p)
 (begin
   (when (> (+ heap-ptr 2) (heap-size))
     (error 'gc:alloc-flat "out of memory"))
   (heap-set! heap-ptr 'prim)
   (heap-set! (+ 1 heap-ptr) p)
   (set! heap-ptr (+ 2 heap-ptr))
   (- heap-ptr 2)))
 
(define (gc:cons f r)
 (begin
   (when (> (+ heap-ptr 3) (heap-size))
     (error 'gc:cons "out of memory"))
   (heap-set! heap-ptr 'cons)
   (heap-set! (+ 1 heap-ptr) f)
   (heap-set! (+ 2 heap-ptr) r)
   (set! heap-ptr (+ 3 heap-ptr))
   (- heap-ptr 3)))
 
(define (gc:cons? a)
 (eq? (heap-ref a) 'cons))
 
(define (gc:first a)
 (heap-ref (+ 1 a)))
 
(define (gc:rest a)
 (heap-ref (+ 2 a)))
 
(define (gc:set-first! a f)
 (if (gc:cons? a)
     (heap-set! (+ 1 a) f)
     (error 'set-first! "expects address of cons")))
 
(define (gc:set-rest! a r)
 (heap-set! (+ 2 a) r))
 
(define (gc:flat? a)
 (eq? (heap-ref a) 'prim))
 
(define (gc:deref a)
 (heap-ref (+ 1 a)))
 
(define (gc:closure c fvs)
 (begin
   (when (> (+ heap-ptr 2 (length fvs)) (heap-size))
     (error 'gc:cons "out of memory"))
   (heap-set! heap-ptr 'closure)
   (heap-set! (+ 1 heap-ptr) c)
   (for ([i (in-naturals)]
         [fv (in-list fvs)])
     (heap-set! (+ 1 heap-ptr i) fv))
   (set! heap-ptr (+ 2 (length fvs) heap-ptr))
   (- heap-ptr 2 (length fvs))))
 
(define (gc:closure-code-ptr a)
 (heap-ref (+ 1 a)))
 
(define (gc:closure-env-ref a i)
 (heap-ref (+ 2 a i)))
 
(define (gc:closure? a)
 (eq? (heap-ref a) 'closure))

This is a very bad collector. In particular, it is unsafe, because it doesn’t verify that, for example, gc:set-rest! is only called on a cons. If you use this collector without fixing these problems, you will get a zero. The errors are there to force you to understand this program, rather than just copying it blindly

3.7.4 Notes

You must store bookkeeping data on the heap provided by GC Collector Racket. You may store 2-3 atomic values, such as addresses into the heap as variables in your garbage collector. We will assume they represent machine registers. However, all compound data structures must be on the heap.

In particular, in the mark & sweep collector, you should maintain a free list in the heap. That is, you should not use any auxiliary data structure; instead, use the available space in the heap to keep track of the free list. You may use one extra box (“register”) to point to the start of the free list. You may need to adjust your allocation accordingly to have enough space to maintain free list pointers!

Some final words of advice:
  • You will want to test your program with small heap sizes, so that the garbage collector runs frequently.

  • You do not need to compact memory in mark-and-sweep.

  • You may find it convenient to use the Racket construct set!, which allows you to mutate a variable without using boxes. We recommend you use this feature only in one instance: when swapping the semispaces in the stop & copy collector. This will save you the trouble of unboxing the heap every time you use it.