0

A constant in a z3 solver can be a usual python type (1) or a z3 Ref (2) as in the following artificial example, where x and y undergo same constraint set through these two types:

from z3 import *

s = Solver()
x, y = BitVecs("x y",7)
d = BitVecVal(6, 7)

                   #constraints:
#                   ------------
s.add(x & 6 != 6)  #1'th or 2'th bit unset using Integer    #....(1)
#                                                   vs
s.add(y & d != d)  #1'th or 2'th bit unset using BitVecVal  #....(2)


check = s.check()
if check == sat:
    print(s.model())

So, is there one of (1), (2) to prefer? Seems, I don't understand why there are constant sorts if the same can be done with usual python types.

2 Answers2

0

The resulting SMT code for Z3 can be shown as follows:

#  https://stackoverflow.com/a/14629021/1911064
def toSMT2Benchmark(f, status="unknown", name="benchmark", logic=""):
  v = (Ast * 0)()
  return Z3_benchmark_to_smtlib_string(f.ctx_ref(), name, logic, status, "", 0, v, f.as_ast())

print(toSMT2Benchmark(x & 6 != 6))
print(toSMT2Benchmark(y & d != d))

Output:

; benchmark
(set-info :status unknown)
(declare-fun x () (_ BitVec 7))
(assert
 (and (distinct (bvand x (_ bv6 7)) (_ bv6 7)) true))
(check-sat)

; benchmark
(set-info :status unknown)
(declare-fun y () (_ BitVec 7))
(assert
 (and (distinct (_ bv6 7) (bvand y (_ bv6 7))) true))
(check-sat)

So, both forms do create pretty much the same input for Z3.

Axel Kemper
  • 10,544
  • 2
  • 31
  • 54
  • Thanks @Axel Kemper! This clearifies pretty much my confusuion and, along with your [reference](https://stackoverflow.com/questions/14628279/z3-convert-z3py-expression-to-smt-lib2?noredirect=1&lq=1) provides decent material for orientation. – Daniel Schwegler Apr 23 '22 at 12:42
0

An easier way to generate SMTLib from the solver is to simply print s.sexpr():

from z3 import *

s = Solver()
x, y = BitVecs("x y",7)
d = BitVecVal(6, 7)

s.add(x & 6 != 6)  #1'th or 2'th bit unset using Integer    #....(1)

check = s.check()
print(s.sexpr())
if check == sat:
    print(s.model())

This prints:

declare-fun k!0 () (_ BitVec 2))
(declare-fun x () (_ BitVec 7))
(declare-fun k!1 () Bool)
(declare-fun k!2 () Bool)
(assert (distinct (bvand x #b0000110) #b0000110))
(model-del k!0)
(model-add x () (_ BitVec 7) (concat #x0 k!0 #b0))
(model-del k!1)
(model-add k!0 () (_ BitVec 2) (ite k!1 #b11 (bvnot #b11)))
(model-del k!2)
(model-add k!1 () Bool (not k!2))

[x = 0]

Two things to notice here:

  • s.sexpr() can be used to print the "internal" form of any solver state at any time.
  • You never actually used an integer in the first form of your constraint: Since you were comparing it to a bit-vector, z3 automatically coerced it into a bit vector. In fact, comparing a bit-vector to an integer value would not be valid, i.e., it would be a type-error. You can see this as follows:
s.add(x & 6 != IntVal(6))

And for this, z3 would throw a sort-mismatch error:

z3.z3types.Z3Exception: sort mismatch

In short, SMTLib is a logic with simple-types, and a bit-vector can only be compared to another bit-vector. It just happens that the Python API inserts implicit coercions from literal values that are appearing in your program. While this can be convenient for programming, it can lead to misunderstandings as to if you were comparing it to an integer; which is not allowed in the first place. Hope that clears it up!

alias
  • 28,120
  • 2
  • 23
  • 40