From 1a0b9f9387fbbf48c39757c9bb2e2389b37597df Mon Sep 17 00:00:00 2001
From: Philippe Suter <philippe.suter@gmail.com>
Date: Fri, 4 Jan 2013 00:44:09 +0100
Subject: [PATCH] --evalground makes FairZ3 evaluate ground applications
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Without the flag, functions applied to ground arguments are treated the
same way as every other one: by unrolling their body. This is
suboptimal, as we can instead pass to Z3 the equality f(a0, a1) = v,
instead of letting it "discover" it by itself.

Note that this hasn't been shown to bring any major performance
improvement; ground applications hardly show up in verification, for
instance. But think about it, you'll agree using evaluation there is
"The right thing to do.™".

Note that testing --evalground currently highlights some bugs.
---
 .../leon/solvers/z3/AbstractZ3Solver.scala    | 42 +++++++++++++++++++
 .../scala/leon/solvers/z3/FairZ3Solver.scala  | 20 +++++----
 .../leon/solvers/z3/FunctionTemplate.scala    | 34 ++++++++++++---
 .../PureScalaVerificationRegression.scala     |  2 +-
 4 files changed, 83 insertions(+), 15 deletions(-)

diff --git a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala
index 2a14a92f5..b3e8df10c 100644
--- a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala
+++ b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala
@@ -690,6 +690,48 @@ trait AbstractZ3Solver extends solvers.IncrementalSolverBuilder {
     rec(tree, expectedType)
   }
 
