UNB/ CS/ David Bremner/ teaching/ cs4613/ lectures/ lecture21/ bitmapped-fits.rkt
#lang plai/gc2/collector
(print-only-errors #t)

;; Define some syntax rules to make it easier to write tests

;; Test if the last two expressions are equal.
;; Takes a vector for a heap

(define-syntax (test/heap stx)
  (syntax-case stx ()
    [(test/heap heap oper ...  expected)
     (syntax-protect
      #`(with-heap heap
          (init-allocator)
          #,(syntax/loc stx
              (test (begin oper ...) expected))))]))

;; Test if one of the expressions before the last throws an exception
;; matching the last expression (a string).
;; Takes a vector for a heap
(define-syntax (test/heap/exn stx)
  (syntax-case stx ()
    [(test/heap heap oper ...  expected)
     (syntax-protect
      #`(with-heap heap
          (init-allocator)
          #,(syntax/loc stx
              (test/exn (begin oper ...) expected))))]))

;(heap-size)>=(bitmap-words)+block-width*(bitmap-words)
(define (block-width) 8)
(define (bitmap-words)
  (quotient (heap-size)
            (add1 (block-width))))

(define (init-allocator)
  (for ([i (in-range (bitmap-words))])
    (heap-set! i 0)))

(module+ test
  (with-heap (make-vector 9 '?) (test (bitmap-words) 1))
  (with-heap (make-vector 8 '?) (test (bitmap-words) 0))
  (with-heap (make-vector 13 '?) (test (bitmap-words) 1))
  (with-heap (make-vector 21 '?) (test (bitmap-words) 2))
  (with-heap (make-vector 27 '?) (test (bitmap-words) 3)))

(module+ test
  (test/heap (make-vector 9 '?)
    (init-allocator)
    (current-heap)
    #(0 ? ? ? ? ? ? ? ?)))

(define (ones k) (sub1 (expt 2 k)))
(define (update-bits! loc how-many set?)
  (define (flip bits)
    (bitwise-xor bits (ones (block-width))))
  (let* ([addr (- loc (bitmap-words))]
         [block (quotient addr (block-width))]
         [index (- addr (* block (block-width)))]
         [diff
          (arithmetic-shift (ones how-many) index)]
         [current (heap-ref block)])
    (heap-set!
     block
     (if set? (bitwise-ior current diff)
         (bitwise-and current (flip diff))))))

;; use dynamic programming to find longest run of 0s
(define (ending-at bits pos acc best)
  (cond
    [(>= pos (block-width)) best]
    [(bitwise-bit-set? bits pos)
     (ending-at bits
                (add1 pos) 0
                (max acc best))]
    [else
     (ending-at bits
                (add1 pos) (add1 acc)
                (max (add1 acc) best))]))

;; memoize the gap finding
(define max-gap
  (let ([gap-table (make-vector (expt 2 (block-width))
                                #f)])
    (lambda (bits)
      (unless (vector-ref gap-table bits)
        (vector-set! gap-table bits
                     (ending-at bits 0 0 0)))
      (vector-ref gap-table bits))))

(module+ test
  (test (max-gap 0) (block-width))
  (test (max-gap 15) (- (block-width) 4))
  (test (max-gap (sub1 (expt 2 (block-width)))) 0)
  ;; specific to 8 bit width
  (test (max-gap 145) 3))

;; assumes there is a gap
;; assumes there is a gap
(define (first-fit block-bits new-bits)
  (define (loop mask offset)
    (cond
      [(>= offset (block-width)) (error 'first-fit "internal error")]
      [(zero? (bitwise-and block-bits mask)) offset]
      [else (loop (arithmetic-shift mask 1)  (add1 offset))]))
  (loop new-bits 0))

(define (find-free-space n)
  (define (loop i)
    (define bits (heap-ref i))
    (cond
      [(> n (block-width))
       (error 'find-free-space
              "allocation > ~a" (block-width))]
      [(>= i (bitmap-words)) #f]
      [(>= (max-gap bits) n)
       (+ (* (block-width) i)
          (first-fit bits (ones n)))]
      [else (loop (add1 i))]))
  (define offset (loop 0))
  (and offset (+ (bitmap-words) offset)))
(module+ test
  (test/heap
      (make-vector 10 '?)
    (find-free-space 4)
    (bitmap-words))

  (test/heap
      (make-vector 9 '?)
    (find-free-space 4)
    (bitmap-words))

  (test/heap
      (make-vector 9 '?)
    (find-free-space 8)
    (bitmap-words))

  (test/heap/exn
      (make-vector 9 '?)
    (find-free-space 9)
    "allocation > 8")
  )

(define (malloc n . extra-roots)
  (define initial (find-free-space n))
  (unless initial
    (collect-garbage extra-roots))
  (define second (or initial (find-free-space n)))
  (unless second
    (error 'alloc "out of memory"))
  (update-bits! second n #t) ;; CHANGED
  second)
(module+ test
  (test/heap
      (make-vector 9 '?)
    (malloc 2)
    (current-heap)
    #(3 ? ? ? ? ? ? ? ?))

  (test/heap/exn
      (make-vector 4 '?)
    (malloc 2)
    (malloc 2)
    "out of memory"))

; collect-garbage : roots? -> void?
(define (collect-garbage . extra-roots)
  (validate-heap)
  (mark-white!)
  (traverse/roots (get-root-set))
  (traverse/roots extra-roots)
  (free-white!)
  (validate-heap))

(define (traverse/roots roots)
  (cond
    [(list? roots)
     (for-each traverse/roots roots)]
    [(root? roots)
     (traverse/loc (read-root roots))]
    [else
     (error 'traverse/roots
            "unexpected roots: ~a" roots)]))

(define (traverse/loc loc)
  (case (heap-ref loc)
    [(flat gray-flat cons gray-cons clos gray-clos) (void)]
    [(white-flat) (heap-set! loc 'flat)]
    [(white-cons) (heap-set! loc 'gray-cons)
     (traverse/loc (heap-ref (+ loc 1)))
     (traverse/loc (heap-ref (+ loc 2)))
     (heap-set! loc 'cons)]
    [(white-clos) (heap-set! loc 'gray-clos)
     (for ([i (in-range 0 (heap-ref (+ loc 2)))])
       (traverse/loc (heap-ref (+ loc i 3))))
     (heap-set! loc 'clos)]
    [else (error 'traverse/loc 
                 "unexpected tag: ~a" loc)]))

(define-syntax-rule (for/bitmap (loc loop) expr ...)
  (for/bitmap/proc (lambda (loc loop) expr ...)))

(define (for/bitmap/proc action)
  (for ([block (in-range 0 (bitmap-words))]
        #:unless (zero? (heap-ref block)))
    (define start (+ (bitmap-words)
                           (* block (block-width))))
    (define (loop loc)
      (define index (- loc start))
      (cond
        [(>= index (block-width)) (void)]
        [(bitwise-bit-set?
          (heap-ref block) index)
         (action loc loop)]
        [else (loop (add1 loc))]))
    (loop start)))
; validate-heap : -> void?

(define (validate-heap)
  (define (valid-pointer? p)
    (unless (< p (heap-size))
      (error 'validate-heap "pointer out of bounds ~a" p))
    (unless (member (heap-ref p) '(flat cons clos free-n free-2))
      (error 'validate-heap "pointer to non-tag ~a" p)))

  (for/bitmap (loc loop)
    (case (heap-ref loc)
      [(flat) (loop (+ loc 2))]
      [(cons)
       (valid-pointer? (heap-ref (+ loc 1)))
       (valid-pointer? (heap-ref (+ loc 2)))
       (loop (+ loc 3))]
      [(clos)
       (for ([j (in-range 0 (heap-ref (+ loc 2)))])
         (valid-pointer? (heap-ref (+ loc 3 j))))
       (loop (+ loc 3 (heap-ref (+ loc 2))))]
      [else (error 'validate-heap "unexpected at pos. ~a in ~a" loc
                   (current-heap))])))

(module+ test
  (with-heap
    (make-vector 9 '?)
    (init-allocator)
    (test (validate-heap) (void)))
  (with-heap
    (make-vector 9 '?)
    (init-allocator)
    (gc:alloc-flat 42)
    (test (validate-heap) (void)))
  (with-heap
    (make-vector 18 '?)
    (init-allocator)
    (gc:alloc-flat 42)
    (test (validate-heap) (void)))
  (with-heap
    (make-vector 18 '?)
    (init-allocator)
    (for ([i (in-range 7)])
      (gc:alloc-flat i))
    (test (validate-heap) (void)))
  )

(define (mark-white!)
  (for/bitmap (loc loop)
    (case (heap-ref loc)
      [(cons)
       (heap-set! loc 'white-cons)
       (loop (+ loc 3))]
      [(flat)
       (heap-set! loc 'white-flat)
       (loop (+ loc 2))]
      [(clos)
       (heap-set! loc 'white-clos)
       (loop (+ loc 3 (heap-ref (+ loc 2))))]
      [else (error 'mark-white!
                   "unexpected tag: ~a" loc)])))

(module+ test
  (with-heap
    (make-vector 9 '?)
    (init-allocator)
    (mark-white!)
    (test (current-heap)  #(0 ? ? ? ? ? ? ? ?)))

  (with-heap
    (make-vector 9 '?)
    (init-allocator)
    (gc:alloc-flat 42)
    (gc:alloc-flat 43)
    (mark-white!)
    (test (current-heap) #(15 white-flat 42 white-flat 43 ? ? ? ?)))

  (with-heap
    (make-vector 18 '?)
    (init-allocator)
    (for ([i (in-range 7)])
      (gc:alloc-flat i))
    (mark-white!)
    (test (current-heap)
          #(255 63 white-flat 0 white-flat 1 white-flat 2 white-flat 3 white-flat 4 white-flat 5 white-flat 6 ? ?)))
  )

(define (free-white!)
  (for/bitmap (loc loop)
    (define (free! width)
      (update-bits! loc width #f) (loop (+ loc width)))
    (case (heap-ref loc)
      [(white-clos) (free! (+ 3 (heap-ref (+ loc 2))))]
      [(clos) (loop (+ loc 3 (heap-ref (+ loc 2))))]
      [(white-flat) (free! 2)]
      [(flat) (loop (+ loc 2))]
      [(white-cons) (free! 3)]
      [(cons) (loop (+ loc 3))]
      [else (error 'free-white!
                   "bad tag at ~a" loc)])))

; gc:flat? : location? -> boolean?
(define (gc:flat? addr)
  (equal? (heap-ref addr) 'flat))
; gc:deref location? -> heap-value?
(define (gc:deref addr)
  (unless (gc:flat? addr)
    (error 'gc:flat? "not a flat: ~a" addr))
  (heap-ref (+ addr 1)))

; gc:cons? : location? -> boolean?
(define (gc:cons? addr)
  (equal? (heap-ref addr) 'cons))
; gc:first : location? -> location?
(define (gc:first addr)
  (unless (gc:cons? addr)
    (error 'gc:first "not a cons: ~a" addr))
  (heap-ref (+ addr 1)))
; gc:rest : location? -> location?
(define (gc:rest addr)
  (unless (gc:cons? addr)
    (error 'gc:rest "not a cons: ~a" addr))
  (heap-ref (+ addr 2)))

; gc:set-first! : location? location? -> void?
(define (gc:set-first! addr v)
  (unless (gc:cons? addr)
    (error 'gc:set-first! "not a cons: ~a" addr))
  (heap-set! (+ addr 1) v))
; gc:set-rest! : location? location? -> void
(define (gc:set-rest! addr v)
  (unless (gc:cons? addr)
    (error 'gc:set-rest! "not a cons: ~a" addr))
  (heap-set! (+ addr 2) v))

; gc:closure? : location? -> boolean?
(define (gc:closure? addr)
  (equal? (heap-ref addr) 'clos))
; gc:closure-code-ptr : location? -> heap-value?
(define (gc:closure-code-ptr addr)
  (unless (gc:closure? addr)
    (error 'gc:closure-code-ptr "not a closure: ~a" addr))
  (heap-ref (+ addr 1)))
; gc:closure-env-ref : location? integer? -> location?
(define (gc:closure-env-ref addr i)
  (unless (gc:closure? addr)
    (error 'gc:closure-env-ref "not a closure: ~a" addr))
  (heap-ref (+ addr 3 i)))

; gc:alloc-flat : heap-value? -> location?
(define (gc:alloc-flat v)
  (define address (malloc 2))
  (heap-set! address 'flat)
  (heap-set! (+ 1 address) v)
  address)
; gc:cons : root? root? -> location?
(define (gc:cons v1 v2)
  (define address (malloc 3 v1 v2))
  (heap-set! address 'cons)
  (heap-set! (+ address 1) (read-root v1))
  (heap-set! (+ address 2) (read-root v2))
  address)

; gc:closure : heap-value? (vectorof location?) -> location?
(define (gc:closure code-ptr free-variables)
  (define address
    (malloc (+ 3 (length free-variables))
            free-variables #f))
  (heap-set! address 'clos)
  (heap-set! (+ address 1) code-ptr)
  (heap-set! (+ address 2) (length free-variables))
  (for ([i (in-range 0 (length free-variables))]
        [f (in-list free-variables)])
    (heap-set! (+ address 3 i) (read-root f)))
  address)


(module+ test
  (test/heap
      (make-vector 9 '?)
    (gc:alloc-flat 1)
    (current-heap)
    #(3 flat 1 ? ? ? ? ? ?))

  (test/heap
      (make-vector 9 '?)
    (gc:alloc-flat 1)
    (find-free-space 2)
    3)

  (test/heap
      (make-vector 9 '?)
    (gc:alloc-flat 1)
    (gc:alloc-flat 2)
    (current-heap)
    #(15 flat 1 flat 2 ? ? ? ?))

  (with-heap (make-vector 9 '?)
    (init-allocator)
    (gc:cons (simple-root (gc:alloc-flat 'first))
             (simple-root (gc:alloc-flat 'rest)))
    (test (current-heap) #(127 flat first flat rest cons 1 3 ? )))

  (with-heap (make-vector 9 '?)
    (init-allocator)
    (gc:closure 'code (list (simple-root (gc:alloc-flat 'value))))
    (test (current-heap) #(63 flat value clos code 1 1 ? ?)))

  (with-heap (make-vector 9 '?)
    (init-allocator)
    (collect-garbage)
    (test (vector-copy (current-heap) 0 4) #(0 ? ? ?))
    (gc:alloc-flat 42)
    (test (vector-copy (current-heap) 0 6) #(3 flat 42 ? ? ?))
    (collect-garbage)
    (test (vector-copy (current-heap) 0 4) #(0 white-flat 42 ?))
    (gc:closure 'code (list (simple-root (gc:alloc-flat 'value))))
    (test (current-heap) #(63 flat value clos code 1 1 ? ?))
    (collect-garbage)
    (test (vector-copy (current-heap) 0 4) #(0 white-flat value white-clos))
    (gc:cons (simple-root (gc:alloc-flat 'first))
             (simple-root (gc:alloc-flat 'rest)))
    (test (current-heap)  #(127 flat first flat rest cons 1 3 ?))
    (collect-garbage)
    (test  (current-heap)  #(0 white-flat first white-flat rest white-cons 1 3 ?))
    ))

(module+ test

  (with-heap (make-vector 4)
    (init-allocator)
    (test/exn
     (gc:cons
      (simple-root (gc:alloc-flat #f))
      (simple-root (gc:alloc-flat #t)))
     "out of memory"))

  (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))
     "not a flat"))

  (with-heap (make-vector 1000)
    (init-allocator)
    (test/exn
     (let ([flat-addr (gc:alloc-flat #f)])
       (gc:first flat-addr))
     "not a cons"))

  (with-heap (make-vector 1000)
    (init-allocator)
    (test/exn
     (let ([flat-addr (gc:alloc-flat #f)])
       (gc:closure-code-ptr flat-addr))
     "not a closure"))

  (with-heap (make-vector 1000)
    (init-allocator)
    (test/exn
     (let ([flat-addr (gc:alloc-flat #f)])
       (gc:closure-env-ref flat-addr 0))
     "not a closure"))
  )