racket/collects/redex/examples/racket-machine/randomized-tests.rkt
Eli Barzilay af6be85ff5 Fix lots of indentation mistakes.
(Found by my ayatollah script...)
2013-03-14 10:55:47 -04:00

346 lines
13 KiB
Racket

#lang racket
(require redex/reduction-semantics
(only-in "grammar.rkt" bytecode)
(only-in "reduction.rkt" -> load runtime)
(only-in "verification.rkt" bytecode-ok?)
"model-impl.rkt"
compiler/zo-parse compiler/zo-marshal)
(provide (all-defined-out))
;; Test the internal properties (verifier totality, safety, and confluence)
;; on acyclic expressions. The verifier's totality is tested implicitly by
;; treating a "no clauses matched" exception as a test failure.
(define (test-internal-properties
#:verifier [verified? bytecode-ok?]
#:reduction [-> ->]
#:prepare [prepare fix]
#:steps [steps 100]
#:attempts [attempts 5000])
(redex-check
bytecode e (safe-and-confluent? (term e) (term ()) verified? -> steps)
#:prepare prepare
#:attempts attempts))
;; Tests the internal properties on cyclic expressions.
(define (test-internal-properties/cycles
#:steps [steps 100]
#:attempts [attempts 5000]
#:print? [print? #t])
(redex-check
bytecode (side-condition (e (x_0 (name e_0 (proc-const (τ ...) e_0*))) ...)
(equal? (term (x_0 ...))
(remove-duplicates (term (x_0 ...)))))
(safe-and-confluent? (term e) (term ((x_0 e_0) ...)) bytecode-ok? -> steps)
#:attempts attempts
#:prepare fix-cyclic
#:print? print?))
;; Tests the external properties (the model and implementation agree on
;; verification and evaluation questions).
(define (test-external-properties
#:prepare [prepare (compose optimize fix)]
#:model-verifier [model-verified? bytecode-ok?]
#:steps [steps 100]
#:timeout [timeout 10]
#:attempts [attempts 5000]
#:print? [print? #t])
(redex-check
bytecode e
(model-impl-consistent? (term e) '() steps timeout model-verified?)
#:prepare prepare
#:attempts attempts
#:print? print?))
; result = answer | stuck | cutoff | non-conf
(define-struct cutoff ())
(define-struct stuck (state))
(define-struct answer (value))
(define-struct non-conf (values))
; run: e ((x e) ...) nat -> result
(define (run e cycles cutoff #:reduction [-> ->])
(let ([cache (make-hash)])
(let loop ([s (term (load ,e ,cycles))]
[c cutoff])
(let ([r (hash-ref cache s #f)])
(if (and r (>= (car r) c))
(cdr r)
(begin
; If we see this state again while it's marked pending,
; it will be with a lesser cutoff, and it will indicate a
; cycle in the reduction graph. Without pruning, a cycle
; the reduction graph produces a cutoff result; with it
; a cylce produces a pending, which is treated identically.
(hash-set! cache s (cons c 'pending))
(let ([r
(cond [(term (halted? ,s))
(make-answer
(if (eq? s 'error)
'error
(car s)))]
[(zero? c) (make-cutoff)]
[else
(let ([nexts (map (curryr loop (sub1 c)) (apply-reduction-relation -> s))])
(if (null? nexts)
(make-stuck s)
(or (findf non-conf? nexts)
(findf stuck? nexts)
(let ([answers (filter answer? nexts)])
(if (null? answers)
(make-cutoff)
(let cmp ([others (cdr answers)])
(if (null? others)
(car answers)
(if (equal? (answer-value (car answers))
(answer-value (car others)))
(cmp (cdr others))
(make-non-conf
(list (answer-value (car answers))
(answer-value (car others))))))))))))])])
(begin
(hash-set! cache s (cons c r))
r))))))))
(define (verified/cycles? expr cycles verified?)
(and (verified? expr)
(andmap (match-lambda [`(,_ ,e) (verified? e)])
cycles)))
(define (safe-and-confluent? expr cycles verified? -> steps)
(with-handlers ([exn:fail? (λ (_) #f)])
(or (not (verified/cycles? expr cycles verified?))
(match (run expr cycles steps #:reduction ->)
[(cutoff) #t]
[(answer _) #t]
[(stuck _) #f]
[(non-conf _) #f]))))
(define (model-impl-consistent? expr cycles steps timeout model-verified?)
(define (equiv-results? m i)
(match m
['uninit #f]
[`(box ,_) #f]
['undefined (impl-undefined-val? i)]
[`(clos ,_) (impl-clos-val? i)]
['void (void? i)]
[else (equal? m i)]))
(with-handlers ([exn:fail? (λ (_) #f)])
(let ([model-verified? (verified/cycles? expr cycles model-verified?)]
[impl-result (eval-impl-external (model->impl expr cycles) timeout)])
(and
(if model-verified?
(not (impl-rejected? impl-result))
(impl-rejected? impl-result))
(or (not model-verified?)
(match (run expr cycles steps)
[(cutoff) #t]
[(stuck _) #f]
[(non-conf _) #f]
[(answer a) (equiv-results?
a
(match impl-result
[(impl-answer v) v]
[(impl-exception _) 'error]))]))))))
;; A reimplementation of the optimizations in the implementation's
;; bytecode reader
(define (optimize expr)
(define value?
(let ([v? (redex-match bytecode v)])
(λ (e)
(or (v? e)
(match e
[`(proc-const ,_ ,_) #t]
[`(lam ,_ ,_ ,_) #t]
[`(case-lam ,_ ...) #t]
[else #f])))))
(define omittable?
(match-lambda
[`(loc ,_) #t]
[`(loc-noclr ,_) #t]
[`(loc-box ,_) #t]
[`(loc-box-noclr ,_) #t]
[`(proc-const ,_ ,_) #t]
[`(lam ,_ ,_ ,_) #t]
[`(case-lam ,_ ...) #t]
[(? (redex-match bytecode v)) #t]
[`(branch ,c ,t ,e)
(and (omittable? c)
(omittable? t)
(omittable? e))]
[`(let-one ,r ,b)
(and (omittable? r)
(omittable? b))]
[`(,(or 'let-void 'let-void-box)
,_
(,(or 'install-value 'install-value-box) 0 ,r ,b))
(and (omittable? r)
(omittable? b))]
[`(,(or 'let-void 'let-void-box) ,_ ,b)
(omittable? b)]
[`(let-rec ,_ ,b)
(omittable? b)]
[`(application void ,es ...)
(andmap omittable? es)]
[else #f]))
(let recur ([e expr])
(match e
[`(branch ,c ,t ,e)
(let ([c* (recur c)])
(if (value? c*)
(if c* (recur t) (recur e))
`(branch ,c* ,(recur t) ,(recur e))))]
[`(seq ,es ...)
(letrec ([flatten (λ (es)
(foldr (λ (e a)
(match e
[`(seq ,es ...)
(append es a)]
[else (cons e a)]))
'() es))]
; preserves tail position
[omit (λ (es kept?)
(if (null? es)
'()
(if (and (omittable? (car es))
(not (null? (cdr es))))
(omit (cdr es) kept?)
(cons (car es) (omit (cdr es) #t)))))])
(match (omit (map recur (flatten es)) #f)
['() 'void]
[`(,e) e]
[es `(seq ,@es)]))]
[(? list?) (map recur e)]
[else e])))
;; Performs a write-read round trip to see the optimizations performed
;; by the implementation's bytecode reader
(define (write-read expr)
(let-values ([(in out) (make-pipe)]
[(tmp) (make-temporary-file)])
(zo-marshal-to expr out)
(let ([optimized (parameterize ([read-accept-compiled #t])
(read in))])
(call-with-output-file tmp #:exists 'truncate
(λ (p) (write optimized p))))
(begin0
(call-with-input-file tmp zo-parse)
(delete-file tmp))))
(define-metafunction runtime
halted? : p -> b
[(halted? (V S H T ())) #t]
[(halted? error) #t]
[(halted? (V S H T (i_0 i_1 ...))) #f])
(define random-expr
(let ([generator (generate-term bytecode e)])
(λ (attempt)
(generator (inexact->exact (round (/ (log attempt) (log 5))))))))
(define random-value
(let ([generator (generate-term bytecode v)])
(λ () (generator 3))))
(define-syntax-rule (ref-or-else n e e*)
(if (zero? n) e* e))
(define cycle-targets (make-parameter '()))
;; Makes three classes of corrections:
;; 1. replaces out-of-bounds stack offsets with random in-bounds
;; ones (without checking that the new target contains an
;; appropriate value),
;; 2. replaces `ref' argument annotations with `val' annotations
;; when the procedure appears in a context that does not allow
;; `ref' arguments (but does not alter the procedure's body), and
;; 3. replaces `indirect' targets with ones in the `cycle-targets'
;; parameter (and replaces the entire `indirect' expression
;; with a random value if `cycle-targets' is empty).
(define (fix expr)
(define (localref? i)
(memq i '(loc loc-noclr loc-clr loc-box loc-box-noclr loc-box-clr)))
(let recur ([depth 0] [refs? #f] [expr expr])
(match expr
[`(,(? localref? i) ,n)
(ref-or-else depth `(,i ,(modulo n depth)) (random-value))]
[`(let-one ,e1 ,e2)
`(let-one ,(recur (add1 depth) #f e1)
,(recur (add1 depth) #f e2))]
[`(,(and (or 'let-void 'let-void-box) i) ,n ,e)
`(,i ,n ,(recur (+ depth n) #f e))]
[`(boxenv ,n ,e)
(let ([e* (recur depth #f e)])
(ref-or-else depth `(boxenv ,(modulo n depth) ,e*) e*))]
[`(,(and (or 'install-value 'install-value-box) i) ,n ,e1 ,e2)
(let ([e1* (recur depth #f e1)]
[e2* (recur depth #f e2)])
(ref-or-else depth `(,i ,(modulo n depth) ,e1* ,e2*) `(seq ,e1* ,e2*)))]
[`(application ,e ,es ...)
(let ([m (length es)])
`(application
,(recur depth #t e)
,@(map (curry recur (+ depth m) #f) es)))]
[`(,(and (or 'branch 'seq) i) ,es ...)
`(,i ,@(map (curry recur depth #f) es))]
[`(let-rec (,ls ...) ,e)
(let ([n (min depth (length ls))]
[ls* (map (curry recur depth #f) ls)])
`(let-rec ,(map (curry recur depth #f) (take ls* n))
,(recur depth #f e)))]
[`(indirect ,c)
(cond [(memq c (cycle-targets))
`(indirect ,c)]
[(not (null? (cycle-targets)))
`(indirect ,(list-ref (cycle-targets) (random (length (cycle-targets)))))]
[else
(random-value)])]
[`(proc-const (,τs ...) ,e)
(let ([n (length τs)])
`(proc-const ,(if refs? τs (build-list n (λ (_) 'val)))
,(recur n #f e)))]
[`(case-lam ,ls ...)
`(case-lam ,@(map (curry recur depth #f) ls))]
[`(lam (,τs ...) (,ns ...) ,e)
(let ([m (length τs)])
`(lam ,(if refs? τs (build-list m (λ (_) 'val)))
,(if (zero? depth)
'()
(map (curryr modulo depth) ns))
,(recur (+ m (if (zero? depth) 0 (length ns))) #f e)))]
[(? number?) expr]
['void expr]
[`(quote ,_) expr]
[(? boolean?) expr])))
(define fix-cyclic
(match-lambda
[`(,e (,xs ,es) ...)
(parameterize ([cycle-targets xs])
(cons (fix e)
(map (λ (x e) (list x (fix e)))
xs es)))]))
;; Replaces all `indirect' expressions with random values
(define no-indirects
(match-lambda
[`(indirect ,_) (random-value)]
[(? list? l) (map no-indirects l)]
[e e]))
(define (main)
(define-syntax-rule (test t k)
(match t
[#t k]
[(counterexample p)
(pretty-print p (current-error-port))
(exit 1)]))
(test (time (test-internal-properties/cycles #:attempts 4000 #:print? #f))
(test (time (test-external-properties #:attempts 250 #:print? #f))
(void))))
(module+ main (void (main)))