From 7687b79788b4514cb3c9dfdb00317382de3eb4cd Mon Sep 17 00:00:00 2001
From: Etienne Kneuss <ekneuss@gmail.com>
Date: Wed, 9 Jan 2013 15:49:52 +0100
Subject: [PATCH] CEGIS: Implement enumerate&test given known valid inputs

CE-Testing allows CEGIS to compile the program at the current unrolling
level and test with provided Bs (the determinized program) on previously
discovered inputs. This allows us to skip a SAT query in case one of
them fails.

Given a series of known valid inputs, it can prune program candidates by
enumerating them and testing against these inputs. This should
dramatically reduce the number of SAT queries. Currently we have no
heuristic to disable enumarting when branching factor becomes too big.
We might want to do random sampling.

We start by figuring out one basic examples that we can use for pruning.
In the presence of a path-condition, we need to perform a simple SAT
query

Also, B-Paths enforces sub-branches to use a fixed value if the parent
branch is closed. This should prune the program exploration behind
closed branches. We have observed no clear benefits in terms of
performance yet.

CEGIS will only use fully determined functions as candidates for
gencalls. (e.g. no functions containing 'choose')
---
 .../scala/leon/codegen/CodeGeneration.scala   |  33 +-
 .../scala/leon/codegen/CompilationUnit.scala  |  12 +-
 .../leon/evaluators/CodeGenEvaluator.scala    |  41 +-
 .../leon/evaluators/DefaultEvaluator.scala    |   7 +-
 .../scala/leon/solvers/z3/FairZ3Solver.scala  | 140 ++++-
 .../leon/solvers/z3/FunctionTemplate.scala    |  52 ++
 .../leon/synthesis/SynthesisContext.scala     |   2 +
 .../leon/synthesis/SynthesizerOptions.scala   |  19 +-
 .../scala/leon/synthesis/rules/Cegis.scala    | 537 ++++++++++++++----
 .../leon/synthesis/utils/Benchmarks.scala     |   1 +
 .../test/evaluators/EvaluatorsTests.scala     |  31 +-
 .../leon/test/synthesis/SynthesisSuite.scala  |   2 +-
 .../XLangVerificationRegression.scala         |   2 -
 13 files changed, 710 insertions(+), 169 deletions(-)

diff --git a/src/main/scala/leon/codegen/CodeGeneration.scala b/src/main/scala/leon/codegen/CodeGeneration.scala
index c495ccb5d..0afef05bf 100644
--- a/src/main/scala/leon/codegen/CodeGeneration.scala
+++ b/src/main/scala/leon/codegen/CodeGeneration.scala
@@ -45,6 +45,9 @@ object CodeGeneration {
     case _ : MapType =>
       "L" + MapClass + ";"
 
+    case ArrayType(base) =>
+      "[" + typeToJVM(base)
+
     case _ => throw CompilationException("Unsupported type : " + tpe)
   }
 
@@ -61,7 +64,7 @@ object CodeGeneration {
       case Int32Type | BooleanType | UnitType =>
         ch << IRETURN
 
-      case _ : ClassType | _ : TupleType | _ : SetType | _ : MapType =>
+      case _ : ClassType | _ : TupleType | _ : SetType | _ : MapType | _ : ArrayType =>
         ch << ARETURN
 
       case other =>
@@ -282,6 +285,34 @@ object CodeGeneration {
         mkExpr(e, ch)
         ch << INEG
 
+      case ArrayLength(a) =>
+        mkExpr(a, ch)
+        ch << ARRAYLENGTH
+
+      case as @ ArraySelect(a,i) =>
+        mkExpr(a, ch)
+        mkExpr(i, ch)
+        ch << (as.getType match {
+          case Untyped => throw CompilationException("Cannot compile untyped array access.")
+          case Int32Type => IALOAD
+          case BooleanType => BALOAD
+          case _ => AALOAD
+        })
+
+      case a @ FiniteArray(es) =>
+        ch << Ldc(es.size)
+        val storeInstr = a.getType match {
+          case ArrayType(Int32Type) => ch << NewArray.primitive("T_INT"); IASTORE
+          case ArrayType(BooleanType) => ch << NewArray.primitive("T_BOOLEAN"); BASTORE
+          case ArrayType(other) => ch << NewArray(typeToJVM(other)); AASTORE
+          case other => throw CompilationException("Cannot compile finite array expression whose type is %s.".format(other))
+        }
+        for((e,i) <- es.zipWithIndex) {
+          ch << DUP << Ldc(i)
+          mkExpr(e, ch) 
+          ch << storeInstr
+        }
+
       // Misc and boolean tests
       case Error(desc) =>
         ch << New(ErrorClass) << DUP
diff --git a/src/main/scala/leon/codegen/CompilationUnit.scala b/src/main/scala/leon/codegen/CompilationUnit.scala
index 799241c5c..0d547d27a 100644
--- a/src/main/scala/leon/codegen/CompilationUnit.scala
+++ b/src/main/scala/leon/codegen/CompilationUnit.scala
@@ -67,7 +67,12 @@ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFi
       val cons = caseClassConstructors(ccd)
       cons.newInstance(args.map(valueToJVM).toArray : _*).asInstanceOf[AnyRef]
 
-    // just slightly overkill...
+    // For now, we only treat boolean arrays separately.
+    // We have a use for these, mind you.
+    case f @ FiniteArray(exprs) if f.getType == ArrayType(BooleanType) =>
+      exprs.map(e => valueToJVM(e).asInstanceOf[java.lang.Boolean].booleanValue).toArray
+
+    // Just slightly overkill...
     case _ =>
       compileExpression(e, Seq()).evalToJVM(Seq())
   }
@@ -112,6 +117,9 @@ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFi
   }
 
   def compileExpression(e: Expr, args: Seq[Identifier]): CompiledExpression = {
+    if(e.getType == Untyped) {
+      throw new IllegalArgumentException("Cannot compile untyped expression [%s].".format(e))
+    }
 
     val id = nextExprId
 
@@ -149,7 +157,7 @@ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFi
       case Int32Type | BooleanType =>
         ch << IRETURN
 
-      case UnitType | TupleType(_)  | SetType(_) | MapType(_, _) | AbstractClassType(_) | CaseClassType(_) =>
+      case UnitType | TupleType(_)  | SetType(_) | MapType(_, _) | AbstractClassType(_) | CaseClassType(_) | ArrayType(_) =>
         ch << ARETURN
 
       case other =>
diff --git a/src/main/scala/leon/evaluators/CodeGenEvaluator.scala b/src/main/scala/leon/evaluators/CodeGenEvaluator.scala
index 7202c4ff1..f45372327 100644
--- a/src/main/scala/leon/evaluators/CodeGenEvaluator.scala
+++ b/src/main/scala/leon/evaluators/CodeGenEvaluator.scala
@@ -30,21 +30,30 @@ class CodeGenEvaluator(ctx : LeonContext, val unit : CompilationUnit) extends Ev
     import leon.codegen.runtime.LeonCodeGenRuntimeException
     import leon.codegen.runtime.LeonCodeGenEvaluationException
 
-    val ce = unit.compileExpression(expression, argorder)
-
-    Some((args : Seq[Expr]) => {
-      try {
-        EvaluationSuccessful(ce.eval(args))
-      } catch {
-        case e : ArithmeticException =>
-          EvaluationFailure(e.getMessage)
-
-        case e : LeonCodeGenRuntimeException =>
-          EvaluationFailure(e.getMessage)
-
-        case e : LeonCodeGenEvaluationException =>
-          EvaluationError(e.getMessage)
-      }
-    })
+    try {
+      val ce = unit.compileExpression(expression, argorder)
+
+      Some((args : Seq[Expr]) => {
+        try {
+          EvaluationSuccessful(ce.eval(args))
+        } catch {
+          case e : ArithmeticException =>
+            EvaluationFailure(e.getMessage)
+
+          case e : ArrayIndexOutOfBoundsException =>
+            EvaluationFailure(e.getMessage)
+
+          case e : LeonCodeGenRuntimeException =>
+            EvaluationFailure(e.getMessage)
+
+          case e : LeonCodeGenEvaluationException =>
+            EvaluationError(e.getMessage)
+        }
+      })
+    } catch {
+      case t: Throwable =>
+        ctx.reporter.warning("Error while compiling expression: "+t.getMessage)
+        None
+    }
   }
 }
