Compare commits

..

No commits in common. "xenia/patches" and "3.2" have entirely different histories.

245 changed files with 6398 additions and 11245 deletions

View File

@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

View File

@ -1,50 +0,0 @@
name: Docker
on:
push:
tags:
- '*'
branches:
- master
pull_request:
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Clone Repository
uses: actions/checkout@v4
- name: Configure Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=ref,event=tag
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Authenticate to Package Registry
uses: docker/login-action@v3
if: ${{ github.event_name != 'pull_request' }}
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and Publish Rosette Image
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -5,32 +5,30 @@ on: [push, pull_request]
env: env:
CVC4_URL: "http://cvc4.cs.stanford.edu/downloads/builds/x86_64-linux-opt/cvc4-1.8-x86_64-linux-opt" CVC4_URL: "http://cvc4.cs.stanford.edu/downloads/builds/x86_64-linux-opt/cvc4-1.8-x86_64-linux-opt"
BOOLECTOR_URL: "https://github.com/Boolector/boolector/archive/3.2.1.tar.gz" BOOLECTOR_URL: "https://github.com/Boolector/boolector/archive/3.2.1.tar.gz"
CVC5_URL: "https://github.com/cvc5/cvc5/releases/download/cvc5-1.0.7/cvc5-Linux"
BITWUZLA_URL: "https://github.com/bitwuzla/bitwuzla/archive/93a3d930f622b4cef0063215e63b7c3bd10bd663.tar.gz"
STP_URL: "https://github.com/stp/stp/archive/d70085462f07c8a5a2f1225f727cda3ef505b141.tar.gz"
YICES2_URL: "https://github.com/SRI-CSL/yices2/archive/e27cf308cffb0ecc6cc7165c10e81ca65bc303b3.tar.gz"
jobs: jobs:
test: test:
strategy: strategy:
matrix: matrix:
racket-version: ['8.1', 'current'] racket-version: ['7.0', 'current']
racket-variant: ['CS'] racket-variant: ['regular']
allow-failure: [false] allow-failure: [false]
include:
- racket-version: 'current'
racket-variant: 'CS'
allow-failure: true
name: Racket ${{ matrix.racket-version }} (${{ matrix.racket-variant }}) name: Racket ${{ matrix.racket-version }} (${{ matrix.racket-variant }})
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: ${{ matrix.allow-failure }} continue-on-error: ${{ matrix.allow-failure }}
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
- name: Setup Racket - name: Setup Racket
uses: Bogdanp/setup-racket@v1.14 uses: Bogdanp/setup-racket@v0.10
with: with:
architecture: x64 architecture: x64
version: ${{ matrix.racket-version }} version: ${{ matrix.racket-version }}
variant: ${{ matrix.racket-variant }} variant: ${{ matrix.racket-variant }}
- name: Install solvers - name: Install solvers
# Note that setting LD_LIBRARY_PATH can be removed once this bug is
# fixed: https://github.com/stp/stp/issues/485
run: | run: |
mkdir bin && mkdir bin &&
wget $CVC4_URL -nv -O bin/cvc4 && wget $CVC4_URL -nv -O bin/cvc4 &&
@ -46,52 +44,9 @@ jobs:
make && make &&
popd && popd &&
cp boolector/build/bin/boolector bin/ && cp boolector/build/bin/boolector bin/ &&
rm -rf boolector* && rm -rf boolector*
wget $CVC5_URL -nv -O bin/cvc5 &&
chmod +x bin/cvc5 &&
sudo apt-get update &&
sudo apt-get install -y ninja-build &&
pip3 install meson &&
wget $BITWUZLA_URL -nv -O bitwuzla.tar.gz &&
mkdir bitwuzla &&
tar xzf bitwuzla.tar.gz -C bitwuzla --strip-components=1 &&
pushd bitwuzla &&
./configure.py &&
pushd build &&
ninja &&
popd &&
popd &&
cp bitwuzla/build/src/main/bitwuzla bin/ &&
sudo apt-get install -y build-essential git cmake bison flex libboost-all-dev libtinfo-dev python3 perl &&
wget $STP_URL -nv -O stp.tar.gz &&
mkdir stp &&
tar xzf stp.tar.gz -C stp --strip-components=1 &&
pushd stp &&
echo "LD_LIBRARY_PATH=$PWD/deps/cadical/build:$PWD/deps/cadiback/:$LD_LIBRARY_PATH" >> $GITHUB_ENV &&
./scripts/deps/setup-gtest.sh &&
./scripts/deps/setup-outputcheck.sh &&
./scripts/deps/setup-cms.sh &&
./scripts/deps/setup-minisat.sh &&
mkdir build &&
pushd build &&
cmake .. &&
cmake --build . &&
popd &&
popd &&
ln -s stp/build/stp bin/stp &&
sudo apt-get install -y gperf &&
wget $YICES2_URL -nv -O yices2.tar.gz &&
mkdir yices2 &&
tar xvf yices2.tar.gz -C yices2 --strip-components=1 &&
pushd yices2 &&
autoconf &&
./configure --prefix=$PWD/out/ &&
make &&
make install &&
popd &&
cp yices2/out/bin/yices-smt2 bin/yices-smt2
- name: Install Rosette - name: Install Rosette
run: raco pkg install --auto --name rosette run: raco pkg install --auto
- name: Compile Rosette tests - name: Compile Rosette tests
run: raco make test/all-rosette-tests.rkt run: raco make test/all-rosette-tests.rkt
- name: Run Rosette tests - name: Run Rosette tests

View File

@ -1,97 +0,0 @@
FROM alpine:3.15
## ========================== [ Install Racket ] =========================== ##
## Define default Racket version and variant. The Racket version is of the form
## <major>.<minor>. The variant can be "cs" (Chez Scheme), "bc" (Before Chez) or
## "natipkg" (where external libraries are included in the Racket packages).
##
ARG RACKET_VERSION=8.4
ARG RACKET_VARIANT=cs
## Install Racket. We first install system dependencies: [gcompat] is needed for
## Racket and [ncurses] is needed for the [xrepl] and [expeditor] packages,
## providing the REPL. We then download the installer, run it with the right
## parameters, then remove it. After that, all that remains is to set-up the
## Racket packages and install [expeditor]. See later for a description of the
## arguments to [raco pkg install].
##
RUN apk add --no-cache gcompat ncurses
RUN wget "https://download.racket-lang.org/installers/${RACKET_VERSION}/racket-minimal-${RACKET_VERSION}-x86_64-linux-${RACKET_VARIANT}.sh"
RUN echo 'yes\n1\n' | sh racket-minimal-${RACKET_VERSION}-x86_64-linux-${RACKET_VARIANT}.sh --create-dir --unix-style --dest /usr/
RUN rm racket-minimal-${RACKET_VERSION}-x86_64-linux-${RACKET_VARIANT}.sh
RUN raco setup --no-docs
RUN raco pkg install -i --batch --auto --no-docs expeditor-lib
## =================== [ Install Rosette's Dependencies ] =================== ##
## Work on Rosette's installation within /usr/local. This directory will be
## cleaned up later on so it could be anything.
##
WORKDIR /usr/local/rosette
## Get all the info.rkt files. Trying to install Rosette based only on these
## files would fail, but we can use them to only install dependencies.
##
COPY info.rkt .
COPY rosette/info.rkt rosette/
## Install only Rosette's dependencies. We have to install the external
## dependencies [libstdc++] and [libgcc] because Z3 needs them at runtime. As
## for the Racket dependencies only, we achieve that in three steps:
##
## 1. We use [raco pkg install --no-setup] to download and register Rosette
## and all its dependencies without setting them up, that is without
## compiling them. At this point, the system is in an inconsistent state,
## where packages are registered but not actually present. The other flags
## are the following:
##
## -i install packages for all users
## --batch disable interactive mode and suppress prompts
## --auto download missing packages automatically
##
## 2. We use [raco pkg remove --no-setup] to unregister Rosette. This keeps
## the dependencies as registered. The system is still in an inconsistent
## state. See above for the flags.
##
## 3. We use [raco setup] to set up all the registered package. This brings
## the system back in a consistent state. Since Rosette's dependencies were
## registered but not Rosette itself, this achieves our goal. The flags are
## the following:
##
## --fail-fast fail on the first error encountered
## --no-docs do not compile the documentations
##
RUN apk add --no-cache libstdc++ libgcc
RUN raco pkg install -i --batch --auto --no-setup ../rosette
RUN raco pkg remove -i --no-setup rosette
RUN raco setup --fail-fast --no-docs
## ========================== [ Install Rosette ] =========================== ##
## Get all of Rosette; build and install it. The dependencies should all be
## installed, so we can remove the --auto flag which will lead us to failure if
## a dependency cannot be found. The additional flags are the following:
##
## --copy copy content to install path (instead of linking)
##
COPY . .
RUN raco pkg install -i --batch --copy --no-docs ./rosette
RUN rm -R /usr/local/rosette
## ===================== [ Prepare Clean Entry Point ] ====================== ##
## For further use of the image, we can start with user `rosette`, group
## `rosette` in `/rosette` by default.
##
RUN addgroup rosette
RUN adduser --system --shell /bin/false --disabled-password \
--home /rosette --ingroup rosette rosette
RUN chown -R rosette:rosette /rosette
USER rosette
WORKDIR /rosette
## Rosette files are simply Racket files using the Rosette library: the default
## entry point of this image is therefore the Racket executable.
##
ENTRYPOINT ["/usr/bin/racket", "-I", "rosette"]

View File

@ -1,30 +1,5 @@
# Release Notes # Release Notes
## Version 4.1
This is a minor bug-fixing release.
## Version 4.0
This is a major release with significant changes to the language and the runtime. Rosette 4.0 is *not backward compatible* with Rosette 3.x. But porting Rosette 3.x code to Rosette 4.0 should be straightforward for most applications.
This release includes the following features:
- Support for assumptions (see `assume`).
- New symbolic evaluation core that tracks verification conditions (VCs) rather than path conditions and assertions.
- New symbolic reflection constructs for working with VCs, including `vc`, `with-vc`, and `clear-vc!`.
- New symbolic reflection facilities for managing symbolic `terms`, including the option of using a garbage-collected data structure.
- Updated `verify`, `synthesize`, `solve`, and `optimize` queries.
- New synthesis library with efficient support for grammar holes (see `define-grammar`).
- New list and vector operators that use bitvectors instead of integers.
- Updates to The Rosette Guide to document the new language in detail.
The following features have been removed:
- The `debug` query.
- Reflection facilities for working with path conditions and assertions: `pc`, `with-asserts`, `with-asserts-only`, `clear-asserts!`, and `asserts`.
- Support for CPLEX.
## Version 3.2 ## Version 3.2
This release includes minor updates and a new [value destructuring library]. This release includes minor updates and a new [value destructuring library].

View File

@ -9,7 +9,7 @@ The Rosette Language
The easiest way to install Rosette is from Racket's package manager: The easiest way to install Rosette is from Racket's package manager:
* Download and install Racket 8.1 or later from http://racket-lang.org * Download and install Racket 7.0 or later from http://racket-lang.org
* Use Racket's `raco` tool to install Rosette: * Use Racket's `raco` tool to install Rosette:
@ -19,7 +19,7 @@ The easiest way to install Rosette is from Racket's package manager:
Alternatively, you can install Rosette from source: Alternatively, you can install Rosette from source:
* Download and install Racket 8.1 or later from http://racket-lang.org * Download and install Racket 7.0 or later from http://racket-lang.org
* Clone the rosette repository: * Clone the rosette repository:
@ -63,7 +63,7 @@ Alternatively, you can install Rosette from source:
* The `rosette` language includes all of Racket. This places the burden * The `rosette` language includes all of Racket. This places the burden
on the programmer to decide whether a given Racket construct (which on the programmer to decide whether a given Racket construct (which
is not overridden by Rosette) is safe to use in a given context. is not overriden by Rosette) is safe to use in a given context.
Rosette provides no guarantees or checks for programs that use Rosette provides no guarantees or checks for programs that use
unsafe constructs. In the best case, such a program will fail with unsafe constructs. In the best case, such a program will fail with
an exception if a symbolic value flows to a construct that does not an exception if a symbolic value flows to a construct that does not

View File

@ -5,7 +5,7 @@
(define deps '("custom-load" (define deps '("custom-load"
"sandbox-lib" "sandbox-lib"
"scribble-lib" "scribble-lib"
("racket" #:version "8.1") ("racket" #:version "7.0")
"r6rs-lib" "r6rs-lib"
"rfc6455" "rfc6455"
"net-lib" "net-lib"
@ -28,4 +28,4 @@
(define test-omit-paths (if (getenv "PLT_PKG_BUILD_SERVICE") 'all '())) (define test-omit-paths (if (getenv "PLT_PKG_BUILD_SERVICE") 'all '()))
(define pkg-desc "Rosette solver-aided host language") (define pkg-desc "Rosette solver-aided host language")
(define version "4.0") (define version "3.0")

View File

@ -2,7 +2,7 @@
(require (for-syntax racket/syntax "../core/lift.rkt") racket/provide (require (for-syntax racket/syntax "../core/lift.rkt") racket/provide
"../core/safe.rkt" "generic.rkt" "../core/safe.rkt" "generic.rkt"
(only-in "../core/store.rkt" store!) (only-in "../core/effects.rkt" apply!)
(only-in "../core/type.rkt" define-lifted-type type-cast) (only-in "../core/type.rkt" define-lifted-type type-cast)
(only-in "../core/equality.rkt" @eq? @equal?) (only-in "../core/equality.rkt" @eq? @equal?)
(only-in "../core/bool.rkt" instance-of? && ||) (only-in "../core/bool.rkt" instance-of? && ||)
@ -40,15 +40,12 @@
[(box v) v] [(box v) v]
[(union vs) (apply merge* (for/list ([gv vs]) (cons (car gv) (unbox (cdr gv)))))])) [(union vs) (apply merge* (for/list ([gv vs]) (cons (car gv) (unbox (cdr gv)))))]))
(define (box-ref x idx) (unbox x)) ; For the purpose of tracking mutations to the store,
(define (box-set! x idx v) (set-box! x v)) ; boxes are treated as 1-element vectors that ignore the index argument.
(define (@set-box! b v) (define (@set-box! b v)
(match (type-cast @box? b 'set-box!) (match (type-cast @box? b 'set-box!)
[(? box? x) [(? box? x)
(store! x 0 v box-ref box-set!)] (apply! set-box! unbox x v)]
[(union vs) [(union vs)
(for ([gv vs]) (for ([gv vs])
(let ([x (cdr gv)]) (let ([x (cdr gv)])
(store! x 0 (merge (car gv) v (unbox x)) box-ref box-set!)))])) (apply! set-box! unbox x (merge (car gv) v (unbox x)))))]))

View File

@ -1,161 +0,0 @@
#lang racket
(require
(only-in "list.rkt" @list?)
(only-in "vector.rkt" @vector? @vector-set!)
(only-in "../core/lift.rkt" lift-id)
(only-in "../core/forall.rkt" for/all for*/all)
(only-in "../core/term.rkt" get-type type-cast term?)
"../core/union.rkt"
"../core/bitvector.rkt"
"../core/merge.rkt"
"../core/safe.rkt")
(provide @list-ref-bv @list-set-bv
@take-bv @take-right-bv
@drop-bv @drop-right-bv @list-tail-bv
@split-at-bv @split-at-right-bv
@length-bv
@vector-ref-bv @vector-set!-bv @vector-length-bv)
(define (bv-lit-or-term? v)
(or (bv? v) (and (term? v) (bitvector? (get-type v)))))
(define-syntax-rule (lift-body #:with (id xs idx seq-length) #:type t #:max n #:body body ...)
(let* ([t (get-type idx)]
[2^k (expt 2 (bitvector-size t))]
[sz (seq-length xs)]
[n (min sz 2^k)])
(when (>= (- 2^k 1) sz)
(assert (@bvult idx (@integer->bitvector sz t))
(index-too-large-error 'id xs idx)))
body ...))
(define-syntax (define-lift-bv stx)
(syntax-case stx ()
[(_ (proc-bv xs idx arg ...) @seq? seq?)
#`(define-lift-bv #,(lift-id #'proc-bv) (proc-bv xs idx arg ...) @seq? seq?)]
[(_ @proc-bv (proc-bv xs idx arg ...) @seq? seq?)
#'(define (@proc-bv xs idx arg ...)
(if (and (seq? xs) (bv-lit-or-term? idx))
(proc-bv xs idx arg ...)
(match* ((type-cast @seq? xs 'proc-bv)
(bvcoerce idx 'proc-bv))
[((? seq? xs) (? bv-lit-or-term? idx))
(proc-bv xs idx arg ...)]
[(xs idx)
(for*/all ([xs xs][idx idx])
(proc-bv xs idx arg ...))])))]))
(define-syntax (define-length-bv stx)
(syntax-case stx ()
[(_ length-bv @seq? seq? seq-length)
#`(begin
(define (length-bv xs t) ; (-> seq bitvector? @bv?)
(@integer->bitvector (seq-length xs) t))
(define (#,(lift-id #'length-bv) xs t)
(match (type-cast @seq? xs 'length-bv)
[(? seq? xs) (length-bv xs t)]
[xs (for/all ([xs xs]) (length-bv xs t))])))]))
(define-syntax-rule (define-ref-bv ref-bv @seq? seq? seq-ref seq-length)
(begin
(define (ref-bv xs idx) ; (-> type? bv-lit-or-term? any/c)
(if (bv? idx)
(seq-ref xs (@bitvector->natural idx))
(lift-body
#:with (ref-bv xs idx seq-length)
#:type t
#:max n
#:body
(apply
merge*
(for/list ([x xs] [i n])
(cons (@bveq (bv i t) idx) x))))))
(define-lift-bv (ref-bv xs idx) @seq? seq?)))
; ---- list bv procedures ---- ;
(define-length-bv length-bv @list? list? length)
(define-ref-bv list-ref-bv @list? list? list-ref length)
(define (list-set-bv xs idx v)
(if (bv? idx)
(list-set xs (@bitvector->natural idx) v)
(lift-body
#:with (list-set-bv xs idx length)
#:type t
#:max n
#:body (for/list ([(x i) (in-indexed xs)])
(if (< i n)
(merge (@bveq (bv i t) idx) v x)
x)))))
(define-lift-bv (list-set-bv xs idx v) @list? list?)
(define (pair-length ps bound)
(if (list? ps)
(min (length ps) bound)
(let loop ([ps ps] [acc 0])
(if (and (pair? ps) (< acc bound))
(loop (cdr ps) (add1 acc))
acc))))
(define-syntax (define-get-bv stx)
(syntax-case stx ()
[(_ get-bv seq-get)
#`(begin
(define (get-bv xs idx)
(if (bv? idx)
(seq-get xs (@bitvector->natural idx))
(let* ([t (get-type idx)]
[2^k (expt 2 (bitvector-size t))]
[sz (pair-length xs (sub1 2^k))])
(when (> (- 2^k 1) sz)
(assert (@bvule idx (@integer->bitvector sz t))
(index-too-large-error 'id xs idx)))
(apply
merge*
(for/list ([i (add1 sz)])
(cons (@bveq (bv i t) idx)
(seq-get xs i)))))))
(define (#,(lift-id #'get-bv) xs idx)
(if (and (not (union? xs)) (bv-lit-or-term? idx))
(get-bv xs idx)
(match* (xs (bvcoerce idx 'get-bv))
[((not (? union? xs)) (? bv-lit-or-term? idx))
(get-bv xs idx)]
[(xs idx)
(for*/all ([xs xs][idx idx])
(get-bv xs idx))]))))]))
(define-get-bv take-bv take)
(define-get-bv take-right-bv take-right)
(define-get-bv drop-bv drop)
(define-get-bv drop-right-bv drop-right)
(define-get-bv list-tail-bv list-tail)
(define (@split-at-bv xs idx)
(values (@take-bv xs idx) (@drop-bv xs idx)))
(define (@split-at-right-bv xs idx)
(values (@drop-right-bv xs idx) (@take-right-bv xs idx)))
; ---- vector bv procedures ---- ;
(define (vector-set!-bv xs idx v)
(if (bv? idx)
(@vector-set! xs (@bitvector->natural idx) v)
(lift-body
#:with (vector-set!-bv xs idx vector-length)
#:type t
#:max n
#:body
(for ([x xs] [i n])
(@vector-set! xs i (merge (@bveq (bv i t) idx) v x))))))
(define-length-bv vector-length-bv @vector? vector? vector-length)
(define-ref-bv vector-ref-bv @vector? vector? vector-ref vector-length)
(define-lift-bv (vector-set!-bv xs idx v) @vector? vector?)

View File

@ -99,7 +99,7 @@
;; List Iteration ;; List Iteration
(define (bad-lengths-error name . args) (define (bad-lengths-error name . args)
(argument-error name "lists of equal length" (map ~.a args))) (thunk (error name "all lists must have same size\n given: ~a" (map ~.a args))))
(define (lengths xs) (define (lengths xs)
(match xs (match xs
@ -282,6 +282,20 @@
(@+ rank (@if (ranked>? (key-of x) i (key-of y) j) 1 0))))]) (@+ rank (@if (ranked>? (key-of x) i (key-of y) j) 1 0))))])
(for/list ([i len]) (for/list ([i len])
(for/fold ([v 0]) ([x xs] [r ranks]) (merge (@= i r) x v))))])) (for/fold ([v 0]) ([x xs] [r ranks]) (merge (@= i r) x v))))]))
#|(define vars (for/list ([i (in-range len)]) (define-symbolic* rank @integer?) rank))
(for ([v vars])
(assert (@<= 0 v))
(assert (@< v len)))
(let loop ([vars vars] [xs l])
(match* (vars xs)
[((or (list) (list _)) _) (void)]
[((list v v-rest ...) (list x x-rest ...))
(let ([key (key-of x)])
(for ([v1 v-rest] [x1 x-rest])
(assert (@if (less? key (key-of x1)) (@< v v1) (@< v1 v)))))
(loop v-rest x-rest)]))
(for/list ([i (in-range (length l))])
(apply merge* (for/list ([x l] [v vars]) (cons (@= v i) x))))]))|#
(define (fast-sort less? getkey cache-keys? xs) (define (fast-sort less? getkey cache-keys? xs)
(sort xs less? #:key getkey #:cache-keys? cache-keys?)) (sort xs less? #:key getkey #:cache-keys? cache-keys?))
(define/lift/applicator fast-sort less? getkey cache-keys? xs) (define/lift/applicator fast-sort less? getkey cache-keys? xs)
@ -429,8 +443,8 @@
(define @apply (define @apply
(case-lambda [() (assert #f (argument-error 'apply "at least 2 arguments" 0))] (case-lambda [() (error 'apply "arity mismatch;\n expected: at least 2\n given: 0")]
[(proc) (assert #f (argument-error 'apply "at least 2 arguments" 1))] [(proc) (error 'apply "arity mismatch;\n expected: at least 2\n given: 1")]
[(proc xs) (lift/apply/higher-order apply proc xs : list? -> @list?)] [(proc xs) (lift/apply/higher-order apply proc xs : list? -> @list?)]
[(proc x0 xs) (lift/apply/higher-order apply proc x0 xs : list? -> @list?)] [(proc x0 xs) (lift/apply/higher-order apply proc x0 xs : list? -> @list?)]
[(proc x0 x1 xs) (lift/apply/higher-order apply proc x0 x1 xs : list? -> @list?)] [(proc x0 x1 xs) (lift/apply/higher-order apply proc x0 x1 xs : list? -> @list?)]
@ -510,26 +524,29 @@
(merge** ys (insert* _ i v))])))) (merge** ys (insert* _ i v))]))))
(splicing-local (splicing-local
[(define ($list-set xs i v) [(define (replace xs i v)
(for/list ([(x idx) (in-indexed xs)]) (let-values ([(left right) (split-at xs i)])
(merge (@= i idx) v x)))] (append left (cons v (cdr right)))))
(define (@list-set xs i v) (define (replace* xs i v)
(or (and (list? xs) (number? i) (list-set xs i v)) (apply merge* (for/list ([(x idx) (in-indexed xs)])
(match* ((type-cast @list? xs 'list-set) (type-cast @integer? i 'list-set)) (cons (@= i idx) (replace xs idx v)))))]
[((? list? xs) (? number? i)) (list-set xs i v)] (define (@replace xs i v)
(or (and (list? xs) (number? i) (replace xs i v))
(match* ((type-cast @list? xs 'replace) (type-cast @integer? i 'replace))
[((? list? xs) (? number? i)) (replace xs i v)]
[((? list? xs) i) [((? list? xs) i)
(assert-bound [0 @<= i @< (length xs)] 'list-set) (assert-bound [0 @<= i @< (length xs)] 'replace)
($list-set xs i v)] (replace* xs i v)]
[((union ys) (? number? i)) [((union ys) (? number? i))
(assert-bound [0 <= i] 'list-set) (assert-bound [0 <= i] 'replace)
(apply merge* (assert-some (apply merge* (assert-some
(for/list ([y ys] #:when (< i (length (cdr y)))) (for/list ([y ys] #:when (< i (length (cdr y))))
(cons (car y) (list-set (cdr y) i v))) (cons (car y) (replace (cdr y) i v)))
#:unless (length ys) #:unless (length ys)
(index-too-large-error 'list-set xs i)))] (index-too-large-error 'replace xs i)))]
[((union ys) i) [((union ys) i)
(assert-bound [0 @<= i @< (@length xs)] 'list-set) (assert-bound [0 @<= i @< (@length xs)] 'replace)
(merge** ys ($list-set _ i v))])))) (merge** ys (replace* _ i v))]))))
#| #|

View File

@ -6,7 +6,7 @@
"../core/safe.rkt" "../core/lift.rkt" "seq.rkt" "../core/forall.rkt" "generic.rkt" "../core/safe.rkt" "../core/lift.rkt" "seq.rkt" "../core/forall.rkt" "generic.rkt"
(only-in "list.rkt" @list?) (only-in "list.rkt" @list?)
(only-in "../form/control.rkt" @when) (only-in "../form/control.rkt" @when)
(only-in "../core/store.rkt" store!) (only-in "../core/effects.rkt" apply!)
(only-in "../core/term.rkt" define-lifted-type @any/c type-cast) (only-in "../core/term.rkt" define-lifted-type @any/c type-cast)
(only-in "../core/equality.rkt" @eq? @equal?) (only-in "../core/equality.rkt" @eq? @equal?)
(only-in "../core/bool.rkt" instance-of? && ||) (only-in "../core/bool.rkt" instance-of? && ||)
@ -58,15 +58,16 @@
(define (merge-set! vec idx val guard) (define (merge-set! vec idx val guard)
(for ([i (in-range (vector-length vec))]) (for ([i (in-range (vector-length vec))])
(store! vec i (merge (&& guard (@= i idx)) val (vector-ref vec i)) vector-ref vector-set!))) (apply! vector-set! vector-ref
vec i (merge (&& guard (@= i idx)) val (vector-ref vec i)))))
(define (@vector-set! vec idx val) (define (@vector-set! vec idx val)
;(printf "vector-set! ~a ~a ~a\n" (eq-hash-code vec) idx val) ;(printf "vector-set! ~a ~a ~a\n" (eq-hash-code vec) idx val)
(if (and (vector? vec) (number? idx)) (if (and (vector? vec) (number? idx))
(store! vec idx val vector-ref vector-set!) (apply! vector-set! vector-ref vec idx val)
(match* ((type-cast @vector? vec 'vector-set!) (type-cast @integer? idx 'vector-set!)) (match* ((type-cast @vector? vec 'vector-set!) (type-cast @integer? idx 'vector-set!))
[((? vector? vs) (? number? idx)) [((? vector? vs) (? number? idx))
(store! vs idx val vector-ref vector-set!)] (apply! vector-set! vector-ref vs idx val)]
[((? vector? vs) idx) [((? vector? vs) idx)
(assert-bound [0 @<= idx @< (vector-length vs)] 'vector-set!) (assert-bound [0 @<= idx @< (vector-length vs)] 'vector-set!)
(merge-set! vs idx val #t)] (merge-set! vs idx val #t)]
@ -75,7 +76,8 @@
(assert-|| (for/list ([v vs] #:when (< idx (vector-length (cdr v)))) (assert-|| (for/list ([v vs] #:when (< idx (vector-length (cdr v))))
(let ([guard (car v)] (let ([guard (car v)]
[vec (cdr v)]) [vec (cdr v)])
(store! vec idx (merge guard val (vector-ref vec idx)) vector-ref vector-set!) (apply! vector-set! vector-ref
vec idx (merge guard val (vector-ref vec idx)))
guard)) guard))
#:unless (length vs) #:unless (length vs)
(index-too-large-error 'vector-set! vec idx))] (index-too-large-error 'vector-set! vec idx))]
@ -88,13 +90,14 @@
(match (type-cast @vector? vec 'vector-fill!) (match (type-cast @vector? vec 'vector-fill!)
[(? vector? vs) [(? vector? vs)
(for ([i (in-range (vector-length vs))]) (for ([i (in-range (vector-length vs))])
(store! vs i val vector-ref vector-set!))] (apply! vector-set! vector-ref vs i val))]
[(union vs) [(union vs)
(for ([v vs]) (for ([v vs])
(let ([guard (car v)] (let ([guard (car v)]
[vec (cdr v)]) [vec (cdr v)])
(for ([i (in-range (vector-length vec))]) (for ([i (in-range (vector-length vec))])
(store! vec i (merge guard val (vector-ref vec i)) vector-ref vector-set!))))])) (apply! vector-set! vector-ref
vec i (merge guard val (vector-ref vec i))))))]))
; Vector copy helper procedure. Requires dest and src to be ; Vector copy helper procedure. Requires dest and src to be
; vectors (rather than unions of vectors), and dest-start, src-start ; vectors (rather than unions of vectors), and dest-start, src-start

View File

@ -7,18 +7,17 @@
"core/bool.rkt" "core/real.rkt" "core/numerics.rkt" "core/bitvector.rkt" "core/bvlib.rkt" "core/bool.rkt" "core/real.rkt" "core/numerics.rkt" "core/bitvector.rkt" "core/bvlib.rkt"
"core/function.rkt" "core/function.rkt"
"core/procedure.rkt" "core/equality.rkt" "core/distinct.rkt" "core/reflect.rkt" "core/procedure.rkt" "core/equality.rkt" "core/distinct.rkt" "core/reflect.rkt"
"adt/box.rkt" "adt/list.rkt" "adt/vector.rkt" "adt/bvseq.rkt" "adt/box.rkt" "adt/list.rkt" "adt/vector.rkt"
"struct/struct.rkt" "struct/generics.rkt" "struct/struct.rkt" "struct/generics.rkt"
"form/define.rkt" "form/control.rkt" "form/module.rkt" "form/app.rkt") "form/state.rkt" "form/define.rkt" "form/control.rkt" "form/module.rkt" "form/app.rkt")
(provide (provide
(rename-out [@|| ||]) ; The character sequence || does not play nicely with the filtered-out form. (rename-out [@|| ||]) ; The character sequence || does not play nicely with the filtered-out form.
(filtered-out drop@ (filtered-out drop@
(combine-out (combine-out
; core/bool.rkt ; core/bool.rkt
vc with-vc clear-vc! vc? vc-true? vc-true vc-assumes vc-asserts pc with-asserts with-asserts-only asserts clear-asserts!
@assert @assume @assert @boolean? @false? @! @&& @=> @<=> @forall @exists
@boolean? @false? @! @&& @=> @<=> @forall @exists
; core/real.rkt ; core/real.rkt
@integer? @real? @= @< @<= @>= @> @integer? @real? @= @< @<= @>= @>
@+ @* @- @/ @quotient @remainder @modulo @abs @+ @* @- @/ @quotient @remainder @modulo @abs
@ -33,7 +32,6 @@
@bvnot @bvor @bvand @bvxor @bvshl @bvlshr @bvashr @bvnot @bvor @bvand @bvxor @bvshl @bvlshr @bvashr
@bvneg @bvadd @bvsub @bvmul @bvudiv @bvsdiv @bvurem @bvsrem @bvsmod @bvneg @bvadd @bvsub @bvmul @bvudiv @bvsdiv @bvurem @bvsrem @bvsmod
@concat @extract @sign-extend @zero-extend @concat @extract @sign-extend @zero-extend
@z3_ext_rotate_left @z3_ext_rotate_right
@integer->bitvector @bitvector->integer @bitvector->natural @integer->bitvector @bitvector->integer @bitvector->natural
; core/bvlib.rkt ; core/bvlib.rkt
bit lsb msb bvzero? bvadd1 bvsub1 bit lsb msb bvzero? bvadd1 bvsub1
@ -48,14 +46,11 @@
@eq? @equal? @eq? @equal?
; core/reflect.rkt ; core/reflect.rkt
symbolics type? solvable? @any/c type-of type-cast for/all for*/all symbolics type? solvable? @any/c type-of type-cast for/all for*/all
symbolic? concrete?
term? constant? expression? term? constant? expression?
term expression constant term-type term expression constant term-type
term=? term->datum term=? term->datum clear-terms! term-cache
terms terms-count terms-ref with-terms clear-terms! gc-terms!
union? union union-contents union-guards union-values union? union union-contents union-guards union-values
union-filter in-union in-union* in-union-guards in-union-values union-filter in-union in-union* in-union-guards in-union-values
result? result-value result-state normal normal? failed failed?
; adt/box.rkt ; adt/box.rkt
@box @box-immutable @box? @unbox @set-box! @box @box-immutable @box? @unbox @set-box!
; adt/list.rkt : Pair Constructors and Selectors ; adt/list.rkt : Pair Constructors and Selectors
@ -79,9 +74,9 @@
@take @drop @split-at @take-right @drop-right @split-at-right @take @drop @split-at @take-right @drop-right @split-at-right
@add-between @append* @flatten @remove-duplicates @add-between @append* @flatten @remove-duplicates
@filter-map @count @partition @append-map @filter-not @shuffle @filter-map @count @partition @append-map @filter-not @shuffle
@argmin @argmax @list-set @argmin @argmax
; adt/list.rkt : Non-Standard Functions ; adt/list.rkt : Non-Standard Functions
@insert @insert @replace
; adt/vector.rkt : Basic Functions ; adt/vector.rkt : Basic Functions
@vector? @vector @vector-immutable @vector? @vector @vector-immutable
@vector-length @vector-ref @vector-set! @vector->list @list->vector @vector->immutable-vector @vector-length @vector-ref @vector-set! @vector->list @list->vector @vector->immutable-vector
@ -90,16 +85,12 @@
@vector-append @vector-append
; adt/procedure.rkt ; adt/procedure.rkt
@procedure? @apply @procedure-rename @negate @void? @procedure? @apply @procedure-rename @negate @void?
; adt/bvseq.rkt
@list-ref-bv @list-set-bv @length-bv
@take-bv @take-right-bv
@drop-bv @drop-right-bv @list-tail-bv
@split-at-bv @split-at-right-bv
@vector-ref-bv @vector-set!-bv @vector-length-bv
; struct/struct.rkt ; struct/struct.rkt
struct struct-field-index define/generic define-struct struct struct-field-index define/generic define-struct
; struct/generics.rkt ; struct/generics.rkt
@define-generics @make-struct-type-property @define-generics @make-struct-type-property
; form/state.rkt
current-oracle oracle oracle?
; form/define.rkt ; form/define.rkt
define-symbolic define-symbolic* define-symbolic define-symbolic*
; form/control.rkt ; form/control.rkt

View File

@ -14,19 +14,21 @@
@bvnot @bvor @bvand @bvxor @bvshl @bvlshr @bvashr @bvnot @bvor @bvand @bvxor @bvshl @bvlshr @bvashr
@bvneg @bvadd @bvsub @bvmul @bvudiv @bvsdiv @bvurem @bvsrem @bvsmod @bvneg @bvadd @bvsub @bvmul @bvudiv @bvsdiv @bvurem @bvsrem @bvsmod
@concat @extract @sign-extend @zero-extend @concat @extract @sign-extend @zero-extend
@z3_ext_rotate_left @z3_ext_rotate_right
@integer->bitvector @bitvector->integer @bitvector->natural) @integer->bitvector @bitvector->integer @bitvector->natural)
;; ----------------- Bitvector Types ----------------- ;; ;; ----------------- Bitvector Types ----------------- ;;
; Cache of all bitvector types constructed so far, mapping sizes to types. ; Cache of all bitvector types constructed so far, mapping sizes to types.
(define bitvector-types (make-hasheq)) (define bitvector-types (make-hash))
; Returns the bitvector type of the given size. ; Returns the bitvector type of the given size.
(define (bitvector-type size) (define (bitvector-type size)
(assert (and (exact-positive-integer? size) (fixnum? size)) (unless (exact-positive-integer? size)
(argument-error 'bitvector "(and/c exact-positive-integer? fixnum?)" size)) (raise-argument-error 'bitvector "exact-positive-integer?" size))
(hash-ref! bitvector-types size (λ () (bitvector size)))) (or (hash-ref bitvector-types size #f)
(let ([t (bitvector size)])
(hash-set! bitvector-types size t)
t)))
; Represents a bitvector type. ; Represents a bitvector type.
(struct bitvector (size) (struct bitvector (size)
@ -51,9 +53,9 @@
[(bv _ (== self)) v] [(bv _ (== self)) v]
[(term _ (== self)) v] [(term _ (== self)) v]
[(union (list _ ... (cons gt (and (? typed? vt) (app get-type (== self)))) _ ...) _) [(union (list _ ... (cons gt (and (? typed? vt) (app get-type (== self)))) _ ...) _)
(assert gt (type-error caller self v)) (assert gt (thunk (error caller "expected ~a, given ~.a" self v)))
vt] vt]
[_ (assert #f (type-error caller self v))])) [_ (assert #f (thunk (error caller "expected ~a, given ~.a" self v)))]))
(define (type-eq? self u v) (@bveq u v)) (define (type-eq? self u v) (@bveq u v))
(define (type-equal? self u v) (@bveq u v)) (define (type-equal? self u v) (@bveq u v))
(define (type-compress self f? ps) (generic-merge* ps)) (define (type-compress self f? ps) (generic-merge* ps))
@ -119,14 +121,14 @@
; be either an exact-positive-integer? or a bitvector type. ; be either an exact-positive-integer? or a bitvector type.
; The number may be a real, non-infinite, non-NaN concrete value. ; The number may be a real, non-infinite, non-NaN concrete value.
(define (make-bv val precision) (define (make-bv val precision)
(assert (and (real? val) (not (infinite? val)) (not (nan? val))) (unless (and (real? val) (not (infinite? val)) (not (nan? val)))
(arguments-error 'bv "expected a real, non-infinite, non-NaN number" "value" val)) (raise-arguments-error 'bv "expected a real, non-infinite, non-NaN number" "value" val))
(cond [(exact-positive-integer? precision) (cond [(exact-positive-integer? precision)
(bv (sfinitize val precision) (bitvector-type precision))] (bv (sfinitize val precision) (bitvector-type precision))]
[(bitvector? precision) [(bitvector? precision)
(bv (sfinitize val (bitvector-size precision)) precision)] (bv (sfinitize val (bitvector-size precision)) precision)]
[else [else
(assert #f (arguments-error 'bv "exact-positive-integer? or bitvector? type" "precision" precision))])) (raise-arguments-error 'bv "exact-positive-integer? or bitvector? type" "precision" precision)]))
; Pattern matching for bitvector literals. ; Pattern matching for bitvector literals.
(define-match-expander @bv (define-match-expander @bv
@ -339,13 +341,6 @@
(ite (bveq (bv 0 t) (bvand x (bv (bvsmin t) t))) (bv 0 t) (bv -1 t))] (ite (bveq (bv 0 t) (bvand x (bv (bvsmin t) t))) (bv 0 t) (bv -1 t))]
[(_ _) (expression @bvashr x y)])) [(_ _) (expression @bvashr x y)]))
(define (z3_ext_rotate_left x y)
(expression @z3_ext_rotate_left x y))
(define (z3_ext_rotate_right x y)
(expression @z3_ext_rotate_right x y))
(define-lifted-operator @bvnot bvnot T*->T) (define-lifted-operator @bvnot bvnot T*->T)
(define-lifted-operator @bvand bvand T*->T) (define-lifted-operator @bvand bvand T*->T)
(define-lifted-operator @bvor bvor T*->T) (define-lifted-operator @bvor bvor T*->T)
@ -353,8 +348,6 @@
(define-lifted-operator @bvshl bvshl T*->T) (define-lifted-operator @bvshl bvshl T*->T)
(define-lifted-operator @bvlshr bvlshr T*->T) (define-lifted-operator @bvlshr bvlshr T*->T)
(define-lifted-operator @bvashr bvashr T*->T) (define-lifted-operator @bvashr bvashr T*->T)
(define-lifted-operator @z3_ext_rotate_left z3_ext_rotate_left T*->T)
(define-lifted-operator @z3_ext_rotate_right z3_ext_rotate_right T*->T)
;; ----------------- Simplification ruules for bitwise operators ----------------- ;; ;; ----------------- Simplification ruules for bitwise operators ----------------- ;;
@ -603,7 +596,7 @@
_)) _))
(if (< i size) (if (< i size)
(extract i j a) (extract i j a)
(expression @bvop a (bitvector-type (add1 i))))] (expression @bvop a (bitvector (add1 i))))]
[(_ _ _) (expression @extract i j x)])) [(_ _ _) (expression @extract i j x)]))
(define-operator @extract (define-operator @extract

View File

@ -1,17 +1,13 @@
#lang racket #lang racket
(require "term.rkt" "union.rkt" "exn.rkt" "result.rkt" "reporter.rkt") (require "term.rkt" "union.rkt" "exn.rkt")
(provide (provide @boolean? @false?
;; ---- lifted boolean? operations ---- ;;
@boolean? @false? @true?
! && || => <=> @! @&& @|| @=> @<=> @exists @forall ! && || => <=> @! @&& @|| @=> @<=> @exists @forall
and-&& or-|| instance-of? T*->boolean? and-&& or-|| instance-of?
;; ---- VC generation ---- ;; @assert pc with-asserts with-asserts-only
@assert @assume $assert $assume (rename-out [export-asserts asserts]) clear-asserts!
(rename-out [get-vc vc]) clear-vc! merge-vc! with-vc T*->boolean?)
vc? vc-assumes vc-asserts
vc-true vc-true?)
;; ----------------- Boolean type ----------------- ;; ;; ----------------- Boolean type ----------------- ;;
(define-lifted-type @boolean? (define-lifted-type @boolean?
@ -26,9 +22,9 @@
[(? boolean?) v] [(? boolean?) v]
[(term _ (== self)) v] [(term _ (== self)) v]
[(union : [g (and (or (? boolean?) (term _ (== self))) u)] _ ...) [(union : [g (and (or (? boolean?) (term _ (== self))) u)] _ ...)
($assert g (argument-error caller "boolean?" v)) (@assert g (thunk (raise-argument-error caller "expected a boolean?" v)))
u] u]
[_ ($assert #f (argument-error caller "boolean?" v))])) [_ (@assert #f (thunk (raise-argument-error caller "expected a boolean?" v)))]))
(define (type-compress self force? ps) (define (type-compress self force? ps)
(match ps (match ps
[(list _) ps] [(list _) ps]
@ -72,9 +68,12 @@
[((list (constant _ (? primitive-solvable?)) (... ...)) body) [((list (constant _ (? primitive-solvable?)) (... ...)) body)
($op @vars body)] ($op @vars body)]
[(_ _) [(_ _)
($assert (@assert
#f #f
(argument-error '$op "list of symbolic constants of primitive solvable types" @vars))]))))) (thunk
(raise-argument-error
'$op
"expected a list of symbolic constants of primitive solvable types" @vars)))])))))
;; ----------------- Basic boolean operators ----------------- ;; ;; ----------------- Basic boolean operators ----------------- ;;
(define (! x) (define (! x)
@ -86,26 +85,9 @@
(define && (logical-connective @&& @|| #t #f)) (define && (logical-connective @&& @|| #t #f))
(define || (logical-connective @|| @&& #f #t)) (define || (logical-connective @|| @&& #f #t))
(define (=> x y) ; (|| (! x) y)) (define (=> x y) (|| (! x) y))
(cond
[(equal? x y) #t]
[(eq? x #f) #t]
[(eq? y #t) #t]
[(eq? x #t) y]
[(eq? y #f) (! x)]
[(cancel? x y) y]
[else
(match y
[(expression (== @||) _ ... (== x) _ ...) #t]
[(expression (== @&&) (== x) b) (=> x b)]
[(expression (== @&&) b (== x)) (=> x b)]
[(expression (== @&&) (expression (== @||) _ ... (== x) _ ...) b) (=> x b)]
[(expression (== @&&) b (expression (== @||) _ ... (== x) _ ...)) (=> x b)]
[(expression (== @<=>) (== x) b) (=> x b)]
[(expression (== @<=>) b (== x)) (=> x b)]
[_ (|| (! x) y)])]))
(define (<=> x y) (define (<=> x y) ;(|| (&& x y) (&& (! x) (! y))))))
(cond [(equal? x y) #t] (cond [(equal? x y) #t]
[(boolean? x) (if x y (! y))] [(boolean? x) (if x y (! y))]
[(boolean? y) (if y x (! x))] [(boolean? y) (if y x (! x))]
@ -132,15 +114,12 @@
[_ (loop (cdr xs))]))] [_ (loop (cdr xs))]))]
[_ #f])) [_ #f]))
(define (@true? v)
(or (eq? #t v) (! (@false? v))))
(define-quantifier exists @exists) (define-quantifier exists @exists)
(define-quantifier forall @forall) (define-quantifier forall @forall)
;; ----------------- Additional operators and utilities ----------------- ;; ;; ----------------- Additional operators ----------------- ;;
(define-syntax and-&& (define-syntax and-&&
(syntax-rules () (syntax-rules ()
[(_) #t] [(_) #t]
@ -164,15 +143,6 @@
(and (union? v) (apply || (for/list ([g (in-union-guards v symbolic-type)]) g))))] (and (union? v) (apply || (for/list ([g (in-union-guards v symbolic-type)]) g))))]
[_ #f])) [_ #f]))
(define (void))
(define-syntax first-term-or-bool
(syntax-rules ()
[(_ e) e]
[(_ e0 e ...) (let ([v e0])
(if (void? v)
(first-term-or-bool e ...)
v))]))
;; ----------------- Partial evaluation rules for ∀ and ∃ ----------------- ;; ;; ----------------- Partial evaluation rules for ∀ and ∃ ----------------- ;;
@ -195,7 +165,7 @@
[((== !iden) _) !iden] [((== !iden) _) !iden]
[(_ (== !iden)) !iden] [(_ (== !iden)) !iden]
[(_ _) [(_ _)
(first-term-or-bool (first-value
(simplify-connective op co !iden x y) (simplify-connective op co !iden x y)
(if (term<? x y) (expression op x y) (expression op y x)))])] (if (term<? x y) (expression op x y) (expression op y x)))])]
[xs [xs
@ -206,19 +176,27 @@
[(list x) x] [(list x) x]
[ys (apply expression op (sort ys term<?))])])])) [ys (apply expression op (sort ys term<?))])])]))
(define (void))
(define-syntax first-value
(syntax-rules ()
[(_ e) e]
[(_ e0 e ...) (let ([v e0])
(if (void? v)
(first-value e ...)
v))]))
(define (simplify-connective op co !iden x y) (define (simplify-connective op co !iden x y)
(match* (x y) (match* (x y)
[(_ (== x)) x] [(_ (== x)) x]
[((? expression?) (? expression?)) [((? expression?) (? expression?))
(first-term-or-bool (first-value
(if (term<? y x)
(simplify-connective:expr/any op co !iden x y) (simplify-connective:expr/any op co !iden x y)
(simplify-connective:expr/any op co !iden y x)) (simplify-connective:expr/any op co !iden y x)
(simplify-connective:expr/expr op co !iden x y))] (simplify-connective:expr/expr op co !iden x y))]
[((? expression?) _) [((? expression?) _)
(if (term<? y x) (simplify-connective:expr/any op co !iden x y) )] (simplify-connective:expr/any op co !iden x y)]
[(_ (? expression?)) [(_ (? expression?))
(if (term<? x y) (simplify-connective:expr/any op co !iden y x) )] (simplify-connective:expr/any op co !iden y x)]
[(_ _) ])) [(_ _) ]))
(define (simplify-connective:expr/any op co !iden x y) (define (simplify-connective:expr/any op co !iden x y)
@ -228,13 +206,21 @@
[(expression (== op) _ ... (== y) _ ...) x] [(expression (== op) _ ... (== y) _ ...) x]
[(expression (== op) _ ... (expression (== @!) (== y)) _ ...) !iden] [(expression (== op) _ ... (expression (== @!) (== y)) _ ...) !iden]
[(expression (== @!) (expression (== co) _ ... (== y) _ ...)) !iden] [(expression (== @!) (expression (== co) _ ... (== y) _ ...)) !iden]
[(expression (== @!) (expression (== co) _ ... (expression (== @!) (== y)) _ ...)) x]
[(expression (== @!) (expression (== op) _ ... (expression (== @!) (== y)) _ ...)) y]
[(expression (== @!) a)
(match y
[(expression (== op) _ ... (== a) _ ...) !iden]
[_ ])]
[_ ])) [_ ]))
; Applies the following simplification rules symmetrically:
; (1) (op (op a1 ... an) (op ai ... aj)) ==> (op a1 ... an)
; (2) (op (op a1 ... ai ... an) (op b1 ... (neg ai) ... bn) ==> !iden
; (3) (op (co a1 ... an) (co ai ... aj)) ==> (co ai ... aj)
; Returns ⊥ if none of the rules applicable; otherwise returns the simplified result.
(define (simplify-connective:expr/expr op co !iden a b) (define (simplify-connective:expr/expr op co !iden a b)
(match* (a b) (match* (a b)
[((expression (== op) _ ... x _ ...) (expression (== @!) x)) !iden]
[((expression (== @!) x) (expression (== op) _ ... x _ ...)) !iden]
[((expression (== op) xs ...) (expression (== op) ys ...)) [((expression (== op) xs ...) (expression (== op) ys ...))
(cond [(sublist? xs ys) b] (cond [(sublist? xs ys) b]
[(sublist? ys xs) a] [(sublist? ys xs) a]
@ -244,12 +230,6 @@
(cond [(sublist? xs ys) a] (cond [(sublist? xs ys) a]
[(sublist? ys xs) b] [(sublist? ys xs) b]
[else ])] [else ])]
[((expression (== op) xs ...) (expression (== co) ys ...))
(cond [(for*/or ([x xs][y ys]) (equal? x y)) a]
[else ])]
[((expression (== co) xs ...) (expression (== op) ys ...))
(cond [(for*/or ([y ys][x xs]) (equal? x y)) b]
[else ])]
[(_ _) ])) [(_ _) ]))
(define (simplify-fp op co !iden xs) (define (simplify-fp op co !iden xs)
@ -282,196 +262,54 @@
[(_ _) #f])) [(_ _) #f]))
;; ----------------- VC generation ----------------- ;; ;; ----------------- Assertions and path condition ----------------- ;;
(define (export-asserts) (remove-duplicates (asserts)))
; A verification condition (VC) consists of two @boolean? (define (clear-asserts!) (asserts '()))
; values representing assumptions and assertions issued
; during execution. A VC is legal if at least one of its
; constituent fields is true under all models.
(struct vc (assumes asserts) #:transparent) (define asserts
; The true verification condition.
(define vc-true (vc #t #t))
(define (vc-true? s) (equal? s vc-true))
; Returns (vc (s.assumes && (s.asserts => g)) s.asserts).
(define (assuming s g) ; g must be a symbolic or concrete boolean
(vc (&& (vc-assumes s) (=> (vc-asserts s) g)) (vc-asserts s)))
; Returns (vc s.assumes (s.asserts && (s.assumes => g))).
(define (asserting s g) ; g must be a symbolic or concrete boolean
(vc (vc-assumes s) (&& (vc-asserts s) (=> (vc-assumes s) g))))
; The current-vc parameter keeps track of the current verification condition,
; which is an instance of vc?. The default value for this parameter is vc-true.
(define current-vc
(make-parameter (make-parameter
vc-true '()
(lambda (v) (unless (vc? v) (raise-argument-error 'vc "vc?" v)) v))) (match-lambda [(? list? xs) xs]
[x (if (eq? x #t) (asserts) (cons x (asserts)))])))
; Returns the current vc, without exposing the parameter outside the module. (define pc
(define (get-vc) (current-vc)) (make-parameter
#t
(lambda (new-pc)
(or (boolean? new-pc)
(and (term? new-pc) (equal? @boolean? (term-type new-pc)))
(error 'pc "expected a boolean path condition, given a ~s" (type-of new-pc)))
(or (&& (pc) new-pc)
(raise-exn:fail:rosette:infeasible)))))
; Clears the current vc by setting it to the true spec.
(define (clear-vc!) (current-vc vc-true))
; Returns #t if x && (g => y) is equivalent to x according to the embedded
; rewrite rules. Otherwise returns #f.
(define (merge-absorbs? x g y)
(match y
[(== x) #t] ; x && (g => x)
[(expression (== @&&) (== x) (== g)) #t] ; x && (g => (x && g))
[(expression (== @&&) (== g) (== x)) #t] ; x && (g => (x && g))
[(expression (== @&&) (== x) (expression (== @||) _ ... (== g) _ ...)) #t] ; x && (g => (x && (_ => g)))
[(expression (== @&&) (expression (== @||) _ ... (== g) _ ...) (== x)) #t] ; x && (g => ((_ => g) && x))
[_ #f]))
; Returns (field x) && (gs[0] => (field ys[0])) ... && (gs[n-1] => (field gs[n-1])).
(define (merge-field field x gs ys)
(define xf (field x))
(apply && xf
(for*/list ([(g y) (in-parallel gs ys)]
[yf (in-value (field y))]
#:unless (merge-absorbs? xf g yf))
(=> g yf))))
;; Returns (field x) && (gs[0] => (field ys[0])) ... && (gs[n-1] => (field gs[n-1])).
;; Assumes that ys[i] => x for all i, and at most one gs evaluates to true in any model.
;(define (merge-field field x gs ys)
; (define xf (field x))
; (define gs=>ys
; (for*/list ([(g y) (in-parallel gs ys)]
; [yf (in-value (field y))]
; #:unless (merge-absorbs? xf g yf))
; (=> g yf)))
; (match gs=>ys
; [(list) xf]
; [(list gy) (&& xf gy)]
; [(or (list (expression (== @||) _ ... g _ ...) (expression (== @||) _ ... (expression (== @!) g) _ ...))
; (list (expression (== @||) _ ... (expression (== @!) g) _ ...) (expression (== @||) _ ... g _ ...)))
; (apply && gs=>ys)]
; [_ (apply && xf gs=>ys)]))
; Takes as input a list of n guards and n vcs and sets the current vc
; to (current-vc) && (vc-guard guard1 vc1) && ... && (vc-guard guardn vcn).
; Then, it checks if either the assumes or the asserts of the resulting vc
; are false? and if so, throws either an exn:fail:svm:assume? or
; exn:fail:svm:assert? exception. This procedure makes the following assumptions:
; * at most one of the given guards is true in any model,
; * (vc-assumes vcs[i]) => (vc-assumes (current-vc)) for all i, and
; * (vc-asserts vcs[i]) => (vc-asserts (current-vc)) for all i.
(define (merge-vc! guards vcs)
(unless (null? vcs)
(define vc*
(vc (merge-field vc-assumes (current-vc) guards vcs)
(merge-field vc-asserts (current-vc) guards vcs)))
(current-vc vc*)
(when (false? (vc-assumes vc*))
(raise-exn:fail:svm:assume:core "contradiction"))
(when (false? (vc-asserts vc*))
(raise-exn:fail:svm:assert:core "contradiction"))))
; Sets the current vc to (vc-proc (current-vc) g) where g is (@true? val).
; If g is #f or the resulting vc's vc-field value is #f,
; uses raise-exn throws an exn:fail:svm exception.
(define-syntax-rule (vc-set! val msg vc-proc vc-field raise-exn)
(let* ([guard (@true? val)]
[vc* (vc-proc (current-vc) guard)])
(current-vc vc*)
(when (false? guard)
(raise-exn msg))
(when (false? (vc-field vc*))
(raise-exn "contradiction"))))
; Sets the current vc to (asserting (current-vc) g) where g is (@true? val).
; If g is #f or the resulting vc's asserts field is #f, throws an
; exn:fail:svm:assert exception of the given kind.
(define-syntax-rule (vc-assert! val msg raise-kind)
(vc-set! val msg asserting vc-asserts raise-kind))
; Sets the current vc to (assuming (current-vc) g) where g is (@true? val).
; If g is #f or the resulting vc's assumes field is #f, throws an
; exn:fail:svm:assume exception of the given kind.
(define-syntax-rule (vc-assume! val msg raise-kind)
(vc-set! val msg assuming vc-assumes raise-kind))
; The $assert form has three variants: ($assert val), ($assert val msg),
; and ($assert val msg kind), where val is the value being asserted, msg
; is the failure message, and kind is a procedure that returns a subtype of
; exn:fail:svm:assert. Default values for msg and kind are #f and
; raise-exn:fail:svm:assert:core, respectively.
; The first two variants of this form are used for issuing assertions from
; within the Rosette core. The third variant is used to implement the @assert
; form that is exposed to user code. An $assert call modifies the current vc to
; reflect the issued assertion. If the issued assertion or the vc-assert of the
; current vc reduce to #f, the call throws an exception of the given kind after
; updating the vc.
(define-syntax ($assert stx)
(syntax-case stx ()
[(_ val) (syntax/loc stx ($assert val #f raise-exn:fail:svm:assert:core))]
[(_ val msg) (syntax/loc stx ($assert val msg raise-exn:fail:svm:assert:core))]
[(_ val msg kind) (syntax/loc stx (vc-assert! val msg kind))]))
; Analogous to the $assert form, except that it modifies the current vc to
; reflect the issued assumption.
(define-syntax ($assume stx)
(syntax-case stx ()
[(_ val) (syntax/loc stx ($assume val #f raise-exn:fail:svm:assume:core))]
[(_ val msg) (syntax/loc stx ($assume val msg raise-exn:fail:svm:assume:core))]
[(_ val msg kind) (syntax/loc stx (vc-assume! val msg kind))]))
; The @assert form modifies the current vc to reflect the issued assertion.
; The form has two variants (@assert val) and (@assert val msg), where val
; is the value being asserted and msg is the optional error message in case
; val is #f. This form is exposed to user code.
(define-syntax (@assert stx) (define-syntax (@assert stx)
(syntax-case stx () (syntax-case stx ()
[(_ val) (syntax/loc stx ($assert val #f raise-exn:fail:svm:assert:user))] [(_ val) (syntax/loc stx (@assert val #f))]
[(_ val msg) (syntax/loc stx ($assert val msg raise-exn:fail:svm:assert:user))])) [(_ val msg)
; The @assume form modifies the current vc to reflect the issued assumption.
; The form has two variants (@assume val) and (@assume val msg), where val
; is the value being assume and msg is the optional error message in case
; val is #f. This form is exposed to user code.
(define-syntax (@assume stx)
(syntax-case stx ()
[(_ val) (syntax/loc stx ($assume val #f raise-exn:fail:svm:assume:user))]
[(_ val msg) (syntax/loc stx ($assume val msg raise-exn:fail:svm:assume:user))]))
(define (halt-svm ex)
(define result (failed ex (current-vc)))
((current-reporter) 'exception result)
result)
(define (halt-err ex) ; Treat an exn:fail? error as an assertion failure.
(define result
(failed (make-exn:fail:svm:assert:err (exn-message ex) (exn-continuation-marks ex))
(asserting (current-vc) #f)))
((current-reporter) 'exception result)
result)
; The with-vc form has two variants, (with-vc body) and (with-vc vc0 body).
; The former expands into (with-vc (current-vc) body). The latter sets the current
; vc to vc0, evaluates the given body, returns the result, and reverts current-vc
; to the value it held before the call to with-vc.
;
; If the evaluation of the body terminates normally, (with-vc vc0 body)
; outputs (normal v vc*) where v is the value computed by the body, and vc* is
; the vc (i.e., assumes and asserts) generated during the evaluation,
; with vc0 as the initial vc.
;
; If the evaluation of the body terminates abnormally with an exn:fail? exception,
; (with-vc vc0 body) outputs (failed v vc*) where v is an exn:fail:svm? exception
; that represents the cause of the abnormal termination, and vc* is the vc
; generated during the evaluation, with vc0 as the initial vc.
(define-syntax (with-vc stx)
(syntax-case stx ()
[(_ body) (syntax/loc stx (with-vc (current-vc) body))]
[(_ vc0 body)
(syntax/loc stx (syntax/loc stx
(parameterize ([current-vc vc0]) (let ([guard (not-false? val)])
(with-handlers ([exn:fail:svm? halt-svm] (asserts (=> (pc) guard))
[exn:fail? halt-err]) (when (false? guard)
(normal (let () body) (current-vc)))))])) (raise-assertion-error msg))))]))
(define (not-false? v)
(or (eq? v #t) (! (@false? v))))
(define (raise-assertion-error msg)
(if (procedure? msg)
(msg)
(raise-exn:fail:rosette:assertion (if msg (format "~a" msg) "failed"))))
(define (evaluate-with-asserts closure)
(parameterize ([asserts '()])
(let* ([val (closure)]
[bools (remove-duplicates (asserts))])
(values val bools))))
(define-syntax-rule (with-asserts form)
(evaluate-with-asserts (thunk form)))
(define-syntax-rule (with-asserts-only form)
(let-values ([(out asserts) (with-asserts form)])
asserts))

View File

@ -0,0 +1,177 @@
#lang racket
(require
(for-syntax racket)
"../util/ord-dict.rkt" "bool.rkt" "reporter.rkt")
(provide speculate speculate* apply! location=? (rename-out [state-val location-final-value]))
; The env parameter stores an eq? based hash-map which we use to keep
; track of boxes, vectors and structs that are mutated.
(define env (make-parameter #f))
(define-syntax-rule (speculate-core guard body rollback-proc)
; using an eq? rather than equal? hash map to manage the environment bindings
; is critical for mutable objects whose hash code may change upon mutation. note
; that variables are keyed by the symbol representing their name, so eq? comparisons
; for them are equivalent to equal? comparisons.
(parameterize ([env (odict null eq?)])
; roll-back state updates, encapsulate
; updates to set! variables as specified above,
; and return the value of the body together with the
; encapsulation of the state changes according to rollback-proc
(with-handlers ([exn:fail? (rollback/suppress guard)])
(values body (rollback-proc)))))
; The speculate expression takes the form (speculate body), where body is
; an expression. A speculate call produces two values: the value that the
; body would produce if executed in the current environment, and a closure
; that stores a representation of all state updates that the execution of
; body would make. The closure accepts a two argument function f, and
; applies encapsulated state updates so that each updated location is set
; to (f v body-v), where body-v is the final value the body would assign to v.
;
; Any exceptions thrown by body are caught, all updates are rolled-back without
; encapsulating the final states, and the result of speculate is (values #f #f).
(define-syntax-rule (speculate guard body)
(let ([guard-v guard])
(speculate-core guard-v
(parameterize ([pc guard-v]) body)
rollback/encapsulate)))
; The speculate* expression takes the form (speculate* body), where body is
; an expression. A speculate* call produces two values: the value that the
; body would produce if executed in the current environment, and a list of
; locations, each of which encapsulates the pre and post state of a location
; mutated during the execution of the body. The returned locations can be
; compared with location=?.
;
; Each encapsulated update acts as a procedure that accepts a two-argument
; function f. The location for the encapsulated updated is then set to
; (f v body-v), where body-v is the final value the body would assign to the
; location and v is the current value in that location. The procedure
; (location-final-value loc) can be used to obtain the final value that the
; body would assign to a given location.
;
; Any exceptions thrown by body are caught, all updates are rolled-back without
; encapsulating the final states, and the result of speculate is (values #f #f).
(define-syntax-rule (speculate* guard body)
(let ([guard-v guard])
(speculate-core guard-v
(parameterize ([pc guard-v]) body)
rollback/collect)))
; A function that handles calls to structure mutators.
(define apply!
(case-lambda
[(setter getter receiver key val)
(record! receiver key getter setter)
(setter receiver key val)]
[(setter getter receiver val)
(record! receiver setter getter setter)
(setter receiver val)]))
; Stores the state of a mutation to the location in a given receiver,
; together with getters and setters that can be used to read/write
; the mutated location. The val field stores the value that was read
; from the location at some point in time (e.g., beginning/end of
; speculation). The attached procedure accepts a two argument function f
; and sets the encapsulated location to (f (getter) val).
(struct state (receiver location val getter setter)
#:transparent
#:property prop:procedure
(lambda (self proc)
(let ([receiver (state-receiver self)]
[location (state-location self)]
[getter (state-getter self)]
[setter (state-setter self)])
(record! receiver location getter setter)
(cond [(dict? receiver)
(setter receiver location (proc (getter receiver location) (state-val self)))]
[else ; struct or box
(setter receiver (proc (getter receiver) (state-val self)))]))))
(define (get getter receiver location)
(cond [(dict? receiver) (getter receiver location)]
[else (getter receiver)]))
(define (state-rollback! s)
(let ([receiver (state-receiver s)]
[location (state-location s)]
[getter (state-getter s)]
[setter (state-setter s)])
(cond [(dict? receiver)
(setter receiver location (state-val s))]
[else ; struct or box
(setter receiver (state-val s))])))
; Returns true iff both objects encapsulate updates to the same location.
(define (location=? s0 s1)
(match* (s0 s1)
[((state rec0 loc0 _ _ _) (state rec1 loc1 _ _ _))
(and (eq? rec0 rec1) (equal? loc0 loc1))]
[(_ _) #f]))
; Adds a record of the given variable's or object's current state
; to the environment, if the environment is valid and does not
; already have a mapping for the record!-ed variable or object.
(define-syntax-rule (record! obj location getter setter)
(when (and (env)
(not (env-has-state? obj location))) ; we do this check separately so that the getter/setter
(env-set! obj location getter setter))) ; lambdas don't get created unless they are needed
; Returns a true value if the current environment (assumed not be #f)
; has a state record for the given mutation receiver and location of
; mutation. For structs, the location is the field-setter function for
; the mutated field. For dictionary objects, the location is the key within the
; dictionary to which the dict-set! operation is being applied. For boxes,
; the location is the set-box! procedure.
(define (env-has-state? receiver location)
(let ([env (env)])
(and (dict-has-key? env receiver) ; compound object
(dict-has-key? (dict-ref env receiver) location))))
; Augments env with a mapping from the given receiver to a state record reflecting
; the current state at the given location, as obtained by the given getter
; procedure. This function assumes that (env-has-state? receiver location) is false.
(define (env-set! receiver location getter setter)
(let ([env (env)]
[new-state (state receiver location (get getter receiver location) getter setter)])
(let ([locations (dict-ref! env receiver make-hash)]) ; compound object
(dict-set! locations location new-state))))
; Reverts the state of set! variables and struct fields to
; their initial values, without encapsulating the final state updates.
; Returns (values #f #f). The error argument is ignored.
(define ((rollback/suppress guard) err)
;(printf "\n\nERROR: ~a\n\n" err)
((current-reporter) 'exception guard err)
(unless (zero? (dict-count (env)))
(for* ([states (in-dict-values (env))]
[s (if (list? states) (in-list states) (in-dict-values states))])
(state-rollback! s))) ; roll-back
(values #f #f))
; Reverts the state of set! variables and struct fields to
; their initial values, and returns an encapsulation of
; the final state updates.
(define (rollback/encapsulate)
(if (zero? (dict-count (env)))
void
(let ([updates (rollback/collect)])
(lambda (proc)
(for ([s (in-list updates)])
(s proc))))))
; Reverts the state of set! variables and struct fields to
; their initial values, and returns a list that contains a
; copy of the final state of each location bound in the current
; environment.
(define (rollback/collect)
(for*/list ([states (in-dict-values (env))]
[s (if (list? states) (in-list states) (in-dict-values states))])
(let ([final (get (state-getter s) (state-receiver s) (state-location s))])
(state-rollback! s) ; roll-back
(struct-copy state s [val final])))) ; collect final states

View File

@ -1,61 +0,0 @@
#lang racket
(require
(only-in "bool.rkt" with-vc $assume merge-vc!)
"exn.rkt" "result.rkt" "store.rkt" "merge.rkt")
(provide eval-assuming eval-guarded!)
; Takes as input a concrete or symbolic boolean and a thunk,
; evaluates thunk under the assumption that the guard holds,
; and returns the result. This result takes one of two forms.
;
; If the evaluation of the thunk terminates normally, the result
; is (normal (normal v st) vc*) where v is the value computed by the
; thunk, st captures all stores mutations performed during evaluation,
; and vc* captures the verification condition generated during the
; evaluation, starting from the current vc.
;
; If the thunk terminates abnormally, the result is (failed ex vc*),
; where ex is an exn:fail:svm? exception that represents the cause
; of the abnormal termination, and vc* captures the verification
; condition generated during the evaluation, starting from the current vc.
;
; Neither the current store nor the current vc are modified after
; eval-assuming returns.
(define (eval-assuming guard thunk)
(with-vc
(begin
($assume guard)
(with-store (thunk)))))
; Takes as input a list of n guards and n thunks, evaluates each thunk
; under its guard using eval-assuming, merges the resulting vcs into
; the current vc, merges the resulting stores (if any) into the current
; store, and merges the resulting values (if any) before returning them
; as output. If all of the thunks fail under their guards, eval-guarded
; raises an exn:fail:svm:merge exception after the specs are merged into
; the current vc.
; This procedure makes the following assumptions, based on the Lean
; formalization:
; (1) At most one guard evaluates to true under any model.
; (2) For all models m under which (vc) evaluates to vc-true, there is
; exactly one guard in guards that evaluates to #t under m.
; (3) For all models m under which (vc) doesn't evaluate to vc-true,
; every vc produced by evaluating the given thunks evaluates to
; the same spec as (vc) under m.
(define (eval-guarded! guards thunks)
(define results (map eval-assuming guards thunks))
(merge-vc! guards (map result-state results))
(define-values (gs rs)
(for/lists (gs rs) ([g guards][r results] #:when (normal? r))
(values g (result-value r))))
(if (null? rs)
(raise-exn:fail:svm:merge)
(begin
(merge-stores! gs (map result-state rs))
(apply merge* (for/list ([g gs][r rs])
(cons g (result-value r)))))))

View File

@ -1,107 +1,21 @@
#lang racket #lang racket/base
(require (only-in racket/string string-split) (provide raise-exn:fail:rosette:infeasible
(for-syntax racket/syntax racket/string) raise-exn:fail:rosette:assertion
racket/provide) exn:fail:rosette?
exn:fail:rosette:infeasible?
exn:fail:rosette:assertion?)
(provide (matching-identifiers-out #px"^exn:fail:svm.*\\?$" (all-defined-out)) (struct exn:fail:rosette exn:fail ())
(matching-identifiers-out #px"^make\\-exn:fail:svm.*$" (all-defined-out)) (struct exn:fail:rosette:infeasible exn:fail:rosette ())
(matching-identifiers-out #px"^raise\\-exn:fail:svm.*$" (all-defined-out)) (struct exn:fail:rosette:assertion exn:fail:rosette ())
exn:fatal? fatal
argument-error arguments-error type-error contract-error index-too-large-error)
;; --------------- Exceptions --------------- ;;
; Four kinds of failures can happen during symbolic evaluation:
; (1) the execution reaches (assert e) where e evaluates to #f, or asserting e reduces vc's asserts to #f;
; (2) the execution reaches (assume e) where e evaluates to #f, or assuming e reduces vc's assumes to #f;
; (3) the execution reaches e where e raises an exn:fail? exception; and
; (4) all paths at a given merge point led to a failure.
; Within the first two types of failures, we distinguish between
; assertions and assumptions issued by user code and core (Rosette) code.
; The third type of failure is treated as an assertion failure for the
; purposes of verification condition generation. Finally,
; the fourth type of failure is tracked via exn:fail:svm:merge.
; The top of the exception hierarchy for failures raised
; during symbolic evaluation.
(struct exn:fail:svm exn:fail ())
; An assert exception can be one of the following kinds:
; * :core represents an assertion failure raised in Rosette code,
; * :user represents an assertion failure raised in user code, and
; * :err indicates that an exn:fail? exception was raised during evaluation.
(struct exn:fail:svm:assert exn:fail:svm ())
(struct exn:fail:svm:assert:core exn:fail:svm:assert ())
(struct exn:fail:svm:assert:user exn:fail:svm:assert ())
(struct exn:fail:svm:assert:err exn:fail:svm:assert ())
; An assume exception can be one of the following kinds:
; * :core represents an assumption failure raised in Rosette code, and
; * :user represents an assumption failure raised in user code.
(struct exn:fail:svm:assume exn:fail:svm ())
(struct exn:fail:svm:assume:core exn:fail:svm:assume ())
(struct exn:fail:svm:assume:user exn:fail:svm:assume ())
; An merge exception is raised when all paths at a branching point lead to a failure.
(struct exn:fail:svm:merge exn:fail:svm ())
(define-syntax (define-make-and-raise stx)
(syntax-case stx ()
[(_ id)
(with-syntax ([make-id (format-id #'id "make-~a" (syntax-e #'id))]
[raise-id (format-id #'id "raise-~a" (syntax-e #'id))]
[prefix (list-ref (string-split (symbol->string (syntax-e #'id)) ":") 3)])
#'(begin
(define (make-id [msg #f] [cont-marks #f])
(id (format "[~a] ~a" prefix (or msg "failed"))
(or cont-marks (current-continuation-marks))))
(define (raise-id [msg #f] [cont-marks #f])
(raise (make-id msg cont-marks)))))]
[(_ id ...)
#'(begin (define-make-and-raise id) ...)]))
; Creates two procedures make-* and raise-* for each exception type that
; creates and raises an exception of the given type, respectively.
(define-make-and-raise
exn:fail:svm:assert:core
exn:fail:svm:assert:user
exn:fail:svm:assert:err
exn:fail:svm:assume:core
exn:fail:svm:assume:user
exn:fail:svm:merge)
;; --------------- Messages --------------- ;;
; Fatal errors indicate bugs in the Rosette implementation.
; Since Rosette only catches and handles errors of subtype exn:fail?,
; exn:fatal is a subtype of exn and hence will not be caught as part
; of symbolic evaluation.
(struct exn:fatal exn ())
(define (fatal msg) (raise (exn:fatal msg (current-continuation-marks))))
(define (argument-error name expected given)
(format "~a: contract violation\n expected: ~a\n given: ~a"
name expected given))
(define (arguments-error name message . field-value)
(define o (open-output-string))
(fprintf o "~a: ~a" name message)
(let loop ([fvs field-value])
(match fvs
[(list) (get-output-string o)]
[(list f) (fatal (format "arguments-error: missing value after field string ~a" f))]
[(list f v rest ...)
(fprintf o "\n ~a: ~a" f v)
(loop rest)])))
(define (type-error name expected given)
(argument-error name (format "~a" expected) given))
(define (contract-error name contract given)
(argument-error name (format "~a" (contract-name contract)) given))
(define (index-too-large-error who xs idx)
(arguments-error who "index is too large" "index" idx "in" xs))
(define-syntax-rule (raise-exn:fail:rosette:infeasible)
(raise (exn:fail:rosette:infeasible
"pc: infeasible path condition"
(current-continuation-marks))))
(define-syntax-rule (raise-exn:fail:rosette:assertion msg)
(raise (exn:fail:rosette:assertion
(string-append "assert: " msg)
(current-continuation-marks))))

View File

@ -4,12 +4,13 @@
syntax/parse/define syntax/parse/define
(only-in racket/unsafe/ops [unsafe-car car] [unsafe-cdr cdr]) (only-in racket/unsafe/ops [unsafe-car car] [unsafe-cdr cdr])
(only-in "merge.rkt" merge merge* merge-same) (only-in "merge.rkt" merge merge* merge-same)
(only-in "bool.rkt" ! || &&) (only-in "bool.rkt" ! || && pc)
(only-in "union.rkt" union union?) (only-in "union.rkt" union union?)
(only-in "term.rkt" expression) (only-in "term.rkt" expression)
(only-in "polymorphic.rkt" guarded guarded-test guarded-value ite ite*) (only-in "polymorphic.rkt" guarded guarded-test guarded-value ite ite*)
(only-in "equality.rkt" @equal?) (only-in "equality.rkt" @equal?)
"safe.rkt" "../core/eval.rkt" "../core/store.rkt" "../core/result.rkt") (only-in "effects.rkt" speculate* location=? location-final-value)
"safe.rkt")
(provide for/all for*/all guard-apply) (provide for/all for*/all guard-apply)
@ -80,6 +81,9 @@
(cdr gv))))] (cdr gv))))]
[_ (list (cons (apply && guards) val))])))) [_ (list (cons (apply && guards) val))]))))
(define (all-paths-infeasible)
(error 'for/all "all paths infeasible"))
; Applies the given procedure to each of the guarded values, ; Applies the given procedure to each of the guarded values,
; given as guard/value structures. The application of the procedure ; given as guard/value structures. The application of the procedure
; to each value is done under the value's guard, and so are all ; to each value is done under the value's guard, and so are all
@ -88,17 +92,64 @@
; The guard-apply procedure also merges any state updates resulting ; The guard-apply procedure also merges any state updates resulting
; from successful guarded evaluations of proc on the given values. ; from successful guarded evaluations of proc on the given values.
; ;
; At most one of the given guards may be true under any model. ; All given guards are required to be pairwise mutually exclusive,
; and at least one of the guards must always evaluate to true.
(define (guard-apply proc guarded-values [guard-of car] [value-of cdr]) (define (guard-apply proc guarded-values [guard-of car] [value-of cdr])
; If any of the guarded-values has #t as its guard, it's executed (cond
; directly, since all the guards must be #f under all models. [(andmap (compose1 boolean? guard-of) guarded-values)
(define gv (findf (lambda (gv) (eq? (guard-of gv) #t)) guarded-values)) ;; Either (1) concrete is empty or (2) the value is also concrete.
;; This case doesn't require speculation which is expensive.
;; Simply search for a value in gv pairs whose guard is true
;; and apply proc to it if there's one, error otherwise.
(define gv (findf guard-of guarded-values))
(cond (cond
[gv (proc (value-of gv))] [gv (proc (value-of gv))]
[else (eval-guarded! (map guard-of guarded-values) [else (assert #f all-paths-infeasible)])]
(map (lambda (gv) (thunk (proc (value-of gv)))) guarded-values))])) [else
(define-values (guards outputs states)
(guard-speculate* proc guarded-values guard-of value-of))
(when (null? guards) (assert #f all-paths-infeasible))
(when (ormap pair? states)
(merge-states guards states))
(apply merge* (map cons guards outputs))]))
; Speculatively executes the given procedure on the provided
; guarded values and returns three lists---guards, outputs,
; and states---of equal length. For each g/v input value
; in guarded-values for which (proc v) terminates without an
; error, there is an index i such that the ith element of the
; guards list is g, the ith element of the outputs list is
; (proc v), and the ith element of the states list is the list
; of all states updates that were performed when executing (proc v).
; Note that all state update objects for the ith execution are
; are unique according to location=?, but two state updates in
; different executions may be location=?. (That is, proc would
; update the same location if it were called with two different
; values.)
(define (guard-speculate* proc guarded-values [guard-of car] [value-of cdr])
(for/fold ([guards '()] [outputs '()] [states '()]) ([gv guarded-values])
(define guard (guard-of gv))
(define val (value-of gv))
(define-values (output state) (speculate* guard (proc val)))
(cond [state (values (cons guard guards) (cons output outputs) (cons state states))]
[else (assert (! guard) all-paths-infeasible)
(values guards outputs states)])))
; Given a list of n guards and their corresponding lists of
; state-update objects, performs an n-way merge of all updates
; to memory locations that are encapsulated in those states.
(define (merge-states guards states)
(define locations (remove-duplicates (apply append states) location=?))
(define guarded-states (append-map (lambda (g sts) (map (curry cons g) sts)) guards states))
(define max-guards-per-location (length guards))
(define (merge-procedure gss)
(if (= (length gss) max-guards-per-location)
(lambda (pre post) (apply merge* gss))
(lambda (pre post) (apply merge* (cons (! (apply || (map car gss))) pre) gss))))
(for ([loc locations])
(loc (merge-procedure
(for/list ([gs guarded-states]
#:when (location=? loc (cdr gs)))
(cons (car gs) (location-final-value (cdr gs))))))))

View File

@ -1,6 +1,7 @@
#lang racket #lang racket
(require (only-in racket/unsafe/ops [unsafe-car car] [unsafe-cdr cdr]) (require (only-in rnrs/base-6 assert)
(only-in racket/unsafe/ops [unsafe-car car] [unsafe-cdr cdr])
"term.rkt" "union.rkt" "bool.rkt" "reporter.rkt") "term.rkt" "union.rkt" "bool.rkt" "reporter.rkt")
(provide merge merge* unsafe-merge* merge-same) (provide merge merge* unsafe-merge* merge-same)
@ -34,56 +35,70 @@
(let ([simp (simplify ps)]) (let ([simp (simplify ps)])
((current-reporter) 'merge (length simp)) ((current-reporter) 'merge (length simp))
(match (compress force? simp) (match (compress force? simp)
[(list (cons g v)) v] [(list (cons g v)) (assert (not (false? g))) v]
[(list _ (... ...) (cons #t v) _ (... ...)) v] [(list _ (... ...) (cons #t v) _ (... ...)) v]
[vs (apply union vs)]))) [vs (apply union vs)])))
(define (guard g gvs) (define (guard-&& a b)
(for*/list ([gv gvs] (match b
[gg (in-value (&& g (car gv)))] [(expression (== @&&) c ...) (apply && a c)]
#:when gg) [_ (&& a b)]))
(cons gg (cdr gv))))
(define (guard g vs)
(filter-map (lambda (v)
(let ([gv (guard-&& g (car v))])
(and gv (cons gv (cdr v)))))
vs))
(define (simplify ps) (define (simplify ps)
(let loop ([ps ps] [out '()])
(match ps (match ps
[(list _ ... (and (cons #t _) p) _ ...) [(list) out]
[(list (and (cons #t v) p) _ ...)
(list p)] (list p)]
[_ (for/fold ([out '()]) ([p ps]) [(list (cons #f _) rest ...)
(match p (loop rest out)]
[(cons #f _) out] [(list (cons g (union (and (not (? null?)) vs))) rest ...)
[(cons g (union (and (not (? null?)) gvs))) (loop rest (append (guard g vs) out))]
(append (guard g gvs) out)] [(list p rest ...)
[_ (cons p out)]))])) (loop rest (cons p out))])))
(define (type-of-value gv) (type-of (cdr gv))) (define (group ps)
(let ([types (remove-duplicates (for/list ([p ps]) (type-of (cdr p))))])
(for*/list ([t types] [p ps] #:when (equal? t (type-of (cdr p)))) p)))
(define (compress force? ps) (define (compress force? ps)
(match ps (match ps
[(list _) ps] [(list _) ps]
[(list (cons _ (app type-of t)) (cons _ (app type-of t))) [(list (cons g (app type-of t)) (cons h (app type-of t)))
(type-compress t force? (merge-same ps))] (type-compress t force? (merge-same ps))]
[(list _ _) ps] [(list _ _) ps]
[_ (append-map [_ (let loop ([ps (group ps)] [type #f] [acc '()])
(lambda (group) ;(printf "compress ~a ~a ~a\n" ps type acc)
(type-compress (match ps
(type-of (cdar group)) [(list)
(append-map (lambda (group)
(type-compress (type-of (cdar group))
force? force?
(merge-same group))) (merge-same group)))
(group-by type-of-value ps))])) acc)]
[(list (and (cons _ (app type-of (== type))) p) rest ...)
(loop rest type (cons (cons p (car acc)) (cdr acc)))]
[(list p rest ...)
(loop rest (type-of (cdr p)) (cons (list p) acc))]))]))
(define (merge-same ps) (define (merge-same ps)
;(printf "merge ~a\n" ps)
(match ps (match ps
[(or (list) (list _)) ps] [(or (list) (list _)) ps]
[(list (cons g v) (cons h u)) [(list (cons g v) (cons h u)) (if (eq? v u) (list (cons (|| g h) v)) ps)]
(if (eq? v u) (list (cons (|| g h) v)) ps)] [_ (let loop ([ps ps] [out '()])
[_ (let loop ([ps (group-by cdr ps eq?)] [out '()]) (if (null? ps)
(match ps out
[(list) out] (match-let*-values
[(list (list gv) rest ...) ([((cons g v)) (car ps)]
(loop rest (cons gv out))] [((list (cons h _) ...) rest) (partition (compose (curry eq? v) cdr) (cdr ps))]
[(list group rest ...) [(g) (apply || g h)])
(let ([g (apply || (map car group))] (if (equal? g #t)
[v (cdar group)])
(if (eq? g #t)
(list (cons g v)) (list (cons g v))
(loop rest (cons (cons g v) out))))]))])) (loop rest (cons (cons g v) out))))))]))

View File

@ -7,7 +7,10 @@
(provide @number? @positive? @negative? @zero? @even? @odd? (provide @number? @positive? @negative? @zero? @even? @odd?
@add1 @sub1 @sgn @truncate @floor @ceiling @min @max @add1 @sub1 @sgn @truncate @floor @ceiling @min @max
@exact->inexact @inexact->exact @expt @exact->inexact @inexact->exact @expt
extreme) extreme
;@sqrt @bitwise-not @bitwise-and @bitwise-ior @bitwise-xor
;@<< @>> @>>> @bitwise-bit-set? @bitwise-bit-field
)
(define (@number? v) (or (number? v) (@real? v))) (define (@number? v) (or (number? v) (@real? v)))
(define (@positive? x) (@> x 0)) (define (@positive? x) (@> x 0))

View File

@ -152,7 +152,7 @@
(match* ((car p) (cdr p)) (match* ((car p) (cdr p))
[(a (expression (== ite) a x _)) (cons a x)] [(a (expression (== ite) a x _)) (cons a x)]
[(a (expression (== ite) (expression (== @!) a) _ x)) (cons a x)] [(a (expression (== ite) (expression (== @!) a) _ x)) (cons a x)]
[((and (expression (== @!) a) !a) (expression (== ite) a _ x)) (cons !a x)] [((expression (== @!) a) (expression (== ite) a _ x)) (cons a x)]
[(_ _) p])) [(_ _) p]))

View File

@ -3,7 +3,7 @@
(require (require
racket/provide racket/provide
(for-syntax racket/syntax (only-in "lift.rkt" with@)) (for-syntax racket/syntax (only-in "lift.rkt" with@))
(only-in "type.rkt" define-lifted-type type-cast typed? get-type subtype? type-applicable? @any/c) (only-in "type.rkt" define-lifted-type typed? get-type subtype? type-applicable? @any/c)
(only-in "bool.rkt" || @false?) (only-in "bool.rkt" || @false?)
(only-in "union.rkt" union union? in-union-guards union-filter union-guards) (only-in "union.rkt" union union? in-union-guards union-filter union-guards)
(only-in "safe.rkt" assert argument-error) (only-in "safe.rkt" assert argument-error)
@ -75,8 +75,8 @@
[(union gvs) (guard-apply (curryr procedure-rename name) gvs)] [(union gvs) (guard-apply (curryr procedure-rename name) gvs)]
[(? procedure?) (procedure-rename proc name)])) [(? procedure?) (procedure-rename proc name)]))
(define (@negate p) (define (@negate f)
(define f (type-cast @procedure? p 'negate)) (unless (@procedure? f) (raise-argument-error 'negate "procedure?" f))
(let-values ([(arity) (procedure-arity f)] [(_ kwds) (procedure-keywords f)]) (let-values ([(arity) (procedure-arity f)] [(_ kwds) (procedure-keywords f)])
(case (and (null? kwds) arity) ; optimize some simple cases (case (and (null? kwds) arity) ; optimize some simple cases
[(0) (lambda () (@false? (f)))] [(0) (lambda () (@false? (f)))]

View File

@ -295,7 +295,7 @@
(define T*->integer? (const @integer?)) (define T*->integer? (const @integer?))
(define (undefined-for-zero-error name) (define (undefined-for-zero-error name)
(arguments-error name "undefined for 0")) (thunk (raise-arguments-error name "undefined for 0")))
(define-syntax-rule (define-lifted-int-operator @op $op op) (define-syntax-rule (define-lifted-int-operator @op $op op)
(define-operator @op (define-operator @op

View File

@ -1,17 +1,16 @@
#lang racket #lang racket
(require (only-in "forall.rkt" for/all for*/all) (require (only-in "forall.rkt" for/all for*/all)
"term.rkt" "union.rkt" "result.rkt") "term.rkt" "union.rkt")
(provide type? solvable? @any/c type-of type-cast for/all for*/all (provide type? solvable? @any/c type-of type-cast for/all for*/all
term? constant? expression? term? constant? expression?
term expression constant term expression constant
term-type term=? term->datum term-type term=?
terms terms-count terms-ref with-terms clear-terms! gc-terms! term->datum clear-terms! term-cache
union? union union-contents union-guards union-values union? union union-contents union-guards union-values
union-filter in-union in-union* in-union-guards in-union-values union-filter in-union in-union* in-union-guards in-union-values
(struct-out normal) (struct-out failed) result? result-value result-state symbolics)
symbolics concrete? symbolic?)
(define (term=? s0 s1) (define (term=? s0 s1)
(and (term? s0) (term? s1) (equal? s0 s1))) (and (term? s0) (term? s1) (equal? s0 s1)))
@ -19,57 +18,27 @@
(define (symbolics vs) (define (symbolics vs)
(match vs (match vs
[(list (? constant?) ...) (remove-duplicates vs)] [(list (? constant?) ...) (remove-duplicates vs)]
[(? constant?) (list vs)] [_ (let ([cache (mutable-set)]
[_ (let ([terms (mutable-set)]
[objs (mutable-set)]
[result '()]) [result '()])
(let loop ([datum vs]) (let loop ([vs vs])
(if (term? datum) (unless (set-member? cache vs)
(let ([id (term-id datum)]) (set-add! cache vs)
(unless (set-member? terms id) (match vs
(set-add! terms id)
(match datum
[(expression _ x ...) (for-each loop x)]
[(? constant?) (set! result (cons datum result))])))
(unless (set-member? objs datum)
(set-add! objs datum)
(match datum
[(union (list (cons guard value) ...)) [(union (list (cons guard value) ...))
(for-each loop guard) (for-each loop value)] (for-each loop guard) (for-each loop value)]
[(expression _ x ...) (for-each loop x)]
[(? constant? v) (set! result (cons v result))]
[(box v) (loop v)] [(box v) (loop v)]
[(? list?) (for-each loop datum)] [(? list?) (for-each loop vs)]
[(cons x y) (loop x) (loop y)] [(cons x y) (loop x) (loop y)]
[(vector v ...) (for-each loop v)] [(vector v ...) (for-each loop v)]
[(and (? typed?) (app get-type t)) [(and (? typed?) (app get-type t))
(match (type-deconstruct t datum) (match (type-deconstruct t vs)
[(list (== datum)) (void)] [(list (== vs)) (void)]
[components (for-each loop components)])] [components (for-each loop components)])]
[_ (void)])))) [_ (void)])))
(reverse result))])) (reverse result))]))
(define (concrete? val)
(define objs (mutable-set))
(let all-concrete? ([val val])
(and (not (term? val))
(not (union? val))
(or
(set-member? objs val)
(begin
(set-add! objs val)
(match val
[(box v) (all-concrete? v)]
[(? list?) (for/and ([v val]) (all-concrete? v))]
[(cons x y) (and (all-concrete? x) (all-concrete? y))]
[(? vector?) (for/and ([v val]) (all-concrete? v))]
[(and (? typed?) (app get-type t))
(match (type-deconstruct t val)
[(list (== val)) #t]
[components (for/and ([v components]) (all-concrete? v))])]
[_ #t]))))))
(define (symbolic? val) (not (concrete? val)))
(define (term->datum val) (define (term->datum val)
(let convert ([val val] [cache (make-hash)]) (let convert ([val val] [cache (make-hash)])
(if (hash-has-key? cache val) (if (hash-has-key? cache val)

View File

@ -1,17 +0,0 @@
#lang racket
(provide (struct-out normal) (struct-out failed)
result? result-value result-state)
; Represents the result of symbolic evaluation,
; which includes an output value and a representation
; of some aspect of the symbolic state.
(struct result (value state) #:transparent)
; Represents the result of a normally terminated evaluation.
(struct normal result () #:transparent)
; Represents the result of an evaluation that resulted in
; an exn:fail? exception being raised. In this case,
; the result-value field stores the exception that was raised.
(struct failed result () #:transparent)

View File

@ -1,38 +1,57 @@
#lang racket #lang racket
(require "bool.rkt" "exn.rkt") (require (only-in "type.rkt" type-cast)
"bool.rkt"
racket/performance-hint)
(provide argument-error arguments-error type-error contract-error index-too-large-error (provide argument-error arguments-error type-error contract-error index-too-large-error
assert assert-some assert-|| assert-bound assert-arity-includes) assert assert-some assert-|| assert-bound assert-arity-includes)
(begin-encourage-inline
(define (arguments-error name message . field-value)
(thunk (apply raise-arguments-error name message field-value)))
(define (argument-error name expected given)
(thunk (raise-argument-error name expected given)))
(define (type-error name expected given)
(argument-error name (format "~a" expected) given))
(define (contract-error name contract given)
(argument-error name (format "~a" (contract-name contract)) given))
(define (index-too-large-error who xs idx)
(arguments-error who "index is too large" "index" idx "in" xs))
)
(define-syntax (assert stx) (define-syntax (assert stx)
(syntax-case stx () (syntax-case stx ()
[(_ expr) (syntax/loc stx ($assert expr #f))] [(_ expr err-thunk) (syntax/loc stx (@assert expr err-thunk))]
[(_ expr msg) (syntax/loc stx ($assert expr msg))])) [(_ expr) (syntax/loc stx (@assert expr #f))]))
(define-syntax assert-some (define-syntax assert-some
(syntax-rules () (syntax-rules ()
[(_ expr #:unless size msg) [(_ expr #:unless size err-thunk)
(let* ([val expr]) (let* ([val expr])
(unless (= size (length val)) (unless (= size (length val))
(assert (apply || (map car val)) msg)) (assert (apply || (map car val)) err-thunk))
val)] val)]
[(_ expr #:unless size) [(_ expr #:unless size)
(assert-some expr #:unless size #f)] (assert-some expr #:unless size #f)]
[(_ expr msg) [(_ expr err-thunk)
(let* ([val expr]) (let* ([val expr])
(assert (apply || (map car val)) msg) (assert (apply || (map car val)) err-thunk)
val)] val)]
[(_ expr) [(_ expr)
(assert-some expr #f)])) (assert-some expr #f)]))
(define-syntax assert-|| (define-syntax assert-||
(syntax-rules () (syntax-rules ()
[(_ expr #:unless size msg) [(_ expr #:unless size err-thunk)
(let ([val expr]) (let ([val expr])
(unless (= size (length val)) (unless (= size (length val))
(assert (apply || val) msg)))] (assert (apply || val) err-thunk)))]
[(_ expr #:unless size) (assert-|| expr #:unless size #f)])) [(_ expr #:unless size) (assert-|| expr #:unless size #f)]))

View File

@ -1,181 +0,0 @@
#lang racket
(require "result.rkt" "merge.rkt")
(provide with-store store! merge-stores!
location? location-base location-offset
location-ref location-set!)
; The current-store parameter contains a store that
; maps (abstract) memory locations to values. Each mapped
; location identifies a storage cell that has been the mutated
; via a store! call in the dynamic extent of a call to
; with-store. The store maps each such location to the value
; that was stored at that location before the current call
; to with-store.
(define current-store (make-parameter #f))
; A store maps abstract memory locations to their initial values.
; An abstract memory location identifies a storage cell that holds
; a single value; locations consist of a base object (e.g., a vector)
; and an offset value (e.g., the index 0) that identifies a
; storage cell within that object.
;
; A store uses a refs set, as returned by make-refs, to keep track
; of the locations that have been mutated via store! calls.
; The initial value of each mutated location is held in the store's
; vals list. This list maps locations to the values they held prior
; to the current call to with-store.
(struct store (refs [vals #:mutable]) #:transparent)
; Returns an empty store.
(define (make-store) (store (make-refs) (list)))
; Returns an empty set of base/offset pairs.
(define (make-refs) (make-hasheq))
; Adds the given base/offset pair to rs if not
; already present. Returns #t if rs changed as
; a result of this operation; otherwise returns #f.
(define (refs-add! rs base offset)
(define bits (hash-ref rs base 0))
(and (not (bitwise-bit-set? bits offset))
(hash-set! rs base (bitwise-ior bits (arithmetic-shift 1 offset)))
#t))
; Extends the store s with a mapping from the location
; (loc base offset getter setter) to its current value,
; unless s already contains a mapping for this location.
(define (store-add! s base offset getter setter)
(when (refs-add! (store-refs s) base offset)
(let ([l (location base offset getter setter)]
[vals (store-vals s)])
(set-store-vals! s (cons (cons l (location-ref l)) vals)))))
; Performs the mutation to the storage
; cell at the location (loc base offset getter setter),
; and if this cell has not been mutated before, its
; initial value is added to current-store.
; The getter and setter procedures should read / write
; the cell's value when applied to its base and offset.
(define (store! base offset val getter setter)
(let ([s (current-store)])
(when s
(store-add! s base offset getter setter)))
;(printf "store! ~a ~a ~a ~a ~a, ~a\n" base offset val getter setter (current-store))
(setter base offset val))
; Returns true if the store s is empty.
(define (store-empty? s)
(zero? (length (store-vals s))))
; Represents the location of a single mutable storage cell.
; A cell location consists of a base object (e.g., a vector)
; and an offset value (e.g., 0) that identifies a
; storage cell within that object. Locations
; also include getter and setter procedures that can be
; used to read from and write to the referenced cell.
; Two locations are equal? iff their base and offset
; are both eq? to one another.
(struct location (base offset accessor mutator)
#:transparent
#:methods gen:equal+hash
[(define (equal-proc l1 l2 rec-equal?)
(and (eq? (location-base l1) (location-base l2))
(eq? (location-offset l1) (location-offset l2))))
(define (hash-proc l rec-equal-hash)
(equal-hash-code (cons (eq-hash-code (location-base l)) (eq-hash-code (location-offset l)))))
(define (hash2-proc l rec-equal-hash2)
(equal-secondary-hash-code (cons (eq-hash-code (location-base l)) (eq-hash-code (location-offset l)))))])
; Returns the current value stored at the location l.
(define (location-ref l)
((location-accessor l) (location-base l) (location-offset l)))
; Stores the value v at the location l.
(define (location-set! l v)
(store! (location-base l) (location-offset l) v (location-accessor l) (location-mutator l)))
; Rollbacks the contents of all mutated storage cells to their initial
; values, as given in (current-store), and raises the exception e.
; The current-store is assumed to contain the values that
; mutated cells held before the current call to with-store.
; This procedure can be called only in the dynamic extent of a
; with-store call.
(define (rollback-exn! e)
;(printf "exn: ~a\n" e)
(for ([lv (store-vals (current-store))])
(match-define (cons (location base offset _ setter) init) lv)
(setter base offset init))
(raise e))
; Rollbacks the contents of all mutated storage cells to their initial
; values, as given in (current-store), and returns a list of pairs
; that maps a reference to each mutated cell to its current value.
; The current-store is assumed to contain the values that
; mutated cells held before the current call to with-store.
; This procedure can be called only in the dynamic extent of a
; with-store call.
(define (rollback-capture!)
;(printf "capture: ~a\n" (store-vals (current-store)))
(for/list ([lv (store-vals (current-store))])
(match-define (cons (location base offset getter setter) init) lv)
(define fin (getter base offset))
(setter base offset init)
(cons (car lv) fin)))
; The with-store form takes as input an expression, evaluates it,
; and reverts each mutated memory location to its pre-state
; (i.e., the value it held before the call to with-store).
;
; If the evaluation of the body terminates normally, (with-store body)
; outputs a result (normal v s) where v is the value computed by the body,
; and s is an association list that maps each mutated location? to its
; post-state (i.e., the value it held after the evaluation of the body).
; In essence, evaluating the body in the current environment has the
; same effect on memory as evaluating (with-store body) and then setting
; the returned memory locations to their post-state value.
;
; If the evaluation of the body terminates abnormally with an exn:fail?
; exception, with-store reverts all mutated locations to their pre-state
; and re-raises the same exception.
(define-syntax-rule (with-store body)
(parameterize ([current-store (make-store)])
(with-handlers ([exn:fail? rollback-exn!])
(let ([out body])
(normal out (rollback-capture!))))))
; Takes as input a list of n guards and a list of n stores, where
; each store is a list of location/value pairs. For each location l
; occurring in the stores, merge-store mutates l to contain the value
; m = (merge* ... (cons gi vi) ...), where gi = guards[i] and
; vi = stores[i][l] if stores[i] has a binding for l; otherwise,
; vi = (location-ref l). The procedure assumes that no store contains a
; duplicate binding for any location.
;
; This store merging procedure is correct under the assumption that
; (1) the guards are disjoint under all models (i.e., at most one
; is ever true), and (2) the verification conditions force at least
; one guard to be true under all models that satisfy both the
; asserts and the assumes.
(define (merge-stores! guards stores)
(match stores
[(list (list) ...) (void)] ; Nothing to merge.
[(list s) (for ([lv s]) ; If given only one store, just apply its effects
(location-set! (car lv) (cdr lv)))] ; since its guard must be true under the current spec.
[_ (define hash-stores (map make-hash stores))
(for ([lv (remove-duplicates (apply append stores) equal? #:key car)])
(define loc (car lv))
(define val (location-ref loc))
(location-set! loc
(apply merge*
(for/list ([g guards] [hs hash-stores])
(cons g (if (hash-has-key? hs loc)
(hash-ref hs loc)
val))))))]))

View File

@ -1,38 +1,32 @@
#lang racket #lang racket
(require racket/syntax (for-syntax racket racket/syntax syntax/parse) (require racket/syntax (for-syntax racket racket/syntax) racket/generic
racket/generic syntax/parse
"type.rkt" "reporter.rkt") "type.rkt" "reporter.rkt")
(provide (provide
terms terms-count terms-ref with-terms clear-terms! gc-terms! term-cache clear-terms!
term? constant? expression? term? constant? expression?
(rename-out [a-term term] [an-expression expression] [a-constant constant] [term-ord term-id]) (rename-out [a-term term] [an-expression expression] [a-constant constant])
term-type term<? sublist? @app term-type term<? sublist? @app
define-operator operator? operator-unsafe define-operator operator? operator-unsafe
(all-from-out "type.rkt")) (all-from-out "type.rkt"))
#|-----------------------------------------------------------------------------------|# #|-----------------------------------------------------------------------------------|#
; The current-terms cache stores terms for the purposes of partial cannonicalization. ; Term cache stores terms for the purposes of partial cannonicalization.
; That is, it ensures that no syntactically identical terms are created. ; That is, it ensures that no syntactically identical terms are created.
; The current-index parameter is used to assign unique IDs (creation timestamps) to terms. ; It also assigns unique IDs (creation timestamps) to terms. These IDs
; These IDs are never reused, and they are used to impose an ordering on the children ; are never reused, and they are used to impose an ordering on the children
; of expressions with commutative operators. ; of expressions with commutative operators.
#|-----------------------------------------------------------------------------------|# #|-----------------------------------------------------------------------------------|#
(define term-cache (make-parameter (make-hash)))
(define term-count (make-parameter 0))
;; Initialize with #f so that the hash table cooperates with garbage collector. ; Clears the entire term-cache if invoked with #f (default), or
;; See #247
(define current-terms (make-parameter #f))
(current-terms (make-hash))
(define current-index (make-parameter 0))
; Clears the entire term cache if invoked with #f (default), or
; it clears all terms reachable from the given set of leaf terms. ; it clears all terms reachable from the given set of leaf terms.
(define (clear-terms! [terms #f]) (define (clear-terms! [terms #f])
(if (false? terms) (if (false? terms)
(hash-clear! (current-terms)) (hash-clear! (term-cache))
(let ([cache (current-terms)] (let ([cache (term-cache)]
[evicted (list->mutable-set terms)]) [evicted (list->mutable-set terms)])
(for ([t terms]) (for ([t terms])
(hash-remove! cache (term-val t))) (hash-remove! cache (term-val t)))
@ -46,70 +40,12 @@
(set-add! evicted t)) (set-add! evicted t))
(loop)))))) (loop))))))
; Sets the current term cache to a garbage-collected (weak) hash.
; The setting preserves all reachable terms from (current-terms).
(define (gc-terms!)
(unless (hash-weak? (current-terms)) ; Already a weak hash.
(define cache
(impersonate-hash
(make-weak-hash)
(lambda (h k)
(values k (lambda (h k e) (ephemeron-value e #f))))
(lambda (h k v)
(values k (make-ephemeron k v)))
(lambda (h k) k)
(lambda (h k) k)
hash-clear!))
(for ([(k v) (current-terms)])
(hash-set! cache k v))
(current-terms cache)))
; Returns the term from current-terms that has the given contents. If
; no such term exists, failure-result is returned, unless it is a procedure.
; If failure-result is a procedure, it is called and its result is returned instead.
(define (terms-ref contents [failure-result (lambda () (error 'terms-ref "no term for ~a" contents))])
(hash-ref (current-terms) contents failure-result))
; Returns a list of all terms in the current-term scache, in an unspecified order.
(define (terms)
(hash-values (current-terms)))
; Returns the size of the current-terms cache.
(define (terms-count)
(hash-count (current-terms)))
; Evaluates expr with (terms) set to terms-expr, returns the result, and
; restores (terms) to its old value. If terms-expr is not given, it defaults to
; (terms), so (with-terms expr) is equivalent to (with-terms (terms) expr).
(define-syntax (with-terms stx)
;; Parameterize with #f so that the hash table cooperates with garbage collector.
;; See #247
(syntax-parse stx
[(_ expr)
#'(let ([orig-terms (current-terms)])
(parameterize ([current-terms #f])
(current-terms (hash-copy orig-terms))
expr))]
[(_ terms-expr expr)
#'(let ([orig-terms (current-terms)])
(parameterize ([current-terms #f])
(current-terms (hash-copy-clear orig-terms))
(let ([ts terms-expr]
[cache (current-terms)])
(for ([t ts])
(hash-set! cache (term-val t) t))
expr)))]))
#|-----------------------------------------------------------------------------------|# #|-----------------------------------------------------------------------------------|#
; The term structure defines a symbolic value, which can be a variable or an expression. ; The term structure defines a symbolic value, which can be a variable or an expression.
; The val field of a constant is its unique identifier, and it can be anything. The val ; The val field of a constant is its unique identifier, and it can be anything. The val
; field of an expression is a list, in which the first argument is always a function. ; field of an expression is a list, in which the first argument is always a function.
; That function can be interpreted (that is, an operator), or uninterpreted (that is, ; That function can be interpreted (that is, an operator), or uninterpreted (that is,
; its interpretation is determined by the solver). Terms are totally ordered and a ; its interpretation is determined by the solver).
; subterm is guaranteed to be term<? than its parent.
#|-----------------------------------------------------------------------------------|# #|-----------------------------------------------------------------------------------|#
(struct term (struct term
(val ; (or/c any/c (cons/c function? (non-empty-listof any/c))) (val ; (or/c any/c (cons/c function? (non-empty-listof any/c)))
@ -132,21 +68,14 @@
(define (term<? s1 s2) (< (term-ord s1) (term-ord s2))) (define (term<? s1 s2) (< (term-ord s1) (term-ord s2)))
(define-syntax-rule (make-term term-constructor args type rest ...) (define-syntax-rule (make-term term-constructor args type rest ...)
(let ([val args] (let ([val args])
[ty type]) (or (hash-ref (term-cache) val #f)
(define cached (hash-ref (current-terms) val #f)) (let* ([ord (term-count)]
(cond [out (term-constructor val type ord rest ...)])
[cached (term-count (add1 ord))
(unless (equal? (term-type cached) ty)
(error 'define-symbolic "type should remain unchanged"))
cached]
[else
(define ord (current-index))
(define out (term-constructor val ty ord rest ...))
(current-index (add1 ord))
((current-reporter) 'new-term out) ((current-reporter) 'new-term out)
(hash-set! (current-terms) val out) (hash-set! (term-cache) val out)
out]))) out))))
(define (make-const id t) (define (make-const id t)
(unless (and (type? t) (solvable? t)) (unless (and (type? t) (solvable? t))

View File

@ -43,8 +43,8 @@
[type-cast type val [caller]] ; (-> type? any/c symbol? any/c) [type-cast type val [caller]] ; (-> type? any/c symbol? any/c)
[type-name type] ; (-> type? symbol?) [type-name type] ; (-> type? symbol?)
[type-applicable? type] ; (-> type? boolean?) [type-applicable? type] ; (-> type? boolean?)
[type-eq? type u v] ; (-> type? any/c any/c @boolean?) [type-eq? type u v] ; (-> type? (-> any/c any/c @boolean?)))
[type-equal? type u v] ; (-> type? any/c any/c @boolean?) [type-equal? type u v] ; (-> type? (-> any/c any/c @boolean?)))
[type-compress type force? ps] ; (-> type? (listof (cons @boolean? any/c)) (listof (cons @boolean? any/c))) [type-compress type force? ps] ; (-> type? (listof (cons @boolean? any/c)) (listof (cons @boolean? any/c)))
[type-construct type vals] ; (-> type? (listof any/c) any/c) [type-construct type vals] ; (-> type? (listof any/c) any/c)
[type-deconstruct type val]) ; (-> type? any/c (listof any/c)) [type-deconstruct type val]) ; (-> type? any/c (listof any/c))

View File

@ -20,18 +20,19 @@
[(define (get-type self) (union-type self))] [(define (get-type self) (union-type self))]
#:methods gen:custom-write #:methods gen:custom-write
[(define (write-proc self port mode) [(define (write-proc self port mode)
(fprintf port "(union") (fprintf port "{")
(case mode (case mode
[(#t #f) [(#t #f)
(fprintf port " #:size ~a #:hash ~a" (length (union-contents self)) (equal-hash-code self))] (fprintf port "~a:~a" (equal-hash-code self) (length (union-contents self)))]
[else [else
(let ([vs (union-contents self)]) (let ([vs (union-contents self)])
(unless (null? vs) (unless (null? vs)
(parameterize ([error-print-width (max 4 (quotient (error-print-width) (* 2 (length vs))))]) (parameterize ([error-print-width (max 4 (quotient (error-print-width) (* 2 (length vs))))])
(for ([v vs]) (fprintf-entry port (car vs) mode)
(for ([v (cdr vs)])
(fprintf port " ") (fprintf port " ")
(fprintf-entry port v mode)))))]) (fprintf-entry port v mode)))))])
(fprintf port ")"))]) (fprintf port "}"))])
(define (fprintf-entry port p mode) (define (fprintf-entry port p mode)
(fprintf port "[") (fprintf port "[")

View File

@ -1,12 +1,27 @@
#lang racket #lang racket
(require "../core/eval.rkt" "../core/store.rkt" "../core/result.rkt" (require "../core/effects.rkt"
"../core/term.rkt" "../core/equality.rkt" "../core/term.rkt" "../core/equality.rkt"
"../core/merge.rkt" "../core/bool.rkt") "../core/merge.rkt" "../core/bool.rkt")
(provide @if @and @or @not @nand @nor @xor @implies (provide @if @and @or @not @nand @nor @xor @implies
@unless @when @cond @case else) @unless @when @cond @case else)
; Symbolic conditions are handled by speculatively executing both branches,
; and then merging their results and updates to state (if any). When a branch is
; executed speculatively, its state mutations are captured and then undone.
; The result of the capture is a closure that can be used with a merging
; procedure to selectively re-apply the updates. If an error is thrown
; during speculation, all updates are undone, but they are not captured
; (since the branch is infeasible). After both branches have been speculatively
; executed, their results and updates to state are merged using the merge function.
;
; Speculative execution of either branch is guarded by the path condition, stored
; in the pc parameter. Parameterizing pc with a new value coinjoins that
; value with the current path condition. If the result of the conjunction is false,
; indicating that the branch is infeasible, an error is thrown, and the branch is
; not executed. The error is captured by the speculate form and later handled by
; the merge function.
(define-syntax (@if stx) (define-syntax (@if stx)
(syntax-case stx () (syntax-case stx ()
[(_ test-expr then-expr else-expr) [(_ test-expr then-expr else-expr)
@ -16,10 +31,28 @@
(thunk else-expr)))])) (thunk else-expr)))]))
(define (branch-and-merge test-expr then-branch else-branch) (define (branch-and-merge test-expr then-branch else-branch)
(define test (@true? test-expr)) (define test (! (@false? test-expr)))
(cond [(eq? test #t) (then-branch)] (cond [(eq? test #t) (then-branch)]
[(eq? test #f) (else-branch)] [(eq? test #f) (else-branch)]
[else (eval-guarded! (list test (! test)) (list then-branch else-branch))])) [else
(let-values ([(then-val then-state) (speculate test (then-branch))]
[(else-val else-state) (speculate (! test) (else-branch))])
(cond [(and then-state else-state) ; both branches feasible
(then-state (lambda (pre post-then) (merge test post-then pre)))
(else-state (lambda (post-then post-else) (merge test post-then post-else)))
(merge test then-val else-val)]
[then-state ; only then branch feasible
(@assert test "both branches infeasible")
(then-state select-post)
then-val]
[else-state ; only else branch feasible
(@assert (! test) "both branches infeasible")
(else-state select-post)
else-val]
[else ; neither branch feasible
(@assert #f "both branches infeasible")]))]))
(define (select-post pre post) post)
(define-syntax (@and stx) (define-syntax (@and stx)
(syntax-case stx () (syntax-case stx ()

View File

@ -1,48 +1,85 @@
#lang racket #lang racket
(require syntax/parse (for-syntax syntax/parse racket) (require (for-syntax racket)
"../core/term.rkt") "../util/array.rkt" "../core/term.rkt" "state.rkt")
(provide define-symbolic define-symbolic*) (provide define-symbolic define-symbolic*)
(define-for-syntax (module-or-top? . args) #|--------------define forms--------------|#
(case (syntax-local-context)
[(module top-level) #t]
[else #f]))
(define-for-syntax (static? k)
(with-handlers ([exn:fail? module-or-top?])
(natural? (eval k))))
(define-syntax (define-symbolic stx) (define-syntax (define-symbolic stx)
(syntax-parse stx (syntax-case stx ()
[(_ var:id type) [(_ var type)
#'(define var (constant #'var type))] (identifier? #'var)
[(_ var:id type #:length k) (syntax/loc stx (define var (constant #'var type)))]
#:declare k (expr/c #'natural? #:name "length argument") [(_ var type [ k ... ])
#:fail-unless (static? #'k) "expected a natural? for #:length" (and (identifier? #'var) (implies (identifier? #'type) (identifier-binding #'type)))
#'(define var (define-array stx #'var #'type #'(k ...))]
(for/list ([i k.c]) [(_ v ... type)
(constant (list #'var i) type)))] (andmap identifier? (syntax->list #'(v ...)))
[(_ var:id ...+ type) (syntax/loc stx (define-values (v ...) (values (constant #'v type) ...)))]))
#'(begin (define-symbolic var type) ...)]))
(define current-index (make-parameter 0))
(define (index!)
(define idx (current-index))
(current-index (add1 idx))
idx)
(define-syntax (define-symbolic* stx) (define-syntax (define-symbolic* stx)
(syntax-parse stx (syntax-case stx ()
[(_ var:id type) [(_ [var oracle] type)
#'(define var (constant (list #'var (index!)) type))] (identifier? #'var)
[(_ var:id type #:length k) (syntax/loc stx (define var (constant (list #'var (oracle #'var)) type)))]
#:declare k (expr/c #'natural? #:name "length argument") [(_ var type)
#'(define var (identifier? #'var)
(for/list ([i k.c]) (syntax/loc stx (define-symbolic* [var (current-oracle)] type))]
[(_ var type [ k ... ])
(and (identifier? #'var) (implies (identifier? #'type) (identifier-binding #'type)))
(syntax/loc stx (define var (reshape (list k ...) (for/list ([i (in-range (* k ...))])
(define-symbolic* var type) (define-symbolic* var type)
var))] var))))]
[(_ var:id ...+ type) [(_ v0 v ... type)
#'(begin (define-symbolic* var type) ...)])) (and (identifier? #'v0) (andmap identifier? (syntax->list #'(v ...))))
(syntax/loc stx (begin (define-symbolic* v0 type) (define-symbolic* v type) ...))]
))
#|--------------helper functions--------------|#
(module util racket
(require racket/syntax)
(provide var-ids indices)
(define (var-ids id-stx dim-spec [separator '@])
(for/list ([idx (apply indices (dims dim-spec))])
(format-id id-stx "~a~a~a" id-stx separator idx #:source id-stx)))
(define (dims spec)
(begin0 spec
(for ([dim spec])
(unless (and (integer? dim) (>= dim 0))
(error 'define-symbolic "expected a non-negative integer, given ~a" dim)))))
(define (indices . k)
(cond [(null? k) k]
[(null? (cdr k)) (build-list (car k) (lambda (i) (format-symbol "~a" i)))]
[else (let ([car-idx (indices (car k))]
[cdr-idx (apply indices (cdr k))])
(append-map (lambda (i)
(map (lambda (j)
(format-symbol "~a:~a" i j))
cdr-idx))
car-idx))])))
(require (for-syntax 'util) 'util)
(define-for-syntax (define-array stx var type dims)
(with-syntax ([var var]
[type type]
[(k ...) dims])
(with-handlers ([exn:fail?
(lambda (e)
(case (syntax-local-context)
[(module top-level)
(quasisyntax/loc stx
(define var (reshape (list k ...)
(map (lambda (id) (constant id type))
(var-ids #'var (list k ...))))))]
[else (raise e)]))])
(with-syntax ([(v ...) (var-ids #'var (eval #'(list k ...)))])
(quasisyntax/loc stx
(define var (reshape (list k ...) (list (constant #'v type) ...))))))))

View File

@ -0,0 +1,29 @@
#lang racket
(provide current-oracle oracle? (rename-out [make-oracle oracle]))
#|--------------current state parameters--------------|#
(struct oracle ([tbl])
#:property prop:procedure
(lambda (self var)
(let* ([vars (oracle-tbl self)]
[choice-idx (hash-ref vars var 0)])
(hash-set! vars var (+ choice-idx 1))
choice-idx))
#:methods gen:custom-write
[(define (write-proc self port mode)
(fprintf port "oracle~a" (oracle-tbl self)))])
(define make-oracle
(case-lambda
[() (oracle (make-hash))]
[(other) (oracle (hash-copy (oracle-tbl other)))]))
(define current-oracle
(make-parameter (make-oracle)
(lambda (oracle)
(unless (oracle? oracle)
(error 'current-oracle "expected an oracle procedure, given ~s" oracle))
oracle)))

View File

@ -3,7 +3,7 @@
(require (for-syntax "../core/lift.rkt" racket/syntax) (require (for-syntax "../core/lift.rkt" racket/syntax)
(only-in racket/private/generic-methods generic-property) (only-in racket/private/generic-methods generic-property)
(only-in "../core/store.rkt" store!) (only-in "../core/effects.rkt" apply!)
"../core/term.rkt" "../core/lift.rkt" "../core/safe.rkt" "../core/term.rkt" "../core/lift.rkt" "../core/safe.rkt"
(only-in "../core/bool.rkt" || && and-&&) (only-in "../core/bool.rkt" || && and-&&)
(only-in "../core/type.rkt" @any/c type-cast gen:typed get-type) (only-in "../core/type.rkt" @any/c type-cast gen:typed get-type)
@ -80,43 +80,33 @@
(values struct:t make-t @struct:t t-ref t-set!)) (values struct:t make-t @struct:t t-ref t-set!))
(define (struct-field-accessor-name @struct:t i field-id)
(if field-id
(format "~a-~a" (object-name (struct-type-make @struct:t)) field-id)
(format "~a-field~a" (object-name (struct-type-make @struct:t)) i)))
(define (struct-field-mutator-name @struct:t i field-id)
(format "set-~a!" (struct-field-accessor-name @struct:t i field-id)))
(define (@make-struct-field-mutator struct:t i field-id) (define (@make-struct-field-mutator struct:t i field-id)
(let* ([@struct:t (get-type struct:t)] (let* ([@struct:t (get-type struct:t)]
[native? (struct-type-native? @struct:t)] [native? (struct-type-native? @struct:t)]
[name (string->symbol (struct-field-mutator-name @struct:t i field-id))] [setter (make-struct-field-mutator (struct-type-set! @struct:t) i field-id)]
[setter (struct-type-set! @struct:t)] [getter (make-struct-field-accessor (struct-type-ref @struct:t) i field-id)])
[getter (struct-type-ref @struct:t)])
(procedure-rename (procedure-rename
(lambda (receiver value) (lambda (receiver value)
(if (native? receiver) (if (native? receiver)
(store! receiver i value getter setter) (apply! setter getter receiver value)
(match (type-cast @struct:t receiver name) (match (type-cast @struct:t receiver (object-name setter))
[(? native? r) (store! r i value getter setter)] [(? native? r) (apply! setter getter r value)]
[(union rs) (for ([r rs]) [(union rs) (for ([r rs])
(store! (cdr r) i (merge (car r) value (getter (cdr r) i)) getter setter))]))) (apply! setter getter (cdr r) (merge (car r) value (getter (cdr r)))))])))
name))) (object-name setter))))
(define (@make-struct-field-accessor struct:t i field-id) (define (@make-struct-field-accessor struct:t i field-id)
(let* ([@struct:t (get-type struct:t)] (let* ([@struct:t (get-type struct:t)]
[native? (struct-type-native? @struct:t)] [native? (struct-type-native? @struct:t)]
[name (string->symbol (struct-field-accessor-name @struct:t i field-id))] [getter (make-struct-field-accessor (struct-type-ref @struct:t) i field-id)])
[getter (struct-type-ref @struct:t)])
(procedure-rename (procedure-rename
(lambda (receiver) (lambda (receiver)
(if (native? receiver) (if (native? receiver)
(getter receiver i) (getter receiver)
(match (type-cast @struct:t receiver name) (match (type-cast @struct:t receiver (object-name getter))
[(? native? r) (getter r i)] [(? native? r) (getter r)]
[(union r) (merge** r (getter _ i))]))) [(union r) (merge** r getter)])))
name))) (object-name getter))))
(struct struct-type (pred super native? make ref set! fields immutable? transparent? procedure? equal+hash) (struct struct-type (pred super native? make ref set! fields immutable? transparent? procedure? equal+hash)
#:property prop:procedure #:property prop:procedure

View File

@ -0,0 +1,76 @@
#lang racket
(require racket/syntax racket/splicing )
(provide define-array array-procedure reshape split-at* list-ref*)
; Provides a macro for defining multidimensional arrays. The
; form (define-array var dims vals) defines a multidimensional
; array from a dimension specification and a flat list of values. The macro
; introduces a name transformer var. When used as an identifer
; the var transformer returns the array in the form of a list of
; lists. When used as a function, it takes a sequence of indices
; and returns the element (or a subarray) at the specified position.
;
; The dimension specification should be list of positive natural
; numbers. For example, '(3 2 3) specifies a 3x2x3 array. The
; vals list should contain exactly (apply * dims) values.
;
; The form (define-array var vals) assumes that vals is already a
; list of lists. It simply introduces a name transformer for var,
; as described above.
(define-syntax (define-array stx)
(syntax-case stx ()
[(_ id dims vals)
#`(define-array id (reshape dims vals))]
[(_ id vals)
#`(splicing-let ([array (array-procedure vals)])
(define-syntax id
(syntax-id-rules (set!)
[(set! id e)
(error 'set! "cannot modify an immutable reference: ~s" (syntax->datum #'id))]
[(id idx (... ...)) (array idx (... ...))]
[id (array)])))]))
; This macro expands to a procedure wrapper that allows
; the elements in the given nested list representation of
; an array to be accessed using a call of the form (vals idx ...).
; Applying the resulting procedure to no arguments yields the
; entire array (that is, list of lists).
(define-syntax-rule (array-procedure vals)
(let ([array vals])
(procedure-rename
(lambda pos
(apply list-ref* array pos))
(string->symbol (format "array~aD" (length array))))))
; This function returns a nested list representation
; of the given flat list using the given shape specification.
; The shape specification is a flat list of positive natural
; numbers. For example, '(3 2) specifies a nested list that
; corresponds to a 3x2 array in row major order, i.e.,
; (reshape '(3 2) '(0 1 2 3 4 5)) yields '((0 1 2) (3 4 5)).
; The behavior of this function is unspecified if the length of
; the vals list is not exactly (apply * dims).
(define (reshape dims vals)
(cond [(null? dims) null]
[(null? (cdr dims)) vals]
[else (let ([rest (cdr dims)])
(map (curry reshape (cdr dims)) (split-at* vals (apply * rest))))]))
; Splits a list of size k*n into k sublists of size n. The
; sublists are returned in a list. The behavior of this function
; is unspecified if the length of the list is not a multiple of n.
(define (split-at* vals n)
(if (null? vals)
null
(let-values ([(left right) (split-at vals n)])
(cons left (split-at* right n)))))
; Returns the value in the given nested list representation of a
; mulitdimensional array that is at the specified position. The
; value itself may be a list; for example, (list-ref* '((0 1) (2 3)) 0)
; produces '(0 1) while (list-ref* '((0 1) (2 3)) 1 0) produces 2.
(define (list-ref* vals . pos)
(if (null? pos)
vals
(apply list-ref* (list-ref vals (car pos)) (cdr pos))))

View File

@ -0,0 +1,51 @@
#lang racket
(provide odict? (rename-out [make-odict odict]))
(struct odict (tbl [ord #:mutable] eq)
#:methods gen:dict
[(define (dict-ref dict key [default (lambda () (error "key not found" key))])
(hash-ref (odict-tbl dict) key default))
(define (dict-set! dict key val)
(match-define (odict tbl ord _) dict)
(unless (hash-has-key? tbl key)
(set-odict-ord! dict (cons key ord)))
(hash-set! tbl key val))
(define (dict-remove! dict key)
(match-define (odict tbl ord eq) dict)
(when (hash-has-key? tbl key)
(hash-remove! tbl key)
(set-odict-ord! dict (remove key ord eq))))
(define (dict-iterate-first dict)
(match-define (odict _ ord _) dict)
(and (not (null? ord)) ord))
(define (dict-iterate-next dict pos)
(and (> (length pos) 1) (cdr pos)))
(define (dict-iterate-key dict pos)
(car pos))
(define (dict-iterate-value dict pos)
(hash-ref (odict-tbl dict) (car pos)))
(define (dict-count dict)
(hash-count (odict-tbl dict)))
(define (dict-has-key dict key)
(hash-has-key? (odict-tbl dict) key))
])
(define (make-odict [assocs null] [is-equal? equal?])
(define make-table
(match is-equal?
[(== equal?) make-hash]
[(== eq?) make-hasheq]
[(== eqv?) make-hasheqv]
[_ (error 'odict "expected equal?, eq?, or eqv? equivalence predicate, given ~a" is-equal?)]))
(odict (make-table assocs) (map car assocs) is-equal?))

View File

@ -1,7 +1,7 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label @(require (for-label
rosette/base/form/define rosette/base/form/define rosette/query/form rosette/query/eval rosette/solver/solution
rosette/base/core/term rosette/base/core/term
(only-in rosette/base/core/union union?) (only-in rosette/base/core/union union?)
(only-in rosette/base/base bv bv? bitvector bitvector? bitvector-size (only-in rosette/base/base bv bv? bitvector bitvector? bitvector-size
@ -13,10 +13,11 @@
bit lsb msb bvzero? bvadd1 bvsub1 bit lsb msb bvzero? bvadd1 bvsub1
bvsmin bvsmax bvumin bvumax bvsmin bvsmax bvumin bvumax
rotate-left rotate-right bvrol bvror rotate-left rotate-right bvrol bvror
bool->bitvector bitvector->bool bitvector->bits bool->bitvector bitvector->bool bitvector->bits)
assert vc)) (only-in rosette/base/core/safe assert)
(only-in rosette/base/core/bool asserts))
(for-label racket) (for-label racket)
scribble/core scribble/html-properties scribble/examples racket/sandbox scribble/core scribble/html-properties scribble/eval racket/sandbox
"../util/lifted.rkt") "../util/lifted.rkt")
@ -36,21 +37,19 @@ Technically, Rosette's bitvector datatype embeds the
into a programming language. into a programming language.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(code:line (bv 4 (bitvector 7)) (code:comment "A bitvector literal of size 7.")) (code:line (bv 4 (bitvector 7)) (code:comment "a bitvector literal of size 7"))
(code:line (bv 4 7) (code:comment "A shorthand for the same literal.")) (code:line (bv 4 7) (code:comment "a shorthand for the same literal"))
(code:line (define-symbolic x y (bitvector 7)) (code:comment "Symbolic bitvector constants.")) (code:line (define-symbolic x y (bitvector 7)) (code:comment "symbolic bitvector constants"))
(code:line (bvslt (bv 4 7) (bv -1 7)) (code:comment "Signed 7-bit < comparison of 4 and -1.")) (code:line (bvslt (bv 4 7) (bv -1 7)) (code:comment "signed 7-bit < comparison of 4 and -1"))
(code:line (bvult (bv 4 7) (bv -1 7)) (code:comment "Unsigned 7-bit < comparison of 4 and -1.")) (code:line (bvult (bv 4 7) (bv -1 7)) (code:comment "unsigned 7-bit < comparison of 4 and -1"))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (bvadd x (if b y (bv 3 4))) (code:comment "This typechecks only when b is true,")) (code:line (bvadd x (if b y (bv 3 4))) (code:comment "this typechecks only when b is true"))
(code:line (vc) (code:comment "so Rosette emits a corresponding assertion."))] (code:line (asserts) (code:comment "so Rosette emits a corresponding assertion"))]
@defproc[(bitvector [size (and/c integer? positive? (not/c term?) (not/c union?))]) bitvector?]{ @defproc[(bitvector [size (and/c integer? positive? (not/c term?) (not/c union?))]) bitvector?]{
Returns a type predicate that recognizes bitvectors of the given @racket[size]. Returns a type predicate that recognizes bitvectors of the given @racket[size].
Note that @racket[size] must be a concrete positive integer. Note that @racket[size] must be a concrete positive integer.
The type predicate itself is recognized by the @racket[bitvector?] predicate. The type predicate itself is recognized by the @racket[bitvector?] predicate.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define bv6? (bitvector 6)) (define bv6? (bitvector 6))
(bv6? 1) (bv6? 1)
@ -61,17 +60,15 @@ into a programming language.
} }
@defproc[(bitvector? [v any/c]) boolean?]{ @defproc[(bitvector? [v any/c]) boolean?]{
Returns true if @racket[v] is a concrete type predicate that recognizes bitvector values. Returns true if @racket[v] is a concrete type predicate that recognizes bitvector values.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define bv6? (bitvector 6)) (define bv6? (bitvector 6))
(define bv7? (bitvector 7)) (define bv7? (bitvector 7))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (bitvector? bv6?) (code:comment "A concrete bitvector type.")) (code:line (bitvector? bv6?) (code:comment "a concrete bitvector type"))
(code:line (bitvector? (if b bv6? bv7?)) (code:comment "Not a concrete type.")) (code:line (bitvector? (if b bv6? bv7?)) (code:comment "not a concrete type"))
(code:line (bitvector? integer?) (code:comment "Not a bitvector type.")) (code:line (bitvector? integer?) (code:comment "not a bitvector type"))
(code:line (bitvector? 3) (code:comment "Not a type."))]} (code:line (bitvector? 3) (code:comment "not a type"))]}
@defproc[(bv [val (and/c integer? (not/c term?) (not/c union?))] @defproc[(bv [val (and/c integer? (not/c term?) (not/c union?))]
[size (and/c (or/c bitvector? (and/c integer? positive?)) [size (and/c (or/c bitvector? (and/c integer? positive?))
@ -89,7 +86,7 @@ into a programming language.
(bv? (if b (bv 3 6) #t))] (bv? (if b (bv 3 6) #t))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@section{Comparison Operators} @section{Comparison Operators}
@ -102,83 +99,71 @@ into a programming language.
[(bvugt [x (bitvector n)] [y (bitvector n)]) boolean?] [(bvugt [x (bitvector n)] [y (bitvector n)]) boolean?]
[(bvsge [x (bitvector n)] [y (bitvector n)]) boolean?] [(bvsge [x (bitvector n)] [y (bitvector n)]) boolean?]
[(bvuge [x (bitvector n)] [y (bitvector n)]) boolean?])]{ [(bvuge [x (bitvector n)] [y (bitvector n)]) boolean?])]{
Compares two bitvector values of the same bitvector type. Compares two bitvector values of the same bitvector type.
Comparison relations include Comparison relations include
equality (@racket[bveq]) and signed / unsigned versions of equality (@racket[bveq]) and signed / unsigned versions of
<, <=, >, and >= (@racket[bvslt], @racket[bvult], @racket[bvsle], @racket[bvule], <, <=, >, and >= (@racket[bvslt], @racket[bvult], @racket[bvsle], @racket[bvule],
@racket[bvsgt], and @racket[bvugt]). @racket[bvsgt], and @racket[bvugt]).
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(code:line (define-symbolic u v (bitvector 7)) (code:comment "Symbolic bitvector constants.")) (code:line (define-symbolic u v (bitvector 7)) (code:comment "symbolic bitvector constants"))
(code:line (bvslt (bv 4 7) (bv -1 7)) (code:comment "Signed 7-bit < comparison of 4 and -1.")) (code:line (bvslt (bv 4 7) (bv -1 7)) (code:comment "signed 7-bit < comparison of 4 and -1"))
(code:line (bvult (bv 4 7) (bv -1 7)) (code:comment "Unsigned 7-bit < comparison of 4 and -1.")) (code:line (bvult (bv 4 7) (bv -1 7)) (code:comment "unsigned 7-bit < comparison of 4 and -1"))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (bvsge u (if b v (bv 3 4))) (code:comment "This typechecks only when b is true,")) (code:line (bvsge u (if b v (bv 3 4))) (code:comment "this typechecks only when b is true"))
(code:line (vc) (code:comment "so Rosette emits a corresponding assertion."))] (code:line (asserts) (code:comment "so Rosette emits a corresponding assertion"))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@section{Bitwise Operators} @section{Bitwise Operators}
@defproc[(bvnot [x (bitvector n)]) (bitvector n)]{ @defproc[(bvnot [x (bitvector n)]) (bitvector n)]{
Returns the bitwise negation of the given bitvector value. Returns the bitwise negation of the given bitvector value.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bvnot (bv -1 4)) (bvnot (bv -1 4))
(bvnot (bv 0 4)) (bvnot (bv 0 4))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (bvnot (if b 0 (bv 0 4))) (code:comment "This typechecks only when b is false,")) (code:line (bvnot (if b 0 (bv 0 4))) (code:comment "this typechecks only when b is false"))
(code:line (vc) (code:comment "so Rosette emits a corresponding assertion."))] (code:line (asserts) (code:comment "so Rosette emits a corresponding assertion"))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(bvand [x (bitvector n)] ...+) (bitvector n)] @defproc*[([(bvand [x (bitvector n)] ...+) (bitvector n)]
[(bvor [x (bitvector n)] ...+) (bitvector n)] [(bvor [x (bitvector n)] ...+) (bitvector n)]
[(bvxor [x (bitvector n)] ...+) (bitvector n)])]{ [(bvxor [x (bitvector n)] ...+) (bitvector n)])]{
Returns the bitwise "and", "or", "xor" of one or more bitvector values of the same type.
Returns the bitwise ``and'', ``or'', ``xor'' of one or more bitvector values of the same type.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bvand (bv -1 4) (bv 2 4)) (bvand (bv -1 4) (bv 2 4))
(bvor (bv 0 3) (bv 1 3)) (bvor (bv 0 3) (bv 1 3))
(bvxor (bv -1 5) (bv 1 5)) (bvxor (bv -1 5) (bv 1 5))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (code:line (bvand (bv -1 4)
(bvand (bv -1 4) (if b 0 (bv 2 4))) (code:comment "this typechecks only when b is false"))
(if b 0 (bv 2 4))) (code:comment "This typechecks only when b is false,")) (code:line (asserts) (code:comment "so Rosette emits a corresponding assertion"))]
(code:line
(vc) (code:comment "so Rosette emits a corresponding assertion."))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(bvshl [x (bitvector n)] [y (bitvector n)]) (bitvector n)] @defproc*[([(bvshl [x (bitvector n)] [y (bitvector n)]) (bitvector n)]
[(bvlshr [x (bitvector n)] [y (bitvector n)]) (bitvector n)] [(bvlshr [x (bitvector n)] [y (bitvector n)]) (bitvector n)]
[(bvashr [x (bitvector n)] [y (bitvector n)]) (bitvector n)])]{ [(bvashr [x (bitvector n)] [y (bitvector n)]) (bitvector n)])]{
Returns the left, logical right, or arithmetic right shift of @racket[x] by Returns the left, logical right, or arithmetic right shift of @racket[x] by
@racket[y] bits, where @racket[x] and @racket[y] are bitvector values of the same type. @racket[y] bits, where @racket[x] and @racket[y] are bitvector values of the same type.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bvshl (bv 1 4) (bv 2 4)) (bvshl (bv 1 4) (bv 2 4))
(bvlshr (bv -1 3) (bv 1 3)) (bvlshr (bv -1 3) (bv 1 3))
(bvashr (bv -1 5) (bv 1 5)) (bvashr (bv -1 5) (bv 1 5))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (bvshl (bv -1 4) (code:line (bvshl (bv -1 4)
(if b 0 (bv 2 4))) (code:comment "This typechecks only when b is false,")) (if b 0 (bv 2 4))) (code:comment "this typechecks only when b is false"))
(code:line (vc) (code:comment "so Rosette emits a corresponding assertion."))] (code:line (asserts) (code:comment "so Rosette emits a corresponding assertion"))]
} }
@section{Arithmetic Operators} @section{Arithmetic Operators}
@defproc[(bvneg [x (bitvector n)]) (bitvector n)]{ @defproc[(bvneg [x (bitvector n)]) (bitvector n)]{
Returns the arithmetic negation of the given bitvector value. Returns the arithmetic negation of the given bitvector value.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bvneg (bv -1 4)) (bvneg (bv -1 4))
(bvneg (bv 0 4)) (bvneg (bv 0 4))
@ -186,52 +171,46 @@ Returns the left, logical right, or arithmetic right shift of @racket[x] by
(bvneg z)] (bvneg z)]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@(rosette-eval '(clear-terms!)) @(rosette-eval '(clear-terms!))
@defproc*[([(bvadd [x (bitvector n)] ...+) (bitvector n)] @defproc*[([(bvadd [x (bitvector n)] ...+) (bitvector n)]
[(bvsub [x (bitvector n)] ...+) (bitvector n)] [(bvsub [x (bitvector n)] ...+) (bitvector n)]
[(bvmul [x (bitvector n)] ...+) (bitvector n)])]{ [(bvmul [x (bitvector n)] ...+) (bitvector n)])]{
Returns the sum, difference, or product of one or more bitvector values of the same type. Returns the sum, difference, or product of one or more bitvector values of the same type.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bvadd (bv -1 4) (bv 2 4)) (bvadd (bv -1 4) (bv 2 4))
(bvsub (bv 0 3) (bv 1 3)) (bvsub (bv 0 3) (bv 1 3))
(bvmul (bv -1 5) (bv 1 5)) (bvmul (bv -1 5) (bv 1 5))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(bvadd (bv -1 4) (bv 2 4) (if b (bv 1 4) "bad")) (bvadd (bv -1 4) (bv 2 4) (if b (bv 1 4) "bad"))
(vc)] (asserts)]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(bvsdiv [x (bitvector n)] [y (bitvector n)]) (bitvector n)] @defproc*[([(bvsdiv [x (bitvector n)] [y (bitvector n)]) (bitvector n)]
[(bvudiv [x (bitvector n)] [y (bitvector n)]) (bitvector n)] [(bvudiv [x (bitvector n)] [y (bitvector n)]) (bitvector n)]
[(bvsrem [x (bitvector n)] [y (bitvector n)]) (bitvector n)] [(bvsrem [x (bitvector n)] [y (bitvector n)]) (bitvector n)]
[(bvurem [x (bitvector n)] [y (bitvector n)]) (bitvector n)] [(bvurem [x (bitvector n)] [y (bitvector n)]) (bitvector n)]
[(bvsmod [x (bitvector n)] [y (bitvector n)]) (bitvector n)])]{ [(bvsmod [x (bitvector n)] [y (bitvector n)]) (bitvector n)])]{
Returns (un)signed quotient, remainder, or modulo of two bitvector values of the same type. Returns (un)signed quotient, remainder, or modulo of two bitvector values of the same type.
All five operations are defined even when the second argument is zero. All five operations are defined even when the second argument is zero.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bvsdiv (bv -3 4) (bv 2 4)) (bvsdiv (bv -3 4) (bv 2 4))
(bvudiv (bv -3 3) (bv 2 3)) (bvudiv (bv -3 3) (bv 2 3))
(bvsmod (bv 1 5) (bv 0 5)) (bvsmod (bv 1 5) (bv 0 5))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(bvsrem (bv -3 4) (if b (bv 2 4) "bad")) (bvsrem (bv -3 4) (if b (bv 2 4) "bad"))
(vc)] (asserts)]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@section{Conversion Operators} @section{Conversion Operators}
@defproc[(concat [x bv?] ...+) bv?]{ @defproc[(concat [x bv?] ...+) bv?]{
Returns the bitwise concatenation of the given bitvector values. Returns the bitwise concatenation of the given bitvector values.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(concat (bv -1 4) (bv 0 1) (bv -1 3)) (concat (bv -1 4) (bv 0 1) (bv -1 3))
(define-symbolic b boolean?) (define-symbolic b boolean?)
@ -239,96 +218,89 @@ All five operations are defined even when the second argument is zero.
} }
@defproc[(extract [i integer?] [j integer?] [x (bitvector n)]) (bitvector (+ 1 (- i j)))]{ @defproc[(extract [i integer?] [j integer?] [x (bitvector n)]) (bitvector (+ 1 (- i j)))]{
Extracts bits @racket[i] down to @racket[j] from a bitvector of size @racket[n], yielding a Extracts bits @racket[i] down to @racket[j] from a bitvector of size @racket[n], yielding a
bitvector of size i - j + 1. This procedure assumes that @racket[n] > @racket[i] >= @racket[j] >= 0. bitvector of size i - j + 1. This procedure assumes that @racket[n] > @racket[i] >= @racket[j] >= 0.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(extract 2 1 (bv -1 4)) (extract 2 1 (bv -1 4))
(extract 3 3 (bv 1 4)) (extract 3 3 (bv 1 4))
(define-symbolic i j integer?) (define-symbolic i j integer?)
(extract i j (bv 1 2)) (eval:alts (extract i j (bv 1 2)) (begin (extract i j (bv 1 2)) "{[(&& (= 0 j) (= 1 i)) (bv 1 2)] ...}"))
(vc)] (asserts)]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(sign-extend [x bv?] [t (or/c bitvector? union?)]) bv?] @defproc*[([(sign-extend [x bv?] [t (or/c bitvector? union?)]) bv?]
[(zero-extend [x bv?] [t (or/c bitvector? union?)]) bv?])]{ [(zero-extend [x bv?] [t (or/c bitvector? union?)]) bv?])]{
Returns a bitvector of type @racket[t] that represents the (un)signed Returns a bitvector of type @racket[t] that represents the (un)signed
extension of the bitvector @racket[x]. extension of the bitvector @racket[x].
Note that both @racket[x] and @racket[t] may be symbolic. The size of @racket[t] Note that both @racket[x] and @racket[t] may be symbolic. The size of @racket[t]
must not be smaller than the size of @racket[x]'s type. must not be smaller than the size of @racket[x]'s type.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(sign-extend (bv -3 4) (bitvector 6)) (sign-extend (bv -3 4) (bitvector 6))
(zero-extend (bv -3 4) (bitvector 6)) (zero-extend (bv -3 4) (bitvector 6))
(define-symbolic b c boolean?) (define-symbolic b c boolean?)
(zero-extend (bv -3 4) (if b (bitvector 5) (bitvector 6))) (zero-extend (bv -3 4) (if b (bitvector 5) (bitvector 6)))
(zero-extend (bv -3 4) (if b (bitvector 5) "bad")) (zero-extend (bv -3 4) (if b (bitvector 5) "bad"))
(vc) (asserts)
(zero-extend (bv -3 4) (if c (bitvector 5) (bitvector 1))) (zero-extend (bv -3 4) (if c (bitvector 5) (bitvector 1)))
(vc)] (asserts)]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(bitvector->integer [x bv?]) integer?] @defproc*[([(bitvector->integer [x bv?]) integer?]
[(bitvector->natural [x bv?]) integer?])]{ [(bitvector->natural [x bv?]) integer?])]{
Returns the (un)signed integer value of the given bitvector. Returns the (un)signed integer value of the given bitvector.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bitvector->integer (bv -1 4)) (bitvector->integer (bv -1 4))
(bitvector->natural (bv -1 4)) (bitvector->natural (bv -1 4))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(bitvector->integer (if b (bv -1 3) (bv -3 4))) (bitvector->integer (if b (bv -1 3) (bv -3 4)))
(bitvector->integer (if b (bv -1 3) "bad")) (bitvector->integer (if b (bv -1 3) "bad"))
(vc)] (asserts)]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(integer->bitvector [i integer?] [t (or/c bitvector? union?)]) bv?])]{ @defproc*[([(integer->bitvector [i integer?] [t (or/c bitvector? union?)]) bv?])]{
Returns a bitvector of type @racket[t] that represents the @var[k] lowest order bits Returns a bitvector of type @racket[t] that represents the @var[k] lowest order bits
of the integer @racket[i], where @var[k] is the size of @racket[t]. of the integer @racket[i], where @var[k] is the size of @racket[t].
Note that both @racket[i] and @racket[t] may be symbolic. Note that both @racket[i] and @racket[t] may be symbolic.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(integer->bitvector 4 (bitvector 2)) (integer->bitvector 4 (bitvector 2))
(integer->bitvector 15 (bitvector 4)) (integer->bitvector 15 (bitvector 4))
(define-symbolic b c boolean?) (define-symbolic b c boolean?)
(integer->bitvector (if b pi 3) (if c (bitvector 5) (bitvector 6))) (integer->bitvector (if b pi 3) (if c (bitvector 5) (bitvector 6)))
(vc)] (asserts)]
} }
@section{Additional Operators} @section{Additional Operators}
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc[(bit [i integer?] [x (bitvector n)]) (bitvector 1)]{ @defproc[(bit [i integer?] [x (bitvector n)]) (bitvector 1)]{
Extracts the @racket[i]th bit from the bitvector @racket[x] of size @racket[n], yielding a Extracts the @racket[i]th bit from the bitvector @racket[x] of size @racket[n], yielding a
bitvector of size 1. This procedure assumes that @racket[n] > @racket[i] >= 0. bitvector of size 1. This procedure assumes that @racket[n] > @racket[i] >= 0.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bit 1 (bv 3 4)) (bit 1 (bv 3 4))
(bit 2 (bv 1 4)) (bit 2 (bv 1 4))
(define-symbolic i integer?) (define-symbolic i integer?)
(define-symbolic x (bitvector 4)) (define-symbolic x (bitvector 4))
(bit i x) (eval:alts (bit i x)
(vc)] (begin (bit i x)
"(ite* (⊢ (= 0 i) (extract 0 0 x))
(⊢ (= 1 i) (extract 1 1 x))
(⊢ (= 2 i) (extract 2 2 x))
(⊢ (= 3 i) (extract 3 3 x))))"))
(asserts)]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(lsb [x (bitvector n)]) (bitvector 1)] @defproc*[([(lsb [x (bitvector n)]) (bitvector 1)]
[(msb [x (bitvector n)]) (bitvector 1)])]{ [(msb [x (bitvector n)]) (bitvector 1)])]{
Returns the least or most significant bit of @racket[x]. Returns the least or most significant bit of @racket[x].
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(lsb (bv 3 4)) (lsb (bv 3 4))
(msb (bv 3 4)) (msb (bv 3 4))
@ -339,12 +311,10 @@ Note that both @racket[i] and @racket[t] may be symbolic.
] ]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc[(bvzero? [x (bitvector n)]) boolean?]{ @defproc[(bvzero? [x (bitvector n)]) boolean?]{
Returns @racket[(bveq x (bv 0 n))]. Returns @racket[(bveq x (bv 0 n))].
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x (bitvector 4)) (define-symbolic x (bitvector 4))
(bvzero? x) (bvzero? x)
@ -355,13 +325,11 @@ Returns @racket[(bveq x (bv 0 n))].
] ]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(bvadd1 [x (bitvector n)]) (bitvector n)] @defproc*[([(bvadd1 [x (bitvector n)]) (bitvector n)]
[(bvsub1 [x (bitvector n)]) (bitvector n)])]{ [(bvsub1 [x (bitvector n)]) (bitvector n)])]{
Returns @racket[(bvadd x (bv 1 n))] or @racket[(bvsub x (bv 1 n))]. Returns @racket[(bvadd x (bv 1 n))] or @racket[(bvsub x (bv 1 n))].
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x (bitvector 4)) (define-symbolic x (bitvector 4))
(bvadd1 x) (bvadd1 x)
@ -374,15 +342,13 @@ Returns @racket[(bveq x (bv 0 n))].
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(bvsmin [x (bitvector n)] ...+) (bitvector n)] @defproc*[([(bvsmin [x (bitvector n)] ...+) (bitvector n)]
[(bvumin [x (bitvector n)] ...+) (bitvector n)] [(bvumin [x (bitvector n)] ...+) (bitvector n)]
[(bvsmax [x (bitvector n)] ...+) (bitvector n)] [(bvsmax [x (bitvector n)] ...+) (bitvector n)]
[(bvumax [x (bitvector n)] ...+) (bitvector n)])]{ [(bvumax [x (bitvector n)] ...+) (bitvector n)])]{
Returns the (un)signed minimum or maximum of one or more bitvector values of the same type. Returns the (un)signed minimum or maximum of one or more bitvector values of the same type.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bvsmin (bv -1 4) (bv 2 4)) (bvsmin (bv -1 4) (bv 2 4))
(bvumin (bv -1 4) (bv 2 4)) (bvumin (bv -1 4) (bv 2 4))
@ -390,74 +356,72 @@ Returns the (un)signed minimum or maximum of one or more bitvector values of the
(bvumax (bv -1 4) (bv 2 4)) (bvumax (bv -1 4) (bv 2 4))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(bvsmin (bv -1 4) (bv 2 4) (if b (bv 1 4) (bv 3 8))) (bvsmin (bv -1 4) (bv 2 4) (if b (bv 1 4) (bv 3 8)))
(vc)] (asserts)]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(bvrol [x (bitvector n)] [y (bitvector n)]) (bitvector n)] @defproc*[([(bvrol [x (bitvector n)] [y (bitvector n)]) (bitvector n)]
[(bvror [x (bitvector n)] [y (bitvector n)]) (bitvector n)])]{ [(bvror [x (bitvector n)] [y (bitvector n)]) (bitvector n)])]{
Returns the left or right rotation of @racket[x] by @racket[(bvurem y n)] bits, where Returns the left or right rotation of @racket[x] by @racket[(bvurem y n)] bits, where
@racket[x] and @racket[y] are bitvector values of the same type. @racket[x] and @racket[y] are bitvector values of the same type.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bvrol (bv 3 4) (bv 2 4)) (bvrol (bv 3 4) (bv 2 4))
(bvrol (bv 3 4) (bv -2 4)) (bvrol (bv 3 4) (bv -2 4))
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (code:line (bvror (bv 3 4)
(bvror (bv 3 4) (if b 0 (bv 2 4))) (code:comment "this typechecks only when b is false"))
(if b 0 (bv 2 4))) (code:comment "This typechecks only when b is false,")) (code:line (asserts) (code:comment "so Rosette emits a corresponding assertion"))]
(code:line
(vc) (code:comment "so Rosette emits a corresponding assertion."))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(rotate-left [i integer?] [x (bitvector n)]) (bitvector n)] @defproc*[([(rotate-left [i integer?] [x (bitvector n)]) (bitvector n)]
[(rotate-right [i integer?] [x (bitvector n)]) (bitvector n)])]{ [(rotate-right [i integer?] [x (bitvector n)]) (bitvector n)])]{
Returns the left or right rotation of @racket[x] by @racket[i] bits. Returns the left or right rotation of @racket[x] by @racket[i] bits.
These procedures assume that @racket[n] > @racket[i] >= 0. See @racket[bvrol] These procedures assume that @racket[n] > @racket[i] >= 0. See @racket[bvrol]
and @racket[bvror] for an alternative way to perform rotations that usually and @racket[bvror] for an alternative way to perform rotations that usually
leads to faster solving times. leads to faster solving times.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(rotate-left 3 (bv 3 4)) (rotate-left 3 (bv 3 4))
(rotate-right 1 (bv 3 4)) (rotate-right 1 (bv 3 4))
(define-symbolic i integer?) (define-symbolic i integer?)
(define-symbolic b boolean?) (define-symbolic b boolean?)
(eval:alts (rotate-left i (if b (bv 3 4) (bv 7 8)))
(begin
(rotate-left i (if b (bv 3 4) (bv 7 8))) (rotate-left i (if b (bv 3 4) (bv 7 8)))
(vc) "{[b (ite* (⊢ (= 0 i) (bv #x3 4)) (⊢ (= 1 i) (bv #x6 4)) (⊢ (= 2 ...) ...) ...)]
[(! b) (ite* (⊢ (= 0 i) (bv #x07 8)) (⊢ (= 1 i) (bv #x0e 8)) (⊢ (= ...) ...) ...)]}"))
(asserts)
] ]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc[(bitvector->bits [x (bitvector n)]) (listof (bitvector 1))]{
@defproc[(bitvector->bits [x (bitvector n)]) (listof (bitvector n))]{
Returns the bits of @racket[x] as a list, i.e., @racket[(list (bit 0 x) ... (bit (- n 1) x))]. Returns the bits of @racket[x] as a list, i.e., @racket[(list (bit 0 x) ... (bit (- n 1) x))].
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bitvector->bits (bv 3 4)) (bitvector->bits (bv 3 4))
(define-symbolic y (bitvector 2)) (define-symbolic y (bitvector 2))
(bitvector->bits y) (bitvector->bits y)
(define-symbolic b boolean?) (define-symbolic b boolean?)
(bitvector->bits (if b (bv 3 4) y)) ] (eval:alts (bitvector->bits (if b (bv 3 4) y))
"{[b ((bv #b1 1) (bv #b1 1) (bv #b0 1) (bv #b0 1))]
[(! b) ((extract 0 0 y) (extract 1 1 y))]}")
]
} }
@defproc[(bitvector->bool [x (bitvector n)]) boolean?]{ @defproc[(bitvector->bool [x (bitvector n)]) boolean?]{
Returns @racket[(not (bvzero? x))]. Returns @racket[(not (bvzero? x))].
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc[(bool->bitvector [b any/c] [t (or/c positive-integer? (bitvector n)) (bitvector 1)]) bv?]{ @defproc[(bool->bitvector [b any/c] [t (or/c positive-integer? (bitvector n)) (bitvector 1)]) bv?]{
Returns @racket[(bv 0 t)] if @racket[(false? b)] and otherwise returns @racket[(bv 1 t)], where Returns @racket[(bv 0 t)] if @racket[(false? b)] and otherwise returns @racket[(bv 1 t)], where
@racket[t] is @racket[(bitvector 1)] by default. If provided, @racket[t] must be a concrete positive @racket[t] is @racket[(bitvector 1)] by default. If provided, @racket[t] must be a concrete positive
integer or a concrete bitvector type value. integer or a concrete bitvector type value.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(bool->bitvector #f 3) (bool->bitvector #f 3)
(bool->bitvector "non-false-value") (bool->bitvector "non-false-value")

View File

@ -1,12 +1,13 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label @(require (for-label
rosette/base/form/define rosette/query/form rosette/query/query rosette/solver/solution rosette/base/form/define rosette/query/form rosette/query/eval rosette/solver/solution
rosette/base/core/term (only-in rosette/query/finitize current-bitwidth) rosette/base/core/term (only-in rosette/query/finitize current-bitwidth)
(only-in rosette/base/base ! && || => <=> exists forall function? assert vc with-vc (only-in rosette/base/base ! && || => <=> exists forall function?)
result-state result-value)) (only-in rosette/base/core/safe assert)
(only-in rosette/base/core/bool asserts with-asserts))
(except-in (for-label racket) =>) (except-in (for-label racket) =>)
scribble/core scribble/html-properties scribble/examples racket/sandbox racket/runtime-path scribble/core scribble/html-properties scribble/eval racket/sandbox racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@ -15,7 +16,7 @@
@(define bools (select '(boolean? false? true false boolean=? not nand nor implies xor))) @(define bools (select '(boolean? false? true false boolean=? not nand nor implies xor)))
@(define nums (select (remove* '(expt) '(number? complex? real? rational? integer? exact-integer? exact-nonnegative-integer? exact-positive-integer? inexact-real? fixnum? flonum? double-flonum? single-flonum? zero? positive? negative? even? odd? exact? inexact? inexact->exact exact->inexact real->single-flonum real->double-flonum + - * / quotient remainder quotient/ modulo add1 sub1 abs max min gcd lcm round floor ceiling truncate numerator denominator rationalize = < <= > >= sqrt integer-sqrt integer-sqrt/ expt exp log sin cos tan asin acos atan make-rectangular make-polar real-part imag-part magnitude angle bitwise-ior bitwise-and bitwise-xor bitwise-not bitwise-bit-set? bitwise-bit-field arithmetic-shift integer-length random random-seed make-pseudo-random-generator pseudo-random-generator? current-pseudo-random-generator pseudo-random-generator->vector vector->pseudo-random-generator vector->pseudo-random-generator! pseudo-random-generator-vector? number->string string->number real->decimal-string integer-bytes->integer integer->integer-bytes floating-point-bytes->real real->floating-point-bytes system-big-endian? pi pi.f degrees->radians radians->degrees sqr sgn conjugate sinh cosh tanh exact-round exact-floor exact-ceiling exact-truncate order-of-magnitude nan? infinite?)))) @(define nums (select '(number? complex? real? rational? integer? exact-integer? exact-nonnegative-integer? exact-positive-integer? inexact-real? fixnum? flonum? double-flonum? single-flonum? zero? positive? negative? even? odd? exact? inexact? inexact->exact exact->inexact real->single-flonum real->double-flonum + - * / quotient remainder quotient/ modulo add1 sub1 abs max min gcd lcm round floor ceiling truncate numerator denominator rationalize = < <= > >= sqrt integer-sqrt integer-sqrt/ expt exp log sin cos tan asin acos atan make-rectangular make-polar real-part imag-part magnitude angle bitwise-ior bitwise-and bitwise-xor bitwise-not bitwise-bit-set? bitwise-bit-field arithmetic-shift integer-length random random-seed make-pseudo-random-generator pseudo-random-generator? current-pseudo-random-generator pseudo-random-generator->vector vector->pseudo-random-generator vector->pseudo-random-generator! pseudo-random-generator-vector? number->string string->number real->decimal-string integer-bytes->integer integer->integer-bytes floating-point-bytes->real real->floating-point-bytes system-big-endian? pi pi.f degrees->radians radians->degrees sqr sgn conjugate sinh cosh tanh exact-round exact-floor exact-ceiling exact-truncate order-of-magnitude nan? infinite?)))
@title[#:tag "sec:bools+ints+reals"]{Booleans, Integers, and Reals} @title[#:tag "sec:bools+ints+reals"]{Booleans, Integers, and Reals}
@ -27,6 +28,7 @@ Rosette lifts the following operations on booleans, integers, and reals:
(list (list @elem{Booleans} @bools) (list (list @elem{Booleans} @bools)
(list @elem{Integers and Reals} @nums))] (list @elem{Integers and Reals} @nums))]
Lifted boolean operations retain their Racket semantics on both concrete and symbolic values. Lifted boolean operations retain their Racket semantics on both concrete and symbolic values.
In particular, Rosette extends the intepretation of these operations to work on symbolic values in (logically) the In particular, Rosette extends the intepretation of these operations to work on symbolic values in (logically) the
same way that they work on concrete values. same way that they work on concrete values.
@ -37,7 +39,7 @@ same way that they work on concrete values.
(boolean? #t) (boolean? #t)
(boolean? #f) (boolean? #f)
(boolean? 1) (boolean? 1)
(code:line (not b) (code:comment "Produces a logical negation of b."))] (code:line (not b) (code:comment "produces a logical negation of b"))]
Lifted numeric operations, in contrast, match their Racket semantics Lifted numeric operations, in contrast, match their Racket semantics
only when applied to concrete values. Their symbolic semantics depends on the only when applied to concrete values. Their symbolic semantics depends on the
@ -61,7 +63,7 @@ logical meaning; e.g., unlike Racket's shortcircuiting
evaluates all of its arguments before taking their evaluates all of its arguments before taking their
conjunction. conjunction.
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc[(! [v boolean?]) boolean?]{ @defproc[(! [v boolean?]) boolean?]{
Returns the negation of the given boolean value. Returns the negation of the given boolean value.
@ -69,11 +71,11 @@ conjunction.
(! #f) (! #f)
(! #t) (! #t)
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (! (if b #f 3)) (code:comment "This typechecks only when b is true,")) (code:line (! (if b #f 3)) (code:comment "this typechecks only when b is true"))
(code:line (vc) (code:comment "so Rosette emits a corresponding assertion."))] (code:line (asserts) (code:comment "so Rosette emits a corresponding assertion"))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(&& [v boolean?] ...) boolean?] @defproc*[([(&& [v boolean?] ...) boolean?]
[(|| [v boolean?] ...) boolean?])]{ [(|| [v boolean?] ...) boolean?])]{
@ -81,22 +83,22 @@ Returns the logical conjunction or disjunction of zero or more boolean values.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(&&) (&&)
(||) (||)
(code:line (&& #f (begin (displayln "hello") #t)) (code:comment "No shortcircuiting.")) (code:line (&& #f (begin (displayln "hello") #t)) (code:comment "no shortcircuiting"))
(define-symbolic a b boolean?) (define-symbolic a b boolean?)
(code:line (&& a (if b #t 1)) (code:comment "This typechecks only when b is true,")) (code:line (&& a (if b #t 1)) (code:comment "this typechecks only when b is true"))
(code:line (vc) (code:comment "so Rosette emits a corresponding assertion."))] (code:line (asserts) (code:comment "so Rosette emits a corresponding assertion"))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc*[([(=> [x boolean?] [y boolean?]) boolean?] @defproc*[([(=> [x boolean?] [y boolean?]) boolean?]
[(<=> [x boolean?] [y boolean?]) boolean?])]{ [(<=> [x boolean?] [y boolean?]) boolean?])]{
Returns the logical implication or bi-implication of two boolean values. Returns the logical implication or bi-implication of two boolean values.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(code:line (=> #f (begin (displayln "hello") #f)) (code:comment "No shortcircuiting.")) (code:line (=> #f (begin (displayln "hello") #f)) (code:comment "no shortcircuiting"))
(define-symbolic a b boolean?) (define-symbolic a b boolean?)
(code:line (<=> a (if b #t 1)) (code:comment "This typechecks only when b is true,")) (code:line (<=> a (if b #t 1)) (code:comment "this typechecks only when b is true"))
(code:line (vc) (code:comment "so Rosette emits a corresponding assertion."))] (code:line (asserts) (code:comment "so Rosette emits a corresponding assertion"))]
} }
@section[#:tag "sec:quantifiers"]{Quantifiers} @section[#:tag "sec:quantifiers"]{Quantifiers}
@ -109,7 +111,7 @@ body may have side effects (e.g., generate assertions). When
there are no side effects, however, these constructs have there are no side effects, however, these constructs have
their usual logical meaning. their usual logical meaning.
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@(rosette-eval '(current-bitwidth #f)) @(rosette-eval '(current-bitwidth #f))
@defproc*[([(forall [vs (listof constant?)] [body boolean?]) boolean?] @defproc*[([(forall [vs (listof constant?)] [body boolean?]) boolean?]
[(exists [vs (listof constant?)] [body boolean?]) boolean?])]{ [(exists [vs (listof constant?)] [body boolean?]) boolean?])]{
@ -119,32 +121,27 @@ symbolic constants @racket[vs] are treated as quantified variables.
Each constant in @racket[vs] must have a non-@racket[function?] @racket[solvable?] type. Each constant in @racket[vs] must have a non-@racket[function?] @racket[solvable?] type.
The @racket[body] argument is a boolean value, which is usually a symbolic The @racket[body] argument is a boolean value, which is usually a symbolic
boolean expression over the quantified variables @racket[vs] and, boolean expression over the quantified variables @racket[vs] and,
optionally, over free symbolic (Skolem) constants. Any assertions and assumptions emitted during optionally, over free symbolic (Skolem) constants. Any assertions emitted during
the evaluation of @racket[body] are added to the current verification condition @racket[(vc)]. the evaluation of @racket[body] are @emph{added to the assertion store}. This
This may be the desired behavior in some circumstances but not in others, so to avoid may be the desired behavior in some circumstances but not in others, so to avoid
surprises, it is best to handle side effects separately and call quantifiers surprises, it is best to handle these assertions separately and call quantifiers
with pure bodies, as shown below. with pure bodies, as shown below.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(current-bitwidth #f) (current-bitwidth #f)
(define-symbolic x y integer?) (define-symbolic x y integer?)
(code:line (code:line (exists (list x y) (= x y)) (code:comment "pure body expression"))
(exists (list x y) (= x y)) (code:comment "Pure body expression.")) (define-symbolic t boolean?)
(define-symbolic b boolean?) (code:line (forall (list t x y) (= (+ (if t x 'x) 1) y)) (code:comment "body emits a type assertion"))
(code:line (asserts)
(forall (list b x y) (clear-asserts!)
(= (+ (if b x 'x) 1) y)) (code:comment "Body emits a type assertion.")) (code:comment "To avoid surprises, capture assertions using with-asserts,")
(vc)
(clear-vc!)
(code:comment "To avoid surprises, capture assertions and assumptions using with-vc,")
(code:comment "and handle as desired, e.g.:") (code:comment "and handle as desired, e.g.:")
(define out (with-vc (= (+ (if b x 'x) 1) y))) (define-values (q-body q-asserts) (with-asserts (= (+ (if t x 'x) 1) y)))
out q-body
(define out-val (result-value out)) q-asserts
(define out-vc (result-state out)) (forall (list t x y) (=> (apply && q-asserts) q-body))
(forall (list b x y) (asserts)
(=> (&& (vc-assumes out-vc) (vc-asserts out-vc)) out-val))
(vc)
] ]
The usual lexical scoping rules apply to quantified symbolics; if @racket[body] is The usual lexical scoping rules apply to quantified symbolics; if @racket[body] is
@ -154,33 +151,19 @@ Quantified symbolics are not bound in a @racket[model], unless they also appear
freely in some formulas. freely in some formulas.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x y integer?) (define-symbolic a b integer?)
(code:line (code:line (define f (forall (list a) (exists (list b) (= a (+ a b))))) (code:comment "a and b are not free in f,"))
(define f (code:line (solve (assert f)) (code:comment "so not bound in the model."))
(forall (list x) (code:line (define g (forall (list a) (= a (+ a b)))) (code:comment "b is free in g,"))
(exists (list y) (code:line (solve (assert g)) (code:comment "so it is bound in the model."))
(= x (+ x y))))) (code:comment "x and y are not free in f,")) (code:line (define h (exists (list a) (forall (list a) (= a (+ a a))))) (code:comment "body refers to the innermost a,"))
(code:line (code:line (solve (assert h)) (code:comment "so h is unsatisfiable."))
(solve (assert f)) (code:comment "so they are not bound in the model."))
(code:line
(define g
(forall (list x)
(= x (+ x y)))) (code:comment "y is free in g,"))
(code:line
(solve (assert g)) (code:comment "so it is bound in the model."))
(code:line
(define h
(exists (list x)
(forall (list x)
(= x (+ x x))))) (code:comment "The body of h refers to the innermost x,"))
(code:line
(solve (assert h)) (code:comment "so h is unsatisfiable."))
] ]
When executing queries over assertions that contain quantified formulas, When executing queries over assertions that contain quantified formulas,
the @racket[current-bitwidth] parameter must be set to @racket[#f]. the @racket[current-bitwidth] parameter must be set to @racket[#f].
Quantified formulas may not appear in any assertion or assumption that is passed Quantified formulas may not appear in any assertion that is passed to a @racket[synthesize] query,
to a @racket[synthesize] query. either via an (implicit or explicit) assumption or a guarantee expression.
} }

View File

@ -13,15 +13,15 @@
0 0
() ()
() ()
(c values c (0 (u . "(! b)\n")))) (c values c (0 (u . "(! b)"))))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((! #f) ((3) 0 () 0 () () (q values #t)) #"" #"") ((! #f) ((3) 0 () 0 () () (q values #t)) #"" #"")
((! #t) ((3) 0 () 0 () () (q values #f)) #"" #"") ((! #t) ((3) 0 () 0 () () (q values #f)) #"" #"")
((define-symbolic b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((! (if b #f 3)) ((3) 0 () 0 () () (q values #t)) #"" #"") ((! (if b #f 3)) ((3) 0 () 0 () () (q values #t)) #"" #"")
((vc) ((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -30,15 +30,15 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t b)\n")))) (c values c (c (0 (u . "b")))))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((&&) ((3) 0 () 0 () () (q values #t)) #"" #"") ((&&) ((3) 0 () 0 () () (q values #t)) #"" #"")
((||) ((3) 0 () 0 () () (q values #f)) #"" #"") ((||) ((3) 0 () 0 () () (q values #f)) #"" #"")
((&& #f (begin (displayln "hello") #t)) ((&& #f (begin (displayln "hello") #t))
((3) 0 () 0 () () (q values #f)) ((3) 0 () 0 () () (q values #f))
#"hello\n" #""
#"") #"")
((define-symbolic a b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic a b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((&& a (if b #t 1)) ((&& a (if b #t 1))
@ -50,10 +50,10 @@
0 0
() ()
() ()
(c values c (0 (u . "a\n")))) (c values c (0 (u . "a"))))
#"" #""
#"") #"")
((vc) ((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -62,13 +62,13 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t b)\n")))) (c values c (c (0 (u . "b")))))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((=> #f (begin (displayln "hello") #f)) ((=> #f (begin (displayln "hello") #f))
((3) 0 () 0 () () (q values #t)) ((3) 0 () 0 () () (q values #t))
#"hello\n" #""
#"") #"")
((define-symbolic a b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic a b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((<=> a (if b #t 1)) ((<=> a (if b #t 1))
@ -80,10 +80,10 @@
0 0
() ()
() ()
(c values c (0 (u . "a\n")))) (c values c (0 (u . "a"))))
#"" #""
#"") #"")
((vc) ((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -92,10 +92,10 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t b)\n")))) (c values c (c (0 (u . "b")))))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((current-bitwidth #f) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((current-bitwidth #f) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((current-bitwidth #f) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((current-bitwidth #f) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x y integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic x y integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
@ -108,11 +108,11 @@
0 0
() ()
() ()
(c values c (0 (u . "(exists (x y) (= x y))\n")))) (c values c (0 (u . "(exists (x y) (= x y))"))))
#"" #""
#"") #"")
((define-symbolic b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic t boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((forall (list b x y) (= (+ (if b x 'x) 1) y)) ((forall (list t x y) (= (+ (if t x 'x) 1) y))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -121,10 +121,10 @@
0 0
() ()
() ()
(c values c (0 (u . "(forall (b x y) (= y (+ 1 x)))\n")))) (c values c (0 (u . "(forall (t x y) (= y (+ 1 x)))"))))
#"" #""
#"") #"")
((vc) ((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -133,15 +133,15 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t b)\n")))) (c values c (c (0 (u . "t")))))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define out (with-vc (= (+ (if b x 'x) 1) y))) ((define-values (q-body q-asserts) (with-asserts (= (+ (if t x 'x) 1) y)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
(out (q-body
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -150,20 +150,10 @@
0 0
() ()
() ()
(c values c (0 (u . "(normal (= y (+ 1 x)) (vc #t b))\n")))) (c values c (0 (u . "(= y (+ 1 x))"))))
#"" #""
#"") #"")
((define out-val (result-value out)) (q-asserts
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define out-vc (result-state out))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((forall
(list b x y)
(=> (&& (vc-assumes out-vc) (vc-asserts out-vc)) out-val))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -172,10 +162,10 @@
0 0
() ()
() ()
(c values c (0 (u . "(forall (b x y) (|| (! b) (= y (+ 1 x))))\n")))) (c values c (c (0 (u . "t")))))
#"" #""
#"") #"")
((vc) ((forall (list t x y) (=> (apply && q-asserts) q-body))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -184,11 +174,12 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t #t)\n")))) (c values c (0 (u . "(forall (t x y) (|| (! t) (= y (+ 1 x))))"))))
#"" #""
#"") #"")
((define-symbolic x y integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((asserts) ((3) 0 () 0 () () (q values ())) #"" #"")
((define f (forall (list x) (exists (list y) (= x (+ x y))))) ((define-symbolic a b integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define f (forall (list a) (exists (list b) (= a (+ a b)))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
@ -201,10 +192,10 @@
0 0
() ()
() ()
(c values c (0 (u . "(model)\n")))) (c values c (0 (u . "(model)"))))
#"" #""
#"") #"")
((define g (forall (list x) (= x (+ x y)))) ((define g (forall (list a) (= a (+ a b))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
@ -217,10 +208,10 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [y 0])\n")))) (c values c (0 (u . "(model\n [b 0])"))))
#"" #""
#"") #"")
((define h (exists (list x) (forall (list x) (= x (+ x x))))) ((define h (exists (list a) (forall (list a) (= a (+ a a)))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
@ -233,6 +224,6 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")

View File

@ -15,40 +15,6 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
(sol ((evaluate v1 sol) ((3) 0 () 0 () () (c values c (b! . 4))) #"" #"")
((3) ((evaluate v2 sol) ((3) 0 () 0 () () (c values c (b! . 4))) #"" #"")
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [x 4]\n [b #t])\n"))))
#""
#"")
((evaluate v1 sol)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'#&4\n"))))
#""
#"")
((evaluate v2 sol)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'#&4\n"))))
#""
#"")
((evaluate (eq? v1 v2) sol) ((3) 0 () 0 () () (q values #t)) #"" #"") ((evaluate (eq? v1 v2) sol) ((3) 0 () 0 () () (q values #t)) #"" #"")

View File

@ -1,8 +1,7 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label rosette/base/form/define rosette/query/query racket @(require (for-label rosette/base/form/define racket)
(only-in rosette/base/base assert)) scribble/core scribble/html-properties scribble/eval racket/sandbox racket/runtime-path
scribble/core scribble/html-properties scribble/examples racket/sandbox racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@(define box-ops (select '(box? box box-immutable unbox set-box! box-cas!))) @(define box-ops (select '(box? box box-immutable unbox set-box! box-cas!)))
@ -31,10 +30,9 @@ they point to the same box object. Boxes can be concrete or symbolic, and they
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x integer?) (define-symbolic x integer?)
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (define v1 (box x)) (code:comment "v1 is a box with symbolic contents.")) (code:line (define v1 (box x)) (code:comment "v1 is a box with symbolic contents"))
(code:line (define v2 (if b v1 (box 3))) (code:comment "v2 is a symbolic box.")) (code:line (define v2 (if b v1 (box 3))) (code:comment "v2 is a symbolic box"))
(define sol (solve (assert (= 4 (unbox v2))))) (define sol (solve (assert (= 4 (unbox v2)))))
sol
(evaluate v1 sol) (evaluate v1 sol)
(evaluate v2 sol) (evaluate v2 sol)
(evaluate (eq? v1 v2) sol)] (evaluate (eq? v1 v2) sol)]

View File

@ -25,7 +25,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(ite b 1 3)\n")))) (c values c (0 (u . "(ite b 1 3)"))))
#"" #""
#"") #"")
((point-y p) ((point-y p)
@ -37,7 +37,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(ite b 2 4)\n")))) (c values c (0 (u . "(ite b 2 4)"))))
#"" #""
#"") #"")
((define sol (solve (assert (= (point-x p) 3)))) ((define sol (solve (assert (= (point-x p) 3))))
@ -53,7 +53,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(point 3 4)\n")))) (c values c (0 (u . "#(struct:point 3 4)"))))
#"" #""
#"") #"")
((define-generics viewable (view viewable)) ((define-generics viewable (view viewable))
@ -93,7 +93,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(ite b 2 3)\n")))) (c values c (0 (u . "(ite b 2 3)"))))
#"" #""
#"") #"")
((define sol (solve (assert (= (view p) 3)))) ((define sol (solve (assert (= (view p) 3))))
@ -109,6 +109,6 @@
0 0
() ()
() ()
(c values c (0 (u . "(circle 3)\n")))) (c values c (0 (u . "#(struct:circle 3)"))))
#"" #""
#"") #"")

View File

@ -1,10 +1,10 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label @(require (for-label
rosette/base/form/define rosette/query/query rosette/base/form/define rosette/query/query rosette/query/eval
rosette/base/core/term (only-in rosette/base/base assert) rosette/base/core/term (only-in rosette/base/core/safe assert)
racket racket/generic) racket racket/generic)
scribble/core scribble/html-properties scribble/examples racket/sandbox racket/runtime-path scribble/core scribble/html-properties scribble/eval racket/sandbox racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@(define-runtime-path root ".") @(define-runtime-path root ".")
@ -27,10 +27,10 @@ Structure types are defined using the @racket[struct] syntax. Defining a
structure type in this way also defines the necessary procedures for structure type in this way also defines the necessary procedures for
creating instances of that type and for accessing their fields. creating instances of that type and for accessing their fields.
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(struct point (x y) #:transparent) (code:comment "Immutable transparent type.") (struct point (x y) #:transparent) (code:comment "immutable transparent type")
(struct pt (x y)) (code:comment "Opaque immutable type.") (struct pt (x y)) (code:comment "opaque immutable type")
(struct pnt (x y) #:mutable #:transparent) (code:comment "Mutable transparent type.")] (struct pnt (x y) #:mutable #:transparent) (code:comment "mutable transparent type")]
Rosette structures can be concrete or symbolic. Their semantics matches that of Racket, Rosette structures can be concrete or symbolic. Their semantics matches that of Racket,
with one important exception: immutable transparent structures are treated as values with one important exception: immutable transparent structures are treated as values
@ -39,10 +39,10 @@ rather than references. This @seclink["sec:equality"]{means} that two such stru
Structures of opaque or mutable types are treated as references. Two such structures are Structures of opaque or mutable types are treated as references. Two such structures are
@racket[eq?] only if the point to the same instance of the same type. @racket[eq?] only if the point to the same instance of the same type.
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(code:line (eq? (point 1 2) (point 1 2)) (code:comment "point structures are values.")) (code:line (eq? (point 1 2) (point 1 2)) (code:comment "point structures are values"))
(code:line (eq? (pt 1 2) (pt 1 2)) (code:comment "pt structures are references.")) (code:line (eq? (pt 1 2) (pt 1 2)) (code:comment "pt structures are references"))
(code:line (eq? (pnt 1 2) (pnt 1 2)) (code:comment "pnt structures are references."))] (code:line (eq? (pnt 1 2) (pnt 1 2)) (code:comment "pnt structures are references"))]
Like @tech[#:key "unsolvable type"]{unsolvable built-in datatypes}, Like @tech[#:key "unsolvable type"]{unsolvable built-in datatypes},
symbolic structures cannot be created using @racket[define-symbolic]. Instead, symbolic structures cannot be created using @racket[define-symbolic]. Instead,
@ -50,9 +50,9 @@ they are created implicitly, by, for example, using an @racket[if] expression
together with a symbolic value. together with a symbolic value.
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (define p (if b (point 1 2) (point 3 4))) (code:comment "p holds a symbolic structure.")) (code:line (define p (if b (point 1 2) (point 3 4))) (code:comment "p holds a symbolic structure"))
(point-x p) (point-x p)
(point-y p) (point-y p)
(define sol (solve (assert (= (point-x p) 3)))) (define sol (solve (assert (= (point-x p) 3))))
@ -67,8 +67,7 @@ properties, generic interfaces, and facilities for defining new properties and i
(list @elem{Lifted Generics} @elem{@generics} ))] (list @elem{Lifted Generics} @elem{@generics} ))]
Lifted generics work as expected with symbolic values: Lifted generics work as expected with symbolic values:
@interaction[#:eval rosette-eval
@examples[#:eval rosette-eval #:label #f
(define-generics viewable (view viewable)) (define-generics viewable (view viewable))
(struct square (side) (struct square (side)
@ -81,7 +80,7 @@ Lifted generics work as expected with symbolic values:
[(define (view self) (circle-radius self))]) [(define (view self) (circle-radius self))])
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (define p (if b (square 2) (circle 3))) (code:comment "p holds a symbolic structure.")) (code:line (define p (if b (square 2) (circle 3))) (code:comment "p holds a symbolic structure"))
(view p) (view p)
(define sol (solve (assert (= (view p) 3)))) (define sol (solve (assert (= (view p) 3))))
(evaluate p sol)] (evaluate p sol)]

View File

@ -2,7 +2,7 @@
@(require (for-label rosette/base/form/define racket) @(require (for-label rosette/base/form/define racket)
(for-label (only-in rosette/base/base function? distinct? ~> bv)) (for-label (only-in rosette/base/base function? distinct? ~> bv))
scribble/core scribble/html-properties scribble/examples racket/sandbox scribble/core scribble/html-properties scribble/eval racket/sandbox
"../util/lifted.rkt") "../util/lifted.rkt")
@ -27,7 +27,7 @@ notion of @racket[equal?] is specified for a particular datatype.
(equal? n 1) (equal? n 1)
(equal? (box n) (box 1)) (equal? (box n) (box 1))
(define-symbolic f g (~> integer? integer?)) (define-symbolic f g (~> integer? integer?))
(code:line (equal? f g) (code:comment "f and g are different procedures."))] (code:line (equal? f g) (code:comment "f and g are different procedures"))]
@(kill-evaluator rosette-eval) @(kill-evaluator rosette-eval)
@(set! rosette-eval (rosette-evaluator)) @(set! rosette-eval (rosette-evaluator))
@ -43,25 +43,25 @@ or two transparent immutable values with @racket[eq?] contents.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(eq? 1 #t) (eq? 1 #t)
(code:line (eq? 1 1.0) (code:comment "equal? transparent solvable values.")) (code:line (eq? 1 1.0) (code:comment "equal? transparent solvable values"))
(code:line (eq? (list 1) (list 1.0)) (code:comment "Transparent immutable values with eq? contents.")) (code:line (eq? (list 1) (list 1.0)) (code:comment "transparent immutable values with eq? contents"))
(code:line (eq? (box 1) (box 1)) (code:comment "But boxes are mutable, so eq? follows Racket,")) (code:line (eq? (box 1) (box 1)) (code:comment "but boxes are mutable, so eq? follows Racket"))
(eq? (list (box 1)) (list (box 1))) (eq? (list (box 1)) (list (box 1)))
(define-symbolic n integer?) (define-symbolic n integer?)
(eq? n 1) (eq? n 1)
(eq? (box n) (box 1)) (eq? (box n) (box 1))
(define-symbolic f g (~> integer? integer?)) (define-symbolic f g (~> integer? integer?))
(code:line (eq? f g) (code:comment "and procedures are opaque, so eq? follows Racket.")) (code:line (eq? f g) (code:comment "and procedures are opaque, so eq? follows Racket"))
(eq? f f)] (eq? f f)]
In addition to lifting Racket's equality predicates, Rosette also provides a @racket[distinct?] predicate In addition to lifting Racket's equality predicates, Rosette also provides a @racket[distinct?] predicate
that returns true iff all of its arguments are distinct from each other. Invoking this predicate that returns true iff all of its arguments are distinct from each other. Invoking this predicate
on arbitrary values has the effect of performing O(@var[N]@superscript{2}) equality on arbitrary values has the effect of performing O(@var[N]@superscript{2}) @racket[not] @racket[equal?]
comparisons. But when applied to symbolic values of a primitive comparisons. But when applied to symbolic values of a primitive
@tech[#:key "solvable type"]{solvable} type, @racket[distinct?] will produce a compact @tech[#:key "solvable type"]{solvable} type, @racket[distinct?] will produce a compact
symbolic value that can be directly solved by the underlying solver. symbolic value that can be directly solved by the underlying solver.
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc[(distinct? [v any/c] ...) boolean?]{ @defproc[(distinct? [v any/c] ...) boolean?]{
Returns true iff all of the given values @racket[v] are distinct---i.e., pairwise un-@racket[equal?] Returns true iff all of the given values @racket[v] are distinct---i.e., pairwise un-@racket[equal?]
to each other. If all values @racket[v] are of the same primitive (non-@racket[function?]) to each other. If all values @racket[v] are of the same primitive (non-@racket[function?])

View File

@ -11,18 +11,7 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((evaluate xs sol) ((evaluate xs sol) ((3) 0 () 0 () () (q values ())) #"" #"")
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'()\n"))))
#""
#"")
((define sol ((define sol
(solve (solve
(begin (begin
@ -32,18 +21,7 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((evaluate xs sol) ((evaluate xs sol) ((3) 0 () 0 () () (q values (-1 0))) #"" #"")
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'(-1 0)\n"))))
#""
#"")
((define-symbolic b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define p (if b (cons 1 2) (cons 4 #f))) ((define p (if b (cons 1 2) (cons 4 #f)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
@ -53,31 +31,9 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((evaluate p sol) ((evaluate p sol) ((3) 0 () 0 () () (q values (4 . #f))) #"" #"")
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'(4 . #f)\n"))))
#""
#"")
((define sol (solve (assert (odd? (car p))))) ((define sol (solve (assert (odd? (car p)))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((evaluate p sol) ((evaluate p sol) ((3) 0 () 0 () () (q values (1 . 2))) #"" #"")
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'(1 . 2)\n"))))
#""
#"")

View File

@ -3,15 +3,9 @@
@(require (for-label @(require (for-label
rosette/base/form/define rosette/query/query rosette/base/form/define rosette/query/query
rosette/base/core/term rosette/base/core/term
(only-in rosette/base/base assert vc clear-vc! define-symbolic (only-in rosette/base/core/safe assert)
length-bv list-ref-bv list-set-bv
take-bv take-right-bv
drop-bv drop-right-bv
list-tail-bv split-at-bv split-at-right-bv
union? bitvector bitvector? bv?
bitvector->natural integer->bitvector)
racket) racket)
scribble/core scribble/html-properties scribble/examples racket/sandbox racket/runtime-path scribble/core scribble/html-properties scribble/eval racket/sandbox racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@ -24,7 +18,7 @@
@(define list-filtering (select '(filter remove remq remv remove* remq* remv* sort))) @(define list-filtering (select '(filter remove remq remv remove* remq* remv* sort)))
@(define list-searching (select '(member memv memq memf findf assoc assv assq assf))) @(define list-searching (select '(member memv memq memf findf assoc assv assq assf)))
@(define more-pair-ops (select '(caar cadr cdar cddr caaar caadr cadar caddr cdaar cdadr cddar cdddr caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr))) @(define more-pair-ops (select '(caar cadr cdar cddr caaar caadr cadar caddr cdaar cdadr cddar cdddr caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr)))
@(define more-list-ops (select '(empty cons? empty? first rest second third fourth fifth sixth seventh eighth ninth tenth last last-pair make-list take drop split-at takef dropf splitf-at take-right drop-right split-at-right takef-right dropf-right splitf-at-right add-between append* flatten remove-duplicates filter-map count partition range append-map filter-not shuffle permutations in-permutations argmin argmax list-set))) @(define more-list-ops (select '(empty cons? empty? first rest second third fourth fifth sixth seventh eighth ninth tenth last last-pair make-list take drop split-at takef dropf splitf-at take-right drop-right split-at-right takef-right dropf-right splitf-at-right add-between append* flatten remove-duplicates filter-map count partition range append-map filter-not shuffle permutations in-permutations argmin argmax )))
@title[#:tag "sec:pair"]{Pairs and Lists} @title[#:tag "sec:pair"]{Pairs and Lists}
@ -40,11 +34,10 @@ and lists cannot be created
via @seclink["sec:symbolic-constants"]{@code{define-symbolic[*]}}. via @seclink["sec:symbolic-constants"]{@code{define-symbolic[*]}}.
Instead, they are created by applying pair- or list-producing procedures to symbolic inputs, Instead, they are created by applying pair- or list-producing procedures to symbolic inputs,
or by controlling the application of such procedures with symbolic values. This or by controlling the application of such procedures with symbolic values. This
pattern for creating non-primitive symbolic values generalizes to all unsolvable datatypes. pattern for creating non-primitive symbolic values generalizes to all unsolvable datatypes.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x y z n integer?) (define-symbolic x y z n integer?)
(code:line (define xs (take (list x y z) n)) (code:comment "(1) xs is a symbolic list.")) (code:line (define xs (take (list x y z) n)) (code:comment "(1) xs is a symbolic list"))
(define sol (solve (assert (null? xs)))) (define sol (solve (assert (null? xs))))
(evaluate xs sol) (evaluate xs sol)
(define sol (define sol
@ -56,15 +49,13 @@ pattern for creating non-primitive symbolic values generalizes to all unsolvable
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (define p (if b (cons 1 2) (cons 4 #f))) (code:comment "(2) p is a symbolic pair.")) (code:line (define p (if b (cons 1 2) (cons 4 #f))) (code:comment "(2) p is a symbolic pair"))
(define sol (solve (assert (boolean? (cdr p))))) (define sol (solve (assert (boolean? (cdr p)))))
(evaluate p sol) (evaluate p sol)
(define sol (solve (assert (odd? (car p))))) (define sol (solve (assert (odd? (car p)))))
(evaluate p sol) (evaluate p sol)
] ]
@section{Lifted Operations on Pairs and Lists}
Rosette lifts the following operations on pairs and lists: Rosette lifts the following operations on pairs and lists:
@tabular[#:style (style #f (list (attributes '((id . "lifted")(class . "boxed"))))) @tabular[#:style (style #f (list (attributes '((id . "lifted")(class . "boxed")))))
(list (list @elem{Pair Operations} @pairs:constructors+selectors) (list (list @elem{Pair Operations} @pairs:constructors+selectors)
@ -76,118 +67,3 @@ Rosette lifts the following operations on pairs and lists:
(list @elem{Additional List Operations} @more-list-ops))] (list @elem{Additional List Operations} @more-list-ops))]
@(kill-evaluator rosette-eval) @(kill-evaluator rosette-eval)
@(set! rosette-eval (rosette-evaluator))
@section{Additional Operations on Pairs and Lists}
Rosette provides the following procedures for operating on lists using @seclink["sec:bitvectors"]{bitvector} indices and lengths. These procedures produce symbolic values that avoid @racketlink[bitvector->natural]{casting} their bitvector arguments to integers, leading to @seclink["sec:notes"]{more efficiently solvable queries}.
@declare-exporting[rosette/base/base #:use-sources (rosette/base/base)]
@defproc[(length-bv [lst list?] [t (or/c bitvector? union?)]) bv?]{
Equivalent to @racket[(integer->bitvector (length lst) t)] but avoids the @racket[integer->bitvector] cast for better solving performance.
@examples[#:eval rosette-eval
(define-symbolic b boolean?)
(define xs (if b '(1 2) '(3 4 5 6)))
xs
(integer->bitvector (length xs) (bitvector 4))
(length-bv xs (bitvector 4))]
}
@defproc[(list-ref-bv [lst list?] [pos bv?]) any/c]{
Equivalent to @racket[(list-ref lst (bitvector->natural pos))] but avoids the @racket[bitvector->natural] cast for better solving performance.
@examples[#:eval rosette-eval
(define-symbolic p (bitvector 1))
(define xs '(1 2 3 4))
(code:comment "Uses a cast and generates a redundant assertion on the range of p:")
(list-ref xs (bitvector->natural p))
(vc)
(clear-vc!)
(code:comment "No cast and no redundant range assertion:")
(list-ref-bv xs p)
(vc)
(code:comment "But the range assertion is generated when needed:")
(define-symbolic q (bitvector 4))
(list-ref-bv xs q)
(vc)]
}
@(rosette-eval '(clear-vc!))
@defproc[(list-set-bv [lst list?] [pos bv?] [val any/c]) list?]{
Equivalent to @racket[(list-set lst (bitvector->natural pos) val)] but avoids the @racket[bitvector->natural] cast for better solving performance.
@examples[#:eval rosette-eval
(define-symbolic p (bitvector 1))
(define xs '(1 2 3 4))
(code:comment "Uses a cast and generates a redundant assertion on the range of p:")
(list-set xs (bitvector->natural p) 5)
(vc)
(clear-vc!)
(code:comment "No cast and no redundant range assertion:")
(list-set-bv xs p 5)
(vc)
(code:comment "But the range assertion is generated when needed:")
(define-symbolic q (bitvector 4))
(list-set-bv xs q 5)
(vc)]
}
@(rosette-eval '(clear-vc!))
@defproc*[([(take-bv [lst any/c] [pos bv?]) list?]
[(take-right-bv [lst any/c] [pos bv?]) any/c]
[(drop-bv [lst any/c] [pos bv?]) any/c]
[(drop-right-bv [lst any/c] [pos bv?]) list?]
[(list-tail-bv [lst any/c] [pos bv?]) any/c])]{
Equivalent to @racket[take], @racket[take-right],
@racket[drop], @racket[drop-right], or @racket[list-tail]
applied to @racket[lst] and
@racket[(bitvector->natural pos)], but avoids the
@racket[bitvector->natural] cast for better solving
performance.
@examples[#:eval rosette-eval
(define-symbolic p (bitvector 1))
(define xs (cons 1 (cons 2 (cons 3 4))))
(code:comment "Uses a cast and generates a redundant assertion on the range of p:")
(take xs (bitvector->natural p))
(vc)
(clear-vc!)
(code:comment "No cast and no redundant range assertion:")
(take-bv xs p)
(vc)
(code:comment "But the range assertion is generated when needed:")
(define-symbolic q (bitvector 4))
(take-bv xs q)
(vc)]
}
@(rosette-eval '(clear-vc!))
@defproc*[([(split-at-bv [lst any/c] [pos bv?]) (list? any/c)]
[(split-at-right-bv [lst any/c] [pos bv?]) (list? any/c)])]{
Equivalent to
@racket[(split-at lst (bitvector->natural pos))] or
@racket[(split-at-right lst (bitvector->natural pos))], but
avoids the @racket[bitvector->natural] cast for better
solving performance.
@examples[#:eval rosette-eval
(define-symbolic p (bitvector 1))
(define xs (cons 1 2))
(code:comment "Uses a cast and generates a redundant assertion on the range of p:")
(split-at xs (bitvector->natural p))
(vc)
(clear-vc!)
(code:comment "No cast and no redundant range assertion:")
(split-at-bv xs p)
(vc)
(code:comment "But the range assertion is generated when needed:")
(define-symbolic q (bitvector 4))
(split-at-bv xs q)
(vc)]}
@(kill-evaluator rosette-eval)

View File

@ -19,7 +19,7 @@
0 0
() ()
() ()
(c values c (0 (u . "*\n")))) (c values c (0 (u . "*"))))
#"" #""
#"") #"")
((define sol (synthesize #:forall (list x) #:guarantee (assert (= x (p x 0))))) ((define sol (synthesize #:forall (list x) #:guarantee (assert (= x (p x 0)))))
@ -35,6 +35,6 @@
0 0
() ()
() ()
(c values c (0 (u . "-\n")))) (c values c (0 (u . "-"))))
#"" #""
#"") #"")

View File

@ -1,11 +1,11 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label @(require (for-label
rosette/base/form/define rosette/query/query rosette/base/form/define rosette/query/query rosette/query/eval
rosette/base/core/term rosette/base/core/term
(only-in rosette/base/base assert) (only-in rosette/base/core/safe assert)
racket) racket)
scribble/core scribble/html-properties scribble/examples racket/sandbox racket/runtime-path scribble/core scribble/html-properties scribble/eval racket/sandbox racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@ -27,7 +27,7 @@ which any symbolic procedure could @racket[evaluate] under any @racket[solution?
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic b boolean?) (define-symbolic b boolean?)
(define-symbolic x integer?) (define-symbolic x integer?)
(code:line (define p (if b * -)) (code:comment "p is a symbolic procedure.")) (code:line (define p (if b * -)) (code:comment "p is a symbolic procedure"))
(define sol (synthesize #:forall (list x) (define sol (synthesize #:forall (list x)
#:guarantee (assert (= x (p x 1))))) #:guarantee (assert (= x (p x 1)))))
(evaluate p sol) (evaluate p sol)

View File

@ -2,17 +2,13 @@
@(require (for-label @(require (for-label
rosette/solver/solver rosette/solver/solution rosette/solver/solver rosette/solver/solution
rosette/solver/smt/z3 rosette/solver/smt/cvc4 (only-in rosette/query/debug debug)
rosette/solver/smt/boolector rosette/solver/smt/z3 rosette/solver/smt/cvc4 rosette/solver/smt/boolector rosette/solver/smt/yices rosette/solver/mip/cplex
rosette/solver/smt/bitwuzla rosette/base/form/define rosette/query/query rosette/query/core
rosette/solver/smt/cvc5
rosette/solver/smt/stp
rosette/solver/smt/yices
rosette/base/form/define rosette/query/query
rosette/base/core/term (only-in rosette/base/base bv?) rosette/base/core/term (only-in rosette/base/base bv?)
(only-in rosette/base/base assert) (only-in rosette/base/core/safe assert)
racket) racket)
scribble/core scribble/html-properties scribble/examples racket/sandbox racket/runtime-path scribble/core scribble/html-properties scribble/eval racket/sandbox racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@(define-runtime-path root ".") @(define-runtime-path root ".")
@ -20,34 +16,31 @@
@title[#:tag "sec:solvers-and-solutions"]{Solvers and Solutions} @title[#:tag "sec:solvers-and-solutions"]{Solvers and Solutions}
@declare-exporting[rosette/query/query @declare-exporting[rosette/query/core
rosette/query/eval
rosette/solver/solver rosette/solver/solver
rosette/solver/solution rosette/solver/solution
rosette/solver/smt/z3 rosette/solver/smt/z3
rosette/solver/mip/cplex
rosette/solver/smt/cvc4 rosette/solver/smt/cvc4
rosette/solver/smt/boolector
rosette/solver/smt/bitwuzla
rosette/solver/smt/cvc5
rosette/solver/smt/stp
rosette/solver/smt/yices rosette/solver/smt/yices
rosette/solver/smt/boolector
#:use-sources #:use-sources
(rosette/query/finitize (rosette/query/finitize
rosette/query/query rosette/query/eval
rosette/solver/solver rosette/solver/solver
rosette/solver/solution rosette/solver/solution
rosette/solver/smt/z3 rosette/solver/smt/z3
rosette/solver/mip/cplex
rosette/solver/smt/cvc4 rosette/solver/smt/cvc4
rosette/solver/smt/boolector rosette/solver/smt/yices
rosette/solver/smt/bitwuzla rosette/solver/smt/boolector)]
rosette/solver/smt/cvc5
rosette/solver/smt/stp
rosette/solver/smt/yices)]
A @deftech{solver} is an automatic reasoning engine, used to answer A @deftech{solver} is an automatic reasoning engine, used to answer
@seclink["sec:queries"]{queries} about Rosette programs. The result of @seclink["sec:queries"]{queries} about Rosette programs. The result of
a solver invocation is a @deftech{solution}, containing either a solver invocation is a @deftech{solution}, containing either
a @tech{binding} of symbolic constants to concrete values, or a @tech{binding} of symbolic constants to concrete values, or
an @link["https://en.wikipedia.org/wiki/Unsatisfiable_core"]{unsatisfiable core}. an @tech[#:key "MUC"]{unsatisfiable core}.
Solvers and solutions may not be symbolic. Two solvers (resp. solutions) are @racket[eq?]/@racket[equal?] Solvers and solutions may not be symbolic. Two solvers (resp. solutions) are @racket[eq?]/@racket[equal?]
if they refer to the same object. if they refer to the same object.
@ -196,7 +189,7 @@ Returns the options the given solver is configured with
@section{Supported Solvers} @section{Supported Solvers}
Rosette supports several SMT solvers. Rosette supports several SMT solvers as well as the CPLEX mixed-integer programming solver.
The @racket[current-solver] parameter controls the solver used for answering solver-aided queries. The @racket[current-solver] parameter controls the solver used for answering solver-aided queries.
Each supported solver is contained in a separate module Each supported solver is contained in a separate module
(e.g., @racketmodname[rosette/solver/smt/z3]), (e.g., @racketmodname[rosette/solver/smt/z3]),
@ -211,7 +204,6 @@ to create a new solver instance.
[#:logic logic (or/c symbol? #f) #f] [#:logic logic (or/c symbol? #f) #f]
[#:options options (hash/c symbol? any/c) (hash)]) solver?] [#:options options (hash/c symbol? any/c) (hash)]) solver?]
[(z3? [v any/c]) boolean?])]{ [(z3? [v any/c]) boolean?])]{
Returns a @racket[solver?] wrapper for the @hyperlink["https://github.com/Z3Prover/z3/"]{Z3} solver from Microsoft Research. Returns a @racket[solver?] wrapper for the @hyperlink["https://github.com/Z3Prover/z3/"]{Z3} solver from Microsoft Research.
Rosette automatically installs a version of Z3; Rosette automatically installs a version of Z3;
the optional @racket[path] argument overrides this version with a path to a new Z3 binary. the optional @racket[path] argument overrides this version with a path to a new Z3 binary.
@ -236,7 +228,6 @@ will send the command @tt{(set-option :smt.relevancy 0)} to Z3 prior to solving.
[#:logic logic (or/c symbol? #f) #f] [#:logic logic (or/c symbol? #f) #f]
[#:options options (hash/c symbol? any/c) (hash)]) solver?] [#:options options (hash/c symbol? any/c) (hash)]) solver?]
[(cvc4? [v any/c]) boolean?])]{ [(cvc4? [v any/c]) boolean?])]{
Returns a @racket[solver?] wrapper for the @hyperlink["http://cvc4.cs.stanford.edu/web/"]{CVC4} solver from NYU and UIowa. Returns a @racket[solver?] wrapper for the @hyperlink["http://cvc4.cs.stanford.edu/web/"]{CVC4} solver from NYU and UIowa.
To use this solver, download and install CVC4 (version 1.8 or later), To use this solver, download and install CVC4 (version 1.8 or later),
@ -268,7 +259,6 @@ without its optional @racket[path] argument.}
[#:logic logic (or/c symbol? #f) #f] [#:logic logic (or/c symbol? #f) #f]
[#:options options (hash/c symbol? any/c) (hash)]) solver?] [#:options options (hash/c symbol? any/c) (hash)]) solver?]
[(boolector? [v any/c]) boolean?])]{ [(boolector? [v any/c]) boolean?])]{
Returns a @racket[solver?] wrapper for the @hyperlink["http://fmv.jku.at/boolector/"]{Boolector} solver from JKU. Returns a @racket[solver?] wrapper for the @hyperlink["http://fmv.jku.at/boolector/"]{Boolector} solver from JKU.
To use this solver, download and install Boolector (version 2.4.1 or later), To use this solver, download and install Boolector (version 2.4.1 or later),
@ -291,146 +281,81 @@ Returns true if the Boolector solver is available for use (i.e., Rosette can loc
If this returns @racket[#f], @racket[(boolector)] will not succeed If this returns @racket[#f], @racket[(boolector)] will not succeed
without its optional @racket[path] argument.} without its optional @racket[path] argument.}
@subsection{Bitwuzla}
@defmodule[rosette/solver/smt/bitwuzla #:no-declare] @subsection{Yices}
@defproc*[([(bitwuzla [#:path path (or/c path-string? #f) #f]
[#:logic logic (or/c symbol? #f) #f]
[#:options options (hash/c symbol? any/c) (hash)]) solver?]
[(bitwuzla? [v any/c]) boolean?])]{
Returns a @racket[solver?] wrapper for the @hyperlink["https://bitwuzla.github.io/"]{Bitwuzla} solver.
To use this solver, download prebuilt Bitwuzla or build it yourself,
and ensure the executable is on your @tt{PATH} or pass the path to the
executable as the optional @racket[path] argument.
Rosette currently tests Bitwuzla at commit
@tt{93a3d930f622b4cef0063215e63b7c3bd10bd663}.
The optional @racket[logic] argument specifies an SMT logic for the solver to use (e.g., @racket['QF_BV]).
Specifying a logic can improve solving performance, but Rosette makes no effort to check that
emitted constraints fall within the chosen logic. The default is @racket[#f],
which uses Bitwuzla's default logic.
The @racket[options] argument provides additional options that are sent to Bitwuzla
via the @tt{set-option} SMT command.
For example, setting @racket[options] to @racket[(hash ':seed 5)]
will send the command @tt{(set-option :seed 5)} to Bitwuzla prior to solving.
}
@defproc[(bitwuzla-available?) boolean?]{
Returns true if the Bitwuzla solver is available for use (i.e., Rosette can locate a @tt{bitwuzla} binary).
If this returns @racket[#f], @racket[(bitwuzla)] will not succeed
without its optional @racket[path] argument.}
@subsection{CVC5}
@defmodule[rosette/solver/smt/cvc5 #:no-declare]
@defproc*[([(cvc5 [#:path path (or/c path-string? #f) #f]
[#:logic logic (or/c symbol? #f) #f]
[#:options options (hash/c symbol? any/c) (hash)]) solver?]
[(cvc5? [v any/c]) boolean?])]{
Returns a @racket[solver?] wrapper for the @hyperlink["https://cvc5.github.io/"]{CVC5} solver.
To use this solver, download prebuilt CVC5 or build it yourself,
and ensure the executable is on your @tt{PATH} or pass the path to the
executable as the optional @racket[path] argument.
Rosette currently tests CVC5 at version 1.0.7.
The optional @racket[logic] argument specifies an SMT logic for the solver to use (e.g., @racket['QF_BV]).
Specifying a logic can improve solving performance, but Rosette makes no effort to check that
emitted constraints fall within the chosen logic. The default is @racket[#f],
which uses CVC5's default logic.
The @racket[options] argument provides additional options that are sent to CVC5
via the @tt{set-option} SMT command.
For example, setting @racket[options] to @racket[(hash ':seed 5)]
will send the command @tt{(set-option :seed 5)} to CVC5 prior to solving.
}
@defproc[(cvc5-available?) boolean?]{
Returns true if the CVC5 solver is available for use (i.e., Rosette can locate a @tt{cvc5} binary).
If this returns @racket[#f], @racket[(cvc5)] will not succeed
without its optional @racket[path] argument.}
@subsection{STP}
@defmodule[rosette/solver/smt/stp #:no-declare]
@defproc*[([(stp [#:path path (or/c path-string? #f) #f]
[#:logic logic (or/c symbol? #f) #f]
[#:options options (hash/c symbol? any/c) (hash)]) solver?]
[(stp? [v any/c]) boolean?])]{
Returns a @racket[solver?] wrapper for the @hyperlink["https://stp.github.io/"]{STP} solver.
To use this solver, download prebuilt STP or build it yourself,
and ensure the executable is on your @tt{PATH} or pass the path to the
executable as the optional @racket[path] argument.
Rosette currently tests STP at commit
@tt{0510509a85b6823278211891cbb274022340fa5c}.
Note that as of December 2023, the STP version on Mac Homebrew is too old to be
supported by Rosette.
The optional @racket[logic] argument specifies an SMT logic for the solver to use (e.g., @racket['QF_BV]).
Specifying a logic can improve solving performance, but Rosette makes no effort to check that
emitted constraints fall within the chosen logic. The default is @racket[#f],
which uses STP's default logic.
The @racket[options] argument provides additional options that are sent to STP
via the @tt{set-option} SMT command.
For example, setting @racket[options] to @racket[(hash ':seed 5)]
will send the command @tt{(set-option :seed 5)} to STP prior to solving.
}
@defproc[(stp-available?) boolean?]{
Returns true if the STP solver is available for use (i.e., Rosette can locate a @tt{stp} binary).
If this returns @racket[#f], @racket[(stp)] will not succeed
without its optional @racket[path] argument.}
@subsection{Yices2}
@defmodule[rosette/solver/smt/yices #:no-declare] @defmodule[rosette/solver/smt/yices #:no-declare]
@defproc*[([(yices [#:path path (or/c path-string? #f) #f] @defproc*[([(yices [#:path path (or/c path-string? #f) #f]
[#:logic logic (or/c symbol? #f) 'QF_BV] [#:logic logic symbol? 'ALL]
[#:options options (hash/c symbol? any/c) (hash)]) solver?] [#:options options (hash/c symbol? any/c) (hash)]) solver?]
[(yices? [v any/c]) boolean?])]{ [(yices? [v any/c]) boolean?])]{
Returns a @racket[solver?] wrapper for the @hyperlink["http://yices.csl.sri.com/"]{Yices} solver from SRI.
Returns a @racket[solver?] wrapper for the @hyperlink["https://yices.csl.sri.com/"]{Yices2} solver. To use this solver, download and install Yices (version 2.6.0 or later),
and either add the @tt{yices-smt2} executable to your @tt{PATH}
To use this solver, download prebuilt Yices2 or build it yourself, or pass the path to the executable as the optional @racket[path] argument.
and ensure the executable is on your @tt{PATH} or pass the path to the
executable as the optional @racket[path] argument.
Rosette specifically uses the @tt{yices-smt2} executable, which is the Yices2
solver with its SMTLIB2 frontend enabled.
Note that just building (without installing) Yices2 will produce an executable
named @tt{yices_smt2}. Running the installation step produces an executable
with the correct name. However, it is safe to skip the installation step and
simply rename or symlink the @tt{yices_smt2} executable to @tt{yices-smt2}.
Rosette currently tests Yices2 at commit
@tt{e27cf308cffb0ecc6cc7165c10e81ca65bc303b3}.
The optional @racket[logic] argument specifies an SMT logic for the solver to use (e.g., @racket['QF_BV]). The optional @racket[logic] argument specifies an SMT logic for the solver to use (e.g., @racket['QF_BV]).
Specifying a logic can improve solving performance, but Rosette makes no effort to check that Specifying a logic can improve solving performance, but Rosette makes no effort to check that
emitted constraints fall within the chosen logic. Yices2 expects a logic to be emitted constraints fall within the chosen logic. The default is @racket['ALL].
set; Rosette defaults to @racket['QF_BV].
The @racket[options] argument provides additional options that are sent to Yices2 The @racket[options] argument provides additional options that are sent to Yices
via the @tt{set-option} SMT command. via the @tt{set-option} SMT command.
For example, setting @racket[options] to @racket[(hash ':seed 5)] For example, setting @racket[options] to @racket[(hash ':random-seed 5)]
will send the command @tt{(set-option :seed 5)} to Yices2 prior to solving. will send the command @tt{(set-option :random-seed 5)} to Yices prior to solving.
} }
@defproc[(yices-available?) boolean?]{ @defproc[(yices-available?) boolean?]{
Returns true if the Yices2 solver is available for use (i.e., Rosette can locate a @tt{yices-smt2} binary). Returns true if the Yices solver is available for use (i.e., Rosette can locate a @tt{yices-smt2} binary).
If this returns @racket[#f], @racket[(yices)] will not succeed If this returns @racket[#f], @racket[(yices)] will not succeed
without its optional @racket[path] argument.} without its optional @racket[path] argument.}
@subsection{CPLEX}
@defmodule[rosette/solver/mip/cplex #:no-declare]
@defproc*[([(cplex [#:path path (or/c path-string? #f) #f]
[#:options options (hash/c symbol? any/c) (hash)]) solver?]
[(cplex? [v any/c]) boolean?])]{
Returns a @racket[solver?] wrapper for the
@hyperlink["https://www.ibm.com/developerworks/community/blogs/jfp/entry/CPLEX_Is_Free_For_Students?lang=en"]{CPLEX} solver from IBM.
To use this solver, download and install CPLEX,
and locate the CPLEX interactive executable,
which is likely to be at @tt{CPLEX_Studio*/cplex/bin/x86-64*/cplex}.
Either add this directory to your @tt{PATH},
or pass the path to the executable as the @racket[path] argument.
The @racket[options] argument provides additional options for configuring CPLEX.
Setting the key @racket['timeout] in @racket[options] to an integer controls the solving timeout in seconds.
Setting the key @racket['verbose] in @racket[options] to @racket[#t] displays detailed output from the CPLEX solver.
The assertions given to @racket[solver-assert] must be linear in order to use the CPLEX solver. Otherwise, an @racket[exn:fail] exception is raised.
}
@defproc[(cplex-available?) boolean?]{
Returns true if the CPLEX solver is available for use (i.e., Rosette can locate a @tt{cplex} binary).
If this returns @racket[#f], @racket[(cplex)] will not succeed
without its optional @racket[path] argument.}
@defproc[(solver-check-with-init
[solver cplex?]
[#:mip-sol final-solution-file (or/c path-string? #f) #f]
[#:mip-start initial-solution-file (or/c path-string? #f) #f])
solution?]{
Like @racket[solver-check], but accepts only a CPLEX solver,
and takes optional arguments @racket[final-solution-file] and/or @racket[initial-solution-file].
When @racket[final-solution-file] is a @racket[path-string?],
the solver will save the solution to the given file in
@hyperlink["https://www.ibm.com/support/knowledgecenter/bs/SSSA5P_12.6.2/ilog.odms.cplex.help/CPLEX/FileFormats/topics/MST.html"]{MST format}
if a solution exists.
This file can be used as the @racket[initial-solution-file] in a later call to @racket[solver-check-with-init]
to provide starting values for variables.
Note that @racket[initial-solution-file] does not have to be a satisfiable solution, but it must be in MST format.
}
@section{Solutions} @section{Solutions}
A solution to a set of formulas may be satisfiable (@racket[sat?]), unsatisfiable (@racket[unsat?]), A solution to a set of formulas may be satisfiable (@racket[sat?]), unsatisfiable (@racket[unsat?]),
@ -443,17 +368,17 @@ the given constraints are satisfiable or not.
A solution supports the following operations: A solution supports the following operations:
@defproc[(solution? [v any/c]) boolean?]{ @defproc[(solution? [value any/c]) boolean?]{
Returns true if @racket[v] is a solution.} Returns true if the given @racket[value] is a solution.}
@defproc[(sat? [v any/c]) boolean?]{ @defproc[(sat? [value any/c]) boolean?]{
Returns true if @racket[v] is a satisfiable solution.} Returns true if the given @racket[value] is a satisfiable solution.}
@defproc[(unsat? [v any/c]) boolean?]{ @defproc[(unsat? [value any/c]) boolean?]{
Returns true if @racket[v] is an unsatisfiable solution.} Returns true if the given @racket[value] is an unsatisfiable solution.}
@defproc[(unknown? [v any/c]) boolean?]{ @defproc[(unknown? [value any/c]) boolean?]{
Returns true if @racket[v] is an unknown solution.} Returns true if the given @racket[value] is an unknown solution.}
@defproc*[([(sat) sat?] @defproc*[([(sat) sat?]
[(sat [binding (hash/c constant? any/c #:immutable #t)]) sat?])]{ [(sat [binding (hash/c constant? any/c #:immutable #t)]) sat?])]{
@ -470,27 +395,26 @@ are provided, applying @racket[core] to the resulting solution produces @racket[
indicating that there is no satisfying solution but indicating that there is no satisfying solution but
core extraction was not performed. (Core extraction is an expensive core extraction was not performed. (Core extraction is an expensive
operation that is not supported by all solvers; those that do support it operation that is not supported by all solvers; those that do support it
do not compute a core unless explicitly asked for one via @racket[solver-debug].)} usually don't compute a core unless explicitly asked for one via @racket[solver-debug].)}
@defproc[(unknown) unknown?]{ @defproc[(unknown) unknown?]{
Returns an unknown solution.} Returns an unknown solution.}
@defproc[(model [sol sat?]) (hash/c constant? any/c #:immutable #t)]{ @defproc[(model [solution sat?]) (hash/c constant? any/c #:immutable #t)]{
Returns the binding stored in the given satisfiable solution. The binding is an immutable Returns the binding stored in the given satisfiable solution. The binding is an immutable
hashmap from symbolic constants to values. hashmap from symbolic constants to values.
} }
@defproc[(core [sol unsat?]) (or/c (listof (and/c constant? boolean?)) #f)]{ @defproc[(core [solution unsat?]) (or/c (listof (and/c constant? boolean?)) #f)]{
Returns the unsatisfiable core stored in the given satisfiable solution. If the solution is Returns the unsatisfiable core stored in the given satisfiable solution. If the solution is
@racket[unsat?] and a core was computed, the result is a list of boolean values that @racket[unsat?] and a core was computed, the result is a list of boolean values that
are collectively unsatisfiable. Otherwise, the result is @racket[#f]. are collectively unsatisfiable. Otherwise, the result is @racket[#f].
} }
@defproc[(evaluate [v any/c] [sol sat?]) any/c]{ @defproc[(evaluate [v any/c] [solution sat?]) any/c]{
Given a Rosette value and a satisfiable solution, @racket[evaluate] produces a Given a Rosette value and a satisfiable solution, @racket[evaluate] produces a
new value obtained by replacing every symbolic constant @var[c] in @racket[v] new value obtained by replacing every symbolic constant @var[c] in @racket[v]
with @racket[(sol #, @var[c])] and simplifying the result. with @racket[(solution #, @var[c])] and simplifying the result.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic a b boolean?) (define-symbolic a b boolean?)
(define-symbolic x y integer?) (define-symbolic x y integer?)
@ -502,27 +426,22 @@ with @racket[(sol #, @var[c])] and simplifying the result.
(evaluate (list 4 5 x) sol) (evaluate (list 4 5 x) sol)
(define vec (vector a)) (define vec (vector a))
(evaluate vec sol) (evaluate vec sol)
(code:line (eq? vec (evaluate vec sol)) (code:comment "Evaluation produces a new vector.")) (code:line (eq? vec (evaluate vec sol)) (code:comment "evaluation produces a new vector"))
(evaluate (+ x y) sol) (evaluate (+ x y) sol)
(evaluate (and a b) sol) (evaluate (and a b) sol)
]} ]}
@defproc[(complete-solution [sol solution?] [consts (listof constant?)]) solution?]{ @defproc[(complete-solution [sol solution?] [consts (listof constant?)]) solution?]{
Given a solution @racket[sol] and a list of symbolic constants @racket[consts],
Given a solution @racket[sol] and a list of symbolic returns a solution that is complete with respect to the given list.
constants @racket[consts], returns a solution that is In particular, if @racket[sol] is satisfiable, the returned solution is also satisfiable, and
complete with respect to the given list. In particular, if it extends the @racket[sol] model with default bindings for all constants in @racket[consts]
@racket[sol] is satisfiable, the returned solution is also that are not bound by @racket[sol]. Otherwise, @racket[sol] itself is returned.
satisfiable, and it extends the @racket[sol] model with
default bindings for all constants in @racket[consts] that
are not bound by @racket[sol]. Otherwise, @racket[sol]
itself is returned.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic a boolean?) (define-symbolic a boolean?)
(define-symbolic x integer?) (define-symbolic x integer?)
(define sol (solve (assert a))) (define sol (solve (assert a)))
(code:line sol (code:comment "No binding for x.")) (code:line sol (code:comment "no binding for x"))
(complete-solution sol (list a x)) (complete-solution sol (list a x))
(complete-solution (solve (assert #f)) (list a x)) (complete-solution (solve (assert #f)) (list a x))
]} ]}

View File

@ -8,7 +8,7 @@
0 0
() ()
() ()
(c values c (0 (u . "#<z3>\n")))) (c values c (0 (u . "#<z3>"))))
#"" #""
#"") #"")
((define-symbolic a b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic a b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
@ -18,31 +18,9 @@
#"" #""
#"") #"")
((sat? sol) ((3) 0 () 0 () () (q values #t)) #"" #"") ((sat? sol) ((3) 0 () 0 () () (q values #t)) #"" #"")
((evaluate (list 4 5 x) sol) ((evaluate (list 4 5 x) sol) ((3) 0 () 0 () () (q values (4 5 1))) #"" #"")
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'(4 5 1)\n"))))
#""
#"")
((define vec (vector a)) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define vec (vector a)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((evaluate vec sol) ((evaluate vec sol) ((3) 0 () 0 () () (c values c (v! #t))) #"" #"")
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'#(#t)\n"))))
#""
#"")
((eq? vec (evaluate vec sol)) ((3) 0 () 0 () () (q values #f)) #"" #"") ((eq? vec (evaluate vec sol)) ((3) 0 () 0 () () (q values #f)) #"" #"")
((evaluate (+ x y) sol) ((3) 0 () 0 () () (q values 3)) #"" #"") ((evaluate (+ x y) sol) ((3) 0 () 0 () () (q values 3)) #"" #"")
((evaluate (and a b) sol) ((evaluate (and a b) sol)
@ -54,7 +32,7 @@
0 0
() ()
() ()
(c values c (0 (u . "b\n")))) (c values c (0 (u . "b"))))
#"" #""
#"") #"")
((define-symbolic a boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic a boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
@ -72,7 +50,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [a #t])\n")))) (c values c (0 (u . "(model\n [a #t])"))))
#"" #""
#"") #"")
((complete-solution sol (list a x)) ((complete-solution sol (list a x))
@ -84,7 +62,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [a #t]\n [x 0])\n")))) (c values c (0 (u . "(model\n [a #t]\n [x 0])"))))
#"" #""
#"") #"")
((complete-solution (solve (assert #f)) (list a x)) ((complete-solution (solve (assert #f)) (list a x))
@ -96,6 +74,6 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")

View File

@ -13,7 +13,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(app f 1)\n")))) (c values c (0 (u . "(app f 1)"))))
#"" #""
#"") #"")
((define-symbolic x real?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic x real?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
@ -26,10 +26,10 @@
0 0
() ()
() ()
(c values c (0 (u . "(app f (real->integer x))\n")))) (c values c (0 (u . "(app f (real->integer x))"))))
#"" #""
#"") #"")
((vc) ((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -38,7 +38,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t (int? x))\n")))) (c values c (c (0 (u . "(int? x)")))))
#"" #""
#"") #"")
((define sol (solve (assert (not (equal? (f x) (f 1)))))) ((define sol (solve (assert (not (equal? (f x) (f 1))))))
@ -55,7 +55,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(fv integer?~>boolean?)\n")))) (c values c (0 (u . "(fv integer?~>boolean?)"))))
#"" #""
#"") #"")
((evaluate x sol) ((3) 0 () 0 () () (q values 0)) #"" #"") ((evaluate x sol) ((3) 0 () 0 () () (q values 0)) #"" #"")
@ -71,7 +71,13 @@
0 0
() ()
() ()
(c values c (0 (u . "(ite (= 1 (real->integer x)) #f #t)\n")))) (c
values
c
(0
(u
.
"(ite (= 1 (real->integer x)) #t (ite (= 0 (real->integer x)) #f #t))"))))
#"" #""
#"") #"")
((define t (~> integer? real? boolean? (bitvector 4))) ((define t (~> integer? real? boolean? (bitvector 4)))
@ -87,7 +93,7 @@
0 0
() ()
() ()
(c values c (0 (u . "integer?~>real?~>boolean?~>(bitvector 4)\n")))) (c values c (0 (u . "integer?~>real?~>boolean?~>(bitvector 4)"))))
#"" #""
#"") #"")
((~> t integer?) ((~> t integer?)
@ -112,7 +118,7 @@
() ()
(q (q
exn exn
"function: expected a primitive solvable type\n range: (union [b boolean?] [(! b) real?])")) "function: expected a primitive solvable type\n range: {[b boolean?] [(! b) real?]}"))
#"" #""
#"") #"")
((~> real?) ((~> real?)
@ -124,7 +130,7 @@
() ()
(q (q
exn exn
"~>: arity mismatch;\n the expected number of arguments does not match the given number\n expected: at least 2\n given: 1")) "~>: arity mismatch;\n the expected number of arguments does not match the given number\n expected: at least 2\n given: 1\n arguments...:\n real?"))
#"" #""
#"") #"")
((define t0? (~> integer? real? boolean? (bitvector 4))) ((define t0? (~> integer? real? boolean? (bitvector 4)))
@ -141,7 +147,7 @@
((function? (if b t0? t1?)) ((3) 0 () 0 () () (q values #f)) #"" #"") ((function? (if b t0? t1?)) ((3) 0 () 0 () () (q values #f)) #"" #"")
((function? integer?) ((3) 0 () 0 () () (q values #f)) #"" #"") ((function? integer?) ((3) 0 () 0 () () (q values #f)) #"" #"")
((function? 3) ((3) 0 () 0 () () (q values #f)) #"" #"") ((function? 3) ((3) 0 () 0 () () (q values #f)) #"" #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic f (~> boolean? boolean?)) ((define-symbolic f (~> boolean? boolean?))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
@ -158,7 +164,7 @@
0 0
() ()
() ()
(c values c (0 (u . "b\n")))) (c values c (0 (u . "b"))))
#"" #""
#"") #"")
((define sol (solve (begin (assert (not (f #t))) (assert (f #f))))) ((define sol (solve (begin (assert (not (f #t))) (assert (f #f)))))
@ -175,7 +181,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(fv boolean?~>boolean?)\n")))) (c values c (0 (u . "(fv boolean?~>boolean?)"))))
#"" #""
#"") #"")
((fv? g) ((3) 0 () 0 () () (q values #t)) #"" #"") ((fv? g) ((3) 0 () 0 () () (q values #t)) #"" #"")
@ -188,6 +194,6 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")

View File

@ -1,13 +1,15 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label @(require (for-label
rosette/base/form/define rosette/query/query rosette/solver/solution rosette/base/form/define rosette/query/form rosette/query/eval rosette/solver/solution
rosette/base/core/term (only-in rosette/query/finitize current-bitwidth) rosette/base/core/term (only-in rosette/query/finitize current-bitwidth)
(only-in rosette/base/core/union union?) (only-in rosette/base/core/union union?)
(only-in rosette/base/core/function ~> function? fv?) (only-in rosette/base/core/function ~> function? fv?)
(only-in rosette/base/base bv bitvector assert vc clear-vc!)) (only-in rosette/base/base bv bitvector)
(only-in rosette/base/core/safe assert)
(only-in rosette/base/core/bool asserts))
(for-label racket) racket/runtime-path (for-label racket) racket/runtime-path
scribble/core scribble/html-properties scribble/examples racket/sandbox scribble/core scribble/html-properties scribble/eval racket/sandbox
"../util/lifted.rkt") "../util/lifted.rkt")
@(define-runtime-path root ".") @(define-runtime-path root ".")
@ -17,7 +19,7 @@
@declare-exporting[rosette/base/base #:use-sources (rosette/base/core/function @declare-exporting[rosette/base/base #:use-sources (rosette/base/core/function
rosette/query/finitize rosette/query/finitize
rosette/base/base)] rosette/base/core/safe)]
In Rosette, functions are special kinds of @seclink["sec:proc"]{procedures} that are pure In Rosette, functions are special kinds of @seclink["sec:proc"]{procedures} that are pure
(have no side effects) and total (defined on every input value). (have no side effects) and total (defined on every input value).
@ -33,60 +35,54 @@ as the result of a @seclink["sec:queries"]{solver-aided query}.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(current-bitwidth #f) (current-bitwidth #f)
(code:comment "An uninterpreted function from integers to booleans:") (code:comment "an uninterpreted function from integers to booleans:")
(define-symbolic f (~> integer? boolean?)) (define-symbolic f (~> integer? boolean?))
(code:line (f 1) (code:comment "No built-in interpretation for 1.")) (code:line (f 1) (code:comment "no built-in interpretation for 1"))
(define-symbolic x real?) (define-symbolic x real?)
(code:line (f x) (code:comment "This typechecks when x is an integer,")) (code:line (f x) (code:comment "this typechecks when x is an integer"))
(code:line (vc) (code:comment "so Rosette emits the corresponding assertion.")) (code:line (asserts) (code:comment "so Rosette emits the corresponding assertion"))
(define sol (solve (assert (not (equal? (f x) (f 1)))))) (define sol (solve (assert (not (equal? (f x) (f 1))))))
(code:line (define g (evaluate f sol)) (code:comment "An interpretation of f.")) (code:line (define g (evaluate f sol)) (code:comment "an interpretation of f"))
g g
(evaluate x sol) (evaluate x sol)
(code:line (fv? f) (code:comment "f is a function value,")) (code:line (fv? f) (code:comment "f is a function value"))
(code:line (fv? g) (code:comment "and so is g.")) (code:line (fv? g) (code:comment "and so is g"))
(code:line (g 2) (code:comment "We can apply g to concrete values")) (code:line (g 2) (code:comment "we can apply g to concrete values"))
(code:line (g x) (code:comment "and to symbolic values."))] (code:line (g x) (code:comment "and to symbolic values"))]
@defproc[(~> [d (and/c solvable? (not/c function?))] ...+ @defproc[(~> [d (and/c solvable? (not/c function?))] ...+
[r (and/c solvable? (not/c function?))]) function?]{ [r (and/c solvable? (not/c function?))]) function?]{
Returns a type predicate for recognizing functions that take as input Returns a type predicate for recognizing functions that take as input
values of types @racket[d...+] and produce values of type @racket[r]. values of types @racket[d...+] and produce values of type @racket[r].
The domain and range arguments must be concrete @racket[solvable?] types that are The domain and range arguments must be concrete @racket[solvable?] types that are
not themselves functions. Note that @racket[~>] expects at least one domain not themselves functions. Note that @racket[~>] expects at least one domain
type to be given, disallowing zero-argument functions. type to be given, disallowing zero-argument functions.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define t (~> integer? real? boolean? (bitvector 4))) (define t (~> integer? real? boolean? (bitvector 4)))
t t
(eval:error (~> t integer?)) (~> t integer?)
(define-symbolic b boolean?) (define-symbolic b boolean?)
(eval:error (~> integer? (if b boolean? real?))) (~> integer? (if b boolean? real?))
(eval:error (~> real?))] (~> real?)]
} }
@defproc[(function? [v any/c]) boolean?]{ @defproc[(function? [v any/c]) boolean?]{
Returns true if @racket[v] is a concrete type predicate that recognizes function values. Returns true if @racket[v] is a concrete type predicate that recognizes function values.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define t0? (~> integer? real? boolean? (bitvector 4))) (define t0? (~> integer? real? boolean? (bitvector 4)))
(define t1? (~> integer? real?)) (define t1? (~> integer? real?))
(function? t0?) (function? t0?)
(function? t1?) (function? t1?)
(define-symbolic b boolean?) (define-symbolic b boolean?)
(code:line (function? (if b t0? t1?)) (code:comment "Not a concrete type.")) (code:line (function? (if b t0? t1?)) (code:comment "not a concrete type"))
(code:line (function? integer?) (code:comment "Not a function type.")) (code:line (function? integer?) (code:comment "not a function type"))
(code:line (function? 3) (code:comment "Not a type."))] (code:line (function? 3) (code:comment "not a type"))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc[(fv? [v any/c]) boolean?]{ @defproc[(fv? [v any/c]) boolean?]{
Returns true if @racket[v] is a concrete or symbolic function value. Returns true if @racket[v] is a concrete or symbolic function value.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic f (~> boolean? boolean?)) (define-symbolic f (~> boolean? boolean?))
(fv? f) (fv? f)
@ -99,9 +95,9 @@ g
(assert (not (f #t))) (assert (not (f #t)))
(assert (f #f))))) (assert (f #f)))))
(define g (evaluate f sol)) (define g (evaluate f sol))
(code:line g (code:comment "g implements logical negation.")) (code:line g (code:comment "g implements logical negation"))
(fv? g) (fv? g)
(code:comment "Verify that g implements logical negation:") (code:comment "verify that g implements logical negation:")
(verify (assert (equal? (g b) (not b))))] (verify (assert (equal? (g b) (not b))))]
} }

View File

@ -28,300 +28,6 @@
#"" #""
#"") #"")
((evaluate n sol) ((3) 0 () 0 () () (q values 1)) #"" #"") ((evaluate n sol) ((3) 0 () 0 () () (q values 1)) #"" #"")
((evaluate (list x y z) sol) ((evaluate (list x y z) sol) ((3) 0 () 0 () () (q values (4 0 0))) #"" #"")
((3) ((evaluate vs sol) ((3) 0 () 0 () () (c values c (v! 4))) #"" #"")
1 ((evaluate xs sol) ((3) 0 () 0 () () (q values (4))) #"" #"")
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'(4 0 0)\n"))))
#""
#"")
((evaluate vs sol)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'#(4)\n"))))
#""
#"")
((evaluate xs sol)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'(4)\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define xs (if b (vector 1 2) (vector 3 4 5 6)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
(xs
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(union [b #(1 2)] [(! b) #(3 4 5 6)])\n"))))
#""
#"")
((integer->bitvector (vector-length xs) (bitvector 4))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(integer->bitvector (ite b 2 4) (bitvector 4))\n"))))
#""
#"")
((vector-length-bv xs (bitvector 4))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(ite b (bv #x2 4) (bv #x4 4))\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic p (bitvector 1))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define xs (vector 1 2 3 4)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vector-ref xs (bitvector->natural p))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(ite*\n (⊢ (= 0 (bitvector->natural p)) 1)\n (⊢ (= 1 (bitvector->natural p)) 2)\n (⊢ (= 2 (bitvector->natural p)) 3)\n (⊢ (= 3 (bitvector->natural p)) 4))\n\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(vc #t (&& (<= 0 (bitvector->natural p)) (< (bitvector->natural p) 4)))\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vector-ref-bv xs p)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(ite* (⊢ (bveq (bv #b0 1) p) 1) (⊢ (bveq (bv #b1 1) p) 2))\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((define-symbolic q (bitvector 4))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((vector-ref-bv xs q)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(ite*\n (⊢ (bveq (bv #x0 4) q) 1)\n (⊢ (bveq (bv #x1 4) q) 2)\n (⊢ (bveq (bv #x2 4) q) 3)\n (⊢ (bveq (bv #x3 4) q) 4))\n\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t (bvult q (bv #x4 4)))\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic p (bitvector 1))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define xs (vector 1 2 3 4)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vector-set! xs (bitvector->natural p) 5)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
(xs
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(vector\n (ite (= 0 (bitvector->natural p)) 5 1)\n (ite (= 1 (bitvector->natural p)) 5 2)\n (ite (= 2 (bitvector->natural p)) 5 3)\n (ite (= 3 (bitvector->natural p)) 5 4))\n\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(vc #t (&& (<= 0 (bitvector->natural p)) (< (bitvector->natural p) 4)))\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define xs (vector 1 2 3 4)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vector-set!-bv xs p 5) ((3) 0 () 0 () () (c values c (void))) #"" #"")
(xs
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(vector (ite (bveq (bv #b0 1) p) 5 1) (ite (bveq (bv #b1 1) p) 5 2) 3 4)\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((define-symbolic q (bitvector 4))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define xs (vector 1 2 3 4)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vector-set!-bv xs q 5) ((3) 0 () 0 () () (c values c (void))) #"" #"")
(xs
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(vector\n (ite (bveq (bv #x0 4) q) 5 1)\n (ite (bveq (bv #x1 4) q) 5 2)\n (ite (bveq (bv #x2 4) q) 5 3)\n (ite (bveq (bv #x3 4) q) 5 4))\n\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t (bvult q (bv #x4 4)))\n"))))
#""
#"")

View File

@ -3,13 +3,9 @@
@(require (for-label @(require (for-label
rosette/base/form/define rosette/query/query rosette/base/form/define rosette/query/query
rosette/base/core/term rosette/base/core/term
rosette/solver/solution (only-in rosette/base/core/safe assert)
(only-in rosette/base/base assert define-symbolic union?
vc clear-vc! bitvector bitvector? bv?
bitvector->natural integer->bitvector
vector-length-bv vector-ref-bv vector-set!-bv)
racket) racket)
scribble/core scribble/html-properties scribble/examples racket/sandbox racket/runtime-path scribble/core scribble/html-properties scribble/eval racket/sandbox racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@ -50,8 +46,8 @@ if they have the same length and @racket[equal?] contents.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x y z n integer?) (define-symbolic x y z n integer?)
(code:line (define xs (take (list x y z) n)) (code:comment "xs is a symbolic list.")) (code:line (define xs (take (list x y z) n)) (code:comment "xs is a symbolic list"))
(code:line (define vs (list->vector xs)) (code:comment "vs is a symbolic vector.")) (code:line (define vs (list->vector xs)) (code:comment "vs is a symbolic vector"))
(define sol (define sol
(solve (solve
(begin (begin
@ -62,74 +58,8 @@ if they have the same length and @racket[equal?] contents.
(evaluate vs sol) (evaluate vs sol)
(evaluate xs sol)] (evaluate xs sol)]
@section{Lifted Operations on Vectors}
The following vector operations are lifted to work on both concrete and symbolic values: The following vector operations are lifted to work on both concrete and symbolic values:
@tabular[#:style (style #f (list (attributes '((id . "lifted")(class . "boxed"))))) @tabular[#:style (style #f (list (attributes '((id . "lifted")(class . "boxed")))))
(list (list @elem{Vector Operations} @elem{@vector-ops, @more-vector-ops}))] (list (list @elem{@vector-ops, @more-vector-ops}))]
@(rosette-eval '(clear-vc!))
@section{Additional Operations on Vectors}
Rosette provides the following procedures for operating on vectors using @seclink["sec:bitvectors"]{bitvector} indices and lengths. These procedures produce symbolic values that avoid @racketlink[bitvector->natural]{casting} their bitvector arguments to integers, leading to @seclink["sec:notes"]{more efficiently solvable queries}.
@declare-exporting[rosette/base/base #:use-sources (rosette/base/base)]
@defproc[(vector-length-bv [vec vector?] [t (or/c bitvector? union?)]) bv?]{
Equivalent to @racket[(integer->bitvector (vector-length vec) t)] but avoids the @racket[integer->bitvector] cast for better solving performance.
@examples[#:eval rosette-eval
(define-symbolic b boolean?)
(define xs (if b (vector 1 2) (vector 3 4 5 6)))
xs
(integer->bitvector (vector-length xs) (bitvector 4))
(vector-length-bv xs (bitvector 4))]
}
@(rosette-eval '(clear-vc!))
@defproc[(vector-ref-bv [vec vector?] [pos bv?]) any/c]{
Equivalent to @racket[(vector-ref vec (bitvector->natural pos))] but avoids the @racket[bitvector->natural] cast for better solving performance.
@examples[#:eval rosette-eval
(define-symbolic p (bitvector 1))
(define xs (vector 1 2 3 4))
(code:comment "Uses a cast and generates a redundant assertion on the range of p:")
(vector-ref xs (bitvector->natural p))
(vc)
(clear-vc!)
(code:comment "No cast and no redundant range assertion:")
(vector-ref-bv xs p)
(vc)
(code:comment "But the range assertion is generated when needed:")
(define-symbolic q (bitvector 4))
(vector-ref-bv xs q)
(vc)]
}
@(rosette-eval '(clear-vc!))
@defproc[(vector-set!-bv [vec vector?] [pos bv?] [val any/c]) void?]{
Equivalent to @racket[(vector-set! vec (bitvector->natural pos) val)] but avoids the @racket[bitvector->natural] cast for better solving performance.
@examples[#:eval rosette-eval
(define-symbolic p (bitvector 1))
(define xs (vector 1 2 3 4))
(code:comment "Uses a cast and generates a redundant assertion on the range of p:")
(vector-set! xs (bitvector->natural p) 5)
xs
(vc)
(clear-vc!)
(code:comment "No cast and no redundant range assertion:")
(define xs (vector 1 2 3 4))
(vector-set!-bv xs p 5)
xs
(vc)
(code:comment "But the range assertion is generated when needed:")
(define-symbolic q (bitvector 4))
(define xs (vector 1 2 3 4))
(vector-set!-bv xs q 5)
xs
(vc)]
}
@(kill-evaluator rosette-eval)

View File

@ -1,9 +1,5 @@
;; This file was created by make-log-based-eval ;; This file was created by make-log-based-eval
((require (only-in rosette/guide/scribble/util/lifted format-opaque)) ((define-symbolic xs integer? (4))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define-symbolic xs integer? #:length 4)
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
@ -17,7 +13,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model)\n")))) (c values c (0 (u . "(model)"))))
#"" #""
#"") #"")
((define-symbolic opt boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic opt boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
@ -34,41 +30,14 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((require rackunit) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((verify
((define (post xs) (assert (= (sum xs) (sum (filter-not zero? xs))))) #:assume
((3) 0 () 0 () () (c values c (void))) (assert (positive? (sum xs)))
#"" #:guarantee
#"") (assert (ormap positive? xs)))
((define (query xs) (verify (post xs)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define example-tests
(test-suite
"An example suite for a sum query."
#:before
clear-vc!
#:after
clear-vc!
(test-case
"Test sum with concrete values."
(check = (sum '()) 0)
(check = (sum '(-1)) -1)
(check = (sum '(-2 2)) 0)
(check = (sum '(-1 0 3)) 2))
(test-case
"Test query post for exceptions."
(before (clear-vc!) (check-not-exn (thunk (post xs)))))
(test-case
"Test query outcome."
(before (clear-vc!) (check-pred unsat? (query xs))))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((format-opaque "~a" (run-test example-tests))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -77,7 +46,51 @@
0 0
() ()
() ()
(c values c (0 (u . "(#<test-error> #<test-failure> #<test-failure>)\n")))) (c values c (0 (u . "(unsat)"))))
#""
#"")
((require rackunit) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define (assumed xs) (assert (positive? (sum xs))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define (guaranteed xs) (assert (ormap positive? xs)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define (query xs) (verify #:assume (assumed xs) #:guarantee (guaranteed xs)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define example-tests
(test-suite
"An example suite for a sum query."
#:before
clear-asserts!
#:after
clear-asserts!
(test-case
"Test sum with concrete values."
(check = (sum '()) 0)
(check = (sum '(-1)) -1)
(check = (sum '(-2 2)) 0)
(check = (sum '(-1 0 3)) 2))
(test-case
"Test query parts for exceptions."
(check-not-exn (thunk (assumed xs)))
(check-not-exn (thunk (guaranteed xs))))
(test-case "Test query outcome." (check-pred unsat? (query xs)))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
('(|#<test-error>| |#<test-failure>| |#<test-success>|)
((3)
0
()
0
()
()
(q values (|#<test-error>| |#<test-failure>| |#<test-success>|)))
#"" #""
#"") #"")
((define (sum xs) ((define (sum xs)
@ -89,95 +102,25 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((assume (positive? (sum xs))) ((3) 0 () 0 () () (c values c (void))) #"" #"") ('(|#<test-success>| |#<test-success>| |#<test-success>|)
((verify (assert (ormap positive? xs)))
((3) ((3)
1 0
(((lib "rosette/guide/scribble/util/lifted.rkt") ()
.
deserialize-info:opaque-v0))
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (q values (|#<test-success>| |#<test-success>| |#<test-success>|)))
#""
#"")
((define (pre xs) (assume (positive? (sum xs))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define (post xs) (assert (ormap positive? xs)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define (query xs) (pre xs) (verify (post xs)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define example-tests
(test-suite
"An example suite for a sum query."
#:before
clear-vc!
#:after
clear-vc!
(test-case
"Test sum with concrete values."
(check = (sum '()) 0)
(check = (sum '(-1)) -1)
(check = (sum '(-2 2)) 0)
(check = (sum '(-1 0 3)) 2))
(test-case
"Test query post for exceptions."
(before (clear-vc!) (check-not-exn (thunk (pre xs)))))
(test-case
"Test query post for exceptions."
(before (clear-vc!) (check-not-exn (thunk (post xs)))))
(test-case
"Test query outcome."
(before (clear-vc!) (check-pred unsat? (query xs))))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((format-opaque "~a" (run-test example-tests))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(#<test-success> #<test-success> #<test-success> #<test-success>)\n"))))
#"" #""
#"") #"")
((test-case "Test sum for any failures." (check-pred unsat? (verify (sum xs)))) ((test-case "Test sum for any failures." (check-pred unsat? (verify (sum xs))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"--------------------\nTest sum for any failures.\nFAILURE\nname: check-pred\nlocation: eval:20:0\nparams:\n '(#<procedure:unsat?> (model\n [xs$0 0]\n [xs$1 0]\n [xs$2 0]\n [xs$3 0]))\n--------------------\n")
((verify (begin (assume (positive? (sum xs))) (assert (ormap positive? xs))))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(model\n [xs$0 1]\n [xs$1 1]\n [xs$2 1]\n [xs$3 1])\n"))))
#""
#"") #"")
((assume (positive? (sum xs))) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((verify
((verify (assert (ormap positive? xs))) #:assume
(assert (positive? (sum xs)))
#:guarantee
(assert (ormap positive? xs)))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -186,7 +129,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((define (select xs n) ((define (select xs n)
@ -205,12 +148,11 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((define-symbolic n k integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((verify
((assume (and (<= 0 n (sub1 (length xs))) (= k (select xs n)))) #:assume
((3) 0 () 0 () () (c values c (void))) (assert (and (<= 0 n (sub1 (length xs))) (= k (select xs n))))
#"" #:guarantee
#"") (assert (= k (list-ref (sort xs <) n))))
((verify (assert (= k (list-ref (sort xs <) n))))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -219,6 +161,6 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")

View File

@ -1,14 +1,18 @@
#lang scribble/manual #lang scribble/manual
@(require scribble/core scribble/html-properties @(require scribble/core scribble/html-properties
scribble/bnf scribble/example scribble/bnf scribble/example scriblib/footnote
(for-label (except-in racket list-set) errortrace (for-label (except-in racket list-set) errortrace
rosette/base/core/term rosette/base/core/term
rosette/base/form/define rosette/base/form/define
rosette/query/form rosette/query/form
(only-in rosette/query/debug debug)
rosette/base/core/union rosette/base/core/union
(only-in rosette unsat model evaluate sat? unsat? clear-vc! current-bitwidth) (only-in rosette unsat model evaluate sat? unsat? clear-asserts!)
(only-in rosette/base/base assume assert vc clear-vc!) (only-in rosette/query/finitize current-bitwidth)
(only-in rosette/base/base bitvector)
(only-in rosette/base/core/safe assert)
(only-in rosette/base/core/forall for/all)
rackunit) rackunit)
racket/runtime-path racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@ -19,8 +23,6 @@
@(define-runtime-path interface.png "interface.png") @(define-runtime-path interface.png "interface.png")
@(define-runtime-path quickselect.png "quickselect.png") @(define-runtime-path quickselect.png "quickselect.png")
@(rosette-eval '(require (only-in rosette/guide/scribble/util/lifted format-opaque)))
@title[#:tag "ch:error-tracing"]{Debugging} @title[#:tag "ch:error-tracing"]{Debugging}
Bugs in Rosette programs often manifest as runtime Bugs in Rosette programs often manifest as runtime
@ -28,14 +30,15 @@ exceptions. For example, calling a procedure with too few
arguments will cause a runtime exception in Rosette, just as arguments will cause a runtime exception in Rosette, just as
it would in Racket. But unlike Racket, Rosette treats it would in Racket. But unlike Racket, Rosette treats
exceptions as assertion failures: it catches the exception, exceptions as assertion failures: it catches the exception,
updates the @tech{verification condition} to reflect the adds the (path) condition under which the exception is
failure, and proceeds with symbolic evaluation. This thrown to the @tech{assertion store}, and proceeds with
treatment of exceptions ensures that the program's symbolic evaluation. This treatment of exceptions
@seclink["ch:syntactic-forms:rosette"]{ solver-aided ensures that the program's
queries} correctly return a @racket[sat?] or @racket[unsat?] @seclink["ch:syntactic-forms:rosette"]{solver-aided queries}
correctly return a @racket[sat?] or @racket[unsat?]
solution, but it can also make solver-aided code tricky to solution, but it can also make solver-aided code tricky to
debug. This chapter describes common problems that are due debug. This chapter describes common problems that are due to
to intercepted exceptions, how to test for them, and how to intercepted exceptions, how to test for them, and how to
find them with the @code{symtrace} tool for error tracing. find them with the @code{symtrace} tool for error tracing.
@ -59,8 +62,8 @@ As an example, consider the following verification query,
which tries to prove that the sum of a list of integers which tries to prove that the sum of a list of integers
remains the same when all zeros are removed from the list: remains the same when all zeros are removed from the list:
@examples[#:eval rosette-eval #:label #f @examples[#:label #f #:eval rosette-eval
(define-symbolic xs integer? #:length 4) (define-symbolic xs integer? [4])
(code:line (define (sum xs) (foldl + xs)) (code:comment "bug: missing 0 after +")) (code:line (define (sum xs) (foldl + xs)) (code:comment "bug: missing 0 after +"))
(verify (assert (= (sum xs) (sum (filter-not zero? xs))))) (verify (assert (= (sum xs) (sum (filter-not zero? xs)))))
] ]
@ -75,7 +78,7 @@ arguments. This omission will cause every call to
@racket[sum] to raise an exception, including @racket[sum] to raise an exception, including
@racket[(sum xs)] in the body of our query. Rosette @racket[(sum xs)] in the body of our query. Rosette
intercepts this exception and adds @racket[#f] to the intercepts this exception and adds @racket[#f] to the
query's @tech{verification condition} because the exception query's @tech{assertion store} because the exception
happens unconditionally (on all paths). happens unconditionally (on all paths).
This false assertion then causes the query to return a This false assertion then causes the query to return a
trivial counterexample, @racket[(model)], indicating that trivial counterexample, @racket[(model)], indicating that
@ -85,7 +88,7 @@ to an error.
As another example, consider the following synthesis query As another example, consider the following synthesis query
involving @racket[sum]: involving @racket[sum]:
@examples[#:eval rosette-eval #:label #f @examples[#:label #f #:eval rosette-eval
(define-symbolic opt boolean?) (define-symbolic opt boolean?)
(synthesize (synthesize
#:forall xs #:forall xs
@ -96,33 +99,62 @@ Here, the expected result is a model that binds @racket[opt]
to the value @racket[#t], and this is the outcome we see to the value @racket[#t], and this is the outcome we see
once we fix the bug in @racket[sum]. The bug, however, once we fix the bug in @racket[sum]. The bug, however,
causes the @racket[#:guarantee] expression to fail causes the @racket[#:guarantee] expression to fail
unconditionally. Rosette then intercepts the exception and unconditionally. The query then intercepts the exception and
returns @racket[(unsat)] to indicate that no choice of returns @racket[(unsat)] to indicate that no choice of
@racket[opt] can satisfy the specification. @racket[opt] can satisfy the specification.
Bugs of this kind can be found through testing. A good test In addition to unexpected results, exceptions intercepted in
suite should check that queries produce expected results on queries can also lead to more subtle errors, with no obvious
small inputs, and that query parts do not throw exceptions. manifestation. For example, consider the following query
When possible, it is also good practice to test all that verifies another simple property of @racket[sum]---if
solver-aided code against concrete inputs and outputs. Here @racket[sum] returns a positive integer, then at least one
is an example test suite for our first query that includes of its arguments must have been positive.
all of these checks:
@examples[#:eval rosette-eval #:label #f @examples[#:label #f #:eval rosette-eval
(verify
#:assume (assert (positive? (sum xs)))
#:guarantee (assert (ormap positive? xs)))
]
This query returns @racket[(unsat)], as expected, despite the
bug in @racket[sum]. To see why, recall that the verifier
returns a counterexample when it can find an input that
satisfies the @racket[#:assume]d assertions and violates the
@racket[#:guarantee]d assertions. But there is no input that
satisfies the assumptions in our query: as before, the call
to @racket[(sum xs)] throws an unconditional exception that
is intercepted and treated as @racket[(assert #f)]. This
false assumption leads to a vacuous proof of correctness,
and the verifier returns @racket[(unsat)].
The bugs due to query exceptions can generally be found
through testing. To guard against such bugs, a test suite
should check that queries produce expected results on small
inputs, and it should also check for unexpected exceptions
in all parts of the query's body. When possible, it is also
good practice to test all solver-aided code against concrete
inputs and outputs. Here is an example test suite for our
last query that includes all of these checks:
@examples[#:label #f #:eval rosette-eval
(eval:no-prompt (eval:no-prompt
(require rackunit) (require rackunit)
(define (post xs) (define (assumed xs)
(assert (= (sum xs) (sum (filter-not zero? xs))))) (assert (positive? (sum xs))))
(define (guaranteed xs)
(assert (ormap positive? xs)))
(define (query xs) (define (query xs)
(verify (post xs))) (verify #:assume (assumed xs)
#:guarantee (guaranteed xs)))
(define example-tests (define example-tests
(test-suite (test-suite
"An example suite for a sum query." "An example suite for a sum query."
#:before clear-vc! #:before clear-asserts!
#:after clear-vc! #:after clear-asserts!
(test-case (test-case
"Test sum with concrete values." "Test sum with concrete values."
@ -132,23 +164,19 @@ all of these checks:
(check = (sum '(-1 0 3)) 2)) (check = (sum '(-1 0 3)) 2))
(test-case (test-case
"Test query post for exceptions." "Test query parts for exceptions."
(before (check-not-exn (thunk (assumed xs)))
(clear-vc!) (check-not-exn (thunk (guaranteed xs))))
(check-not-exn (thunk (post xs)))))
(test-case (test-case
"Test query outcome." "Test query outcome."
(before (check-pred unsat? (query xs))
(clear-vc!) ))))
(check-pred unsat? (query xs)))))))
(eval:alts (eval:alts (run-test example-tests) '(\#<test-error> \#<test-failure> \#<test-success>))
(run-test example-tests)
(format-opaque "~a" (run-test example-tests)))
] ]
All tests in this suite fail when invoked on the All but the last test in this suite fail when invoked on the
buggy @racket[sum], and they all pass once the bug is fixed. buggy @racket[sum], and they all pass once the bug is fixed.
@ -164,115 +192,45 @@ trickier, in both concrete and solver-aided code, as our next
example shows. example shows.
Consider the following buggy version of @racket[sum]: Consider the following buggy version of @racket[sum]:
@examples[#:eval rosette-eval #:label #f @examples[#:label #f #:eval rosette-eval
(define (sum xs) (define (sum xs)
(cond (cond
[(null? xs) 0] [(null? xs) 0]
[(null? (cdr xs)) (car xs)] [(null? (cdr xs)) (car xs)]
[(andmap (curry = (car xs)) (cdr xs)) [(andmap (curry = (car xs)) (cdr xs))
(* (length xs) (cdr xs))] (code:comment "Bug: cdr should be car.") (* (length xs) (cdr xs))] (code:comment "bug: cdr should be car")
[else (apply + xs)])) [else (apply + xs)]))
] ]
This version of @racket[sum] implements three simple This version of @racket[sum] implements three simple
optimizations. It returns 0 when given an empty list; optimizations. It returns 0 when given an empty list; @code{
@code{xs[0]} when given a list of length 1; and xs[0]} when given a list of length 1; and @code{|xs| * xs[0]}
@code{|xs| * xs[0]} when given a list of identical elements. when given a list of identical elements. This last
This last optimization is buggy (it uses @racket[cdr] when optimization is buggy (it uses @racket[cdr] when it should
it should have used @racket[car]), and any execution path have used @racket[car]), and any execution path that goes
that goes through it will end with an exception. through it will end with an exception.
Suppose that we want to verify another simple property of Our test suite from the
@racket[sum]: if it returns a positive integer, then at least @seclink["sec:errors-under-queries"]{previous section} will
one element in the argument list must have been positive.
@examples[#:eval rosette-eval #:label #f
(assume (positive? (sum xs)))
(verify
(assert (ormap positive? xs)))]
This query returns @racket[(unsat)], as expected, despite
the bug in @racket[sum]. To see why, recall that
@racket[(verify #, @var{expr})] searches for an input that
violates an assertion in @var{expr}, while satisfying all
the assumptions and assertions accumulated in the
verification condition @racket[(vc)] before the call to
@racket[verify]. So, our query is @racket[unsat?] because
@racket[(ormap positive? xs)] holds whenever
@racket[(sum xs)] successfully computes a positive value.
A basic test suite, adapted from the
@seclink["sec:errors-under-queries"]{previous section}, will
not uncover this bug. If we run the tests against the new not uncover this bug. If we run the tests against the new
@racket[sum], all the checks pass: @racket[sum], all the checks pass:
@examples[#:eval rosette-eval #:label #f @examples[#:label #f #:eval rosette-eval
(eval:no-prompt (eval:alts (run-test example-tests)
(define (pre xs) '(\#<test-success> \#<test-success> \#<test-success>))
(assume (positive? (sum xs))))
(define (post xs)
(assert (ormap positive? xs)))
(define (query xs)
(pre xs)
(verify (post xs)))
(define example-tests
(test-suite
"An example suite for a sum query."
#:before clear-vc!
#:after clear-vc!
(test-case
"Test sum with concrete values."
(check = (sum '()) 0)
(check = (sum '(-1)) -1)
(check = (sum '(-2 2)) 0)
(check = (sum '(-1 0 3)) 2))
(test-case
"Test query post for exceptions."
(before
(clear-vc!)
(check-not-exn (thunk (pre xs)))))
(test-case
"Test query post for exceptions."
(before
(clear-vc!)
(check-not-exn (thunk (post xs)))))
(test-case
"Test query outcome."
(before
(clear-vc!)
(check-pred unsat? (query xs)))))))
(eval:alts
(run-test example-tests)
(format-opaque "~a" (run-test example-tests)))
] ]
One way to detect bugs of this kind is to run a "unit One way to detect bugs of this kind is to run a "unit
verification query" for each key procedure in the program, verification query" for each key procedure in the program,
searching for assertion failures where none are expected: searching for assertion failures where none are expected:
@examples[#:eval rosette-eval #:label #f @examples[#:label #f #:eval rosette-eval
(test-case (test-case
"Test sum for any failures." "Test sum for any failures."
(check-pred unsat? (verify (sum xs)))) (check-pred unsat? (verify (sum xs))))
] ]
Another strategy is to avoid issuing any assumptions or But writing such queries is not always possible, or
assertions outside of queries:
@examples[#:eval rosette-eval #:label #f
(verify
(begin
(assume (positive? (sum xs)))
(assert (ormap positive? xs))))]
But neither strategy is always possible, or
foolproof, for large programs. So, in addition to testing, foolproof, for large programs. So, in addition to testing,
we recommend debugging all important queries with we recommend debugging all important queries with
@tech[#:key "error tracer"]{error tracing}. @tech[#:key "error tracer"]{error tracing}.
@ -298,12 +256,15 @@ The error tracer will open a web browser and stream
all exceptions that Rosette intercepted. For instance, all exceptions that Rosette intercepted. For instance,
here is the output from the error tracer when running our here is the output from the error tracer when running our
last query on the buggy @racket[sum] example from the last query on the buggy @racket[sum] example from the
@seclink["sec:errors-under-symbolic-eval"]{previous section}: @seclink["sec:errors-under-symbolic-eval"]{previous
section}:
@examples[#:eval rosette-eval #:label #f #:no-prompt
(assume (positive? (sum xs))) @examples[#:label #f #:eval rosette-eval
(verify (verify
(assert (ormap positive? xs)))] #:assume (assert (positive? (sum xs)))
#:guarantee (assert (ormap positive? xs)))
]
@(image interface.png #:scale 0.5) @(image interface.png #:scale 0.5)
@ -340,9 +301,9 @@ The @exec{raco symtrace @nonterm{prog}} command accepts the following command-li
@item{@DFlag{racket} --- instrument code in any language, not @item{@DFlag{racket} --- instrument code in any language, not
just those derived from Rosette.} just those derived from Rosette.}
@;{@item{@DFlag{solver} --- do not show exceptions raised on @item{@DFlag{solver} --- do not show exceptions raised on
infeasible paths, using the solver to decide if paths are infeasible paths, using the solver to decide if paths are
feasible. This option can cause significant performance degradation.}} feasible. This option can cause significant performance degradation.}
@item{@DFlag{assert} --- do not show exceptions due to @item{@DFlag{assert} --- do not show exceptions due to
assertion errors, which are usually expected exceptions.} assertion errors, which are usually expected exceptions.}
@ -371,7 +332,7 @@ verifying the following buggy implementation of the
@link["https://en.wikipedia.org/wiki/Quickselect"]{ @link["https://en.wikipedia.org/wiki/Quickselect"]{
quickselect algorithm}. quickselect algorithm}.
@examples[#:eval rosette-eval #:label #f #:no-prompt @examples[#:label #f #:eval rosette-eval
(define (select xs n) (define (select xs n)
(cond (cond
[(empty? xs) (assert #f "unexpected empty list")] [(empty? xs) (assert #f "unexpected empty list")]
@ -382,39 +343,62 @@ verifying the following buggy implementation of the
(define len< (length <pivot)) (define len< (length <pivot))
(cond (cond
[(= n len<) pivot] [(= n len<) pivot]
[(< n len<) (select <pivot)] (code:comment "Bug: should be (select <pivot n).") [(< n len<) (select <pivot)] (code:comment "bug: should be (select <pivot n)")
[else (select >=pivot (- n len< 1))])])) [else (select >=pivot (- n len< 1))])]))
(define-symbolic n k integer?)
(assume
(and (<= 0 n (sub1 (length xs)))
(= k (select xs n))))
(verify (verify
(assert (= k (list-ref (sort xs <) n)))) #:assume (assert (and (<= 0 n (sub1 (length xs)))
(= k (select xs n))))
#:guarantee (assert (= k (list-ref (sort xs <) n))))
] ]
As before, the verification query succeeds despite the bug. As before, the verification query succeeds despite the bug.
But unlike before, the bug is harder to detect. So we But unlike before, the bug is much harder to detect. So we
run the error tracer on it and obtain the following output: run the error tracer on it and obtain the following output:
@(image quickselect.png #:scale 0.5) @(image quickselect.png #:scale 0.5)
The output from the error tracer includes 8 exceptions. Four The output from the error tracer includes 9 exceptions. Four
are arity mismatch exceptions that are due to the bug, and are arity mismatch exceptions that are due to the bug, and
the rest are benign assertion failures that cannot happen in the rest are benign assertion failures that cannot happen in
our example. any concrete execution.
Because benign assertion failures are so common, the error Because benign assertion failures are so common, the error
tracer provides an option to heuristically suppress them tracer provides an option to suppress them from the output
from the output via the via the @seclink["sec:symtrace:opts"]{@DFlag{assert}}
@seclink["sec:symtrace:opts"]{@DFlag{assert}} flag. With the flag. With the flag enabled, the output contains only the
flag enabled, the output contains only the four arity four arity mismatch exceptions.
mismatch exceptions.
Some assertion failures are bugs, however, so filtering with @define-footnote[pc-note make-pc-note]
@DFlag{assert} can end up hiding true positives and should
be used with this caveat in mind.
Some assertion failures are bugs, however, so aggressive
filtering with @DFlag{assert} can end up hiding true
positives. For this reason, the error tracer also includes a
more conservative filtering option, @DFlag{solver}, that
will never miss true positives, at the cost of showing more
false alarms. The @DFlag{solver} option suppresses
exceptions that are raised on paths with
@seclink["sec:state-reflection"]{infeasible path
conditions}, which involves calling the solver to check the
feasibility of the path condition for each
exception.@pc-note{Exceptions with infeasible path
conditions are guaranteed to be unreachable, so no true
positive are missed. But exceptions with feasible path
conditions may still be unreachable due to the way that
Rosette represents symbolic state, so some false positives
may be included in the output.} Solver calls are expensive,
however, so enabling this flag can cause significant
performance degradation. In our example, using the @DFlag{solver}
flag is both fast and effective at pruning false
positives: it suppresses all the assertion failures as well
as one of the arity mismatch errors.
Lastly, it is possible to use both of these filtering flags,
@DFlag{solver} and @DFlag{assert}, together. In our case,
the result is the same as using the @DFlag{solver} flag
alone. But in general, using both flags can lead to better
performance compared to using @DFlag{solver} alone, with the
caveat that the inclusion of @DFlag{assert} may filter out
true positives.
@make-pc-note[]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 195 KiB

View File

@ -1,24 +0,0 @@
#lang rosette
(define-symbolic xs integer? #:length 4)
(define-symbolic k integer?)
(define-symbolic n integer?)
(define (select xs n)
(cond
[(empty? xs) (assert #f "unexpected empty list")]
[else (define pivot (first xs))
(define non-pivot (rest xs))
(define <pivot (filter (λ (x) (< x pivot)) non-pivot))
(define >=pivot (filter (λ (x) (>= x pivot)) non-pivot))
(define len< (length <pivot))
(cond
[(= n len<) pivot]
[(< n len<) (select <pivot)]
[else (select >=pivot (- n len< 1))])]))
(assume (and (<= 0 n (sub1 (length xs)))
(= k (select xs n))))
(verify (assert (= k (list-ref (sort xs <) n))))

View File

@ -1,15 +0,0 @@
#lang rosette
(define (sum xs)
(cond
[(null? xs) 0]
[(null? (cdr xs)) (car xs)]
[(andmap (curry = (car xs)) (cdr xs))
(* (length xs) (cdr xs))]
[else (apply + xs)]))
(define-symbolic xs integer? #:length 4)
(assume (positive? (sum xs)))
(verify (assert (ormap positive? xs)))

View File

@ -1,96 +0,0 @@
#lang rosette
(define (sum0 xs) (foldl + xs))
(define (sum1 xs) (foldl + 0 xs))
(define (sum2 xs)
(cond
[(null? xs) 0]
[(null? (cdr xs)) (car xs)]
[(andmap (curry = (car xs)) (cdr xs))
(* (length xs) (cdr xs))]
[else (apply + xs)]))
(define-symbolic xs integer? #:length 4)
(require rackunit)
(define (tests0 sum)
(define (post xs)
(assert (= (sum xs) (sum (filter-not zero? xs)))))
(define (query xs)
(verify (post xs)))
(test-suite
"An example suite for a sum query."
#:before clear-vc!
#:after clear-vc!
(test-case
"Test sum with concrete values."
(check = (sum '()) 0)
(check = (sum '(-1)) -1)
(check = (sum '(-2 2)) 0)
(check = (sum '(-1 0 3)) 2))
(test-case
"Test query post for exceptions."
(before
(clear-vc!)
(check-not-exn (thunk (post xs)))))
(test-case
"Test query outcome."
(before
(clear-vc!)
(check-pred unsat? (query xs))))))
(define (tests1 sum)
(define (pre xs)
(assume (positive? (sum xs))))
(define (post xs)
(assert (ormap positive? xs)))
(define (query xs)
(pre xs)
(verify (post xs)))
(test-suite
"An example suite for a sum query."
#:before clear-vc!
#:after clear-vc!
(test-case
"Test sum with concrete values."
(check = (sum '()) 0)
(check = (sum '(-1)) -1)
(check = (sum '(-2 2)) 0)
(check = (sum '(-1 0 3)) 2))
(test-case
"Test query post for exceptions."
(before
(clear-vc!)
(check-not-exn (thunk (pre xs)))))
(test-case
"Test query post for exceptions."
(before
(clear-vc!)
(check-not-exn (thunk (post xs)))))
(test-case
"Test query outcome."
(before
(clear-vc!)
(check-pred unsat? (query xs))))))
(run-test (tests0 sum0))
(run-test (tests0 sum1))
(run-test (tests0 sum2))
(run-test (tests1 sum2))

View File

@ -1,202 +0,0 @@
#lang rosette
(require (only-in racket/sandbox with-deep-time-limit))
(require rosette/solver/smt/z3)
(require rosette/guide/scribble/util/demo)
(require rosette/lib/synthax)
(provide (all-defined-out))
(define int32? (bitvector 32))
(define (int32 i)
(bv i int32?))
(define (bvmid lo hi)
(bvsdiv (bvadd lo hi) (int32 2)))
(define (bvmid-no-overflow lo hi)
(bvadd lo (bvsdiv (bvsub hi lo) (int32 2))))
(define (check-mid impl lo hi)
(assume (bvsle (int32 0) lo))
(assume (bvsle lo hi))
(define mi (impl lo hi))
(define diff
(bvsub (bvsub hi mi)
(bvsub mi lo)))
(assert (bvsle lo mi))
(assert (bvsle mi hi))
(assert (bvsle (int32 0) diff))
(assert (bvsle diff (int32 1))))
(define-symbolic l h int32?)
(define-grammar (fast-int32 x y)
[expr (choose
x y (?? int32?)
((bop) (expr) (expr))
((uop) (expr)))]
[bop (choose bvadd bvsub bvand bvor bvxor bvshl bvlshr bvashr)]
[uop (choose bvneg bvnot)])
(define (bvmid-fast lo hi)
(fast-int32 lo hi #:depth 2))
(define (bvmid-and? lo hi)
(equal? (fast-int32 l h #:depth 1) (fast-int32 l h #:depth 1)))
(define (check-sqrt impl n)
(assume (bvsle (int32 0) n))
(define √n (impl l))
(define √n+1 (bvadd √n (int32 1)))
(assert (bvule (bvmul √n √n) n))
(assert (bvult n (bvmul √n+1 √n+1))))
(define (check-mid-slow impl lo hi)
(assume (bvsle (int32 0) lo))
(assume (bvsle lo hi))
(assert
(equal?
(bitvector->integer (impl lo hi))
(quotient (+ (bitvector->integer lo) (bitvector->integer hi)) 2))))
(define-demo check-mid-demo
(demo (check-mid bvmid (int32 0) (int32 0)))
(demo (check-mid bvmid (int32 0) (int32 1)))
(demo (check-mid bvmid (int32 0) (int32 2)))
(demo (check-mid bvmid (int32 10) (int32 10000))))
(define-demo verify-demo
(define cex (time (verify (check-mid bvmid l h))))
(demo cex)
(define cl (evaluate l cex))
(define ch (evaluate h cex))
(demo cl)
(demo ch)
(demo (bvmid cl ch))
(demo (check-mid bvmid cl ch))
(demo (verify (check-mid bvmid-no-overflow l h))))
(define-demo synthesize-demo
(define sol
(time
(synthesize
#:forall (list l h)
#:guarantee (check-mid bvmid-fast l h))))
(demo (dict-count (model sol)))
(demo sol)
(demo (print-forms sol)))
(define-demo solve-demo
(define (bvmid-fast lo hi)
(bvlshr (bvadd hi lo) (bv #x00000001 32)))
(demo
(print-forms
(time
(synthesize
#:forall (list l h)
#:guarantee
(begin
(assume (not (equal? l h)))
(assume (bvsle (int32 0) l))
(assume (bvsle l h))
(assert
(<=> (bvmid-and? l h)
(equal? (bvand l h) (bvmid-fast l h))))))))))
(define-demo slowdown-demo
(demo (time (verify (check-mid bvmid l h))))
(demo (time (verify (check-mid-slow bvmid l h))))
(demo (time (verify (check-mid bvmid-no-overflow l h))))
(demo (with-deep-time-limit 10 (verify (check-mid-slow bvmid-no-overflow l h)))))
(define-demo current-bitwidth-64-demo
(parameterize ([current-bitwidth 64])
(demo (time (verify (check-mid-slow bvmid l h))))
(demo (time (verify (check-mid-slow bvmid-no-overflow l h))))))
(define-demo current-bitwidth-32-demo
(parameterize ([current-bitwidth 32])
(demo (time (verify (check-mid-slow bvmid l h))))
(demo (time (verify (check-mid-slow bvmid-no-overflow l h))))))
(define-demo solver-options-demo
(demo (current-solver (z3 #:logic 'QF_BV)))
(demo (time (verify (check-mid bvmid l h))))
(demo (time (verify (check-mid-slow bvmid l h))))
(current-solver (z3)))
(define-demo infinite-loop-demo
(define (bvsqrt n)
(cond
[(bvult n (int32 2)) n]
[else
(define s0 (bvshl (bvsqrt (bvlshr n (int32 2))) (int32 1)))
(define s1 (bvadd s0 (int32 1)))
(if (bvugt (bvmul s1 s1) n) s0 s1)]))
(demo (bvsqrt (int32 3)))
(demo (bvsqrt (int32 4)))
(demo (bvsqrt (int32 15)))
(demo (bvsqrt (int32 16)))
(demo (with-terms
(with-deep-time-limit 10 (bvsqrt l)))))
(define-demo sound-finitization-demo
(define fuel (make-parameter 5))
(define-syntax-rule
(define-bounded (id param ...) body ...)
(define (id param ...)
(assert (> (fuel) 0) "Out of fuel") ; <--- no false negatives
(parameterize ([fuel (sub1 (fuel))])
body ...)))
(define-bounded (bvsqrt n)
(cond
[(bvult n (int32 2)) n]
[else
(define s0 (bvshl (bvsqrt (bvlshr n (int32 2))) (int32 1)))
(define s1 (bvadd s0 (int32 1)))
(if (bvugt (bvmul s1 s1) n) s0 s1)]))
(demo (time (verify (check-sqrt bvsqrt l))))
(demo (fuel 16))
(demo (time (verify (check-sqrt bvsqrt l)))))
(define-demo complete-finitization-demo
(define fuel (make-parameter 5))
(define-syntax-rule
(define-bounded (id param ...) body ...)
(define (id param ...)
(assume (> (fuel) 0) "Out of fuel") ; <--- no false positives
(parameterize ([fuel (sub1 (fuel))])
body ...)))
(define-bounded (bvsqrt n)
(cond
[(bvult n (int32 2)) n]
[else
(define s0 (bvshl (bvsqrt (bvlshr n (int32 2))) (int32 1)))
(define s1 (bvadd s0 (int32 1)))
(if (bvugt (bvmul s1 s1) n) s0 s1)]))
(demo (time (verify (check-sqrt bvsqrt l))))
(demo (fuel 16))
(demo (time (verify (check-sqrt bvsqrt l)))))
(module+ main
(check-mid-demo)
(verify-demo)
(synthesize-demo)
(solve-demo)
(slowdown-demo)
(current-bitwidth-64-demo)
(current-bitwidth-32-demo)
(solver-options-demo)
(infinite-loop-demo)
(sound-finitization-demo)
(complete-finitization-demo))

View File

@ -9,7 +9,7 @@
0 0
() ()
() ()
(c values c (0 (u . "b\n")))) (c values c (0 (u . "b"))))
#"" #""
#"") #"")
((boolean? b) ((3) 0 () 0 () () (q values #t)) #"" #"") ((boolean? b) ((3) 0 () 0 () () (q values #t)) #"" #"")
@ -23,7 +23,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(vector b 1)\n")))) (c values c (v! (0 (u . "b")) 1)))
#"" #""
#"") #"")
((not b) ((not b)
@ -35,7 +35,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(! b)\n")))) (c values c (0 (u . "(! b)"))))
#"" #""
#"") #"")
((boolean? (not b)) ((3) 0 () 0 () () (q values #t)) #"" #"") ((boolean? (not b)) ((3) 0 () 0 () () (q values #t)) #"" #"")
@ -58,7 +58,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(= y$1 y$2)\n")))) (c values c (0 (u . "(= y$0 y$1)"))))
#"" #""
#"") #"")
((define (yet-another-x) (define-symbolic x boolean?) x) ((define (yet-another-x) (define-symbolic x boolean?) x)
@ -74,16 +74,13 @@
0 0
() ()
() ()
(c values c (0 (u . "(<=> x x)\n")))) (c values c (0 (u . "(<=> x x)"))))
#"" #""
#"") #"")
((assert #t) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((assert #t) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assert #f) ((3) 0 () 0 () () (q exn "[assert] failed")) #"" #"") ((assert #f) ((3) 0 () 0 () () (q exn "assert: failed")) #"" #"")
((vc-asserts (vc)) ((3) 0 () 0 () () (q values #f)) #"" #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc-asserts (vc)) ((3) 0 () 0 () () (q values #t)) #"" #"")
((assert (not b)) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((assert (not b)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc-asserts (vc)) ((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -92,284 +89,107 @@
0 0
() ()
() ()
(c values c (0 (u . "(! b)\n")))) (c values c (c (0 (u . "(! b)")) q #f)))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assume #t) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((asserts) ((3) 0 () 0 () () (q values ())) #"" #"")
((vc-assumes (vc)) ((3) 0 () 0 () () (q values #t)) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assume #f) ((3) 0 () 0 () () (q exn "[assume] failed")) #"" #"") ((define (poly x) (+ (* x x x x) (* 6 x x x) (* 11 x x) (* 6 x)))
((vc-assumes (vc)) ((3) 0 () 0 () () (q values #f)) #"" #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic i j integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assume (> j 0)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc-assumes (vc))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(< 0 j)\n"))))
#""
#"")
((assert (< (- i j) i)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc-asserts (vc))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(|| (! (< 0 j)) (< (+ i (- j)) i))\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc (< 0 j) (|| (! (< 0 j)) (< (+ i (- j)) i)))\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define int32? (bitvector 32)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define (int32 i) (bv i int32?))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((int32? 1) ((3) 0 () 0 () () (q values #f)) #"" #"") ((define (factored x) (* x (+ x 1) (+ x 2) (+ x 2)))
((int32? (int32 1)) ((3) 0 () 0 () () (q values #t)) #"" #"")
((int32 1)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bv #x00000001 32)\n"))))
#""
#"")
((define (bvmid lo hi) (bvsdiv (bvadd lo hi) (int32 2)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((define (check-mid impl lo hi) ((define (same p f x) (assert (= (p x) (f x))))
(assume (bvsle (int32 0) lo))
(assume (bvsle lo hi))
(define mi (impl lo hi))
(define diff (bvsub (bvsub hi mi) (bvsub mi lo)))
(assert (bvsle lo mi))
(assert (bvsle mi hi))
(assert (bvsle (int32 0) diff))
(assert (bvsle diff (int32 1))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((check-mid bvmid (int32 0) (int32 0)) ((same poly factored 0) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((same poly factored -1) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((same poly factored -2) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic i integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define cex (verify (same poly factored i)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((check-mid bvmid (int32 0) (int32 1)) ((evaluate i cex) ((3) 0 () 0 () () (q values -6)) #"" #"")
((poly -6) ((3) 0 () 0 () () (q values 360)) #"" #"")
((factored -6) ((3) 0 () 0 () () (q values 480)) #"" #"")
((same poly factored -6) ((3) 0 () 0 () () (q exn "assert: failed")) #"" #"")
((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((require (only-in racket/draw read-bitmap))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((check-mid bvmid (int32 0) (int32 2)) ((require rosette/query/debug) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define (poly x) (+ (* x x x x) (* 6 x x x) (* 11 x x) (* 6 x)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((check-mid bvmid (int32 10) (int32 10000)) ((define/debug (factored x) (* x (+ x 1) (+ x 2) (+ x 2)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((define-symbolic l h int32?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define (same p f x) (assert (= (p x) (f x))))
((define cex (verify (check-mid bvmid l h)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
(cex ((define ucore (debug (integer?) (same poly factored -6)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(model\n [l (bv #x394f0402 32)]\n [h (bv #x529e7c00 32)])\n"))))
#""
#"")
((define cl (evaluate l cex)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define ch (evaluate h cex)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((list cl ch)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list (bv #x394f0402 32) (bv #x529e7c00 32))\n"))))
#""
#"")
((define il (bitvector->integer cl))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((define ih (bitvector->integer ch))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((list il ih)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'(961479682 1386118144)\n"))))
#""
#"")
((define m (bvmid cl ch)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
(m
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bv #xc5f6c001 32)\n"))))
#""
#"")
((bitvector->integer m) ((3) 0 () 0 () () (q values -973684735)) #"" #"")
((quotient (+ il ih) 2) ((3) 0 () 0 () () (q values 1173798913)) #"" #"")
((int32 (quotient (+ il ih) 2))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bv #x45f6c001 32)\n"))))
#""
#"")
((check-mid bvmid cl ch) ((3) 0 () 0 () () (q exn "[assert] failed")) #"" #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((bvadd cl ch)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bv #x8bed8002 32)\n"))))
#""
#"")
((bitvector->integer (bvadd cl ch))
((3) 0 () 0 () () (q values -1947369470))
#""
#"")
((+ il ih) ((3) 0 () 0 () () (q values 2347597826)) #"" #"")
((- (expt 2 31) 1) ((3) 0 () 0 () () (q values 2147483647)) #"" #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define (bvmid-no-overflow lo hi) (bvadd lo (bvsdiv (bvsub hi lo) (int32 2))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((verify (check-mid bvmid-no-overflow l h))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#""
#"")
((require rosette/lib/synthax) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((require rosette/lib/synthax) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-grammar ((define (poly x) (+ (* x x x x) (* 6 x x x) (* 11 x x) (* 6 x)))
(fast-int32 x y)
(expr (choose x y (?? int32?) ((bop) (expr) (expr)) ((uop) (expr))))
(bop (choose bvadd bvsub bvand bvor bvxor bvshl bvlshr bvashr))
(uop (choose bvneg bvnot)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((require (only-in rosette/guide/scribble/essentials/bvmid bvmid-fast)) ((define (factored x) (* (+ x (??)) (+ x 1) (+ x (??)) (+ x (??))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((require (only-in rosette/guide/scribble/util/demo print-forms-alt)) ((define (same p f x) (assert (= (p x) (f x))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((define sol ((define-symbolic i integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
(synthesize #:forall (list l h) #:guarantee (check-mid bvmid-fast l h))) ((define binding
(synthesize #:forall (list i) #:guarantee (same poly factored i)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
(sol ('(define (factored x) (* (+ x 0) (+ x 1) (+ x 2) (+ x 3)))
((3) ((3)
1 0
(((lib "rosette/guide/scribble/util/lifted.rkt") ()
.
deserialize-info:opaque-v0))
0 0
() ()
() ()
(c (q values (define (factored x) (* (+ x 0) (+ x 1) (+ x 2) (+ x 3)))))
values
c
(0
(u
.
"(model\n [0$choose:bvmid:37:9$expr:bvmid:37:3$fast-int32:bvmid:45:3 #f]\n [1$choose:bvmid:37:9$expr:bvmid:37:3$fast-int32:bvmid:45:3 #f]\n [2$choose:bvmid:37:9$expr:bvmid:37:3$fast-int32:bvmid:45:3 #f]\n [3$choose:bvmid:37:9$expr:bvmid:37:3$fast-int32:bvmid:45:3 #t]\n ...)\n\n"))))
#""
#"")
((print-forms-alt sol)
((3) 0 () 0 () () (c values c (void)))
#"(define (bvmid-fast lo hi) (bvlshr (bvadd hi lo) (bv #x00000001 32)))\n"
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define (bvmid-fast lo hi) (bvlshr (bvadd hi lo) (bv 1 32)))
((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((define-symbolic x y integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define sol ((define sol
(solve (solve
(begin (begin
(assume (not (equal? l h))) (assert (not (= x y)))
(assume (bvsle (int32 0) l)) (assert (< (abs x) 10))
(assume (bvsle l h)) (assert (< (abs y) 10))
(assert (equal? (bvand l h) (bvmid-fast l h)))))) (assert (not (= (poly x) 0)))
(assert (= (poly x) (poly y))))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
(sol ((evaluate x sol) ((3) 0 () 0 () () (q values 1)) #"" #"")
((evaluate y sol) ((3) 0 () 0 () () (q values -4)) #"" #"")
((evaluate (poly x) sol) ((3) 0 () 0 () () (q values 24)) #"" #"")
((evaluate (poly y) sol) ((3) 0 () 0 () () (q values 24)) #"" #"")
((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((current-bitwidth 5) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((solve (begin (assert (= x 64)) (assert (= x 0))))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -378,13 +198,10 @@
0 0
() ()
() ()
(c (c values c (0 (u . "(model\n [x 0])"))))
values
c
(0 (u . "(model\n [l (bv #x3f761e94 32)]\n [h (bv #x3f761e95 32)])\n"))))
#"" #""
#"") #"")
((evaluate (bvand l h) sol) ((verify (assert (not (and (= x 64) (= x 0)))))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -393,179 +210,39 @@
0 0
() ()
() ()
(c values c (0 (u . "(bv #x3f761e94 32)\n")))) (c values c (0 (u . "(model\n [x 0])"))))
#"" #""
#"") #"")
((evaluate (bvmid-fast l h) sol)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bv #x3f761e94 32)\n"))))
#""
#"")
((define (bvmid-and? lo hi) #f) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((void) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define (check-mid-slow impl lo hi)
(assume (bvsle (int32 0) lo))
(assume (bvsle lo hi))
(assert
(equal?
(bitvector->integer (impl lo hi))
(quotient (+ (bitvector->integer lo) (bitvector->integer hi)) 2))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((require (only-in racket error))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((time (verify (check-mid bvmid l h)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(model\n [l (bv #x394f0402 32)]\n [h (bv #x529e7c00 32)])\n"))))
#"cpu time: 0 real time: 38 gc time: 0\n"
#"")
((time (verify (check-mid-slow bvmid l h)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(model\n [l (bv #x2faef9a1 32)]\n [h (bv #x5eefb8dd 32)])\n"))))
#"cpu time: 1 real time: 172 gc time: 0\n"
#"")
((time (verify (check-mid bvmid-no-overflow l h)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#"cpu time: 0 real time: 160 gc time: 0\n"
#"")
((error 'call-with-deep-time-limit "out of time")
((3) 0 () 0 () () (q exn "call-with-deep-time-limit: out of time"))
#""
#"")
((current-bitwidth) ((3) 0 () 0 () () (q values #f)) #"" #"")
((current-bitwidth 64) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((time (verify (check-mid-slow bvmid l h)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(model\n [l (bv #x00000001 32)]\n [h (bv #x7fffffff 32)])\n"))))
#"cpu time: 0 real time: 23 gc time: 0\n"
#"")
((time (verify (check-mid-slow bvmid-no-overflow l h)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#"cpu time: 0 real time: 159 gc time: 0\n"
#"")
((current-bitwidth 32) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((time (verify (check-mid-slow bvmid l h)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#"cpu time: 0 real time: 0 gc time: 0\n"
#"")
((time (verify (check-mid-slow bvmid-no-overflow l h)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(model\n [l (bv #x71979fa3 32)]\n [h (bv #x76b91b88 32)])\n"))))
#"cpu time: 1 real time: 152 gc time: 0\n"
#"")
((current-bitwidth 512) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((time (verify (check-mid-slow bvmid l h)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(model\n [l (bv #x70000006 32)]\n [h (bv #x73fffffa 32)])\n"))))
#"cpu time: 1 real time: 385 gc time: 0\n"
#"")
((time (verify (check-mid-slow bvmid-no-overflow l h)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#"cpu time: 0 real time: 430 gc time: 0\n"
#"")
((current-bitwidth #f) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((current-bitwidth #f) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((require rosette/solver/smt/z3) ((solve (begin (assert (= x 64)) (assert (= x 0))))
((3) 0 () 0 () () (c values c (void))) ((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((current-solver (z3 #:logic 'QF_BV)) ((verify (assert (not (and (= x 64) (= x 0)))))
((3) 0 () 0 () () (c values c (void))) ((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((time (verify (check-mid bvmid l h))) ((define-symbolic x integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((letrec ((adder
(lambda (vs n)
(if (null? vs) (list) (cons (+ (car vs) n) (adder (cdr vs) n))))))
(adder '(1 2 3) x))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -577,211 +254,6 @@
(c (c
values values
c c
(0 (u . "(model\n [l (bv #x394f0402 32)]\n [h (bv #x529e7c00 32)])\n")))) (c (0 (u . "(+ 1 x)")) c (0 (u . "(+ 2 x)")) c (0 (u . "(+ 3 x)")))))
#"cpu time: 3 real time: 33 gc time: 0\n"
#"")
((time (verify (check-mid-slow bvmid l h)))
((3)
0
()
0
()
()
(q
exn
"read-solution: unrecognized solver output: (error line 68 column 19: Invalid function definition: unknown sort 'Int')"))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((current-solver (z3)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define (bvsqrt n)
(cond
((bvult n (int32 2)) n)
(else
(define s0 (bvshl (bvsqrt (bvlshr n (int32 2))) (int32 1)))
(define s1 (bvadd s0 (int32 1)))
(if (bvugt (bvmul s1 s1) n) s0 s1))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((bvsqrt (int32 3))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bv #x00000001 32)\n"))))
#""
#"")
((bvsqrt (int32 4))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bv #x00000002 32)\n"))))
#""
#"")
((bvsqrt (int32 15))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bv #x00000003 32)\n"))))
#""
#"")
((bvsqrt (int32 16))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bv #x00000004 32)\n"))))
#""
#"")
((error 'call-with-deep-time-limit "out of time")
((3) 0 () 0 () () (q exn "call-with-deep-time-limit: out of time"))
#""
#"")
((define n0 l) ((3) 0 () 0 () () (c values c (void))) #"" #"")
(n0
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "l\n"))))
#""
#"")
((define n1 (bvlshr n0 (int32 2)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
(n1
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(bvlshr l (bv #x00000002 32))\n"))))
#""
#"")
((define n2 (bvlshr n1 (int32 2)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
(n2
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(bvlshr (bvlshr l (bv #x00000002 32)) (bv #x00000002 32))\n"))))
#""
#"")
((define n3 (bvlshr n2 (int32 2)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
(n3
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(bvlshr\n (bvlshr (bvlshr l (bv #x00000002 32)) (bv #x00000002 32))\n (bv #x00000002 32))\n\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((require (only-in racket make-parameter parameterize))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define fuel (make-parameter 5))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define-syntax-rule
(define-bounded (id param ...) body ...)
(define (id param ...)
(assert (> (fuel) 0) "Out of fuel.")
(parameterize ((fuel (sub1 (fuel)))) body ...)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define-bounded
(bvsqrt n)
(cond
((bvult n (int32 2)) n)
(else
(define s0 (bvshl (bvsqrt (bvlshr n (int32 2))) (int32 1)))
(define s1 (bvadd s0 (int32 1)))
(if (bvugt (bvmul s1 s1) n) s0 s1))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define (check-sqrt impl n)
(assume (bvsle (int32 0) n))
(define √n (impl l))
(define √n+1 (bvadd √n (int32 1)))
(assert (bvule (bvmul √n √n) n))
(assert (bvult n (bvmul √n+1 √n+1))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define cex (time (verify (check-sqrt bvsqrt l))))
((3) 0 () 0 () () (c values c (void)))
#"cpu time: 4 real time: 1143 gc time: 0\n"
#"")
((bvsqrt (evaluate l cex))
((3) 0 () 0 () () (q exn "[assert] Out of fuel."))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((fuel 16) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((time (verify (check-sqrt bvsqrt l)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#"cpu time: 4 real time: 67938 gc time: 0\n"
#"")

View File

@ -1,21 +1,17 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label @(require (for-label racket)
racket (for-label
(only-in racket/sandbox with-deep-time-limit) rosette/base/form/define (only-in rosette/base/core/safe assert)
rosette/base/form/define rosette/query/query (only-in rosette asserts clear-asserts!)
rosette/query/query (only-in rosette/base/base bv?)
rosette/solver/solution (except-in rosette/query/debug assert) rosette/query/eval
(only-in rosette/base/base (only-in rosette/lib/synthax ?? print-forms) rosette/lib/render))
assert assume vc vc-asserts vc-assumes clear-vc!
bv? bitvector
bvsdiv bvadd bvsle bvsub bvand
bvor bvxor bvshl bvlshr bvashr
bvnot bvneg)
rosette/lib/synthax))
@(require racket/sandbox racket/runtime-path scribble/core scribble/racket @(require racket/sandbox racket/runtime-path scribble/core
scribble/example scribble/html-properties scriblib/footnote) scribble/eval scribble/html-properties ;scriblib/footnote
(only-in racket [unsyntax racket/unsyntax])
(only-in racket/draw read-bitmap))
@(require (only-in "../refs.scrbl" ~cite rosette:onward13 rosette:pldi14) @(require (only-in "../refs.scrbl" ~cite rosette:onward13 rosette:pldi14)
"../util/lifted.rkt") "../util/lifted.rkt")
@ -25,22 +21,37 @@
@(define (symbolic s) @racketresultfont[s]) @(define (symbolic s) @racketresultfont[s])
@(define-footnote footnote footnote-part) @(define seen '())
@(define (footnote . xs)
(define ord (add1 (length seen)))
(define mark (superscript (format "~a" ord)))
(define t (format "footnote~a" ord))
(set! seen (cons (apply elemtag t mark xs) seen))
(elemref t mark))
@(define (footnote-part)
(let ([ts (reverse seen)])
(if (null? ts)
null
(cons (para #:style (style #f (list (attributes '((class . "footnoteblock")))))
(car ts))
(map para (cdr ts))))))
@;(define-footnote footnote footnote-part)
@title[#:tag "ch:essentials"]{Rosette Essentials} @title[#:tag "ch:essentials"]{Rosette Essentials}
Rosette adds to Racket a collection of solver-aided facilities. Rosette adds to Racket a collection of solver-aided facilities.
These facilities enable programmers to conveniently access a constraint solver These facilities enable programmers to conveniently access a constraint solver
that can answer interesting questions about program behaviors. They are based on four that can answer interesting questions about program behaviors. They are based on three
key concepts: @emph{symbolic values}, @emph{assertions}, @emph{assumptions}, and @emph{queries}. key concepts: @emph{symbolic values}, @emph{assertions} and @emph{queries}.
We use assertions and assumptions to express desired program behaviors and symbolic values to We use assertions to express desired program behaviors and symbolic values to
formulate queries about these behaviors. formulate queries about these behaviors.
This chapter illustrates the basics of solver-aided programming. This chapter illustrates the basics of solver-aided programming with a
More advanced tutorials, featuring extended examples, can be found few simple examples. More advanced tutorials, featuring extended examples, can be found
in Section 2 of @~cite[rosette:onward13 rosette:pldi14].@footnote{Code examples in in Section 2 of @~cite[rosette:onward13 rosette:pldi14].@footnote{Code examples in
these references are written in earlier versions of Rosette. these references are written in Rosette 1.0.
While Rosette 4 is not backward compatible with these versions, While Rosette 2.0 is not backward compatible with Rosette 1.0,
they share the same conceptual core.} they share the same conceptual core.}
The following chapters describe the subset The following chapters describe the subset
@ -51,10 +62,10 @@ and @seclink["ch:programmer-defined-datatypes"]{programmer-defined}),
@section[#:tag "sec:symbolic-values"]{Symbolic Values} @section[#:tag "sec:symbolic-values"]{Symbolic Values}
The Rosette language includes two kinds of values: concrete and symbolic. Concrete values are plain Racket values (@racket[#t], @racket[#f], @racket[0], @racket[1], etc.), and Rosette programs that operate only on concrete values behave like Racket programs. Accessing the solver-aided features of Rosette---such as code synthesis or verification---requires the use of symbolic values. The Rosette language includes two kinds of values: concrete and symbolic. Concrete values are plain Racket values (@racket[#t], @racket[#f], @racket[0], @racket[1], etc.), and Rosette programs that operate only on concrete values behave just like Racket programs. Accessing the solver-aided features of Rosette---such as code synthesis or verification---requires the use of symbolic values.
@deftech[#:key "symbolic constant"]{Symbolic constants} are the simplest kind of symbolic value. They can be created using the @racket[define-symbolic] form: @deftech[#:key "symbolic constant"]{Symbolic constants} are the simplest kind of symbolic value. They can be created using the @racket[define-symbolic] form:
@examples[#:eval rosette-eval #:label #f @def+int[#:eval rosette-eval
(define-symbolic b boolean?) (define-symbolic b boolean?)
b] b]
This generates a fresh symbolic constant of type boolean and binds it to the variable @racket[b]. This generates a fresh symbolic constant of type boolean and binds it to the variable @racket[b].
@ -62,7 +73,7 @@ This generates a fresh symbolic constant of type boolean and binds it to the var
You can think of a symbolic constant as a placeholder for a concrete constant of the same type. As we will see shortly, the solver, once called, determines which concrete value a given symbolic constant represents: it will tell us whether the constant @symbolic{b} is @racket[#t] or @racket[#f], depending on what question we ask about the behavior of a program (or a procedure) applied to @symbolic{b}. You can think of a symbolic constant as a placeholder for a concrete constant of the same type. As we will see shortly, the solver, once called, determines which concrete value a given symbolic constant represents: it will tell us whether the constant @symbolic{b} is @racket[#t] or @racket[#f], depending on what question we ask about the behavior of a program (or a procedure) applied to @symbolic{b}.
Symbolic values, including constants, can be used just like concrete values of the same type. They can be stored in data structures or passed to procedures to obtain other values, either concrete or symbolic: Symbolic values, including constants, can be used just like concrete values of the same type. They can be stored in data structures or passed to procedures to obtain other values, either concrete or symbolic:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(boolean? b) (boolean? b)
(integer? b) (integer? b)
(vector b 1) (vector b 1)
@ -72,19 +83,17 @@ In our example, all but the fourth expression produce concrete values. The four
Rosette provides one more construct for creating symbolic constants besides @racket[define-symbolic]: Rosette provides one more construct for creating symbolic constants besides @racket[define-symbolic]:
@examples[#:eval rosette-eval #:label #f #:no-prompt @def+int[#:eval rosette-eval
(define-symbolic* n integer?)] (define-symbolic* n integer?)]
The two constructs differ in how they bind variables to constants when evaluated more than once. The two constructs differ in how they bind variables to constants when evaluated more than once.
The @racket[define-symbolic] form binds the variable to the same (unique) constant every time it is evaluated. The @racket[define-symbolic*] form, in contrast, creates a stream of (unique) constants, binding the variable to the next constant from its stream whenever the form is evaluated. The following example illustrates the difference: The @racket[define-symbolic] form binds the variable to the same (unique) constant every time it is evaluated. The @racket[define-symbolic*] form, in contrast, creates a stream of (unique) constants, binding the variable to the next constant from its stream whenever the form is evaluated. The following example illustrates the difference:
@examples[#:eval rosette-eval #:label #f @defs+int[#:eval rosette-eval
(eval:no-prompt ((define (static)
(define (static) (define-symbolic x boolean?) (code:comment "creates the same constant when evaluated")
(define-symbolic x boolean?) (code:comment "Creates the same constant when evaluated.") x)
x))
(eval:no-prompt (define (dynamic)
(define (dynamic) (define-symbolic* y integer?) (code:comment "creates a different constant when evaluated")
(define-symbolic* y integer?) (code:comment "Creates a fresh constant when evaluated.")
y)) y))
(eq? (static) (static)) (eq? (static) (static))
@ -92,444 +101,221 @@ The @racket[define-symbolic] form binds the variable to the same (unique) consta
Printed constant names, such as @symbolic{x} or @symbolic{b}, are just comments. Two constants created by evaluating two distinct @racket[define-symbolic] (or, @racket[define-symbolic*]) forms are distinct, even if they have the same printed name. They may still represent the same concrete value, but that is determined by the solver: Printed constant names, such as @symbolic{x} or @symbolic{b}, are just comments. Two constants created by evaluating two distinct @racket[define-symbolic] (or, @racket[define-symbolic*]) forms are distinct, even if they have the same printed name. They may still represent the same concrete value, but that is determined by the solver:
@examples[#:eval rosette-eval #:label #f @def+int[#:eval rosette-eval
(eval:no-prompt (define (yet-another-x)
(define (yet-another-x)
(define-symbolic x boolean?) (define-symbolic x boolean?)
x)) x)
; Produces a boolean expression whose meaning is 'true' if and only if the
; constant returned by (static) and the constant returned by (yet-another-x)
; have the same concrete interpretation.
(eq? (static) (yet-another-x))] (eq? (static) (yet-another-x))]
@section[#:tag "sec:asserts"]{Assertions}
@section[#:tag "sec:asserts"]{Assertions and Assumptions} Like many other languages, Rosette provides a construct for expressing @emph{assertions}---important properties of programs that are checked in every execution. Rosette assertions work just like Java or Racket assertions when given a concrete value: if the value is false, the execution terminates with a runtime error. Otherwise, the execution proceeds normally.
@interaction[#:eval rosette-eval
Like many languages, Rosette provides a construct for expressing @emph{assertions}---important properties of programs that are checked in every execution. Rosette assertions work just like Java or Racket assertions when given a concrete value: if the value is false, the execution terminates with a runtime exception. Otherwise, the execution proceeds normally.
@examples[#:eval rosette-eval #:label #f
(assert #t) (assert #t)
(eval:error (assert #f))] (assert #f)]
When given a symbolic boolean value, however, a Rosette assertion has no immediate effect. Instead, the value is accumulated in the current @tech{verification condition} (VC), and the assertion's effect (whether it passes or fails) is eventually determined by the solver. When given a symbolic boolean value, however, a Rosette assertion has no immediate effect. Instead, its effect (whether it passes or fails) is eventually determined by the solver.
@interaction[#:eval rosette-eval
(code:comment "add (not b) to the stack of assertions to be solved")
(assert (not b))
(code:comment "retrieve the assertion store")
(asserts)
(code:comment "clear the assertion store")
(clear-asserts!)
(asserts)]
@examples[#:eval rosette-eval #:label #f @(rosette-eval '(clear-asserts!))
(code:line (vc-asserts (vc)) (code:comment "We asserted #f above, so the current VC reflects that."))
(code:line (clear-vc!) (code:comment "Clear the current VC."))
(vc-asserts (vc))
(code:line (assert (not b)) (code:comment "Add the assertion (not b) to the VC."))
(vc-asserts (vc))
(clear-vc!)]
Assertions express properties that a program must satisfy on all @emph{legal} inputs. In Rosette, as in other solver-aided frameworks, we use @emph{assumptions} to describe which inputs are legal. If a program violates an assertion on a legal input, we blame the program. But if it violates an assertion on an illegal input, we blame the caller. In other words, a program is considered incorrect only when it violates an assertion on a legal input.
Assumptions behave analogously to assertions on both concrete and symbolic values. In the concrete case, assuming @racket[#f] aborts the execution with a runtime exception, and assuming a true value is equivalent to calling @racket[(void)]. In the symbolic case, the assumed value is accumulated in the current VC.
@examples[#:eval rosette-eval #:label #f
(assume #t)
(vc-assumes (vc))
(eval:alts
(code:line (assume #f) (code:comment "Assuming #f aborts the execution with an exception."))
(eval:error (assume #f)))
(vc-assumes (vc))
(clear-vc!)
(define-symbolic i j integer?)
(code:line (assume (> j 0)) (code:comment "Add the assumption (> j 0) to the VC."))
(vc-assumes (vc))
(assert (< (- i j) i))
(code:line (vc-asserts (vc)) (code:comment "The assertions must hold when the assumptions hold."))
(code:line (vc) (code:comment "VC tracks the assumptions and the assertions."))]
@(rosette-eval '(clear-vc!))
@section[#:tag "sec:queries"]{Solver-Aided Queries} @section[#:tag "sec:queries"]{Solver-Aided Queries}
The solver reasons about assumed and asserted properties only when we ask a question about them---for example, ``Does my program have an execution that violates an assertion while satisfying all the assumptions?'' We pose such @emph{solver-aided queries} with the help of constructs explained in the remainder of this chapter. The solver reasons about asserted properties only when we ask a question about them---for example, "Does my program have an execution that violates an assertion?" We pose such @emph{solver-aided queries} with the help of constructs explained in the remainder of this chapter.
We will illustrate the queries on the following toy example. Suppose that we want to implement a We will illustrate the queries on the following toy example, where the @racket[factored] polynomial is intended to behave just like @racket[poly] on all inputs:
procedure @racket[bvmid] that takes as input two non-negative 32-bit integers, @racket[lo] ≤ @racket[hi], @defs+int[#:eval rosette-eval
and returns the midpoint of the interval [@racket[lo], @racket[hi]]. In C or Java, we would declare ((define (poly x)
the inputs and output of @racket[bvmid] to be of type ``int''. In Rosette, we model finite precision (+ (* x x x x) (* 6 x x x) (* 11 x x) (* 6 x)))
(i.e., machine) integers as @seclink["sec:bitvectors"]{bitvectors} of length 32.
@examples[#:eval rosette-eval #:label #f #:no-prompt (define (factored x)
(code:comment "int32? is a shorthand for the type (bitvector 32).") (* x (+ x 1) (+ x 2) (+ x 2)))
(define int32? (bitvector 32))]
@examples[#:eval rosette-eval #:label #f #:no-prompt
(code:comment "int32 takes as input an integer literal and returns")
(code:comment "the corresponding 32-bit bitvector value.")
(define (int32 i)
(bv i int32?))]
@examples[#:eval rosette-eval #:label #f
(code:line (int32? 1) (code:comment "1 is not a 32-bit integer"))
(code:line (int32? (int32 1)) (code:comment "but (int32 1) is."))
(int32 1)]
Bitvectors support the usual operations on machine integers, and we can use them to implement @racket[bvmid] as follows: (define (same p f x)
(assert (= (p x) (f x)))))
@examples[#:eval rosette-eval #:label #f #:no-prompt (code:comment "check zeros; all seems well ...")
(code:comment "Returns the midpoint of the interval [lo, hi].") (same poly factored 0)
(define (bvmid lo hi) (code:comment "(lo + hi) / 2") (same poly factored -1)
(bvsdiv (bvadd lo hi) (int32 2)))] (same poly factored -2)]
As the above implementation suggests, we intend the midpoint to be the mathematical
integer @tt{mi = (lo + hi) / 2}, where @tt{/} stands for integer division. Assuming
that @tt{0 ≤ lo ≤ hi}, the midpoint @tt{mi} is fully characterized by two properties:
(1) @tt{lo ≤ mi ≤ hi} and (2) @tt{0 ≤ (hi - mi) - (mi - lo) ≤ 1}. We can use these
properties to define a generic correctness specification for any implementation of
@racket[bvmid] as follows:
@examples[#:eval rosette-eval #:label #f #:no-prompt
(code:line
(define (check-mid impl lo hi) (code:comment "Assuming that")
(assume (bvsle (int32 0) lo)) (code:comment "0 ≤ lo and")
(assume (bvsle lo hi)) (code:comment "lo ≤ hi,")
(define mi (impl lo hi)) (code:comment "and letting mi = impl(lo, hi) and")
(define diff (code:comment "diff = (hi - mi) - (mi - lo),")
(bvsub (bvsub hi mi)
(bvsub mi lo))) (code:comment "we require that")
(assert (bvsle lo mi)) (code:comment "lo ≤ mi,")
(assert (bvsle mi hi)) (code:comment "mi ≤ hi,")
(assert (bvsle (int32 0) diff)) (code:comment "0 ≤ diff, and")
(assert (bvsle diff (int32 1)))) (code:comment "diff ≤ 1."))]
This is not the only way to specify the behavior of @racket[bvmid], and we will see an
alternative specification later on. In general, there are many ways to describe what it
means for a program to be correct, and often, these descriptions are partial:
they constrain some aspects of the implementation (e.g., the output is positive)
without fully defining its behavior. In our example, @racket[check-mid] is a
@emph{full functional correctness specification} in that it admits exactly one output value for @racket[(impl lo hi)], namely, @tt{(lo + hi) / 2}.
Testing @racket[bvmid] against its specification on a few concrete legal inputs, we find
that it triggers no assertion failures, as expected:
@examples[#:eval rosette-eval #:label #f
(check-mid bvmid (int32 0) (int32 0))
(check-mid bvmid (int32 0) (int32 1))
(check-mid bvmid (int32 0) (int32 2))
(check-mid bvmid (int32 10) (int32 10000)) ]
But does it work correctly on @emph{all} legal inputs? The answer, as we will see below, is ``no''.
In fact, @racket[bvmid] reproduces @hyperlink["https://en.wikipedia.org/wiki/Binary_search_algorithm#Implementation_issues"]{a famous bug}
that lurked for years in widely used C and Java implementations of binary search.
@subsection[#:tag "sec:verify"]{Verification} @subsection[#:tag "sec:verify"]{Verification}
How can we check if @racket[bvmid] satisfies its specification on all legal inputs? One approach is to enumerate all pairs of 32-bit integers with @racket[0 ≤ lo ≤ hi] and apply @racket[(check-mid bvmid hi lo)] to each. This approach is sound (it is guaranteed to find a bug if one exists), but a quick calculation shows that it is impractical even for our toy example: @racket[bvmid] has roughly 2.3 × 10@superscript{18} legal inputs. A better approach is to delegate such checks to a constraint solver, which can search large input spaces much more effectively than naive enumeration. In Rosette, this is done with the help of the @racket[verify] query: To verify that @racket[poly] and @racket[factored] behave identically, we could simply enumerate all k-bit integers and apply the @racket[same] check to each. This naive approach to verification would, of course, be very slow for a large k---and impossible for infinite precision integers. A better approach is to delegate such checks to a constraint solver, which can search large input spaces more effectively. In Rosette, this is done with the help of the @racket[verify] query:
@interaction[#:eval rosette-eval
(define-symbolic i integer?)
(define cex (verify (same poly factored i)))]
@examples[#:eval rosette-eval #:label #f The @racket[(verify #, @var[expr])] form queries the solver for a @deftech{binding} from symbolic constants to concrete values that causes the evaluation of @var[expr] to fail when the bound symbolic constants are replaced with the corresponding concrete values. If such a binding exists, as it does in our case, it is called a @emph{counterexample}.
(eval:no-prompt (define-symbolic l h int32?))
(define cex (verify (check-mid bvmid l h)))
cex]
The @racket[(verify #, @var[expr])] form queries the solver for a @deftech{binding} from symbolic constants to concrete values that causes the evaluation of @var[expr] to violate an assertion, while satisfying all the assumptions, when the bound symbolic constants are replaced with the corresponding concrete values. If such a binding exists, as it does in our case, it is called a @emph{counterexample}.
Bindings are first-class values in Rosette, and they can be freely manipulated by programs. We can also interpret any Rosette value with respect to a binding using the built-in @racket[evaluate] procedure: Bindings are first-class values in Rosette, and they can be freely manipulated by programs. We can also interpret any Rosette value with respect to a binding using the built-in @racket[evaluate] procedure:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(define cl (evaluate l cex)) (evaluate i cex)
(define ch (evaluate h cex)) (poly -6)
(list cl ch) (factored -6)
(code:comment "We can convert these values to integer? constants for debugging:") (same poly factored -6)]
(define il (bitvector->integer cl)) In our example, evaluating @racket[i] with respect to @racket[cex] reveals that @racket[poly] and @racket[factored] produce different results on the input -6 thus causing the assertion in the @racket[same] procedure to fail.
(define ih (bitvector->integer ch))
(list il ih)
(code:comment "Here is the computed midpoint:")
(define m (bvmid cl ch))
m
(bitvector->integer m)
(code:comment "This is clearly wrong. We expect (il + ih) / 2 instead:")
(quotient (+ il ih) 2)
(code:comment "Expressed as a 32-bit integer, the correct answer is:")
(int32 (quotient (+ il ih) 2))
(code:comment "So, check-mid fails on (bvmid cl ch):")
(eval:error (check-mid bvmid cl ch))]
@(rosette-eval '(clear-vc!))
In our example, evaluating @racket[l] and @racket[h] with respect to @racket[cex] reveals that @racket[bvmid] fails to return the correct midpoint value, thus causing the first assertion in the @racket[check-mid] procedure to fail. The bug is due to overflow: @(rosette-eval '(clear-asserts!))
the expression @racket[(bvadd lo hi)] in @racket[bvmid] produces a negative value in @(rosette-eval '(require (only-in racket/draw read-bitmap)))
the 32-bit representation when the sum of
@racket[lo] and @racket[hi] exceeds 2@\superscript{31}-1.
@examples[#:eval rosette-eval #:label #f @subsection[#:tag "sec:debug"]{Debugging}
(bvadd cl ch)
(bitvector->integer (bvadd cl ch))
(+ il ih)
(- (expt 2 31) 1)]
@(rosette-eval '(clear-vc!)) Now that we have an input on which @racket[factored] differs from @racket[poly], the next step is to debug it, by figuring out which of its subexpressions are responsible for the fault. Rosette provides a query for this as well. To access it, we import the debugging facilities, mark @racket[factored] as a candidate for debugging, and issue a @racket[debug] query.
A common @hyperlink["https://en.wikipedia.org/wiki/Binary_search_algorithm#Implementation_issues"]{solution} First, save these definitions to a file:
to this problem is to calculate the midpoint as @tt{lo + ((hi - lo) / 2)}. It is easy to see that all intermediate values in this calculation are at most @racket[hi] when @racket[lo] and @racket[hi] are both non-negative, so no overflow can happen. We can also verify this with Rosette: @racketblock[
#, @elem{#}lang rosette
@examples[#:eval rosette-eval #:label #f (require rosette/query/debug rosette/lib/render)
(eval:no-prompt
(define (bvmid-no-overflow lo hi)
(bvadd lo (bvsdiv (bvsub hi lo) (int32 2)))))
(verify (check-mid bvmid-no-overflow l h))] (define (poly x)
(+ (* x x x x) (* 6 x x x) (* 11 x x) (* 6 x)))
(define/debug (factored x) (code:comment "define/debug marks a procedure as part of")
(* x (+ x 1) (+ x 2) (+ x 2))) (code:comment "the code to be debugged")
(define (same p f x)
(assert (= (p x) (f x))))]
Then call the debugger after loading the above definitions:
@racketblock[
#, @elem{>} (define ucore (debug [integer?] (same poly factored -6)))
#, @elem{>} (render ucore)
#,(call-with-input-file (build-path root "pict.png") (lambda (in) (read-bitmap in 'png)))]
@(rosette-eval '(require rosette/query/debug))
@(rosette-eval '(define (poly x)
(+ (* x x x x) (* 6 x x x) (* 11 x x) (* 6 x))))
@(rosette-eval '(define/debug (factored x)
(* x (+ x 1) (+ x 2) (+ x 2))))
@(rosette-eval '(define (same p f x)
(assert (= (p x) (f x)))))
@(rosette-eval '(define ucore (debug [integer?] (same poly factored -6))))
The @racket[(debug [#, @var[predicate]] #, @var[expr])] query takes as input an expression whose execution leads to an assertion failure, and one or more dynamic type predicates specifying which executed expressions should be treated as potentially faulty by the solver. That is, the predicates express the hypothesis that the failure is caused by an expression with one of the given types. Expressions that produce values of a different type are assumed to be correct.@footnote{For now, only primitive (@racket[boolean?], @racket[integer?], @racket[real?], and @racket[bv?]) types can be used in @racket[debug] forms.}
The output of a @racket[debug] query is a minimal set of program expressions, called a @deftech[#:key "MUC"]{minimal unsatisfiable core}, that form an irreducible cause of the failure. Expressions outside of the core are irrelevant to the failure---there is no way to replace them with constants so that the resulting program satisfies the failing assertion. The failing assertion can only be satisfied if we are allowed to also replace one of the core expressions with a carefully chosen constant. In general, a failing expression may have many different cores, but since every core highlights a buggy subexpression, examining one or two cores often leads to the root cause of the error.
Like bindings, cores are first-class values. In our example, we simply visualize the core using the utility procedure @racket[render].@footnote{@racket[render] can only visualize cores for code that has been saved to a file.} The visualization reveals that the grayed-out subexpression @racket[(+ x 1)] is irrelevant to the failure of @racket[factored] on the input -6. To repair this failure, we have to modify at least one of the remaining expressions, which are highlighted in red.
@subsection[#:tag "sec:synthesize"]{Synthesis} @subsection[#:tag "sec:synthesize"]{Synthesis}
The solution given in @racket[bvmid-no-overflow] avoids the overflow problem in @racket[bvmid] at the cost of performing an additional arithmetic operation. Both implementations also rely on signed division, which is slow and expensive compared to addition, subtraction, and bitwise operations. So our ideal implementation would be correct, small, and composed of only cheap arithmetic and bitwise operations. Does such an implementation exist? To find out, we turn to Rosette's @racket[synthesize] query. The solver can not only find failure-inducing inputs and localize faults, it can also synthesize repairs for buggy expressions. To repair a program, we first replace each buggy expression with a syntactic "@deftech{hole}." A program with holes is called a @deftech{sketch}. The solver completes a sketch by filling its holes with expressions, in such a way that all assertions in the resulting program pass on all inputs.
The following code snippet shows the sketch for our buggy @racket[factored] procedure. We obtained it by replacing the constants in the @seclink["sec:debug"]{minimal core} with @racket[(??)] holes, which are filled with numerical constants.@footnote{This simple replacement strategy is sufficient since we know that a factorization of an @var{n}-degree polynomial takes the form @tt{(* (+ x @var[c]@subscript{0}) ... (+ x @var[c]@subscript{@var{n}}))}, where @var[c]@subscript{@var{i}} is a constant.}
@defs+int[#:eval rosette-eval
((require rosette/lib/synthax)
(define (poly x)
(+ (* x x x x) (* 6 x x x) (* 11 x x) (* 6 x)))
(define (factored x)
(* (+ x (??)) (+ x 1) (+ x (??)) (+ x (??))))
(define (same p f x)
(assert (= (p x) (f x)))))]
The @racket[(??)] construct is imported from the @racket[rosette/lib/synthax] library, which also provides constructs for specifying more complex holes. For example, you can specify a hole that is filled with an expression, drawn from a grammar you define.
The synthesis query uses the solver to search for a correct program in a space of candidate implementations defined by a syntactic @deftech{sketch}. A sketch is a program with @deftech[#:key "hole"]{holes}, which the solver fills with expressions drawn from a specified set of options. For example, @racket[(?? int32?)] stands for a hole that can be filled with any 32-bit integer constant, so the sketch @racket[(bvadd x (?? int32?))] represents all 2@superscript{32} programs that add a 32-bit constant to the variable @racket[x]. Rosette also lets you define richer holes that can be filled with expressions from a given grammar. For example, here is a grammar of all @racket[int32?] expressions that consist of cheap arithmetic and bitwise operations: We query the solver for a correct completion of our sketch as follows:
@interaction[#:eval rosette-eval
@examples[#:eval rosette-eval #:label #f #:no-prompt (code:comment "Call after saving the above definitions to a file:")
(code:line (define-symbolic i integer?)
(require rosette/lib/synthax) (code:comment "Require the sketching library.")) (define binding
(code:line (synthesize #:forall (list i)
(define-grammar (fast-int32 x y) (code:comment "Grammar of int32 expressions over two inputs:") #:guarantee (same poly factored i)))
[expr (eval:alts (print-forms binding) '(define (factored x) (* (+ x 0) (+ x 1) (+ x 2) (+ x 3))))]
(choose x y (?? int32?) (code:comment "<expr> := x | y | <32-bit integer constant> |") The @racket[(synthesize #:forall #, @var[input] #:guarantee #, @var[expr])] query uses the @var[input] form to specify a set of distinguished symbolic values, which are treated as inputs to the expression @var[expr]. The result, if any, is a binding for the remaining symbolic values, created by evaluating holes. This binding guarantees successful evaluation of @var[expr] for @emph{all} possible bindings of the @var[input] values. Passing it to the @racket[print-forms] procedure yields a syntactic representation of the completed sketch.@footnote{@racket[print-forms] can only print the completion of a sketch that has been saved to a file.}
((bop) (expr) (expr)) (code:comment " (<bop> <expr> <expr>) |")
((uop) (expr)))] (code:comment " (<uop> <expr>)")
[bop
(choose bvadd bvsub bvand (code:comment "<bop> := bvadd | bvsub | bvand |")
bvor bvxor bvshl (code:comment " bvor | bvxor | bvshl |")
bvlshr bvashr)] (code:comment " bvlshr | bvashr")
[uop
(choose bvneg bvnot)]) (code:comment "<uop> := bvneg | bvnot"))]
Using this grammar, we can sketch a fast implementation of the midpoint calculation as follows:
@examples[#:eval rosette-eval #:label #f #:no-prompt
(eval:alts
(define (bvmid-fast lo hi)
(fast-int32 lo hi #:depth 2))
(require (only-in rosette/guide/scribble/essentials/bvmid bvmid-fast)))]
The above sketch describes the space of all expressions from the @racket[fast-int32] grammar that have parse trees of depth at most 2. The depth argument is optional. If ommitted, Rosette will use the value of the @racket[(current-grammar-depth)] parameter to bound the depth of the expressions drawn from the grammar.
At this point, it is worth noting that holes and sketches are not fundamental concepts in Rosette. Instead, they are macros defined in terms of the core constructs we have already seen, symbolic constants and assertions. For example, @racket[(?? int32?)] is syntactic sugar for @racket[(let () (define-symbolic #, @var[id] int32?) #, @var[id])], where @var[id] is an internally generated name. Similarly, @racket[(choose bvneg bvnot)] expands to @racket[(if (?? boolean?) bvneg bvnot)]. Finally, a grammar hole such as @racket[(fast-int32 lo hi #:depth 2)] inlines its productions @racket[#:depth] times to create a nested expression of the form @racket[(choose lo hi (?? int32?) ((choose bvadd ...) (choose ...) (choose ...)) ((choose bvneg bvnot) (choose ...)))]. Assigning concrete values to the symbolic constants generated by this expression has the effect of selecting a parse tree (of depth 2 in our example) from the hole's grammar. So, completing a sketch is a matter of finding a suitable binding for the symbolic constants generated by the holes.
With this in mind, we can query the solver for a completion of the @racket[bvmid-fast] sketch (if any) that satisfies our correctness specification:
@(rosette-eval '(require (only-in rosette/guide/scribble/util/demo print-forms-alt)))
@examples[#:eval rosette-eval #:label #f
(code:comment "Save the above definitions to a file before calling print-forms.")
(define sol
(synthesize
#:forall (list l h)
#:guarantee (check-mid bvmid-fast l h)))
sol
(eval:alts
(print-forms sol)
(print-forms-alt sol))]
The synthesis query takes the form @racket[(synthesize #:forall #, @var[input] #:guarantee #, @var[expr])], where @var[input] lists the symbolic constants that represent inputs to a sketched program, and @var[expr] gives the correctness specification for the sketch. The solver searches for a binding from the hole (i.e., non-@var[input]) constants to values such that @var[expr] satisfies its assertions on all legal @var[input]s. Passing this binding to @racket[print-forms] converts it to a syntactic representation of the completed sketch.@footnote{@racket[print-forms] works only on sketches that have been saved to disk.} In our example, the synthesized program implements the midpoint calculation using the logical shift operation, i.e., the midpoint between @racket[lo] and @racket[hi] is calculated as @tt{(lo + hi) >>@subscript{u} 1}.
@(rosette-eval '(clear-vc!))
@subsection[#:tag "sec:solve"]{Angelic Execution} @subsection[#:tag "sec:solve"]{Angelic Execution}
Rosette supports one more solver-aided query, which we call angelic execution. This query is the dual of verification. Given a program with symbolic values, it instructs the solver to find a binding for them that will cause the program to execute normally---that is, without any assumption or assertion failures. Rosette supports one more solver-aided query, which we call "angelic execution." This query is the opposite of verification. Given a program with symbolic values, it instructs the solver to find a binding for them that will cause the program to execute successfully---that is, without any assertion failures.
Angelic execution can be used to solve puzzles, to run incomplete code, or to ``invert'' a program, by searching for inputs that produce a desired output. For example, we can ask the solver to search for two distinct legal inputs, @racket[l] and @racket[h], whose midpoint is the bitwise-and of their bits:
@examples[#:eval rosette-eval #:label #f
(define (bvmid-fast lo hi)
(bvlshr (bvadd hi lo) (bv #x00000001 32)))
Angelic execution can be used to solve puzzles, to run incomplete code, or to "invert" a program, by searching for inputs that produce a desired output. For example, we can ask the solver to find two distinct input values, which are not zeros of the @racket[poly] function, but which @racket[poly] still maps to the same output:
@interaction[#:eval rosette-eval
(define-symbolic x y integer?)
(define sol (define sol
(solve (solve (begin (assert (not (= x y)))
(begin (assert (< (abs x) 10))
(assume (not (equal? l h))) (assert (< (abs y) 10))
(assume (bvsle (int32 0) l)) (assert (not (= (poly x) 0)))
(assume (bvsle l h)) (assert (= (poly x) (poly y))))))
(assert (equal? (bvand l h) (bvmid-fast l h)))))) (evaluate x sol)
(evaluate y sol)
(evaluate (poly x) sol)
(evaluate (poly y) sol)]
sol You can find more examples of angelic execution and other solver-aided queries in the @hyperlink["https://github.com/emina/rosette/blob/master/sdsl/"]{@racket[sdsl]} folder of your Rosette distribution.
(evaluate (bvand l h) sol) @(rosette-eval '(clear-asserts!))
(evaluate (bvmid-fast l h) sol)]
As a fun exercise that builds on this result, try using program synthesis to discover the condition, @racket[(bvmid-and? l h)], that is both necessary and sufficient to ensure that @racket[bvand] and @racket[bvmid-fast] produce the same value on all distinct legal inputs @racket[l] and @racket[r]. (Hint: you can reuse the @racket[fast-int32] grammar from the previous section.)
@examples[#:eval rosette-eval #:label #f
(eval:no-prompt
(define (bvmid-and? lo hi)
#f (code:comment "<--- replace with your sketch\n")
))
(eval:alts
(print-forms
(synthesize
#:forall (list l h)
#:guarantee
(begin
(assume (not (equal? l h)))
(assume (bvsle (int32 0) l))
(assume (bvsle l h))
(assert
(<=> (bvmid-and? l h)
(equal? (bvand l h) (bvmid-fast l h)))))))
(void))]
@(rosette-eval '(clear-vc!))
@section[#:tag "sec:notes"]{Symbolic Reasoning} @section[#:tag "sec:notes"]{Symbolic Reasoning}
We conclude this chapter with a quick overview of common patterns and anti-patterns for programming in Rosette. For more details, see Chapters @seclink["ch:unsafe"]{8}--@seclink["ch:error-tracing"]{10}. Rosette implements solver-aided queries by translating them to the input language of an SMT solver.
This translation is performed using a given @tech["reasoning precision"], as specified
by the @racket[current-bitwidth] parameter. Setting @racket[current-bitwidth]
to a positive integer @var{k} instructs Rosette to approximate both reals and integers with @var{k}-bit words.
Setting it to @racket[#f] instructs Rosette to use infinite precision for real and integer operations.
@subsection{Mixing Theories} The following snippet shows the effect of different @racket[current-bitwidth] settings on query behavior:
@interaction[#:eval rosette-eval
(define-symbolic x integer?)
(current-bitwidth 5) (code:comment "64 = 0 in the 5-bit representation")
(solve (begin (assert (= x 64))
(assert (= x 0))))
(verify (assert (not (and (= x 64) (= x 0)))))
(current-bitwidth #f) (code:comment "but no solutions exist under infinite-precision semantics")
(solve (begin (assert (= x 64))
(assert (= x 0))))
(verify (assert (not (and (= x 64) (= x 0)))))]
Rosette implements solver-aided queries by translating them to the input language of an SMT solver. By default, this translation respects types: a symbolic constant of type @racket[integer?] will be translated to an SMT constant of the same type, i.e., an infinite precision mathematical integer. These types determine which @emph{theories} the solver will need to use to solve a query. As a rule of thumb, the theory of bitvectors tends to elicit fastest solving times, and mixing theories can lead to severe performance degradation. For that reason, it is best to use the types from the same theory throughout your program (e.g., bitvectors). By default, @racket[current-bitwidth] is set to @racket[#f] to be consistent with Racket's
infinite-precision semantics for integers and reals. Beware, however, that using @racket[#f] or a large @var{k}
for @racket[current-bitwidth] may have a negative effect on solver performance. In the worst case, using @racket[#f] can cause the underlying solver to run forever.@footnote{Technically, Rosette translates solver-aided queries to the theory of bitvectors when @racket[current-bitwidth] is set to an integer @var{k}. In particular, it uses @var{k}-bit bitvectors to represent integers and reals, with smaller bitvectors leading to better performance. When @racket[current-bitwidth] is @racket[#f], Rosette uses the theories of integers and reals instead. These theories work well for linear constraints, but reasoning about non-linear integer arithmetic is undecidable.}
To illustrate the impact of mixing theories, consider the following mixed-theory specification for our midpoint example: Non-termination can also be caused by passing symbolic values to recursive procedures. In particular, the expression that determines whether a recursion (or a loop) terminates must be executed on concrete values.
@examples[#:eval rosette-eval #:label #f #:no-prompt @interaction[
(code:line (code:comment "while sandboxed evaluation of (ones x) times out,")
(define (check-mid-slow impl lo hi) (code:comment "Assuming that") (code:comment "normal evaluation would not terminate")
(assume (bvsle (int32 0) lo)) (code:comment "0 ≤ lo and") (eval:alts (define-symbolic x integer?) (void))
(assume (bvsle lo hi)) (code:comment "lo ≤ hi,")
(assert (code:comment "we require that")
(equal?
(bitvector->integer (impl lo hi)) (code:comment "⌈impl(lo, hi)⌉ = ")
(quotient (code:comment "(⌈lo⌉ + ⌈hi⌉) / 2, where")
(+ (bitvector->integer lo) (code:comment "⌈e⌉ stands for the mathematical")
(bitvector->integer hi)) (code:comment "integer corresponding to the")
2)))) (code:comment "32-bit integer e."))]
This new specification uses both bitvectors and integers. Compared to @racket[check-mid], which uses only bitvectors, @racket[check-mid-slow] causes one of our verification queries to become an order of magnitude slower and the other to time out:
@(rosette-eval '(require (only-in racket error)))
@examples[#:eval rosette-eval #:label #f
(time (verify (check-mid bvmid l h)))
(time (verify (check-mid-slow bvmid l h)))
(time (verify (check-mid bvmid-no-overflow l h)))
(eval:alts (eval:alts
(with-deep-time-limit 600 (code:comment "Timeout after 10 minutes ...") (letrec ([ones (lambda (n)
(verify (check-mid-slow bvmid-no-overflow l h))) (if (<= n 0)
(eval:error (error 'call-with-deep-time-limit "out of time")))] (list)
(cons 1 (ones (- n 1)))))])
(printf "~a" (ones 3))
(printf "~a" (ones x)))
(begin (printf "(1 1 1)")
(fprintf (current-error-port) "with-limit: out of time")))]
@subsection{Reasoning Precision} It is, however, safe to apply recursive procedures to symbolic values if they are not used in termination checks.
@interaction[#:eval rosette-eval
While slower than bitvectors, integers are more convenient to use for demos, prototyping, and interfacing with Racket. To bridge this gap, Rosette provides the option of approximating symbolic integers (and reals) as bitvectors of length @var{k}, by setting the @racket[current-bitwidth] parameter to @var{k}. With this setting, integers (and reals) are treated as infinite precision values during evaluation, but when solving queries, they are translated to bitvectors of length @var{k} for better performance. (define-symbolic x integer?)
(letrec ([adder (lambda (vs n)
For example, our slow midpoint queries become orders-of-magnitude faster when allowed to approximate integers with bitvectors: (if (null? vs)
@examples[#:eval rosette-eval #:label #f (list)
(code:comment "By default, current-bitwidth is set to #f, so Rosette translates") (cons (+ (car vs) n) (adder (cdr vs) n))))])
(code:comment "integer? values precisely, using the SMT theory of integers.") (adder '(1 2 3) x))]
(current-bitwidth)
(code:comment "After we set current-bitwidth to 64, integer? values in")
(code:comment "check-mid-slow are translated to SMT bitvectors of length 64.")
(current-bitwidth 64)
(time (verify (check-mid-slow bvmid l h)))
(time (verify (check-mid-slow bvmid-no-overflow l h)))]
In this example, we have chosen @racket[current-bitwidth] carefully to ensure that the resulting approximation is both performant and sound---i.e., the approximate query returns a counterexample exactly when one would be returned by the corresponding integer query. But choosing the right bitwidth is difficult to do in general. If we underapproximate the number of bits that are needed to represent every integer value in a query, we lose soundness, and if we overapproximate it, we lose performance.
For instance, when we re-run the slow midpoint queries with @racket[current-bitwidth] set to 32, the buggy query fails to produce a counterexample and the correct query returns a bogus counterexample. Both results are correct for 32-bit machine integers but incorrect for (infinite-precision) mathematical integers:
@examples[#:eval rosette-eval #:label #f
(code:comment "The bitwidth is too low, so we get ...")
(current-bitwidth 32)
(code:comment "no counterexample to a buggy query, and")
(time (verify (check-mid-slow bvmid l h)))
(code:comment "a bogus counterexample to a correct query.")
(time (verify (check-mid-slow bvmid-no-overflow l h)))]
We can restore soundness by sacrificing performance and setting @racket[current-bitwidth] conservatively to a large value (e.g., 512). In our case, the queries are small so an order-of-magnitude slowdown is acceptable. For large queries, this would lead to timeouts:
@examples[#:eval rosette-eval #:label #f
(code:comment "The bitwidth is too high, so we get a 3-10X slowdown.")
(current-bitwidth 512)
(time (verify (check-mid-slow bvmid l h)))
(time (verify (check-mid-slow bvmid-no-overflow l h)))]
In practice, it is usually best to leave @racket[current-bitwidth] at its default setting (@racket[#f]), and limit the use of integers to code that will be evaluated concretely. This approach works especially well when the solver is configured to accept only bitvectors, so if any integers have made it into a query, the solver fails fast:
@examples[#:eval rosette-eval #:label #f
(current-bitwidth #f)
(require rosette/solver/smt/z3)
(code:line (current-solver (z3 #:logic 'QF_BV)) (code:comment "Allow only bitvectors."))
(code:line (time (verify (check-mid bvmid l h))) (code:comment "Accepted."))
(eval:alts
(code:line (time (verify (check-mid-slow bvmid l h))) (code:comment "Rejected."))
(eval:error (time (verify (check-mid-slow bvmid l h)))))]
@(rosette-eval '(clear-vc!))
@(rosette-eval '(current-solver (z3)))
@subsection{Symbolic Evaluation}
The process by which Rosette turns a query into an SMT constraint is called @deftech{symbolic evaluation}. At a high level, Rosette's symbolic evaluation works by executing @emph{all} paths through a program, and collecting all the assumptions and assertions on these paths into the current verification condition @racket[(vc)]. The resulting @racket[(vc)] is then translated to the SMT language and passed to the solver. This evaluation model has two practical implications on writing performant and terminating Rosette code.
First, if a program is slow or runs forever under standard (concrete) evaluation, it will perform at least as poorly under all-path (symbolic) evaluation. Second, if a program terminates quickly on all concrete inputs, it can still perform poorly or fail to terminate on symbolic inputs. So, extra care must be taken to ensure good performance and termination in the presence of symbolic values.
To illustrate, consider the procedure @racket[bvsqrt] for computing the @hyperlink["https://en.wikipedia.org/wiki/Integer_square_root#Using_bitwise_operations"]{integer square root} of non-negative 32-bit integers. This procedure terminates on all concrete values of @racket[n] but runs forever when given a symbolic input:
@examples[#:eval rosette-eval #:label #f
(eval:no-prompt
(define (bvsqrt n)
(cond
[(bvult n (int32 2)) n]
[else
(define s0 (bvshl (bvsqrt (bvlshr n (int32 2))) (int32 1)))
(define s1 (bvadd s0 (int32 1)))
(if (bvugt (bvmul s1 s1) n) s0 s1)])))
(bvsqrt (int32 3))
(bvsqrt (int32 4))
(bvsqrt (int32 15))
(bvsqrt (int32 16))
(eval:alts
(with-deep-time-limit 10 (code:comment "Timeout after 10 seconds ...")
(bvsqrt l))
(eval:error (error 'call-with-deep-time-limit "out of time")))]
The reason is simple: a call to @racket[bvsqrt] terminates when @racket[n] becomes less than 2. But if we start with a symbolic @racket[n], this never happens because Rosette right-shifts @racket[n] by 2 in each recursive call to generate a new symbolic value:
@examples[#:eval rosette-eval #:label #f
(define n0 l)
n0
(define n1 (bvlshr n0 (int32 2)))
n1
(define n2 (bvlshr n1 (int32 2)))
n2
(define n3 (bvlshr n2 (int32 2)))
n3]
In general, recursion terminates under symbolic evaluation only when the stopping condition is reached with concrete values.
We can force termination by placing a concrete bound @var{k} on the number of times @racket[bvsqrt] can call itself recursively. This approach is called @deftech{finitization}, and it is the standard way to handle unbounded loops and recursion under symbolic evaluation. The following code shows how to implement a @emph{sound} finitization policy. If a @racket[verify] query returns @racket[(unsat)] under a sound policy, we know that (1) the unrolling bound @var{k} is sufficient to execute all possible inputs to @racket[bvsqrt], and (2) all of these executions satisfy the query. If we pick a bound that is too small, the query will generate a counterexample input that needs a larger bound to compute the result. In our example, the bound of 16 is sufficient to verify the correctness of @racket[bvsqrt] on all inputs:
@(rosette-eval '(clear-vc!))
@(rosette-eval '(require (only-in racket make-parameter parameterize)))
@examples[#:eval rosette-eval #:label #f #:no-prompt
(code:comment "Parameter that controls the number of unrollings (5 by default).")
(define fuel (make-parameter 5))
(eval:no-prompt)
(code:comment "A simple macro for defining bounded procedures")
(code:comment "that use (fuel) to limit recursion.")
(define-syntax-rule
(define-bounded (id param ...) body ...)
(define (id param ...)
(assert (> (fuel) 0) "Out of fuel.")
(parameterize ([fuel (sub1 (fuel))])
body ...)))
(eval:no-prompt)
(code:comment "Computes bvsqrt taking at most (fuel) steps.")
(define-bounded (bvsqrt n)
(cond
[(bvult n (int32 2)) n]
[else
(define s0 (bvshl (bvsqrt (bvlshr n (int32 2))) (int32 1)))
(define s1 (bvadd s0 (int32 1)))
(if (bvugt (bvmul s1 s1) n) s0 s1)]))
(eval:no-prompt)
(code:comment "Correctness specification for bvsqrt:")
(code:line
(define (check-sqrt impl n)
(assume (bvsle (int32 0) n)) (code:comment "Assuming n ≥ 0,")
(define √n (impl l))
(define √n+1 (bvadd √n (int32 1))) (code:comment "we require that")
(assert (bvule (bvmul √n √n) n)) (code:comment "(√n)^2 ≤ n and")
(assert (bvult n (bvmul √n+1 √n+1)))) (code:comment "n < (√n + 1)^2."))]
@examples[#:eval rosette-eval #:label #f
(code:comment "Verification fails due to insufficient fuel.")
(define cex (time (verify (check-sqrt bvsqrt l))))
(eval:error (bvsqrt (evaluate l cex)))
(clear-vc!)
(code:comment "Verification succeeds with enough fuel.")
(fuel 16)
(time (verify (check-sqrt bvsqrt l)))]
Many other finitization policies can be defined in a similar way. For example, if we change @racket[define-bounded] to use @racket[assume] instead of @racket[assert], we obtain a finitization policy that ensures @emph{completeness}. If a @racket[verify] query returns a counterexample under a complete policy, we know that the program is buggy, and it violates a query assertion within @var{k} recursive calls. But if the query returns @racket[(unsat)], we know only that there are no bugs within @var[k] or fewer unrollings---we cannot conclude anything about longer executions. So a complete policy prevents false positives, while a sound one prevents false negatives. What kind of policy to use depends on the application, and Rosette leaves that choice to the programmer.
See Chapters @seclink["ch:symbolic-reflection"]{7} and @seclink["ch:unsafe"]{8} to
learn more about common patterns and anti-patterns for effective symbolic reasoning.
@(kill-evaluator rosette-eval) @(kill-evaluator rosette-eval)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,41 @@
#lang rosette/safe
(require rosette/query/debug rosette/lib/render)
(define (poly x)
(+ (* x x x x) (* 6 x x x) (* 11 x x) (* 6 x)))
(define/debug (factored x)
(* x (+ x 1) (+ x 2) (+ x 2)))
(define (same p f x)
(assert (= (p x) (f x))))
(define-symbolic i integer?)
(define cex (verify (same poly factored i)))
(evaluate i cex)
(define c (debug [integer?] (same poly factored -6)))
(render c)
(require rosette/lib/synthax)
(define (factored* x)
(* (+ x (??)) (+ x 1) (+ x (??)) (+ x (??))))
(define binding
(synthesize #:forall (list i)
#:guarantee (same poly factored* i)))
(print-forms binding)
(define-symbolic x y integer?)
(define env
(solve (begin (assert (not (= x y)))
(assert (< (abs x) 10))
(assert (< (abs y) 10))
(assert (not (= (poly x) 0)))
(assert (= (poly x) (poly y))))))
env

View File

@ -5,7 +5,7 @@
@title[#:tag "ch:syntactic-forms" #:style 'toc]{Syntactic Forms} @title[#:tag "ch:syntactic-forms" #:style 'toc]{Syntactic Forms}
The core of the Rosette language (@racketmodname[rosette/safe]) consists of two kinds of syntax forms: a set of basic forms @deftech[#:key "lifted constructs"]{lifted} from Racket, and a set of forms for @seclink["ch:essentials"]{solver-aided programming}. We use the term ``lifted'' to refer to parts of the Racket language that can be used with symbolic values and other solver-aided constructs. The core of the Rosette language (@racket[rosette/safe]) consists of two kinds of syntax forms: a set of basic forms @deftech[#:key "lifted constructs"]{lifted} from Racket, and a set of forms for @seclink["ch:essentials"]{solver-aided programming}. We use the term "lifted" to refer to parts of the Racket language that can be used with symbolic values and other solver-aided constructs.
@[table-of-contents] @[table-of-contents]
@include-section["racket-forms.scrbl"] @include-section["racket-forms.scrbl"]

View File

@ -2,7 +2,7 @@
@(require (for-label rosette/base/form/define) @(require (for-label rosette/base/form/define)
(for-label racket) (for-label racket)
scribble/core scribble/html-properties scribble/examples racket/sandbox scribble/core scribble/html-properties scribble/eval racket/sandbox
"../util/lifted.rkt") "../util/lifted.rkt")
@ -58,7 +58,7 @@ Rosette lifts the following @seclink["syntax" #:doc '(lib "scribblings/reference
(list @elem{Syntax Quoting} @syntax-quoting))] (list @elem{Syntax Quoting} @syntax-quoting))]
Lifted forms have the same meaning in Rosette programs as they do in Racket programs. For example, the Racket expression @racket[(if #, @var[test-expr] #, @var[then-expr] #, @var[else-expr])] evaluates @var[test-expr] first and then, depending on the outcome, it returns the result of evaluating either @var[then-expr] or @var[else-expr]. Rosette preserves this interpretation of @racket[if] for concrete values, and also extends it to work with symbolic values: Lifted forms have the same meaning in Rosette programs as they do in Racket programs. For example, the Racket expression @racket[(if #, @var[test-expr] #, @var[then-expr] #, @var[else-expr])] evaluates @var[test-expr] first and then, depending on the outcome, it returns the result of evaluating either @var[then-expr] or @var[else-expr]. Rosette preserves this interpretation of @racket[if] for concrete values, and also extends it to work with symbolic values:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(let ([y 0]) (let ([y 0])
(if #t (void) (set! y 3)) (if #t (void) (set! y 3))
(printf "y unchanged: ~a\n" y) (printf "y unchanged: ~a\n" y)

View File

@ -12,7 +12,7 @@
0 0
() ()
() ()
(c values c (0 (u . "x\n")))) (c values c (0 (u . "x"))))
#"" #""
#"") #"")
((always-same) ((always-same)
@ -24,54 +24,10 @@
0 0
() ()
() ()
(c values c (0 (u . "x\n")))) (c values c (0 (u . "x"))))
#""
#"")
((equal? (always-same) (always-same)) ((3) 0 () 0 () () (q values #t)) #"" #"")
((define (always-same-3) (define-symbolic y integer? #:length (+ 1 2)) y)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((always-same-3)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list y$0 y$1 y$2)\n"))))
#""
#"")
((always-same-3)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list y$0 y$1 y$2)\n"))))
#""
#"")
((equal? (always-same-3) (always-same-3))
((3) 0 () 0 () () (q values #t))
#""
#"")
((lambda (n) (define-symbolic y integer? #:length n) y)
((3)
0
()
0
()
()
(q
exn
"eval:9.0: define-symbolic: expected a natural? for #:length\n at: (define-symbolic y integer? #:length n)\n in: (define-symbolic y integer? #:length n)"))
#"" #""
#"") #"")
((eq? (always-same) (always-same)) ((3) 0 () 0 () () (q values #t)) #"" #"")
((define (always-different) (define-symbolic* x integer?) x) ((define (always-different) (define-symbolic* x integer?) x)
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
@ -85,7 +41,7 @@
0 0
() ()
() ()
(c values c (0 (u . "x$0\n")))) (c values c (0 (u . "x$0"))))
#"" #""
#"") #"")
((always-different) ((always-different)
@ -97,7 +53,7 @@
0 0
() ()
() ()
(c values c (0 (u . "x$1\n")))) (c values c (0 (u . "x$1"))))
#"" #""
#"") #"")
((eq? (always-different) (always-different)) ((eq? (always-different) (always-different))
@ -109,69 +65,15 @@
0 0
() ()
() ()
(c values c (0 (u . "(= x$2 x$3)\n")))) (c values c (0 (u . "(= x$2 x$3)"))))
#""
#"")
((define (always-different-n n) (define-symbolic* y integer? #:length n) y)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((always-different-n 2)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list y$4 y$5)\n"))))
#""
#"")
((always-different-n 3)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list y$6 y$7 y$8)\n"))))
#""
#"")
((equal? (always-different-n 4) (always-different-n 4))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(&& (= y$9 y$13) (= y$10 y$14) (= y$11 y$15) (= y$12 y$16))\n"))))
#"" #""
#"") #"")
((assert #t) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((assert #t) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assert 1) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((assert 1) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc) ((asserts) ((3) 0 () 0 () () (q values ())) #"" #"")
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((define-symbolic x boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic x boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assert x) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((assert x) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc) ((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -180,14 +82,14 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t x)\n")))) (c values c (c (0 (u . "x")))))
#"" #""
#"") #"")
((assert #f "bad value") ((assert #f "bad value")
((3) 0 () 0 () () (q exn "[assert] bad value")) ((3) 0 () 0 () () (q exn "assert: bad value"))
#"" #""
#"") #"")
((vc) ((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -196,11 +98,14 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t #f)\n")))) (c values c (c #f c (0 (u . "x")))))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc) ((asserts) ((3) 0 () 0 () () (q values ())) #"" #"")
((define-symbolic x y boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assert x) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -209,83 +114,14 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t #t)\n")))) (c values c (c (0 (u . "x")))))
#"" #""
#"") #"")
((vc) ((define sol (solve (assert y)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((assume #t) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assume 1) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((define-symbolic x boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assume x) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc x #t)\n"))))
#""
#"")
((assume #f "bad value")
((3) 0 () 0 () () (q exn "[assume] bad value"))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #f #t)\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((define-symbolic a b c d boolean?)
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((vc) ((asserts)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -294,271 +130,11 @@
0 0
() ()
() ()
(c values c (0 (u . "(vc #t #t)\n")))) (c values c (c (0 (u . "x")))))
#""
#"")
((verify (assert a))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [a #f])\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((assume a) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assert b) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc a (|| b (! a)))\n"))))
#""
#"")
((verify (begin (assume c) (assert d)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [a #t]\n [b #t]\n [c #t]\n [d #f])\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc a (|| b (! a)))\n"))))
#""
#"")
((verify (assert a))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((verify (assert a))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [a #f])\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x c integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((synthesize
#:forall
(list x)
#:guarantee
(begin (assume (even? x)) (assert (odd? (+ x c)))))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [c 1])\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((assume (odd? x)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc (! (= 0 (remainder x 2))) #t)\n"))))
#""
#"")
((synthesize #:forall (list x) #:guarantee (assert (odd? (+ x c))))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [c 0])\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc (! (= 0 (remainder x 2))) #t)\n"))))
#""
#"")
((synthesize
#:forall
(list x)
#:guarantee
(begin (assume (even? x)) (assert (odd? (+ x c)))))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((synthesize
#:forall
(list x)
#:guarantee
(begin (assume (even? x)) (assert (odd? (+ x c)))))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [c 1])\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x y boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assume x) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc x #t)\n"))))
#""
#"")
((solve (assert y))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [x #t]\n [y #t])\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc x #t)\n"))))
#"" #""
#"") #"")
((evaluate x sol) ((3) 0 () 0 () () (q values #t)) #"" #"")
((evaluate y sol) ((3) 0 () 0 () () (q values #t)) #"" #"")
((solve (assert (not x))) ((solve (assert (not x)))
((3) ((3)
1 1
@ -568,35 +144,10 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((solve (assert (not x)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [x #f])\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x y integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic x y integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define inc (solve+)) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define inc (solve+)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((inc (< x y)) ((inc (< x y))
@ -608,7 +159,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 0]\n [y 1])\n")))) (c values c (0 (u . "(model\n [x 0]\n [y 1])"))))
#"" #""
#"") #"")
((inc (> x 5)) ((inc (> x 5))
@ -620,7 +171,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 6]\n [y 7])\n")))) (c values c (0 (u . "(model\n [x 6]\n [y 7])"))))
#"" #""
#"") #"")
((inc (< y 4)) ((inc (< y 4))
@ -632,7 +183,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((inc 1) ((inc 1)
@ -644,7 +195,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 6]\n [y 7])\n")))) (c values c (0 (u . "(model\n [x 6]\n [y 7])"))))
#"" #""
#"") #"")
((inc (< y 9)) ((inc (< y 9))
@ -656,7 +207,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 6]\n [y 7])\n")))) (c values c (0 (u . "(model\n [x 6]\n [y 7])"))))
#"" #""
#"") #"")
((inc 'shutdown) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((inc 'shutdown) ((3) 0 () 0 () () (c values c (void))) #"" #"")
@ -669,16 +220,13 @@
() ()
(q (q
exn exn
"solver-push: contract violation:\n expected: solver?\n given: #f\n argument position: 1st")) "solver-push: contract violation:\nexpected: solver?\ngiven: #f\nargument position: 1st"))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x y integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic x y boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((optimize ((assert x) ((3) 0 () 0 () () (c values c (void))) #"" #"")
#:maximize ((asserts)
(list (+ x y))
#:guarantee
(begin (assume (< x 2)) (assert (< (- y x) 1))))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -687,10 +235,119 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 1]\n [y 1])\n")))) (c values c (c (0 (u . "x")))))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define sol (verify (assert y)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((asserts)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (c (0 (u . "x")))))
#""
#"")
((evaluate x sol) ((3) 0 () 0 () () (q values #t)) #"" #"")
((evaluate y sol) ((3) 0 () 0 () () (q values #f)) #"" #"")
((verify #:assume (assert y) #:guarantee (assert (and x y)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)"))))
#""
#"")
((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x c integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assert (even? x)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((asserts)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (c (0 (u . "(= 0 (remainder x 2))")))))
#""
#"")
((define sol (synthesize #:forall x #:guarantee (assert (odd? (+ x c)))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((asserts)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (c (0 (u . "(= 0 (remainder x 2))")))))
#""
#"")
((evaluate x sol)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "x"))))
#""
#"")
((evaluate c sol) ((3) 0 () 0 () () (q values 77)) #"" #"")
((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((current-bitwidth #f) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x y integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assert (< x 2)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((asserts)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (c (0 (u . "(< x 2)")))))
#""
#"")
((define sol
(optimize #:maximize (list (+ x y)) #:guarantee (assert (< (- y x) 1))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((asserts)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (c (0 (u . "(< x 2)")))))
#""
#"")
((evaluate x sol) ((3) 0 () 0 () () (q values 1)) #"" #"")
((evaluate y sol) ((3) 0 () 0 () () (q values 1)) #"" #"")
((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((current-bitwidth 5) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((current-bitwidth 5) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x y real?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic x y real?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic f (~> real? real?)) ((define-symbolic f (~> real? real?))
@ -707,7 +364,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 3])\n")))) (c values c (0 (u . "(model\n [x 3])"))))
#"" #""
#"") #"")
((solve (assert (= x 64))) ((solve (assert (= x 64)))
@ -719,7 +376,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 0])\n")))) (c values c (0 (u . "(model\n [x 0])"))))
#"" #""
#"") #"")
((solve (assert (and (= x 64) (= x 0)))) ((solve (assert (and (= x 64) (= x 0))))
@ -731,7 +388,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 0])\n")))) (c values c (0 (u . "(model\n [x 0])"))))
#"" #""
#"") #"")
((solve (assert (forall (list x) (= x (+ x y))))) ((solve (assert (forall (list x) (= x (+ x y)))))
@ -768,7 +425,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 7/2])\n")))) (c values c (0 (u . "(model\n [x 7/2])"))))
#"" #""
#"") #"")
((solve (assert (= x 64))) ((solve (assert (= x 64)))
@ -780,7 +437,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 64])\n")))) (c values c (0 (u . "(model\n [x 64])"))))
#"" #""
#"") #"")
((solve (assert (and (= x 64) (= x 0)))) ((solve (assert (and (= x 64) (= x 0))))
@ -792,7 +449,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((solve (assert (forall (list x) (= x (+ x y))))) ((solve (assert (forall (list x) (= x (+ x y)))))
@ -804,7 +461,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [y 0])\n")))) (c values c (0 (u . "(model\n [y 0])"))))
#"" #""
#"") #"")
((solve (assert (= x (f x)))) ((solve (assert (= x (f x))))
@ -816,7 +473,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [x 0]\n [f (fv real?~>real?)])\n")))) (c values c (0 (u . "(model\n [x 0]\n [f (fv real?~>real?)])"))))
#"" #""
#"") #"")
((define-symbolic i j integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic i j integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
@ -833,6 +490,6 @@
0 0
() ()
() ()
(c values c (0 (u . "(unknown)\n")))) (c values c (0 (u . "(unknown)"))))
#"" #""
#"") #"")

View File

@ -1,17 +1,17 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label @(require (for-label
rosette/base/form/define rosette/query/query rosette/solver/solution rosette/base/form/define rosette/query/form rosette/query/eval rosette/solver/solution
(only-in rosette/solver/solver solver?) (only-in rosette/solver/solver solver?)
rosette/base/core/term rosette/base/core/term (only-in rosette/query/debug define/debug debug)
(only-in rosette/query/finitize current-bitwidth) (only-in rosette/query/finitize current-bitwidth)
(only-in rosette/base/base (only-in rosette/base/core/safe assert)
assert assume vc vc-asserts vc-assumes clear-vc! (only-in rosette/base/core/bool asserts clear-asserts!)
bv? forall) (only-in rosette/base/base bv?)
(only-in rosette/base/core/function function? ~>) (only-in rosette/base/core/function function?)
(only-in rosette/base/core/reflect symbolics)) (only-in rosette/base/core/reflect symbolics))
(for-label racket) (for-label racket)
scribble/core scribble/html-properties scribble/examples racket/sandbox racket/runtime-path scribble/core scribble/html-properties scribble/eval racket/sandbox racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@(define-runtime-path root ".") @(define-runtime-path root ".")
@ -19,75 +19,42 @@
@title[#:tag "ch:syntactic-forms:rosette"]{Solver-Aided Forms} @title[#:tag "ch:syntactic-forms:rosette"]{Solver-Aided Forms}
The @seclink["ch:essentials"]{Essentials} chapter introduced The @seclink["ch:essentials"]{Essentials} chapter introduced the key concepts of solver-aided programming. This section defines the corresponding syntactic constructs more precisely.
the key concepts of solver-aided programming. This section
describes the corresponding syntactic constructs in detail.
@declare-exporting[rosette/base/form/define @declare-exporting[rosette/base/form/define
rosette/query/form rosette/query/form
rosette/base/base rosette/base/core/safe
rosette/base/core/bool rosette/base/core/bool
rosette/query/finitize rosette/query/finitize
#:use-sources #:use-sources
(rosette/base/form/define (rosette/base/form/define
rosette/query/form rosette/query/form
rosette/base/base rosette/base/core/safe
rosette/base/core/bool rosette/base/core/bool
rosette/query/finitize)] rosette/query/finitize)]
@section[#:tag "sec:symbolic-constants"]{Symbolic Constants} @section[#:tag "sec:symbolic-constants"]{Symbolic Constants}
@defform*[((define-symbolic id ...+ type) @defform[(define-symbolic id ...+ type)
(define-symbolic id type #:length k))
#:contracts #:contracts
[(type (and/c solvable? type?)) [(type (and/c solvable? type?))]]{
(k natural?)]]{ Binds each provided identifier to a distinct @tech["symbolic constant"] of the given
@tech["solvable type"]. The identifiers are bound to the same constants every time the form is
The first form binds each provided identifier to a distinct evaluated.
@tech["symbolic constant"] of the given
@tech["solvable type"]. The identifiers are bound to the
same constants every time the form is evaluated.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define (always-same) (define (always-same)
(define-symbolic x integer?) (define-symbolic x integer?)
x) x)
(always-same) (always-same)
(always-same) (always-same)
(equal? (always-same) (always-same)) ] (eq? (always-same) (always-same))]
The second form creates a list of @racket[k] distinct
constants and binds it to @racket[id]. The same constants
are bound to @racket[id] every time the form is evaluated.
The form requires @racket[k] to evaluate to a natural number
statically---i.e., at macro expansion time.
@examples[#:eval rosette-eval
(define (always-same-3)
(define-symbolic y integer? #:length (+ 1 2))
y)
(always-same-3)
(always-same-3)
(equal? (always-same-3) (always-same-3))
(eval:alts
(define (always-same-n n)
(define-symbolic y integer? #:length n)
y)
(eval:error (lambda (n) (define-symbolic y integer? #:length n) y)))]
} }
@defform[(define-symbolic* id ...+ type)
@defform*[((define-symbolic* id ...+ type)
(define-symbolic* id type #:length k))
#:contracts #:contracts
[(type (and/c solvable? type?)) [(type (and/c solvable? type?))]]{
(k natural?)]]{ Creates a stream of distinct @tech["symbolic constant"]s of the given
@tech["solvable type"] for each identifier, binding the identifier to the
The first form creates a stream of distinct next element from its stream every time the form is evaluated.
@tech["symbolic constant"]s of the given
@tech["solvable type"] for each identifier, binding the
identifier to the next element from its stream every time
the form is evaluated.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define (always-different) (define (always-different)
(define-symbolic* x integer?) (define-symbolic* x integer?)
@ -95,266 +62,65 @@ describes the corresponding syntactic constructs in detail.
(always-different) (always-different)
(always-different) (always-different)
(eq? (always-different) (always-different))] (eq? (always-different) (always-different))]
}
The second form binds @racket[id] to a list of the next @section[#:tag "sec:assertions"]{Assertions}
@racket[k] elements from its stream every time the form is
evaluated. The expression @racket[k] may produce different
natural numbers depending on the calling context.
@examples[#:eval rosette-eval @defform[(assert expr maybe-message)
(define (always-different-n n) #:grammar
(define-symbolic* y integer? #:length n) [(maybe-message (code:line) msg)]
y)
(always-different-n 2)
(always-different-n 3)
(equal? (always-different-n 4) (always-different-n 4))]}
@section[#:tag "sec:assertions"]{Assertions and Assumptions}
@defform*[((assert expr)
(assert expr msg))
#:contracts #:contracts
[(msg string?)]]{ [(msg (or/c string? procedure?))]]{
Provides a mechanism for communicating desired
Checks that @racket[expr] produces a true value. Rosette program properties to the underlying solver. Rosette keeps track of all
keeps track of all assertions and assumptions encountered assertions evaluated during an execution in an @tech{assertion store}.
during symbolic evaluation in the current @tech{verification condition} (VC). If @racket[expr] evaluates to @racket[#f], an error is thrown using the
If @racket[expr] evaluates to @racket[#f], optional failure message, and @racket[#f] is added to the assertion store. The error message
the assertion adds @racket[#f] to the VC and throws an error can be either a string or a no-argument procedure that throws an error when called.
with the optional failure message @racket[msg]. If If @racket[expr] evaluates to a symbolic boolean value, that value is added to the assertion store.
@racket[expr] evaluates to a symbolic boolean value, this If @racket[expr] evaluates to any other value, @racket[assert] has no effect. The contents
value is added to the VC and execution continues. If of the assertion store can be examined using the @racket[asserts] procedure, and they can be
@racket[expr] evaluates to any other value, @racket[assert] cleared using the @racket[clear-asserts!] procedure.
has no effect. The contents of the VC can be examined using
the @racket[(vc)] procedure, and they can be cleared using
the @racket[clear-vc!] procedure.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(code:line (assert #t) (code:comment "No effect.")) (code:line (assert #t) (code:comment "no effect"))
(code:line (assert 1) (code:comment "No effect.")) (code:line (assert 1) (code:comment "no effect"))
(code:line (vc) (code:comment "The VC tracks assumptions and assertions.")) (code:line (asserts) (code:comment "retrieve the assertion store"))
(define-symbolic x boolean?) (define-symbolic x boolean?)
(assert x) (assert x)
(code:line (vc) (code:comment "x is added to the VC's asserts.")) (code:line (asserts) (code:comment "x added to the assertion store"))
(eval:error (assert #f "bad value")) (assert #f "bad value")
(vc) (asserts)
(code:line (clear-vc!) (code:comment "Clear the VC.")) (code:line (clear-asserts!) (code:comment "clear the assertion store"))
(vc)] (asserts)]
} }
@defform*[((assume expr)
(assume expr msg))
#:contracts
[(msg string?)]]{
Behaves like @racket[assert] except that it updates the
assumption component of the current verification condition
when @racket[expr] evaluates to @racket[#f] or to a
symbolic boolean.
@examples[#:eval rosette-eval
(vc)
(code:line (assume #t) (code:comment "No effect."))
(code:line (assume 1) (code:comment "No effect."))
(code:line (vc) (code:comment "The VC tracks assumptions and assertions."))
(define-symbolic x boolean?)
(assume x)
(code:line (vc) (code:comment "x is added to the VC's assumes."))
(eval:error (assume #f "bad value"))
(vc)
(code:line (clear-vc!) (code:comment "Clear the VC."))
(vc)]
}
@section{Verification}
@defform[(verify expr)]{
Searches for a binding of symbolic constants to concrete
values that satisfies all the assumptions and violates at
least one of the assertions encountered during the
evaluation of @racket[expr]. The binding must also satisfy
all the assumptions and assertions accumulated in the
verification condition @racket[(vc)] before the call to
@racket[verify]. If such a binding exists, the query returns
one as part of a satisfiable @racket[solution?]; otherwise,
the query returns @racket[(unsat)].
Formally,
@racket[(verify expr)] searches for a model of the formula
@racket[(vc-assumes #, @var{P})] ∧ @racket[(vc-asserts #, @var{P})] ∧
@racket[(vc-assumes #, @var{Q})] ∧ ¬ @racket[(vc-asserts #, @var{Q})],
where @var{P} is the verification condition before the
call to @racket[verify] and @var{Q} is the verification condition
generated by evaluating @racket[expr].
The @racket[verify]
query does not retain the assumptions and assertions generated
by @racket[expr], leaving the current verification condition
@racket[(vc)] unchanged after the query returns.
@examples[#:eval rosette-eval
(define-symbolic a b c d boolean?)
(vc)
(code:comment "This query forces a to be false:")
(verify (assert a))
(code:line (vc) (code:comment "VC is unchanged."))
(assume a)
(assert b)
(vc)
(code:comment "This query forces a, b, c to be true, and d to be false:")
(verify
(begin
(assume c)
(assert d)))
(vc)
(code:comment "This query has no solution because we assumed a above:")
(verify (assert a))
(code:comment "Clearing the VC gives the expected solution:")
(clear-vc!)
(vc)
(verify (assert a))]
}
@(rosette-eval '(clear-vc!))
@section{Synthesis}
@defform*[((synthesize input expr)
(synthesize #:forall input #:guarantee expr))]{
Taking @var{I} to be the set of symbolic constants @racket[(symbolics input)]
and @var{H} to be the complement of @var{I},
searches for a binding @var{B}@subscript{@var{H}}
from @var{H} to concrete values that
satisfies @racket[expr] as follows.
If @var{B}@subscript{@var{H}} is extended with any binding
@var{B}@subscript{@var{I}} from @var{I}
to concrete values, then the extended binding
@var{B}@subscript{@var{H}} @var{B}@subscript{@var{I}}
satisfies all the assertions generated by evaluating @racket[expr] whenever
it satisfies the assumptions generated by @racket[expr], as
well as the assumptions and assertions accumulated in the verification condition
@racket[(vc)] before @racket[expr] is evaluated. If such a binding
@var{B}@subscript{@var{H}} exists, the query returns one as part of a
satisfiable @racket[solution?]; otherwise, the query returns
@racket[(unsat)].
Formally, @racket[(synthesize input expr)] searches for a model of
the formula
∃ @var{H}. (∃ @var{I}. @var{pre}(@var{H}, @var{I})) ∧
(∀ @var{I}. @var{pre}(@var{H}, @var{I}) ⇒ @var{post}(@var{H}, @var{I})),
where @var{pre}(@var{H}, @var{I}) is @racket[(vc-assumes #, @var{P})] ∧
@racket[(vc-asserts #, @var{P})] ∧ @racket[(vc-assumes #, @var{Q})],
@var{post}(@var{H}, @var{I}) is @racket[(vc-asserts #, @var{Q})],
@var{P} is the verification condition accumulated before the evaluation of
@racket[expr], and @var{Q} is the verification condition
generated by evaluating @racket[expr]. This formula is stronger than the
classic synthesis formula
∃ @var{H}. ∀ @var{I}. @var{pre}(@var{H}, @var{I}) ⇒ @var{post}(@var{H}, @var{I}).
The additional constraint, ∃ @var{I}. @var{pre}(@var{H}, @var{I}), rules out
trivial solutions that allow @var{pre}(@var{H}, @var{I}) to
be false on all inputs @var{I}.
The formulas @var{pre}(@var{H}, @var{I}) and
@var{post}(@var{H}, @var{I}) are required to be free of quantifiers, so
no @tech[#:key "quantified formula"]{quantified formulas} can be part of
the assumptions or assertions that make up a synthesis query.
The @racket[synthesize] query does not retain the
assumptions and assertions generated by @racket[expr],
but it does retain the updates to @racket[(vc)], if any,
produced by evaluating @racket[input]. In other words,
@racket[(synthesize input expr)] is equivalent to
@racket[(let ([v input]) (synthesize v expr))], where @racket[v]
is a fresh variable.
@examples[#:eval rosette-eval
(define-symbolic x c integer?)
(vc)
(code:comment "This query finds a binding for c that works for all even x:")
(synthesize
#:forall (list x)
#:guarantee
(begin
(assume (even? x))
(assert (odd? (+ x c)))))
(code:line (vc) (code:comment "VC is unchanged."))
(assume (odd? x))
(vc)
(code:comment "This query finds a binding for c that works for all odd x:")
(synthesize
#:forall (list x)
#:guarantee (assert (odd? (+ x c))))
(vc)
(code:comment "This query has no solution because we assumed (odd? x) above:")
(synthesize
#:forall (list x)
#:guarantee
(begin
(assume (even? x))
(assert (odd? (+ x c)))))
(code:comment "Clearing the VC gives the expected solution:")
(clear-vc!)
(vc)
(synthesize
#:forall (list x)
#:guarantee
(begin
(assume (even? x))
(assert (odd? (+ x c)))))]
}
@(rosette-eval '(clear-vc!))
@section{Angelic Execution} @section{Angelic Execution}
@defform[(solve expr)]{ @defform[(solve expr)]{
Searches for a binding of symbolic constants to concrete values that satisfies all assertions encountered
Searches for a binding of symbolic constants to concrete before the invocation of @racket[solve] and during the evaluation of @racket[expr].
values that satisfies all the assumptions and assertions If such a binding exists, it is returned in the form of a satisfiable @racket[solution?]; otherwise,
encountered during the evaluation of @racket[expr], as well the result is an unsatisfiable solution. The assertions encountered while
as all the assumptions and assertions accumulated in the evaluating @racket[expr] are removed from the global @tech["assertion store"] once @racket[solve] returns. As a result,
verification condition @racket[(vc)] before the call to @racket[solve] has no observable effect on the @tech["assertion store"].
@racket[solve]. If such a binding exists, the query returns The solver's ability to find solutions depends on the current @tech["reasoning precision"], as
one as part of a satisfiable @racket[solution?]; otherwise, determined by the @racket[current-bitwidth] parameter.
the result is an unsatisfiable solution.
Formally,
@racket[(solve expr)] searches for a model of the formula
@racket[(vc-assumes #, @var{P})] ∧ @racket[(vc-asserts #, @var{P})] ∧
@racket[(vc-assumes #, @var{Q})] ∧ @racket[(vc-asserts #, @var{Q})],
where @var{P} is the verification condition before the
call to @racket[solve] and @var{Q} is the verification condition
generated by evaluating @racket[expr].
The @racket[solve]
query does not retain the assumptions and assertions generated
by @racket[expr], leaving the current verification condition
@racket[(vc)] unchanged after the query returns.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x y boolean?) (define-symbolic x y boolean?)
(assume x) (assert x)
(code:line (vc) (code:comment "x is added to the VC's assumes.")) (code:line (asserts) (code:comment "x added to the assertion store"))
(code:comment "This query forces both x and y to be true.") (define sol (solve (assert y)))
(solve (assert y)) (code:line (asserts) (code:comment "assertion store same as before"))
(code:line (vc) (code:comment "VC is unchanged.")) (code:line (evaluate x sol) (code:comment "x must be true"))
(code:comment "This query has solution because we assumed (not x) above:") (code:line (evaluate y sol) (code:comment "y must be true"))
(solve (assert (not x)))
(code:comment "Clearing the VC gives the expected solution:")
(clear-vc!)
(vc)
(solve (assert (not x)))] (solve (assert (not x)))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@defproc[(solve+) procedure?]{ @defproc[(solve+) procedure?]{
Returns a stateful procedure that uses a fresh @racket[solver?] instance Returns a stateful procedure that uses a fresh @racket[solver?] instance
to incrementally solve a sequence of constraints. to incrementally solve a sequence of constraints (with respect to @racket[current-bitwidth]).
The returned procedure consumes a constraint (i.e., a boolean value or @tech["symbolic term"]), The returned procedure consumes a constraint (i.e., a boolean value or @tech["symbolic term"]),
a positive integer, or the symbol @racket['shutdown]. a positive integer, or the symbol @racket['shutdown].
@ -370,78 +136,153 @@ subsequent calls to the procedure throw an exception.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x y integer?) (define-symbolic x y integer?)
(define inc (solve+)) (define inc (solve+))
(code:line (inc (< x y)) (code:comment "Push (< x y) and solve.")) (code:line (inc (< x y)) (code:comment "push (< x y) and solve"))
(code:line (inc (> x 5)) (code:comment "Push (> x 5) and solve.")) (code:line (inc (> x 5)) (code:comment "push (> x 5) and solve"))
(code:line (inc (< y 4)) (code:comment "Push (< y 4) and solve.")) (code:line (inc (< y 4)) (code:comment "push (< y 4) and solve"))
(code:line (inc 1) (code:comment "Pop (< y 4) and solve.")) (code:line (inc 1) (code:comment "pop (< y 4) and solve"))
(code:line (inc (< y 9)) (code:comment "Push (< y 9) and solve.")) (code:line (inc (< y 9)) (code:comment "push (< y 9) and solve"))
(code:line (inc 'shutdown) (code:comment "Release resources.")) (code:line (inc 'shutdown) (code:comment "release resources"))
(eval:alts (code:line (inc (> y 4)) (code:comment "unusable"))
(code:line (inc (> y 4)) (code:comment "Unusable after shutdown.")) ]
(eval:error (inc (> y 4))))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@section{Verification}
@defform*[((verify guarantee-expr)
(verify #:assume assume-expr #:guarantee guarantee-expr))]{
Searches for a binding of symbolic constants to concrete values that violates at least one of the
assertions encountered during the evaluation of @racket[guarantee-expr], but that satisfies all
assertions encountered before the invocation of @racket[verify] and during the evaluation of
@racket[assume-expr]. If such a binding exists, it is returned in the form of a
satisfiable @racket[solution?]; otherwise, the result is an unsatisfiable solution. The assertions encountered while
evaluating @racket[assume-expr] and @racket[guarantee-expr] are removed from the global @tech["assertion store"] once
@racket[verify] returns.
The solver's ability to find solutions depends on the current @tech["reasoning precision"], as
determined by the @racket[current-bitwidth] parameter.
@examples[#:eval rosette-eval
(define-symbolic x y boolean?)
(assert x)
(code:line (asserts) (code:comment "x added to the assertion store"))
(define sol (verify (assert y)))
(code:line (asserts) (code:comment "assertion store same as before"))
(code:line (evaluate x sol) (code:comment "x must be true"))
(code:line (evaluate y sol) (code:comment "y must be false"))
(verify #:assume (assert y) #:guarantee (assert (and x y)))]
}
@(rosette-eval '(clear-asserts!))
@section{Synthesis}
@defform*[((synthesize input-expr expr)
(synthesize
#:forall input-expr
maybe-assume
#:guarantee guarantee-expr))
#:grammar ([maybe-assume (code:line) (code:line #:assume assume-expr)])]{
Searches for a binding of symbolic constants
to concrete values that has the following properties:
@itemlist[#:style 'ordered
@item{it does not map the constants in @racket[(symbolics input-expr)]; and,}
@item{it satisfies all assertions encountered during the evaluation of
@racket[guarantee-expr], for every binding of @racket[input-expr] constants to values that satisfies
the assertions encountered before the invocation of @racket[synthesize] and during the evaluation of
@racket[assume-expr].}]
If no such binding exists, the result is an unsatisfiable @racket[solution?]. The assertions encountered while
evaluating @racket[assume-expr] and @racket[guarantee-expr] are removed from the global @tech["assertion store"] once
@racket[synthesize] returns. These assertions @bold{may not include} @tech[#:key "quantified formula"]{quantified formulas}.
The solver's ability to find solutions depends on the current @tech["reasoning precision"],
as determined by the @racket[current-bitwidth] parameter.
@examples[#:eval rosette-eval
(define-symbolic x c integer?)
(assert (even? x))
(code:line (asserts) (code:comment "assertion pushed on the store"))
(define sol
(synthesize #:forall x
#:guarantee (assert (odd? (+ x c)))))
(code:line (asserts) (code:comment "assertion store same as before"))
(code:line (evaluate x sol) (code:comment "x is unbound"))
(code:line (evaluate c sol) (code:comment "c must an odd integer"))]
}
@(rosette-eval '(clear-asserts!))
@section{Optimization} @section{Optimization}
@defform[(optimize @defform[(optimize
maybe-minimize maybe-minimize
maybe-maximize maybe-maximize
#:guarantee expr) #:guarantee guarantee-expr)
#:grammar ([maybe-minimize (code:line) (code:line #:minimize min-expr)] #:grammar ([maybe-minimize (code:line) (code:line #:minimize minimize-expr)]
[maybe-maximize (code:line) (code:line #:maximize max-expr)]) [maybe-maximize (code:line) (code:line #:maximize maximize-expr)])
#:contracts [(min-expr (listof (or/c integer? real? bv?))) #:contracts [(minimize-expr (listof (or/c integer? real? bv?)))
(max-expr (listof (or/c integer? real? bv?)))]]{ (maximize-expr (listof (or/c integer? real? bv?)))]]{
Searches for a binding of symbolic constants to concrete values that satisfies all assertions encountered
Searches for an optimal binding of symbolic constants to before the invocation of @racket[optimize] and during the evaluation of @racket[minimize-expr],
concrete values that satisfies all the assumptions and @racket[maximize-expr], and @racket[guarantee-expr].
assertions encountered during the evaluation of If such a binding exists, it is returned in the form of a satisfiable @racket[solution?]; otherwise,
@racket[expr], as well as all the assumptions and assertions the result is an unsatisfiable solution. Any satisfiable solution returned by @racket[optimize] is optimal with respect
accumulated in the verification condition @racket[(vc)] to the cost terms provided in the @racket[minimize-expr] and @racket[maximize-expr] lists. Specifically, these
before the evaluation of @racket[expr]. The binding is terms take on the minimum or maximum values when evaluated with respect to a satisfiable solution. For more details on
optimal in that it minimizes the cost terms in
the @racket[min-expr] list and maximizes those in the
@racket[max-expr] list. If such a binding exists, the query
returns one as part of a satisfiable @racket[solution?]; otherwise,
the query returns @racket[(unsat)]. For more details on
solving optimization problems, see the solving optimization problems, see the
@hyperlink["https://rise4fun.com/Z3/tutorial/optimization"]{Z3 optimization tutorial}. @hyperlink["https://rise4fun.com/Z3/tutorial/optimization"]{Z3 optimization tutorial}.
As is the case for other solver-aided queries, the assertions encountered while
Formally, evaluating @racket[minimize-expr],
@racket[(solve expr)] searches for an optimal model of the formula @racket[maximize-expr], and @racket[guarantee-expr] are removed from the global @tech["assertion store"] once
@racket[(vc-assumes #, @var{P})] ∧ @racket[(vc-asserts #, @var{P})] ∧ the query returns. As a result,
@racket[(vc-assumes #, @var{Q})] ∧ @racket[(vc-asserts #, @var{Q})], @racket[optimize] has no observable effect on the @tech["assertion store"].
where @var{P} is the verification condition before the The solver's ability to find solutions (as well as their optimality) depends on the current @tech["reasoning precision"],
evaluation of @racket[expr], @var{Q} is the verification condition as determined by the @racket[current-bitwidth] parameter.
generated by evaluating @racket[expr], the cost terms @racket[min-expr] are
minimized, and the cost terms @racket[max-expr] are maximized.
The @racket[optimize] query does not retain the
assumptions and assertions generated by @racket[expr],
but it does retain the updates to @racket[(vc)], if any,
produced by evaluating @racket[min-expr] and @racket[max-expr]. In other words,
@racket[(optimize #:minimize min-expr #:maximize max-expr #:guarantee expr)] is
equivalent to
@racket[(let ([v min-expr] [w max-expr]) (optimize #:minimize v #:maximize w #:guarantee expr))],
where @racket[v] and @racket[w] are fresh variables.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(code:line (current-bitwidth #f) (code:comment "use infinite-precision arithmetic"))
(define-symbolic x y integer?) (define-symbolic x y integer?)
(code:comment "This query maximizes x + y while ensuring that y - x < 1 whenever x < 2:") (assert (< x 2))
(optimize (code:line (asserts) (code:comment "assertion added to the store"))
#:maximize (list (+ x y)) (define sol
#:guarantee (optimize #:maximize (list (+ x y))
(begin #:guarantee (assert (< (- y x) 1))))
(assume (< x 2)) (code:line (asserts) (code:comment "assertion store same as before"))
(assert (< (- y x) 1))))] (code:line (evaluate x sol) (code:comment "x + y is maximal at x = 1"))
(code:line (evaluate y sol) (code:comment "and y = 1"))]
} }
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@(rosette-eval '(current-bitwidth 5)) @(rosette-eval '(current-bitwidth 5))
@section{Debugging}
@defmodule[rosette/query/debug #:use-sources (rosette/query/debug)]
@defform[(define/debug head body ...)
#:grammar
([head id (id ...)])]{
Defines a procedure or an expression, and marks it as a candidate for debugging.
When a @racket[debug] query is applied to a failing execution,
forms that are not marked in this way are considered
correct. The solver will apply the debugging algorithm only to
expressions and procedures marked as potentially faulty using
@racket[define/debug].
}
@defform[(debug [type ...+] expr)
#:contracts
([type (and/c solvable? type? (not/c function?))])]{
Searches for a minimal set of @racket[define/debug] expressions of
the given @tech["solvable type"](s) that are collectively responsible for the observed failure of @racket[expr].
If no expressions of the specified types are relevent to the failure, an error is thrown. The
returned expressions, if any, are called a minimal unsatisfiable core. The core expressions
are relevant to the observed failure in that preventing the failure requries modifying at least one
core expression. In particular, if all of the non-core expressions were replaced with
fresh constants created using @racket[define-symbolic*], @racket[(solve expr)] would still fail. It
can only execute successfully if at least one of the core expressions is also
replaced with a fresh constant. See the @seclink["ch:essentials"]{Essentials} chapter for example uses of
the @racket[debug] form.}
@section[#:tag "sec:reasoning-precision"]{Reasoning Precision} @section[#:tag "sec:reasoning-precision"]{Reasoning Precision}
@defparam[current-bitwidth k (or/c #f positive-integer?) @defparam[current-bitwidth k (or/c #f positive-integer?)
@ -479,28 +320,21 @@ subsequent calls to the procedure throw an exception.
#:eval rosette-eval #:eval rosette-eval
(define-symbolic x y real?) (define-symbolic x y real?)
(define-symbolic f (~> real? real?)) (define-symbolic f (~> real? real?))
(code:line (current-bitwidth 5) (code:comment "Use 5 bits for integers and reals.")) (current-bitwidth 5)
(code:line (solve (assert (= x 3.5))) (code:comment "3.5 = 3 under 5-bit semantics.")) (code:line (solve (assert (= x 3.5))) (code:comment "3.5 = 3 under 5-bit semantics"))
(code:line (solve (assert (= x 64))) (code:comment "0 = 64 under 5-bit semantics,")) (code:line (solve (assert (= x 64))) (code:comment "0 = 64 under 5-bit semantics"))
(code:line (solve (assert (and (= x 64) (= x 0)))) (code:comment "leading to counterintuitive results.")) (code:line (solve (assert (and (= x 64) (= x 0)))) (code:comment "leading to counterintuitive results."))
(eval:alts (code:line (solve (assert (forall (list x) (= x (+ x y))))) (code:comment "Quantifiers are not supported,"))
(solve (code:comment "Quantifiers are not supported,") (code:line (solve (assert (= x (f x)))) (code:comment "and neither are uninterpreted functions."))
(assert (forall (list x) (= x (+ x y))))) (current-bitwidth #f)
(eval:error (solve (assert (forall (list x) (= x (+ x y))))))) (code:line (solve (assert (= x 3.5))) (code:comment "Infinite-precision semantics produces expected results."))
(eval:alts
(solve (code:comment "and neither are uninterpreted functions.")
(assert (= x (f x))))
(eval:error (solve (assert (= x (f x))))))
(code:line (current-bitwidth #f) (code:comment "Use infinite-precision semantics ..."))
(code:line (solve (assert (= x 3.5))) (code:comment "to obtain the expected results."))
(solve (assert (= x 64))) (solve (assert (= x 64)))
(solve (assert (and (= x 64) (= x 0)))) (solve (assert (and (= x 64) (= x 0))))
(solve (code:comment "Quantifiers work, and") (code:line (solve (assert (forall (list x) (= x (+ x y))))) (code:comment "Quantifiers work, and"))
(assert (forall (list x) (= x (+ x y)))))
(code:line (solve (assert (= x (f x)))) (code:comment "so do uninterpreted functions.")) (code:line (solve (assert (= x (f x)))) (code:comment "so do uninterpreted functions."))
(define-symbolic i j integer?) (code:line (define-symbolic i j integer?) (code:comment "But nonlinear integer arithmetic is undecidable."))
(solve (code:comment "But nonlinear integer arithmetic") (solve
(begin (code:comment "is undecidable.") (begin
(assert (> i 0)) (assert (> i 0))
(assert (> j 0)) (assert (> j 0))
(assert (or (= (/ i j) 2) (= (/ j i) 2)))))] (assert (or (= (/ i j) 2) (= (/ j i) 2)))))]

View File

@ -1,197 +0,0 @@
#lang rosette
(require rosette/lib/synthax rosette/guide/scribble/util/demo)
(provide bvmul2_?? bvmul2_choose bvmul2_bitfast
bvsdiv2_bitcmp bvsdiv2_bvcmp bvsdiv2_bvcmp*)
(define (bvmul2_?? x)
(bvshl x (?? (bitvector 8))))
(define (bvmul2_choose x)
((choose bvshl bvashr bvlshr) x (?? (bitvector 8))))
(define-grammar (bitfast y)
[expr
(choose y (?? (bitvector 8))
((bop) (expr) (expr)))]
[bop
(choose bvshl bvashr bvlshr
bvand bvor bvxor
bvadd bvsub)])
(define (bvmul2_bitfast x)
(bitfast x #:depth 2))
(define-grammar (bitcmp y)
[cmp
(choose
((op) (bitfast y) (bitfast y))
(and (cmp) (cmp)))]
[op
(choose
bvult bvule
bvslt bvsle)])
(define (bvsdiv2_bitcmp x)
(if (bitcmp x)
(bitfast x)
(bitfast x)))
(define (bvsdiv2 x)
(if (bvsge x (bv 0 8))
(bvlshr x (bv 1 8))
(bvadd (bvand x (bv 1 8))
(bvashr x (bv 1 8)))))
(define-grammar (bvcmp xs)
[cmp
(choose
((op) xs xs)
(and (cmp) (cmp)))]
[op
(choose
bvult bvule
bvslt bvsle)])
(define (bvsdiv2_bvcmp x)
(if (bvcmp (bitfast x))
(bitfast x)
(bitfast x)))
(define (bveq_sketch x y)
(bvcmp (choose x y) #:depth 2))
(define-grammar (bvcmp* xs)
[cmp
(choose
((op) (arg) (arg))
(and (cmp) (cmp)))]
[op
(choose
bvult bvule
bvslt bvsle)]
[arg xs])
(define (bveq_sketch* x y)
(bvcmp* (choose x y) #:depth 2))
(define (bvsdiv2_bvcmp* x)
(if (bvcmp* (bitfast x))
(bitfast x)
(bitfast x)))
(define-symbolic x (bitvector 8))
(define-demo ??-demo
(demo (bvmul2_?? (bv 1 8)))
(demo (bvmul2_?? (bv 3 8)))
(demo (bvmul2_?? x))
(demo (equal? (bvmul2_?? x) (bvmul2_?? x)))
(define sol
(synthesize
#:forall (list x)
#:guarantee (assert (equal? (bvmul2_?? x) (bvmul x (bv 2 8))))))
(demo sol)
(demo (print-forms sol)))
(define-demo choose-demo
(demo (bvmul2_choose (bv 1 8)))
(demo (bvmul2_choose (bv 3 8)))
(demo (bvmul2_choose x))
(demo (equal? (bvmul2_choose x) (bvmul2_choose x)))
(clear-vc!)
(define sol
(synthesize
#:forall (list x)
#:guarantee (assert (equal? (bvmul2_choose x) (bvmul x (bv 2 8))))))
(demo sol)
(demo (print-forms sol)))
(define-demo grammar-demo
(demo (bvmul2_bitfast x))
(demo (equal? (bvmul2_bitfast x) (bvmul2_bitfast x)))
(clear-vc!)
(define sol
(synthesize
#:forall (list x)
#:guarantee (assert (equal? (bvmul2_bitfast x) (bvmul x (bv 2 8))))))
(demo sol)
(demo (print-forms sol)))
(define-demo grammar-with-grammar-hole-demo
(clear-vc!)
(current-grammar-depth 2)
(define sol
(synthesize
#:forall (list x)
#:guarantee
(assert (equal? (bvsdiv2_bitcmp x) (bvsdiv x (bv 2 8))))))
(demo (print-forms sol)))
(define-demo grammar-with-argument-choose-hole-demo
(clear-vc!)
(define-symbolic x y (bitvector 8))
(current-grammar-depth 2)
(demo
(synthesize
#:forall (list x y)
#:guarantee
(assert (equal? (bveq_sketch x y) (bveq x y)))))
(demo
(print-forms
(synthesize
#:forall (list x y)
#:guarantee
(assert (equal? (bveq_sketch* x y) (bveq x y)))))))
(define-demo grammar-with-argument-bitfast-hole-demo
(clear-vc!)
(current-grammar-depth 2)
(demo
(synthesize
#:forall (list x)
#:guarantee
(assert (equal? (bvsdiv2_bvcmp x) (bvsdiv x (bv 2 8))))))
(demo
(print-forms
(synthesize
#:forall (list x)
#:guarantee
(assert (equal? (bvsdiv2_bvcmp* x) (bvsdiv x (bv 2 8))))))))
(module+ main
(??-demo)
(choose-demo)
(grammar-demo)
(grammar-with-grammar-hole-demo)
(grammar-with-argument-choose-hole-demo)
(grammar-with-argument-bitfast-hole-demo))

View File

@ -1,48 +0,0 @@
#lang rosette
(require rosette/lib/angelic rosette/lib/destruct)
(define BV (bitvector 8))
(struct Add (arg) #:transparent)
(struct Mul (arg) #:transparent)
(struct Sqr () #:transparent)
(define (interpret prog [acc (bv 0 BV)])
(if (null? prog)
acc
(interpret
(cdr prog)
(destruct (car prog)
[(Add v) (bvadd acc v)]
[(Mul v) (bvmul acc v)]
[(Sqr) (bvmul acc acc)]
[_ acc]))))
(define (inst*)
(define-symbolic* arg BV)
(choose* (Add arg) (Mul arg) (Sqr)))
(define (prog* n)
(if (<= n 0)
(list)
(cons (inst*) (prog* (- n 1)))))
(define-symbolic acc BV)
(define prog (prog* 3))
(define sol
(synthesize
#:forall (list acc)
#:guarantee
(assert
(equal? (interpret prog acc)
(bvsub (bvmul (bv 3 BV) acc acc) (bv 1 BV))))))
(evaluate prog sol)

View File

@ -4,7 +4,7 @@
@title[#:tag "ch:libraries" #:style 'toc]{Libraries} @title[#:tag "ch:libraries" #:style 'toc]{Libraries}
Chapters @seclink["ch:getting-started"]{1}-@seclink["ch:programmer-defined-datatypes"]{5} introduce the basic constructs and datatypes for programming in Rosette. This chapter describes the parts of the core Racket libraries (e.g., I/O procedures) that are exported by @racketmodname[rosette/safe], as well as Rosette libraries that provide additional facilities for solver-aided development. Chapters @seclink["ch:getting-started"]{1}-@seclink["ch:programmer-defined-datatypes"]{5} introduce the basic constructs and datatypes for programming in Rosette. This chapter describes the parts of the core Racket libraries (e.g., I/O procedures) that are exported by @racket[rosette/safe], as well as Rosette libraries that provide additional facilities for solver-aided development.
@[table-of-contents] @[table-of-contents]

View File

@ -1,14 +1,86 @@
;; This file was created by make-log-based-eval ;; This file was created by make-log-based-eval
((require rosette/lib/synthax) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((require rosette/lib/synthax) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((require (only-in rosette/guide/scribble/util/demo print-forms-alt)) ((define (div2 x)
((choose bvshl bvashr bvlshr bvadd bvsub bvmul) x (?? (bitvector 8))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((require (only-in rosette/guide/scribble/libs/bvmul2 bvmul2_??)) ((define-symbolic i (bitvector 8))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((bvmul2_?? (bv 1 8)) ('(define (div2 x) (bvlshr x (bv 1 8)))
((3) 0 () 0 () () (q values (define (div2 x) (bvlshr x (bv 1 8)))))
#""
#"")
((code:comment "Defines a grammar for boolean expressions")
((3)
0
()
0
()
()
(q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#""
#"")
((code:comment "in negation normal form (NNF).")
((3)
0
()
0
()
()
(q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#""
#"")
((define-synthax
(nnf x y depth)
#:base
(choose x (! x) y (! y))
#:else
(choose
x
(! x)
y
(! y)
((choose && ||) (nnf x y (- depth 1)) (nnf x y (- depth 1)))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((code:comment "The body of nnf=> is a hole to be filled with an")
((3)
0
()
0
()
()
(q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#""
#"")
((code:comment "expression of depth (up to) 1 from the NNF grammar.")
((3)
0
()
0
()
()
(q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#""
#"")
((define (nnf=> x y) (nnf x y 1))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define-symbolic a b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
(`(define (nnf=> x y) (,|| (! x) y))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -17,44 +89,71 @@
0 0
() ()
() ()
(c values c (0 (u . "(bvshl (bv #x01 8) ??:bvmul2:9:12)\n")))) (c values c (c define c (q nnf=> x y) c (c (0 (u . "||")) q (! x) y))))
#"" #""
#"") #"")
((bvmul2_?? (bv 3 8)) ((code:comment "A grammar for linear arithmetic.")
((3) ((3)
1 0
(((lib "rosette/guide/scribble/util/lifted.rkt") ()
.
deserialize-info:opaque-v0))
0 0
() ()
() ()
(c values c (0 (u . "(bvshl (bv #x03 8) ??:bvmul2:9:12)\n")))) (q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#"" #""
#"") #"")
((define-symbolic x (bitvector 8)) ((define-synthax LA (((_ e ...) (+ (* e (??)) ...))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((bvmul2_?? x) ((code:comment "The following query has no solution because (??) in")
((3) ((3)
1 0
(((lib "rosette/guide/scribble/util/lifted.rkt") ()
.
deserialize-info:opaque-v0))
0 0
() ()
() ()
(c values c (0 (u . "(bvshl x ??:bvmul2:9:12)\n")))) (q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#""
#"")
((code:comment "(LA e ...) generates a single integer hole that is")
((3)
0
()
0
()
()
(q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#""
#"")
((code:comment "shared by all e passed to LA, in this case x and y.")
((3)
0
()
0
()
()
(q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#""
#"")
((define-symbolic* x y integer?)
((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((equal? (bvmul2_?? x) (bvmul2_?? x)) ((3) 0 () 0 () () (q values #t)) #"" #"")
((define sol ((define sol
(synthesize (synthesize
#:forall #:forall
(list x) (list x y)
#:guarantee #:guarantee
(assert (equal? (bvmul2_?? x) (bvmul x (bv 2 8)))))) (assert (= (LA x y) (+ (* 2 x) y)))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
@ -67,22 +166,135 @@
0 0
() ()
() ()
(c values c (0 (u . "(model\n [??:bvmul2:9:12 (bv #x01 8)])\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((print-forms-alt sol) ((code:comment "The following query has a solution because the second")
((3) 0 () 0 () () (c values c (void))) ((3)
#"(define (bvmul2_?? x) (bvshl x (bv #x01 8)))\n" 0
()
0
()
()
(q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#""
#"") #"")
((require (only-in rosette/guide/scribble/libs/bvmul2 bvmul2_choose)) ((code:comment "clause of LA2 creates two independent (??) holes.")
((3)
0
()
0
()
()
(q
exn
"code:comment: undefined;\n cannot reference an identifier before its definition\n in module: 'program"))
#""
#"")
((define-synthax
LA2
(((_ e) (* e (??))) ((_ e1 e2) (+ (* e1 (??)) (* e2 (??))))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((define-symbolic x (bitvector 8)) ((define sol2
(synthesize
#:forall
(list x y)
#:guarantee
(assert (= (LA2 x y) (+ (* 2 x) y)))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((bvmul2_choose x) ('(define sol
(synthesize
#:forall
(list x y)
#:guarantee
(assert (= (+ (* x 2) (* y 1)) (+ (* 2 x) y)))))
((3)
0
()
0
()
()
(q
values
(define sol
(synthesize
#:forall
(list x y)
#:guarantee
(assert (= (+ (* x 2) (* y 1)) (+ (* 2 x) y)))))))
#""
#"")
((require rosette/lib/angelic) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define (static) (choose 1 2 3))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define (dynamic) (choose* 1 2 3))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((static)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(ite 0$choose:eval:27:0 1 (ite 1$choose:eval:27:0 2 3))"))))
#""
#"")
((static)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0 (u . "(ite 0$choose:eval:27:0 1 (ite 1$choose:eval:27:0 2 3))"))))
#""
#"")
((dynamic)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(ite xi?$0 1 (ite xi?$1 2 3))"))))
#""
#"")
((dynamic)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(ite xi?$2 1 (ite xi?$3 2 3))"))))
#""
#"")
((equal? (static) (static)) ((3) 0 () 0 () () (q values #t)) #"" #"")
((equal? (dynamic) (dynamic))
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -95,250 +307,6 @@
values values
c c
(0 (0
(u (u . "(= (ite xi?$4 1 (ite xi?$5 2 3)) (ite xi?$6 1 (ite xi?$7 2 3)))"))))
.
"(ite*\n (⊢ 0$choose:bvmul2:12:4 (bvshl x ??:bvmul2:12:35))\n (⊢\n (&& 1$choose:bvmul2:12:4 (! 0$choose:bvmul2:12:4))\n (bvashr x ??:bvmul2:12:35))\n (⊢\n (&& (! 0$choose:bvmul2:12:4) (! 1$choose:bvmul2:12:4))\n (bvlshr x ??:bvmul2:12:35)))\n\n"))))
#""
#"")
((equal? (bvmul2_choose x) (bvmul2_choose x))
((3) 0 () 0 () () (q values #t))
#""
#"")
((define sol
(synthesize
#:forall
(list x)
#:guarantee
(assert (equal? (bvmul2_choose x) (bvmul x (bv 2 8))))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
(sol
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(model\n [0$choose:bvmul2:12:4 #t]\n [1$choose:bvmul2:12:4 #f]\n [??:bvmul2:12:35 (bv #x01 8)])\n\n"))))
#""
#"")
((print-forms-alt sol)
((3) 0 () 0 () () (c values c (void)))
#"(define (bvmul2_choose x) (bvshl x (bv #x01 8)))\n"
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-grammar
(bitfast y)
(expr (choose y (?? (bitvector 8)) ((bop) (expr) (expr))))
(bop (choose bvshl bvashr bvlshr bvand bvor bvxor bvadd bvsub)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((require (only-in rosette/guide/scribble/libs/bvmul2 bvmul2_bitfast))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define-symbolic x (bitvector 8))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((begin0 (equal? (bvmul2_bitfast x) (bvmul2_bitfast x)) (clear-vc!))
((3) 0 () 0 () () (q values #t))
#""
#"")
((define sol
(synthesize
#:forall
(list x)
#:guarantee
(assert (equal? (bvmul2_bitfast x) (bvmul x (bv 2 8))))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((print-forms-alt sol)
((3) 0 () 0 () () (c values c (void)))
#"(define (bvmul2_bitfast x) (bvshl x (bv #x01 8)))\n"
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-grammar
(bitcmp y)
(cmp (choose ((op) (bitfast y) (bitfast y)) (and (cmp) (cmp))))
(op (choose bvult bvule bvslt bvsle)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((require (only-in rosette/guide/scribble/libs/bvmul2 bvsdiv2_bitcmp))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define-symbolic x (bitvector 8))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((current-grammar-depth 2) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define sol
(synthesize
#:forall
(list x)
#:guarantee
(assert (equal? (bvsdiv2_bitcmp x) (bvsdiv x (bv 2 8))))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((print-forms-alt sol)
((3) 0 () 0 () () (c values c (void)))
#"(define (bvsdiv2_bitcmp x)\n (if (and (bvult x (bv #x81 8)) (bvsle (bv #x23 8) (bv #x72 8)))\n (bvashr x (bv #x01 8))\n (bvadd (bvashr x (bv #x01 8)) (bvand (bv #x01 8) x))))\n"
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-grammar
(bvcmp xs)
(cmp (choose ((op) xs xs) (and (cmp) (cmp))))
(op (choose bvult bvule bvslt bvsle)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((require (only-in rosette/guide/scribble/libs/bvmul2 bvsdiv2_bvcmp))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define-symbolic x (bitvector 8))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((current-grammar-depth 2) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((synthesize
#:forall
(list x)
#:guarantee
(assert (equal? (bvsdiv2_bvcmp x) (bvsdiv x (bv 2 8)))))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#""
#"")
((define-grammar
(bvcmp* xs)
(cmp (choose ((op) (arg) (arg)) (and (cmp) (cmp))))
(op (choose bvult bvule bvslt bvsle))
(arg xs))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((require (only-in rosette/guide/scribble/libs/bvmul2 bvsdiv2_bvcmp*))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x (bitvector 8))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((current-grammar-depth 2) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define sol
(synthesize
#:forall
(list x)
#:guarantee
(assert (equal? (bvsdiv2_bvcmp* x) (bvsdiv x (bv 2 8))))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((print-forms-alt sol)
((3) 0 () 0 () () (c values c (void)))
#"(define (bvsdiv2_bvcmp* x)\n (if (bvult x (bv #x81 8))\n (bvashr (bvashr x (bv #x01 8)) (bv #x00 8))\n (bvashr (bvadd x (bv #x01 8)) (bvsub (bv #x49 8) (bv #x48 8)))))\n"
#"")
((require rosette/lib/angelic rosette/lib/destruct)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define BV (bitvector 8)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((struct Add (arg) #:transparent)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((struct Mul (arg) #:transparent)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((struct Sqr () #:transparent) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define (interpret prog (acc (bv 0 BV)))
(if (null? prog)
acc
(interpret
(cdr prog)
(destruct
(car prog)
((Add v) (bvadd acc v))
((Mul v) (bvmul acc v))
((Sqr) (bvmul acc acc))
(_ acc)))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define (inst*) (define-symbolic* arg BV) (choose* (Add arg) (Mul arg) (Sqr)))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define (prog* n) (if (<= n 0) (list) (cons (inst*) (prog* (- n 1)))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define-symbolic acc BV) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define prog (prog* 3)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
(prog
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"'((union\n [xi?$1 #(struct:Add arg$0)]\n [(&& xi?$2 (! xi?$1)) #(struct:Mul arg$0)]\n [(&& (! xi?$1) (! xi?$2)) #(struct:Sqr)])\n (union\n [xi?$4 #(struct:Add arg$3)]\n [(&& xi?$5 (! xi?$4)) #(struct:Mul arg$3)]\n [(&& (! xi?$4) (! xi?$5)) #(struct:Sqr)])\n (union\n [xi?$7 #(struct:Add arg$6)]\n [(&& xi?$8 (! xi?$7)) #(struct:Mul arg$6)]\n [(&& (! xi?$7) (! xi?$8)) #(struct:Sqr)]))\n\n"))))
#""
#"")
((define sol
(synthesize
#:forall
(list acc)
#:guarantee
(assert
(equal?
(interpret prog acc)
(bvsub (bvmul (bv 3 BV) acc acc) (bv 1 BV))))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((evaluate prog sol)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list (Sqr) (Add (bv #x55 8)) (Mul (bv #x03 8)))\n"))))
#"" #""
#"") #"")

View File

@ -17,4 +17,5 @@ Rosette exports the following facilities from the core Racket libraries:
These facilities are safe to use in Rosette programs, even in the presence of symbolic values, assertions, and solver-aided queries. They are not, however, @tech[#:key "lifted constructs"]{lifted}: if their Racket implementation expects a concrete value of a given type, they will fail when given a symbolic value. These constructs are safe to use in the sense that they will fail in a predictable fashion, according to their concrete Racket specification, instead of causing the enclosing Rosette program to exhibit unexpected behavior. These facilities are safe to use in Rosette programs, even in the presence of symbolic values, assertions, and solver-aided queries. They are not, however, @tech[#:key "lifted constructs"]{lifted}: if their Racket implementation expects a concrete value of a given type, they will fail when given a symbolic value. These constructs are safe to use in the sense that they will fail in a predictable fashion, according to their concrete Racket specification, instead of causing the enclosing Rosette program to exhibit unexpected behavior.
The @racketmodname[rosette/safe] language allows programs to import arbitrary Racket code using the standard @racket[require] mechanism. This is strongly discouraged, however, unless the use of such code obeys the restrictions outlined in the @seclink["ch:unsafe"]{Chapter 8}. Violating these restrictions may lead to incorrect program behavior, crashes, and loss of data (for programs that perform external side-effects, such as writing to files). In other words, arbitrary Racket code is, by default, unsafe to use. The @racket[rosette/safe] language allows programs to import arbitrary Racket code using the standard @racket[require] mechanism. This is strongly discouraged, however, unless the use of such code obeys the restrictions outlined in the @seclink["ch:unsafe"]{Chapter 8}. Violating these restrictions may lead to incorrect program behavior, crashes, and loss of data (for programs that perform external side-effects, such as writing to files). In other words, arbitrary Racket code is, by default, unsafe to use.

View File

@ -0,0 +1,72 @@
#lang rosette
(require rosette/lib/synthax)
(define (div2 x) ([choose bvshl bvashr bvlshr bvadd bvsub bvmul] x (?? (bitvector 8))))
(define-symbolic i (bitvector 8))
(define m1
(synthesize #:forall (list i)
#:guarantee (assert (equal? (div2 i) (bvudiv i (bv 2 8))))))
(print-forms m1)
(generate-forms m1)
(define-synthax (nnf x y depth)
#:base (choose x (! x) y (! y))
#:else (choose
x (! x) y (! y)
((choose && ||) (nnf x y (- depth 1))
(nnf x y (- depth 1)))))
(define (nnf=> x y) (nnf x y 1))
(define-symbolic a b boolean?)
(print-forms
(synthesize
#:forall (list a b)
#:guarantee (assert (equal? (=> a b) (nnf=> a b)))))
; A grammar for linear arithmetic.
(define-synthax LA
([(_ e ...) (+ (* e (??)) ...)]))
; The following query has no solution because (??) in
; (LA e ...) generates a single integer hole that is
; shared by all e passed to LA, in this case x and y.
(define-symbolic* x y integer?)
(define sol
(synthesize
#:forall (list x y)
#:guarantee (assert (= (LA x y) (+ (* 2 x) y)))))
sol
; The following query has a solution because the second
; clause of LA2 creates two independent (??) holes.
(define-synthax LA2
([(_ e) (* e (??))]
[(_ e1 e2) (+ (* e1 (??)) (* e2 (??)))]))
(define sol2
(synthesize
#:forall (list x y)
#:guarantee (assert (= (LA2 x y) (+ (* 2 x) y)))))
(print-forms sol2)
#|
(define-synthax [shift terminal ... k]
#:assert (>= k 0)
[choose
terminal ... (??)
([choose >> << >>>] (shift terminal ... (- k 1))
(shift terminal ... (- k 1)))])
(define (div2mul4 x) (shift x 2))
(define m2
(synthesize #:forall (list i)
#:assume (assert (>= i 0))
#:guarantee (assert (= (div2mul4 i) (* 4 (quotient i 2))))))
(print-forms m2)|#

View File

@ -1,387 +1,159 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label @(require (for-label
rosette/base/form/define rosette/solver/solution rosette/query/query rosette/base/form/define rosette/solver/solution rosette/query/query rosette/query/eval
rosette/base/core/term rosette/lib/angelic rosette/base/core/term rosette/lib/angelic
rosette/lib/synthax (except-in rosette/query/debug assert )
(only-in rosette/base/base assert function? bitvector (only-in rosette/lib/synthax ?? choose define-synthax generate-forms print-forms)
bvshl bvashr bvlshr bvadd bvsub bvmul bvsdiv (only-in rosette/base/core/safe assert)
bvand bvor bvxor bvult bvule bvslt bvsle) (only-in rosette/base/base function? bitvector bvshl bvashr bvlshr bvadd bvsub bvmul)
(only-in rosette/lib/destruct destruct) rosette/lib/render
racket) racket (only-in pict pict?))
scribble/core scribble/html-properties scribble/examples racket/sandbox racket/runtime-path scribble/core scribble/html-properties scribble/eval racket/sandbox racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@(define-runtime-path root ".") @(define-runtime-path root ".")
@(define rosette-eval (rosette-log-evaluator (logfile root "libs-log"))) @(define rosette-eval (rosette-log-evaluator (logfile root "libs-log")))
@title[#:tag "sec:rosette-libs"]{Solver-Aided Libraries} @title[#:tag "sec:rosette-libs"]{Solver-Aided Libraries}
In principle, solver-aided programming requires only In principle, solver-aided programming requires only symbolic values and the basic constructs described in Chapter @seclink["ch:syntactic-forms:rosette"]{3}. In practice, however, it is often convenient to work with richer constructs, which are built on top of these primitives. Rosette ships with three libraries that provide such constructs, as well as utility procedures for turning the results of synthesis and debugging queries into code.
symbolic values and the basic constructs described in
Chapter @seclink["ch:syntactic-forms:rosette"]{3}. In
practice, however, it is often convenient to work with
richer constructs, which are built on top of these
primitives. Rosette ships with two libraries that provide
such constructs, as well as utility procedures for turning
the results of synthesis queries into code.
@section{Synthesis Library} @section{Synthesis Library}
@defmodule[rosette/lib/synthax #:use-sources (rosette/lib/synthax)] @defmodule[rosette/lib/synthax #:use-sources (rosette/lib/synthax/core rosette/lib/synthax/form)]
@(rosette-eval '(require rosette/lib/synthax)) @(rosette-eval '(require rosette/lib/synthax))
@(rosette-eval '(require (only-in rosette/guide/scribble/util/demo print-forms-alt)))
@defform[(?? maybe-type) @defform[(?? maybe-type)
#:grammar [(maybe-type (code:line) #:grammar [(maybe-type (code:line)
type-expr)] type-expr)]
#:contracts ([type-expr (and/c solvable? type? (not/c function?))]) #:contracts ([type-expr (and/c solvable? type? (not/c function?))])
]{ ]{
Introduces a @tech{hole} into a program---a placeholder for a concrete constant of the given type.
Introduces a constant @tech{hole} into a program---a The default type for holes, if one is not provided, is @racket[integer?].
placeholder for a concrete constant of the given type. The Chapter @seclink["sec:synthesize"]{2.3.3} shows an example of using integer holes to @tech{sketch}
default value for @racket[type-expr], if one is not a factored polynomial function, which is then completed with the help of a @racket[synthesize] query.
provided, is @racket[integer?]. The @racket[??] construct The @racket[(??)] construct @seclink["sec:symbolic-constants"]{creates}
@seclink["sec:symbolic-constants"]{creates} and returns a and returns a fresh symbolic constant of type @racket[type-expr] (or @racket[integer?]).
distinct symbolic constant of type @racket[type-expr].
When used in a recursive @racketlink[define-grammar]{grammar definition},
a @racket[??] hole generates a fresh constant
every time it is evaluated, similarly to
@racket[define-symbolic*]. Otherwise, a @racket[??] hole
always generates the same constant, similarly to
@racket[define-symbolic].
See also @racket[define-grammar].
@examples[#:eval rosette-eval #:no-prompt
(code:comment "Uses a ?? hole to sketch a procedure for multiplying")
(code:comment "an 8-bit number by 2 using a constant left shift.")
(eval:alts
(define (bvmul2_?? x)
(bvshl x (?? (bitvector 8))))
(require (only-in rosette/guide/scribble/libs/bvmul2 bvmul2_??)))]
@examples[#:eval rosette-eval #:label #f
(code:comment "bvmul2_?? hole always returns the same constant:")
(bvmul2_?? (bv 1 8))
(bvmul2_?? (bv 3 8))
(define-symbolic x (bitvector 8))
(bvmul2_?? x)
(equal? (bvmul2_?? x) (bvmul2_?? x))
(code:comment "Use synthesis to fill the hole with a constant:")
(define sol
(synthesize
#:forall (list x)
#:guarantee (assert (equal? (bvmul2_?? x) (bvmul x (bv 2 8))))))
sol
(code:comment "Save bvmul2_?? to a file before calling print-forms.")
(eval:alts (print-forms sol) (print-forms-alt sol))
]
} }
@defform[(choose expr ...+)]{ @defform[(choose expr ...+)]{
Introduces a choice @tech{hole} into a program---a placeholder to be filled with one of the given expressions.
Introduces a choice @tech{hole} into a program---a This construct defines @var[n]-1 fresh boolean constants and uses them to conditionally select one of the @var[n]
placeholder to be filled with one of the given expressions. provided expressions.
This construct defines @var[n]-1 distinct boolean constants @examples[#:eval rosette-eval
and uses them to conditionally select one of the @var[n] (define (div2 x)
provided expressions. ([choose bvshl bvashr bvlshr bvadd bvsub bvmul] x (?? (bitvector 8))))
(define-symbolic i (bitvector 8))
When used in a recursive @racketlink[define-grammar]{grammar definition},
a @racket[choose] hole generates fresh selector
constants and may select a different expression every time
it is evaluated. Otherwise, a @racket[choose] hole generates
the same constants and makes the same selection every time.
See also @racket[choose*] and @racket[define-grammar].
@examples[#:eval rosette-eval #:no-prompt
(code:comment "Uses a chooose hole to sketch a procedure for multiplying")
(code:comment "an 8-bit number by 2 using a constant shift.")
(eval:alts (eval:alts
(define (bvmul2_choose x) (print-forms
((choose bvshl bvashr bvlshr) x (?? (bitvector 8)))) (synthesize #:forall (list i)
(require (only-in rosette/guide/scribble/libs/bvmul2 bvmul2_choose)))] #:guarantee (assert (equal? (div2 i) (bvudiv i (bv 2 8))))))
@examples[#:eval rosette-eval #:label #f '(define (div2 x) (bvlshr x (bv #x01 8))))]
(code:comment "bvmul2_choose holes always generate the same constants:")
(define-symbolic x (bitvector 8))
(bvmul2_choose x)
(equal? (bvmul2_choose x) (bvmul2_choose x))
(code:comment "Use synthesis to fill the holes:")
(define sol
(synthesize
#:forall (list x)
#:guarantee (assert (equal? (bvmul2_choose x) (bvmul x (bv 2 8))))))
sol
(code:comment "Save bvmul2_choose to a file before calling print-forms.")
(eval:alts (print-forms sol) (print-forms-alt sol))
]
} }
@defform*[((define-synthax id
([pattern expr] ...+))
(define-synthax (id terminal ... k)
#:base base-expr
#:else else-expr))]{
Defines a grammar of expressions that can be used to
fill holes of the form @racket[(id e ...)]. That is, writing
@racket[(id e ...)] introduces a @tech{hole} that is to
be filled with an expression from the @racket[id] grammar.
The first form, @racket[(define-synthax id ([pattern expr] ...+))],
works like Racket's @racket[syntax-rules] macro: it fills
the hole @racket[(id e ...)] with the expression @racket[expr] that
corresponds to the first @racket[pattern] matching the hole.
The second form, @racket[(define-synthax (id terminal ... k) #:base base-expr #:else else-expr)],
defines a recursive grammar that is inlined a finite number of times
to fill a hole. The @racket[base-expr] usually chooses among
the provided terminals,
while the @racket[else-expr] chooses among the terminals and a set of productions.
The hole @racket[(id e ... k)] must specify the inlining bound
@racket[k] as an integer literal.
@defform[(define-grammar (id arg-id ...) @examples[#:eval rosette-eval
[rule-id rule-expr] ...+)]{ (eval:no-prompt
(code:comment "Defines a grammar for boolean expressions")
(code:comment "in negation normal form (NNF).")
(define-synthax (nnf x y depth)
#:base (choose x (! x) y (! y))
#:else (choose
x (! x) y (! y)
((choose && ||) (nnf x y (- depth 1))
(nnf x y (- depth 1))))))
(eval:no-prompt
(code:comment "The body of nnf=> is a hole to be filled with an")
(code:comment "expression of depth (up to) 1 from the NNF grammar.")
(define (nnf=> x y)
(nnf x y 1)))
(define-symbolic a b boolean?)
(eval:alts
(print-forms
(synthesize
#:forall (list a b)
#:guarantee (assert (equal? (=> a b) (nnf=> a b)))))
`(define (nnf=> x y) (,|| (! x) y)))
]
Defines a macro for creating and filling grammar Since @racket[define-synthax] uses macros to implement recursive grammars,
@tech[#:key "hole"]{holes}. The macro uses instantiating a recursive grammar with a large limit (e.g., k > 3) can cause
@racket[(id arg-id ...)] as the pattern for creating holes long compilation times, especially if @racket[else-expr] contains many
and @racket[[rule-id rule-expr] ...+] as the rules for recursive instantiations of the grammar.
filling these holes with expressions. In particular, writing
@racket[(id #, @var[arg-expr] ...)] creates a hole for an
expression derived from the grammar
@racket[[rule-id rule-expr] ...+], where the pattern
variables @racket[arg-id] are instantianted with the hole
arguments @racket[#, @var[arg-expr]].
Each hole defines a local scope that binds the rule names Note also that the @racket[...] transformer cannot be used to create multiple
@racket[rule-id] to procedures of the form @racket[??] or @racket[choose] holes within a @racket[define-synthax] form.
@racket[(lambda () ... rule-expr)]. This scope behaves like In particular, a given syntactic occurrence of @racket[??] or @racket[choose]
@racket[letrec], so rules can refer to themselves and each always refers to the same hole, as shown below.
other via @racket[(rule-id)] to specify a recursive grammar.
For example, the following definition specifies a
grammar of fast bitvector expressions over one input:
@(rosette-eval '(clear-vc!))
@examples[#:eval rosette-eval #:label #f #:no-prompt
(code:line
(define-grammar (bitfast y)
[expr (code:comment "<expr> :=")
(choose y (?? (bitvector 8)) (code:comment " y | <8-bit constant> |")
((bop) (expr) (expr)))] (code:comment " ((bop) <expr> <expr>)")
[bop (code:comment "<bop> :=")
(choose bvshl bvashr bvlshr (code:comment " bvshl | bvashr | bvlshr |")
bvand bvor bvxor (code:comment " bvand | bvor | bvxor | ")
bvadd bvsub)]) (code:comment " bvadd | bvsub"))]
By default, a grammar hole is filled by evaluating the
first grammar rule @racket[(#, @elem{@var{rule-id}@subscript{0}})]
in the hole's local scope, and limiting the call stack to include at
most @racket[(current-grammar-depth)] applications of the grammar rules.
Holes can override these defaults by providing the optional arguments
@racket[#:depth n] and @racket[#:start #, @elem{@var{rule-id}@subscript{k}}],
which control the call stack depth and the starting rule, respectively.
@examples[#:eval rosette-eval #:label #f #:no-prompt
(code:comment "Uses a bitfast hole to sketch a procedure consisting")
(code:comment "of a fast bitvector expression over x, created by at")
(code:comment "most 2 applications of the bitfast grammar rules,")
(code:comment "following the initial application of the (expr) rule.")
(eval:alts
(define (bvmul2_bitfast x)
(bitfast x #:depth 2))
(require (only-in rosette/guide/scribble/libs/bvmul2 bvmul2_bitfast)))]
Whenever a grammar hole is evaluated, its constituent
@racket[choose] and @racket[??] holes generate the same set
of constants, with each rule application generating a
distinct subset of this set. This allows different
applications of the same rule to produce different
components of the unique expression that fills a grammar
hole.
@examples[#:eval rosette-eval #:label #f
(code:comment "The choose and ?? holes that are part of the same bitfast")
(code:comment "hole always generate the same set of constants:")
(define-symbolic x (bitvector 8))
(eval:alts
(equal? (bvmul2_bitfast x) (bvmul2_bitfast x))
(begin0
(equal? (bvmul2_bitfast x) (bvmul2_bitfast x))
(clear-vc!)))
@examples[#:eval rosette-eval
(eval:no-prompt
(code:comment "A grammar for linear arithmetic.")
(define-synthax LA
([(_ e ...) (+ (* e (??)) ...)]))
(code:comment "The following query has no solution because (??) in")
(code:comment "(LA e ...) generates a single integer hole that is")
(code:comment "shared by all e passed to LA, in this case x and y.")
(define-symbolic* x y integer?)
(define sol (define sol
(synthesize (synthesize
#:forall (list x) #:forall (list x y)
#:guarantee (assert (equal? (bvmul2_bitfast x) (bvmul x (bv 2 8)))))) #:guarantee (assert (= (LA x y) (+ (* 2 x) y))))))
(code:comment "Save bvmul2_bitfast to a file before calling print-forms.") sol
(eval:alts (print-forms sol) (print-forms-alt sol))]
Grammar rules can include grammar holes as part of their (eval:no-prompt
definition. An included grammar hole behaves just like an (code:comment "The following query has a solution because the second")
included @racket[choose] or @racket[??] hole: it can be (code:comment "clause of LA2 creates two independent (??) holes.")
filled with different expressions in different applications (define-synthax LA2
of the enclosing rule. ([(_ e) (* e (??))]
[(_ e1 e2) (+ (* e1 (??)) (* e2 (??)))]))
@(rosette-eval '(clear-vc!)) (define sol2
@examples[#:eval rosette-eval #:label #f #:no-prompt
(code:comment "Specifies a grammar of signed and unsigned")
(code:comment "comparisons of bitfast expressions.")
(code:line
(define-grammar (bitcmp y)
[cmp
(choose (code:comment "<cmp> :=")
((op) (bitfast y) (bitfast y)) (code:comment " (<op> <bitfast y> <bitfast y>) |")
(and (cmp) (cmp)))] (code:comment " (and <cmp> <cmp>)")
[op
(choose (code:comment "<op> :=")
bvult bvule (code:comment " bvult | bvule |")
bvslt bvsle)]) (code:comment " bvslt | bvsle"))
(code:comment "Uses bitcmp and bitfast holes to sketch")
(code:comment "a procedure with a conditional, intended")
(code:comment "to implement fast signed division by 2.")
(eval:alts
(define (bvsdiv2_bitcmp x)
(if (bitcmp x)
(bitfast x)
(bitfast x)))
(require (only-in rosette/guide/scribble/libs/bvmul2 bvsdiv2_bitcmp)))]
@examples[#:eval rosette-eval #:label #f
(define-symbolic x (bitvector 8))
(code:line (current-grammar-depth 2) (code:comment "Set the grammar depth."))
(define sol
(synthesize (synthesize
#:forall (list x) #:forall (list x y)
#:guarantee (assert (equal? (bvsdiv2_bitcmp x) (bvsdiv x (bv 2 8)))))) #:guarantee (assert (= (LA2 x y) (+ (* 2 x) y))))))
(code:comment "Save bvsdiv2_bitcmp to a file before calling print-forms.")
(code:comment "Note that the solver can return any correct solution.")
(code:comment "In this case, there is a shorter solution that omits the")
(code:comment "trivially true right child from the test expression.")
(eval:alts (print-forms sol) (print-forms-alt sol))]
Finally, grammar holes can accept other holes as arguments.
Unlike included holes, argument holes are @emph{shared}
within the same rule. If an argument @racket[arg-id] is
bound to a hole @var[arg-expr], then @emph{all} occurrences
of @racket[arg-id] in a given rule refer to @emph{one}
shared @var[arg-expr] hole, which can be filled with
different expressions in different applications of the rule.
In contrast, if a rule explicitly includes @var[arg-expr]
multiple times, then each occurrence of @var[arg-expr]
creates a distinct hole. The following examples illustrate
the difference and show how to structure grammars to avoid
unwanted sharing of argument holes.
@(rosette-eval '(clear-vc!))
@examples[#:eval rosette-eval #:label #f #:no-prompt
(code:comment "This grammar is intended to take a hole xs")
(code:comment "as an argument and use it to define the space")
(code:comment "of signed and unsigned comparisons over the xs")
(code:comment "expressions. The grammar is the same as bitcmp")
(code:comment "except that it uses xs instead of (bitfast y)")
(code:comment "in the cmp rule.")
(define-grammar (bvcmp xs)
[cmp
(choose (code:comment "Both occurrences of xs")
((op) xs xs) (code:comment "refer to a single shared")
(and (cmp) (cmp)))] (code:comment "hole in this rule.")
[op
(choose
bvult bvule
bvslt bvsle)])
(code:comment "This sketch is the same as bvsdiv2_bitcmp except")
(code:comment "that it replaces bitcmp with (bvcmp (bitfast x)).")
(eval:alts (eval:alts
(define (bvsdiv2_bvcmp x) (print-forms sol2)
(if (bvcmp (bitfast x)) '(define sol
(bitfast x)
(bitfast x)))
(require (only-in rosette/guide/scribble/libs/bvmul2 bvsdiv2_bvcmp)))]
@examples[#:eval rosette-eval #:label #f
(define-symbolic x (bitvector 8))
(current-grammar-depth 2)
(code:comment "There is no solution because the two xs in the")
(code:comment "cmp rule of the bvcmp grammar refer to the same")
(code:comment "hole, so it is not possible for the rule to")
(code:comment "create a comparison expression with different left")
(code:comment "and right children (comparing x to a constant).")
(synthesize (synthesize
#:forall (list x) #:forall
#:guarantee (assert (equal? (bvsdiv2_bvcmp x) (bvsdiv x (bv 2 8)))))] (list x y)
@examples[#:eval rosette-eval #:label #f #:no-prompt #:guarantee
(code:comment "To avoid the sharing, we refactor bvcmp to place xs into") (assert (= (+ (* x 2) (* y 1)) (+ (* 2 x) y))))))]
(code:comment "its own rule, arg, which is then called by cmp. ") }
(code:comment "This works because every application of the arg rule")
(code:comment "can fill its sole xs hole with a different expression.")
(define-grammar (bvcmp* xs)
[cmp
(choose
((op) (arg) (arg))
(and (cmp) (cmp)))]
[op
(choose
bvult bvule
bvslt bvsle)]
[arg xs])
(code:comment "This sketch is the same as bvsdiv2_bitcmp except")
(code:comment "that it replaces bitcmp with (bvcmp* (bitfast x)).")
(eval:alts
(define (bvsdiv2_bvcmp* x)
(if (bvcmp* (bitfast x))
(bitfast x)
(bitfast x)))
(require (only-in rosette/guide/scribble/libs/bvmul2 bvsdiv2_bvcmp*)))]
@(rosette-eval '(clear-vc!))
@examples[#:eval rosette-eval #:label #f
(define-symbolic x (bitvector 8))
(current-grammar-depth 2)
(code:comment "A solution exists now because the arg rule produces")
(code:comment "different left and right children for the comparison")
(code:comment "operator used in the bvsdiv2_bvcmp* conditional test.")
(define sol
(synthesize
#:forall (list x)
#:guarantee (assert (equal? (bvsdiv2_bvcmp* x) (bvsdiv x (bv 2 8))))))
(code:comment "Save bvsdiv2_bvcmp* to a file before calling print-forms.")
(eval:alts (print-forms sol) (print-forms-alt sol))]}
@defform[(define-simple-grammar (id arg-id ...) body)]{
Defines a grammar with a single (possibly recursive) rule. Equivalent to
@(racketblock
(define-grammar (id param ...)
[id body]))}
@defparam[current-grammar-depth n natural?
#:value 0]{
A parameter that controls the evaluation of
@racketlink[define-grammar]{grammar} holes. Evaluating a
grammar hole involves evaluating the rules of its grammar.
The @racket[(current-grammar-depth)] parameter controls how
many of these rules can appear on the call stack after the
start rule is evaluated. The default value of 0 allows
only the start rule to be evaluated, and a value of
@racket[n > 0] allows at most @racket[n] additional rules to
be added to the call stack. For best performance, keep the
value of this parameter as low as possible.}
@defproc[(generate-forms [solution solution?]) (listof syntax?)]{ @defproc[(generate-forms [solution solution?]) (listof syntax?)]{
Given a satisfiable @racket[solution?] to a @racket[synthesize] query,
Given a satisfiable @racket[solution?] to a returns a list of @tech{sketch} completions for that query.
@racket[synthesize] query, returns a list of @tech{sketch} Sketch completions can only be generated for programs that have been saved to disk.
completions for that query. Sketch completions can only be }
generated for programs that have been saved to disk.
This procedure cooperates with the constructs in the
@racketmodname[rosette/lib/synthax] library to turn solutions into
code. It works by scanning program files for
@racketlink[??]{constant}, @racketlink[choose]{choice}, and
@racketlink[define-grammar]{grammar} holes, and replacing
all identified holes with code. The code for a hole is
computed from the @racket[solution] bindings for the
symbolic constants generated by the hole. Holes are
identified by their original names: @racket[??],
@racket[choose], and any @racket[id] introduced by
@racket[define-grammar] or @racket[define-simple-grammar].
If a program uses different names for these holes (e.g., by
renaming them in a @racket[require] clause),
@racket[generate-forms] will not recognize or replace them,
even if the given solution has bindings for their symbolic
constants.}
@defproc[(print-forms [solution solution?]) void?]{ @defproc[(print-forms [solution solution?]) void?]{
Pretty-prints the result of applying @racket[generate-forms] to the given Pretty-prints the result of applying @racket[generate-forms] to the given
@ -392,83 +164,36 @@ sol
@section{Angelic Execution Library} @section{Angelic Execution Library}
@defmodule[rosette/lib/angelic #:use-sources (rosette/lib/angelic)] @defmodule[rosette/lib/angelic #:use-sources (rosette/lib/angelic)]
@(rosette-eval '(require rosette/lib/angelic))
@defproc[(choose* [v any/c] ...+) any/c]{ @defproc[(choose* [v any/c] ...+) any/c]{
Non-determinstically chooses between the provided values. The difference
between @racket[choose*] and @racket[choose] is analogous to the difference
between @racket[define-symbolic*] and @racket[define-symbolic]: the former
is dynamic and the latter is static.
@examples[#:eval rosette-eval
(define (static) (choose 1 2 3))
(define (dynamic) (choose* 1 2 3))
(static)
(static)
(dynamic)
(dynamic)
(equal? (static) (static))
(equal? (dynamic) (dynamic))]
}
Symbolically chooses one of the provided values. The @section{Debugging Library}
procedure creates @var[n]-1 fresh symbolic boolean constants @defmodule[rosette/lib/render #:use-sources (rosette/lib/render)]
every time it is evaluated, and uses these constants to
conditionally select one of the @var[n] provided
expressions.
The @racket[choose*] procedure is related to the
@racket[choose] construct from the synthesis library.
Semantically, the difference between @racket[choose*] and
@racket[choose] is analogous to the difference between
@racket[define-symbolic*] and @racket[define-symbolic].
They also differ in how they are typically used:
@racket[choose] is used primarily for synthesis, while
@racket[choose*] is suitable for both synthesis (via the
@racket[synthesize] query) and angelic execution (via the
@racket[solve] query).
In the context of synthesis, @racket[choose] is used to
sketch programs in (a subset of) the Rosette language, and
@racket[choose*] can be used to sketch programs in any
language that has a Rosette-based interpreter.
@examples[#:eval rosette-eval #:no-prompt
(require rosette/lib/angelic rosette/lib/destruct)
(define BV (bitvector 8))
(code:comment "A toy language for programs that manipulate a")
(code:comment "a single storage cell by adding a constant to")
(code:comment "it, multiplying it by constant, and squaring it.")
(struct Add (arg) #:transparent)
(struct Mul (arg) #:transparent)
(struct Sqr () #:transparent)
(define (interpret prog [acc (bv 0 BV)])
(if (null? prog)
acc
(interpret
(cdr prog)
(destruct (car prog)
[(Add v) (bvadd acc v)]
[(Mul v) (bvmul acc v)]
[(Sqr) (bvmul acc acc)]
[_ acc]))))
(code:comment "Creates a symbolic instruction (a hole) in the toy language.")
(define (inst*)
(define-symbolic* arg BV)
(choose* (Add arg) (Mul arg) (Sqr)))
(code:comment "Creates a toy program consisting of n symbolic instructions.")
(define (prog* n)
(if (<= n 0)
(list)
(cons (inst*) (prog* (- n 1)))))]
@examples[#:eval rosette-eval #:label #f
(define-symbolic acc BV)
(define prog (prog* 3))
prog
(code:comment "Complete prog so that it implements 3 * acc^2 - 1 for all acc.")
(define sol
(synthesize
#:forall (list acc)
#:guarantee
(assert
(equal? (interpret prog acc)
(bvsub (bvmul (bv 3 BV) acc acc) (bv 1 BV))))))
(evaluate prog sol)]
@defproc[(render [solution solution?] [font-size natural/c 16]) pict?]{
Given an unsatisfiable @racket[solution?] to a @racket[debug] query, returns a
@racket[pict?] visualization of that solution. The visualization displays the
debugged code, highlighting the faulty expressions (i.e., those in the @racket[solution]'s core) in red.
The optional @racket[font-size] parameter controls the size of the font used to typeset the code.
Visualizations can only be constructed for programs that have been saved to disk.
See Chapter @seclink["sec:debug"]{2.3.2} for an example of using @racket[render].
} }
@(kill-evaluator rosette-eval) @(kill-evaluator rosette-eval)

View File

@ -10,7 +10,8 @@
(only-in racket/gui snip%) (only-in racket/gui snip%)
rosette/base/form/define rosette/base/form/define
rosette/base/core/term rosette/base/core/term
(only-in rosette/base/base bv bitvector ~> assert) (only-in rosette/base/base bv bitvector ~>)
(only-in rosette/base/core/safe assert)
racket) racket)
scribble/core scribble/html-properties racket/runtime-path) scribble/core scribble/html-properties racket/runtime-path)

View File

@ -1,8 +1,4 @@
;; This file was created by make-log-based-eval ;; This file was created by make-log-based-eval
((require (only-in rosette/guide/scribble/util/lifted format-opaque))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((current-bitwidth 5) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((current-bitwidth 5) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic x integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic x integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((solve (assert (> x 15))) ((solve (assert (> x 15)))
@ -14,7 +10,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((define (list-set lst idx val) ((define (list-set lst idx val)
@ -23,19 +19,8 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((list-set '(a b c) 1 'd) ((list-set '(a b c) 1 'd) ((3) 0 () 0 () () (q values (a d c))) #"" #"")
((3) ((define-symbolic* idx len integer?)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'(a d c)\n"))))
#""
#"")
((define-symbolic idx len integer?)
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
@ -43,7 +28,7 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((format-opaque "~a" (list-set lst idx 'd)) ((list-set lst idx 'd)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -52,7 +37,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(union #:size 6 #:hash -437123915298968900)\n")))) (c values c (0 (u . "{3340589848165897971:6}"))))
#"" #""
#"") #"")
((define (list-set* lst idx val) ((define (list-set* lst idx val)
@ -65,7 +50,8 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((list-set* '(a b c) 1 'd) ((list-set* '(a b c) 1 'd) ((3) 0 () 0 () () (q values (a d c))) #"" #"")
((list-set* lst idx 'd)
((3) ((3)
1 1
(((lib "rosette/guide/scribble/util/lifted.rkt") (((lib "rosette/guide/scribble/util/lifted.rkt")
@ -74,26 +60,17 @@
0 0
() ()
() ()
(c values c (0 (u . "'(a d c)\n")))) (c values c (0 (u . "{1844165362669051823:4}"))))
#""
#"")
((format-opaque "~a" (list-set* lst idx 'd))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(union #:size 4 #:hash -976833215883912073)\n"))))
#"" #""
#"") #"")
((define-values (width height) (values 5 5)) ((define-values (width height) (values 5 5))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((define-symbolic x y integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic* x y integer?)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define grid/2d (for/vector ((_ height)) (make-vector width #f))) ((define grid/2d (for/vector ((_ height)) (make-vector width #f)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
@ -114,7 +91,10 @@
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((define-symbolic idx integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic* idx integer?)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((maybe-ref '(5 6 7) idx) ((maybe-ref '(5 6 7) idx)
((3) ((3)
1 1
@ -130,7 +110,7 @@
(0 (0
(u (u
. .
"(ite\n (&& (<= 0 idx) (<= idx 1))\n (ite* (⊢ (= 0 idx) 5) (⊢ (= 1 idx) 6) (⊢ (= 2 idx) 7))\n -1)\n\n")))) "(ite (&& (<= 0 idx$0) (<= idx$0 1)) (ite* (⊢ (= 0 idx$0) 5) (⊢ (= 1 idx$0) 6) (⊢ (= 2 idx$0) 7)) -1)"))))
#"" #""
#"") #"")
((define (maybe-ref* lst idx) ((define (maybe-ref* lst idx)
@ -147,7 +127,7 @@
0 0
() ()
() ()
(c values c (0 (u . "(ite (= 0 idx) 5 (ite (= 1 idx) 6 -1))\n")))) (c values c (0 (u . "(ite (= 0 idx$0) 5 (ite (= 1 idx$0) 6 -1))"))))
#"" #""
#"") #"")
((define-values (Add Sub Sqr Nop) (values (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) ((define-values (Add Sub Sqr Nop) (values (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2)))
@ -210,12 +190,15 @@
0 0
() ()
() ()
(c values c (0 (u . "(unsat)\n")))) (c values c (0 (u . "(unsat)"))))
#"" #""
#"") #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-asserts!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((clear-terms!) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((clear-terms!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic idx integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic* idx integer?)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((list-set '(1 2 3) idx 4) ((list-set '(1 2 3) idx 4)
((3) ((3)
1 1
@ -228,10 +211,15 @@
(c (c
values values
c c
(c
(0 (u . "(ite (= 0 idx$0) 4 1)"))
c
(0 (u . "(ite (= 0 idx$0) 2 (ite (= 0 (+ -1 idx$0)) 4 2))"))
c
(0 (0
(u (u
. .
"(list\n (ite (= 0 idx) 4 1)\n (ite (= 0 idx) 2 (ite (= 0 (+ -1 idx)) 4 2))\n (ite (= 0 idx) 3 (ite (= 0 (+ -1 idx)) 3 (ite (= 0 (+ -2 idx)) 4 3))))\n\n")))) "(ite (= 0 idx$0) 3 (ite (= 0 (+ -1 idx$0)) 3 (ite (= 0 (+ -2 idx$0)) 4 3)))")))))
#"" #""
#"") #"")
((define (list-set* lst idx val) ((define (list-set* lst idx val)
@ -254,9 +242,11 @@
(c (c
values values
c c
(0 (c
(u (0 (u . "(ite (= 0 idx$0) 4 1)"))
. c
"(list\n (ite (= 0 idx) 4 1)\n (ite (= 0 (+ -1 idx)) 4 2)\n (ite (= 0 (+ -2 idx)) 4 3))\n\n")))) (0 (u . "(ite (= 0 (+ -1 idx$0)) 4 2)"))
c
(0 (u . "(ite (= 0 (+ -2 idx$0)) 4 3)")))))
#"" #""
#"") #"")

View File

@ -1,13 +1,16 @@
#lang scribble/manual #lang scribble/manual
@(require scribble/core scribble/html-properties @(require scribble/core scribble/html-properties
scribble/bnf scribble/example scribble/bnf scribble/eval
(for-label (except-in racket list-set) errortrace (for-label (except-in racket list-set) errortrace
rosette/base/core/term rosette/base/core/term
rosette/base/form/define rosette/base/form/define
rosette/query/query rosette/query/form
(only-in rosette/base/base bitvector assert for/all) rosette/base/core/union
(only-in rosette current-bitwidth)) (only-in rosette/query/finitize current-bitwidth)
(only-in rosette/base/base bitvector)
(only-in rosette/base/core/safe assert)
(only-in rosette/base/core/forall for/all))
racket/runtime-path racket/runtime-path
"../util/lifted.rkt" "../util/lifted.rkt"
(only-in "../refs.scrbl" ~cite sympro:oopsla18)) (only-in "../refs.scrbl" ~cite sympro:oopsla18))
@ -18,14 +21,15 @@
@(define-runtime-path profile.png "profile.png") @(define-runtime-path profile.png "profile.png")
@(define-runtime-path profile-xform.png "profile-xform.png") @(define-runtime-path profile-xform.png "profile-xform.png")
@(rosette-eval '(require (only-in rosette/guide/scribble/util/lifted format-opaque)))
@title[#:tag "ch:performance"]{Performance} @title[#:tag "ch:performance"]{Performance}
Rosette provides an efficient, general-purpose runtime for solver-aided programming.
This chapter describes common performance problems in But as with any other form of programming,
Rosette programs, how to solve them, and how to diagnose scaling this runtime to challenging problems involves careful design,
them with the built-in @tech{symbolic profiler}. and sometimes requires changes to code to better suit Rosette's
symbolic virtual machine (SVM) style of execution.
This chapter describes common performance problems and solutions,
as well as tools built into Rosette for diagnosing these problems.
@section[#:tag "sec:antipatterns"]{Common Performance Issues} @section[#:tag "sec:antipatterns"]{Common Performance Issues}
@ -34,51 +38,45 @@ it is often due to one of four common issues, described next.
@subsection{Integer and Real Theories} @subsection{Integer and Real Theories}
Rosette supports assertions and assumptions containing Rosette supports assertions containing symbolic values of integer or real type.
symbolic values of integer or real type. But solving queries But satisfiability solving with these types is expensive (or, in the worst case, undecidable),
with these types is expensive (or, in the worst case, and even simple queries can be unacceptably slow.
undecidable), and even simple queries can be unacceptably
slow.
One solution is to change Rosette's One solution to performance issues with assertions involving integers or reals
@racket[current-bitwidth] parameter, which controls the is Rosette's @racket[current-bitwidth] parameter,
@tech{reasoning precision} used for queries. When which controls the @tech{reasoning precision} used for queries.
@racket[current-bitwidth] is set to a value @emph{k} other When @racket[current-bitwidth] is set to a value @emph{k} other than @racket[#f],
than @racket[#f], Rosette approximates @racket[integer?] and Rosette approximates @racket[integer?] and @racket[real?] values using signed @emph{k}-bit @racket[bitvector]s.
@racket[real?] values using signed @emph{k}-bit This approximation can make assertions involving integers and reals
@racket[bitvector]s, which can make queries involving more efficiently decidable.
integers and reals faster to solve.
But this approximation is unsound and may produce results But this approximation is unsound and may produce results that are
that are incorrect under the infinite-precision semantics of incorrect under the infinite-precision semantics of integers and reals
integers and reals (while being correct under the (while being correct under the finite-precision semantics).
finite-precision semantics). For example, this program For example, this program incorrectly says that no integer greater than 15 exists,
incorrectly says that no integer greater than 15 exists, because the setting of @racket[current-bitwidth] causes it to consider only values of @racket{x}
because the setting of @racket[current-bitwidth] causes it that can be represented as a 5-bit bitvector.
to consider only values of @racket[x] that can be
represented as a 5-bit bitvector.
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(current-bitwidth 5) (current-bitwidth 5)
(define-symbolic x integer?) (define-symbolic x integer?)
(solve (assert (> x 15)))] (solve (assert (> x 15)))]
So, choosing the right reasoning precision for an So, choosing the right reasoning precision for an application involves navigating this tradeoff
application involves navigating this tradeoff between between performance and soundness.
performance and soundness.
@subsection{Algorithmic Mismatch} @subsection{Algorithmic Mismatch}
Small algorithmic changes can have a large impact on the Small algorithmic changes can have a large impact on the efficiency
efficiency of symbolic evaluation. Often, the most efficient of the symbolic evaluation performed by the Rosette SVM.
algorithm for symbolic inputs is different to the most Often, the most efficient algorithm for symbolic inputs
efficient one for concrete inputs---an @deftech{algorithmic is different to the most efficient one for concrete inputs---an
mismatch}. @deftech{algorithmic mismatch}.
For example, consider this function to set the @tt{idx}th For example,
element of a list @tt{lst} to @tt{val}: consider this function to set the @tt{idx}th element of a list @tt{lst} to @tt{val}:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(define (list-set lst idx val) (define (list-set lst idx val)
(let-values ([(front back) (split-at lst idx)]) (let-values ([(front back) (split-at lst idx)])
(append front (cons val (cdr back))))) (append front (cons val (cdr back)))))
@ -89,76 +87,70 @@ While appropriate for concrete inputs,
this function exhibits poor performance when the this function exhibits poor performance when the
inputs are symbolic: inputs are symbolic:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(define-symbolic idx len integer?) (define-symbolic* idx len integer?)
(define lst (take '(a b c) len)) (define lst (take '(a b c) len))
(eval:alts (code:line (list-set lst idx 'd) (code:comment "symbolic union with 6 parts"))
(code:line (list-set lst idx 'd) (code:comment "Symbolic union with 6 parts."))
(format-opaque "~a" (list-set lst idx 'd)))
] ]
The root cause is the @racket[split-at] operation, which The root cause is the @racket[split-at] operation,
separates the front and back of the list into different which separates the front and back of the list
variables. Because the index @racket[idx] to split at is into different variables.
symbolic, Rosette creates two @tech{symbolic unions} to Because the index @racket[idx] to split at is symbolic,
capture the possible front and back values as a function of the Rosette SVM creates two @tech{symbolic unions} to capture
@racket[idx]. Even though the possible values of the possible front and back values
@racket[front] and @racket[back] are related, this as a function of @racket[idx].
separation loses the relationship. Even though the possible values of @racket[front] and @racket[back]
are related, this separation loses the relationship.
A better implementation for symbolic inputs avoids splitting A better implementation for symbolic inputs
the list by iterating over it, updating each position avoids splitting the list by iterating over it,
depending on whether its index is equal to @tt{idx}: updating each position depending on whether its index is equal to @tt{idx}:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(define (list-set* lst idx val) (define (list-set* lst idx val)
(for/all ([lst lst]) (for/all ([lst lst])
(map (lambda (i v) (if (= idx i) val v)) (map (lambda (i v) (if (= idx i) val v))
(build-list (length lst) identity) (build-list (length lst) identity)
lst))) lst)))
(list-set* '(a b c) 1 'd) (list-set* '(a b c) 1 'd)
(eval:alts (code:line (list-set* lst idx 'd) (code:comment "smaller symbolic union with 4 parts"))
(code:line (list-set* lst idx 'd) (code:comment "Smaller symbolic union with 4 parts."))
(format-opaque "~a" (list-set* lst idx 'd)))
] ]
@subsection{Irregular Representation} @subsection{Irregular Representation}
Just as the best algorithm for symbolic inputs can differ Just as the best algorithm for symbolic inputs can differ
from that for concrete inputs (an @tech{algorithmic from that for concrete inputs (an @tech{algorithmic mismatch}),
mismatch}), so can the best data structure. Programming with so can the best data structure.
symbolic values is most efficient when data structures are Programming with symbolic values is most efficient
regular. Even though an @deftech{irregular representation} when data structures are regular;
may be more space efficient for concrete data, it can have even though an @deftech{irregular representation} may be more space efficient for concrete data,
negative performance impacts when the data is symbolic. it can have negative performance impacts when the data is symbolic.
For example, consider representing a (mutable) 2D grid data For example, consider representing a (mutable) 2D grid data structure
structure using Rosette's lifted support for using Rosette's lifted support for @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{vectors}.
@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{vectors}. The obvious representation is to use nested vectors to represent the two dimensions:
The obvious representation is to use nested
vectors to represent the two dimensions:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(define-values (width height) (values 5 5)) (define-values (width height) (values 5 5))
(define-symbolic x y integer?) (define-symbolic* x y integer?)
(define grid/2d (define grid/2d
(for/vector ([_ height]) (for/vector ([_ height])
(make-vector width #f))) (make-vector width #f)))
(vector-set! (vector-ref grid/2d y) x 'a) (vector-set! (vector-ref grid/2d y) x 'a)
] ]
This representation is inefficient when indexed with This representation is inefficient when indexed with symbolic values,
symbolic values, because the dereferences are irregular: the because the dereferences are irregular:
dereference of the @racket[y]-coordinate returns a vector, the dereference of the @racket[y]-coordinate returns a vector,
whereas the dereference of the @racket[x]-coordinate returns whereas the dereference of the @racket[x]-coordinate returns a value.
a value. This irregularity requires Rosette to perform more This irregularity requires Rosette to perform more symbolic evaluation work
symbolic evaluation work to faithfully track the usages of to faithfully track the usages of the nested vector.
the nested vector.
An alternative representation stores the entire grid in one vector, An alternative representation stores the entire grid in one vector,
indexed using simple arithmetic: indexed using simple arithmetic:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(define grid/flat (define grid/flat
(make-vector (* width height) #f)) (make-vector (* width height) #f))
(vector-set! grid/flat (+ (* y width) x) 'a) (vector-set! grid/flat (+ (* y width) x) 'a)
@ -168,38 +160,37 @@ This variant improves performance by about 2×.
@subsection{Missed Concretization} @subsection{Missed Concretization}
In addition to employing careful algorithmic and In addition to employing careful algorithmic and representational choices,
representational choices, fast solver-aided code provides as fast solver-aided code provides as much information as possible
much information as possible about the feasible choices of about the feasible choices of symbolic values.
symbolic values. Failure to make this information explicit Failure to make this information explicit
results in @deftech{missed concretization} opportunities, results in @deftech{missed concretization} opportunities, and
leading to performance degradation. these misses can cause significant performance degradation.
For example, consider the following toy procedure, For example, consider the following toy procedure,
that returns the @racket[idx]th element of the list @racket[lst], that returns the @racket[idx]th element of the list @racket[lst],
but only if @racket[idx] is 0 or 1: but only if @racket[idx] is 0 or 1:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(define (maybe-ref lst idx) (define (maybe-ref lst idx)
(if (<= 0 idx 1) (if (<= 0 idx 1)
(list-ref lst idx) (list-ref lst idx)
-1)) -1))
(define-symbolic idx integer?) (define-symbolic* idx integer?)
(maybe-ref '(5 6 7) idx) (maybe-ref '(5 6 7) idx)
] ]
This procedure has poor performance when given a symbolic This procedure has poor performance when given a symbolic index @racket[idx],
index @racket[idx], because the call to because the call to @racket[(list-ref lst idx)]
@racket[(list-ref lst idx)] passes a symbolic index, but the passes a symbolic index,
conditional establishes that the only possible values for but the conditional establishes that the only possible values for that index are 0 or 1.
that index are 0 or 1. When Rosette evaluates the When the Rosette SVM evaluates the first side of the conditional,
first side of the conditional, it does not simplify the it does not simplify the value of @racket[idx] to be only 0 or 1,
value of @racket[idx] to be only 0 or 1, and so the and so the resulting encoding creates infeasible branches
resulting encoding creates infeasible branches for cases for cases where @racket[idx] is outside that range.
where @racket[idx] is outside that range. An alternative An alternative version captures that concreteness:
version captures that concreteness:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(define (maybe-ref* lst idx) (define (maybe-ref* lst idx)
(cond [(= idx 0) (list-ref lst 0)] (cond [(= idx 0) (list-ref lst 0)]
[(= idx 1) (list-ref lst 1)] [(= idx 1) (list-ref lst 1)]
@ -214,13 +205,12 @@ for the cases where @racket[idx] is greater than 1.
Rosette includes a @deftech[#:key "symbolic profiler"]{symbolic profiler} Rosette includes a @deftech[#:key "symbolic profiler"]{symbolic profiler}
for diagnosing performance issues in solver-aided programs. for diagnosing performance issues in solver-aided programs.
The symbolic profiler instruments key metrics in Rosette's The symbolic profiler instruments key metrics in the Rosette SVM
symbolic evaluator and relates them to the performance of a and relates them to the performance of a Rosette program,
Rosette program, suggesting the locations of potential suggesting the locations of potential bottlenecks---that is, parts of
bottlenecks---i.e., parts of the program that are the program that are difficult to evaluate symbolically.
difficult to evaluate symbolically. More details about More details about symbolic profiling are available
symbolic profiling are available in the related technical in the related technical paper @~cite[sympro:oopsla18].
paper @~cite[sympro:oopsla18].
To run the symbolic profiler on a program file @nonterm{prog}, To run the symbolic profiler on a program file @nonterm{prog},
use the @exec{raco} command: use the @exec{raco} command:
@ -231,7 +221,7 @@ After executing @nonterm{prog},
the symbolic profiler produces a browser-based output summarizing the results, the symbolic profiler produces a browser-based output summarizing the results,
similar to this output: similar to this output:
@(image profile.png #:scale 0.425) @(image profile.png #:scale 0.7)
The top half of this output visualizes the evolution of the Racket call stack The top half of this output visualizes the evolution of the Racket call stack
over time. over time.
@ -240,17 +230,16 @@ with its width corresponding to the total time taken by the call.
Blue highlighted regions reflect @tech{solver} activity Blue highlighted regions reflect @tech{solver} activity
generated by Rosette @seclink["sec:queries"]{queries}. generated by Rosette @seclink["sec:queries"]{queries}.
The bottom half summarizes important metrics about the The bottom half summarizes important metrics about the SVM on a per-procedure basis.
symbolic evaluation on a per-procedure basis. Each procedure Each procedure invoked by the program has a row in the table,
invoked by the program has a row in the table, with columns with columns that measure:
that measure:
@itemlist[ @itemlist[
@item{The total @bold{time} taken by all invocations of the procedure.} @item{The total @bold{time} taken by all invocations of the procedure.}
@item{The @bold{term count}, which is the total number of @tech[#:key "symbolic term"]{symbolic terms} created by the procedure.} @item{The @bold{term count}, the total number of @tech[#:key "symbolic term"]{symbolic terms} created by the procedure.}
@item{The number of @bold{unused terms}, which are terms created but never sent to a solver.} @item{The number of @bold{unused terms}, which are terms created but never sent to a solver.}
@item{The @bold{union size}, which is the total size of all @tech[#:key "symbolic unions"]{symbolic unions} created by the procedure.} @item{The @bold{union size}, counting the total size of all @tech[#:key "symbolic unions"]{symbolic unions} created by the procedure.}
@item{The @bold{merge cases}, which is the total number of execution paths merged by Rosette within the procedure.} @item{The @bold{merge count}, summing the number of execution paths merged by the SVM within the procedure.}
] ]
Procedures are ranked by a @bold{score}, which summarizes the other data in the table. Procedures are ranked by a @bold{score}, which summarizes the other data in the table.
@ -297,14 +286,15 @@ The @exec{raco symprofile @nonterm{prog}} command accepts the following command-
@section{Walkthrough: Debugging Rosette Performance} @section{Walkthrough: Debugging Rosette Performance}
To illustrate a typical Rosette performance debugging To illustrate a typical Rosette performance debugging process,
process, consider building a small solver-aided program for consider building a small solver-aided program
verifying optimizations in a toy calculator language. First, for verifying optimizations in a toy calculator language.
we define the calculator language, in which programs are First, we define the calculator language,
lists of operations, and specify its semantics with a simple in which programs are lists of operations,
recursive interpreter: and specify its semantics
with a simple recursive interpreter:
@examples[#:eval rosette-eval #:label #f #:no-prompt @interaction[#:eval rosette-eval
(code:comment "Calculator opcodes.") (code:comment "Calculator opcodes.")
(define-values (Add Sub Sqr Nop) (define-values (Add Sub Sqr Nop)
(values (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) (values (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2)))
@ -327,12 +317,12 @@ recursive interpreter:
[(eq? op Sqr) (bvmul acc acc)] [(eq? op Sqr) (bvmul acc acc)]
[else acc]))]))] [else acc]))]))]
One potential optimization for programs in this language is One potential optimization for programs in this language
to replace subtractions with additions. The @tt{sub->add} is to replace subtractions with additions.
procedure performs this operation at a given index in a The @tt{sub->add} procedure performs this operation
program: at a given index in a program:
@examples[#:eval rosette-eval #:label #f #:no-prompt @interaction[#:eval rosette-eval
(code:comment "Functionally sets lst[idx] to val.") (code:comment "Functionally sets lst[idx] to val.")
(define (list-set lst idx val) ; Functionally sets (define (list-set lst idx val) ; Functionally sets
(match lst ; lst[idx] to val. (match lst ; lst[idx] to val.
@ -349,13 +339,14 @@ program:
(list-set prog idx (list Add (bvneg (cadr ins)))) (list-set prog idx (list Add (bvneg (cadr ins))))
prog))] prog))]
To check that this optimization is correct, we implement a To check that this optimization is correct,
tiny verification tool @tt{verify-xform} that constructs a we implement a tiny verification tool @tt{verify-xform}
symbolic calculator program of size @tt{N}, applies the that constructs a symbolic calculator program of size @tt{N},
optimization, and checks that the original and optimized applies the optimization,
programs produce the same outputs: and checks that the original and optimized programs
produce the same outputs:
@examples[#:eval rosette-eval #:label #f #:no-prompt @interaction[#:eval rosette-eval
(code:comment "Verifies the given transform for all programs of length N.") (code:comment "Verifies the given transform for all programs of length N.")
(define (verify-xform xform N) ; Verifies the given (define (verify-xform xform N) ; Verifies the given
(define P ; transform for all (define P ; transform for all
@ -372,50 +363,51 @@ programs produce the same outputs:
We can verify @tt{sub->add} for all calculator programs of size 5: We can verify @tt{sub->add} for all calculator programs of size 5:
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(verify-xform sub->add 5) (verify-xform sub->add 5)
] ]
This produces no counterexamples, as expected. which produces no counterexamples, as expected.
@(rosette-eval '(clear-vc!)) @(rosette-eval '(clear-asserts!))
@(rosette-eval '(clear-terms!)) @(rosette-eval '(clear-terms!))
@subsection{Performance Bottlenecks} @subsection{Performance Bottlenecks}
Verifying @tt{sub->add} for larger values of @tt{N} causes Verifying @tt{sub->add} for larger values of @tt{N}
the performance of @tt{verify-xform} to degrade, from a causes the performance of @tt{verify-xform} to degrade,
couple of seconds when @tt{N} = 5 to over a minute when @tt{N} from less than a second when @tt{N} = 5 to
= 20. To identify the source of this performance issue, we a dozen seconds when @tt{N} = 20.
can invoke the @tech{symbolic profiler} on the verifier, To identify the source of this performance issue,
producing the output below (after selecting the ``Collapse we can invoke the @tech{symbolic profiler} on the verifier,
solver time'' checkbox): producing the output below (after selecting the "Collapse solver time" checkbox):
@(image profile-xform.png #:scale 0.425) @(image profile-xform.png #:scale 0.7)
The symbolic profiler identifies @tt{list-set} as the The symbolic profiler identifies @tt{list-set} as the bottleneck in this program.
bottleneck in this program. The output shows that @tt{list-set} The output shows that @tt{list-set} creates many symbolic terms,
creates many symbolic terms, and performs many symbolic and performs many symbolic operations (the "Union Size" and "Merge Count" columns).
operations (the ``Union Size'' and ``Merge Cases'' columns).
The core issue here is an @tech{algorithmic mismatch}: @tt{list-set} The core issue here is an @tech{algorithmic mismatch}:
makes a recursive call guarded by a short-circuiting @tt{list-set} makes a recursive call guarded by a short-circuiting condition
condition @racket[(= idx 0)] that is symbolic when @racket[(= idx 0)] that is symbolic when @racket[idx] is unknown.
@racket[idx] is unknown. When a condition's truth value is When a condition's truth value is unknown,
unknown, Rosette must execute both branches of the the Rosette SVM must execute both branches of the conditional,
conditional, and then merge the resulting symbolic values. and then merge the two resulting values together
So, in our example, Rosette always executes the recursive under @tech{path conditions} that summarize the branching decisions
call, and each call creates a larger symbolic value that required to reach each value.
encodes the results of all the previous recursive calls. In this example, this @emph{symbolic execution}
This behavior leads to a quadratic growth in the symbolic therefore always executes the recursive call,
representation of the list returned by @tt{list-set}: and each call creates a larger path condition,
since it must summarize all the previous recursive calls.
@examples[#:eval rosette-eval #:label #f This behavior leads to a quadratic growth in the symbolic representation of the list
(define-symbolic idx integer?) returned by @tt{list-set}:
@interaction[#:eval rosette-eval
(define-symbolic* idx integer?)
(list-set '(1 2 3) idx 4)] (list-set '(1 2 3) idx 4)]
The solution is to alter @tt{list-set} to recurse unconditionally: The solution is to alter @tt{list-set} to recurse unconditionally:
@examples[#:eval rosette-eval #:label #f #:no-prompt @interaction[#:eval rosette-eval
(define (list-set* lst idx val) (define (list-set* lst idx val)
(match lst (match lst
[(cons x xs) [(cons x xs)
@ -423,12 +415,12 @@ The solution is to alter @tt{list-set} to recurse unconditionally:
(list-set* xs (- idx 1) val))] (list-set* xs (- idx 1) val))]
[_ lst]))] [_ lst]))]
In this revision, Rosette still evaluates both branches In this revision, the SVM still evaluates both branches
of the conditional, but neither side of the conditional recurses, of the conditional, but neither side of the conditional recurses,
and so the verification conditions no longer grow quadratically. and so the path conditions no longer grow quadratically.
@examples[#:eval rosette-eval #:label #f @interaction[#:eval rosette-eval
(list-set* '(1 2 3) idx 4)] (list-set* '(1 2 3) idx 4)]
The performance of @tt{verify-xform} after this change The performance of @tt{verify-xform} after this change
improves by 3× for @tt{N} = 20. improves by 2× for @tt{N} = 20.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

@ -1,62 +0,0 @@
#lang rosette
(define-values (Add Sub Sqr Nop)
(values (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2)))
(define (calculate prog [acc (bv 0 4)])
(cond ; An interpreter for
[(null? prog) acc] ; calculator programs.
[else ; A program is list of
(define ins (car prog)) ; '(op) or '(op arg)
(define op (car ins)) ; instructions that up-
(calculate ; date acc, where op is
(cdr prog) ; a 2-bit opcode and arg
(cond ; is a 4-bit constant.
[(eq? op Add) (bvadd acc (cadr ins))]
[(eq? op Sub) (bvsub acc (cadr ins))]
[(eq? op Sqr) (bvmul acc acc)]
[else acc]))]))
(define (list-set lst idx val) ; Functionally sets
(match lst ; lst[idx] to val.
[(cons x xs)
(if (= idx 0)
(cons val xs)
(cons x (list-set xs (- idx 1) val)))]
[_ lst]))
(define (list-set* lst idx val)
(match lst
[(cons x xs)
(cons (if (= idx 0) val x)
(list-set* xs (- idx 1) val))]
[_ lst]))
(define (sub->add prog idx list-set)
(define ins (list-ref prog idx))
(if (eq? (car ins) Sub)
(list-set prog idx (list Add (bvneg (cadr ins))))
prog))
(define (verify-xform xform N list-set)
(define P
(for/list ([i N])
(define-symbolic* op (bitvector 2))
(define-symbolic* arg (bitvector 4))
(if (eq? op Sqr) (list op) (list op arg))))
(define-symbolic* acc (bitvector 4))
(define-symbolic* idx integer?)
(define xP (xform P idx list-set))
(verify
(assert (eq? (calculate P acc) (calculate xP acc)))))
(module+ slow
(clear-terms!)
(clear-vc!)
(time (verify-xform sub->add 20 list-set)))
(module+ fast
(clear-terms!)
(clear-vc!)
(time (verify-xform sub->add 20 list-set*)))

View File

@ -1,436 +0,0 @@
;; This file was created by make-log-based-eval
((require (only-in rosette/guide/scribble/util/lifted opaque))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define-symbolic a b c d boolean?)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((terms)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list a c d b)\n"))))
#""
#"")
((assume a) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc a #t)\n"))))
#""
#"")
((if b
(begin
(printf "Then branch:\n ~a\n" (vc))
(assert c)
(printf " ~a\n" (vc))
1)
(begin
(printf "Else branch:\n ~a\n" (vc))
(assert d)
(printf " ~a\n" (vc))
2))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(ite b 1 2)\n"))))
#"Then branch:\n #(struct:vc (&& a b) #t)\n #(struct:vc (&& a b) (|| c (! (&& a b))))\nElse branch:\n #(struct:vc (&& a (! b)) #t)\n #(struct:vc (&& a (! b)) (|| d (! (&& a (! b)))))\n"
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(vc a (&& (|| (! b) (|| c (! (&& a b)))) (|| b (|| d (! (&& a (! b)))))))\n"))))
#""
#"")
((append (take (terms) 5) (list (opaque "...")))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(list\n a\n c\n d\n b\n (&& (|| (! b) (|| c (! (&& a b)))) (|| b (|| d (! (&& a (! b))))))\n ...)\n\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
(vc-true
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((vc? vc-true) ((3) 0 () 0 () () (q values #t)) #"" #"")
((vc-assumes vc-true) ((3) 0 () 0 () () (q values #t)) #"" #"")
((vc-asserts vc-true) ((3) 0 () 0 () () (q values #t)) #"" #"")
((vc-true? vc-true) ((3) 0 () 0 () () (q values #t)) #"" #"")
((vc-true? 1) ((3) 0 () 0 () () (q values #f)) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((define-symbolic a b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((assume a) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc a #t)\n"))))
#""
#"")
((assert b) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc a (|| b (! a)))\n"))))
#""
#"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc #t #t)\n"))))
#""
#"")
((define-symbolic a b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((assume a) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc a #t)\n"))))
#""
#"")
((with-vc (begin (when b (error 'b "No b allowed!")) 1))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(normal 1 (vc a (|| (! b) (! (&& a b)))))\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc a #t)\n"))))
#""
#"")
((with-vc vc-true (if b (assume #f) (assert #f)))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c
values
c
(0
(u
.
"(failed\n (exn:fail:svm:merge \"[merge] failed\" #<continuation-mark-set>)\n (vc (! b) b))\n\n"))))
#""
#"")
((vc)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(vc a #t)\n"))))
#""
#"")
((clear-terms!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((clear-vc!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((terms)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'()\n"))))
#""
#"")
((define (get-a) (define-symbolic a integer?) a)
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define a0 (get-a)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((+ a0 3)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(+ 3 a)\n"))))
#""
#"")
((terms)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list a (+ 3 a))\n"))))
#""
#"")
((define (query-a a) (verify (assert (= a (get-a)))))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((query-a a0)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(unsat)\n"))))
#""
#"")
((terms)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list a (+ 3 a))\n"))))
#""
#"")
((clear-terms!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((terms)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'()\n"))))
#""
#"")
((query-a a0)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [a 1]\n [a 0])\n"))))
#""
#"")
((terms)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list a (! (= a a)) (= a a))\n"))))
#""
#"")
((clear-terms!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((require (only-in racket collect-garbage))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((define (unused-terms! n)
(define-symbolic* a integer?)
(let loop ((n n)) (if (<= n 1) a (+ a (loop (- n 1)))))
(void))
((3) 0 () 0 () () (c values c (void)))
#""
#"")
((length (terms)) ((3) 0 () 0 () () (q values 0)) #"" #"")
((time (unused-terms! 50000))
((3) 0 () 0 () () (c values c (void)))
#"cpu time: 357 real time: 365 gc time: 17\n"
#"")
((length (terms)) ((3) 0 () 0 () () (q values 50000)) #"" #"")
((collect-garbage) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((length (terms)) ((3) 0 () 0 () () (q values 50000)) #"" #"")
((clear-terms!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((collect-garbage) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((gc-terms!) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((length (terms)) ((3) 0 () 0 () () (q values 0)) #"" #"")
((time (unused-terms! 50000))
((3) 0 () 0 () () (c values c (void)))
#"cpu time: 410 real time: 416 gc time: 40\n"
#"")
(50000 ((3) 0 () 0 () () (q values 50000)) #"" #"")
((collect-garbage) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((length (terms)) ((3) 0 () 0 () () (q values 0)) #"" #"")
((define-symbolic a b integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((terms)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list b a)\n"))))
#""
#"")
((with-terms (begin0 (verify (assert (= (+ a b) 0))) (println (terms))))
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(model\n [a 1]\n [b 0])\n"))))
#"(list (+ a b) b a (! (= 0 (+ a b))) (= 0 (+ a b)))\n"
#"")
((terms)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list b a)\n"))))
#""
#"")
((with-terms
(list)
(begin (println (terms)) (define-symbolic c integer?) (println (terms))))
((3) 0 () 0 () () (c values c (void)))
#"'()\n(list c)\n"
#"")
((terms)
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "(list b a)\n"))))
#""
#"")

View File

@ -2,351 +2,130 @@
@(require (for-label @(require (for-label
rosette/query/query rosette/base/form/define rosette/query/query rosette/base/form/define
(only-in rosette/base/base rosette/base/core/term
assert assume term? (only-in rosette/base/core/bool pc asserts clear-asserts! with-asserts with-asserts-only)
vc vc? vc-assumes vc-asserts (only-in rosette/base/core/safe assert)
clear-vc! with-vc vc-true vc-true?
result? result-value result-state
normal normal? failed failed?
terms with-terms clear-terms! gc-terms!)
racket) racket)
scribble/core scribble/html-properties scribble/example racket/sandbox scribble/core scribble/html-properties scribble/eval racket/sandbox
racket/runtime-path
"../util/lifted.rkt") "../util/lifted.rkt")
@(require (only-in "../refs.scrbl" ~cite rosette:pldi14))
@(define rosette-eval (rosette-evaluator))
@(define-runtime-path root ".")
@(define rosette-eval (rosette-log-evaluator (logfile root "state-log")))
@title[#:tag "sec:state-reflection"]{Reflecting on Symbolic State} @title[#:tag "sec:state-reflection"]{Reflecting on Symbolic State}
Like standard program execution, Rosette's symbolic evaluation @~cite[rosette:pldi14] can be
understood as a sequence of transitions from one @deftech{program state} to the next. In
addition to the memory and register values, the state of a Rosette program also includes
the current @deftech{path condition} and the current @deftech{assertion store}. The path
condition is a boolean value encoding the branch decisions taken to reach the present state,
and the assertion store is the set of boolean values (i.e., constraints) that have been asserted
so far. This section describes the built-in facilities for accessing and modifying various
aspects of the symbolic state from within a Rosette program. In general, these facilities should
only be used by expert Rosette programmers to implement low-level solver-aided tools.
Like standard program execution, Rosette's @tech{symbolic @declare-exporting[rosette/base/base #:use-sources (rosette/base/core/bool rosette/base/core/term)]
evaluation} can be viewed as a function that operates on
@deftech[#:key "program state"]{program states}. The state
of a Rosette program includes its Racket state (memory,
environment, and so on) and its symbolic state. The key
parts of the symbolic state are the current @deftech{verification condition}
(VC) and the current @deftech{symbolic heap}.
The heap consists of all the @tech[#:key "symbolic term"]{symbolic terms}
created so far. The verification condition tracks all the
@racketlink[assume]{assumptions} and @racketlink[assert]{assertions}
issued on @emph{any} path between the starting program state
and the current state. Unlike concrete execution, which
evaluates just one path through a program, Rosette evaluates
all possible paths when the decision on which path to take
depends on a symbolic value. The symbolic heap and the
verification condition reflect the current state of this
all-path evaluation.
@(rosette-eval '(require (only-in rosette/guide/scribble/util/lifted opaque)))
@examples[#:eval rosette-eval #:label #f #:no-prompt
(define-symbolic a b c d boolean?)
(code:line (terms) (code:comment "Symbolic heap."))
(assume a)
(code:line (vc) (code:comment "Verification condition."))
(if b
(begin
(printf "Then branch:\n ~a\n" (vc))
(assert c)
(printf " ~a\n" (vc))
1)
(begin
(printf "Else branch:\n ~a\n" (vc))
(assert d)
(printf " ~a\n" (vc))
2))
(vc)
(eval:alts
(terms)
(append (take (terms) 5) (list (opaque "..."))))]
This section describes the built-in facilities for accessing
and modifying various aspects of the symbolic state from
within a Rosette program. These facilites are useful for
low-level debugging and optimizations of Rosette-based
tools. But they also make it possible to
violate the state invariants maintained by Rosette, so it is
best to use them sparingly.
@declare-exporting[rosette/base/base
#:use-sources
(rosette/base/base)]
@(rosette-eval '(clear-vc!))
@section[#:tag "sec:vc"]{Verification Conditions}
@deftogether[(@defproc[(vc? [v any/c]) boolean?]
@defproc[(vc-assumes [v vc?]) boolean?]
@defproc[(vc-asserts [v vc?]) boolean?])]{
A verification condition (VC) is a structure of type
@racket[vc?]. It consists of two (concrete or symbolic)
boolean values that reflect the assumptions and assertions
issued during symbolic evaluation. Programs can access these
values using @racket[vc-assumes] and @racket[vc-asserts]. }
@deftogether[(@defthing[vc-true vc?]
@defproc[(vc-true? [v any/c]) boolean?])]{
The true verification condition, @racket[vc-true], is a
value of type @racket[vc?] that consists of a true
assumption and assertion. The true verification condition
is recognized by @racket[vc-true?].
@examples[#:eval rosette-eval
vc-true
(vc? vc-true)
(vc-assumes vc-true)
(vc-asserts vc-true)
(vc-true? vc-true)
(vc-true? 1)]
}
@defproc[(vc) vc?]{
The current @tech{verification condition}, @racket[(vc)],
is a value of type @racket[vc?]. At the start of evaluation,
@racket[(vc)] has the value @racket[vc-true]. As the
evaluation progresses along all paths, the current
@racket[(vc)] accumulates all the assertions and assumptions
issued on these paths.
The current @racket[(vc)] state can be cleared using
@racket[clear-vc!]. This resets @racket[(vc)] to its
starting value, @racket[vc-true].
@defproc[(pc) boolean?]{
Returns the current path condition.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(vc)
(define-symbolic a b boolean?) (define-symbolic a b boolean?)
(vc) (if a
(assume a) (if b
(vc) #f
(pc))
#f)]
}
@defproc[(asserts) (listof boolean?)]{
Returns the contents of the @tech["assertion store"] as a list of symbolic
boolean values (or @racket[#f]) that have been asserted so far.
@examples[#:eval rosette-eval
(define-symbolic a b boolean?)
(assert a)
(asserts)
(assert b) (assert b)
(vc) (asserts)]
(clear-vc!)
(vc)]
} }
@(rosette-eval '(clear-asserts!))
@defproc[(clear-vc!) void?]{ @defproc[(clear-asserts!) void?]{
Clears the current verification condition by setting it to @racket[vc-true]. Clears the @tech["assertion store"] from all symbolic
See @racket[vc] for details. boolean values (or @racket[#f]) that have been asserted so far.
} Rosette programs @emph{should not clear} the assertion
store unless they are implementing low-level tools.
@defform*[((with-vc expr)
(with-vc vc-expr expr))
#:contracts ([vc-expr vc?])]{
Evaluates @racket[expr] with @racket[(vc)] set to
@racket[vc-expr], returns the result, and restores
@racket[(vc)] to its old value. If @racket[vc-expr] is not
given, it defaults to @racket[(vc)], so
@racket[(with-vc expr)] is equivalent to
@racket[(with-vc (vc) expr)].
The result of a @racket[with-vc] expression is a value of
type @racket[result?]. If @racket[expr] terminates normally,
then the result is @racket[normal?], and its
@racket[result-value] contains the value computed by
@racket[expr]. If @racket[expr] fails, the result is
@racket[failed?], and its @racket[result-value] contains an
@racket[exn:fail?] exception that represents the cause of
the abnormal termination. In either case,
@racket[result-state] holds the final verification condition
generated during the evaluation of @racket[expr], starting
with @racket[vc-expr], or @racket[(vc)], as the initial
verification condition.
In the context of symbolic evaluation, @racket[expr]
terminates normally if it has at least one path of execution
that terminates normally. A path terminates normally if it
is free of any assumption or assertion violations---i.e., any
@racket[(assume #, @var{e})] or @racket[(assert #, @var{e})]
expressions where @var{e} evaluates to @racket[#f]. Failures
due to exceptions are treated as assertion violations.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic a b boolean?) (define-symbolic a b boolean?)
(assume a) (assert a)
(vc) (assert b)
(code:comment "Normal termination, starting with (vc).") (asserts)
(code:comment "The error along the path where b is true") (clear-asserts!)
(code:comment "is treated as an assertion violation.") (asserts)]
(with-vc
(begin
(when b
(error 'b "No b allowed!"))
1))
(code:line (vc) (code:comment "(vc) is unchanged."))
(code:comment "Abnormal termination, starting with vc-true.")
(with-vc vc-true
(if b
(assume #f)
(assert #f)))
(code:line (vc) (code:comment "(vc) is unchanged."))]
} }
@deftogether[(@defproc[(result? [v any/c]) boolean?] @(rosette-eval '(clear-asserts!))
@defproc[(result-value [v result?]) any/c] @defform[(with-asserts expr)]{
@defproc[(result-state [v result?]) any/c] Returns two values: the result of evaluating @racket[expr] and the assertions
@defproc[(normal? [v any/c]) boolean?] generated during the evaluation of @racket[expr]. These
@defproc[(failed? [v any/c]) boolean?])]{ assertions will not appear in the assertion store after
@racket[with-asserts] returns.
A @racket[result?] value represents the result of symbolic
evaluation, which includes an output value,
@racket[(result-value v)], and a part of the output state,
@racket[(result-state v)]. Every result is either
@racket[normal?], if the evaluation terminated normally, or
@racket[failed?] otherwise.
See also @racket[with-vc].
}
@section[#:tag "sec:heap"]{Symbolic Heap}
@(rosette-eval '(clear-terms!))
@(rosette-eval '(clear-vc!))
@defproc[(terms) (listof term?)]{
Returns a list of all @seclink["sec:symbolic-terms"]{symbolic terms}
generated during symbolic evaluation. These
terms form the contents of the current @tech{symbolic heap}.
Rosette uses the symbolic heap to ensure that no
syntactically identical terms are created.
The symbolic heap is not garbage collected, which means
that long running applications may experience memory
pressure. There are two ways to solve this issue, depending
on the application's behavior.
If the application is performing many independent queries
that do @emph{not} share @emph{any} symbolic constants, then
the application may safely clear the heap between
query invocations using @racket[clear-terms!]. This is the
most common scenario.
If the application is solving a series of related queries,
using @racket[clear-terms!] can lead to incorrect behavior.
In this scenario, the safe solution is to use a
garbage-collected heap by invoking @racket[(gc-terms!)].
This has the effect of changing the internal representation
of the heap to use a more expensive data structure that
cooperates with Racket's garbage collector. Because of the
added runtime overhead, this setting is best saved for when
the default heap consumes too much memory.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(terms) (define-symbolic a b boolean?)
(define (get-a) (define-values (result asserted)
(define-symbolic a integer?) (with-asserts
a) (begin (assert a)
(define a0 (get-a)) (assert b)
(+ a0 3) 4)))
(terms) (printf "result = ~a\n" result)
(code:comment "In the following scenario, using clear-terms! leads to") (printf "asserted = ~a\n" asserted)
(code:comment "incorrect behavior: (query-a a0) should always return") (asserts)
(code:comment "unsat? according to the semantics of define-symbolic.") ]
(code:comment "But if the heap is cleared, it returns sat? because a0")
(code:comment "is bound to a constant that no longer exists in the heap.")
(define (query-a a)
(verify
(assert (= a (get-a)))))
(query-a a0)
(terms)
(clear-terms!)
(terms)
(code:line (query-a a0) (code:comment "Wrong result."))
(terms)]
} }
@defform[(with-asserts-only expr)]{
@defproc[(clear-terms! [ts (or/c #f (listof term?)) #f]) void?]{ Like @racket[with-asserts], but returns only the assertions generated
during the evaluation of @racket[expr].
Clears the symbolic heap of all terms if @racket[ts] is @examples[#:eval rosette-eval
false; otherwise, clears all the terms in @racket[ts] and (define-symbolic a b boolean?)
any expressions that transitively contain them. Clearing the (with-asserts-only
heap is @emph{not} safe in general; see @racket[terms] for (begin (assert a)
details and examples. } (assert b)
4))
]
}
@(rosette-eval '(clear-terms!)) @(rosette-eval '(clear-terms!))
@(rosette-eval '(require (only-in racket collect-garbage))) @defparam[term-cache h hash?]{
@defproc[(gc-terms!) void?]{ A parameter that holds the cache of all @seclink["sec:symbolic-terms"]{symbolic terms}
constructed during symbolic evaluation. This cache stores terms for the purposes of
Changes the internal representation of the symbolic heap to partial cannonicalization. In particular, Rosette uses the term cache to ensure that no
a data structure that cooperates with Racket's garbage syntactically identical terms are created. Rosette programs
collector. The resulting heap is initialized with the @emph{should not modify} the term cache unless they are implementing low-level tools.
reachable terms from the current symbolic heap, as given by @examples[#:eval rosette-eval
@racket[(terms)]. This setting ensures that unused terms are (pretty-print (term-cache))
garbage-collected throughout symbolic evaluation. (define-symbolic a b c d integer?)
(pretty-print (term-cache))
@examples[#:eval rosette-eval (* d (- (+ a b) c))
(code:comment "Creates n unreachable terms of the form (+ a (+ a ...)).") (pretty-print (term-cache))]
(define (unused-terms! n)
(define-symbolic* a integer?)
(let loop ([n n])
(if (<= n 1)
a
(+ a (loop (- n 1)))))
(void))
(code:line (length (terms)) (code:comment "Empty heap."))
(time (unused-terms! 50000))
(length (terms))
(collect-garbage)
(code:line (length (terms)) (code:comment "GC has no effect on the default heap."))
(clear-terms!)
(collect-garbage)
(code:line (gc-terms!) (code:comment "Use gc-terms! to change the representation."))
(length (terms))
(time (unused-terms! 50000))
(eval:alts (length (terms)) 50000)
(collect-garbage)
(code:line (length (terms)) (code:comment "The heap now cooperates with GC."))
]
} }
@(rosette-eval '(clear-terms!))
@defform*[((with-terms expr) @defproc[(clear-terms! [terms (or/c #f (listof term?)) #f]) void?]{
(with-terms terms-expr expr)) Clears the entire term-cache if invoked with @racket[#f] (default), or
#:contracts ([terms-expr (listof term?)])]{ it evicts all of the given @racket[terms] as well as any expressions that transitively
contain them. Rosette programs @emph{should not clear} the term cache unless they are
Evaluates @racket[expr] with @racket[(terms)] set to implementing low-level tools.
@racket[terms-expr], returns the result, and restores @examples[#:eval rosette-eval
@racket[(terms)] to the value it held before the evaluation (term-cache)
of @racket[expr]. If @racket[terms-expr] is not given, it (define-symbolic a b c d integer?)
defaults to @racket[(terms)], so @racket[(with-terms expr)] (pretty-print (term-cache))
is equivalent to @racket[(with-terms (terms) expr)]. (* d (- (+ a b) c))
(pretty-print (term-cache))
The result of a @racket[with-terms] expression is the output (clear-terms! (list c d))
of @racket[expr]. (pretty-print (term-cache))
(clear-terms!)
Note that none of the terms created during the evaluation (pretty-print (term-cache))]
of @racket[expr] are preserved in the heap, and having those
terms escape the dynamic extent of @racket[expr] is usually
a sign of a programming error. It can lead to incorrect
behavior, similarly to using @racket[clear-terms!].
@examples[#:eval rosette-eval
(define-symbolic a b integer?)
(terms)
(with-terms
(begin0
(verify (assert (= (+ a b) 0)))
(println (terms))))
(terms)
(with-terms (list) ; Empty heap
(begin
(println (terms))
(define-symbolic c integer?)
(println (terms))))
(terms)]
} }
@(kill-evaluator rosette-eval) @(kill-evaluator rosette-eval)

View File

@ -5,7 +5,7 @@
@title[#:tag "ch:symbolic-reflection" #:style 'toc]{Symbolic Reflection} @title[#:tag "ch:symbolic-reflection" #:style 'toc]{Symbolic Reflection}
This chapter describes @deftech{symbolic reflection}, a This chapter describes @deftech{symbolic reflection}, a convenient
mechanism for manipulating the representation of symbolic values mechanism for manipulating the representation of symbolic values
(Section @seclink["sec:value-reflection"]{7.1}) and (Section @seclink["sec:value-reflection"]{7.1}) and
the state of the symbolic evaluation from within a Rosette program the state of the symbolic evaluation from within a Rosette program

View File

@ -0,0 +1,23 @@
#lang rosette
(define-symbolic b boolean?)
(define v (vector 1))
(define w (vector 2 3))
(define s (if b v w))
s
(type-of s)
(eq? s v)
(eq? s w)
(define u (if b '(1 2) 3))
u
(type-of u)
(define (test)
(define-symbolic c boolean?)
(define v (if c #t 0))
(define u (if b (vector v) 4))
(list v u))
(test)
(union-contents u)

View File

@ -2,12 +2,15 @@
@(require (for-label @(require (for-label
rosette/solver/solver rosette/solver/solution rosette/query/query rosette/solver/solver rosette/solver/solution rosette/query/query
rosette/base/form/define rosette/base/form/define rosette/query/eval (only-in rosette/base/base bitvector ~>)
rosette/base/core/term rosette/base/core/type rosette/base/core/union rosette/base/core/term rosette/base/core/type rosette/base/core/union
(only-in rosette/base/base vc clear-vc! bitvector ~>) (only-in rosette/base/core/bool asserts)
(only-in rosette/base/core/reflect symbolics concrete? symbolic?) (only-in rosette/base/core/reflect symbolics)
rosette/base/core/forall racket) rosette/base/core/forall rosette/lib/lift ;rosette/lib/match
scribble/core scribble/html-properties scribble/example racket/sandbox (only-in rosette/base/core/safe assert)
racket)
;(except-in racket match match* match-lambda))
scribble/core scribble/html-properties scribble/eval racket/sandbox
"../util/lifted.rkt") "../util/lifted.rkt")
@ -15,37 +18,30 @@
@title[#:tag "sec:value-reflection"]{Reflecting on Symbolic Values} @title[#:tag "sec:value-reflection"]{Reflecting on Symbolic Values}
There are two kinds of symbolic values in Rosette: symbolic There are two kinds of symbolic values in Rosette: symbolic terms and
terms and symbolic unions. A Rosette program can inspect the symbolic unions. A Rosette program can inspect the representation of
representation of both kinds of values. This is useful for both kinds of values. This is useful for @tech[#:key "lifted constructs"]{lifting} additional
@tech[#:key "lifted constructs"]{lifting} additional (unlifted) Racket procedures to work on symbolic values, and for
(unlifted) Racket procedures to work on symbolic values, and controlling the performance of Rosette's symbolic evaluator.
for controlling the performance of Rosette's symbolic
evaluator.
@section[#:tag "sec:symbolic-terms"]{Symbolic Terms} @section[#:tag "sec:symbolic-terms"]{Symbolic Terms}
@declare-exporting[rosette/base/core/term rosette/base/core/reflect @declare-exporting[rosette/base/core/term rosette/base/core/reflect #:use-sources (rosette/base/core/type rosette/base/core/term rosette/base/core/reflect)]
#:use-sources (rosette/base/core/type rosette/base/core/term rosette/base/core/reflect)]
A @deftech{symbolic term} is either a symbolic constant, A @deftech{symbolic term} is either a symbolic constant, created via
created via @seclink["sec:symbolic-constants"]{@code{define-symbolic[*]}}, @seclink["sec:symbolic-constants"]{@code{define-symbolic[*]}},
or a symbolic expression, produced by a lifted operator. or a symbolic expression, produced by a lifted operator.
Terms are strongly typed, and always belong to a @tech{solvable type}. Terms are strongly typed, and always belong to a @tech{solvable type}.
Symbolic values of all other Symbolic values of all other (@tech[#:key "unsolvable type"]{unsolvable}) types take the form of
(@tech[#:key "unsolvable type"]{unsolvable}) types take the @seclink["sec:symbolic-unions"]{symbolic unions}.
form of @seclink["sec:symbolic-unions"]{symbolic unions}.
@deftogether[(@defproc[(term? [v any/c]) boolean?] @deftogether[(@defproc[(term? [v any/c]) boolean?]
@defproc[(expression? [v any/c]) boolean?] @defproc[(expression? [v any/c]) boolean?]
@defproc[(constant? [v any/c]) boolean?])]{ @defproc[(constant? [v any/c]) boolean?])]{
Predicates for recognizing symbolic terms, expressions, and constants, respectively.
Predicates for recognizing symbolic terms, expressions, and
constants, respectively.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(code:line (define-symbolic x integer?) (code:comment "Constant.")) (code:line (define-symbolic x integer?) (code:comment "constant"))
(code:line (define e (+ x 1)) (code:comment "Expression.")) (code:line (define e (+ x 1)) (code:comment "expression"))
(list (term? x) (term? e)) (list (term? x) (term? e))
(list (constant? x) (constant? e)) (list (constant? x) (constant? e))
(list (expression? x) (expression? e)) (list (expression? x) (expression? e))
@ -56,13 +52,10 @@ form of @seclink["sec:symbolic-unions"]{symbolic unions}.
@deftogether[(@defform[(term content type)] @deftogether[(@defform[(term content type)]
@defform[(expression op child ...+)] @defform[(expression op child ...+)]
@defform[(constant id type)])]{ @defform[(constant id type)])]{
Pattern matching forms for symbolic terms, expressions, and constants, respectively.
Pattern matching forms for symbolic terms, expressions, and
constants, respectively.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(code:line (define-symbolic x integer?) (code:comment "Constant.")) (code:line (define-symbolic x integer?) (code:comment "constant"))
(code:line (define e (+ x 1)) (code:comment "Expression.")) (code:line (define e (+ x 1)) (code:comment "expression"))
(match x (match x
[(constant identifier type) (list identifier type)]) [(constant identifier type) (list identifier type)])
(match x (match x
@ -73,41 +66,19 @@ form of @seclink["sec:symbolic-unions"]{symbolic unions}.
[(term content type) (list content type)])]} [(term content type) (list content type)])]}
@defproc[(symbolics [v any/c]) (listof constant?)]{ @defproc[(symbolics [v any/c]) (listof constant?)]{
Returns all symbolic constants that are Returns all symbolic constants that are
transitively contained in the given value. transitively contained in the given value.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x y z integer?) (define-symbolic x y z integer?)
(symbolics x) (symbolics x)
(symbolics (if (= x y) 2 #f)) (symbolics (if (= x y) 2 #f))
(symbolics (list y z y)) (symbolics (list y z y))
(struct cell (value) #:transparent) (struct cell (value) #:transparent)
(symbolics (list (vector (box 1) 3) (cell (cons 4 5))))
(symbolics (list 5 (cons (box y) z) (vector (cell x) z)))] (symbolics (list 5 (cons (box y) z) (vector (cell x) z)))]
} }
@defproc[(concrete? [v any/c]) boolean?]{
Equivalent to @racket[(null? (symbolics v))] but more efficient.
@examples[#:eval rosette-eval
(define-symbolic x y z integer?)
(concrete? x)
(concrete? (if (= x y) 2 #f))
(concrete? (list y z y))
(struct cell (value) #:transparent)
(concrete? (list (vector (box 1) 3) (cell (cons 4 5))))
(concrete? (list 5 (cons (box y) z) (vector (cell x) z)))]
}
@defproc[(symbolic? [v any/c]) boolean?]{
Equivalent to @racket[(not (concrete? v))].
}
@defproc*[([(type-of [v any/c] ...+) type?])]{ @defproc*[([(type-of [v any/c] ...+) type?])]{
Returns the most specific @racket[type?] predicate that accepts all of the given values.
Returns the most specific @racket[type?] predicate that
accepts all of the given values.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic x integer?) (define-symbolic x integer?)
(type-of x) (type-of x)
@ -118,12 +89,8 @@ Equivalent to @racket[(not (concrete? v))].
} }
@defproc[(type? [v any/c]) boolean?]{ @defproc[(type? [v any/c]) boolean?]{
Returns true when given a predicate that recognizes a @seclink["ch:built-in-datatypes"]{built-in} or
Returns true when given a predicate that recognizes a a @seclink["ch:programmer-defined-datatypes"]{structure} type. Otherwise returns false.
@seclink["ch:built-in-datatypes"]{built-in} or a
@seclink["ch:programmer-defined-datatypes"]{structure} type.
Otherwise returns false.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(type? integer?) (type? integer?)
(type? boolean?) (type? boolean?)
@ -135,15 +102,12 @@ Equivalent to @racket[(not (concrete? v))].
} }
@defproc[(solvable? [v any/c]) boolean?]{ @defproc[(solvable? [v any/c]) boolean?]{
Returns true if @racket[v] is a type predicate for a @tech{solvable type}.
Returns true if @racket[v] is a type predicate for a @tech{solvable type}.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(solvable? boolean?) (solvable? boolean?)
(solvable? integer?) (solvable? integer?)
(solvable? real?) (solvable? real?)
(solvable? (bitvector 8)) (solvable? (~> (bitvector 3) (bitvector 4)))
(solvable? (~> (bitvector 32) (bitvector 64)))
(solvable? list?) (solvable? list?)
(struct circle (radius)) (struct circle (radius))
(solvable? circle?) (solvable? circle?)
@ -154,20 +118,14 @@ Equivalent to @racket[(not (concrete? v))].
@declare-exporting[rosette/base/core/union #:use-sources (rosette/base/core/union)] @declare-exporting[rosette/base/core/union #:use-sources (rosette/base/core/union)]
Rosette represents symbolic values of an Rosette represents symbolic values of an @tech[#:key "unsolvable type"]{unsolvable type}
@tech[#:key "unsolvable type"]{unsolvable type} (e.g., (e.g., @racket[list?]) as @deftech[#:key "symbolic union"]{symbolic unions}.
@racket[list?]) as @deftech[#:key "symbolic union"]{symbolic unions}. A symbolic union is a set of two or more @deftech[#:key "guarded value"]{guarded values}.
A symbolic union is a set of two or more A guarded value, in turn, combines a guard, which is a symbolic @racket[boolean?] term,
@deftech[#:key "guarded value"]{guarded values}. A guarded and a (non-union) value. Rosette's symbolic evaluator guarantees that the guards in
value, in turn, combines a guard, which is a symbolic a symbolic union are disjoint: only one of them can ever be true. For example, the
@racket[boolean?] term, and a (non-union) value. Rosette's symbolic vector @racket[s] defined below is represented as a symbolic union of two guarded vectors:
symbolic evaluator guarantees that the guards in a symbolic @interaction[#:eval rosette-eval
union are disjoint: only one of them can ever be true. For
example, the symbolic vector @racket[s] defined below is
represented as a symbolic union of two guarded vectors:
@examples[#:eval rosette-eval #:label #f
(define-symbolic b boolean?) (define-symbolic b boolean?)
(define v (vector 1)) (define v (vector 1))
(define w (vector 2 3)) (define w (vector 2 3))
@ -177,39 +135,27 @@ s
(eq? s v) (eq? s v)
(eq? s w)] (eq? s w)]
The values that appear in a union are themselves never The values that appear in a union are themselves never unions. They may, however, contain unions.
unions. They may, however, contain unions. They may also They may also belong to several different types. In that case, the type of the union is the most
belong to several different types. In that case, the type of specific @racket[type?] predicate that accepts all members of the union.
the union is the most specific @racket[type?] predicate that This will always be an unsolvable type---possibly @racket[any/c], the most general unsolvable type.
accepts all members of the union. This will always be an @interaction[#:eval rosette-eval
unsolvable type---possibly @racket[any/c], the most general
unsolvable type.
@examples[#:eval rosette-eval #:label #f
(define-symbolic b boolean?) (define-symbolic b boolean?)
(define-symbolic c boolean?) (define-symbolic c boolean?)
(define v (if c "c" 0)) (define v (if c "c" 0))
(define u (if b (vector v) 4)) (define u (if b (vector v) 4))
u u
(type-of u) (type-of u)]
(union-contents u)]
Symbolic unions are recognized by the @racket[union?] Symbolic unions are recognized by the @racket[union?] predicate, and Rosette programs can inspect
predicate, and Rosette programs can inspect union contents union contents using the @racket[union-contents] procedure. Applications may use @racket[union?] and
using the @racket[union-contents] procedure. Applications @racket[union-contents] directly to @tech[#:key "lifted constructs"]{lift} Racket code to work on
may use @racket[union?] and @racket[union-contents] directly symbolic unions, but Rosette also provides dedicated lifting constructs, described in
to @tech[#:key "lifted constructs"]{lift} Racket code to the @seclink["sec:lifting-constructs"]{next section},
work on symbolic unions, but Rosette also provides dedicated to make this process easier and the resulting lifted code more efficient.
lifting constructs, described in the
@seclink["sec:lifting-constructs"]{next section}, to make
this process easier and the resulting lifted code more
efficient.
@defproc[(union? [v any/c]) boolean?]{ @defproc[(union? [v any/c]) boolean?]{
Returns true if the given value is a symbolic union. Otherwise returns false.
Returns true if the given value is a symbolic union.
Otherwise returns false.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic b boolean?) (define-symbolic b boolean?)
(define u (if b '(1 2) 3)) (define u (if b '(1 2) 3))
@ -218,192 +164,128 @@ efficient.
} }
@defproc[(union-contents [u union?]) (listof (cons/c (and/c boolean? term?) (not/c union?)))]{ @defproc[(union-contents [u union?]) (listof (cons/c (and/c boolean? term?) (not/c union?)))]{
Returns a list of guard-value pairs contained in the given union.
Returns a list of guard-value pairs contained in the given
union.
@examples[#:eval rosette-eval @examples[#:eval rosette-eval
(define-symbolic b boolean?) (define-symbolic b boolean?)
(define v (if b '(1 2) 3)) (define v (if b '(1 2) 3))
(union-contents v)] (union-contents v)]
} }
@section[#:tag "sec:lifting-constructs"]{Symbolic Lifting} @section[#:tag "sec:lifting-constructs"]{Symbolic Lifting}
Rosette provides a construct, @racket[for/all], for Rosette provides two main constructs for @tech[#:key "lifted constructs"]{lifting}
controlling how symbolic evaluation is performed. This Racket code to work on symbolic unions: @racket[for/all] and @racket[define-lift].
construct is built into the language. It is used internally The @racket[for/all] construct is built into the language. It is used internally by Rosette
by Rosette to @tech[#:key "lifted constructs"]{lift} to lift operations on @tech[#:key "unsolvable type"]{unsolvable types}. The
operations on @tech[#:key "unsolvable type"]{unsolvable @racket[define-lift] construct is syntactic sugar implemented on top of
types}. Rosette programs can also use it for this purpose, @racket[for/all]; it is exported by the @racket[rosette/lib/lift] library.
as well as for tuning the performance of symbolic
evaluation. @declare-exporting[rosette/base/core/forall rosette/lib/lift
#:use-sources (rosette/base/core/forall rosette/lib/lift)]
@defform[(for/all ([id val-expr]) body ...+)]{
If @racket[val-expr] evaluates to a value that is not a @racket[union?],
@racket[for/all] behaves like a @racket[let] expression. It binds
@racket[id] to the value and evaluates the @racket[body] with that binding.
If @racket[val-expr] evaluates to a symbolic union, then for each
guard-value pair @racket['(#, @var[g] . #, @var[v])] in that union, @racket[for/all]
binds @racket[id] to @var[v] and evaluates the @racket[body]
under the guard @var[g]. The results of the individual evaluations of
the @racket[body] are re-assembled into a single (concrete or symbolic)
output value, which is the result of the @racket[for/all] expression.
If the evaluation of @racket[body] executes any procedure @var[p] that is neither
implemented in nor provided by the @racket[rosette/safe] language, then @var[p]
@bold{must be pure}---it may not perform any observable side-effects,
such as writes to memory or disk. There is no purity requirement for using procedures
that are implemented in or exported by @racket[rosette/safe] (e.g., @racket[vector-set!]).
@declare-exporting[rosette/base/core/forall The @racket[for/all] construct is useful both for lifting pure Racket procedures to work
#:use-sources (rosette/base/core/forall)] on symbolic unions and for controling the performance of Rosette's symbolic evaluation.
The following examples show both use cases:
@defform[(for/all ([id val-expr maybe-exhaustive]) body ...+) @itemlist[
#:grammar [(maybe-exhaustive (code:line) #:exhaustive)]]{ @item{@emph{Lifting a pure Racket procedure
to work on symbolic unions.}
Splits the evaluation of @racket[body ...+] into multiple @defs+int[#:eval rosette-eval
paths, based on the value of @racket[val-expr]. In [(require (only-in racket [string-length racket/string-length]))
particular, @racket[for/all] evaluates @racket[val-expr],
decomposes the resulting value @var{V} into a list of
guard-value pairs @racket[(g v) ...+], and evaluates the equivalent of
the following expression:
@(racketblock
(cond [g (let ([id v]) body ...)] ...))
The keyword @racket[#:exhaustive] controls the (define (string-length value)
decomposition of @var{V}.
When @racket[#:exhaustive] is omitted, @var{V} is decomposed into
@racket[(union-contents #, @var{V})] if it is a symbolic
union. Otherwise, it is decomposed trivially into
@racket[(list (cons #t #, @var{V}))].
@examples[#:eval rosette-eval
(define-symbolic b boolean?)
(define u (if b (list 1 2) (list 3)))
(code:line
u (code:comment "u is a symbolic union,"))
(for/all ([v u]) (code:comment "so the body of for/all is evaluated")
(printf "~a, " (vc)) (code:comment "under each of its guards,")
(printf "~a\n" v) (code:comment "with the corresponding value bound to v.")
(map add1 v))
(vc)
(define i (if b 5 10))
(code:line
i (code:comment "i is not a union,"))
(for/all ([v i]) (code:comment "so the body of for/all is evaluated")
(printf "~a, " (vc)) (code:comment "once under the trival #t guard,")
(printf "~a\n" v) (code:comment "with i itself bound to v.")
(add1 v))
(vc)]
When @racket[#:exhaustive] is included, @var{V} is
decomposed into its constituent guard-value pairs
recursively if it is either a union or a conditional
symbolic term. Otherwise, it is decomposed trivally.
@examples[#:eval rosette-eval
(code:comment "i is a conditional term, so with #:exhaustive, it is")
(code:comment "split into its constituent guard-value pairs.")
(for/all ([v i #:exhaustive])
(printf "~a, " (vc))
(printf "~a\n" v)
(add1 v))
(code:comment "The #:exhaustive decomposition is done recursively,")
(code:comment "until no more decomposable terms are left.")
(define-symbolic a c boolean?)
(define j (if c i 0))
j
(define u (if a (list 1 2) j))
u
(for/all ([v u #:exhaustive])
(printf "~a, " (vc))
(printf "~a\n" v)
(add1 v))
(code:comment "The path guarded by a leads to failure,")
(code:comment "as reflected in the resulting vc.")
(vc)
(clear-vc!)
(code:comment "The above for/all expression outputs a term")
(code:comment "that is syntactically different from but")
(code:comment "semantically equivalent to the output of (add1 u),")
(code:comment "with the same effect on the vc.")
(add1 u)
(vc)
(clear-vc!)]
Programs can use @racket[for/all] to lift pure Racket
procedures to work on symbolic values and to control the performance
of Rosette's symbolic evaluation.
For example, the following snippet shows how to use
@racket[for/all] to lift @racket[string-length]
to work on symbolic strings. This approach
generalizes to all unlifted datatypes, since their
symbolic representation is always a union of guarded
concrete values.
@examples[#:eval rosette-eval #:label #f
(eval:no-prompt
(require (only-in racket [string-length racket/string-length])))
(eval:no-prompt
(define (string-length value)
(for/all ([str value]) (for/all ([str value])
(racket/string-length str)))) (racket/string-length str)))]
(string-length "abababa") (string-length "abababa")
(eval:error (string-length 3)) (string-length 3)
(define-symbolic b boolean?) (define-symbolic b boolean?)
(string-length (if b "a" "abababa")) (string-length (if b "a" "abababa"))
(vc) (string-length (if b "a" 3))
(string-length (if b "a" 3)) (asserts)
(vc) (string-length (if b 3 #f))]}
(clear-vc!)]
Here is also an example of using @racket[for/all] for @item{@emph{Making symbolic evaluation more efficient.} @(rosette-eval '(clear-asserts!))
performance tuning. Note that adding @racket[for/all]s in @defs+int[#:eval rosette-eval
too many places, or in the wrong places, will lead to worse [(require (only-in racket build-list))
performance. @seclink["ch:performance"]{Profiling} is the
best way to determine whether and where to insert them.
@examples[#:eval rosette-eval #:label #f (define limit 1000)
(eval:no-prompt
(require (only-in racket build-list)))
(eval:no-prompt (define (slow xs)
(define (vector-update! v idx proc) (code:comment "v[idx] := proc(v[idx])") (and (= (length xs) limit) (car (map add1 xs))))
(vector-set! v idx (proc (vector-ref v idx)))))
(eval:no-prompt (define (fast xs)
(define (vector-update!! v idx proc) (for/all ([xs xs]) (slow xs)))
(for/all ([idx idx #:exhaustive]) (code:comment "Split paths based on idx.")
(vector-update! v idx proc))))
(define-symbolic a b boolean?) (define ys (build-list limit identity))
(define idx (if a 0 (if b 5 10)))
(define limit 10000)
(code:line (define vals (build-list limit identity)) (code:comment "'(0 1 ... 9999)"))
(code:comment "vector-update! is 2 orders of magnitude slower than") (define-symbolic a boolean?)
(code:comment "vector-update!! on the following tests.")
(code:comment "By default, Rosette treats idx as an opaque symbolic")
(code:comment "integer when evaluating vector-update!, so it must")
(code:comment "account for the possibility of idx taking on any value")
(code:comment "between 0 and limit. The for/all in vector-update!!")
(code:comment "instructs Rosette to split the index and consider only")
(code:comment "the values that idx can in fact take on: 0, 5, 10.")
(define v0 (list->vector vals))
(time (vector-update! v0 idx add1))
(define v1 (list->vector vals))
(time (vector-update!! v1 idx add1))
(code:comment "But the default evaluation strategry is better for indices")
(code:comment "that are fully symbolic conditional terms.")
(define-symbolic i0 i1 i2 i3 integer?)
(define idx (if a (if b i0 i1) (if b i2 i3)))
(define v2 (list->vector vals))
(time (vector-update! v2 idx add1))
(define v3 (list->vector vals))
(time (vector-update!! v3 idx add1))]}
@defform[(for*/all ([id val-expr maybe-exhaustive]) body ...+) (define xs (if a ys (cdr ys)))]
#:grammar [(maybe-exhaustive (code:line) #:exhaustive)]]{
Expands to a nested use of @racket[for/all], just like (time (slow xs))
@racket[let*] expands to a nested use of @racket[let]. (time (fast xs))]
Note that the above transformation will not always lead to better performance.
Experimenting is the best way to determine whether and where to insert
performance-guiding @racket[for/all]s.
}]}
@defform[(for*/all ([id val-expr] ...+) body ...+)]{
Expands to a nested use of @racket[for/all],
just like @racket[let*] expands to a nested use of @racket[let].}
@defmodule[rosette/lib/lift #:no-declare]
@defform*[((define-lift id [(arg-type ...) racket-procedure-id])
(define-lift id [arg-type racket-procedure-id]))]{
Binds @racket[id] to a procedure that lifts @racket[racket-procedure-id] to
work on symbolic unions. In particular, the lifted procedure will work when given
either a concrete Racket value or a symbolic union contains a guarded value of
a suitable type, as given by @racket[arg-type]. Note that the lifted procedure
will not work on symbolic terms, only on symbolic unions or concrete values. The
Racket procedure bound to @racket[racket-procedure-id] must be pure (see @racket[for/all]).
When @racket[racket-procedure-id] takes a specific number of arguments,
the first form should be used, and the type of each argument should be given.
When @racket[racket-procedure-id] takes a variable number of arguments,
the type of all arguments should be given. Note that the second form omits
the parentheses around the argument type to indicate a variable number of
arguments, just like Racket's @racket[case-lambda] form.
The following example shows how to lift Racket's @racket[string-length] procedure
to work on symbolic unions that contain strings.
@defs+int[#:eval rosette-eval
[(require rosette/lib/lift)
(require (only-in racket [string-length racket/string-length] string?))
(define-lift string-length [(string?) racket/string-length])]
(string-length "abababa")
(define-symbolic b boolean?)
(string-length (if b "a" "abababa"))
(string-length (if b "a" 3))
(asserts)]
} }
@(kill-evaluator rosette-eval) @(kill-evaluator rosette-eval)

View File

@ -9,28 +9,21 @@
@(define rosette:onward13 @(define rosette:onward13
(make-bib (make-bib
#:title @hyperlink["http://dl.acm.org/citation.cfm?id=2509586"]{Growing Solver-Aided Languages with Rosette} #:title @hyperlink["http://homes.cs.washington.edu/~emina/pubs/rosette.onward13.pdf"]{Growing Solver-Aided Languages with Rosette}
#:author (authors "Emina Torlak" "Rastislav Bodik") #:author (authors "Emina Torlak" "Rastislav Bodik")
#:date 2013 #:date 2013
#:location "New Ideas, New Paradigms, and Reflections on Programming and Software (Onward!)")) #:location "New Ideas, New Paradigms, and Reflections on Programming and Software (Onward!)"))
@(define rosette:pldi14 @(define rosette:pldi14
(make-bib (make-bib
#:title @hyperlink["http://dl.acm.org/citation.cfm?id=2594340"]{A Lightweight Symbolic Virtual Machine for Solver-Aided Host Languages} #:title @hyperlink["http://homes.cs.washington.edu/~emina/pubs/rosette.pldi14.pdf"]{A Lightweight Symbolic Virtual Machine for Solver-Aided Host Languages}
#:author (authors "Emina Torlak" "Rastislav Bodik") #:author (authors "Emina Torlak" "Rastislav Bodik")
#:date 2014 #:date 2014
#:location "Programming Language Design and Implementation (PLDI)")) #:location "Programming Language Design and Implementation (PLDI)"))
@(define sympro:oopsla18 @(define sympro:oopsla18
(make-bib (make-bib
#:title @hyperlink["https://dl.acm.org/citation.cfm?id=3276519"]{Finding Code That Explodes Under Symbolic Evaluation} #:title @hyperlink["https://unsat.cs.washington.edu/papers/bornholt-sympro.pdf"]{Finding Code That Explodes Under Symbolic Evaluation}
#:author (authors "James Bornholt" "Emina Torlak") #:author (authors "James Bornholt" "Emina Torlak")
#:date 2018 #:date 2018
#:location "Object Oriented Programming, Systems, Languages, and Applications (OOPSLA)")) #:location "Object Oriented Programming, Systems, Languages, and Applications (OOPSLA)"))
@(define rosette:popl22
(make-bib
#:title @hyperlink["https://doi.org/10.1145/3498709"]{A Formal Foundation for Symbolic Evaluation with Merging}
#:author (authors "Sorawee Porncharoenwase" "Luke Nelson" "Xi Wang" "Emina Torlak")
#:date 2022
#:location "Principles of Programming Languages (POPL)"))

View File

@ -14,41 +14,19 @@
0 0
() ()
() ()
(c values c (0 (u . "(vector 0 (ite b 3 1) (ite b 2 4))\n")))) (c values c (v! 0 (0 (u . "(ite b 3 1)")) (0 (u . "(ite b 2 4)")))))
#"" #""
#"") #"")
((define sol1 (solve (assert b))) ((define env1 (solve (assert b)))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((evaluate y sol1) ((evaluate y env1) ((3) 0 () 0 () () (c values c (v! 0 3 2))) #"" #"")
((3) ((define env2 (solve (assert (not b))))
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'#(0 3 2)\n"))))
#""
#"")
((define sol2 (solve (assert (not b))))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
#"") #"")
((evaluate y sol2) ((evaluate y env2) ((3) 0 () 0 () () (c values c (v! 0 1 4))) #"" #"")
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'#(0 1 4)\n"))))
#""
#"")
((require (only-in racket make-hash hash-clear! hash-ref)) ((require (only-in racket make-hash hash-clear! hash-ref))
((3) 0 () 0 () () (c values c (void))) ((3) 0 () 0 () () (c values c (void)))
#"" #""
@ -60,19 +38,5 @@
((define-symbolic key integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic key integer?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((define-symbolic b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"") ((define-symbolic b boolean?) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((hash-ref h key 0) ((3) 0 () 0 () () (q values 0)) #"" #"") ((hash-ref h key 0) ((3) 0 () 0 () () (q values 0)) #"" #"")
((when b (pretty-print (vc)) (hash-clear! h)) ((when b (hash-clear! h)) ((3) 0 () 0 () () (c values c (void))) #"" #"")
((3) 0 () 0 () () (c values c (void))) (h ((3) 0 () 0 () () (c values c (h ! (equal)))) #"" #"")
#"(vc b #t)\n"
#"")
(h
((3)
1
(((lib "rosette/guide/scribble/util/lifted.rkt")
.
deserialize-info:opaque-v0))
0
()
()
(c values c (0 (u . "'#hash()\n"))))
#""
#"")

View File

@ -1,10 +1,11 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label racket) scribble/core scribble/example) @(require (for-label racket) scribble/core scribble/eval)
@(require (for-label rosette/base/form/define rosette/query/query rosette/solver/solution @(require (for-label rosette/base/form/define rosette/query/query rosette/solver/solution
(only-in rosette/base/base assert vc-true? vc) ) rosette/base/core/term (only-in rosette/query/debug define/debug debug)
racket/runtime-path racket/sandbox) (only-in rosette/base/core/safe assert) )
@(require (only-in "../refs.scrbl" ~cite rosette:pldi14 rosette:popl22)) racket/runtime-path)
@(require (only-in "../refs.scrbl" ~cite rosette:pldi14))
@(require "../util/lifted.rkt") @(require "../util/lifted.rkt")
@(define-runtime-path root ".") @(define-runtime-path root ".")
@ -13,24 +14,24 @@
@title[#:tag "ch:unsafe"]{Unsafe Operations} @title[#:tag "ch:unsafe"]{Unsafe Operations}
Throughout this guide, we have assumed that Rosette programs are Throughout this guide, we have assumed that Rosette programs are
written in the @racketmodname[rosette/safe] dialect of the full language. written in the @racket[rosette/safe] dialect of the full language.
This dialect extends a core subset of Racket with @seclink["ch:essentials"]{solver-aided This dialect extends a core subset of Racket with @seclink["ch:essentials"]{solver-aided
functionality}. In this chapter, we briefly discuss the @racketmodname[rosette] functionality}. In this chapter, we briefly discuss the @racket[rosette]
dialect of the language, which exports all of Racket. dialect of the language, which exports all of Racket.
Safe use of the full @racketmodname[rosette] language requires a basic understanding Safe use of the full @racket[rosette] language requires a basic understanding
of how Rosette's Symbolic Virtual Machine (SVM) works @~cite[rosette:pldi14 rosette:popl22]. of how Rosette's Symbolic Virtual Machine (SVM) works @~cite[rosette:pldi14].
Briefly, the SVM hijacks the normal Racket execution for all procedures and Briefly, the SVM hijacks the normal Racket execution for all procedures and
constructs that are exported by @racketmodname[rosette/safe]. Any programs that are constructs that are exported by @racket[rosette/safe]. Any programs that are
implemented exclusively in the @racketmodname[rosette/safe] language are therefore implemented exclusively in the @racket[rosette/safe] language are therefore
fully under the SVM's control. This means that the SVM can correctly interpret fully under the SVM's control. This means that the SVM can correctly interpret
the application of a procedure or a macro to a symbolic value, and it the application of a procedure or a macro to a symbolic value, and it
can correctly handle any side-effects (in particular, writes to memory) performed can correctly handle any side-effects (in particular, writes to memory) performed
by @racketmodname[rosette/safe] code. by @racket[rosette/safe] code.
The following snippet demonstrates the non-standard execution that the SVM needs to The following snippet demonstrates the non-standard execution that the SVM needs to
perform in order to assign the expected meaning to Rosette code: perform in order to assign the expected meaning to Rosette code:
@examples[#:eval rosette-eval @interaction[#:eval rosette-eval
(define y (vector 0 1 2)) (define y (vector 0 1 2))
(define-symbolic b boolean?) (define-symbolic b boolean?)
@ -47,31 +48,31 @@ perform in order to assign the expected meaning to Rosette code:
y y
(define sol1 (solve (assert b))) (define env1 (solve (assert b)))
(evaluate y sol1) (evaluate y env1)
(define sol2 (solve (assert (not b)))) (define env2 (solve (assert (not b))))
(evaluate y sol2)] (evaluate y env2)]
Because the SVM controls only the execution of @racketmodname[rosette/safe] code, Because the SVM controls only the execution of @racket[rosette/safe] code,
it cannot, in general, guarantee the safety or correctness of arbitrary @racketmodname[rosette] programs. it cannot, in general, guarantee the safety or correctness of arbitrary @racket[rosette] programs.
As soon as a @racketmodname[rosette] program calls an @tech[#:key "lifted construct"]{unlifted} Racket construct As soon as a @racket[rosette] program calls an @tech[#:key "lifted construct"]{unlifted} Racket construct
(that is, a procedure or a macro not implemented in or provided by the @racketmodname[rosette/safe] language), (that is, a procedure or a macro not implemented in or provided by the @racket[rosette/safe] language),
the execution escapes back to the Racket interpreter. The SVM has no control over the side-effects the execution escapes back to the Racket interpreter. The SVM has no control over the side-effects
performed by the Racket interpreter, or the meaning that it (perhaps incorrectly) assigns to programs performed by the Racket interpreter, or the meaning that it (perhaps incorrectly) assigns to programs
in the presence of symbolic values. As a result, the programmer is responsible for ensuring that in the presence of symbolic values. As a result, the programmer is responsible for ensuring that
a @racketmodname[rosette] program continues to behave correctly after the execution returns from the Racket interpreter. a @racket[rosette] program continues to behave correctly after the execution returns from the Racket interpreter.
As an example of incorrect behavior, consider the following @racketmodname[rosette] snippet. As an example of incorrect behavior, consider the following @racket[rosette] snippet.
The procedures @racket[make-hash], @racket[hash-ref], and @racket[hash-clear!] are not in @racketmodname[rosette/safe]. The procedures @racket[make-hash], @racket[hash-ref], and @racket[hash-clear!] are not in @racket[rosette/safe].
Whenever they are invoked, the execution escapes to the Racket interpreter. Whenever they are invoked, the execution escapes to the Racket interpreter.
@(rosette-eval '(require (only-in racket make-hash hash-clear! hash-ref))) @(rosette-eval '(require (only-in racket make-hash hash-clear! hash-ref)))
@examples[#:eval rosette-eval @defs+int[#:eval rosette-eval
(define h (make-hash '((1 . 2)))) [(define h (make-hash '((1 . 2))))
(define-symbolic key integer?) (define-symbolic key integer?)
(define-symbolic b boolean?) (define-symbolic b boolean?)]
(code:comment "The following call produces an incorrect value. Intuitively, we expect the") (code:comment "The following call produces an incorrect value. Intuitively, we expect the")
@ -83,7 +84,6 @@ Whenever they are invoked, the execution escapes to the Racket interpreter.
(code:comment "The following call produces an incorrect state. Intuitively, we expect h") (code:comment "The following call produces an incorrect state. Intuitively, we expect h")
(code:comment "to be empty if b is true and unchanged otherwise.") (code:comment "to be empty if b is true and unchanged otherwise.")
(when b (when b
(pretty-print (vc))
(hash-clear! h)) (hash-clear! h))
h] h]
@ -91,12 +91,12 @@ When is it safe to use a Racket procedure or macro? The answer depends on their
A conservative rule is to only use an unlifted construct @var[c] in an @deftech{effectively concrete} @tech{program state}. A conservative rule is to only use an unlifted construct @var[c] in an @deftech{effectively concrete} @tech{program state}.
The SVM is in such a state when The SVM is in such a state when
@itemlist[#:style 'ordered @itemlist[#:style 'ordered
@item{the current @tech{verification condition} is true, i.e., @racket[(vc-true? (vc))]; and,} @item{the current @tech{path condition} is @racket[#t];}
@item{the @tech{assertion store} is empty; and,}
@item{all local and global variables that may be read by @var[c] contain @deftech{fully concrete value}s. A @item{all local and global variables that may be read by @var[c] contain @deftech{fully concrete value}s. A
value (e.g., a list) is fully concrete if no symbolic values can be reached by recursively traversing its structure.}] value (e.g., a list) is fully concrete if no symbolic values can be reached by recursively traversing its structure.}]
The above uses of @racket[hash-ref] and @racket[hash-clear!] violate these The two uses of @racket[hash-ref] and @racket[hash-clear!] in our buggy example violate the third and first
requirements: @racket[hash-ref] is reading a symbolic value, and @racket[hash-clear!] is requirements, respectively.
evaluated in a state with a symbolic verification condition.
Being conservative, the above rule disallows many scenarios in which it is still safe to use Being conservative, the above rule disallows many scenarios in which it is still safe to use
Racket constructs. These, however, have to be considered on a case-by-case basis. For example, Racket constructs. These, however, have to be considered on a case-by-case basis. For example,
@ -104,5 +104,3 @@ it is safe to use Racket's iteration and comprehension constructs, such as @rack
as long as they iterate over concrete sequences, and all guard expressions produce fully concrete values in as long as they iterate over concrete sequences, and all guard expressions produce fully concrete values in
each iteration. In practice, Rosette programs can safely use many common Racket constructs, and with a each iteration. In practice, Rosette programs can safely use many common Racket constructs, and with a
bit of experience, it becomes easy to see when it is okay to break the effectively-concrete rule. bit of experience, it becomes easy to see when it is okay to break the effectively-concrete rule.
@(kill-evaluator rosette-eval)

View File

@ -1,29 +0,0 @@
#lang rosette
(provide demo define-demo print-forms-alt)
(define-syntax-rule (demo form)
(begin
(printf "> ~a\n" 'form)
(with-handlers ([exn:fail? (lambda (e)
(eprintf "~a\n" (exn-message e))
(clear-vc!)
(solver-shutdown (current-solver)))])
(let ([v form])
(unless (void? v)
(printf "~a\n" v))))))
(define-syntax-rule (define-demo id form ...)
(define (id)
(printf "\n--------~a--------\n" 'id)
form ...))
(require (only-in rosette/lib/synthax generate-forms))
(define (print-forms-alt sol)
(define stxs (generate-forms sol))
(pretty-write (syntax->datum (car stxs)))
(for ([stx (cdr stxs)])
(newline)
(pretty-write (syntax->datum stx))))

View File

@ -1,10 +1,10 @@
#lang racket #lang racket
(provide select rosette-evaluator rosette-log-evaluator logfile opaque format-opaque) (provide select rosette-evaluator rosette-log-evaluator logfile opaque)
(require (require
(for-label racket racket/generic) (for-label racket racket/generic)
(only-in rosette rosette bv) (only-in rosette rosette union union-contents union?)
racket/sandbox racket/serialize scribble/eval racket/sandbox racket/serialize scribble/eval
(only-in scribble/manual elem racket)) (only-in scribble/manual elem racket))
@ -16,6 +16,15 @@
(apply elem (apply elem
(add-between (map (lambda (id) (racket #,#`#,id)) (add-between (map (lambda (id) (racket #,#`#,id))
(filter lifted? racket-ids)) ", "))) (filter lifted? racket-ids)) ", ")))
(define (rosette-printer v)
(match v
[(? void?) (void)]
[(? custom-write?)
((custom-write-accessor v) v (current-output-port) 1)]
[(? pair?) (printf "'~a" v)]
[(? null?) (printf "'()")]
[(? symbol?) (printf "'~a" v)]
[_ (printf "~a" v)]))
(define (rosette-evaluator [eval-limits #f] [lang 'rosette/safe]) (define (rosette-evaluator [eval-limits #f] [lang 'rosette/safe])
(parameterize ([sandbox-output 'string] (parameterize ([sandbox-output 'string]
@ -23,35 +32,32 @@
[sandbox-path-permissions `((execute ,(byte-regexp #".*")))] [sandbox-path-permissions `((execute ,(byte-regexp #".*")))]
[sandbox-memory-limit #f] [sandbox-memory-limit #f]
[sandbox-eval-limits eval-limits] [sandbox-eval-limits eval-limits]
[current-print show]) [current-print rosette-printer])
(make-evaluator lang))) (make-evaluator lang)))
(define (logfile root [filename "log"]) (define (logfile root [filename "log"])
(build-path root (format "~a.txt" filename))) (build-path root (format "~a.txt" filename)))
(define (serialize-for-logging v)
(match v
[(or (? boolean?) (? number?) (? string?) (? void?)) v]
[(? box?) (box (serialize-for-logging (unbox v)))]
[(? pair?) (cons (serialize-for-logging (car v)) (serialize-for-logging (cdr v)))]
[(? list?) (map serialize-for-logging v)]
[(? vector?) (list->vector (map serialize-for-logging (vector->list v)))]
[(? struct?)
(let ([output-str (open-output-string)])
(display v output-str)
(opaque (get-output-string output-str)))]
[_ v]))
(serializable-struct opaque (str) (serializable-struct opaque (str)
#:methods gen:custom-write #:methods gen:custom-write
[(define (write-proc self port mode) [(define (write-proc self port mode)
(fprintf port "~a" (opaque-str self)))]) (fprintf port "~a" (opaque-str self)))])
(define (format-opaque format-str . args)
(opaque (apply format format-str args)))
(define (serialize-for-logging v)
(match v
[(or (? boolean?) (? number?) (? string?) (? symbol?) (? void?)) v]
[_
(define out (open-output-string))
(show v out)
(opaque (get-output-string out))]))
(define (serializing-evaluator evaluator) (define (serializing-evaluator evaluator)
(lambda (expr) (lambda (expr) (serialize-for-logging (evaluator expr))))
(define v (evaluator expr))
(printf "~a" (get-output evaluator))
(eprintf "~a" (get-error-output evaluator))
(serialize-for-logging v)))
(define (rosette-log-evaluator logfile [eval-limits #f] [lang 'rosette/safe]) (define (rosette-log-evaluator logfile [eval-limits #f] [lang 'rosette/safe])
(if (file-exists? logfile) (if (file-exists? logfile)
@ -59,90 +65,3 @@
(parameterize ([current-eval (serializing-evaluator (rosette-evaluator eval-limits lang))]) (parameterize ([current-eval (serializing-evaluator (rosette-evaluator eval-limits lang))])
(make-log-based-eval logfile 'record)))) (make-log-based-eval logfile 'record))))
(define (show v [port (current-output-port)])
(unless (void? v)
(define s (format "~v" v))
(cond
[(<= (string-length s) (pretty-print-columns))
(fprintf port "~a\n" s)]
[else
(define out (open-output-string))
(parameterize ([read-square-bracket-with-tag #t]
[read-curly-brace-with-tag #t]
[current-readtable tuple-readtable-open])
(pretty-write (showable (read (open-input-string s))) out))
(let* ([str (get-output-string out)]
[str (regexp-replace* #px"\\(⟦\\s*" str "[")]
[str (regexp-replace* #px"\\s*⟧\\)" str "]")]
[str (regexp-replace* #px"\\(⦃\\s*" str "{")]
[str (regexp-replace* #px"\\s*⦄\\)" str "}")])
(fprintf port "~a\n" str))])))
(define (showable v)
(match v
[(list '#%brackets rest ...)
`( ,@(map showable rest) )]
[(list '#%braces rest ...)
`( ,@(map showable rest) )]
[(list 'bv (? integer? x) (? integer? len))
(bv x len)]
[(? list?)
(map showable v)]
[_ v]))
; Adapted from https://docs.racket-lang.org/reference/readtables.html
(define (parse-nonempty port read-one src)
(let ([ch (peek-char port)])
(if (eof-object? ch)
null
(case ch
[(#\>) (read-char port)
null]
[else
(cons (read-one)
(parse-nonempty port read-one src))]))))
(define (parse port read-one src)
(if (eq? #\> (peek-char port))
null
(cons (read-one)
(parse-nonempty port read-one src))))
(define (wrap l)
(define out (open-output-string))
(fprintf out "#<")
(for ([v l])
(match v
[(list 'unquote w)
(fprintf out ", ~a" w)]
[_ (fprintf out "~a" v)]))
(fprintf out ">")
(opaque (get-output-string out)))
(define parse-open-tuple
(case-lambda
[(ch port)
(parameterize ([current-readtable (tuple-readtable-close)])
(wrap (parse port
(lambda ()
(read/recursive port #f ))
(object-name port))))]
[(ch port src line col pos)
(datum->syntax
#f
(parameterize ([current-readtable (tuple-readtable-close)])
(wrap (parse port
(lambda ()
(read-syntax/recursive src port #f))
src)))
(let-values ([(l c p) (port-next-location port)])
(list src line col pos (and pos (- p pos)))))]))
(define tuple-readtable-open
(make-readtable #f #\< 'dispatch-macro parse-open-tuple))
(define (tuple-readtable-close)
(make-readtable (current-readtable) #\> 'terminating-macro (lambda _ null)))

View File

@ -16,13 +16,13 @@ Rosette is a @emph{solver-aided} programming system with two components:
compiles them to logical constraints. The SVM enables Rosette compiles them to logical constraints. The SVM enables Rosette
to use the solver to automatically reason about program behaviors.}] to use the solver to automatically reason about program behaviors.}]
The name ``Rosette'' refers both to the language and the whole system. The name "Rosette" refers both to the language and the whole system.
@section[#:tag "sec:get"]{Installing Rosette} @section[#:tag "sec:get"]{Installing Rosette}
To install Rosette, you will need to To install Rosette, you will need to
@itemlist[@item{Download and install @hyperlink["http://racket-lang.org"]{Racket} (version 8.1 or later).} @itemlist[@item{Download and install @hyperlink["http://racket-lang.org"]{Racket} (version 7.0 or later).}
@item{Use Racket's @tt{raco} tool to install Rosette: @item{Use Racket's @tt{raco} tool to install Rosette:
@nested{ @nested{
@verbatim|{> raco pkg install rosette}|}}] @verbatim|{> raco pkg install rosette}|}}]
@ -37,8 +37,8 @@ Example Rosette programs can be found in the @tt{rosette/sdsl} folder. Most of
The Rosette system ships with two dialects of the Rosette language: The Rosette system ships with two dialects of the Rosette language:
@itemlist[@item{a @emph{safe} dialect, which is used in most of this guide, and} @itemlist[@item{a @emph{safe} dialect, which is used throughout this guide, and}
@item{an @emph{unsafe} dialect, which is briefly described in @seclink["ch:unsafe"]{Chapter 8}.}] @item{an @emph{unsafe} dialect, which is briefly described in the @seclink["ch:unsafe"]{last chapter}.}]
@ -50,9 +50,5 @@ To use the unsafe dialect, type this line instead:
@racketmod[rosette] @racketmod[rosette]
We strongly recommend that you start with the safe dialect, We strongly recommend that you start with the safe dialect, which includes a core subset of Racket. The unsafe dialect includes all of Racket, but unless you understand and observe the restrictions on using non-core features, your seemingly correct programs may crash or produce unexpected results.
which includes a core subset of Racket. The unsafe dialect
includes all of Racket, but unless you understand and
observe the restrictions on using non-core features,
seemingly correct programs may crash or produce unexpected
results.

View File

@ -14,7 +14,10 @@
(experimental)))) (experimental))))
;; Documentation category. On Racket 6.3+ this can be any string. ;; Documentation category. On Racket 6.3+ this can be any string.
(define compile-omit-files '("lib/trace/report/node_modules")) ;; Runs the code in `private/install.rkt` before installing this collection.
(define pre-install-collection "private/install.rkt")
(define compile-omit-files '("private/install.rkt"
"lib/trace/report/node_modules"))
(define raco-commands (define raco-commands
'(("symprofile" '(("symprofile"

View File

@ -9,7 +9,7 @@
[(x) x] [(x) x]
[(x y) (define-symbolic* x? boolean?) [(x y) (define-symbolic* x? boolean?)
(if x? x y)] (if x? x y)]
[xs (define-symbolic* xi? boolean? #:length (sub1 (length xs))) [xs (define-symbolic* xi? boolean? [(sub1 (length xs))])
(let loop ([xi? xi?][xs xs]) (let loop ([xi? xi?][xs xs])
(if (or (null? xi?) (car xi?)) (if (or (null? xi?) (car xi?))
(car xs) (car xs)

View File

@ -48,11 +48,10 @@
(define-simple-macro (destruct val [pat:clause-pattern e:expr ...+] ...) (define-simple-macro (destruct val [pat:clause-pattern e:expr ...+] ...)
#:do [(for-each check-duplicate-identifier! (attribute pat.id-set))] #:do [(for-each check-duplicate-identifier! (attribute pat.id-set))]
#:with match-expr (syntax/loc this-syntax (match var [pat (begin e ...)] ...))
#:with result #:with result
(syntax/loc this-syntax (syntax/loc this-syntax
(for/all ([var val]);(guarded-values val)]) (for/all ([var val]);(guarded-values val)])
match-expr)) (match var [pat (begin e ...)] ...)))
result) result)
(define-simple-macro (destruct* (val ...) [(pat:clause-pattern ...) e:expr ...+] ...) (define-simple-macro (destruct* (val ...) [(pat:clause-pattern ...) e:expr ...+] ...)

53
rosette/lib/lift.rkt Normal file
View File

@ -0,0 +1,53 @@
#lang rosette
(require "match.rkt" (for-syntax (only-in racket/syntax generate-temporary))
(only-in racket/splicing splicing-let))
(provide define-lift)
; The define-lift macro lifts a Racket procedure to
; work on symbolic union representations of ordinary Racket
; values. This means the lifted procedure will work when given
; either a concrete Racket value or a symbolic union that
; could evaluate to such a value. Note that the lifted procedure
; will not work on primitive symbolic values (boolean?, integer?, real? and
; bitvector?), only on symbolic unions or concrete values.
;
; The define-lift macro takes takes one of two forms:
; * (define-lifted lifted-id [(arg-type ...) procedure-to-lift])
; * (define-lifted lifted-id [args-type procedure-to-lift])
;
; When the procedure to be lifted takes a specific number of arguments,
; the first form should be used, and the type of each argument should be given.
; When the procedure to be lifted takes a variable number of arguments,
; the type of all arguments should be given. Note that the second form omits
; the parentheses around the argument type to indicate a variable number of
; arguments, just like Racket's case-lambda form.
(define-syntax (define-lift stx)
(syntax-case stx ()
[(_ id [type impl])
(not (identifier? #'impl))
(with-syntax ([tmp (generate-temporary)])
(syntax/loc stx
(splicing-let ([tmp impl])
(define-lift id [type tmp]))))]
[(_ id [(type? ...) impl])
(with-syntax ([(arg ...) (generate-temporaries #'(type? ...))])
(syntax/loc stx
(define (id arg ...)
(match* (arg ...)
[((? type? arg) ...) (impl arg ...)]
[(arg ...) (error 'id "expected ~a, given ~a" (list type? ...) (list arg ...))]))))]
[(_ id [type? impl])
(syntax/loc stx
(define id
(case-lambda
[() (impl)]
[(x) (match x
[(? type? x) (impl x)])]
[(x y) (match* (x y)
[((? type? x) (? type? y)) (impl x y)])]
[xs (id (car xs) (apply id (cdr xs)))])))]))

View File

@ -40,7 +40,7 @@
"Only install the compile handler; do not run the profiler" "Only install the compile handler; do not run the profiler"
(run-profiler? #f)] (run-profiler? #f)]
[("-m" "--module") name [("-m" "--module") name
"Run submodule <name> (defaults to 'main')" "Run submodule <name> (defaults to 'main)"
(module-name (string->symbol name))] (module-name (string->symbol name))]
[("-r" "--racket") [("-r" "--racket")
"Instrument code in any language, not just `#lang rosette`" "Instrument code in any language, not just `#lang rosette`"

View File

@ -78,13 +78,8 @@
; all events from the box. if that happened then the ENTER we're trying ; all events from the box. if that happened then the ENTER we're trying
; to delete has already been published, and so we need to retain the ; to delete has already been published, and so we need to retain the
; corresponding EXIT. ; corresponding EXIT.
(let loop ()
(unless (box-cas! the-box evts (cdr evts)) (unless (box-cas! the-box evts (cdr evts))
(if (eq? evts (unbox the-box)) (do-record-exit! out))]))))))
; spurious CAS failure; retry
(loop)
; box has changed, so we need to retain the EXIT
(do-record-exit! out))))]))))))
; Default version just uses the current-profile/current-reporter params ; Default version just uses the current-profile/current-reporter params
(define default-record-exit (define default-record-exit

Some files were not shown because too many files have changed in this diff Show More