On this page:
7.1.1 Symbolic Terms
term?
expression?
constant?
term
expression
constant
symbolics
concrete?
symbolic?
type-of
type?
solvable?
7.1.2 Symbolic Unions
union?
union-contents
7.1.3 Symbolic Lifting
for/  all
for*/  all
8.12

7.1 Reflecting on Symbolic Values🔗ℹ

There are two kinds of symbolic values in Rosette: symbolic terms and symbolic unions. A Rosette program can inspect the representation of both kinds of values. This is useful for lifting additional (unlifted) Racket procedures to work on symbolic values, and for controlling the performance of Rosette’s symbolic evaluator.

7.1.1 Symbolic Terms🔗ℹ

A symbolic term is either a symbolic constant, created via define-symbolic[*], or a symbolic expression, produced by a lifted operator. Terms are strongly typed, and always belong to a solvable type. Symbolic values of all other (unsolvable) types take the form of symbolic unions.

procedure

(term? v)  boolean?

  v : any/c

procedure

(expression? v)  boolean?

  v : any/c

procedure

(constant? v)  boolean?

  v : any/c
Predicates for recognizing symbolic terms, expressions, and constants, respectively.

Examples:
> (define-symbolic x integer?) ; Constant.
> (define e (+ x 1))           ; Expression.
> (list (term? x) (term? e))

'(#t #t)

> (list (constant? x) (constant? e))

'(#t #f)

> (list (expression? x) (expression? e))

'(#f #t)

> (term? 1)

#f

syntax

(term content type)

syntax

(expression op child ...+)

syntax

(constant id type)

Pattern matching forms for symbolic terms, expressions, and constants, respectively.

Examples:
> (define-symbolic x integer?) ; Constant.
> (define e (+ x 1))           ; Expression.
> (match x
    [(constant identifier type) (list identifier type)])

'(#<syntax:eval:8:0 x> integer?)

> (match x
    [(term content type) (list content type)])

'(#<syntax:eval:8:0 x> integer?)

> (match e
    [(expression op child ...) (cons op child)])

(list + 1 x)

> (match e
    [(term content type) (list content type)])

(list (list + 1 x) integer?)

procedure

(symbolics v)  (listof constant?)

  v : any/c
Returns all symbolic constants that are transitively contained in the given value.

Examples:
> (define-symbolic x y z integer?)
> (symbolics x)

(list x)

> (symbolics (if (= x y) 2 #f))

(list x y)

> (symbolics (list y z y))

(list y z)

> (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)))

(list y z x)

procedure

(concrete? v)  boolean?

  v : any/c
Equivalent to (null? (symbolics v)) but more efficient.

Examples:
> (define-symbolic x y z integer?)
> (concrete? x)

#f

