0

In Most efficient way to represent memory buffers in Z3 the question on how make nested store operations more efficient is answered that one can replace the (nested) store operations with selects as in: (assert (= (select A i1) v1)). However, I need the store operation because previous constraints have to be replaced with the new constraints.

For example: the following constraints simulate the following assembly program:

mov qword ptr [rax], rbx
mov rcx, qword ptr [rax]

I like to proof that rbx and rcx are equal, I assert (= RBX!2 RCX!2) and expect that the model can be satisfied. This works perfectly. I assert (not (= RBX!2 RCX!2)) and expect that the model cannot be satisfied. When I feed the following constraints to Z3 (eg here) it gives an almost instant answer: UNSAT. However, if I proof the same problem in a C# program (see below) it cannot deduce UNSAT (in reasonable time).

Question: What can I try to make the C# program as quick as the SMT2.0 program?

(declare-fun RAX!0 () (_ BitVec 64))
(declare-fun RAX!1 () (_ BitVec 64))
(declare-fun RAX!2 () (_ BitVec 64))

(declare-fun RBX!0 () (_ BitVec 64))
(declare-fun RBX!1 () (_ BitVec 64))
(declare-fun RBX!2 () (_ BitVec 64))

(declare-fun RCX!0 () (_ BitVec 64))
(declare-fun RCX!1 () (_ BitVec 64))
(declare-fun RCX!2 () (_ BitVec 64))

(declare-fun MEM!0 () (Array (_ BitVec 64) (_ BitVec 8)))
(declare-fun MEM!1 () (Array (_ BitVec 64) (_ BitVec 8)))
(declare-fun MEM!2 () (Array (_ BitVec 64) (_ BitVec 8)))