diff --git a/src/main/scala/leon/evaluators/DefaultEvaluator.scala b/src/main/scala/leon/evaluators/DefaultEvaluator.scala
index 9b0bbdc2e..47d86be95 100644
--- a/src/main/scala/leon/evaluators/DefaultEvaluator.scala
+++ b/src/main/scala/leon/evaluators/DefaultEvaluator.scala
@@ -253,7 +253,11 @@ class DefaultEvaluator(ctx : LeonContext, prog : Program) extends Evaluator(ctx,
         case ArraySelect(a, i) => {
           val IntLiteral(index) = rec(ctx, i)
           val FiniteArray(exprs) = rec(ctx, a)
-          exprs(index)
+          try {
+            exprs(index)
+          } catch {
+            case e : IndexOutOfBoundsException => throw RuntimeError(e.getMessage)
+          }
         }
         case FiniteArray(exprs) => {
           FiniteArray(exprs.map(e => rec(ctx, e)))
@@ -297,6 +301,7 @@ class DefaultEvaluator(ctx : LeonContext, prog : Program) extends Evaluator(ctx,
     try {
       EvaluationSuccessful(rec(mapping, expression))
     } catch {
+      case so: StackOverflowError => EvaluationError("Stack overflow")
       case EvalError(msg) => EvaluationError(msg)
       case RuntimeError(msg) => EvaluationFailure(msg)
     }
diff --git a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
index 641b86c81..0b79871ba 100644
--- a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
+++ b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
@@ -36,11 +36,13 @@ class FairZ3Solver(context : LeonContext)
   )
 
   // What wouldn't we do to avoid defining vars?
-  val (feelingLucky, checkModels, useCodeGen, evalGroundApps) = locally {
-    var lucky      = false
-    var check      = false
-    var codegen    = false
-    var evalground = false
+  val (feelingLucky, checkModels, useCodeGen, evalGroundApps, unrollUnsatCores, pruneUnsatCores) = locally {
+    var lucky            = true
+    var check            = false
+    var codegen          = false
+    var evalground       = false
+    var unrollUnsatCores = true
+    var pruneUnsatCores  = true
 
     for(opt <- context.options) opt match {
       case LeonFlagOption("checkmodels")  => check      = true
@@ -50,7 +52,7 @@ class FairZ3Solver(context : LeonContext)
       case _ =>
     }
 
-    (lucky,check,codegen,evalground)
+    (lucky, check, codegen, evalground, unrollUnsatCores, pruneUnsatCores)
   }
 
   private var evaluator : Evaluator = null
@@ -175,7 +177,7 @@ class FairZ3Solver(context : LeonContext)
           (false, asMap)
 
         case EvaluationError(msg) => 
-          reporter.error("Something went wrong. While evaluating the model, we got this : " + msg)
+          reporter.warning("Something went wrong. While evaluating the model, we got this : " + msg)
           (false, asMap)
 
       }
@@ -210,7 +212,7 @@ class FairZ3Solver(context : LeonContext)
     // Keep which function invocation is guarded by which guard with polarity,
     // also specify the generation of the blocker
 
-    private var blockersInfoStack : List[MutableMap[(Z3AST,Boolean), (Int, Z3AST, Set[Z3FunctionInvocation])]] = List(MutableMap())
+    private var blockersInfoStack : List[MutableMap[(Z3AST,Boolean), (Int, Int, Z3AST, Set[Z3FunctionInvocation])]] = List(MutableMap())
 
     def blockersInfo = blockersInfoStack.head
 
@@ -222,14 +224,28 @@ class FairZ3Solver(context : LeonContext)
       blockersInfoStack = blockersInfoStack.drop(lvl)
     }
 
-    def z3CurrentZ3Blockers = blockersInfo.map(_._2._2)
+    def z3CurrentZ3Blockers = blockersInfo.map(_._2._3)
+
+    def dumpBlockers = {
+      blockersInfo.groupBy(_._2._1).toSeq.sortBy(_._1).foreach { case (gen, entries) =>
+        reporter.info("--- "+gen)
+
+        for (((bast, bpol), (gen, origGen, ast, fis)) <- entries) {
+          reporter.info(".     "+(if(!bpol) "¬" else "")+bast +" ~> "+fis.map(_.funDef.id))
+        }
+      }
+    }
 
     def canUnroll = !blockersInfo.isEmpty
 
     def getZ3BlockersToUnlock: Seq[(Z3AST, Boolean)] = {
-      val minGeneration = blockersInfo.values.map(_._1).min
+      if (!blockersInfo.isEmpty) {
+        val minGeneration = blockersInfo.values.map(_._1).min
 
-      blockersInfo.filter(_._2._1 == minGeneration).toSeq.map(_._1)
+        blockersInfo.filter(_._2._1 == minGeneration).toSeq.map(_._1)
+      } else {
+        Seq()
+      }
     }
 
     private def registerBlocker(gen: Int, id: Z3AST, pol: Boolean, fis: Set[Z3FunctionInvocation]) {
@@ -238,12 +254,12 @@ class FairZ3Solver(context : LeonContext)
       val z3ast          = if (pol) z3.mkNot(id) else id
 
       blockersInfo.get(pair) match {
-        case Some((exGen, _, exFis)) =>
+        case Some((exGen, origGen, _, exFis)) =>
           assert(exGen == gen, "Mixing the same pair "+pair+" with various generations "+ exGen+" and "+gen)
 
-          blockersInfo(pair) = ((gen, z3ast, fis++exFis))
+          blockersInfo(pair) = ((gen, origGen, z3ast, fis++exFis))
         case None =>
-          blockersInfo(pair) = ((gen, z3ast, fis))
+          blockersInfo(pair) = ((gen, gen, z3ast, fis))
       }
     }
 
@@ -264,18 +280,42 @@ class FairZ3Solver(context : LeonContext)
 
       val (newClauses, newBlocks) = template.instantiate(template.z3ActivatingBool, true, z3args)
 
-      for(((i, p), fis) <- newBlocks) {
-        registerBlocker(1, i, p, fis)
+      val extraClauses = for(((i, p), fis) <- newBlocks) yield {
+        registerBlocker(nextGeneration(0), i, p, fis)
+        //unlock(i, p)
+        Nil
+      }
+
+      newClauses ++ extraClauses.flatten
+    }
+
+    def nextGeneration(gen: Int) = gen + 3
+
+    def decreaseAllGenerations() = {
+      for ((block, (gen, origGen, ast, finvs)) <- blockersInfo) {
+        // We also decrease the original generation here
+        blockersInfo(block) = (math.max(1,gen-1), math.max(1,origGen-1), ast, finvs)
+      }
+    }
+
+    def prune(ast: Z3AST, pol: Boolean) = {
+      val b = (ast, pol)
+      blockersInfo -= b
+    }
+
+    def promoteBlocker(bast: Z3AST, pol: Boolean) = {
+      val b = (bast, pol)
+      if (blockersInfo contains b) {
+        val (gen, origGen, ast, finvs) = blockersInfo(b)
+        blockersInfo(b) = (1, origGen, ast, finvs)
       }
-      
-      newClauses
     }
 
     def unlock(id: Z3AST, pol: Boolean) : Seq[Z3AST] = {
       val pair = (id, pol)
       assert(blockersInfo contains pair)
 
-      val (gen, _, fis) = blockersInfo(pair)
+      val (gen, origGen, _, fis) = blockersInfo(pair)
       blockersInfo -= pair
 
       var newClauses : Seq[Z3AST] = Seq.empty
@@ -285,7 +325,8 @@ class FairZ3Solver(context : LeonContext)
         val (newExprs, newBlocks) = template.instantiate(id, pol, fi.args)
 
         for(((i, p), fis) <- newBlocks) {
-          registerBlocker(gen+1, i, p, fis)
+          // We use origGen here
+          registerBlocker(nextGeneration(origGen), i, p, fis)
         }
 
         newClauses ++= newExprs
@@ -405,9 +446,9 @@ class FairZ3Solver(context : LeonContext)
 
         reporter.info(" - Running Z3 search...")
 
-        //reporter.info("Searching in:\n"+solver.getAssertions.toSeq.mkString("\nAND\n"))
-        //reporter.info("Unroll.  Assumptions:\n"+unrollingBank.z3CurrentZ3Blockers.mkString("  &&  "))
-        //reporter.info("Userland Assumptions:\n"+assumptionsAsZ3.mkString("  &&  "))
+        reporter.info("Searching in:\n"+solver.getAssertions.toSeq.mkString("\nAND\n"))
+        reporter.info("Unroll.  Assumptions:\n"+unrollingBank.z3CurrentZ3Blockers.mkString("  &&  "))
+        reporter.info("Userland Assumptions:\n"+assumptionsAsZ3.mkString("  &&  "))
 
         z3Time.start
         solver.push() // FIXME: remove when z3 bug is fixed
@@ -459,9 +500,47 @@ class FairZ3Solver(context : LeonContext)
           // distinction is made inside.
           case Some(false) =>
 
-            //val core = z3CoreToCore(solver.getUnsatCore)
+            val z3Core = solver.getUnsatCore
 
-            //reporter.info("UNSAT BECAUSE: "+solver.getUnsatCore.mkString("  AND  "))
+            def coreElemToBlocker(c: Z3AST): (Z3AST, Boolean) = {
+              z3.getASTKind(c) match {
+                case Z3AppAST(decl, args) =>
+                  z3.getDeclKind(decl) match {
+                    case Z3DeclKind.OpNot =>
+                      (args(0), true)
+                    case Z3DeclKind.OpUninterpreted =>
+                      (c, false)
+                  }
+
+                case ast =>
+                  (c, false)
+              }
+            }
+
+            if (unrollUnsatCores) {
+              unrollingBank.decreaseAllGenerations()
+
+              for (c <- solver.getUnsatCore) {
+                val (z3ast, pol) = coreElemToBlocker(c)
+
+                unrollingBank.promoteBlocker(z3ast, pol)
+              }
+
+            }
+
+            if (pruneUnsatCores) {
+              z3Core.toSeq match {
+                case Seq(c) => 
+                  // If there is only one element in the Seq, we can prune
+                  val (z3ast, pol) = coreElemToBlocker(c)
+
+                  unrollingBank.prune(z3ast, !pol)
+                  solver.assertCnstr(if (pol) z3ast else z3.mkNot(z3ast))
+                case _ =>
+              }
+            }
+
+            reporter.info("UNSAT BECAUSE: "+solver.getUnsatCore.mkString("\n  AND  \n"))
             //reporter.info("UNSAT BECAUSE: "+core.mkString("  AND  "))
 
             if (!forceStop) {
@@ -480,10 +559,10 @@ class FairZ3Solver(context : LeonContext)
 
               res2 match {
                 case Some(false) =>
-                  //reporter.info("UNSAT WITHOUT Blockers")
+                  reporter.info("UNSAT WITHOUT Blockers")
                   foundAnswer(Some(false), core = z3CoreToCore(solver.getUnsatCore))
                 case Some(true) =>
-                  //reporter.info("SAT WITHOUT Blockers")
+                  reporter.info("SAT WITHOUT Blockers")
                   if (this.feelingLucky && !forceStop) {
                     // we might have been lucky :D
                     luckyTime.start
@@ -507,15 +586,18 @@ class FairZ3Solver(context : LeonContext)
               reporter.info("- We need to keep going.")
 
               //val toReleaseAsPairs = unrollingBank.getBlockersToUnlock
+              // unrollingBank.dumpBlockers
               val toReleaseAsPairs = unrollingBank.getZ3BlockersToUnlock
 
               reporter.info(" - more unrollings")
 
+              unrollingBank.dumpBlockers
+
               for((id, polarity) <- toReleaseAsPairs) {
                 unlockingTime.start
                 val newClauses = unrollingBank.unlock(id, polarity)
                 unlockingTime.stop
-                //reporter.info(" - - Unrolling behind "+(if(!polarity) "¬" else "")+id)
+                reporter.info(" - - Unrolling behind "+(if(!polarity) "¬" else "")+id)
                 //for (cl <- newClauses) {
                 //  reporter.info(" - - - "+cl)
                 //}
@@ -540,6 +622,8 @@ class FairZ3Solver(context : LeonContext)
       StopwatchCollections.get("FairZ3 Unlocking")   += unlockingTime
       StopwatchCollections.get("FairZ3 ScalaTime")   += scalaTime
 
+      reporter.info(" !! DONE !! ")
+
       if(forceStop) {
         None
       } else {
diff --git a/src/main/scala/leon/solvers/z3/FunctionTemplate.scala b/src/main/scala/leon/solvers/z3/FunctionTemplate.scala
index 66cb6af25..da4de6471 100644
--- a/src/main/scala/leon/solvers/z3/FunctionTemplate.scala
+++ b/src/main/scala/leon/solvers/z3/FunctionTemplate.scala
@@ -142,6 +142,29 @@ object FunctionTemplate {
       }
     }
 
+    // Group elements that satisfy p toghether
+    // List(a, a, a, b, c, a, a), with p = _ == a will produce:
+    // List(List(a,a,a), List(b), List(c), List(a, a))
+    def groupWhile[T](p: T => Boolean, l: Seq[T]): Seq[Seq[T]] = {
+      var res: Seq[Seq[T]] = Nil
+
+      var c = l
+
+      while(!c.isEmpty) {
+        val (span, rest) = c.span(p)
+
+        if (span.isEmpty) {
+          res = res :+ Seq(rest.head)
+          c   = rest.tail
+        } else {
+          res = res :+ span
+          c  = rest
+        }
+      }
+
+      res
+    }
+
     def rec(pathVar : Identifier, pathPol : Boolean, expr : Expr) : Expr = {
       expr match {
         case l @ Let(i, e, b) =>
@@ -154,6 +177,35 @@ object FunctionTemplate {
 
         case m : MatchExpr => sys.error("MatchExpr's should have been eliminated.")
 
+        case i @ Implies(lhs, rhs) =>
+          if (containsFunctionCalls(i)) {
+            rec(pathVar, pathPol, IfExpr(lhs, rhs, BooleanLiteral(true)))
+          } else {
+            i
+          }
+
+        case a @ And(parts) =>
+          if (containsFunctionCalls(a)) {
+            val partitions = groupWhile((e: Expr) => !containsFunctionCalls(e), parts)
+
+            val ifExpr = partitions.map(And(_)).reduceRight{ (a: Expr, b: Expr) => IfExpr(a, b, BooleanLiteral(false)) }
+
+            rec(pathVar, pathPol, ifExpr)
+          } else {
+            a
+          }
+
+        case o @ Or(parts) =>
+          if (containsFunctionCalls(o)) {
+            val partitions = groupWhile((e: Expr) => !containsFunctionCalls(e), parts)
+
+            val ifExpr = partitions.map(Or(_)).reduceRight{ (a: Expr, b: Expr) => IfExpr(a, BooleanLiteral(true), b) }
+
+            rec(pathVar, pathPol, ifExpr)
+          } else {
+            o
+          }
+
         case i @ IfExpr(cond, then, elze) => {
           if(!containsFunctionCalls(cond) && !containsFunctionCalls(then) && !containsFunctionCalls(elze)) {
             i
diff --git a/src/main/scala/leon/synthesis/SynthesisContext.scala b/src/main/scala/leon/synthesis/SynthesisContext.scala
index 1245490fd..92ae31535 100644
--- a/src/main/scala/leon/synthesis/SynthesisContext.scala
+++ b/src/main/scala/leon/synthesis/SynthesisContext.scala
@@ -9,6 +9,7 @@ import purescala.Common.Identifier
 import java.util.concurrent.atomic.AtomicBoolean
 
 case class SynthesisContext(
+  context: LeonContext,
   options: SynthesizerOptions,
   functionContext: Option[FunDef],
   program: Program,
@@ -20,6 +21,7 @@ case class SynthesisContext(
 object SynthesisContext {
   def fromSynthesizer(synth: Synthesizer) = {
     SynthesisContext(
+      synth.context,
       synth.options,
       synth.functionContext,
       synth.program,
diff --git a/src/main/scala/leon/synthesis/SynthesizerOptions.scala b/src/main/scala/leon/synthesis/SynthesizerOptions.scala
index e9d2f4c94..647e4049d 100644
--- a/src/main/scala/leon/synthesis/SynthesizerOptions.scala
+++ b/src/main/scala/leon/synthesis/SynthesizerOptions.scala
@@ -2,11 +2,16 @@ package leon
 package synthesis
 
 case class SynthesizerOptions(
-  generateDerivationTrees: Boolean = false,
-  filterFuns: Option[Set[String]]  = None,
-  searchWorkers: Int               = 1,
-  firstOnly: Boolean               = false,
-  timeoutMs: Option[Long]          = None,
-  costModel: CostModel             = CostModel.default,
-  cegisGenerateFunCalls: Boolean   = false
+  generateDerivationTrees: Boolean    = false,
+  filterFuns: Option[Set[String]]     = None,
+  searchWorkers: Int                  = 1,
+  firstOnly: Boolean                  = false,
+  timeoutMs: Option[Long]             = None,
+  costModel: CostModel                = CostModel.default,
+
+  // Cegis related options
+  cegisGenerateFunCalls: Boolean      = false,
+  cegisUseCETests: Boolean            = true,
+  cegisUseCEPruning: Boolean          = true,
+  cegisUseBPaths: Boolean             = true
 )
diff --git a/src/main/scala/leon/synthesis/rules/Cegis.scala b/src/main/scala/leon/synthesis/rules/Cegis.scala
index 0be347929..2cb41ef38 100644
--- a/src/main/scala/leon/synthesis/rules/Cegis.scala
+++ b/src/main/scala/leon/synthesis/rules/Cegis.scala
@@ -8,6 +8,9 @@ import purescala.Definitions._
 import purescala.TypeTrees._
 import purescala.TreeOps._
 import purescala.Extractors._
+import purescala.ScalaPrinter
+
+import evaluators._
 
 import solvers.z3.FairZ3Solver
 
@@ -15,10 +18,14 @@ case object CEGIS extends Rule("CEGIS") {
   def instantiateOn(sctx: SynthesisContext, p: Problem): Traversable[RuleInstantiation] = {
 
     // CEGIS Flags to actiave or de-activate features
-    val useCounterExamples    = false
+    val useCEAsserts          = false
     val useUninterpretedProbe = false
     val useUnsatCores         = true
     val useFunGenerators      = sctx.options.cegisGenerateFunCalls
+    val useBPaths             = sctx.options.cegisUseBPaths
+    val useCETests            = sctx.options.cegisUseCETests
+    val useCEPruning          = sctx.options.cegisUseCEPruning
+    val evaluator             = new CodeGenEvaluator(sctx.context, sctx.program)
 
     case class Generator(tpe: TypeTree, altBuilder: () => List[(Expr, Set[Identifier])]);
 
@@ -83,7 +90,17 @@ case object CEGIS extends Rule("CEGIS") {
               false
           }
 
-          isSubtypeOf(fd.returnType, t) && !isRecursiveCall
+          val isNotSynthesizable = fd.body match {
+            case Some(b) =>
+              collectChooses(b).isEmpty
+
+            case None =>
+              false
+          }
+
+
+
+          isSubtypeOf(fd.returnType, t) && !isRecursiveCall && isNotSynthesizable
         }
 
         sctx.program.definedFunctions.filter(isCandidate).map{ fd =>
@@ -96,58 +113,255 @@ case object CEGIS extends Rule("CEGIS") {
       }
     }
 
-    class TentativeFormula(val pathcond: Expr,
-                           val phi: Expr,
-                           var program: Expr,
-                           var mappings: Map[Identifier, (Identifier, Expr)],
-                           var recTerms: Map[Identifier, Set[Identifier]]) {
+    class NonDeterministicProgram(val p: Problem,
+                                  val initGuard: Identifier) {
+
+      //var program: Expr = BooleanLiteral(true)
+
+      // b -> (c, ex) means the clause b => c == ex
+      var mappings: Map[Identifier, (Identifier, Expr)] = Map()
+
+      // b -> Set(c1, c2) means c1 and c2 are uninterpreted behing b, requires b to be closed
+      private var guardedTerms: Map[Identifier, Set[Identifier]] = Map(initGuard -> p.xs.toSet)
+
+
+      def isBClosed(b: Identifier) = guardedTerms.contains(b)
+
+      // b -> Map(c1 -> Set(b2, b3), c2 -> Set(b4, b5)) means b protects c1 (with sub alternatives b2/b3), and c2 (with sub b4/b5)
+      private var bTree = Map[Identifier, Map[Identifier, Set[Identifier]]]( initGuard -> p.xs.map(_ -> Set[Identifier]()).toMap)
+
+      // Returns all possible assignments to Bs in order to enumerate all possible programs
+      def allPrograms(): Set[Set[Identifier]] = {
+        def allChildPaths(b: Identifier): Stream[Set[Identifier]] = {
+          if (isBClosed(b)) {
+            Stream.empty
+          } else {
+            bTree.get(b) match {
+              case Some(cToBs) =>
+                val streams = cToBs.values.map { children =>
+                  children.toStream.flatMap(c => allChildPaths(c).map(l => l + b))
+                }
+
+                streams.reduceLeft{ (s1: Stream[Set[Identifier]], s2: Stream[Set[Identifier]]) => for(p1 <- s1; p2 <- s2) yield { p1 ++ p2 } }
+              case None =>
+                Stream.cons(Set(b), Stream.empty)
+            }
+          }
+        }
+
+        allChildPaths(initGuard).toSet
+      }
+
+      /*
+       * Compilation/Execution of programs
+       */
+
+      // b1 => c == F(c2, c3) OR b2 => c == F(c4, c5) is represented here as c -> Set(c2, c3, c4, c5)
+      private var cChildren: Map[Identifier, Set[Identifier]] = Map().withDefaultValue(Set())
+
+
+      private var triedCompilation = false
+      private var progEvaluator: Option[(Seq[Expr], Seq[Expr]) => EvaluationResult] = None
+
+      def canTest() = {
+        if (!triedCompilation) {
+          progEvaluator = compile()
+        }
+
+        progEvaluator.isDefined
+      }
+
+      private var bssOrdered: Seq[Identifier] = Seq()
+
+      def testForProgram(bss: Set[Identifier])(ins: Seq[Expr]): Boolean = {
+        if (canTest()) {
+          val bssValues : Seq[Expr] = bssOrdered.map(i => BooleanLiteral(bss(i)))
+
+          val evalResult = progEvaluator.get.apply(bssValues,  ins)
+
+          evalResult match {
+            case EvaluationSuccessful(res) =>
+              res == BooleanLiteral(true)
+
+            case EvaluationError(err) =>
+              sctx.reporter.error("Error testing CE: "+err)
+              true
+
+            case EvaluationFailure(err) =>
+              sctx.reporter.error("Error testing CE: "+err)
+              true
+          }
+        } else {
+          true
+        }
+      }
+
+      def compile(): Option[(Seq[Expr], Seq[Expr]) => EvaluationResult] = {
+        var unreachableCs: Set[Identifier] = guardedTerms.flatMap(_._2).toSet
+
+        val cToExprs = mappings.groupBy(_._2._1).map {
+          case (c, maps) =>
+            // We only keep cases within the current unrolling closedBs
+            val cases = maps.flatMap{ case (b, (_, ex)) => if (isBClosed(b)) None else Some(b -> ex) }
+
+            // We compute the IF expression corresponding to each c
+            val ifExpr = if (cases.isEmpty) {
+              // This can happen with ADTs with only cases with arguments
+              Error("No valid clause available").setType(c.getType)
+            } else {
+              cases.tail.foldLeft(cases.head._2) {
+                case (elze, (b, then)) => IfExpr(Variable(b), then, elze)
+              }
+            }
+
+            c -> ifExpr
+        } toMap
+
+        // Map each x generated by the program to fresh xs passed as argument
+        val newXs = p.xs.map(x => x -> FreshIdentifier(x.name, true).setType(x.getType))
+
+        val baseExpr = p.phi
+
+        bssOrdered = bss.toSeq.sortBy(_.id)
+
+        var res = baseExpr
+
+        def composeWith(c: Identifier) {
+          cToExprs.get(c) match {
+            case Some(value) =>
+              res = Let(c, cToExprs(c), res)
+            case None =>
+              res = Let(c, Error("No value available").setType(c.getType), res)
+          }
+
+          for (dep <- cChildren(c) if !unreachableCs(dep)) {
+            composeWith(dep)
+          }
+
+        }
+
+        for (c <- p.xs) {
+          composeWith(c)
+        }
+
+        val simplerRes = simplifyLets(res)
+
+        // println("COMPILATION RESULT: ")
+        // println(ScalaPrinter(simplerRes))
+        // println("BSS: "+bssOrdered)
+        // println("FREE: "+variablesOf(simplerRes))
+
+        def compileWithArray(): Option[(Seq[Expr], Seq[Expr]) => EvaluationResult] = {
+          val ba = FreshIdentifier("bssArray").setType(ArrayType(BooleanType))
+          val bav = Variable(ba)
+          val substMap : Map[Expr,Expr] = (bssOrdered.zipWithIndex.map {
+            case (b,i) => Variable(b) -> ArraySelect(bav, IntLiteral(i)).setType(BooleanType)
+          }).toMap
+          val forArray = replace(substMap, simplerRes)
+
+          // println("FORARRAY RESULT: ")
+          // println(ScalaPrinter(forArray))
+          // println("FREE: "+variablesOf(simplerRes))
+
+          // We trust arrays to be fast...
+          // val simple = evaluator.compile(simplerRes, bssOrdered ++ p.as)
+          val eval = evaluator.compile(forArray, ba +: p.as)
+
+          eval.map{e => { case (bss, ins) => 
+            e(FiniteArray(bss).setType(ArrayType(BooleanType)) +: ins)
+          }}
+        }
+
+        def compileWithArgs(): Option[(Seq[Expr], Seq[Expr]) => EvaluationResult] = {
+          val eval = evaluator.compile(simplerRes, bssOrdered ++ p.as)
+
+          eval.map{e => { case (bss, ins) => 
+            e(bss ++ ins)
+          }}
+        }
+
+        triedCompilation = true
+
+        val localVariables = bss.size + cToExprs.size + p.as.size
+
+        if (localVariables < 128) {
+          compileWithArgs().orElse(compileWithArray())
+        } else {
+          compileWithArray()
+        }
+      }
+
+      def determinize(bss: Set[Identifier]): Expr = {
+        val cClauses = mappings.filterKeys(bss).map(_._2).toMap
+
+        def getCValue(c: Identifier): Expr = {
+          val map = for (dep <- cChildren(c) if cClauses contains dep) yield {
+            dep -> getCValue(dep)
+          }
+
+          substAll(map.toMap, cClauses(c))
+        }
+
+        Tuple(p.xs.map(c => getCValue(c))).setType(TupleType(p.xs.map(_.getType)))
+
+      }
 
       def unroll: (List[Expr], Set[Identifier]) = {
-        var newClauses  = List[Expr]()
-        var newRecTerms = Map[Identifier, Set[Identifier]]()
-        var newMappings = Map[Identifier, (Identifier, Expr)]()
+        var newClauses      = List[Expr]()
+        var newGuardedTerms = Map[Identifier, Set[Identifier]]()
+        var newMappings     = Map[Identifier, (Identifier, Expr)]()
+
+        for ((parentGuard, recIds) <- guardedTerms; recId <- recIds) {
 
-        for ((_, recIds) <- recTerms; recId <- recIds) {
           val gen  = getGenerator(recId.getType)
+
           val alts = gen.altBuilder() ::: inputAlternatives(recId.getType) ::: funcAlternatives(recId.getType)
 
-          val altsWithBranches = alts.map(alt => FreshIdentifier("b", true).setType(BooleanType) -> alt)
+          val altsWithBranches = alts.map(alt => FreshIdentifier("B", true).setType(BooleanType) -> alt)
 
-          val bvs = altsWithBranches.map(alt => Variable(alt._1))
-          val distinct = if (bvs.size > 1) {
-            (for (i <- (1 to bvs.size-1); j <- 0 to i-1) yield {
-              Or(Not(bvs(i)), Not(bvs(j)))
-            }).toList
-          } else {
-            List(BooleanLiteral(true))
+          val bvs  = altsWithBranches.map(alt => Variable(alt._1))
+
+          val failedPath = Not(Variable(parentGuard))
+
+          val distinct = bvs.combinations(2).collect {
+            case List(a, b) =>
+              Or(Not(a) :: Not(b) :: Nil)
           }
-          val pre = And(Or(bvs) :: distinct) // (b1 OR b2) AND (Not(b1) OR Not(b2))
+
+          val pre = And(Seq(Or(failedPath :: bvs), Implies(failedPath, And(bvs.map(Not(_))))) ++ distinct)
+
           val cases = for((bid, (ex, rec)) <- altsWithBranches.toList) yield { // b1 => E(gen1, gen2)     [b1 -> {gen1, gen2}]
             if (!rec.isEmpty) {
-              newRecTerms += bid -> rec
+              newGuardedTerms += bid -> rec
+              cChildren       += recId -> (cChildren(recId) ++ rec)
             }
-            newMappings += bid -> (recId -> ex)
+
+            newMappings  += bid -> (recId -> ex)
 
             Implies(Variable(bid), Equals(Variable(recId), ex))
           }
 
+          val newBIds = altsWithBranches.map(_._1).toSet
+          bTree += parentGuard -> (bTree.getOrElse(parentGuard, Map()) + (recId -> newBIds))
+
           newClauses = newClauses ::: pre :: cases
         }
 
-        program  = And(program :: newClauses)
+        //program  = And(program :: newClauses)
 
         mappings = mappings ++ newMappings
 
-        recTerms = newRecTerms
+        guardedTerms = newGuardedTerms
+
+        // Finally, we reset the state of the evalautor
+        triedCompilation = false
+        progEvaluator    = None
 
-        (newClauses, newRecTerms.keySet)
+        (newClauses, newGuardedTerms.keySet)
       }
 
-      def bounds = recTerms.keySet.map(id => Not(Variable(id))).toList
       def bss = mappings.keySet
-      def css : Set[Identifier] = mappings.values.map(_._1).toSet ++ recTerms.flatMap(_._2)
-
-      def entireFormula = And(pathcond :: phi :: program :: bounds)
+      def css : Set[Identifier] = mappings.values.map(_._1).toSet ++ guardedTerms.flatMap(_._2)
     }
 
     val TopLevelAnds(ands) = p.phi
@@ -158,51 +372,121 @@ case object CEGIS extends Rule("CEGIS") {
     val (exprsA, others) = ands.partition(e => (variablesOf(e) & xsSet).isEmpty)
     if (exprsA.isEmpty) {
       val res = new RuleInstantiation(p, this, SolutionBuilder.none) {
-        def apply(sctx: SynthesisContext) = {
+        def apply(sctx: SynthesisContext): RuleApplicationResult = {
           var result: Option[RuleApplicationResult]   = None
 
           var ass = p.as.toSet
           var xss = p.xs.toSet
 
-          val unrolling = new TentativeFormula(p.pc, p.phi, BooleanLiteral(true), Map(), Map() ++ p.xs.map(x => x -> Set(x)))
+          val initGuard = FreshIdentifier("START", true).setType(BooleanType)
+
+          val ndProgram = new NonDeterministicProgram(p, initGuard)
           var unrolings = 0
           val maxUnrolings = 3
-          var predicates: Seq[Expr]        = Seq()
-
 
           val mainSolver: FairZ3Solver = sctx.solver.asInstanceOf[FairZ3Solver]
 
+          var exampleInputs = Set[Seq[Expr]]()
+
+          // We populate the list of examples with a predefined one
+          if (p.pc == BooleanLiteral(true)) {
+            exampleInputs += p.as.map(a => simplestValue(a.getType))
+          } else {
+            val solver = mainSolver.getNewSolver
+
+            solver.assertCnstr(p.pc)
+
+            solver.check match {
+              case Some(true) =>
+                val model = solver.getModel
+                exampleInputs += p.as.map(a => model.getOrElse(a, simplestValue(a.getType)))
+
+              case Some(false) =>
+                return RuleApplicationImpossible
+
+              case None =>
+                sctx.reporter.warning("Solver could not solve path-condition")
+                return RuleApplicationImpossible // This is not necessary though, but probably wanted
+            }
+          }
+
+          // Keep track of collected cores to filter programs to test
+          var collectedCores = Set[Set[Identifier]]()
+
           // solver1 is used for the initial SAT queries
-          val solver1 = mainSolver.getNewSolver
-          solver1.assertCnstr(And(p.pc, p.phi))
+          var solver1 = mainSolver.getNewSolver
+          solver1.assertCnstr(And(p.pc :: p.phi :: Variable(initGuard) :: Nil))
 
-          // solver2 is used for the CE search
+          // solver2 is used for validating a candidate program, or finding new inputs
           val solver2 = mainSolver.getNewSolver
-          solver2.assertCnstr(And(p.pc :: Not(p.phi) :: Nil))
+          solver2.assertCnstr(And(p.pc :: Not(p.phi) :: Variable(initGuard) :: Nil))
+
+
+          var allClauses = List[Expr]()
 
           try {
             do {
-              val (clauses, bounds) = unrolling.unroll
+              var needMoreUnrolling = false
+
+              // Compute all programs that have not been excluded yet
+              var allPrograms: Set[Set[Identifier]] = if (useCEPruning) {
+                ndProgram.allPrograms.filterNot(p => collectedCores.exists(c => c.subsetOf(p)))
+              } else {
+                Set()
+              }
+
+              //println("Programs: "+allPrograms.size)
+              //println("CEs:      "+exampleInputs.size)
+
+              // We further filter the set of working programs to remove those that fail on known examples
+              if (useCEPruning && !exampleInputs.isEmpty && ndProgram.canTest()) {
+                //for (ce <- exampleInputs) {
+                //  println("CE: "+ce)
+                //}
+
+                for (p <- allPrograms) {
+                  if (!exampleInputs.forall(ndProgram.testForProgram(p))) {
+                    // This program failed on at least one example
+                    solver1.assertCnstr(Not(And(p.map(Variable(_)).toSeq)))
+                    allPrograms -= p
+                  }
+                }
+
+                if (allPrograms.isEmpty) {
+                  needMoreUnrolling = true
+                }
+
+                //println("Passing tests: "+allPrograms.size)
+              }
+
+              //allPrograms.foreach { p =>
+              //  println("PATH: "+p)
+              //  println("CLAUSES: "+p.flatMap( b => ndProgram.mappings.get(b).map{ case (c, ex) => c+" = "+ex}).mkString(" && "))
+              //}
+
+              val (clauses, closedBs) = ndProgram.unroll
               //println("UNROLLING: ")
               //for (c <- clauses) {
               //  println(" - " + c)
               //}
-              //println("BOUNDS "+bounds)
+              //println("CLOSED Bs "+closedBs)
 
               val clause = And(clauses)
+              allClauses = clause :: allClauses
+
               solver1.assertCnstr(clause)
               solver2.assertCnstr(clause)
 
               val tpe = TupleType(p.xs.map(_.getType))
-              val bss = unrolling.bss
+              val bss = ndProgram.bss
 
-              var continue = !clauses.isEmpty
+              if (clauses.isEmpty) {
+                needMoreUnrolling = true
+              }
 
-              while (result.isEmpty && continue && !sctx.shouldStop.get) {
-                //println("Looking for CE...")
-                //println("-"*80)
+              while (result.isEmpty && !needMoreUnrolling && !sctx.shouldStop.get) {
 
-                solver1.checkAssumptions(bounds.map(id => Not(Variable(id)))) match {
+                solver1.checkAssumptions(closedBs.map(id => Not(Variable(id)))) match {
                   case Some(true) =>
                     val satModel = solver1.getModel
 
@@ -211,80 +495,120 @@ case object CEGIS extends Rule("CEGIS") {
                       case BooleanLiteral(false) => Not(Variable(b))
                     })
 
+                    //println("CEGIS OUT!")
                     //println("Found solution: "+bssAssumptions)
 
-                    //println("#"*80)
-                    solver2.checkAssumptions(bssAssumptions) match {
-                      case Some(true) =>
-                        //println("#"*80)
-                        val invalidModel = solver2.getModel
-
-                        val fixedAss = And(ass.collect {
-                          case a if invalidModel contains a => Equals(Variable(a), invalidModel(a))
-                        }.toSeq)
-
-                        solver1.push()
-                        solver1.assertCnstr(fixedAss)
-                        //println("Found counter example: "+fixedAss)
-
-                        val unsatCore = if (useUnsatCores) {
-                          solver1.checkAssumptions(bssAssumptions) match {
-                            case Some(false) =>
-                              // Core might be empty if unrolling level is
-                              // insufficient, it becomes unsat no matter what
-                              // the assumptions are.
-                              solver1.getUnsatCore
-                            case _ =>
-                              bssAssumptions
+                    //bssAssumptions.collect { case Variable(b) => ndProgram.mappings(b) }.foreach {
+                    //  case (c, ex) =>
+                    //    println(". "+c+" = "+ex)
+                    //}
+
+                    val validateWithZ3 = if (useCETests && !exampleInputs.isEmpty && ndProgram.canTest()) {
+
+                      val p = bssAssumptions.collect { case Variable(b) => b }
+
+                      if (exampleInputs.forall(ndProgram.testForProgram(p))) {
+                        // All valid inputs also work with this, we need to
+                        // make sure by validating this candidate with z3
+                        true
+                      } else {
+                        // One valid input failed with this candidate, we can skip
+                        solver1.assertCnstr(Not(And(p.map(Variable(_)).toSeq)))
+                        false
+                      }
+                    } else {
+                      // No inputs or capability to test, we need to ask Z3
+                      true
+                    }
+
+                    if (validateWithZ3) {
+                      solver2.checkAssumptions(bssAssumptions) match {
+                        case Some(true) =>
+                          //println("#"*80)
+                          val invalidModel = solver2.getModel
+
+                          val fixedAss = And(ass.collect {
+                            case a if invalidModel contains a => Equals(Variable(a), invalidModel(a))
+                          }.toSeq)
+
+                          val newCE = p.as.map(valuateWithModel(invalidModel))
+
+                          exampleInputs += newCE
+
+                          //println("Found counter example: "+fixedAss)
+
+                          // Retest whether the newly found C-E invalidates all programs
+                          if (useCEPruning && ndProgram.canTest) {
+                            if (allPrograms.forall(p => !ndProgram.testForProgram(p)(newCE))) {
+                              // println("I found a killer example!")
+                              needMoreUnrolling = true
+                            }
                           }
-                        } else {
-                          bssAssumptions
-                        }
-
-                        solver1.pop()
-
-                        if (unsatCore.isEmpty) {
-                          continue = false
-                        } else {
-                          if (useCounterExamples) {
-                            val freshCss = unrolling.css.map(c => c -> Variable(FreshIdentifier(c.name, true).setType(c.getType))).toMap
-                            val ceIn     = ass.collect { 
-                              case id if invalidModel contains id => id -> invalidModel(id)
+
+                          val unsatCore = if (useUnsatCores) {
+                            solver1.push()
+                            solver1.assertCnstr(fixedAss)
+
+                            val core = solver1.checkAssumptions(bssAssumptions) match {
+                              case Some(false) =>
+                                // Core might be empty if unrolling level is
+                                // insufficient, it becomes unsat no matter what
+                                // the assumptions are.
+                                solver1.getUnsatCore
+
+                              case _ =>
+                                bssAssumptions
                             }
 
-                            val ceMap = (freshCss ++ ceIn)
+                            solver1.pop()
+
+                            collectedCores += core.collect{ case Variable(id) => id }
+
+                            core
+                          } else {
+                            bssAssumptions
+                          }
+
+                          if (unsatCore.isEmpty) {
+                            needMoreUnrolling = true
+                          } else {
+                            //if (useCEAsserts) {
+                            //  val freshCss = ndProgram.css.map(c => c -> Variable(FreshIdentifier(c.name, true).setType(c.getType))).toMap
+                            //  val ceIn     = ass.collect { 
+                            //    case id if invalidModel contains id => id -> invalidModel(id)
+                            //  }
 
-                            val counterexample = substAll(ceMap, And(Seq(unrolling.program, unrolling.phi)))
+                            //  val ceMap = (freshCss ++ ceIn)
 
-                            //val And(ands) = counterexample
-                            //println("CE:")
-                            //for (a <- ands) {
-                            //  println(" - "+a)
+                            //  val counterexample = substAll(ceMap, And(Seq(ndProgram.program, p.phi)))
+
+                            //  //val And(ands) = counterexample
+                            //  //println("CE:")
+                            //  //for (a <- ands) {
+                            //  //  println(" - "+a)
+                            //  //}
+
+                            //  solver1.assertCnstr(counterexample)
                             //}
 
-                            solver1.assertCnstr(counterexample)
+                            solver1.assertCnstr(Not(And(unsatCore.toSeq)))
                           }
 
-                          solver1.assertCnstr(Not(And(unsatCore.toSeq)))
-                        }
-
-                      case Some(false) =>
-                        var mapping = unrolling.mappings.filterKeys(satModel.mapValues(_ == BooleanLiteral(true))).values.toMap
+                        case Some(false) =>
 
-                        // Resolve mapping
-                        for ((c, e) <- mapping) {
-                          mapping += c -> substAll(mapping, e)
-                        }
+                          val expr = ndProgram.determinize(satModel.filter(_._2 == BooleanLiteral(true)).keySet)
 
-                        result = Some(RuleSuccess(Solution(BooleanLiteral(true), Set(), Tuple(p.xs.map(valuateWithModel(mapping))).setType(tpe))))
+                          result = Some(RuleSuccess(Solution(BooleanLiteral(true), Set(), expr)))
 
-                      case _ =>
-                        if (!sctx.shouldStop.get) {
-                          sctx.reporter.warning("Solver returned 'UNKNOWN' in a CEGIS iteration.")
-                        }
-                        continue = false
+                        case _ =>
+                          if (!sctx.shouldStop.get) {
+                            sctx.reporter.warning("Solver returned 'UNKNOWN' in a CEGIS iteration.")
+                          }
+                          needMoreUnrolling = true
+                      }
                     }
 
+
                   case Some(false) =>
                     //println("%%%% UNSAT")
 
@@ -298,11 +622,14 @@ case object CEGIS extends Rule("CEGIS") {
                       }
                     }
 
-                    continue = false
+                    needMoreUnrolling = true
 
                   case _ =>
+                    if (!sctx.shouldStop.get) {
+                      sctx.reporter.warning("Solver returned 'UNKNOWN' in a CEGIS iteration.")
+                    }
                     //println("%%%% WOOPS")
-                    continue = false
+                    needMoreUnrolling = true
                 }
               }
 
diff --git a/src/main/scala/leon/synthesis/utils/Benchmarks.scala b/src/main/scala/leon/synthesis/utils/Benchmarks.scala
index 15ebc81e6..8cb4c5fa7 100644
--- a/src/main/scala/leon/synthesis/utils/Benchmarks.scala
+++ b/src/main/scala/leon/synthesis/utils/Benchmarks.scala
@@ -87,6 +87,7 @@ object Benchmarks extends App {
 
     for ((f, ps) <- results.toSeq.sortBy(_._1.id.toString); p <- ps) {
       val sctx = SynthesisContext(
+        context = ctx,
         options = opts,
         functionContext = Some(f),
         program = program,
diff --git a/src/test/scala/leon/test/evaluators/EvaluatorsTests.scala b/src/test/scala/leon/test/evaluators/EvaluatorsTests.scala
index 36edcf9f0..4fe7228d1 100644
--- a/src/test/scala/leon/test/evaluators/EvaluatorsTests.scala
+++ b/src/test/scala/leon/test/evaluators/EvaluatorsTests.scala
@@ -175,7 +175,6 @@ class EvaluatorsTests extends FunSuite {
                |""".stripMargin
 
     implicit val prog = parseString(p)
-
     val evaluators = prepareEvaluators
 
     for(e <- evaluators) {
@@ -211,7 +210,6 @@ class EvaluatorsTests extends FunSuite {
                |}""".stripMargin
 
     implicit val prog = parseString(p)
-
     val evaluators = prepareEvaluators
 
     for(e <- evaluators) {
@@ -274,7 +272,6 @@ class EvaluatorsTests extends FunSuite {
                |}""".stripMargin
 
     implicit val prog = parseString(p)
-
     val evaluators = prepareEvaluators
 
     val nil = mkCaseClass("Nil")
@@ -316,7 +313,6 @@ class EvaluatorsTests extends FunSuite {
                |}""".stripMargin
 
     implicit val prog = parseString(p)
-
     val evaluators = prepareEvaluators
 
     val nil = mkCaseClass("Nil")
@@ -362,7 +358,6 @@ class EvaluatorsTests extends FunSuite {
                |}""".stripMargin
 
     implicit val prog = parseString(p)
-
     val evaluators = prepareEvaluators
 
     val cons1223 = mkCaseClass("PCons", IL(1), IL(2), mkCaseClass("PCons", IL(2), IL(3), mkCaseClass("PNil")))
@@ -379,6 +374,31 @@ class EvaluatorsTests extends FunSuite {
     }
   }
 
+  test("Arrays") {
+    val p = """|object Program {
+               |  def boolArrayRead(bools : Array[Boolean], index : Int) : Boolean = bools(index)
+               |
+               |  def intArrayRead(bools : Array[Int], index : Int) : Int = bools(index)
+               |}
+               |""".stripMargin
+
+    implicit val progs = parseString(p)
+    val evaluators = prepareEvaluators
+    
+    val ba = FiniteArray(Seq(T, F)).setType(ArrayType(BooleanType))
+    val ia = FiniteArray(Seq(IL(41), IL(42), IL(43))).setType(ArrayType(Int32Type))
+
+    for(e <- evaluators) {
+      checkComp(e, mkCall("boolArrayRead", ba, IL(0)), T)
+      checkComp(e, mkCall("boolArrayRead", ba, IL(1)), F)
+      checkComp(e, mkCall("intArrayRead", ia, IL(0)), IL(41))
+      checkComp(e, mkCall("intArrayRead", ia, IL(1)), IL(42))
+      checkComp(e, ArrayLength(ia), IL(3))
+
+      checkError(e, mkCall("boolArrayRead", ba, IL(2)))
+    }
+  }
+
   test("Misc") {
     val p = """|object Program {
                |  import leon.Utils._
@@ -388,7 +408,6 @@ class EvaluatorsTests extends FunSuite {
                |""".stripMargin
 
     implicit val prog = parseString(p)
-
     val evaluators = prepareEvaluators
 
     for(e <- evaluators) {
diff --git a/src/test/scala/leon/test/synthesis/SynthesisSuite.scala b/src/test/scala/leon/test/synthesis/SynthesisSuite.scala
index 96d7104d7..567d30abb 100644
--- a/src/test/scala/leon/test/synthesis/SynthesisSuite.scala
+++ b/src/test/scala/leon/test/synthesis/SynthesisSuite.scala
@@ -44,7 +44,7 @@ class SynthesisSuite extends FunSuite {
 
     for ((f, ps) <- results; p <- ps) {
       test("Synthesizing %3d: %-20s [%s]".format(nextInt(), f.id.toString, title)) {
-        val sctx = SynthesisContext(opts, Some(f), program, solver, new DefaultReporter, new java.util.concurrent.atomic.AtomicBoolean)
+        val sctx = SynthesisContext(ctx, opts, Some(f), program, solver, new DefaultReporter, new java.util.concurrent.atomic.AtomicBoolean)
 
         block(sctx, f, p)
       }
diff --git a/src/test/scala/leon/test/verification/XLangVerificationRegression.scala b/src/test/scala/leon/test/verification/XLangVerificationRegression.scala
index f5b33c3e2..e590093cc 100644
--- a/src/test/scala/leon/test/verification/XLangVerificationRegression.scala
+++ b/src/test/scala/leon/test/verification/XLangVerificationRegression.scala
@@ -79,7 +79,6 @@ class XLangVerificationRegression extends FunSuite {
     assert(report.totalConditions === report.totalValid,
            "All verification conditions ("+report.totalConditions+") should be valid.")
     assert(reporter.errorCount === 0)
-    assert(reporter.warningCount === 0)
   }
 
   forEachFileIn("invalid") { output =>
@@ -89,7 +88,6 @@ class XLangVerificationRegression extends FunSuite {
     assert(report.totalUnknown === 0,
            "There should not be unknown verification conditions.")
     assert(reporter.errorCount >= report.totalInvalid)
-    assert(reporter.warningCount === 0)
   }
 
   forEachFileIn("error", true) { output => () }
-- 
GitLab