UNB/ CS/ David Bremner/ teaching/ cs4613/ lectures/ lecture19/ copying.rkt
#lang plai/gc2/collector
(define (alloc-ptr) 0)
(define (active-semi-space) 1)

;; init-allocator : -> void
;; 2 pointers : allocation pointer
;;              active-semi-space pointer
(define (init-allocator)
  (heap-set! (alloc-ptr) 2) ;; allocation pointer
  (heap-set! (active-semi-space) 'left) ;; active-semi-space
  (for ([i (in-range 2 (heap-size))])
    (heap-set! i 'free)))

(define (mid)
  (+ 1 (round (* (heap-size) 1/2))))

;; 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 cons or clos, then return loc
;; else if loc points to a frwd, return the frwd address
(define (track/loc loc)
  (case (heap-ref loc)
    [(flat cons clos) loc]
    [(frwd) (heap-ref (+ loc 1))]
    [else (error 'track/loc "wrong tag at ~a" loc)]))

;; gc:alloc-flat : heap-value -> loc
(define (gc:alloc-flat fv)
  (define ptr (malloc 2 #f #f))
  (heap-set! ptr 'flat)
  (heap-set! (+ ptr 1) fv)
  ptr)

;; gc:cons : root root -> 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))
  (heap-set! ptr 'cons)
  (heap-set! (+ ptr 1) (track/loc (read-root hd)))
  (heap-set! (+ ptr 2) (track/loc (read-root tl)))
  (when (or (at-from-space? hd)
            (at-from-space? tl))
    (free-from-space))
  ptr)

;; gc:first : loc -> loc
;; must signal an error of pr-loc does not point to a cons
(define (gc:first pr-loc)
  (if (equal? (heap-ref pr-loc) 'cons)
      (heap-ref (+ pr-loc 1))
      (error 'first "non cons @ ~s" pr-loc)))

;; gc:rest : loc -> loc
;; must signal an error of pr-loc does not point to a cons
(define (gc:rest pr-loc)
  (if (equal? (heap-ref pr-loc) 'cons)
      (heap-ref (+ pr-loc 2))
      (error 'first "non cons @ ~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)
    [(cons) #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 cons
(define (gc:set-first! pr-loc new)
  (if (equal? (heap-ref pr-loc) 'cons)
      (heap-set! (+ pr-loc 1) new)
      (error 'set-first! "non cons")))

;; gc:set-rest! : loc loc -> void
;; must signal an error of pr-loc does not point to a cons
(define (gc:set-rest! pr-ptr new)
  (if (equal? (heap-ref pr-ptr) 'cons)
      (heap-set! (+ pr-ptr 2) new)
      (error 'set-rest! "non cons")))

;; 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 fv-vars (map read-root free-vars))
  (define addr (malloc (+ fv-count 3)
                      fv-vars
                      '()))
  (heap-set! addr 'clos)
  (heap-set! (+ addr 1) code-ptr)
  (heap-set! (+ addr 2) fv-count)
  (for ([i (in-range 0 (length free-vars))]
        [v (in-list free-vars)])
    (heap-set! (+ addr 3 i)
               (read-root v)))
  addr)

;; 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)
    [(clos) #t]
    [(frwd) (gc:closure? (heap-ref (+ loc 1)))]
    [else #f]))

;; alloc : number[size] roots roots -> loc
(define (malloc n some-roots more-roots)
  (define addr (heap-ref (alloc-ptr)))
  (cond
    [(<= (+ addr n) (space-limit))
     (heap-set! (alloc-ptr) (+ addr n))
     addr]
    [else
     (collect-garbage some-roots more-roots)
     (define next (heap-ref (alloc-ptr)))
     (unless (<= (+ next n) (space-limit))
       (error 'alloc "no space"))
     (heap-set! (alloc-ptr) (+ next n))
     ;; check for remaining forward info
     (unless (or (at-from-space? some-roots)
                 (at-from-space? more-roots))
       (free-from-space))
     next ; ⋮
     ]))

;; collect-garbage : roots roots -> void
(define (collect-garbage some-roots more-roots)
  (change-active-semi-space)
  (forward/roots (get-root-set))
  (forward/roots some-roots)
  (forward/roots more-roots)
  (forward/ref (semi-space-start)))

;; space-limit -> integer
;; find limit of current semi-heap
(define (space-limit)
  (if (equal? (heap-ref (active-semi-space)) 'left) (mid) (heap-size)))

;; semi-space-start
(define (semi-space-start)
  (if (equal? (heap-ref (active-semi-space)) 'left) 2 (mid)))

;; change-active-semi-space -> void
(define (change-active-semi-space)
  (case (heap-ref (active-semi-space))
    [(left)
     (heap-set! (active-semi-space) 'right)
     (heap-set! (alloc-ptr) (mid))]
    [(right)
     (heap-set! (active-semi-space) 'left)
     (heap-set! (alloc-ptr) 2)]))

;; forward/roots : loc/(listof loc) -> loc
;; move every thing reachable from 'roots'
;; to the to space
(define (forward/roots thing)
  (cond
    [(list? thing)
     (for-each forward/roots thing)]
    [(root? thing)
     (set-root! thing (forward/loc (read-root thing)))]
    [(number? thing)
     (forward/loc thing)]))

;; forward/loc : loc -> loc
;; move object to the other semi-space
;; and return the new addr of moved object
(define (forward/loc loc)
  (cond
    [(at-to-space? loc) loc]
    [else
     (case (heap-ref loc)
       [(flat) (define new-addr (gc/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]
       [(cons) (define new-addr (gc/alloc 3))
               (heap-set! new-addr 'cons)
               (heap-set! (+ new-addr 1) (track/loc (heap-ref (+ loc 1))))
               (heap-set! (+ new-addr 2) (track/loc (heap-ref (+ loc 2))))
               (heap-set! loc 'frwd)
               (heap-set! (+ loc 1) new-addr)
               new-addr] ; ⋮
       [(clos) (define length (+ 3 (heap-ref (+ loc 2))))
             (define new-addr (gc/alloc length))
             (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 'forward/loc "wrong tag at ~a" loc)])
      ]))

;; gc/alloc : num[size] -> loc
(define (gc/alloc n)
  (define addr (heap-ref (alloc-ptr)))
  (unless (<= (+ addr n) (space-limit))
    (error 'gc/alloc "no space"))
  (heap-set! (alloc-ptr) (+ addr n)) addr)

;; forward/ref : loc -> void
;; move the referenced object to the other semi-space
;; recursively scan rest of semi-space
(define (forward/ref loc)
  (cond
    [(= loc (heap-ref (alloc-ptr))) (void)]
    [else
     (case (heap-ref loc)
       [(flat) (forward/ref (+ loc 2))]
       [(cons)
        (gc:set-first! loc (forward/loc
                            (heap-ref (+ loc 1))))
        (gc:set-rest! loc (forward/loc
                           (heap-ref (+ loc 2))))
        (forward/ref (+ loc 3))]
       [(clos) (define fv-count (heap-ref (+ loc 2)))
               (for ([x (in-range 0 fv-count)])
                 (define l (+ loc 3 x))
                 (heap-set! l (forward/loc (heap-ref l))))
               (forward/ref (+ loc 3 fv-count))] ; ⋮
       [else (error 'forward/ref "wrong tag at ~a" loc)])]))

;; free the from space
;; after moved all live objects and their offsprings
;; over to space
(define (free-from-space)
  (case (heap-ref (active-semi-space))
    [(left)
     (for ([i (in-range (mid) (heap-size))])
       (heap-set! i 'free))]
    [(right)
     (for ([i (in-range 2 (mid))])
       (heap-set! i 'free))]))

(define (at-to-space? loc)
  (case (heap-ref (active-semi-space))
    [(left) (and (>= loc 2)
                 (< loc (mid)))]
    [(right) (and (>= loc (mid))
                  (< loc (heap-size)))]))

(define (at-from-space? thing)
  (cond
    [(list? thing)
     (ormap at-from-space? thing)]
    [(root? thing)
     (not (at-to-space? (read-root thing)))]
    [(number? thing)
     (not (at-to-space? thing))]
    [(not thing)
     thing]))

(module+ test
  (print-only-errors #t)
  (define test-heap1 (make-vector 12 'f))
  ;; init-allocator
  (test (with-heap test-heap1
          (init-allocator)
          test-heap1)
        (vector 2 'left 'free 'free
                'free 'free 'free 'free
                'free 'free 'free 'free))
  ;; alloc
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (malloc 2 #f #f))
        4)
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (malloc 2 #f #f)
          test-heap1)
        (vector 6 'left 'flat 0
                'free 'free 'free 'free
                'free 'free 'free 'free))
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:alloc-flat 0)
          (malloc 2 #f #f))
        7)
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:alloc-flat 0)
          (malloc 2 #f #f)
          test-heap1)
        (vector 9 'right 'free 'free
                'free 'free 'free 'free
                'free 'free 'free 'free))
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (malloc 3 2 2))
        4)
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:alloc-flat 0)
          (malloc 3 2 2)
          test-heap1)
        (vector 12 'right 'frwd 7
                'flat 0 'free 'flat
                0 'free 'free 'free))
  ;; gc:alloc-flat
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          test-heap1)
        (vector 4 'left 'flat 0
                'free 'free 'free 'free
                'free 'free 'free 'free))
  (let ([v (make-vector 14 'f)])
    (test (with-heap v
            (init-allocator)
            (gc:alloc-flat 0)
            (gc:alloc-flat 0)
            (gc:alloc-flat 0)
            (gc:alloc-flat 0)
            v)
          (vector 10 'right 'free 'free
                  'free 'free 'free 'free
                  'flat 0 'free 'free
                  'free 'free)))
  ;; gc:cons
  (test (with-heap test-heap1
          (init-allocator)
          (gc:cons (simple-root 2) (simple-root 2))
          test-heap1)
        (vector 5 'left 'cons 2
                2 'free 'free 'free
                'free 'free 'free 'free))
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:cons (simple-root 2) (simple-root 2))
          test-heap1)
        (vector 7 'left 'flat 0
                'cons 2 2 'free
                'free 'free 'free 'free))
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:alloc-flat 0)
          (gc:cons (simple-root 2) (simple-root 2))
          test-heap1)
        (vector 12 'right 'free 'free
                'free 'free 'free 'flat
                0 'cons 7 7))
  (test/exn (with-heap test-heap1
              (init-allocator)
              (gc:alloc-flat 0)
              (gc:alloc-flat 1)
              (gc:cons (simple-root 2) (simple-root 4)))
            "no space")
  (let ([v (make-vector 14 'f)])
    (test (with-heap v
            (init-allocator)
            (gc:alloc-flat 0)
            (gc:alloc-flat 0)
            (gc:alloc-flat 0)
            (gc:cons (simple-root 6) (simple-root 6))
            v)
          (vector 13 'right 'free 'free
                  'free 'free 'free 'free
                  'flat 0 'cons 8
                  8 'free)))
  ;; gc:closure
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:closure 'f empty)
          test-heap1)
        (vector 7 'left 'flat 0
                'clos 'f 0 'free
                'free 'free 'free 'free))
  (test/exn (with-heap test-heap1
              (init-allocator)
              (gc:alloc-flat 0)
              (gc:closure 'f (list (simple-root 2))))
            "no space")
  (let ([v (make-vector 14 'f)])
    (test (with-heap v
            (init-allocator)
            (gc:alloc-flat 0)
            (gc:alloc-flat 0)
            (gc:closure 'f (list (simple-root 2)))
            v)
          #(14 right frwd 8 flat 0 free free flat 0 clos f 1 2)))
  (define test-heap2 (make-vector 20 'f))
  ;; change-active-semi-space
  (test (with-heap test-heap2
          (init-allocator)
          (change-active-semi-space)
          test-heap2)
        (vector 11 'right 'free 'free
                'free 'free 'free 'free
                'free 'free 'free 'free
                'free 'free 'free 'free
                'free 'free 'free 'free))
  ;; forward/loc
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (change-active-semi-space)
          (forward/loc 2)
          test-heap2)
        (vector 13 'right 'frwd 11
                'free 'free 'free 'free
                'free 'free 'free 'flat
                0 'free 'free 'free
                'free 'free 'free 'free))
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:cons (simple-root 2) (simple-root 2))
          (change-active-semi-space)
          (forward/loc 4)
          test-heap2)
        (vector 14 'right 'flat 0
                'frwd 11 2 'free
                'free 'free 'free 'cons
                2 2 'free 'free
                'free 'free 'free 'free))
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:closure 'f (list (simple-root 2)))
          (change-active-semi-space)
          (forward/loc 4)
          test-heap2)
        (vector 15 'right 'flat 0
                'frwd '11 1 2
                'free 'free 'free 'clos
                'f 1 2 'free
                'free 'free 'free 'free))
  ;; forward/ref
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (change-active-semi-space)
          (forward/loc 2)
          (forward/ref 11)
          test-heap2)
        (vector 13 'right 'frwd 11
                'free 'free 'free 'free
                'free 'free 'free 'flat
                0 'free 'free 'free
                'free 'free 'free 'free))
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:cons (simple-root 2) (simple-root 2))
          (change-active-semi-space)
          (forward/loc 4)
          (forward/ref 11)
          test-heap2)
        (vector 16 'right 'frwd 14
                'frwd 11 2 'free
                'free 'free 'free 'cons
                14 14 'flat '0
                'free 'free 'free 'free))
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:closure 'f (list (simple-root 2)))
          (change-active-semi-space)
          (forward/loc 4)
          (forward/ref 11)
          test-heap2)
        (vector 17 'right 'frwd 15
                'frwd '11 1 2
                'free 'free 'free 'clos
                'f 1 15 'flat
                0 'free 'free 'free))
  ;; free-from-space
  (let ([test-heap (vector 11 'right 'flat 0
                           'flat 0 'free 'free
                           'free 'free 'free 'frwd
                           2 'frwd '4 'free
                           'free 'free 'free 'free)])
    (test (with-heap test-heap
            (free-from-space)
            test-heap)
          (vector 11 'right 'free 'free
                  'free 'free 'free 'free
                  'free 'free 'free 'frwd
                  2 'frwd '4 'free
                  'free 'free 'free 'free)))
  (let ([test-heap (vector 2 'left 'frwd 11
                           'frwd 13 'free 'free
                           'free 'free 'free 'flat
                           0 'flat 0 'free
                           'free 'free 'free 'free)])
    (test (with-heap test-heap
            (free-from-space)
            test-heap)
          (vector 2 'left 'frwd 11
                  'frwd 13 'free 'free
                  'free 'free 'free 'free
                  'free 'free 'free 'free
                  'free 'free 'free 'free)))
  ;; collect-garbage
  (test (with-heap test-heap1
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:alloc-flat 0)
          (collect-garbage 2 2)
          test-heap1)
        (vector 9 'right 'frwd 7
                'flat 0 'free 'flat
                0 'free 'free 'free))
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:cons (simple-root 2) (simple-root 2))
          (collect-garbage #f #f)
          test-heap2)
        (vector 11 'right 'flat 0
                'cons 2 2 'free
                'free 'free 'free 'free
                'free 'free 'free 'free
                'free 'free 'free 'free))
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:cons (simple-root 2) (simple-root 2))
          (collect-garbage 2 #f)
          test-heap2)
        (vector 13 'right 'frwd 11
                'cons 2 2 'free
                'free 'free 'free 'flat
                0 'free 'free 'free
                'free 'free 'free 'free))
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:cons (simple-root 2) (simple-root 2))
          (collect-garbage #f 4)
          test-heap2)
        (vector 16 'right 'frwd 14
                'frwd 11 2 'free
                'free 'free 'free 'cons
                14 14 'flat 0
                'free 'free 'free 'free))
  (test (with-heap test-heap2
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:closure 'f (list (simple-root 2)))
          (collect-garbage #f 4)
          test-heap2)
        (vector 17 'right 'frwd 15
                'frwd 11 1 2
                'free 'free 'free 'clos
                'f 1 15 'flat
                0 'free 'free 'free))
  ;; combination
  (define test-heap3 (make-vector 14 'f))
  (test (with-heap test-heap3
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:alloc-flat 0)
          (gc:closure 'f empty)
          (gc:alloc-flat 0)
          test-heap3)
        (vector 13 'right 'free 'free
                'free 'free 'free 'free
                'clos 'f 0 'flat
                0 'free))
  (test (with-heap test-heap3
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:alloc-flat 0)
          (gc:closure 'f empty)
          (change-active-semi-space)
          (forward/loc 8)
          test-heap3)
        (vector 5 'left 'clos 'f
                0 'free 'free 'free
                'frwd 2 0 'free
                'free 'free))
  (test (with-heap test-heap3
          (init-allocator)
          (gc:alloc-flat 0)
          (gc:alloc-flat 0)
          (gc:closure 'f empty)
          (gc:alloc-flat 0)
          (gc:cons (simple-root 8) (simple-root 8))
          test-heap3)
        (vector 8 'left 'clos 'f
                0 'cons 2 2
                'free 'free 'free 'free
                'free 'free))
  )