On this page:
5.1 Building Mutators for GC2
allocator-setup
5.2 Mutator API for GC2
if
and
or
cond
case
define-values
let
let-values
let*
set!
quote
begin
define
lambda
λ
error
add1
sub1
zero?
+
-
*
/
even?
odd?
=
<
>
<=
>=
symbol?
symbol=?
number?
boolean?
empty?
eq?
cons
cons?
first
rest
set-first!
set-rest!
empty
print-only-errors
halt-on-errors
import-primitives
5.3 Testing Mutators with GC2
test/  location=?
test/  value=?
printf
5.4 Generating Random Mutators for GC2
save-random-mutator
find-heap-values

5 GC Mutator Language, 2

 #lang plai/gc2/mutator package: plai-lib

The GC Mutator Scheme language is used to test garbage collectors written with the GC Collector Language, 2 language. Since collectors support a subset of Racket’s values, the GC Mutator Scheme 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 +, cons, etc.

5.1 Building Mutators for GC2

The first expression of a mutator must be:

syntax

(allocator-setup collector-module
                 heap-size)
 
heap-size = exact-nonnegative-integer
collector-module specifies the path to the garbage collector that the mutator should use. The collector must be written in the GC Collector Scheme language.

The rest of a mutator module is a sequence of definitions, expressions and test cases. The GC Mutator Scheme language transforms these definitions and statements to use the collector specified in allocator-setup. In particular, many of the primitive forms, such as cons map directly to procedures such as gc:cons, written in the collector.

5.2 Mutator API for GC2

The GC Mutator Scheme language supports the following procedures and syntactic forms:

syntax

if

Just like Racket’s if.

syntax

and

Just like Racket’s and.

syntax

or

Just like Racket’s or.

syntax

cond

Just like Racket’s cond.

syntax

case

Just like Racket’s case.
Just like Racket’s define-values.

syntax

let

Just like Racket’s let.
Just like Racket’s let-values.

syntax

let*

Just like Racket’s let*.

syntax

set!

Similar to Racket’s set!. Unlike Racket’s set!, this set! is syntactically allowed only in positions that discard its result, e.g., at the top-level or in a begin expression (although not as the last expression in a begin).

syntax

quote

Just like Racket’s quote.

syntax

begin

Just like Racket’s begin.

syntax

(define (id arg-id ...) body-expression ...+)

Just like Racket’s define, except restricted to the simpler form above.

syntax

(lambda (arg-id ...) body-expression ...+)

syntax

(λ (arg-id ...) body-expression ...+)

Just like Racket’s lambda and λ, except restricted to the simpler form above.

value

error : procedure?

Just like Racket’s error.

value

add1 : procedure?

Just like Racket’s add1.

value

sub1 : procedure?

Just like Racket’s sub1.

value

zero? : procedure?

Just like Racket’s zero?.

value

+ : procedure?

Just like Racket’s +.

value

- : procedure?

Just like Racket’s -.

value

* : procedure?

Just like Racket’s *.

value

/ : procedure?

Just like Racket’s /.

value

even? : procedure?

Just like Racket’s even?.

value

odd? : procedure?

Just like Racket’s odd?.

value

= : procedure?

Just like Racket’s =.

value

< : procedure?

Just like Racket’s <.

value

> : procedure?

Just like Racket’s >.

value

<= : procedure?

Just like Racket’s <=.

value

>= : procedure?

Just like Racket’s >=.
Just like Racket’s symbol?.
Just like Racket’s symbol=?.
Just like Racket’s number?.
Just like Racket’s boolean?.
Just like Racket’s empty?.

value

eq? : procedure?

Just like Racket’s eq?.

procedure

(cons hd tl)  cons?

  hd : any/c
  tl : any/c
Constructs a (mutable) pair.

procedure

(cons? v)  boolean?

  v : any/c
Returns #t when given a value created by cons, #f otherwise.

procedure

(first c)  any/c

  c : cons?
Extracts the first component of c.

procedure

(rest c)  any/c

  c : cons?
Extracts the rest component of c.

procedure

(set-first! c v)  void

  c : cons?
  v : any/c
