From 10844cf82fe0c7dda1d5d09c750501799f8c2a19 Mon Sep 17 00:00:00 2001 From: Philippe Suter <philippe.suter@gmail.com> Date: Fri, 26 Oct 2012 18:37:25 +0200 Subject: [PATCH] Basics of UninterpretedZ3Solver in place --- .../leon/solvers/z3/AbstractZ3Solver.scala | 2 +- .../scala/leon/solvers/z3/FairZ3Solver.scala | 287 +----------------- .../solvers/z3/UninterpretedZ3Solver.scala | 86 ++++++ 3 files changed, 99 insertions(+), 276 deletions(-) create mode 100644 src/main/scala/leon/solvers/z3/UninterpretedZ3Solver.scala diff --git a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala index a2a78f952..5c31961fb 100644 --- a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala +++ b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala @@ -375,7 +375,7 @@ trait AbstractZ3Solver { } } - protected[leon] def toZ3Formula(z3: Z3Context, expr: Expr, initialMap: Map[Identifier,Z3AST] = Map.empty) : Option[Z3AST] = { + protected[leon] def toZ3Formula(expr: Expr, initialMap: Map[Identifier,Z3AST] = Map.empty) : Option[Z3AST] = { class CantTranslateException extends Exception val varsInformula: Set[Identifier] = variablesOf(expr) diff --git a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala index b0d7ba9d2..9d654c88f 100644 --- a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala +++ b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala @@ -97,14 +97,14 @@ class FairZ3Solver(reporter: Reporter) extends Solver(reporter) with AbstractZ3S assert(variablesOf(expr) -- argsAsIDs.toSet == Set.empty) val axiom : Z3AST = if(args.isEmpty) { val eq = Equals(FunctionInvocation(fd, Seq(cc)), expr) - toZ3Formula(z3, eq).get + toZ3Formula(eq).get } else { val z3ArgSorts = argsAsIDs.map(i => typeToSort(i.getType)) val boundVars = z3ArgSorts.zipWithIndex.map(p => z3.mkBound(p._2, p._1)) val map : Map[Identifier,Z3AST] = (argsAsIDs zip boundVars).toMap val eq = Equals(FunctionInvocation(fd, Seq(cc)), expr) - val z3IzedEq = toZ3Formula(z3, eq, map).get - val z3IzedCC = toZ3Formula(z3, cc, map).get + val z3IzedEq = toZ3Formula(eq, map).get + val z3IzedCC = toZ3Formula(cc, map).get val pattern = z3.mkPattern(functionDefToDecl(fd)(z3IzedCC)) val nameTypePairs = z3ArgSorts.map(s => (z3.mkFreshIntSymbol, s)) z3.mkForAll(0, List(pattern), nameTypePairs, z3IzedEq) @@ -121,7 +121,7 @@ class FairZ3Solver(reporter: Reporter) extends Solver(reporter) with AbstractZ3S val argSort = typeToSort(fd.args(0).getType) val bound = z3.mkBound(0, argSort) val subst = replace(Map(ResultVariable() -> FunctionInvocation(fd, Seq(fd.args(0).toVariable))), cleaned) - val z3IzedPost = toZ3Formula(z3, subst, Map(fd.args(0).id -> bound)).get + val z3IzedPost = toZ3Formula(subst, Map(fd.args(0).id -> bound)).get val pattern = z3.mkPattern(functionDefToDecl(fd)(bound)) val nameTypePairs = Seq((z3.mkFreshIntSymbol, argSort)) val postAxiom = z3.mkForAll(0, List(pattern), nameTypePairs, z3IzedPost) @@ -219,11 +219,11 @@ class FairZ3Solver(reporter: Reporter) extends Solver(reporter) with AbstractZ3S Logger.debug("we're getting a new clause " + clause, 4, "z3solver") } - val cc = toZ3Formula(z3, And(clauses)).get + val cc = toZ3Formula(And(clauses)).get z3.assertCnstr(cc) // these are the optional sequence of assumption literals - val assumptionsAsZ3: Option[Seq[Z3AST]] = assumptions.map(set => set.toSeq.map(toZ3Formula(z3, _).get)) + val assumptionsAsZ3: Option[Seq[Z3AST]] = assumptions.map(set => set.toSeq.map(toZ3Formula(_).get)) blockingSet ++= Set(guards.map(p => if(p._2) Not(Variable(p._1)) else Variable(p._1)) : _*) @@ -234,7 +234,7 @@ class FairZ3Solver(reporter: Reporter) extends Solver(reporter) with AbstractZ3S iterationsLeft -= 1 blocking2Z3Stopwatch.start - val blockingSetAsZ3 : Seq[Z3AST] = blockingSet.toSeq.map(toZ3Formula(z3, _).get) + val blockingSetAsZ3 : Seq[Z3AST] = blockingSet.toSeq.map(toZ3Formula(_).get) // println("Blocking set : " + blockingSet) blocking2Z3Stopwatch.stop @@ -398,12 +398,12 @@ class FairZ3Solver(reporter: Reporter) extends Solver(reporter) with AbstractZ3S for(ex <- blockingSet) ex match { case Not(Variable(b)) => { z3.push - z3.assertCnstr(toZ3Formula(z3, Variable(b)).get) + z3.assertCnstr(toZ3Formula(Variable(b)).get) z3.check match { case Some(false) => { //println("We had ~" + b + " in the blocking set. We now know it should stay there forever.") z3.pop(1) - z3.assertCnstr(toZ3Formula(z3, Not(Variable(b))).get) + z3.assertCnstr(toZ3Formula(Not(Variable(b))).get) fixedForEver = fixedForEver + ex } case _ => z3.pop(1) @@ -411,12 +411,12 @@ class FairZ3Solver(reporter: Reporter) extends Solver(reporter) with AbstractZ3S } case Variable(b) => { z3.push - z3.assertCnstr(toZ3Formula(z3, Not(Variable(b))).get) + z3.assertCnstr(toZ3Formula(Not(Variable(b))).get) z3.check match { case Some(false) => { //println("We had " + b + " in the blocking set. We now know it should stay there forever.") z3.pop(1) - z3.assertCnstr(toZ3Formula(z3, Variable(b)).get) + z3.assertCnstr(toZ3Formula(Variable(b)).get) fixedForEver = fixedForEver + ex } case _ => z3.pop(1) @@ -446,7 +446,7 @@ class FairZ3Solver(reporter: Reporter) extends Solver(reporter) with AbstractZ3S } for(ncl <- newClauses) { - z3.assertCnstr(toZ3Formula(z3, ncl).get) + z3.assertCnstr(toZ3Formula(ncl).get) } blockingSet = blockingSet ++ Set(newBlockers.map(p => if(p._2) Not(Variable(p._1)) else Variable(p._1)) : _*) } @@ -544,269 +544,6 @@ class FairZ3Solver(reporter: Reporter) extends Solver(reporter) with AbstractZ3S } } - - // the last return value is a map binding ite values to boolean switches - private def clausifyITE(formula : Expr) : (Expr, List[Expr], Map[Identifier,Identifier]) = { - var newClauses : List[Expr] = Nil - var ite2Bools : Map[Identifier,Identifier] = Map.empty - - def clausify(ex : Expr) : Option[Expr] = ex match { - case ie @ IfExpr(cond, then, elze) => { - val switch = FreshIdentifier("path", true).setType(BooleanType).toVariable - val placeHolder = FreshIdentifier("iteval", true).setType(ie.getType).toVariable - newClauses = Iff(switch, cond) :: newClauses - newClauses = Implies(switch, Equals(placeHolder, then)) :: newClauses - newClauses = Implies(Not(switch), Equals(placeHolder, elze)) :: newClauses - // newBools = newBools + switch.id - ite2Bools = ite2Bools + (placeHolder.id -> switch.id) - Some(placeHolder) - } - case _ => None - } - - val cleanedUp = searchAndReplaceDFS(clausify)(formula) - - (cleanedUp, newClauses.reverse, ite2Bools) - } - - protected[leon] override def toZ3Formula(z3: Z3Context, expr: Expr, initialMap: Map[Identifier,Z3AST] = Map.empty) : Option[Z3AST] = { - class CantTranslateException extends Exception - - val varsInformula: Set[Identifier] = variablesOf(expr) - - var z3Vars: Map[Identifier,Z3AST] = if(!initialMap.isEmpty) { - initialMap - } else { - // FIXME TODO pleeeeeeeease make this cleaner. Ie. decide what set of - // variable has to remain in a map etc. - exprToZ3Id.filter(p => p._1.isInstanceOf[Variable]).map(p => (p._1.asInstanceOf[Variable].id -> p._2)) - } - // exprToZ3Id = Map.empty - // z3IdToExpr = Map.empty - - // for((k, v) <- initialMap) { - // exprToZ3Id += (k.toVariable -> v) - // z3IdToExpr += (v -> k.toVariable) - // } - - def rec(ex: Expr): Z3AST = { - //println("Stacking up call for:") - //println(ex) - val recResult = (ex match { - case tu@Tuple(args) => { - // This call is required, because the Z3 sort may not have been generated yet. - // If it has, it's just a map lookup and instant return. - typeToSort(tu.getType) - val constructor = tupleConstructors(tu.getType) - constructor(args.map(rec(_)): _*) - } - case ts@TupleSelect(tu, i) => { - // See comment above for similar code. - typeToSort(tu.getType) - val selector = tupleSelectors(tu.getType)(i-1) - selector(rec(tu)) - } - case Let(i, e, b) => { - val re = rec(e) - z3Vars = z3Vars + (i -> re) - val rb = rec(b) - z3Vars = z3Vars - i - rb - } - case Waypoint(_, e) => rec(e) - case e @ Error(_) => { - val tpe = e.getType - val newAST = z3.mkFreshConst("errorValue", typeToSort(tpe)) - exprToZ3Id += (e -> newAST) - z3IdToExpr += (newAST -> e) - newAST - } - case v@Variable(id) => z3Vars.get(id) match { - case Some(ast) => ast - case None => { - // if (id.isLetBinder) { - // scala.sys.error("Error in formula being translated to Z3: identifier " + id + " seems to have escaped its let-definition") - // } - val newAST = z3.mkFreshConst(id.uniqueName/*name*/, typeToSort(v.getType)) - z3Vars = z3Vars + (id -> newAST) - exprToZ3Id += (v -> newAST) - z3IdToExpr += (newAST -> v) - newAST - } - } - - case ite @ IfExpr(c, t, e) => { - z3.mkITE(rec(c), rec(t), rec(e)) - } - // case ite @ IfExpr(c, t, e) => { - // val switch = z3.mkFreshConst("path", z3.mkBoolSort) - // val placeHolder = z3.mkFreshConst("ite", typeToSort(ite.getType)) - // val clause1 = z3.mkIff(switch, rec(c)) - // val clause2 = z3.mkImplies(switch, z3.mkEq(placeHolder, rec(t))) - // val clause3 = z3.mkImplies(z3.mkNot(switch), z3.mkEq(placeHolder, rec(e))) - - // accumulatedClauses = clause3 :: clause2 :: clause1 :: accumulatedClauses - - // placeHolder - // } - case And(exs) => z3.mkAnd(exs.map(rec(_)): _*) - case Or(exs) => z3.mkOr(exs.map(rec(_)): _*) - case Implies(l, r) => z3.mkImplies(rec(l), rec(r)) - case Iff(l, r) => { - val rl = rec(l) - val rr = rec(r) - z3.mkAnd(z3.mkImplies(rl, rr), z3.mkImplies(rr, rl)) - } - case Not(Iff(l, r)) => z3.mkXor(rec(l), rec(r)) - case Not(Equals(l, r)) => z3.mkDistinct(rec(l), rec(r)) - case Not(e) => z3.mkNot(rec(e)) - case IntLiteral(v) => z3.mkInt(v, intSort) - case BooleanLiteral(v) => if (v) z3.mkTrue() else z3.mkFalse() - case UnitLiteral => unitValue - case Equals(l, r) => z3.mkEq(rec(l), rec(r)) - case Plus(l, r) => if(USEBV) z3.mkBVAdd(rec(l), rec(r)) else z3.mkAdd(rec(l), rec(r)) - case Minus(l, r) => if(USEBV) z3.mkBVSub(rec(l), rec(r)) else z3.mkSub(rec(l), rec(r)) - case Times(l, r) => if(USEBV) z3.mkBVMul(rec(l), rec(r)) else z3.mkMul(rec(l), rec(r)) - case Division(l, r) => if(USEBV) z3.mkBVSdiv(rec(l), rec(r)) else z3.mkDiv(rec(l), rec(r)) - case Modulo(l, r) => if(USEBV) sys.error("I don't know what to do here!") else z3.mkMod(rec(l), rec(r)) - case UMinus(e) => if(USEBV) z3.mkBVNeg(rec(e)) else z3.mkUnaryMinus(rec(e)) - case LessThan(l, r) => if(USEBV) z3.mkBVSlt(rec(l), rec(r)) else z3.mkLT(rec(l), rec(r)) - case LessEquals(l, r) => if(USEBV) z3.mkBVSle(rec(l), rec(r)) else z3.mkLE(rec(l), rec(r)) - case GreaterThan(l, r) => if(USEBV) z3.mkBVSgt(rec(l), rec(r)) else z3.mkGT(rec(l), rec(r)) - case GreaterEquals(l, r) => if(USEBV) z3.mkBVSge(rec(l), rec(r)) else z3.mkGE(rec(l), rec(r)) - case c@CaseClass(cd, args) => { - val constructor = adtConstructors(cd) - constructor(args.map(rec(_)): _*) - } - case c@CaseClassSelector(_, cc, sel) => { - val selector = adtFieldSelectors(sel) - selector(rec(cc)) - } - case c@CaseClassInstanceOf(ccd, e) => { - val tester = adtTesters(ccd) - tester(rec(e)) - } - case f@FunctionInvocation(fd, args) => { - z3.mkApp(functionDefToDecl(fd), args.map(rec(_)): _*) - } - case e@EmptySet(_) => z3.mkEmptySet(typeToSort(e.getType.asInstanceOf[SetType].base)) - - case SetEquals(s1, s2) => z3.mkEq(rec(s1), rec(s2)) - case ElementOfSet(e, s) => z3.mkSetSubset(z3.mkSetAdd(z3.mkEmptySet(typeToSort(e.getType)), rec(e)), rec(s)) - case SubsetOf(s1, s2) => z3.mkSetSubset(rec(s1), rec(s2)) - case SetIntersection(s1, s2) => z3.mkSetIntersect(rec(s1), rec(s2)) - case SetUnion(s1, s2) => z3.mkSetUnion(rec(s1), rec(s2)) - case SetDifference(s1, s2) => z3.mkSetDifference(rec(s1), rec(s2)) - case f@FiniteSet(elems) => elems.foldLeft(z3.mkEmptySet(typeToSort(f.getType.asInstanceOf[SetType].base)))((ast, el) => z3.mkSetAdd(ast, rec(el))) - case SetCardinality(s) => { - val rs = rec(s) - setCardFuns(s.getType.asInstanceOf[SetType].base)(rs) - } - case SetMin(s) => intSetMinFun(rec(s)) - case SetMax(s) => intSetMaxFun(rec(s)) - case s @ SingletonMap(from,to) => s.getType match { - case MapType(fromType, toType) => - val fromSort = typeToSort(fromType) - val toSort = typeToSort(toType) - val constArray = z3.mkConstArray(fromSort, mapRangeNoneConstructors(toType)()) - z3.mkStore(constArray, rec(from), mapRangeSomeConstructors(toType)(rec(to))) - case errorType => scala.sys.error("Unexpected type for singleton map: " + (ex, errorType)) - } - case e @ EmptyMap(fromType, toType) => { - typeToSort(e.getType) //had to add this here because the mapRangeNoneConstructors was not yet constructed... - val fromSort = typeToSort(fromType) - val toSort = typeToSort(toType) - z3.mkConstArray(fromSort, mapRangeNoneConstructors(toType)()) - } - case f @ FiniteMap(elems) => f.getType match { - case MapType(fromType, toType) => - val fromSort = typeToSort(fromType) - val toSort = typeToSort(toType) - elems.foldLeft(z3.mkConstArray(fromSort, mapRangeNoneConstructors(toType)())){ case (ast, SingletonMap(k,v)) => z3.mkStore(ast, rec(k), mapRangeSomeConstructors(toType)(rec(v))) } - case errorType => scala.sys.error("Unexpected type for finite map: " + (ex, errorType)) - } - case mg @ MapGet(m,k) => m.getType match { - case MapType(fromType, toType) => - val selected = z3.mkSelect(rec(m), rec(k)) - mapRangeValueSelectors(toType)(selected) - case errorType => scala.sys.error("Unexpected type for map: " + (ex, errorType)) - } - case MapUnion(m1,m2) => m1.getType match { - case MapType(ft, tt) => m2 match { - case FiniteMap(ss) => - ss.foldLeft(rec(m1)){ - case (ast, SingletonMap(k, v)) => z3.mkStore(ast, rec(k), mapRangeSomeConstructors(tt)(rec(v))) - } - case SingletonMap(k, v) => z3.mkStore(rec(m1), rec(k), mapRangeSomeConstructors(tt)(rec(v))) - case _ => scala.sys.error("map updates can only be applied with concrete map instances") - } - case errorType => scala.sys.error("Unexpected type for map: " + (ex, errorType)) - } - case MapIsDefinedAt(m,k) => m.getType match { - case MapType(ft, tt) => z3.mkDistinct(z3.mkSelect(rec(m), rec(k)), mapRangeNoneConstructors(tt)()) - case errorType => scala.sys.error("Unexpected type for map: " + (ex, errorType)) - } - case fill@ArrayFill(length, default) => { - val at@ArrayType(base) = fill.getType - typeToSort(at) - val cons = arrayTupleCons(at) - val ar = z3.mkConstArray(typeToSort(base), rec(default)) - val res = cons(ar, rec(length)) - res - } - case ArraySelect(a, index) => { - typeToSort(a.getType) - val ar = rec(a) - val getArray = arrayTupleSelectorArray(a.getType) - val res = z3.mkSelect(getArray(ar), rec(index)) - res - } - case ArrayUpdated(a, index, newVal) => { - typeToSort(a.getType) - val ar = rec(a) - val getArray = arrayTupleSelectorArray(a.getType) - val getLength = arrayTupleSelectorLength(a.getType) - val cons = arrayTupleCons(a.getType) - val store = z3.mkStore(getArray(ar), rec(index), rec(newVal)) - val res = cons(store, getLength(ar)) - res - } - case ArrayLength(a) => { - typeToSort(a.getType) - val ar = rec(a) - val getLength = arrayTupleSelectorLength(a.getType) - val res = getLength(ar) - res - } - case AnonymousFunctionInvocation(id, args) => id.getType match { - case ft @ FunctionType(fts, tt) => { - val consFD = funDomainConstructors(ft) - val rid = rec(Variable(id)) - val rargs = args map rec - z3.mkSelect(rid, consFD(rargs: _*)) - } - case errorType => scala.sys.error("Unexpected type for function: " + (ex, errorType)) - } - - case Distinct(exs) => z3.mkDistinct(exs.map(rec(_)): _*) - - case _ => { - reporter.warning("Can't handle this in translation to Z3: " + ex) - throw new CantTranslateException - } - }) - recResult - } - - try { - val res = Some(rec(expr)) - res - } catch { - case e: CantTranslateException => None - } - } - - class UnrollingBank { private val blockMap : MutableMap[(Identifier,Boolean),Set[FunctionInvocation]] = MutableMap.empty private def registerBlocked(id: Identifier, pol: Boolean, fi: FunctionInvocation) : Boolean = diff --git a/src/main/scala/leon/solvers/z3/UninterpretedZ3Solver.scala b/src/main/scala/leon/solvers/z3/UninterpretedZ3Solver.scala new file mode 100644 index 000000000..10162c266 --- /dev/null +++ b/src/main/scala/leon/solvers/z3/UninterpretedZ3Solver.scala @@ -0,0 +1,86 @@ +package leon +package solvers.z3 + +import z3.scala._ +import purescala.Common._ +import purescala.Definitions._ +import purescala.Trees._ +import purescala.Extractors._ +import purescala.TreeOps._ +import purescala.TypeTrees._ +import Extensions._ + +/** This is a rather direct mapping to Z3, where all functions are left uninterpreted. + * It reports the results as follows (based on the negation of the formula): + * - if Z3 reports UNSAT, it reports VALID + * - if Z3 reports SAT and the formula contained no function invocation, it returns INVALID with the counter-example/model + * - otherwise it returns UNKNOWN + * Results should come back very quickly. + */ +class UninterpretedZ3Solver(reporter : Reporter) extends Solver(reporter) with AbstractZ3Solver with Z3ModelReconstruction { + val description = "Uninterpreted Z3 Solver" + override val shortDescription = "Z3-u" + + // this is fixed + protected[leon] val z3cfg = new Z3Config( + "MODEL" -> true, + "MBQI" -> false, + "TYPE_CHECK" -> true, + "WELL_SORTED_CHECK" -> true + ) + toggleWarningMessages(true) + + private var functionMap: Map[FunDef, Z3FuncDecl] = Map.empty + private var reverseFunctionMap: Map[Z3FuncDecl, FunDef] = Map.empty + protected[leon] def prepareFunctions : Unit = { + functionMap = Map.empty + reverseFunctionMap = Map.empty + for(funDef <- program.definedFunctions) { + val sortSeq = funDef.args.map(vd => typeToSort(vd.tpe)) + val returnSort = typeToSort(funDef.returnType) + + val z3Decl = z3.mkFreshFuncDecl(funDef.id.name, sortSeq, returnSort) + functionMap = functionMap + (funDef -> z3Decl) + reverseFunctionMap = reverseFunctionMap + (z3Decl -> funDef) + } + } + protected[leon] def functionDefToDecl(funDef: FunDef) : Z3FuncDecl = functionMap(funDef) + protected[leon] def functionDeclToDef(decl: Z3FuncDecl) : FunDef = reverseFunctionMap(decl) + protected[leon] def isKnownDecl(decl: Z3FuncDecl) : Boolean = reverseFunctionMap.isDefinedAt(decl) + + override def solve(expression: Expr) : Option[Boolean] = solveOrGetCounterexample(expression)._1 + + // Where the solving occurs + override def solveOrGetCounterexample(expression : Expr) : (Option[Boolean],Map[Identifier,Expr]) = { + val emptyModel = Map.empty[Identifier,Expr] + val unknownResult = (None, emptyModel) + val validResult = (Some(true), emptyModel) + + containedInvocations = false + val result = toZ3Formula(expression).map { asZ3 => + z3.assertCnstr(z3.mkNot(asZ3)) + z3.checkAndGetModel() match { + case (Some(false), _) => validResult + case (Some(true), model) if !containedInvocations => { + val variables = variablesOf(expression) + val r = (Some(false), modelToMap(model, variables)) + model.delete + r + } + case _ => unknownResult + } + } getOrElse unknownResult + + result + } + + private var containedInvocations : Boolean = _ + + override def toZ3Formula(expr : Expr, m : Map[Identifier,Z3AST] = Map.empty) : Option[Z3AST] = { + expr match { + case FunctionInvocation(_, _) => containedInvocations = true + case _ => ; + } + super.toZ3Formula(expr,m) + } +} -- GitLab