UNB/ CS/ David Bremner/ teaching/ cs4613/ lectures/ lecture23/ incremental.rkt
#lang plai/gc2/collector
;; Originally by Yixi Zhang
;; simplified for class by db

(require racket/stream)

;; config for collection
(define step-length 10)
(define (1st-gen-size) (round (* (heap-size) 1/4)));  size of young generation
(define (2nd-gen-end) (heap-size)) ; size of old generation
(define (2nd-gen-start)
  (define (table-size) (round (* (heap-size) 1/8)))
  (+ (1st-gen-size) (table-size)));  start of old generation
(define (2nd-gen-alloc-start)  (+ 4 (2nd-gen-start))) ; start position to allocate objects in old generation
(define (alloc-word) 0) ; start of objects allocation for young generation
(define (status-word) (2nd-gen-start)) ; current status of old generation incremental gc: in/out"
(define (free-list-head)   (+ 1 (2nd-gen-start))) ; head of free list
(define (step-count-word) (+ 2 (2nd-gen-start))) ; work count for each tracing round
(define (tracing-head-word) (+ 3 (2nd-gen-start))) ;head of tracing tree
(define (table-start-word) (1st-gen-size)) ; "start position of table for cross-generation pointers"

(define debug #t)
(define (print-heap-layout)
  (when debug
    (printf "init allocator 1-gen ~a 2-gen ~a ~a ~a fl ~a\n" (1st-gen-size)
            (2nd-gen-start) (2nd-gen-end) (2nd-gen-alloc-start) (free-list-head))))

(define (init-allocator)
  (unless (= 0 (modulo (heap-size) 256))
    (error 'init-allocator "heap size is not multiple of 256"))
  (for ([i (in-range 0 (heap-size))])
    (heap-set! i 'free)); ⋮
  (heap-set! (alloc-word) 1)
  (heap-set! (status-word) 'out)
  (let ([s (2nd-gen-alloc-start)])
    (heap-set! s 'free-n)
    (heap-set! (+ 1 s) #f)
    (heap-set! (+ 2 s) (- (2nd-gen-end) s))
    (heap-set! (free-list-head) s))
  (heap-set! (step-count-word) 0)
  (heap-set! (tracing-head-word) #f)
  (heap-set! (table-start-word)
             (+ (table-start-word) 1))
  (print-heap-layout))

(module+ test
  (with-heap (make-vector 512)
    (init-allocator)))

;; gc:deref : loc -> heap-value
;; must signal an error if fl-loc doesn't point to a flat value
(define (gc:deref fl-loc)
  (cond
    [(gc:flat? fl-loc) (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)
  (case (heap-ref loc)
    [(free) (error 'track/loc "wrong tag ~s @ ~a" (heap-ref loc) loc)]
    [(frwd) (heap-ref (+ loc 1))]
    [else 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))
  (when (and (= ptr 1)
             (or (need-forwarding-pointers? hd/loc)
                 (need-forwarding-pointers? tl/loc)))
    (free-1st-gen))
  (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 (gc:cons? pr-loc)
      (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 (gc:cons? pr-loc)
      (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 grey-flat white-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 grey-pair white-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)
                (1st-gen? new))
       (table/alloc (+ loc 1) new))]
    [else (error 'set-first! "non pair @ ~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)
                (1st-gen? new))
       (table/alloc (+ loc 2) new))]
    [else (error 'set-rest! "non pair @ ~s" pr-loc)]))

;; gc:closure : heap-value (listof 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/vector ([v (in-list free-vars)])
                              (track/loc (read-root v))))
  (when (and (= next 1)
             (need-forwarding-pointers? free-vars))
    (free-1st-gen))
  (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)
               (vector-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 grey-proc white-proc) #t]
    [(frwd) (gc:closure? (heap-ref (+ loc 1)))]
    [else #f]))

(define (table/alloc pointer target)
  (define next (heap-ref (table-start-word)))
  (cond
    [(>= (+ next 2) (heap-size))
     (move/pointers (+ 1 (table-start-word)))
     (heap-set! (+ 1 (table-start-word)) pointer)
     (heap-set! (+ 2 (table-start-word)) target)
     (heap-set! (table-start-word) (+ 3 (table-start-word)))]
    [else
     (heap-set! next pointer)
     (heap-set! (+ next 1) target)
     (heap-set! (table-start-word) (+ next 2))]))

;; malloc : number[size] roots -> loc
(define (malloc n . extra-roots)
  (define addr (heap-ref (alloc-word)))
  (cond
    [(space-on-young-heap? addr n)
     (heap-set! (alloc-word) (+ addr n))
     addr]
    [else
     (collect-garbage extra-roots)
     (unless (need-forwarding-pointers? extra-roots)
       (free-1st-gen))
     (unless (space-on-young-heap? 1 n)
       (error 'alloc "object too large"))
     (heap-set! (alloc-word) (+ 1 n))
     1]))

(define (space-on-young-heap? start size)
  (<= (+ start size) (1st-gen-size)))

(define (need-forwarding-pointers? thing)
  (cond
    [(list? thing) (ormap need-forwarding-pointers? thing)]
    [(root? thing) (1st-gen? (read-root thing))]
    [(number? thing) (1st-gen? thing)]
    [else thing]))

;; collect-garbage : roots roots -> void
(define (collect-garbage . extra-roots)
  ;; young->old live objects copying
  (heap-set! (status-word) 'in)
  (make-pointers-to-2nd-gen-roots 1)
  (traverse/roots (get-root-set))
  (traverse/roots extra-roots)
  (move/pointers (+ 1 (table-start-word)))
  ;; only free/mark-white! if traversal is done
  (unless (heap-ref (tracing-head-word))
    (free/mark-white!
     (2nd-gen-alloc-start) #f #f #f))
  (heap-set! (status-word) 'out))

(define (traverse/roots thing)
  (cond
    [(list? thing)
     (for-each traverse/roots thing)]
    [(root? thing)
     (cond
       [(1st-gen? (read-root thing)) (move/roots thing)]
       [else (trace/roots-iff-white thing)])]
    [(number? thing)
     (cond
       [(1st-gen? thing) (move/ref (move/loc thing))]
       [else (when (white? thing) (push/cont thing))])]))

;; 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
    [(1st-gen? loc)
     (case (heap-ref loc)
       [(flat)
        (define new-addr (copy/alloc 2 #f #f))
        (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-addr (copy/alloc 3
                                     (heap-ref (+ loc 1))
                                     (heap-ref (+ loc 2))))
        (heap-set! new-addr 'pair)
        (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]
       [(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 '()))
        (heap-set! new-addr 'proc)
        (heap-set! (+ 1 new-addr) (heap-ref (+ 1 loc)))
        (heap-set! (+ 2 new-addr) (heap-ref (+ 2 loc)))
        (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]))

;; 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)
  (unless (2nd-gen? loc)
    (error 'move/loc "wrong location ~s" loc))
  (case (heap-ref loc)
    [(flat grey-flat white-flat) (void)]
    [(pair grey-pair white-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 grey-proc white-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)))]
    [else (error 'move/ref "wrong tag at ~a" loc)]))

(define (move/pointers loc)
  (cond
    [(or (= loc (2nd-gen-start))
         (equal? 'free (heap-ref loc)))
     (heap-set! (table-start-word) (add1 (table-start-word)))]
    [else
     (define new-addr (move/loc (heap-ref (+ loc 1))))
     (heap-set! (heap-ref loc) new-addr)
     (move/ref new-addr)
     (heap-set! loc 'free)
     (heap-set! (+ loc 1) 'free)
     (move/pointers (+ loc 2))]))

(define (free-1st-gen)
  (for ([i (in-range 1 (1st-gen-size))])
    (heap-set! i 'free)))

(define (1st-gen? loc)
  (and (>= loc 1)
       (< loc (1st-gen-size))))

(define (2nd-gen? loc)
  (and (>= loc (2nd-gen-alloc-start))
       (< loc (2nd-gen-end))))

;; 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 (make-pointers-to-2nd-gen-roots start)
  (cond
    [(= start (1st-gen-size)) (void)]
    [else
     (case (heap-ref start)
       [(flat) (make-pointers-to-2nd-gen-roots (+ start 2))]
       [(pair) (define one-loc (heap-ref (+ start 1)))
               (when (2nd-gen? one-loc) (trace/roots-iff-white one-loc))
               (define another-loc (heap-ref (+ start 2)))
               (when (2nd-gen? another-loc) (trace/roots-iff-white another-loc))
               (make-pointers-to-2nd-gen-roots (+ 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) (trace/roots-iff-white loc)))
               (make-pointers-to-2nd-gen-roots (+ start 3 fv-counts))]
       [(frwd) (define loc (heap-ref (+ start 1)))
               (traverse/roots loc)
               (case (heap-ref loc)
                 [(flat) (make-pointers-to-2nd-gen-roots (+ start 2))]
                 [(pair) (make-pointers-to-2nd-gen-roots (+ start 3))]
                 [(proc) (make-pointers-to-2nd-gen-roots (+ start 3 (heap-ref (+ loc 2))))])]
       [(free) (void)]
       [else (error 'make-pointers-to-2nd-gen-roots "wrong tag at ~a" start)])]))

;; check if any spot within free slots are taken
;; in order to detect heap-stack crash
(define (check/free loc size)
  (case (heap-ref loc)
    [(free-2) #t]
    [(free-n) (stream-andmap (lambda (x)
                               (equal? 'free (heap-ref (+ loc x))))
                             (in-range 3 size))]
    [else (error 'check/free "wrong tag @ ~s" loc)]))

(define (copy/alloc n some-roots more-roots)
  (define next (find-free-space (heap-ref (free-list-head)) #f n))
  (unless next (error 'copy/alloc "out of memory"))
  (unless (check/free next n)
    (error 'copy/alloc "heap hit tracing stack at ~s" next))
  ;; incremental collection
  (heap-set!
   (step-count-word)
   (+ n (heap-ref (step-count-word))))
  (when (>= (heap-ref (step-count-word))
            step-length)
    (traverse/incre-mark (next/cont)))
  next)

(define (trace/roots-iff-white thing)
  (cond
    [(list? thing)
     (for-each trace/roots-iff-white thing)]
    [(root? thing)
     (define loc (read-root thing))
     (when (white? loc)
       (push/cont loc))]
    [(number? thing)
     (when (white? thing)
       (push/cont thing))]))

;; 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)]))

(define (mark-white! i)
  (case (heap-ref i)
    [(flat) (heap-set! i 'white-flat)]
    [(pair) (heap-set! i 'white-pair)]
    [(proc) (heap-set! i 'white-proc)]
    [else (error 'mark-white! "wrong tag at ~a" i)]))

;; for free-n spaces, free its rest slots besides the first three
;; free-rest : location -> void
(define (free-rest loc)
  (unless (equal? 'free-n (heap-ref loc))
    (error 'free-rest "wrong tag @ ~s" loc))

  (for ([i (in-range 3 (heap-ref (+ loc 2)))])
    #:break (equal? 'free (heap-ref (+ loc i)))
    (heap-set! (+ loc i) 'free)))

(define (free/mark-white! loc prev last-start so-far)
  (unless (or (and last-start so-far)
              (not (or last-start so-far)))
    (error 'free/mark-white!
           "cumulating info are incorrect, last-start: ~s, so-far: ~s"
           last-start so-far)) ;⋮
  (cond
    [(>= loc (2nd-gen-end))
     (cond
       [(and last-start so-far)
        (cond
          [(= 1 so-far) (void)]
          [(= 2 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) so-far)
                (heap-set! (if prev (+ prev 1) (free-list-head))
                              last-start)
                (free-rest last-start)])]
       [else (void)])#;(⋮)]
    [else
     (case (heap-ref loc)
       [(flat pair proc) (mark-white! loc)
        (define length (object-length loc))
        (cond
          [(and last-start
                so-far
                (= 1 so-far))
           (free/mark-white! (+ loc length) prev #f #f)]
          [(and last-start
                so-far
                (>= so-far 2))
           (cond
             [(= 2 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) so-far)])
           (if prev
               (heap-set! (+ prev 1) last-start)
               (heap-set! (free-list-head) last-start))
           (free/mark-white! (+ loc length) last-start #f #f)]
          [else (free/mark-white! (+ loc length) prev #f #f)])#;(⋮)]
       [(white-flat white-pair white-proc
                    free free-2 free-n)
        (let* ([length (object-length loc)]
               [join (and last-start so-far)]
               [start (if join last-start loc)]
               [newlen (if join (+ so-far length) length)])
          (free/mark-white!
           (+ loc length) prev start newlen))]
       [else (error 'free/mark-white!
                    "wrong tag at ~a" loc)])]))

(define (traverse/incre-mark loc)
  (cond
    [(not loc) (heap-set! (step-count-word) 0)]
    [else
     (case (heap-ref loc)
       [(flat grey-flat)
        (mark-black loc)
        (step/count 2)
        (continue/incre-mark)] ; ⋮
       [(pair grey-pair)
        (mark-black loc)
        (step/count 3) ; 3 words scanned
        (push/cont (heap-ref (+ loc 2)))
        (push/cont (heap-ref (+ loc 1)))
        (continue/incre-mark)]
       [(proc grey-proc)
        (mark-black loc)
        (define closure-size (heap-ref (+ loc 2)))
        (step/count (+ 3 closure-size))
        (for ([i (in-range closure-size)])
          (push/cont (heap-ref (+ loc 3 i))))
        (continue/incre-mark)] ; ⋮
       [else (error 'traverse/incre-mark
                    "wrong tag at ~s" loc)])]))

(define (mark-black loc)
  (case (heap-ref loc)
    [(grey-flat)
     (heap-set! loc 'flat)]
    [(grey-pair)
     (mark-grey (heap-ref (+ loc 1)))
     (mark-grey (heap-ref (+ loc 2)))
     (heap-set! loc 'pair)]
    [(grey-proc)
     (for ([i (in-range (heap-ref (+ loc 2)))])
       (mark-grey (heap-ref (+ loc 3 i))))
     (heap-set! loc 'proc)]
    [(pair flat proc white-pair white-flat white-proc  cont) (void)]))

(define (mark-grey loc)
  (case (heap-ref loc)
    [(white-pair) (heap-set! loc 'grey-pair)]
    [(white-flat) (heap-set! loc 'grey-flat)]
    [(white-proc) (heap-set! loc 'grey-proc)]
    [(pair flat proc grey-pair grey-flat grey-proc cont) (void)]))

(define (heap-cont/check)
  (check/tag (2nd-gen-alloc-start))
  (heap-check (2nd-gen-alloc-start)))

(define (heap-check loc)
  (when (< loc (2nd-gen-end))
    (case (heap-ref loc)
      [(pair)
       (if (or (white? (heap-ref (+ loc 1)))
               (white? (heap-ref (+ loc 2))))
           (error 'heap-check "black object points to white object at ~a" loc)
           (heap-check (+ loc 3)))]
      [(flat)
       (heap-check (+ loc 2))]
      [(proc)
       (define closure-size (heap-ref (+ loc 2)))
       (case closure-size
         [(free)
          (heap-check (+ loc 3))]
         [else
          (for ([i (in-range closure-size)])
            (when (white? (heap-ref (+ loc 3 i)))
              (error 'heap-check "black object points to white object at ~a" loc)))
          (heap-check (+ loc 3 closure-size))])]
      [(white-pair grey-pair)
       (heap-check (+ loc 3))]
      [(white-flat grey-flat)
       (heap-check (+ loc 2))]
      [(white-proc grey-proc)
       (heap-check (+ loc 3 (heap-ref (+ loc 2))))]
      [(free) (heap-check (+ loc 1))]
      [else
       (cond
         [(location? loc) (check/tag-helper loc)]
         [else (error 'check "unknown tag @ ~a" loc)])])))

(define (check/tag loc)
  (when (< loc (2nd-gen-end))
    (case (heap-ref loc)
      [(pair white-pair grey-pair) (check/tag (+ loc 3))]
      [(flat white-flat grey-flat) (check/tag (+ loc 2))]
      [(proc white-proc grey-proc)
       (define closure-size (heap-ref (+ loc 2)))
       (check/tag (+ loc 3 closure-size))]
      [(free) (check/tag (+ loc 1))]
      [else
       (cond
         [(location? loc) (check/tag-helper loc)]
         [else (error 'check/tag "wrong tag at ~a" loc)])])))

(define (check/tag-helper loc)
  (for ([x (in-range loc (2nd-gen-end))])
    (unless (location? (heap-ref x))
      (error 'check/tag-helper "wrong value @ ~s, should be location" x))))

;; white? : location? -> boolean?
(define (white? loc)
  (case (heap-ref loc)
    [(white-pair white-proc white-flat)
     true]
    [else false]))

(define (next/cont)
  (define loc (heap-ref (tracing-head-word)))
  (cond
    [(equal? loc #f) #f]
    [else
     (define ptr (heap-ref loc))
     (clean/cont loc)
     ptr]))

(define (push/cont ptr)
  (mark-grey ptr)
  (when (not (member (heap-ref ptr) '(pair flat proc)))
    (define loc (heap-ref (tracing-head-word)))
     (when (and loc
                (not (equal? 'free (heap-ref (sub1 loc)))))
       (error 'push/cont "collection crashed at ~s" (sub1 loc))) ; ⋮
     (let ([top
            (if loc (sub1 loc) (- (2nd-gen-end) 1))])
       (heap-set! top ptr)
       (heap-set! (tracing-head-word) top))))

(define (continue/incre-mark)
  (if (step/finished?)
      (heap-set! (step-count-word) 0)
      (traverse/incre-mark (next/cont))))

(define (clean/cont loc)
  (define next (+ loc 1))
  (when (> next (2nd-gen-end))
    (error 'clean/cont "stack is out of bound"))

  (cond
    [(= next (2nd-gen-end)) (heap-set! (tracing-head-word) #f)]
    [else (heap-set! (tracing-head-word) (+ loc 1))])
  (heap-set! loc 'free))

(define (step/count n)
  (heap-set! (step-count-word) (- (heap-ref (step-count-word)) n)))

(define (step/finished?)
  (<= (heap-ref (step-count-word)) 0))

; validate-heap : -> void?
(define (validate-heap from to)
  (define (valid-pointer? p)
    (unless (< p (heap-size))
      (error 'validate-heap "pointer out of bounds ~a" p))
    (unless (member (heap-ref p) '(flat pair proc
                                        grey-flat grey-pair grey-proc
                                        white-flat white-pair white-proc))
      (error 'validate-heap "pointer to non-tag ~a" p)))
  (let loop ([i from])
    (when (< i to)
      (case (heap-ref i)
        [(white-flat grey-flat flat) (loop (+ i 2))]
        [(white-pair grey-pair pair)
         (valid-pointer? (heap-ref (+ i 1)))
         (valid-pointer? (heap-ref (+ i 2)))
         (loop (+ i 3))]
        [(white-proc grey-proc proc)
         (for ([j (in-range 0 (heap-ref (+ i 2)))])
           (valid-pointer? (heap-ref (+ i 3 j))))
         (loop (+ i 3 (heap-ref (+ i 2))))] ; use stored length
        [(free-n)
         (define next (heap-ref (+ i 1)))
         (or (not next) (valid-pointer? next))
         (define len (heap-ref (+ i 2)))
         (unless (and (number? len)
                      (> len 2)
                      (<= len (- (2nd-gen-end) (2nd-gen-alloc-start))))
           (error 'validate-heap "bad free list node at ~a" i))
         (loop (+ i len))]
        [(free-2) (loop (+ i 2))]
        [(free) (loop (+ i 1))]
        [else (error 'validate-heap
                     "unexpected tag ~a at ~a" (heap-ref i) i)]))); @$\vdots$@
  )

(define (validate-nursery)
  (let ([end (heap-ref 0)])
    (validate-heap 1 end)))

(define (validate-2nd-gen)
  (let ([start (2nd-gen-alloc-start)]
        [end (2nd-gen-end)])
    (validate-heap start end)))

(define (validate-both-heaps)
  (validate-nursery)
  (validate-2nd-gen)
  #t)

(module+ test
  (print-only-errors)
  (with-heap (make-vector 256)
    (init-allocator)
    (test (validate-both-heaps) #t)
    ;; fill nursery, but don't force gc
    (for ([i (in-range 31)])
      (gc:alloc-flat i))
    (test (validate-nursery) (void))
    (test (validate-2nd-gen) (void))
    (test (vector-copy (current-heap) 0 3) #(63 flat 0))
    (test (vector-copy (current-heap) 100 103) #(free-n #f 156))
    ;; force minor collection, start at beginning
    (define a-root 1)
    (with-roots (a-root)
      (gc:alloc-flat 32))
    (test (validate-both-heaps) #t)
    (test (vector-copy (current-heap) 0 3) #(3 flat 32))
    (test (vector-copy (current-heap) 100 105) #(white-flat 0 free-n #f 154))
    )
  ;; allocation tests with cons pairs
  (with-heap (make-vector 256)
    (init-allocator)
    (test (validate-nursery) (void))
    ;; fill nursery, but don't force gc
    (for ([i (in-range 9)])
      (gc:cons
       (simple-root (gc:alloc-flat (* 2 i)))
       (simple-root (gc:alloc-flat (add1 (* 2 i))))))
    (test (validate-both-heaps) #t)
    (test (vector-copy (current-heap) 0 8)  #(64 flat 0 flat 1 pair 1 3))
    (test (vector-copy (current-heap) 100 103) #(free-n #f 156))
    ;; force minor collection, start at beginning
    (define a-root 5)
    (with-roots (a-root)
      (gc:alloc-flat 32))
    (test (validate-both-heaps) #t)
    (test (vector-copy (current-heap) 0 3) #(3 flat 32))
    (test (vector-copy (current-heap) 100 107) #(white-pair 103 105 white-flat 0 white-flat 1)))

  ;; one long list
  (with-heap (make-vector 256)
    (init-allocator)
    (test (validate-both-heaps) #t)
    ;; fill nursery, but don't force gc
    (define head
      (for/fold ([lst (gc:alloc-flat empty)])
                ([i (in-range 12)])
        (gc:cons
         (simple-root (gc:alloc-flat i))
         (simple-root lst))))
    (test (validate-both-heaps) #t)
    (with-roots (head)
      (gc:alloc-flat 13))
    (test (validate-both-heaps) #t)
    (test head 100)
    (test (vector-copy (current-heap) 100 105) #(white-pair 103 105 white-flat 11))
    )

  ;; mostly fill memory with a list
  (with-heap (make-vector 256)
     (init-allocator)
     (define head
       (for/fold ([lst (gc:alloc-flat empty)])
                 ([i (in-range 37)])
         (validate-both-heaps)
         (with-roots (lst)
           (gc:cons
            (simple-root (gc:alloc-flat i))
            (simple-root lst)))))
    (test/exn 
     (with-roots (head)
       (gc:alloc-flat 'force-collection))
     "out of memory")
    )

(with-heap (make-vector 256)
  (init-allocator)
  (define head
    (for/fold ([lst (gc:alloc-flat empty)])
              ([i (in-range 30)])
      (validate-both-heaps)
      (with-roots (lst)
         (gc:cons
          (simple-root (gc:alloc-flat i))
          (simple-root lst)))))
  (validate-2nd-gen)
  (define head2
    (for/fold ([lst (gc:alloc-flat empty)])
               ([i (in-range 31 37)])
       (validate-both-heaps)
       (with-roots (lst head)
         (gc:cons
          (simple-root (gc:alloc-flat i))
          (simple-root lst)))))
  ;; force nursery collection
  (for ([i (in-range 33)])
    (with-roots (head2)
      (gc:alloc-flat i)))
  (validate-2nd-gen)
  )
)