> (concrete? (if (= x y) 2 #f))

#f

> (concrete? (list y z y))

#f

> (struct cell (value) #:transparent)
> (concrete? (list (vector (box 1) 3) (cell (cons 4 5))))

#t

> (concrete? (list 5 (cons (box y) z) (vector (cell x) z)))

#f

procedure

(symbolic? v)  boolean?

  v : any/c
Equivalent to (not (concrete? v)).

procedure

(type-of v ...+)  type?

  v : any/c
Returns the most specific type? predicate that accepts all of the given values.

Examples:
> (define-symbolic x integer?)
> (type-of x)

integer?

> (type-of (+ x 1))

integer?

> (type-of x 3.14)

real?

> (type-of #t)

boolean?

> (type-of #t 1)

any/c

procedure

(type? v)  boolean?

  v : any/c
Returns true when given a predicate that recognizes a built-in or a structure type. Otherwise returns false.

Examples:
> (type? integer?)

#t

> (type? boolean?)

#t

> (type? list?)

#t

> (struct circle (radius))
> (type? circle?)

#t

> (type? any/c)

#t

> (type? 1)

#f

procedure

(solvable? v)  boolean?

  v : any/c
Returns true if v is a type predicate for a solvable type.

Examples:
> (solvable? boolean?)

#t

> (solvable? integer?)

#t

> (solvable? real?)

#t

> (solvable? (bitvector 8))

#t

> (solvable? (~> (bitvector 32) (bitvector 64)))

#t

> (solvable? list?)

#f

> (struct circle (radius))
> (solvable? circle?)

#f

> (solvable? any/c)

#f

7.1.2 Symbolic Unions🔗ℹ

Rosette represents symbolic values of an unsolvable type (e.g., list?) as symbolic unions. A symbolic union is a set of two or more guarded values. A guarded value, in turn, combines a guard, which is a symbolic boolean? term, and a (non-union) value. Rosette’s symbolic evaluator guarantees that the guards in a symbolic union are disjoint: only one of them can ever be true. For example, the symbolic vector s defined below is represented as a symbolic union of two guarded vectors:

> (define-symbolic b boolean?)
> (define v (vector 1))
> (define w (vector 2 3))
> (define s (if b v w))
> s

(union [b #(1)] [(! b) #(2 3)])

> (type-of s)

vector?

> (eq? s v)

b

> (eq? s w)

(! b)

The values that appear in a union are themselves never unions. They may, however, contain unions. They may also belong to several different types. In that case, the type of the union is the most specific type? predicate that accepts all members of the union. This will always be an unsolvable type—possibly any/c, the most general unsolvable type.

> (define-symbolic b boolean?)
> (define-symbolic c boolean?)
> (define v (if c "c" 0))
> (define u (if b (vector v) 4))
> u

(union [b #((union #:size 2 #:hash 736602544239193347))] [(! b) 4])

> (type-of u)

any/c

> (union-contents u)

(list (cons b '#((union [c c] [(! c) 0]))) (cons (! b) 4))

Symbolic unions are recognized by the union? predicate, and Rosette programs can inspect union contents using the union-contents procedure. Applications may use union? and union-contents directly to lift Racket code to work on symbolic unions, but Rosette also provides dedicated lifting constructs, described in the next section, to make this process easier and the resulting lifted code more efficient.

procedure

(union? v)  boolean?

  v : any/c
Returns true if the given value is a symbolic union. Otherwise returns false.

Examples:
> (define-symbolic b boolean?)
> (define u (if b '(1 2) 3))
> (union? u)

#t

> (union? b)

#f

procedure

(union-contents u)

  (listof (cons/c (and/c boolean? term?) (not/c union?)))
  u : union?
Returns a list of guard-value pairs contained in the given union.

Examples:
> (define-symbolic b boolean?)
> (define v (if b '(1 2) 3))
> (union-contents v)

(list (list b 1 2) (cons (! b) 3))

7.1.3 Symbolic Lifting🔗ℹ

Rosette provides a construct, for/all, for controlling how symbolic evaluation is performed. This construct is built into the language. It is used internally by Rosette to lift operations on unsolvable types. Rosette programs can also use it for this purpose, as well as for tuning the performance of symbolic evaluation.

syntax

(for/all ([id val-expr maybe-exhaustive]) body ...+)

 
maybe-exhaustive = 
  | #:exhaustive
Splits the evaluation of body ...+ into multiple paths, based on the value of val-expr. In particular, for/all evaluates val-expr, decomposes the resulting value V into a list of guard-value pairs (g v) ...+, and evaluates the equivalent of the following expression:

(cond [g (let ([id v]) body ...)] ...)

The keyword #:exhaustive controls the decomposition of V.

When #:exhaustive is omitted, V is decomposed into (union-contents V) if it is a symbolic union. Otherwise, it is decomposed trivially into (list (cons #t V)).

Examples:
> (define-symbolic b boolean?)
> (define u (if b (list 1 2) (list 3)))
> u                      ; u is a symbolic union,

(union [b (1 2)] [(! b) (3)])

> (for/all ([v u])       ; so the body of for/all is evaluated
    (printf "~a, " (vc)) ; under each of its guards,
    (printf "~a\n" v)    ; with the corresponding value bound to v.
    (map add1 v))

#(struct:vc b #t), (1 2)

#(struct:vc (! b) #t), (3)

(union [b (2 3)] [(! b) (4)])

> (vc)

(vc #t #t)

> (define i (if b 5 10))
> i                      ; i is not a union,

(ite b 5 10)

> (for/all ([v i])       ; so the body of for/all is evaluated
    (printf "~a, " (vc)) ; once under the trival #t guard,
    (printf "~a\n" v)    ; with i itself bound to v.
    (add1 v))

#(struct:vc #t #t), (ite b 5 10)

(ite b 6 11)

> (vc)

(vc #t #t)

When #:exhaustive is included, 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:
; i is a conditional term, so with #:exhaustive, it is
; split into its constituent guard-value pairs.
> (for/all ([v i #:exhaustive])
    (printf "~a, " (vc))
    (printf "~a\n" v)
    (add1 v))

#(struct:vc b #t), 5

#(struct:vc (! b) #t), 10

(ite b 6 11)

; The #:exhaustive decomposition is done recursively,
; until no more decomposable terms are left.
> (define-symbolic a c boolean?)
> (define j (if c i 0))
> j

(ite c (ite b 5 10) 0)

> (define u (if a (list 1 2) j))
> u

(union [a (1 2)] [(! a) (ite c (ite b 5 10) 0)])

> (for/all ([v u #:exhaustive])
    (printf "~a, " (vc))
    (printf "~a\n" v)
    (add1 v))

#(struct:vc (&& (! c) (! a)) #t), 0

#(struct:vc (&& (! b) c (! a)) #t), 10

#(struct:vc (&& b c (! a)) #t), 5

#(struct:vc a #t), (1 2)

(ite* (⊢ (&& b c (! a)) 6) (⊢ (&& (! b) c (! a)) 11) (⊢ (&& (! c) (! a)) 1))

; The path guarded by a leads to failure,
; as reflected in the resulting vc.
> (vc)

(vc #t (! a))

> (clear-vc!)
; The above for/all expression outputs a term
; that is syntactically different from but
; semantically equivalent to the output of (add1 u),
; with the same effect on the vc.
> (add1 u)

(+ 1 (ite c (ite b 5 10) 0))

> (vc)

(vc #t (! a))

> (clear-vc!)

Programs can use 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 for/all to lift 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.

(require (only-in racket [string-length racket/string-length]))

 

(define (string-length value)
  (for/all ([str value])
    (racket/string-length str)))

 

> (string-length "abababa")

7

> (string-length 3)

string-length: contract violation

  expected: string?

  given: 3

> (define-symbolic b boolean?)
> (string-length (if b "a" "abababa"))

(ite b 1 7)

> (vc)

(vc #t #t)

> (string-length (if b "a" 3))

1

> (vc)

(vc #t b)

> (clear-vc!)

Here is also an example of using for/all for performance tuning. Note that adding for/alls in too many places, or in the wrong places, will lead to worse performance. Profiling is the best way to determine whether and where to insert them.

(require (only-in racket build-list))

 

(define (vector-update! v idx proc)  ; v[idx] := proc(v[idx])
  (vector-set! v idx (proc (vector-ref v idx))))

 

(define (vector-update!! v idx proc)
  (for/all ([idx idx #:exhaustive])  ; Split paths based on idx.
    (vector-update! v idx proc)))

 

> (define-symbolic a b boolean?)
> (define idx (if a 0 (if b 5 10)))
> (define limit 10000)
> (define vals (build-list limit identity)) ; '(0 1 ... 9999)
; vector-update! is 2 orders of magnitude slower than
; vector-update!! on the following tests.
; By default, Rosette treats idx as an opaque symbolic
; integer when evaluating vector-update!, so it must
; account for the possibility of idx taking on any value
; between 0 and limit. The for/all in vector-update!!
; instructs Rosette to split the index and consider only
; the values that idx can in fact take on: 0, 5, 10.
> (define v0 (list->vector vals))
> (time (vector-update! v0 idx add1))

cpu time: 389 real time: 395 gc time: 14

> (define v1 (list->vector vals))
> (time (vector-update!! v1 idx add1))

cpu time: 0 real time: 0 gc time: 0

; But the default evaluation strategry is better for indices
; 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))

cpu time: 405 real time: 411 gc time: 16

> (define v3 (list->vector vals))
> (time (vector-update!! v3 idx add1))

cpu time: 3876 real time: 3901 gc time: 544

syntax

(for*/all ([id val-expr maybe-exhaustive]) body ...+)

 
maybe-exhaustive = 
  | #:exhaustive
Expands to a nested use of for/all, just like let* expands to a nested use of let.