3.9 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.9.1 Writing your Garbage Collectors
You must complete this assignment using a specific language. Choose Determine language from source in the DrRacket menu, then writeas 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.
3.9.1.1 Required Functions
Your garbage collectors must implement the following functions:
procedure
(init-allocator) → void
procedure
(gc:alloc-flat v) → location?
v : heap-value?
procedure
(gc:set-first! cons-addr first-addr) → void
cons-addr : location? first-addr : location?
procedure
(gc:set-rest! cons-addr rest-addr) → void
cons-addr : location? rest-addr : location?
procedure
(gc:deref addr) → heap-value?
addr : location?
3.9.1.2 Provided Functions
To help you write these functions, the GC Collector Racket language defines an interface for the heap and the roots:
procedure
(heap-value? v) → boolean?
v : any/c
procedure
addr : location? v : heap-value?
procedure
(heap-ref addr) → heap-value?
addr : location?
syntax
(get-root-set root-identifier ...)
root-identifier : identifier?
Note: Sometimes you have to fake roots in case a live object is only reachable from arguments passed to a collector function. For example, you should pass the first and rest addresses to get-root-set in your implementation of gc:cons. (This is not the only example!)
procedure
(procedure-roots proc) → (listof root?)
proc : procedure?
syntax
(allocator-setup collector-file heap-size)
collector-file : string?
heap-size : number?
3.9.2 Testing your Garbage Collectors
You must complete this assignment using a specific language. Choose Determine language from source in the DrRacket menu, then writeas the first line of your program, overwritting the default #lang at the top of the file.
(allocator-setup "collector.rkt" heap-size)
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.
3.9.3 Sample Code
To get you started, we’ve provided a trivial collector that signals an error when the heap fills up.
3.9.3.1 Trivial Collector
"collector.rkt":
#lang plai/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)))
3.9.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!
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.