Instructions
This years final is takehome, due at noon on April 24. Email the instructor 3 racket files as attachements, one per question. Make sure you have complete test coverage for each question. The exam is open book, you can use anything from the course web site (including textbooks) and the racket docs. The exam must be your work; you are not permitted to use generative AI. The usual UNB rules about plagiarism apply.
Scope
marks: 10
Consider a primitive {call-with {id v1} fex v2}
which is equivalent to
{{let1 id v1 fex} v2}
except that the call is dynamically scoped with respect to id
(and
only id
). Complete the callWith
case of the given
interp function so that given tests pass.
Continuations
marks: 15
Roughly speaking, the Continuation Passing Style (CPS) takes a function call like
(...stuff... (f ...args...) ...more-stuff...)
into
(f/k ...args...
(lambda (<*>)
(...stuff... <*> ...more-stuff...)))
In this question you will start with the syntactic continuation based interpreter from Lecture 17 and add two arithmetic operations to it.
Unary Decrement
To get started, let’s add support for a single argument decrement
function. I’ve written it here as --
, even though it doesn’t mutate
like --
does in most languages.
In this “inside out” style, we need to call interp on the argument, with
a last argument of “what to do next”. The function continue
will
then handle processing the evaluated argument.
Define an
Exp
variantminus1E
, and parse--
tominus1E
. You will need to add a stub tointerp
to get the code compiling and the following test passing(test (parse `{-- 2}) (minus1E (numE 2)))
Define a variant
[doMinus1K (k : Continuation)]
of theContinuation
type.Add a case for
minus1E
ininterp
(usingdoMinus1K
) anddoMinus1K
incontinue
.Your finished code should pass the following tests.
(module+ test (test (run `{-- 2}) (numV 1)) (test (run `{{lam {x} {-- x}} 3}) (numV 2)) (test (run `{{lam {y} {+ {-- y} {-- y}}} 10}) (numV 18)) (test (run `{{lam {f} {f 4}} {lam {x} {-- x}}}) (numV 3)) )
Multiplication
The second part of the question is to define (binary) multiplication. Since there are two arguments, we need to add two continuation steps. Luckily this works almost exactly the same as
Add
, andSub
, so you can copy and modify one of those cases.Your completed code (both parts) should pass the following test.
(module+ test (define fact-prog `{{lam {mkrec} {{lam {fact} ;; Call fact on 5: {fact 5}} ;; Create recursive fact {mkrec {lam {fact} {lam {n} {if0 n 1 {* n {fact {-- n}}}}}}}}} ;; mkrec: {lam {body-proc} {{lam {fX} {fX fX}} {lam {fX} {body-proc {lam {x} {{fX fX} x}}}}}}}) (test (run fact-prog) (numV 120)) )
Tests
As always, make sure you have complete test coverage.
GC
marks: 15
In this question you will implement a two space copying collector similar to the one discussed in Lecture 19. You are welcome to refer to that collector as a reference, but I cannot promise it will be easy to copy code from there. If you do use it directly, make sure to credit your sources.
A skeleton of two space garbage collector is provided. At the top of the file you can find definitions defining the heap structure. There are two slots for metadata, and the rest of the heap is divided into two spaces (conceptually). The first slot is the offset, which defines which space the system is currently allocating into, and the second slot defines where in the current space to next allocate. Doing allocation requires using both slots:
;; malloc : size -> address (define (malloc n) (when (> (+ (ptr) n) (space-size)) (gc!)) (when (> (+ (ptr) n) (space-size)) (error 'malloc "out of memory!")) (heap-set! LOC:PTR (+ (ptr) n)) (+ (heap-ref LOC:OFFSET) (- (ptr) n)))
Swap spaces
Define a function
swap-spaces!
that swaps thefrom
andto
spaces. This function should not move any allocated records, just update the heap metadata. Your function should pass the following tests.(module+ test ;; Initially, allocations are in the left half. (test/heap (make-vector (+ 4 METADATA-SIZE) '?) (gc:alloc-flat #f) (current-heap) #(2 2 flat #f ? ?)) ;; After calling swap-spaces!, allocations are in the right half (test/heap (make-vector (+ 4 METADATA-SIZE) '?) (swap-spaces!) (gc:alloc-flat #f) (current-heap) #(4 2 ? ? flat #f)) ;; Swapping twice is back to allocating in left (test/heap (make-vector (+ 4 METADATA-SIZE) '?) (swap-spaces!) (swap-spaces!) (gc:alloc-flat #f) (current-heap) #(2 2 flat #f ? ?)) )
GC for flat items
Write an initial version of the function
gc!
that can handle “flat” items (e.g. numbers, booleans). Your initial function should pass the following test.(test/heap (make-vector 12 '?) (define f1 (gc:alloc-flat #f)) (with-roots (f1) (gc!) (cons (current-heap) (map read-root (get-root-set)))) (cons #(7 2 forwarded 7 ? ? ? flat #f ? ? ?) '(7)))
GC for cons pairs
Update your function
gc!
so that it can handle cons pairs. This will require moving the two records pointed to by thecons
record.Your updated function should pass the following test.
(test/heap (make-vector 18 '?) (define c1 (gc:cons (simple-root (gc:alloc-flat #f)) (simple-root (gc:alloc-flat #t)))) (with-roots (c1) (gc!) (cons (current-heap) (map read-root (get-root-set)))) (cons #(10 7 forwarded 13 forwarded 15 forwarded 10 4 ? cons 13 15 flat #f flat #t ?) '(10)))
For this part and the next you will need to modify the API of
gc!
andmalloc
to take one or more extra arguments for roots. It’s up to you how to manage this, but I suggest using rest arguments since that will allow leaving most of the calls togc!
as they are.GC for closures
Update your function
gc!
that can handle closures. From a GC perspectiveclos
records are like variable sizedcons
records, so the main difference from the previous part is figuring how many slots to copy. Your updated function should pass the following test.(test/heap (make-vector 18 '?) (define cl1 (gc:closure 'dummy (list (simple-root (gc:alloc-flat #f))))) (with-roots (cl1) (gc!) (cons (current-heap) (map read-root (get-root-set)))) (cons #(10 6 forwarded 14 forwarded 10 1 2 ? ? clos dummy 1 14 flat #f ? ?) '(10)))
Finishing up
Make sure you have complete test coverage and test documentation.
Run some small mutator tests like fib.rkt. Include the mutator tests you run as block comments in your submission.
(module+ test (test/heap (make-vector 12 '?) (define f1 (gc:alloc-flat #f)) (gc! (simple-root f1)) (current-heap) #(7 2 forwarded 7 ? ? ? flat #f ? ? ?)))