Sets the first of the cons cell 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 begin expression (but not in the last expression in a 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 (λ (c v) (begin (set-first! c v) 42)), perhaps picking a different value than 42 as the result.

procedure

(set-rest! c v)  void

  c : cons?
  v : any/c
Sets the rest of the cons cell c, with the same syntactic restrictions as set-first!.

syntax

empty

The identifier empty is defined to invoke (gc:alloc-flat '()) wherever it is used.

Behaves like PLAI’s print-only-errors.

Behaves like PLAI’s 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 scheme may be imported with:

syntax

(import-primitives id ...)

Imports the procedures id ... from 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 heap-value?s, even if the imported procedure accepts or produces structured data.

For example, the GC Mutator Scheme language does not define modulo:

(import-primitives modulo)
 
(test/value=? (modulo 5 3) 2)

5.3 Testing Mutators with GC2

GC Mutator Scheme provides two forms for testing mutators:

syntax

(test/location=? mutator-expr1 mutator-expr2)

test/location=? succeeds if mutator-expr1 and mutator-expr2 reference the same location on the heap.

syntax

(test/value=? mutator-expr datum/quoted)

test/value=? succeeds if mutator-expr and datum/expr are structurally equal. datum/quoted is not allocated on the mutator’s heap. Futhermore, it must either be a quoted value or a literal value.

syntax

(printf format mutator-expr ...)

 
format = literal-string
In GC Mutator Scheme, printf is a syntactic form and not a procedure. The format string, format is not allocated on the mutator’s heap.

5.4 Generating Random Mutators for GC2

 (require plai/gc2/random-mutator) package: plai-lib

This PLAI library provides a facility for generating random mutators, in order to test your garbage collection implementation.

procedure

(save-random-mutator file    
  collector-name    
  [#:heap-values heap-values    
  #:iterations iterations    
  #:program-size program-size    
  #:heap-size heap-size])  void?
  file : path-string?
  collector-name : string?
  heap-values : (cons heap-value? (listof heap-value?))
   = (list 0 1 -1 'x 'y #f #t '())
  iterations : exact-positive-integer? = 200
  program-size : exact-positive-integer? = 10
  heap-size : exact-positive-integer? = 100
Creates a random mutator that uses the collector collector-name and saves it in 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 let expression that binds one variable per node in the graph. If the node has no outgoing edges, it is bound to a 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 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 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:
  • Elements from the heap-values argument are used as the base values when creating nodes with no outgoing edges. See also find-heap-values.

  • The iterations argument controls how many times the graph is created (and traversed).

  • The 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.

  • The heap-size argument controls the size of the heap in the generated mutator.

Example:

(save-random-mutator "tmp.rkt" "mygc.rkt" #:gc2? #t)

will write to "tmp.rkt" with a program like this one:
#lang plai/gc2/mutator
(allocator-setup "mygc.rkt" 200)
(define (build-one)
  (let* ((x0 1)
         (x1 (cons #f #f))
         (x2
          (lambda (x)
            (if (= x 0)
              x0
              (if (= x 1) x0 (if (= x 2) x1 (if (= x 3) x1 x0))))))
         (x3 1)
         (x4 (cons x3 x3))
         (x5 (lambda (x) (if (= x 0) x4 (if (= x 1) x1 x2)))))
    (set-first! x1 x2)
    (set-rest! x1 x3)
    x5))
(define (traverse-one x5) (= 1 (first (x5 0))))
(define (trigger-gc n)
  (if (zero? n) 0 (begin (cons n n) (trigger-gc (- n 1)))))
(define (loop i)
  (if (zero? i)
    'passed
    (let ((obj (build-one)))
      (trigger-gc 200)
      (if (traverse-one obj) (loop (- i 1)) 'failed))))
(loop 200)

procedure

(find-heap-values input)  (listof heap-value?)

  input : (or/c path-string? input-port?)
Processes input looking for occurrences of heap-value?s in the source of the program and returns them. This makes a good start for the heap-values argument to save-random-mutator.

If input is a port, its contents are assumed to be a well-formed PLAI program. If input is a file, the contents of the file are used.