(assert (= RAX!1 RAX!0))
(assert (= RBX!1 RBX!0))
(assert (= RCX!1 RCX!0))
(assert (let ((a!1 (store (store (store MEM!0 RAX!0 ((_ extract 7 0) RBX!0))
                         (bvadd #x0000000000000001 RAX!0)
                         ((_ extract 15 8) RBX!0))
                  (bvadd #x0000000000000002 RAX!0)
                  ((_ extract 23 16) RBX!0))))
(let ((a!2 (store (store (store a!1
                                (bvadd #x0000000000000003 RAX!0)
                                ((_ extract 31 24) RBX!0))
                         (bvadd #x0000000000000004 RAX!0)
                         ((_ extract 39 32) RBX!0))
                  (bvadd #x0000000000000005 RAX!0)
                  ((_ extract 47 40) RBX!0))))
  (= MEM!1
     (store (store a!2
                   (bvadd #x0000000000000006 RAX!0)
                   ((_ extract 55 48) RBX!0))
            (bvadd #x0000000000000007 RAX!0)
            ((_ extract 63 56) RBX!0))))))
(assert (= RAX!2 RAX!1))
(assert (= RBX!2 RBX!1))
(assert (= RCX!2
   (concat (select MEM!1 (bvadd #x0000000000000007 RAX!1))
           (select MEM!1 (bvadd #x0000000000000006 RAX!1))
           (select MEM!1 (bvadd #x0000000000000005 RAX!1))
           (select MEM!1 (bvadd #x0000000000000004 RAX!1))
           (select MEM!1 (bvadd #x0000000000000003 RAX!1))
           (select MEM!1 (bvadd #x0000000000000002 RAX!1))
           (select MEM!1 (bvadd #x0000000000000001 RAX!1))
           (select MEM!1 RAX!1))))
(assert (= MEM!2 MEM!1))
(assert (not (= RBX!2 RCX!2)))

C# code:

Dictionary<string, string> settings = new Dictionary<string, string>
{
    { "unsat-core", "false" },    // enable generation of unsat cores
    { "model", "false" },         // enable model generation
    { "proof", "false" },         // enable proof generation
    { "timeout", "60000" }        // 60000=1min
};
Context ctx = new Context(settings);

Solver solver = ctx.MkSolver(ctx.MkTactic("qfbv"));
BitVecExpr rax0 = ctx.MkBVConst("RAX!0", 64);
BitVecExpr rax1 = ctx.MkBVConst("RAX!1", 64);
BitVecExpr rax2 = ctx.MkBVConst("RAX!2", 64);

BitVecExpr rbx0 = ctx.MkBVConst("RBX!0", 64);
BitVecExpr rbx1 = ctx.MkBVConst("RBX!1", 64);
BitVecExpr rbx2 = ctx.MkBVConst("RBX!2", 64);

BitVecExpr rcx0 = ctx.MkBVConst("RCX!0", 64);
BitVecExpr rcx1 = ctx.MkBVConst("RCX!1", 64);
BitVecExpr rcx2 = ctx.MkBVConst("RCX!2", 64);

ArrayExpr mem0 = ctx.MkArrayConst("MEM!0", ctx.MkBitVecSort(64), ctx.MkBitVecSort(8));
ArrayExpr mem1 = ctx.MkArrayConst("MEM!1", ctx.MkBitVecSort(64), ctx.MkBitVecSort(8));
ArrayExpr mem2 = ctx.MkArrayConst("MEM!2", ctx.MkBitVecSort(64), ctx.MkBitVecSort(8));

solver.Assert(ctx.MkEq(rax1, rax0));
solver.Assert(ctx.MkEq(rbx1, rbx0));
solver.Assert(ctx.MkEq(rcx1, rcx0));
ArrayExpr memX0 = ctx.MkStore(mem0, ctx.MkBVAdd(ctx.MkBV(0, 64), rax0), ctx.MkExtract((1 * 8) - 1, 0 * 8, rbx0));
ArrayExpr memX1 = ctx.MkStore(memX0, ctx.MkBVAdd(ctx.MkBV(1, 64), rax0), ctx.MkExtract((2 * 8) - 1, 1 * 8, rbx0));
ArrayExpr memX2 = ctx.MkStore(memX1, ctx.MkBVAdd(ctx.MkBV(2, 64), rax0), ctx.MkExtract((3 * 8) - 1, 2 * 8, rbx0));
ArrayExpr memX3 = ctx.MkStore(memX2, ctx.MkBVAdd(ctx.MkBV(3, 64), rax0), ctx.MkExtract((4 * 8) - 1, 3 * 8, rbx0));
ArrayExpr memX4 = ctx.MkStore(memX3, ctx.MkBVAdd(ctx.MkBV(4, 64), rax0), ctx.MkExtract((5 * 8) - 1, 4 * 8, rbx0));
ArrayExpr memX5 = ctx.MkStore(memX4, ctx.MkBVAdd(ctx.MkBV(5, 64), rax0), ctx.MkExtract((6 * 8) - 1, 5 * 8, rbx0));
ArrayExpr memX6 = ctx.MkStore(memX5, ctx.MkBVAdd(ctx.MkBV(6, 64), rax0), ctx.MkExtract((7 * 8) - 1, 6 * 8, rbx0));
memX7 = ctx.MkStore(memX6, ctx.MkBVAdd(ctx.MkBV(7, 64), rax0), ctx.MkExtract((8 * 8) - 1, 7 * 8, rbx0));
solver.Assert(ctx.MkEq(mem1, memX7).Simplify() as BoolExpr);

solver.Assert(ctx.MkEq(rax2, rax1));
solver.Assert(ctx.MkEq(rbx2, rbx1));
BitVecExpr y0 = ctx.MkSelect(mem1, ctx.MkBVAdd(ctx.MkBV(0, 64), rax1)) as BitVecExpr;
BitVecExpr y1 = ctx.MkSelect(mem1, ctx.MkBVAdd(ctx.MkBV(1, 64), rax1)) as BitVecExpr;
BitVecExpr y2 = ctx.MkSelect(mem1, ctx.MkBVAdd(ctx.MkBV(2, 64), rax1)) as BitVecExpr;
BitVecExpr y3 = ctx.MkSelect(mem1, ctx.MkBVAdd(ctx.MkBV(3, 64), rax1)) as BitVecExpr;
BitVecExpr y4 = ctx.MkSelect(mem1, ctx.MkBVAdd(ctx.MkBV(4, 64), rax1)) as BitVecExpr;
BitVecExpr y5 = ctx.MkSelect(mem1, ctx.MkBVAdd(ctx.MkBV(5, 64), rax1)) as BitVecExpr;
BitVecExpr y6 = ctx.MkSelect(mem1, ctx.MkBVAdd(ctx.MkBV(6, 64), rax1)) as BitVecExpr;
BitVecExpr y7 = ctx.MkSelect(mem1, ctx.MkBVAdd(ctx.MkBV(7, 64), rax1)) as BitVecExpr;
BitVecExpr y = ctx.MkConcat(y7, ctx.MkConcat(y6, ctx.MkConcat(y5, ctx.MkConcat(y4, ctx.MkConcat(y3, ctx.MkConcat(y2, ctx.MkConcat(y1, y0)))))));
solver.Assert(ctx.MkEq(rcx2, y).Simplify() as BoolExpr);
solver.Assert(ctx.MkEq(mem2, mem1));

Status status_Neg = solver.Check(ctx.MkNot(ctx.MkEq(rbx2, rcx2)));
Console.WriteLine("Status Neg = "+status_Neg); // Go on holiday...
HJLebbink
  • 719
  • 1
  • 11
  • 32

1 Answers1

1

I don't have a way to run the C# program to play with it, unfortunately. But I noticed that you had calls to Simplify:

solver.Assert(ctx.MkEq(mem1, memX7).Simplify() as BoolExpr);

I'm curious why you needed that call? Perhaps that's the culprit?

The other thing to try would be to use uninterpreted functions for representing memory, instead of Arrays. UF's are typically much easier to deal with, and they provide roughly the same abstraction in my personal experience.

It might be a good idea to look at what the C# interface generated as SMT-Lib, to see if the translation is significantly different than what you thought it would be. I guess you could do that using the following function: https://github.com/Z3Prover/z3/blob/master/src/api/dotnet/AST.cs#L195

alias
  • 28,120
  • 2
  • 23
  • 40
  • The Simplify was an experiment, It did not change the Expr on which it was called, and I (incorrectly) assumed that it thus wouldn't matter much. It does matter much but it still does not explain why the SMT2.0 version is 0.05 sec while the c# version is 20 sec. – HJLebbink Jun 03 '17 at 15:19
  • My functional programming experience (Lambda calculus & Haskell) is a bit rusty. Could you please give a hint how such an uninterpreted function for storing a value (8-bits bitvec) to an address (64-bits bitvec) would look like such that a previous value would be overwritten. The destructive write makes it difficult to understand (for me). – HJLebbink Jun 03 '17 at 15:37
  • Sorry, my comment was misleading. By "uninterpreted function" I did not mean SMTLib UFs. Instead, I meant keeping track of the memory structure yourself; only generating values that are accessed. In a sense, you'll be doing coding outside of SMTLib, and translating the "trace" of an execution down to SMTLib. I'm not familiar with the C# interface, but if you're willing to try Haskell, I can help with coding this using SBV. (http://leventerkok.github.io/sbv/). This example, in particular, seems relevant: https://hackage.haskell.org/package/sbv-6.1/docs/Data-SBV-Examples-BitPrecise-Legato.html – alias Jun 04 '17 at 04:24