add in documentation for plai/gc2
This commit is contained in:
parent
49ffca8fc2
commit
9ef439d31f
229
collects/plai/scribblings/collector.scrbl
Normal file
229
collects/plai/scribblings/collector.scrbl
Normal file
|
@ -0,0 +1,229 @@
|
|||
#lang scribble/doc
|
||||
@(require scribble/manual
|
||||
"rkt-exports.rkt"
|
||||
"plai-exports.rkt"
|
||||
"lang-names.rkt"
|
||||
(for-syntax scheme)
|
||||
(for-label (except-in scheme
|
||||
error printf)
|
||||
(prefix-in scheme:
|
||||
scheme)
|
||||
(only-in plai/main
|
||||
type-case define-type error
|
||||
test test/pred test/exn test/regexp
|
||||
abridged-test-output
|
||||
plai-catch-test-exn
|
||||
halt-on-errors print-only-errors
|
||||
test-inexact-epsilon plai-ignore-exn-strings
|
||||
plai-all-test-results)
|
||||
(only-in plai/collector
|
||||
root?
|
||||
heap-size
|
||||
location?
|
||||
heap-value?
|
||||
heap-set! heap-ref with-heap
|
||||
get-root-set read-root set-root!
|
||||
procedure-roots)
|
||||
plai/scribblings/fake-collector
|
||||
plai/scribblings/fake-mutator
|
||||
plai/scribblings/fake-web
|
||||
plai/random-mutator
|
||||
(only-in plai/web
|
||||
no-web-browser
|
||||
static-files-path)
|
||||
(only-in plai/mutator
|
||||
set-first!
|
||||
set-rest!
|
||||
import-primitives
|
||||
test/location=?
|
||||
test/value=?
|
||||
printf)))
|
||||
|
||||
@title[#:tag "collector"]{@COLLECT-LANG}
|
||||
|
||||
@defmodulelang[plai/collector]
|
||||
|
||||
@COLLECT-LANG is based on @seclink["plai-scheme"]{PLAI Scheme}. It provides
|
||||
additional procedures and syntax for writing garbage collectors.
|
||||
|
||||
@section{Garbage Collector Interface}
|
||||
|
||||
The @COLLECT-LANG language provides the following functions that provide access
|
||||
to the heap and root set:
|
||||
|
||||
@defproc[(heap-size) exact-nonnegative-integer?]{
|
||||
|
||||
Returns the size of the heap. The size of the heap is specified by the mutator
|
||||
that uses the garbage collector. See @racket[allocator-setup] for more
|
||||
information.
|
||||
}
|
||||
|
||||
@defproc[(location? [v any/c])
|
||||
boolean?]{
|
||||
Determines if @racket[v] is an integer between @racket[0] and
|
||||
@racket[(- (heap-size) 1)] inclusive.
|
||||
}
|
||||
|
||||
@defproc[(root? [v any/c])
|
||||
boolean?]{
|
||||
Determines if @racket[v] is a root.
|
||||
}
|
||||
|
||||
@defproc[(heap-value? [v any/c]) boolean?]{
|
||||
A value that may be stored on the heap. Roughly corresponds to the contract
|
||||
@racket[(or/c boolean? number? procedure? symbol? empty?)].
|
||||
}
|
||||
|
||||
|
||||
@defproc[(heap-set! (loc location?) (val heap-value?)) void?]{
|
||||
Sets the value at @racket[_loc] to @racket[_val].
|
||||
}
|
||||
|
||||
@defproc[(heap-ref (loc location?)) heap-value?]{
|
||||
Returns the value at @racket[_loc].
|
||||
}
|
||||
|
||||
@defform/subs[(get-root-set id ...)()]{
|
||||
Returns the current roots as a list. Local roots are created for the
|
||||
identifiers @racket[_id] as well.
|
||||
}
|
||||
|
||||
@defproc[(read-root (root root?)) location?]{
|
||||
Returns the location of @racket[_root].
|
||||
}
|
||||
|
||||
@defproc[(set-root! (root root?) (loc location?)) void?]{
|
||||
Updates the root to reference the given location.
|
||||
}
|
||||
|
||||
@defproc[(procedure-roots (proc procedure?)) (listof root?)]{
|
||||
Given a closure stored on the heap, returns a list of the roots reachable
|
||||
from the closure's environment. If @racket[_proc] is not reachable, the
|
||||
empty list is returned.
|
||||
}
|
||||
|
||||
@defform[(with-heap heap-expr body-expr ...)
|
||||
#:contracts ([heap-expr (vectorof heap-value?)])]{
|
||||
Evaluates each of the @racket[body-expr]s in a context where
|
||||
the value of @racket[heap-expr] is used as the heap. Useful in
|
||||
tests:
|
||||
@racketblock[
|
||||
(test (with-heap (make-vector 20)
|
||||
(init-allocator)
|
||||
(gc:deref (gc:alloc-flat 2)))
|
||||
2)
|
||||
]}
|
||||
|
||||
@defform[(with-roots roots-expr expr1 expr2 ...)
|
||||
#:contracts ([roots-expr (listof location?)])]{
|
||||
Evaluates each of @racket[expr1] and the @racket[expr2]s in
|
||||
in a context with the result of @racket[roots-expr]
|
||||
as additional roots.
|
||||
|
||||
This function is intended to be used in test suites
|
||||
for collectors. Since your test suites are not running
|
||||
in the @racketmod[plai/mutator] language, @racket[get-root-set]
|
||||
returns a list consisting only of the roots it created,
|
||||
not all of the other roots it normally would return.
|
||||
Use this function to note specific locations as roots
|
||||
and set up better tests for your GC.
|
||||
|
||||
@racketblock[
|
||||
(test (with-heap (make-vector 4)
|
||||
(define f1 (gc:alloc-flat 1))
|
||||
(define c1 (gc:cons f1 f1))
|
||||
(with-roots (list c1)
|
||||
(gc:deref
|
||||
(gc:first
|
||||
(gc:cons f1 f1)))))
|
||||
1)]
|
||||
|
||||
}
|
||||
|
||||
@section{Garbage Collector Exports}
|
||||
|
||||
@declare-exporting[#:use-sources (plai/scribblings/fake-collector)]
|
||||
|
||||
A garbage collector must define the following functions:
|
||||
|
||||
@defproc[(init-allocator) void?]{
|
||||
|
||||
@racket[init-allocator] is called before all other procedures by a
|
||||
mutator. Place any requisite initialization code here.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:deref (loc location?)) heap-value?]{
|
||||
|
||||
Given the location of a flat Scheme value, this procedure should return that
|
||||
value. If the location does not hold a flat value, this function should signal
|
||||
an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:alloc-flat (val heap-value?)) location?]{
|
||||
|
||||
This procedure should allocate a flat Scheme value (number, symbol, boolean,
|
||||
closure or empty list) on the heap, returning its location (a number). The
|
||||
value should occupy a single heap cell, though you may use additional space to
|
||||
store a tag, etc. You are also welcome to pre-allocate common constants (e.g.,
|
||||
the empty list). This procedure may need to perform a garbage-collection. If
|
||||
there is still insufficient space, it should signal an error.
|
||||
|
||||
Note that closures are flat values. The environment of a closure is internally
|
||||
managed, but contains references to values on the heap. Therefore, during
|
||||
garbage collection, the environment of reachable closures must be updated. The
|
||||
language exposes the environment via the @racket[procedure-roots] function.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:cons (first location?) (rest location?)) location?]{
|
||||
|
||||
Given the location of the @racket[_first] and @racket[_rest] values, this
|
||||
procedure must allocate a cons cell on the heap. If there is insufficient
|
||||
space to allocate the cons cell, it should signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:first (cons-cell location?)) location?]{
|
||||
|
||||
If the given location refers to a cons cell, this should return the first
|
||||
field. Otherwise, it should signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:rest (cons-cell location?)) location?]{
|
||||
|
||||
If the given location refers to a cons cell, this should return the rest
|
||||
field. Otherwise, it should signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:set-first! (cons-cell location?) (first-value location?)) void?]{
|
||||
|
||||
If @racket[_cons-cell] refers to a cons cell, set the head of the cons cell to
|
||||
@racket[_first-value]. Otherwise, signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:set-rest! (cons-cell location?) (rest-value location?)) void?]{
|
||||
|
||||
If @racket[_cons-cell] refers to a cons cell, set the tail of the cons cell to
|
||||
@racket[_rest-value]. Otherwise, signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:cons? (loc location?)) boolean?]{
|
||||
|
||||
|
||||
Returns @racket[true] if @racket[_loc] refers to a cons cell. This function
|
||||
should never signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:flat? (loc location?)) boolean?]{
|
||||
|
||||
Returns @racket[true] if @racket[_loc] refers to a flat value. This function
|
||||
should never signal an error.
|
||||
|
||||
}
|
256
collects/plai/scribblings/collector2.scrbl
Normal file
256
collects/plai/scribblings/collector2.scrbl
Normal file
|
@ -0,0 +1,256 @@
|
|||
#lang scribble/doc
|
||||
@(require scribble/manual
|
||||
"rkt-exports.rkt"
|
||||
"plai-exports.rkt"
|
||||
"lang-names.rkt"
|
||||
(for-syntax scheme)
|
||||
(for-label (except-in scheme
|
||||
error printf)
|
||||
(prefix-in scheme:
|
||||
scheme)
|
||||
(only-in plai/main
|
||||
type-case define-type error
|
||||
test test/pred test/exn test/regexp
|
||||
abridged-test-output
|
||||
plai-catch-test-exn
|
||||
halt-on-errors print-only-errors
|
||||
test-inexact-epsilon plai-ignore-exn-strings
|
||||
plai-all-test-results)
|
||||
(only-in plai/collector
|
||||
root?
|
||||
heap-size
|
||||
location?
|
||||
heap-value?
|
||||
heap-set! heap-ref with-heap
|
||||
get-root-set read-root set-root!
|
||||
procedure-roots)
|
||||
plai/scribblings/fake-collector
|
||||
plai/scribblings/fake-mutator
|
||||
plai/scribblings/fake-web
|
||||
plai/random-mutator
|
||||
(only-in plai/web
|
||||
no-web-browser
|
||||
static-files-path)
|
||||
(only-in plai/mutator
|
||||
set-first!
|
||||
set-rest!
|
||||
import-primitives
|
||||
test/location=?
|
||||
test/value=?
|
||||
printf)))
|
||||
|
||||
|
||||
@title[#:tag "gc2-collector"]{GC Collector, 2}
|
||||
|
||||
@defmodulelang[plai/gc2/collector]
|
||||
|
||||
@COLLECT-LANG is based on @seclink["plai-scheme"]{PLAI}. It provides
|
||||
additional procedures and syntax for writing garbage collectors.
|
||||
|
||||
@section{Garbage Collector Interface for GC2}
|
||||
|
||||
The @COLLECT-LANG language provides the following functions that provide access
|
||||
to the heap and root set:
|
||||
|
||||
@defproc[(heap-size) exact-nonnegative-integer?]{
|
||||
|
||||
Returns the size of the heap. The size of the heap is specified by the mutator
|
||||
that uses the garbage collector. See @racket[allocator-setup] for more
|
||||
information.
|
||||
}
|
||||
|
||||
@defproc[(location? [v any/c])
|
||||
boolean?]{
|
||||
Determines if @racket[v] is an integer between @racket[0] and
|
||||
@racket[(- (heap-size) 1)] inclusive.
|
||||
}
|
||||
|
||||
@defproc[(root? [v any/c])
|
||||
boolean?]{
|
||||
Determines if @racket[v] is a root.
|
||||
}
|
||||
|
||||
@defproc[(heap-value? [v any/c]) boolean?]{
|
||||
A value that may be stored on the heap. Roughly corresponds to the contract
|
||||
@racket[(or/c boolean? number? procedure? symbol? empty?)].
|
||||
}
|
||||
|
||||
|
||||
@defproc[(heap-set! (loc location?) (val heap-value?)) void?]{
|
||||
Sets the value at @racket[_loc] to @racket[_val].
|
||||
}
|
||||
|
||||
@defproc[(heap-ref (loc location?)) heap-value?]{
|
||||
Returns the value at @racket[_loc].
|
||||
}
|
||||
|
||||
@defform/subs[(get-root-set id ...)()]{
|
||||
Returns the current roots as a list. Local roots are created for the
|
||||
identifiers @racket[_id] as well.
|
||||
}
|
||||
|
||||
@defproc[(read-root (root root?)) location?]{
|
||||
Returns the location of @racket[_root].
|
||||
}
|
||||
|
||||
@defproc[(set-root! (root root?) (loc location?)) void?]{
|
||||
Updates the root to reference the given location.
|
||||
}
|
||||
|
||||
@defproc[(procedure-roots (proc procedure?)) (listof root?)]{
|
||||
Given a closure stored on the heap, returns a list of the roots reachable
|
||||
from the closure's environment. If @racket[_proc] is not reachable, the
|
||||
empty list is returned.
|
||||
}
|
||||
|
||||
@defform[(with-heap heap-expr body-expr ...)
|
||||
#:contracts ([heap-expr (vectorof heap-value?)])]{
|
||||
Evaluates each of the @racket[body-expr]s in a context where
|
||||
the value of @racket[heap-expr] is used as the heap. Useful in
|
||||
tests:
|
||||
@racketblock[
|
||||
(test (with-heap (make-vector 20)
|
||||
(init-allocator)
|
||||
(gc:deref (gc:alloc-flat 2)))
|
||||
2)
|
||||
]}
|
||||
|
||||
@defform[(with-roots roots-expr expr1 expr2 ...)
|
||||
#:contracts ([roots-expr (listof location?)])]{
|
||||
Evaluates each of @racket[expr1] and the @racket[expr2]s in
|
||||
in a context with the result of @racket[roots-expr]
|
||||
as additional roots.
|
||||
|
||||
This function is intended to be used in test suites
|
||||
for collectors. Since your test suites are not running
|
||||
in the @racketmod[plai/gc2/mutator] language, @racket[get-root-set]
|
||||
returns a list consisting only of the roots it created,
|
||||
not all of the other roots it normally would return.
|
||||
Use this function to note specific locations as roots
|
||||
and set up better tests for your GC.
|
||||
|
||||
@racketblock[
|
||||
(test (with-heap (make-vector 4)
|
||||
(define f1 (gc:alloc-flat 1))
|
||||
(define c1 (gc:cons f1 f1))
|
||||
(with-roots (list c1)
|
||||
(gc:deref
|
||||
(gc:first
|
||||
(gc:cons f1 f1)))))
|
||||
1)]
|
||||
|
||||
}
|
||||
|
||||
@section{Garbage Collector Exports for GC2}
|
||||
|
||||
@declare-exporting[#:use-sources (plai/scribblings/fake-collector2)]
|
||||
|
||||
A garbage collector must define the following functions:
|
||||
|
||||
@defproc[(init-allocator) void?]{
|
||||
|
||||
@racket[init-allocator] is called before all other procedures by a
|
||||
mutator. Place any requisite initialization code here.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:deref (loc location?)) heap-value?]{
|
||||
|
||||
Given the location of a flat value, this procedure should return that
|
||||
value. If the location does not hold a flat value, this function should signal
|
||||
an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:alloc-flat (val heap-value?)) location?]{
|
||||
|
||||
This procedure should allocate a flat value (number, symbol, boolean,
|
||||
closure or empty list) on the heap, returning its location (a number). The
|
||||
value should occupy a single heap cell, though you may use additional space to
|
||||
store a tag, etc. You are also welcome to pre-allocate common constants (e.g.,
|
||||
the empty list). This procedure may need to perform a garbage-collection. If
|
||||
there is still insufficient space, it should signal an error.
|
||||
|
||||
Note that closures are flat values. The environment of a closure is internally
|
||||
managed, but contains references to values on the heap. Therefore, during
|
||||
garbage collection, the environment of reachable closures must be updated. The
|
||||
language exposes the environment via the @racket[procedure-roots] function.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:cons (first location?) (rest location?)) location?]{
|
||||
|
||||
Given the location of the @racket[_first] and @racket[_rest] values, this
|
||||
procedure must allocate a cons cell on the heap. If there is insufficient
|
||||
space to allocate the cons cell, it should signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:first (cons-cell location?)) location?]{
|
||||
|
||||
If the given location refers to a cons cell, this should return the first
|
||||
field. Otherwise, it should signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:rest (cons-cell location?)) location?]{
|
||||
|
||||
If the given location refers to a cons cell, this should return the rest
|
||||
field. Otherwise, it should signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:set-first! (cons-cell location?) (first-value location?)) void?]{
|
||||
|
||||
If @racket[_cons-cell] refers to a cons cell, set the head of the cons cell to
|
||||
@racket[_first-value]. Otherwise, signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:set-rest! (cons-cell location?) (rest-value location?)) void?]{
|
||||
|
||||
If @racket[_cons-cell] refers to a cons cell, set the tail of the cons cell to
|
||||
@racket[_rest-value]. Otherwise, signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:cons? (loc location?)) boolean?]{
|
||||
|
||||
|
||||
Returns @racket[true] if @racket[_loc] refers to a cons cell. This function
|
||||
should never signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:flat? (loc location?)) boolean?]{
|
||||
|
||||
Returns @racket[true] if @racket[_loc] refers to a flat value. This function
|
||||
should never signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:closure [code-ptr heap-value?] [free-vars (vectorof location?)])
|
||||
location?]{
|
||||
Allocates a closure with 'code-ptr' and the free variables
|
||||
in the vector 'free-vars'.
|
||||
}
|
||||
@defproc[(gc:closure-code-ptr [loc location?]) 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 @racket[_code-ptr] for that closure.
|
||||
}
|
||||
|
||||
@defproc[(gc:closure-env-ref [loc location?] [i exact-nonnegative-integer?])
|
||||
location?]{
|
||||
Given a location returned from an earlier allocation, check
|
||||
to see if it is a closure; if not signal an error. Uf so,
|
||||
return the @racket[i]th variable in the closure (counting from 0).
|
||||
}
|
||||
|
||||
@defproc[(gc:closure? [loc location?]) boolean?]{
|
||||
Determine if a previously allocated location
|
||||
holds a closure. This function will be called only
|
||||
with locations previous returned from an allocating
|
||||
function or passed to @racket[set-root!]. It should
|
||||
never signal an error.
|
||||
}
|
17
collects/plai/scribblings/fake-collector2.rkt
Normal file
17
collects/plai/scribblings/fake-collector2.rkt
Normal file
|
@ -0,0 +1,17 @@
|
|||
#lang scheme
|
||||
(provide (all-defined-out))
|
||||
|
||||
(define init-allocator #f)
|
||||
(define gc:deref #f)
|
||||
(define gc:alloc-flat #f)
|
||||
(define gc:cons #f)
|
||||
(define gc:first #f)
|
||||
(define gc:rest #f)
|
||||
(define gc:set-first! #f)
|
||||
(define gc:set-rest! #f)
|
||||
(define gc:cons? #f)
|
||||
(define gc:flat? #f)
|
||||
(define gc:closure #f)
|
||||
(define gc:closure-code-ptr #f)
|
||||
(define gc:closure-env-ref #f)
|
||||
(define gc:closure? #f)
|
3
collects/plai/scribblings/fake-mutator2.rkt
Normal file
3
collects/plai/scribblings/fake-mutator2.rkt
Normal file
|
@ -0,0 +1,3 @@
|
|||
#lang scheme
|
||||
(provide (all-defined-out))
|
||||
(define-syntax allocator-setup #f)
|
6
collects/plai/scribblings/lang-names.rkt
Normal file
6
collects/plai/scribblings/lang-names.rkt
Normal file
|
@ -0,0 +1,6 @@
|
|||
#lang racket/base
|
||||
(provide PLAI-LANG COLLECT-LANG MUTATE-LANG WEB-LANG)
|
||||
(define PLAI-LANG "PLAI Scheme")
|
||||
(define COLLECT-LANG "GC Collector Scheme")
|
||||
(define MUTATE-LANG "GC Mutator Scheme")
|
||||
(define WEB-LANG "Web Application Scheme")
|
260
collects/plai/scribblings/mutator.scrbl
Normal file
260
collects/plai/scribblings/mutator.scrbl
Normal file
|
@ -0,0 +1,260 @@
|
|||
#lang scribble/doc
|
||||
@(require scribble/manual
|
||||
"rkt-exports.rkt"
|
||||
"plai-exports.rkt"
|
||||
"lang-names.rkt"
|
||||
(for-syntax scheme)
|
||||
(for-label (except-in scheme
|
||||
error printf)
|
||||
(prefix-in scheme:
|
||||
scheme)
|
||||
(only-in plai/main
|
||||
type-case define-type error
|
||||
test test/pred test/exn test/regexp
|
||||
abridged-test-output
|
||||
plai-catch-test-exn
|
||||
halt-on-errors print-only-errors
|
||||
test-inexact-epsilon plai-ignore-exn-strings
|
||||
plai-all-test-results)
|
||||
(only-in plai/collector
|
||||
root?
|
||||
heap-size
|
||||
location?
|
||||
heap-value?
|
||||
heap-set! heap-ref with-heap
|
||||
get-root-set read-root set-root!
|
||||
procedure-roots)
|
||||
plai/scribblings/fake-collector
|
||||
plai/scribblings/fake-mutator
|
||||
plai/scribblings/fake-web
|
||||
plai/random-mutator
|
||||
(only-in plai/web
|
||||
no-web-browser
|
||||
static-files-path)
|
||||
(only-in plai/mutator
|
||||
set-first!
|
||||
set-rest!
|
||||
import-primitives
|
||||
test/location=?
|
||||
test/value=?
|
||||
printf)))
|
||||
|
||||
@title[#:tag "mutator"]{@MUTATE-LANG}
|
||||
|
||||
@defmodulelang[plai/mutator]
|
||||
|
||||
The @MUTATE-LANG language is used to test garbage collectors written with the
|
||||
@secref["collector"] language. Since collectors support a subset of Scheme's
|
||||
values, the @MUTATE-LANG language supports a subset of procedures and syntax.
|
||||
In addition, many procedures that can be written in the mutator are omitted as
|
||||
they make good test cases. Therefore, the mutator language provides only
|
||||
primitive procedures, such as @racket[+], @racket[cons], etc.
|
||||
|
||||
@section{Building Mutators}
|
||||
|
||||
@declare-exporting[#:use-sources (plai/scribblings/fake-mutator)]
|
||||
|
||||
The first expression of a mutator must be:
|
||||
|
||||
@defform/subs[
|
||||
(allocator-setup collector-module
|
||||
heap-size)
|
||||
([heap-size exact-nonnegative-integer?])]{
|
||||
|
||||
@racket[_collector-module] specifies the path to the garbage collector that the
|
||||
mutator should use. The collector must be written in the @COLLECT-LANG
|
||||
language.
|
||||
}
|
||||
|
||||
The rest of a mutator module is a sequence of definitions, expressions and test
|
||||
cases. The @MUTATE-LANG language transforms these definitions and statements to
|
||||
use the collector specified in @racket[allocator-setup]. In particular, many
|
||||
of the primitive forms, such as @racket[cons] map directly to procedures such
|
||||
as @racket[gc:cons], written in the collector.
|
||||
|
||||
@section{Mutator API}
|
||||
|
||||
The @MUTATE-LANG language supports the following procedures and syntactic
|
||||
forms:
|
||||
|
||||
@(define-syntax (document/lift stx)
|
||||
(syntax-case stx ()
|
||||
[(_ a ...)
|
||||
(with-syntax ([(doc ...)
|
||||
(map (λ (a)
|
||||
(with-syntax ([a a]
|
||||
[rkt:a (string->symbol (format "rkt:~a" (syntax-e a)))])
|
||||
#'@defidform[a]{Just like Racket's @|rkt:a|.}))
|
||||
(syntax->list #'(a ...)))])
|
||||
|
||||
#'(begin
|
||||
doc ...))]))
|
||||
|
||||
@document/lift[if and or cond case define-values let let-values let* set! quote error begin]
|
||||
|
||||
@defform[(define (id arg-id ...) body-expression ...+)]{
|
||||
Just like Racket's @racket[define], except restricted to the simpler form
|
||||
above.
|
||||
}
|
||||
@deftogether[(@defform[(lambda (arg-id ...) body-expression ...+)]{}
|
||||
@defform[(λ (arg-id ...) body-expression ...+)]{})]{
|
||||
Just like Racket's @racket[lambda] and @racket[λ], except restricted to the
|
||||
simpler form above.
|
||||
}
|
||||
|
||||
@document/lift[add1 sub1 zero? + - * / even? odd? = < > <= >=
|
||||
symbol? symbol=? number? boolean? empty? eq?]
|
||||
|
||||
@defproc[(cons [hd any/c] [tl any/c]) cons?]{
|
||||
Constructs a (mutable) pair.
|
||||
}
|
||||
@defproc[(cons? [v any/c]) boolean?]{
|
||||
Returns @racket[#t] when given a value created by @racket[cons],
|
||||
@racket[#f] otherwise.
|
||||
}
|
||||
@defproc[(first [c cons?]) any/c]{
|
||||
Extracts the first component of @racket[c].
|
||||
}
|
||||
@defproc[(rest [c cons?]) any/c]{
|
||||
Extracts the rest component of @racket[c].
|
||||
}
|
||||
|
||||
@defproc[(set-first! [c cons?] [v any/c])
|
||||
void]{
|
||||
Sets the @racket[first] of the cons cell @racket[c].
|
||||
}
|
||||
|
||||
@defproc[(set-rest! [c cons?] [v any/c])
|
||||
void]{
|
||||
Sets the @racket[rest] of the cons cell @racket[c].
|
||||
}
|
||||
|
||||
@defidform[empty]{
|
||||
The identifier @racket[empty] is defined to invoke
|
||||
@racket[(gc:alloc-flat empty)] wherever it is used.
|
||||
}
|
||||
|
||||
@defidform[print-only-errors]{
|
||||
Behaves like PLAI's @|plai:print-only-errors|.
|
||||
}
|
||||
|
||||
@defidform[halt-on-errors]{
|
||||
Behaves like PLAI's @|plai:halt-on-errors|.
|
||||
}
|
||||
|
||||
Other common procedures are left undefined as they can be defined in
|
||||
terms of the primitives and may be used to test collectors.
|
||||
|
||||
Additional procedures from @racketmodname[scheme] may be imported with:
|
||||
|
||||
@defform/subs[(import-primitives id ...)()]{
|
||||
|
||||
Imports the procedures @racket[_id ...] from @racketmodname[scheme]. Each
|
||||
procedure is transformed to correctly interface with the mutator. That is, its
|
||||
arguments are dereferenced from the mutator's heap and the result is allocated
|
||||
on the mutator's heap. The arguments and result must be @racket[heap-value?]s,
|
||||
even if the imported procedure accepts or produces structured data.
|
||||
|
||||
For example, the @MUTATE-LANG language does not define @racket[modulo]:
|
||||
|
||||
@racketblock[
|
||||
|
||||
(import-primitives modulo)
|
||||
|
||||
(test/value=? (modulo 5 3) 2)
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
@section{Testing Mutators}
|
||||
|
||||
@MUTATE-LANG provides two forms for testing mutators:
|
||||
|
||||
@defform/subs[(test/location=? mutator-expr1 mutator-expr2)()]{
|
||||
|
||||
@racket[test/location=?] succeeds if @racket[_mutator-expr1] and
|
||||
@racket[_mutator-expr2] reference the same location on the heap.
|
||||
|
||||
}
|
||||
|
||||
@defform/subs[(test/value=? mutator-expr scheme-datum/quoted)()]{
|
||||
|
||||
@racket[test/value=?] succeeds if @racket[_mutator-expr] and
|
||||
@racket[_scheme-datum/expr] are structurally equal.
|
||||
@racket[_scheme-datum/quoted] is not allocated on the mutator's
|
||||
heap. Futhermore, it must either be a quoted value or a literal value.
|
||||
|
||||
}
|
||||
|
||||
@defform/subs[
|
||||
(printf format mutator-expr ...)
|
||||
([format literal-string])]{
|
||||
|
||||
In @|MUTATE-LANG|, @racket[printf] is a syntactic form and not a procedure. The
|
||||
format string, @racket[_format] is not allocated on the mutator's heap.
|
||||
|
||||
}
|
||||
|
||||
@section{Generating Random Mutators}
|
||||
|
||||
@defmodule[plai/random-mutator]
|
||||
|
||||
This PLAI library provides a facility for generating random mutators,
|
||||
in order to test your garbage collection implementation.
|
||||
|
||||
@defproc[(save-random-mutator
|
||||
[file path-string?]
|
||||
[collector-name string?]
|
||||
[#:heap-values heap-values (cons heap-value? (listof heap-value?))
|
||||
(list 0 1 -1 'x 'y #f #t '())]
|
||||
[#:iterations iterations exact-positive-integer? 200]
|
||||
[#:program-size program-size exact-positive-integer? 10]
|
||||
[#:heap-size heap-size exact-positive-integer? 100])
|
||||
void?]{
|
||||
Creates a random mutator that uses the collector @racket[collector-name] and
|
||||
saves it in @racket[file].
|
||||
|
||||
The mutator is created by first making a random graph whose nodes either have
|
||||
no outgoing edges, two outgoing edges, or some random number of outgoing edges
|
||||
and then picking a random path in the graph that ends at one of the nodes with
|
||||
no edges.
|
||||
|
||||
This graph and path are then turned into a PLAI program by creating a
|
||||
@racket[let] expression that binds one variable per node in the graph. If the
|
||||
node has no outgoing edges, it is bound to a @racket[heap-value?]. If the node
|
||||
has two outgoing edges, it is bound to a pair and the two edges are put into
|
||||
the first and rest fields. Otherwise, the node is represented as a procedure
|
||||
that accepts an integer index and returns the destination node of the
|
||||
corresponding edge.
|
||||
|
||||
Once the @racket[let] expression has been created, the program creates a bunch
|
||||
of garbage and then traverses the graph, according to the randomly created
|
||||
path. If the result of the path is the expected heap value, the program does
|
||||
this again, up to @racket[iterations] times. If the result of the path is not
|
||||
the expected heap value, the program terminates with an error.
|
||||
|
||||
The keyword arguments control some aspects of the generation
|
||||
of random mutators:
|
||||
@itemize[
|
||||
@item{Elements from the @racket[heap-values] argument are used as the base
|
||||
values when creating nodes with no outgoing edges. See also
|
||||
@racket[find-heap-values].}
|
||||
@item{The @racket[iterations] argument controls how many times the graph is
|
||||
created (and traversed).}
|
||||
@item{The @racket[program-size] argument is a bound on how big the program it
|
||||
is; it limits the number of nodes, the maximum number of edges, and the
|
||||
length of the path in the graph.}
|
||||
@item{The @racket[heap-size] argument controls the size of the heap in the
|
||||
generated mutator.}]
|
||||
|
||||
}
|
||||
|
||||
@defproc[(find-heap-values [input (or/c path-string? input-port?)])
|
||||
(listof heap-value?)]{
|
||||
Processes @racket[input] looking for occurrences of @racket[heap-value?]s in
|
||||
the source of the program and returns them. This makes a good start for the
|
||||
@racket[heap-values] argument to @racket[save-random-mutator].
|
||||
|
||||
If @racket[input] is a port, its contents are assumed to be a well-formed
|
||||
PLAI program. If @racket[input] is a file, the contents of the file are used.
|
||||
}
|
282
collects/plai/scribblings/mutator2.scrbl
Normal file
282
collects/plai/scribblings/mutator2.scrbl
Normal file
|
@ -0,0 +1,282 @@
|
|||
#lang scribble/doc
|
||||
@(require scribble/manual
|
||||
"rkt-exports.rkt"
|
||||
"plai-exports.rkt"
|
||||
"lang-names.rkt"
|
||||
scribble/decode
|
||||
(for-syntax scheme)
|
||||
(for-label (except-in scheme
|
||||
error printf)
|
||||
(prefix-in scheme:
|
||||
scheme)
|
||||
(only-in plai/main
|
||||
type-case define-type error
|
||||
test test/pred test/exn test/regexp
|
||||
abridged-test-output
|
||||
plai-catch-test-exn
|
||||
halt-on-errors print-only-errors
|
||||
test-inexact-epsilon plai-ignore-exn-strings
|
||||
plai-all-test-results)
|
||||
(only-in plai/collector
|
||||
root?
|
||||
heap-size
|
||||
location?
|
||||
heap-value?
|
||||
heap-set! heap-ref with-heap
|
||||
get-root-set read-root set-root!
|
||||
procedure-roots)
|
||||
plai/random-mutator
|
||||
(only-in plai/web
|
||||
no-web-browser
|
||||
static-files-path)
|
||||
(only-in plai/mutator
|
||||
set-first!
|
||||
set-rest!
|
||||
import-primitives
|
||||
test/location=?
|
||||
test/value=?
|
||||
printf)))
|
||||
|
||||
@title[#:tag "gc2-mutator"]{GC Mutator Language, 2}
|
||||
|
||||
@defmodulelang[plai/gc2/mutator]
|
||||
|
||||
The @MUTATE-LANG language is used to test garbage collectors written with the
|
||||
@secref["collector"] language. Since collectors support a subset of Racket's
|
||||
values, the @MUTATE-LANG language supports a subset of procedures and syntax.
|
||||
In addition, many procedures that can be written in the mutator are omitted as
|
||||
they make good test cases. Therefore, the mutator language provides only
|
||||
primitive procedures, such as @racket[+], @racket[cons], etc.
|
||||
|
||||
@section{Building Mutators for GC2}
|
||||
|
||||
@declare-exporting[#:use-sources (plai/scribblings/fake-mutator2)]
|
||||
|
||||
The first expression of a mutator must be:
|
||||
|
||||
@defform/subs[
|
||||
(allocator-setup collector-module
|
||||
heap-size)
|
||||
([heap-size exact-nonnegative-integer?])]{
|
||||
|
||||
@racket[_collector-module] specifies the path to the garbage collector that the
|
||||
mutator should use. The collector must be written in the @COLLECT-LANG
|
||||
language.
|
||||
}
|
||||
|
||||
The rest of a mutator module is a sequence of definitions, expressions and test
|
||||
cases. The @MUTATE-LANG language transforms these definitions and statements to
|
||||
use the collector specified in @racket[allocator-setup]. In particular, many
|
||||
of the primitive forms, such as @racket[cons] map directly to procedures such
|
||||
as @racket[gc:cons], written in the collector.
|
||||
|
||||
@section{Mutator API for GC2}
|
||||
|
||||
The @MUTATE-LANG language supports the following procedures and syntactic
|
||||
forms:
|
||||
|
||||
@(define-syntax (document/lift stx)
|
||||
(syntax-case stx ()
|
||||
[(_ a ...)
|
||||
(with-syntax ([(doc ...)
|
||||
(for/list ([a (in-list (syntax->list #'(a ...)))])
|
||||
(syntax-case a ()
|
||||
[a
|
||||
(identifier? #'a)
|
||||
(with-syntax ([rkt:a (string->symbol (format "rkt:~a" (syntax-e #'a)))])
|
||||
#'@defidform[a]{Just like Racket's @|rkt:a|.})]
|
||||
[(a stuff)
|
||||
(identifier? #'a)
|
||||
(with-syntax ([rkt:a (string->symbol (format "rkt:~a" (syntax-e #'a)))])
|
||||
#'@defidform[a]{Similar to Racket's @|rkt:a|. @stuff})]))])
|
||||
#'(begin
|
||||
doc ...))]))
|
||||
|
||||
@document/lift[if and or cond case define-values let let-values let*
|
||||
(set! @splice[@list{Unlike Racket's @|rkt:set!|, this @racket[set!] is syntactically allowed only
|
||||
in positions that discard its result, e.g., at the top-level
|
||||
or in a @racket[begin] expression (although not as the last expression
|
||||
in a @racket[begin]).}])
|
||||
quote error begin]
|
||||
|
||||
@defform[(define (id arg-id ...) body-expression ...+)]{
|
||||
Just like Racket's @racket[define], except restricted to the simpler form
|
||||
above.
|
||||
}
|
||||
@deftogether[(@defform[(lambda (arg-id ...) body-expression ...+)]{}
|
||||
@defform[(λ (arg-id ...) body-expression ...+)]{})]{
|
||||
Just like Racket's @racket[lambda] and @racket[λ], except restricted to the
|
||||
simpler form above.
|
||||
}
|
||||
|
||||
@document/lift[add1 sub1 zero? + - * / even? odd? = < > <= >=
|
||||
symbol? symbol=? number? boolean? empty? eq?]
|
||||
|
||||
@defproc[(cons [hd any/c] [tl any/c]) cons?]{
|
||||
Constructs a (mutable) pair.
|
||||
}
|
||||
@defproc[(cons? [v any/c]) boolean?]{
|
||||
Returns @racket[#t] when given a value created by @racket[cons],
|
||||
@racket[#f] otherwise.
|
||||
}
|
||||
@defproc[(first [c cons?]) any/c]{
|
||||
Extracts the first component of @racket[c].
|
||||
}
|
||||
@defproc[(rest [c cons?]) any/c]{
|
||||
Extracts the rest component of @racket[c].
|
||||
}
|
||||
|
||||
@defproc[(set-first! [c cons?] [v any/c])
|
||||
void]{
|
||||
Sets the @racket[first] of the cons cell @racket[c].
|
||||
|
||||
Calls to this function are allowed only in syntactic
|
||||
positions that would discard its result, e.g., at the
|
||||
top-level or inside a @racket[begin] expression (but
|
||||
not in the last expression in a @racket[begin]).
|
||||
Also, this function appear only in the function
|
||||
position of an application expression.
|
||||
|
||||
So, in order to pass around a version of this function,
|
||||
you must write something like this
|
||||
@racket[(λ (c v) (begin (set-first! c v) 42))],
|
||||
perhaps picking a different value than @racket[42]
|
||||
as the result.
|
||||
}
|
||||
|
||||
@defproc[(set-rest! [c cons?] [v any/c])
|
||||
void]{
|
||||
Sets the @racket[rest] of the cons cell @racket[c],
|
||||
with the same syntactic restrictions as
|
||||
@racket[set-first!].
|
||||
}
|
||||
|
||||
@defidform[empty]{
|
||||
The identifier @racket[empty] is defined to invoke
|
||||
@racket[(gc:alloc-flat empty)] wherever it is used.
|
||||
}
|
||||
|
||||
@defidform[print-only-errors]{
|
||||
Behaves like PLAI's @|plai:print-only-errors|.
|
||||
}
|
||||
|
||||
@defidform[halt-on-errors]{
|
||||
Behaves like PLAI's @|plai:halt-on-errors|.
|
||||
}
|
||||
|
||||
Other common procedures are left undefined as they can be defined in
|
||||
terms of the primitives and may be used to test collectors.
|
||||
|
||||
Additional procedures from @racketmodname[scheme] may be imported with:
|
||||
|
||||
@defform/subs[(import-primitives id ...)()]{
|
||||
|
||||
Imports the procedures @racket[_id ...] from @racketmodname[scheme]. Each
|
||||
procedure is transformed to correctly interface with the mutator. That is, its
|
||||
arguments are dereferenced from the mutator's heap and the result is allocated
|
||||
on the mutator's heap. The arguments and result must be @racket[heap-value?]s,
|
||||
even if the imported procedure accepts or produces structured data.
|
||||
|
||||
For example, the @MUTATE-LANG language does not define @racket[modulo]:
|
||||
|
||||
@racketblock[
|
||||
|
||||
(import-primitives modulo)
|
||||
|
||||
(test/value=? (modulo 5 3) 2)
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
@section{Testing Mutators with GC2}
|
||||
|
||||
@MUTATE-LANG provides two forms for testing mutators:
|
||||
|
||||
@defform/subs[(test/location=? mutator-expr1 mutator-expr2)()]{
|
||||
|
||||
@racket[test/location=?] succeeds if @racket[_mutator-expr1] and
|
||||
@racket[_mutator-expr2] reference the same location on the heap.
|
||||
|
||||
}
|
||||
|
||||
@defform/subs[(test/value=? mutator-expr datum/quoted)()]{
|
||||
|
||||
@racket[test/value=?] succeeds if @racket[_mutator-expr] and
|
||||
@racket[_datum/expr] are structurally equal.
|
||||
@racket[_datum/quoted] is not allocated on the mutator's
|
||||
heap. Futhermore, it must either be a quoted value or a literal value.
|
||||
|
||||
}
|
||||
|
||||
@defform/subs[
|
||||
(printf format mutator-expr ...)
|
||||
([format literal-string])]{
|
||||
|
||||
In @|MUTATE-LANG|, @racket[printf] is a syntactic form and not a procedure. The
|
||||
format string, @racket[_format] is not allocated on the mutator's heap.
|
||||
|
||||
}
|
||||
|
||||
@section{Generating Random Mutators for GC2}
|
||||
|
||||
@defmodule[plai/gc2/random-mutator]
|
||||
|
||||
This PLAI library provides a facility for generating random mutators,
|
||||
in order to test your garbage collection implementation.
|
||||
|
||||
@defproc[(save-random-mutator
|
||||
[file path-string?]
|
||||
[collector-name string?]
|
||||
[#:heap-values heap-values (cons heap-value? (listof heap-value?))
|
||||
(list 0 1 -1 'x 'y #f #t '())]
|
||||
[#:iterations iterations exact-positive-integer? 200]
|
||||
[#:program-size program-size exact-positive-integer? 10]
|
||||
[#:heap-size heap-size exact-positive-integer? 100])
|
||||
void?]{
|
||||
Creates a random mutator that uses the collector @racket[collector-name] and
|
||||
saves it in @racket[file].
|
||||
|
||||
The mutator is created by first making a random graph whose nodes either have
|
||||
no outgoing edges, two outgoing edges, or some random number of outgoing edges
|
||||
and then picking a random path in the graph that ends at one of the nodes with
|
||||
no edges.
|
||||
|
||||
This graph and path are then turned into a PLAI program by creating a
|
||||
@racket[let] expression that binds one variable per node in the graph. If the
|
||||
node has no outgoing edges, it is bound to a @racket[heap-value?]. If the node
|
||||
has two outgoing edges, it is bound to a pair and the two edges are put into
|
||||
the first and rest fields. Otherwise, the node is represented as a procedure
|
||||
that accepts an integer index and returns the destination node of the
|
||||
corresponding edge.
|
||||
|
||||
Once the @racket[let] expression has been created, the program creates a bunch
|
||||
of garbage and then traverses the graph, according to the randomly created
|
||||
path. If the result of the path is the expected heap value, the program does
|
||||
this again, up to @racket[iterations] times. If the result of the path is not
|
||||
the expected heap value, the program terminates with an error.
|
||||
|
||||
The keyword arguments control some aspects of the generation
|
||||
of random mutators:
|
||||
@itemize[
|
||||
@item{Elements from the @racket[heap-values] argument are used as the base
|
||||
values when creating nodes with no outgoing edges. See also
|
||||
@racket[find-heap-values].}
|
||||
@item{The @racket[iterations] argument controls how many times the graph is
|
||||
created (and traversed).}
|
||||
@item{The @racket[program-size] argument is a bound on how big the program it
|
||||
is; it limits the number of nodes, the maximum number of edges, and the
|
||||
length of the path in the graph.}
|
||||
@item{The @racket[heap-size] argument controls the size of the heap in the
|
||||
generated mutator.}]
|
||||
|
||||
}
|
||||
|
||||
@defproc[(find-heap-values [input (or/c path-string? input-port?)])
|
||||
(listof heap-value?)]{
|
||||
Processes @racket[input] looking for occurrences of @racket[heap-value?]s in
|
||||
the source of the program and returns them. This makes a good start for the
|
||||
@racket[heap-values] argument to @racket[save-random-mutator].
|
||||
|
||||
If @racket[input] is a port, its contents are assumed to be a well-formed
|
||||
PLAI program. If @racket[input] is a file, the contents of the file are used.
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
@(require scribble/manual
|
||||
"rkt-exports.rkt"
|
||||
"plai-exports.rkt"
|
||||
"lang-names.rkt"
|
||||
(for-syntax scheme)
|
||||
(for-label (except-in scheme
|
||||
error printf)
|
||||
|
@ -38,11 +39,6 @@
|
|||
test/value=?
|
||||
printf)))
|
||||
|
||||
@(define PLAI-LANG "PLAI Scheme")
|
||||
@(define COLLECT-LANG "GC Collector Scheme")
|
||||
@(define MUTATE-LANG "GC Mutator Scheme")
|
||||
@(define WEB-LANG "Web Application Scheme")
|
||||
|
||||
@title{@italic{Programming Languages: Application and Interpretation}}
|
||||
|
||||
This is the documentation for the software accompanying the textbook @bold{Programming Languages: Application and
|
||||
|
@ -249,415 +245,10 @@ This variable is the list of all tests that have been run so far, with the most
|
|||
recent test at the head.
|
||||
|
||||
}
|
||||
|
||||
@section[#:tag "collector"]{@COLLECT-LANG}
|
||||
|
||||
@defmodulelang[plai/collector]
|
||||
|
||||
@COLLECT-LANG is based on @seclink["plai-scheme"]{PLAI Scheme}. It provides
|
||||
additional procedures and syntax for writing garbage collectors.
|
||||
|
||||
@subsection{Garbage Collector Interface}
|
||||
|
||||
The @COLLECT-LANG language provides the following functions that provide access
|
||||
to the heap and root set:
|
||||
|
||||
@defproc[(heap-size) exact-nonnegative-integer?]{
|
||||
|
||||
Returns the size of the heap. The size of the heap is specified by the mutator
|
||||
that uses the garbage collector. See @racket[allocator-setup] for more
|
||||
information.
|
||||
}
|
||||
|
||||
@defproc[(location? [v any/c])
|
||||
boolean?]{
|
||||
Determines if @racket[v] is an integer between @racket[0] and
|
||||
@racket[(- (heap-size) 1)] inclusive.
|
||||
}
|
||||
|
||||
@defproc[(root? [v any/c])
|
||||
boolean?]{
|
||||
Determines if @racket[v] is a root.
|
||||
}
|
||||
|
||||
@defproc[(heap-value? [v any/c]) boolean?]{
|
||||
A value that may be stored on the heap. Roughly corresponds to the contract
|
||||
@racket[(or/c boolean? number? procedure? symbol? empty?)].
|
||||
}
|
||||
|
||||
|
||||
@defproc[(heap-set! (loc location?) (val heap-value?)) void?]{
|
||||
Sets the value at @racket[_loc] to @racket[_val].
|
||||
}
|
||||
|
||||
@defproc[(heap-ref (loc location?)) heap-value?]{
|
||||
Returns the value at @racket[_loc].
|
||||
}
|
||||
|
||||
@defform/subs[(get-root-set id ...)()]{
|
||||
Returns the current roots as a list. Local roots are created for the
|
||||
identifiers @racket[_id] as well.
|
||||
}
|
||||
|
||||
@defproc[(read-root (root root?)) location?]{
|
||||
Returns the location of @racket[_root].
|
||||
}
|
||||
|
||||
@defproc[(set-root! (root root?) (loc location?)) void?]{
|
||||
Updates the root to reference the given location.
|
||||
}
|
||||
|
||||
@defproc[(procedure-roots (proc procedure?)) (listof root?)]{
|
||||
Given a closure stored on the heap, returns a list of the roots reachable
|
||||
from the closure's environment. If @racket[_proc] is not reachable, the
|
||||
empty list is returned.
|
||||
}
|
||||
|
||||
@defform[(with-heap heap-expr body-expr ...)
|
||||
#:contracts ([heap-expr (vectorof heap-value?)])]{
|
||||
Evaluates each of the @racket[body-expr]s in a context where
|
||||
the value of @racket[heap-expr] is used as the heap. Useful in
|
||||
tests:
|
||||
@racketblock[
|
||||
(test (with-heap (make-vector 20)
|
||||
(init-allocator)
|
||||
(gc:deref (gc:alloc-flat 2)))
|
||||
2)
|
||||
]}
|
||||
|
||||
@defform[(with-roots roots-expr expr1 expr2 ...)
|
||||
#:contracts ([roots-expr (listof location?)])]{
|
||||
Evaluates each of @racket[expr1] and the @racket[expr2]s in
|
||||
in a context with the result of @racket[roots-expr]
|
||||
as additional roots.
|
||||
|
||||
This function is intended to be used in test suites
|
||||
for collectors. Since your test suites are not running
|
||||
in the @racketmod[plai/mutator] language, @racket[get-root-set]
|
||||
returns a list consisting only of the roots it created,
|
||||
not all of the other roots it normally would return.
|
||||
Use this function to note specific locations as roots
|
||||
and set up better tests for your GC.
|
||||
|
||||
@racketblock[
|
||||
(test (with-heap (make-vector 4)
|
||||
(define f1 (gc:alloc-flat 1))
|
||||
(define c1 (gc:cons f1 f1))
|
||||
(with-roots (list c1)
|
||||
(gc:deref
|
||||
(gc:first
|
||||
(gc:cons f1 f1)))))
|
||||
1)]
|
||||
|
||||
}
|
||||
|
||||
@subsection{Garbage Collector Exports}
|
||||
|
||||
@declare-exporting[#:use-sources (plai/scribblings/fake-collector)]
|
||||
|
||||
A garbage collector must define the following functions:
|
||||
|
||||
@defproc[(init-allocator) void?]{
|
||||
|
||||
@racket[init-allocator] is called before all other procedures by a
|
||||
mutator. Place any requisite initialization code here.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:deref (loc location?)) heap-value?]{
|
||||
|
||||
Given the location of a flat Scheme value, this procedure should return that
|
||||
value. If the location does not hold a flat value, this function should signal
|
||||
an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:alloc-flat (val heap-value?)) location?]{
|
||||
|
||||
This procedure should allocate a flat Scheme value (number, symbol, boolean,
|
||||
closure or empty list) on the heap, returning its location (a number). The
|
||||
value should occupy a single heap cell, though you may use additional space to
|
||||
store a tag, etc. You are also welcome to pre-allocate common constants (e.g.,
|
||||
the empty list). This procedure may need to perform a garbage-collection. If
|
||||
there is still insufficient space, it should signal an error.
|
||||
|
||||
Note that closures are flat values. The environment of a closure is internally
|
||||
managed, but contains references to values on the heap. Therefore, during
|
||||
garbage collection, the environment of reachable closures must be updated. The
|
||||
language exposes the environment via the @racket[procedure-roots] function.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:cons (first location?) (rest location?)) location?]{
|
||||
|
||||
Given the location of the @racket[_first] and @racket[_rest] values, this
|
||||
procedure must allocate a cons cell on the heap. If there is insufficient
|
||||
space to allocate the cons cell, it should signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:first (cons-cell location?)) location?]{
|
||||
|
||||
If the given location refers to a cons cell, this should return the first
|
||||
field. Otherwise, it should signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:rest (cons-cell location?)) location?]{
|
||||
|
||||
If the given location refers to a cons cell, this should return the rest
|
||||
field. Otherwise, it should signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:set-first! (cons-cell location?) (first-value location?)) void?]{
|
||||
|
||||
If @racket[_cons-cell] refers to a cons cell, set the head of the cons cell to
|
||||
@racket[_first-value]. Otherwise, signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:set-rest! (cons-cell location?) (rest-value location?)) void?]{
|
||||
|
||||
If @racket[_cons-cell] refers to a cons cell, set the tail of the cons cell to
|
||||
@racket[_rest-value]. Otherwise, signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:cons? (loc location?)) boolean?]{
|
||||
|
||||
|
||||
Returns @racket[true] if @racket[_loc] refers to a cons cell. This function
|
||||
should never signal an error.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(gc:flat? (loc location?)) boolean?]{
|
||||
|
||||
Returns @racket[true] if @racket[_loc] refers to a flat value. This function
|
||||
should never signal an error.
|
||||
|
||||
}
|
||||
|
||||
@section[#:tag "mutator"]{@MUTATE-LANG}
|
||||
|
||||
@defmodulelang[plai/mutator]
|
||||
|
||||
The @MUTATE-LANG language is used to test garbage collectors written with the
|
||||
@secref["collector"] language. Since collectors support a subset of Scheme's
|
||||
values, the @MUTATE-LANG language supports a subset of procedures and syntax.
|
||||
In addition, many procedures that can be written in the mutator are omitted as
|
||||
they make good test cases. Therefore, the mutator language provides only
|
||||
primitive procedures, such as @racket[+], @racket[cons], etc.
|
||||
|
||||
@subsection{Building Mutators}
|
||||
|
||||
@declare-exporting[#:use-sources (plai/scribblings/fake-mutator)]
|
||||
|
||||
The first expression of a mutator must be:
|
||||
|
||||
@defform/subs[
|
||||
(allocator-setup collector-module
|
||||
heap-size)
|
||||
([heap-size exact-nonnegative-integer?])]{
|
||||
|
||||
@racket[_collector-module] specifies the path to the garbage collector that the
|
||||
mutator should use. The collector must be written in the @COLLECT-LANG
|
||||
language.
|
||||
}
|
||||
|
||||
The rest of a mutator module is a sequence of definitions, expressions and test
|
||||
cases. The @MUTATE-LANG language transforms these definitions and statements to
|
||||
use the collector specified in @racket[allocator-setup]. In particular, many
|
||||
of the primitive forms, such as @racket[cons] map directly to procedures such
|
||||
as @racket[gc:cons], written in the collector.
|
||||
|
||||
@subsection{Mutator API}
|
||||
|
||||
The @MUTATE-LANG language supports the following procedures and syntactic
|
||||
forms:
|
||||
|
||||
@(define-syntax (document/lift stx)
|
||||
(syntax-case stx ()
|
||||
[(_ a ...)
|
||||
(with-syntax ([(doc ...)
|
||||
(map (λ (a)
|
||||
(with-syntax ([a a]
|
||||
[rkt:a (string->symbol (format "rkt:~a" (syntax-e a)))])
|
||||
#'@defidform[a]{Just like Racket's @|rkt:a|.}))
|
||||
(syntax->list #'(a ...)))])
|
||||
|
||||
#'(begin
|
||||
doc ...))]))
|
||||
|
||||
@document/lift[if and or cond case define-values let let-values let* set! quote error begin]
|
||||
|
||||
@defform[(define (id arg-id ...) body-expression ...+)]{
|
||||
Just like Racket's @racket[define], except restricted to the simpler form
|
||||
above.
|
||||
}
|
||||
@deftogether[(@defform[(lambda (arg-id ...) body-expression ...+)]{}
|
||||
@defform[(λ (arg-id ...) body-expression ...+)]{})]{
|
||||
Just like Racket's @racket[lambda] and @racket[λ], except restricted to the
|
||||
simpler form above.
|
||||
}
|
||||
|
||||
@document/lift[add1 sub1 zero? + - * / even? odd? = < > <= >=
|
||||
symbol? symbol=? number? boolean? empty? eq?]
|
||||
|
||||
@defproc[(cons [hd any/c] [tl any/c]) cons?]{
|
||||
Constructs a (mutable) pair.
|
||||
}
|
||||
@defproc[(cons? [v any/c]) boolean?]{
|
||||
Returns @racket[#t] when given a value created by @racket[cons],
|
||||
@racket[#f] otherwise.
|
||||
}
|
||||
@defproc[(first [c cons?]) any/c]{
|
||||
Extracts the first component of @racket[c].
|
||||
}
|
||||
@defproc[(rest [c cons?]) any/c]{
|
||||
Extracts the rest component of @racket[c].
|
||||
}
|
||||
|
||||
@defproc[(set-first! [c cons?] [v any/c])
|
||||
void]{
|
||||
Sets the @racket[first] of the cons cell @racket[c].
|
||||
}
|
||||
|
||||
@defproc[(set-rest! [c cons?] [v any/c])
|
||||
void]{
|
||||
Sets the @racket[rest] of the cons cell @racket[c].
|
||||
}
|
||||
|
||||
@defidform[empty]{
|
||||
The identifier @racket[empty] is defined to invoke
|
||||
@racket[(gc:alloc-flat empty)] wherever it is used.
|
||||
}
|
||||
|
||||
@defidform[print-only-errors]{
|
||||
Behaves like PLAI's @|plai:print-only-errors|.
|
||||
}
|
||||
|
||||
@defidform[halt-on-errors]{
|
||||
Behaves like PLAI's @|plai:halt-on-errors|.
|
||||
}
|
||||
|
||||
Other common procedures are left undefined as they can be defined in
|
||||
terms of the primitives and may be used to test collectors.
|
||||
|
||||
Additional procedures from @racketmodname[scheme] may be imported with:
|
||||
|
||||
@defform/subs[(import-primitives id ...)()]{
|
||||
|
||||
Imports the procedures @racket[_id ...] from @racketmodname[scheme]. Each
|
||||
procedure is transformed to correctly interface with the mutator. That is, its
|
||||
arguments are dereferenced from the mutator's heap and the result is allocated
|
||||
on the mutator's heap. The arguments and result must be @racket[heap-value?]s,
|
||||
even if the imported procedure accepts or produces structured data.
|
||||
|
||||
For example, the @MUTATE-LANG language does not define @racket[modulo]:
|
||||
|
||||
@racketblock[
|
||||
|
||||
(import-primitives modulo)
|
||||
|
||||
(test/value=? (modulo 5 3) 2)
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
@subsection{Testing Mutators}
|
||||
|
||||
@MUTATE-LANG provides two forms for testing mutators:
|
||||
|
||||
@defform/subs[(test/location=? mutator-expr1 mutator-expr2)()]{
|
||||
|
||||
@racket[test/location=?] succeeds if @racket[_mutator-expr1] and
|
||||
@racket[_mutator-expr2] reference the same location on the heap.
|
||||
|
||||
}
|
||||
|
||||
@defform/subs[(test/value=? mutator-expr scheme-datum/quoted)()]{
|
||||
|
||||
@racket[test/value=?] succeeds if @racket[_mutator-expr] and
|
||||
@racket[_scheme-datum/expr] are structurally equal.
|
||||
@racket[_scheme-datum/quoted] is not allocated on the mutator's
|
||||
heap. Futhermore, it must either be a quoted value or a literal value.
|
||||
|
||||
}
|
||||
|
||||
@defform/subs[
|
||||
(printf format mutator-expr ...)
|
||||
([format literal-string])]{
|
||||
|
||||
In @|MUTATE-LANG|, @racket[printf] is a syntactic form and not a procedure. The
|
||||
format string, @racket[_format] is not allocated on the mutator's heap.
|
||||
|
||||
}
|
||||
|
||||
@subsection{Generating Random Mutators}
|
||||
|
||||
@defmodule[plai/random-mutator]
|
||||
|
||||
This PLAI library provides a facility for generating random mutators,
|
||||
in order to test your garbage collection implementation.
|
||||
|
||||
@defproc[(save-random-mutator
|
||||
[file path-string?]
|
||||
[collector-name string?]
|
||||
[#:heap-values heap-values (cons heap-value? (listof heap-value?))
|
||||
(list 0 1 -1 'x 'y #f #t '())]
|
||||
[#:iterations iterations exact-positive-integer? 200]
|
||||
[#:program-size program-size exact-positive-integer? 10]
|
||||
[#:heap-size heap-size exact-positive-integer? 100])
|
||||
void?]{
|
||||
Creates a random mutator that uses the collector @racket[collector-name] and
|
||||
saves it in @racket[file].
|
||||
|
||||
The mutator is created by first making a random graph whose nodes either have
|
||||
no outgoing edges, two outgoing edges, or some random number of outgoing edges
|
||||
and then picking a random path in the graph that ends at one of the nodes with
|
||||
no edges.
|
||||
|
||||
This graph and path are then turned into a PLAI program by creating a
|
||||
@racket[let] expression that binds one variable per node in the graph. If the
|
||||
node has no outgoing edges, it is bound to a @racket[heap-value?]. If the node
|
||||
has two outgoing edges, it is bound to a pair and the two edges are put into
|
||||
the first and rest fields. Otherwise, the node is represented as a procedure
|
||||
that accepts an integer index and returns the destination node of the
|
||||
corresponding edge.
|
||||
|
||||
Once the @racket[let] expression has been created, the program creates a bunch
|
||||
of garbage and then traverses the graph, according to the randomly created
|
||||
path. If the result of the path is the expected heap value, the program does
|
||||
this again, up to @racket[iterations] times. If the result of the path is not
|
||||
the expected heap value, the program terminates with an error.
|
||||
|
||||
The keyword arguments control some aspects of the generation
|
||||
of random mutators:
|
||||
@itemize[
|
||||
@item{Elements from the @racket[heap-values] argument are used as the base
|
||||
values when creating nodes with no outgoing edges. See also
|
||||
@racket[find-heap-values].}
|
||||
@item{The @racket[iterations] argument controls how many times the graph is
|
||||
created (and traversed).}
|
||||
@item{The @racket[program-size] argument is a bound on how big the program it
|
||||
is; it limits the number of nodes, the maximum number of edges, and the
|
||||
length of the path in the graph.}
|
||||
@item{The @racket[heap-size] argument controls the size of the heap in the
|
||||
generated mutator.}]
|
||||
|
||||
}
|
||||
|
||||
@defproc[(find-heap-values [input (or/c path-string? input-port?)])
|
||||
(listof heap-value?)]{
|
||||
Processes @racket[input] looking for occurrences of @racket[heap-value?]s in
|
||||
the source of the program and returns them. This makes a good start for the
|
||||
@racket[heap-values] argument to @racket[save-random-mutator].
|
||||
|
||||
If @racket[input] is a port, its contents are assumed to be a well-formed
|
||||
PLAI program. If @racket[input] is a file, the contents of the file are used.
|
||||
}
|
||||
@include-section["collector.scrbl"]
|
||||
@include-section["mutator.scrbl"]
|
||||
@include-section["collector2.scrbl"]
|
||||
@include-section["mutator2.scrbl"]
|
||||
|
||||
@section[#:tag "web"]{@WEB-LANG}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user