#lang plai/gc2/collector
;; Originally by Yixi Zhang
;; simplified for class by db
;; config for collection
(define (alloc-word) 0) ; next location of young generation allocation
(define (free-list-head) (2nd-gen-size)) ; a word holds the location of head of free slots list
(define (table-start) (add1 (2nd-gen-size))) ; start position of table for intergenerational pointers
(define debug #t)
(define (print-heap-layout)
(when debug
(printf "init allocator 1-gen ~a 2-gen ~a table ~a fl ~a\n" (1st-gen-size) (2nd-gen-size)
(table-start) (free-list-head))))
;; init-allocator : -> void
(define (init-allocator)
(heap-set! (alloc-word) 1)
(for ([i (in-range 1 (heap-size))])
(heap-set! i 'free))
(heap-set! (1st-gen-size) 'free-n)
(heap-set! (+ 1 (1st-gen-size)) #f)
(heap-set! (+ 2 (1st-gen-size))
(- (2nd-gen-size)
(1st-gen-size)))
(heap-set! (free-list-head)
(1st-gen-size))
(heap-set! (table-start) (add1 (table-start)))
(print-heap-layout))
;; 1st gen takes 1/4 of entire heap
(define (1st-gen-size)
(let ([s (round (* (heap-size) 1/4))])
(if (odd? s)
(add1 s)
s)))
(define (2nd-gen-size)
(round (* (heap-size) 7/8)))
;; -> loc loc
(define (to-space)
;; returns start and end of to-space
(let ([next (heap-ref (alloc-word))]
[half (add1 (round (/ (1st-gen-size) 2)))])
(if (< next half)
(values 1 half)
(values half (1st-gen-size)))))
(define (from-space)
;; returns start and end of from-space
(let ([next (heap-ref (alloc-word))]
[half (add1 (round (/ (1st-gen-size) 2)))])
(if (< next half)
(values half (1st-gen-size))
(values 1 half))))
(define (at-to-space? loc)
(let-values ([(begin end) (to-space)])
(and (>= loc begin)
(< loc end))))
(define (2nd-gen? loc)
(and (>= loc (1st-gen-size))
(< loc (2nd-gen-size))))
;; gc:deref : loc -> heap-value
;; must signal an error if fl-loc doesn't point to a flat value
(define (gc:deref fl-loc)
(case (heap-ref fl-loc)
[(flat) (heap-ref (+ fl-loc 1))]
[(frwd) (gc:deref (heap-ref (+ fl-loc 1)))]
[else (error 'gc:deref
"non-flat @ ~s"
fl-loc)]))
;; track/loc : loc -> loc
;; if loc points to a flat or pair or proc, then return loc
;; else if loc points to a frwd, return the frwd address
(define (track/loc loc-or-root)
(define loc (->location loc-or-root))
(case (heap-ref loc)
[(flat pair proc) loc]
[(frwd) (heap-ref (+ loc 1))]
[else (error 'track/loc "wrong tag ~s at ~a" (heap-ref loc) loc)]))
;; gc:alloc-flat : heap-value -> loc
(define (gc:alloc-flat fv)
(define ptr (malloc 2))
(heap-set! ptr 'flat)
(heap-set! (+ ptr 1) fv)
ptr)
;; ->location : (or/c location? root?) . -> . location?
(define (->location thing)
(cond
[(location? thing) thing]
[(root? thing) (read-root thing)]))
;; gc:cons : loc loc -> loc
;; hd and tl are guaranteed to have been earlier
;; results from either gc:alloc-flat or gc:cons
(define (gc:cons hd tl)
(define ptr (malloc 3 hd tl))
(define hd/loc (->location hd))
(define tl/loc (->location tl))
(define head (track/loc hd/loc))
(define tail (track/loc tl/loc))
(heap-set! ptr 'pair)
(heap-set! (+ ptr 1) head)
(heap-set! (+ ptr 2) tail)
ptr)
;; gc:first : loc -> loc
;; must signal an error of pr-loc does not point to a pair
(define (gc:first pr-loc)
(if (equal? (heap-ref pr-loc) 'pair)
(heap-ref (+ (track/loc pr-loc) 1))
(error 'first "non pair @ ~s" pr-loc)))
;; gc:rest : loc -> loc
;; must signal an error of pr-loc does not point to a pair
(define (gc:rest pr-loc)
(if (equal? (heap-ref pr-loc) 'pair)
(heap-ref (+ (track/loc pr-loc) 2))
(error 'rest "non pair @ ~s" pr-loc)))
;; gc:flat? : loc -> boolean
;; loc is guaranteed to have been an earlier
;; result from either gc:alloc-flat or gc:cons
(define (gc:flat? loc)
(case (heap-ref loc)
[(flat) #t]
[(frwd) (gc:flat? (heap-ref (+ loc 1)))]
[else #f]))
;; gc:cons? : loc -> boolean
;; loc is guaranteed to have been an earlier
;; result from either gc:alloc-flat or gc:cons
(define (gc:cons? loc)
(case (heap-ref loc)
[(pair) #t]
[(frwd) (gc:cons? (heap-ref (+ loc 1)))]
[else #f]))
;; gc:set-first! : loc loc -> void
;; must signal an error of pr-loc does not point to a pair
(define (gc:set-first! pr-loc new)
(cond
[(gc:cons? pr-loc)
(define loc (track/loc pr-loc))
(heap-set! (+ loc 1) new)
(when (and (2nd-gen? loc)
(at-to-space? new))
(table/alloc (+ loc 1) new))]
[else (error 'set-first! "non pair at ~s" pr-loc)]))
;; gc:set-rest! : loc loc -> void
;; must signal an error of pr-loc does not point to a pair
(define (gc:set-rest! pr-loc new)
(cond
[(gc:cons? pr-loc)
(define loc (track/loc pr-loc))
(heap-set! (+ loc 2) new)
(when (and (2nd-gen? loc)
(at-to-space? new))
(table/alloc (+ loc 2) new))]
[else (error 'set-rest! "non pair @ ~s" pr-loc)]))
;; gc:closure : heap-value (vectorof loc) -> loc
;; allocates a closure with 'code-ptr' and the free variables
;; in the vector 'free-vars'.
(define (gc:closure code-ptr free-vars)
(define fv-count (length free-vars))
(define next (malloc (+ fv-count 3)
free-vars
'()))
(define updated-free-vars
(for/list ([v (in-list free-vars)])
(track/loc v)))
(heap-set! next 'proc)
(heap-set! (+ next 1) code-ptr)
(heap-set! (+ next 2) fv-count)
(for ([x (in-range 0 fv-count)])
(heap-set! (+ next 3 x)
(list-ref updated-free-vars x)))
next)
;; gc:closure-code-ptr : loc -> heap-value
;; given a location returned from an earlier allocation
;; check to see if it is a closure; if not signal an
;; error. if so, return the code-ptr
(define (gc:closure-code-ptr loc)
(if (gc:closure? loc)
(heap-ref (+ (track/loc loc) 1))
(error 'gc:closure-code-ptr "non closure at ~a" loc)))
;; gc:closure-env-ref : loc number -> loc
;; given a location returned from an earlier allocation
;; check to see if it is a closure; if not signal an
;; error. if so, return the 'i'th variable in the closure
(define (gc:closure-env-ref loc i)
(if (gc:closure? loc)
(heap-ref (+ (track/loc loc) 3 i))
(error 'gc:closure-env-ref "non closure at ~a" loc)))
;; gc:closure? : loc -> boolean
;; determine if a previously allocated location
;; holds a closure
(define (gc:closure? loc)
(case (heap-ref loc)
[(proc) #t]
[(frwd) (gc:closure? (heap-ref (+ loc 1)))]
[else #f]))
(define (table/alloc pointer target)
(define next (heap-ref (table-start)))
(cond
[(>= (+ next 2) (heap-size))
(move/pointers (+ 1 (table-start)))
(heap-set! (+ 1 (table-start)) pointer)
(heap-set! (+ 2 (table-start)) target)
(heap-set! (table-start) (+ 3 (table-start)))]
[else
(heap-set! next pointer)
(heap-set! (+ next 1) target)
(heap-set! (table-start) (+ next 2))]))
;; alloc : number[size] roots roots -> loc
(define (malloc n . extra-roots)
(define addr (heap-ref (alloc-word)))
(cond
[(enough-to-space? addr n)
(heap-set! (alloc-word) (+ addr n))
addr]
[else
(collect-garbage extra-roots)
(switch/sweep-tospace n)]))
;; loc number -> bool
(define (enough-to-space? start size)
(define-values (begin end) (to-space))
(< (+ start size) end))
;; number -> loc
(define (switch/sweep-tospace number)
(define-values (begin end) (from-space))
(for ([i (in-range begin end)])
(heap-set! i 'free))
(heap-set! (alloc-word) (+ begin number))
begin)
;; find-free-space : find free space by traversing free space list
;; layout := free-2 next
;; | free-n next size
;; next := (or/c location? #f)
;; free-list-head : holds the value of head of free space list
;; | #f means already runs out of free space
;;
;; before give a series of free spaces to alloc function
;; must update the free space list
;; iff its the first free space in list must also udpate (free-list-head) as well
(define (find-free-space start prev size)
(local [(define (next-in-free-list loc)
(heap-ref (+ loc 1)))
(define (update-next-in-prev prev loc)
(heap-set! (if prev
(+ prev 1)
(free-list-head))
loc))]
(cond
[(not start) #f]
[else
(case (heap-ref start)
[(free-2)
(cond
[(= size 2)
(update-next-in-prev prev (next-in-free-list start))
start]
[else (find-free-space (heap-ref (+ start 1)) start size)])]
[(free-n)
(define length (heap-ref (+ start 2)))
(cond
[(= size length)
(update-next-in-prev prev (next-in-free-list start))
start]
[(< size length)
(define new-free (+ start size))
(define new-size (- length size))
(cond
[(= new-size 1)
(update-next-in-prev prev (next-in-free-list start))
(heap-set! new-free 'free)
start]
[else
(update-next-in-prev prev new-free)
(cond
[(= new-size 2)
(heap-set! new-free 'free-2)
(heap-set! (+ new-free 1) (heap-ref (+ start 1)))]
[else (heap-set! new-free 'free-n)
(heap-set! (+ new-free 1) (heap-ref (+ start 1)))
(heap-set! (+ new-free 2) new-size)])
start])]
[else (find-free-space (heap-ref (+ start 1)) start size)])]
[else (error 'find-free-space "wrong tag @ ~s" start)])])))
(define (2nd-gen-gc . extra-roots)
(define start (1st-gen-size))
(mark-white! start)
(traverse/roots (get-root-set))
(traverse/roots extra-roots)
(make-pointers-to-2nd-gen-roots)
(free-white! start #f #f #f))
(define (make-pointers-to-2nd-gen-roots)
(define-values (begin end) (values 1 (1st-gen-size)))
(let loop ([start begin])
(cond
[(= start end) (void)]
[else
(case (heap-ref start)
[(flat) (loop (+ start 2))]
[(pair) (define one-loc (heap-ref (+ start 1)))
(when (2nd-gen? one-loc) (traverse/roots one-loc))
(define another-loc (heap-ref (+ start 2)))
(when (2nd-gen? another-loc) (traverse/roots another-loc))
(loop (+ start 3))]
[(proc) (define fv-counts (heap-ref (+ start 2)))
(for ([i (in-range fv-counts)])
(define loc (heap-ref (+ start 3 i)))
(when (2nd-gen? loc) (traverse/roots loc)))
(loop (+ start 3 fv-counts))]
[(frwd) (define loc (heap-ref (+ start 1)))
(traverse/roots loc)
(case (heap-ref loc)
[(flat) (loop (+ start 2))]
[(pair) (loop (+ start 3))]
[(proc) (loop (+ start 3 (heap-ref (+ loc 2))))])]
[(free) (loop (+ start 1))]
[else (error 'make-pointers-to-2nd-gen-roots "wrong tag at ~a" start)])])))
(define (mark-white! i)
(when (< i (2nd-gen-size))
(case (heap-ref i)
[(flat) (heap-set! i 'white-flat)
(mark-white! (+ i 2))]
[(pair) (heap-set! i 'white-pair)
(mark-white! (+ i 3))]
[(proc) (heap-set! i 'white-proc)
(mark-white! (+ i 3 (heap-ref (+ i 2))))]
[(free) (mark-white! (+ i 1))]
[(free-2) (mark-white! (+ i 2))]
[(free-n) (mark-white! (+ i (heap-ref (+ i 2))))]
[else (error 'mark-white! "wrong tag at ~a" i)])))
(define (traverse/roots thing)
(cond
[(list? thing)
(for-each traverse/roots thing)]
[(root? thing)
(traverse/loc (read-root thing))]
[(number? thing)
(traverse/loc thing)]))
(define (traverse/loc loc)
(when (2nd-gen? loc)
(case (heap-ref loc)
[(white-flat)
(heap-set! loc 'flat)]
[(white-pair)
(heap-set! loc 'pair)
(traverse/loc (heap-ref (+ loc 1)))
(traverse/loc (heap-ref (+ loc 2)))]
[(white-proc)
(heap-set! loc 'proc)
(for ([x (in-range (heap-ref (+ loc 2)))])
(traverse/loc (heap-ref (+ loc 3 x))))]
[(pair flat proc) (void)]
[else (error 'traverse/loc "wrong tag at ~a" loc)])))
;; object-length : location -> number
(define (object-length loc)
(define tag (heap-ref loc))
(case tag
[(free) 1]
[(free-2) 2]
[(free-n) (heap-ref (+ loc 2))]
[(flat white-flat) 2]
[(pair white-pair) 3]
[(proc white-proc) (+ 3 (heap-ref (+ loc 2)))]
[else (error 'object-length "wrong tag ~s @ ~s" tag loc)]))
;; free spaces by constructing free-list
;; free-white! : location location location number -> void
(define (free-white! loc prev last-start spaces-so-far)
(unless (or (and last-start spaces-so-far)
(not (or last-start spaces-so-far)))
(error 'free-white!
"cumulating info are incorrect, last-start: ~s, spaces-so-far: ~s"
last-start spaces-so-far))
(cond
[(>= loc (2nd-gen-size))
(cond
[(and last-start spaces-so-far)
(cond
[(= 1 spaces-so-far) (void)]
[(= 2 spaces-so-far) (heap-set! last-start 'free-2)
(heap-set! (+ 1 last-start) #f)
(heap-set! (if prev (+ prev 1) (free-list-head))
last-start)]
[else (heap-set! last-start 'free-n)
(heap-set! (+ 1 last-start) #f)
(heap-set! (+ 2 last-start) spaces-so-far)
(heap-set! (if prev (+ prev 1) (free-list-head))
last-start)])]
[else (void)])]
[else
(define tag (heap-ref loc))
(case tag
[(flat pair proc)
(define length (object-length loc))
(cond
[(and last-start
spaces-so-far
(= 1 spaces-so-far))
(free-white! (+ loc length) prev #f #f)]
[(and last-start
spaces-so-far
(>= spaces-so-far 2))
(cond
[(= 2 spaces-so-far) (heap-set! last-start 'free-2)
(heap-set! (+ last-start 1) #f)]
[else (heap-set! last-start 'free-n)
(heap-set! (+ last-start 1) #f)
(heap-set! (+ last-start 2) spaces-so-far)])
(if prev
(heap-set! (+ prev 1) last-start)
(heap-set! (free-list-head) last-start))
(free-white! (+ loc length) last-start #f #f)]
[else (free-white! (+ loc length) prev #f #f)])]
[(white-flat white-pair white-proc free free-2 free-n)
(define length (object-length loc))
(cond
[(and last-start spaces-so-far)
(free-white! (+ loc length) prev last-start (+ spaces-so-far length))]
[else (free-white! (+ loc length) prev loc length)])]
[else (error 'free-white! "wrong tag at ~a" loc)])]))
;; collect-garbage : roots -> void
(define (collect-garbage . extra-roots)
(move/roots (get-root-set))
(move/roots extra-roots)
(move/pointers (+ 1 (table-start))))
;; move/roots : loc/(listof loc) -> loc
;; move every thing reachable from 'roots'
;; to the to space
(define (move/roots thing)
(cond
[(list? thing)
(for-each move/roots thing)]
[(root? thing)
(define new-addr (move/loc (read-root thing)))
(set-root! thing new-addr)
(move/ref new-addr)]
[(number? thing)
(move/ref (move/loc thing))]))
;; move/loc : loc -> loc
;; move object to the other semi-space
;; and return the new addr of moved object
(define (move/loc loc)
(cond
[(at-to-space? loc)
(case (heap-ref loc)
[(flat) (define new-addr (copy/alloc 2))
(heap-set! new-addr 'flat)
(heap-set! (+ new-addr 1) (heap-ref (+ loc 1)))
(heap-set! loc 'frwd)
(heap-set! (+ loc 1) new-addr)
new-addr] ; â®
[(pair)
(define new (copy/alloc 3 (heap-ref (+ loc 1))
(heap-ref (+ loc 2))))
(heap-set! new 'pair)
(heap-set! (+ new 1)
(track/loc (heap-ref (+ loc 1))))
(heap-set! (+ new 2)
(track/loc (heap-ref (+ loc 2))))
(heap-set! loc 'frwd) (heap-set! (+ loc 1) new)
new]
[(proc) (define length (+ 3 (heap-ref (+ loc 2))))
(define free-vars (build-vector (- length 3)
(lambda (i)
(heap-ref (+ loc 3 i)))))
(define new-addr (copy/alloc length free-vars '()))
(for ([x (in-range 0 3)])
(heap-set! (+ new-addr x) (heap-ref (+ loc x))))
(for ([x (in-range 3 length)])
(heap-set! (+ new-addr x) (track/loc (heap-ref (+ loc x)))))
(heap-set! loc 'frwd)
(heap-set! (+ loc 1) new-addr)
new-addr] ; â®
[(frwd) (heap-ref (+ loc 1))]
[else (error 'move/loc "wrong tag ~s at ~a" (heap-ref loc) loc)])]
[else loc]))
(define (copy/alloc n . extra-roots)
(define next (find-free-space
(heap-ref (free-list-head)) #f n))
(cond [next next]
[else
(2nd-gen-gc extra-roots)
(define next (find-free-space
(heap-ref (free-list-head)) #f n))
(unless next
(error 'copy/alloc "no space"))
next]))
;; move/ref : loc -> loc
;; move the referenced object to the other semi-space
;; and return the new addr of moved object
(define (move/ref loc)
(case (heap-ref loc)
[(flat) (void)]
[(pair) (gc:set-first! loc (move/loc (heap-ref (+ loc 1))))
(gc:set-rest! loc (move/loc (heap-ref (+ loc 2))))
(move/ref (heap-ref (+ loc 1)))
(move/ref (heap-ref (+ loc 2)))]
[(proc) (define fv-count (heap-ref (+ loc 2)))
(for ([x (in-range 0 fv-count)])
(define l (+ loc 3 x))
(heap-set! l (move/loc (heap-ref l)))
(move/ref (heap-ref l)))]
[(frwd) (move/ref (heap-ref (+ 1 loc)))]
[else (error 'move/ref "wrong tag at ~a" loc)]))
(define (move/pointers loc)
(cond
[(or (= loc (heap-size))
(equal? 'free (heap-ref loc)))
(heap-set! (table-start) (add1 (table-start)))]
[else
(define new-addr (move/loc (heap-ref (+ loc 1))))
(heap-set! (+ loc 1) new-addr)
(move/ref new-addr)
(heap-set! loc 'free)
(heap-set! (+ loc 1) 'free)
(move/pointers (+ loc 2))]))
(module+ test
(print-only-errors #t)
(with-heap (make-vector 1000)
(init-allocator)
(test/exn
(let ([cons-addr
(gc:cons
(simple-root (gc:alloc-flat #f))
(simple-root (gc:alloc-flat #t)))])
(gc:deref cons-addr))
"non-flat"))
(with-heap (make-vector 1000)
(init-allocator)
(test/exn
(let ([flat-addr (gc:alloc-flat #f)])
(gc:first flat-addr))
"non pair"))
(with-heap (make-vector 1000)
(init-allocator)
(test/exn
(let ([flat-addr (gc:alloc-flat #f)])
(gc:closure-code-ptr flat-addr))
"non closure"))
(with-heap (make-vector 1000)
(init-allocator)
(test/exn
(let ([flat-addr (gc:alloc-flat #f)])
(gc:closure-env-ref flat-addr 0))
"non closure"))
)
;; Part 2
(module+ test
(with-heap (make-vector 1000)
(init-allocator)
(let ([flat-addr (gc:alloc-flat #t)])
(test (gc:flat? flat-addr) #t)
(test (gc:cons? flat-addr) #f)
(test (gc:deref flat-addr) #t)))
)
;; Part 3 cons cells
;; first and rest
(module+ test
(with-heap (make-vector 1000)
(init-allocator)
(let ([cons-loc
(gc:cons
(simple-root (gc:alloc-flat 'first))
(simple-root (gc:alloc-flat 'rest)))])
(test (gc:deref (gc:rest cons-loc)) 'rest)
(test (gc:deref (gc:first cons-loc)) 'first)))
;; setting cons parts
(with-heap (make-vector 1000)
(init-allocator)
(let ([cons-loc
(gc:cons
(simple-root (gc:alloc-flat 'first))
(simple-root (gc:alloc-flat 'rest)))])
(test
(begin (gc:set-first! cons-loc (gc:alloc-flat 'first-mutated))
(gc:deref (gc:first cons-loc)))
'first-mutated)
(test
(begin (gc:set-rest! cons-loc (gc:alloc-flat 'rest-mutated))
(gc:deref (gc:rest cons-loc)))
'rest-mutated)))
)
; part 4 closures
(module+ test
(with-heap (make-vector 1000)
(init-allocator)
(let ([closure-loc
(gc:closure 'code-pointer (list (simple-root (gc:alloc-flat 'sekrit))))])
(test (gc:deref (gc:closure-env-ref closure-loc 0)) 'sekrit)
(test (gc:closure-code-ptr closure-loc) 'code-pointer)))
)