racket/collects/plai/scribblings/mutator.scrbl
2013-03-07 13:52:37 -06:00

261 lines
9.5 KiB
Racket

#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.
}