+  // Tries to convert a Z3AST into a *ground* Expr. Doesn't try very hard, because
+  //   1) we assume Z3 simplifies ground terms, so why match for +, etc, and
+  //   2) we use this precisely in one context, where we know function invocations won't show up, etc.
+  protected[leon] def asGround(tree : Z3AST) : Option[Expr] = {
+    val e = new Exception("Not ground.")
+
+    def rec(t : Z3AST) : Expr = z3.getASTKind(t) match {
+      case Z3AppAST(decl, args) => {
+        val argsSize = args.size
+        if(isKnownDecl(decl)) {
+          val fd = functionDeclToDef(decl)
+          FunctionInvocation(fd, args.map(rec))
+        } else if(argsSize == 1 && reverseADTTesters.isDefinedAt(decl)) {
+          CaseClassInstanceOf(reverseADTTesters(decl), rec(args(0)))
+        } else if(argsSize == 1 && reverseADTFieldSelectors.isDefinedAt(decl)) {
+          val (ccd, fid) = reverseADTFieldSelectors(decl)
+          CaseClassSelector(ccd, rec(args(0)), fid)
+        } else if(reverseADTConstructors.isDefinedAt(decl)) {
+          val ccd = reverseADTConstructors(decl)
+          CaseClass(ccd, args.map(rec))
+        } else if(reverseTupleConstructors.isDefinedAt(decl)) {
+          Tuple(args.map(rec))
+        } else {
+          import Z3DeclKind._
+          z3.getDeclKind(decl) match {
+            case OpTrue => BooleanLiteral(true)
+            case OpFalse => BooleanLiteral(false)
+            case _ => throw e
+          }
+        }
+      }
+      case Z3NumeralAST(Some(v)) => IntLiteral(v)
+      case _ => throw e
+    }
+
+    try {
+      Some(rec(tree))
+    } catch {
+      case e : Exception => None
+    }
+  }
+
   protected[leon] def softFromZ3Formula(model: Z3Model, tree : Z3AST, expectedType: TypeTree) : Option[Expr] = {
     try {
       Some(fromZ3Formula(model, tree, Some(expectedType)))
diff --git a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
index 3523e1ce7..641b86c81 100644
--- a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
+++ b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
@@ -29,28 +29,32 @@ class FairZ3Solver(context : LeonContext)
   val description = "Fair Z3 Solver"
 
   override val definedOptions : Set[LeonOptionDef] = Set(
+    LeonFlagOptionDef("evalground",   "--evalground",   "Use evaluator on functions applied to ground arguments"),
     LeonFlagOptionDef("checkmodels",  "--checkmodels",  "Double-check counter-examples with evaluator"),
     LeonFlagOptionDef("feelinglucky", "--feelinglucky", "Use evaluator to find counter-examples early"),
     LeonFlagOptionDef("codegen",      "--codegen",      "Use compiled evaluator instead of interpreter")
   )
 
   // What wouldn't we do to avoid defining vars?
-  val (feelingLucky, checkModels, useCodeGen) = locally {
-    var lucky   = false
-    var check   = false
-    var codegen = false
+  val (feelingLucky, checkModels, useCodeGen, evalGroundApps) = locally {
+    var lucky      = false
+    var check      = false
+    var codegen    = false
+    var evalground = false
 
     for(opt <- context.options) opt match {
-      case LeonFlagOption("checkmodels")  => check   = true
-      case LeonFlagOption("feelinglucky") => lucky   = true
-      case LeonFlagOption("codegen")      => codegen = true
+      case LeonFlagOption("checkmodels")  => check      = true
+      case LeonFlagOption("feelinglucky") => lucky      = true
+      case LeonFlagOption("codegen")      => codegen    = true
+      case LeonFlagOption("evalground")   => evalground = true
       case _ =>
     }
 
-    (lucky,check,codegen)
+    (lucky,check,codegen,evalground)
   }
 
   private var evaluator : Evaluator = null
+  protected[z3] def getEvaluator : Evaluator = evaluator
 
   override def setProgram(prog : Program) {
     super.setProgram(prog)
diff --git a/src/main/scala/leon/solvers/z3/FunctionTemplate.scala b/src/main/scala/leon/solvers/z3/FunctionTemplate.scala
index 2d6399660..66cb6af25 100644
--- a/src/main/scala/leon/solvers/z3/FunctionTemplate.scala
+++ b/src/main/scala/leon/solvers/z3/FunctionTemplate.scala
@@ -1,7 +1,6 @@
 package leon
 package solvers.z3
 
-import z3.scala._
 import purescala.Common._
 import purescala.Trees._
 import purescala.Extractors._
@@ -9,18 +8,22 @@ import purescala.TreeOps._
 import purescala.TypeTrees._
 import purescala.Definitions._
 
+import evaluators._
+
+import z3.scala._
+
 import scala.collection.mutable.{Set=>MutableSet,Map=>MutableMap}
 
 case class Z3FunctionInvocation(funDef: FunDef, args: Seq[Z3AST])
 
-// TODO: maybe a candidate for moving into purescala package?
 class FunctionTemplate private(
-  solver: AbstractZ3Solver,
+  solver: FairZ3Solver,
   val funDef : FunDef,
   activatingBool : Identifier,
   condVars : Set[Identifier],
   exprVars : Set[Identifier],
-  guardedExprs : Map[(Identifier,Boolean),Seq[Expr]]) {
+  guardedExprs : Map[(Identifier,Boolean),Seq[Expr]],
+  isRealFunDef : Boolean) {
 
   private val z3 = solver.z3
 
@@ -67,6 +70,24 @@ class FunctionTemplate private(
   def instantiate(aVar : Z3AST, aPol : Boolean, args : Seq[Z3AST]) : (Seq[Z3AST], Map[(Z3AST, Boolean), Set[Z3FunctionInvocation]]) = {
     assert(args.size == funDef.args.size)
 
+    // The "isRealFunDef" part is to prevent evaluation of "fake" function templates, as generated from FairZ3Solver.
+    if(solver.evalGroundApps && isRealFunDef) {
+      val ga = args.view.map(solver.asGround)
+      if(ga.forall(_.isDefined)) {
+        val leonArgs = ga.map(_.get).force
+        val invocation = FunctionInvocation(funDef, leonArgs)
+        solver.getEvaluator.eval(invocation) match {
+          case EvaluationSuccessful(result) =>
+            val z3Invocation = z3.mkApp(solver.functionDefToDecl(funDef), args: _*)
+            val z3Value      = solver.toZ3Formula(result).get
+            val asZ3         = z3.mkEq(z3Invocation, z3Value)
+            return (Seq(asZ3), Map.empty)
+
+          case _ => throw new Exception("Evaluation of ground term should have succeeded.")
+        }
+      }
+    }
+
     val idSubstMap : Map[Z3AST, Z3AST] =
       Map(z3ActivatingBool -> (if (aPol) aVar else z3.mkNot(aVar))) ++
       (zippedExprVars ++ zippedCondVars).map{ case (id, c) => c -> solver.idToFreshZ3Id(id) } ++
@@ -102,7 +123,7 @@ class FunctionTemplate private(
 }
 
 object FunctionTemplate {
-  def mkTemplate(solver: AbstractZ3Solver, funDef: FunDef, isRealFunDef : Boolean = true) : FunctionTemplate = {
+  def mkTemplate(solver: FairZ3Solver, funDef: FunDef, isRealFunDef : Boolean = true) : FunctionTemplate = {
     val condVars : MutableSet[Identifier] = MutableSet.empty
     val exprVars : MutableSet[Identifier] = MutableSet.empty
 
@@ -210,6 +231,7 @@ object FunctionTemplate {
       storeGuarded(activatingBool, true, finalPred2)
     }
 
-    new FunctionTemplate(solver, funDef, activatingBool, Set(condVars.toSeq : _*), Set(exprVars.toSeq : _*), Map(guardedExprs.toSeq : _*))
+    new FunctionTemplate(solver, funDef, activatingBool, Set(condVars.toSeq : _*), Set(exprVars.toSeq : _*), Map(guardedExprs.toSeq : _*),
+isRealFunDef)
   }
 }
diff --git a/src/test/scala/leon/test/verification/PureScalaVerificationRegression.scala b/src/test/scala/leon/test/verification/PureScalaVerificationRegression.scala
index 74ce1ddc9..c7e712295 100644
--- a/src/test/scala/leon/test/verification/PureScalaVerificationRegression.scala
+++ b/src/test/scala/leon/test/verification/PureScalaVerificationRegression.scala
@@ -68,7 +68,7 @@ class PureScalaVerificationRegression extends FunSuite {
 
     for(f <- fs) {
       mkTest(f, List(LeonFlagOption("feelinglucky")), forError)(block)
-      mkTest(f, List(LeonFlagOption("codegen"), LeonFlagOption("feelinglucky")), forError)(block)
+      mkTest(f, List(LeonFlagOption("codegen"), LeonFlagOption("evalground"), LeonFlagOption("feelinglucky")), forError)(block)
     }
   }
   
-- 
GitLab