diff --git a/src/funcheck/FunCheckPlugin.scala b/src/funcheck/FunCheckPlugin.scala index 863ac8a16905d9e2740430d2990328f7d1a446a9..09ba190b4042dd9f2e932817ace22b501d883042 100644 --- a/src/funcheck/FunCheckPlugin.scala +++ b/src/funcheck/FunCheckPlugin.scala @@ -34,7 +34,8 @@ class FunCheckPlugin(val global: Global) extends Plugin { " -P:funcheck:XP Enable weird transformations and other bug-producing features" + "\n" + " -P:funcheck:PLDI PLDI 2011 settings. Now frozen. Not completely functional. See CAV." + "\n" + " -P:funcheck:CAV CAV 2011 settings. In progress." + "\n" + - " -P:funcheck:prune (with CAV) Use additional SMT queries to rule out some unrollings." + " -P:funcheck:prune (with CAV) Use additional SMT queries to rule out some unrollings." + "\n" + + " -P:funcheck:cores (with CAV) Use UNSAT cores in the unrolling/refinement step." ) /** Processes the command-line options. */ @@ -56,6 +57,7 @@ class FunCheckPlugin(val global: Global) extends Plugin { case "PLDI" => { purescala.Settings.experimental = true; purescala.Settings.useInstantiator = true; purescala.Settings.useFairInstantiator = false; purescala.Settings.useBAPA = false; purescala.Settings.zeroInlining = true } case "CAV" => { purescala.Settings.experimental = true; purescala.Settings.useInstantiator = false; purescala.Settings.useFairInstantiator = true; purescala.Settings.useBAPA = false; purescala.Settings.zeroInlining = true } case "prune" => purescala.Settings.pruneBranches = true + case "cores" => purescala.Settings.useCores = true case s if s.startsWith("unrolling=") => purescala.Settings.unrollingLevel = try { s.substring("unrolling=".length, s.length).toInt } catch { case _ => 0 } case s if s.startsWith("functions=") => purescala.Settings.functionsToAnalyse = Set(splitList(s.substring("functions=".length, s.length)): _*) case s if s.startsWith("extensions=") => purescala.Settings.extensionNames = splitList(s.substring("extensions=".length, s.length)) diff --git a/src/purescala/FairZ3Solver.scala b/src/purescala/FairZ3Solver.scala index de6e68676250b2752b0807bd37ca255613882ea5..d11f7f0f99fc973613c02faba1f82911b9f3a7ad 100644 --- a/src/purescala/FairZ3Solver.scala +++ b/src/purescala/FairZ3Solver.scala @@ -43,7 +43,7 @@ class FairZ3Solver(val reporter: Reporter) extends Solver(reporter) with Abstrac } z3 = new Z3Context(z3cfg) partialEvaluator = new PartialEvaluator(program) - // z3.traceToStdout + //z3.traceToStdout exprToZ3Id = Map.empty z3IdToExpr = Map.empty @@ -260,18 +260,32 @@ class FairZ3Solver(val reporter: Reporter) extends Solver(reporter) with Abstrac var blockingSet : Set[Expr] = Set(guards.map(p => if(p._2) Not(Variable(p._1)) else Variable(p._1)) : _*) + var iterationsLeft : Int = if(Settings.unrollingLevel > 0) Settings.unrollingLevel else 16121984 + while(!foundDefinitiveAnswer) { + iterationsLeft -= 1 + val blockingSetAsZ3 : Seq[Z3AST] = blockingSet.map(toZ3Formula(z3, _).get).toSeq // println("Blocking set : " + blockingSet) - z3.push - if(!blockingSetAsZ3.isEmpty) - z3.assertCnstr(z3.mkAnd(blockingSetAsZ3 : _*)) + if(Settings.useCores) { + // NOOP + } else { + z3.push + if(!blockingSetAsZ3.isEmpty) + z3.assertCnstr(z3.mkAnd(blockingSetAsZ3 : _*)) + } + + val (answer, model, core) : (Option[Boolean], Z3Model, Seq[Z3AST]) = if(Settings.useCores) { + println(blockingSetAsZ3) + z3.checkAssumptions(blockingSetAsZ3 : _*) + } else { + val (a, m) = z3.checkAndGetModel() + (a, m, Seq.empty[Z3AST]) + } - //z3.checkAssumptions(blockingSetAsZ3 : _*) match { reporter.info(" - Running Z3 search...") - z3.checkAndGetModel() match { - // case (None, m, _) => { // UNKNOWN + (answer, model) match { case (None, m) => { // UNKNOWN reporter.warning("Z3 doesn't know because: " + z3.getSearchFailure.message) foundDefinitiveAnswer = true @@ -279,7 +293,6 @@ class FairZ3Solver(val reporter: Reporter) extends Solver(reporter) with Abstrac definitiveModel = Map.empty m.delete } - //case (Some(true), m, _) => { // SAT case (Some(true), m) => { // SAT val (trueModel, model) = validateAndDeleteModel(m, toCheckAgainstModels, varsInVC) @@ -295,108 +308,127 @@ class FairZ3Solver(val reporter: Reporter) extends Solver(reporter) with Abstrac definitiveModel = model } } - // case (Some(false), m, core) if core.isEmpty => { - // reporter.info("Empty core, definitively valid.") - // m.delete - // foundDefinitiveAnswer = true - // definitiveAnswer = Some(true) - // definitiveModel = Map.empty - // } - case (Some(false), m) if blockingSet.isEmpty => { + case (Some(false), m) if Settings.useCores && core.isEmpty => { + reporter.info("Empty core, definitively valid.") + m.delete foundDefinitiveAnswer = true definitiveAnswer = Some(true) definitiveModel = Map.empty } - // case (Some(false), m, core) => { + case (Some(false), m) if !Settings.useCores && blockingSet.isEmpty => { + foundDefinitiveAnswer = true + definitiveAnswer = Some(true) + definitiveModel = Map.empty + } + // This branch is both for with and without unsat cores. The + // distinction is made inside. case (Some(false), m) => { - m.delete - val USEUNSATCORE = false + //m.delete - z3.pop(1) - val (result2,m2) = z3.checkAndGetModel() + if(!Settings.useCores) { + z3.pop(1) + + val (result2,m2) = z3.checkAndGetModel() - if (result2 == Some(false)) { - foundDefinitiveAnswer = true - definitiveAnswer = Some(true) - definitiveModel = Map.empty - } else { - // we might have been lucky :D - val (wereWeLucky, cleanModel) = validateAndDeleteModel(m2, toCheckAgainstModels, varsInVC) - if(wereWeLucky) { + if (result2 == Some(false)) { foundDefinitiveAnswer = true - definitiveAnswer = Some(false) - definitiveModel = cleanModel + definitiveAnswer = Some(true) + definitiveModel = Map.empty + } else { + // we might have been lucky :D + val (wereWeLucky, cleanModel) = validateAndDeleteModel(m2, toCheckAgainstModels, varsInVC) + if(wereWeLucky) { + foundDefinitiveAnswer = true + definitiveAnswer = Some(false) + definitiveModel = cleanModel + } + } + } + + if(!foundDefinitiveAnswer) { + reporter.info("- We need to keep going.") + + val toRelease : Set[Expr] = if(Settings.useCores) { + core.map(ast => fromZ3Formula(ast) match { + case n @ Not(Variable(_)) => n + case v @ Variable(_) => v + case _ => scala.Predef.error("Impossible element extracted from core: " + ast) + }).toSet } else { - reporter.info("- We need to keep going.") - - val toRelease : Set[Expr] = blockingSet - - var fixedForEver : Set[Expr] = Set.empty - - if(Settings.pruneBranches) { - for(ex <- blockingSet) ex match { - case Not(Variable(b)) => { - z3.push - z3.assertCnstr(toZ3Formula(z3, 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) - fixedForEver = fixedForEver + ex - } - case _ => z3.pop(1) + blockingSet + } + + var fixedForEver : Set[Expr] = Set.empty + + if(Settings.pruneBranches) { + for(ex <- blockingSet) ex match { + case Not(Variable(b)) => { + z3.push + z3.assertCnstr(toZ3Formula(z3, 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) + fixedForEver = fixedForEver + ex } + case _ => z3.pop(1) } - case Variable(b) => { - z3.push - z3.assertCnstr(toZ3Formula(z3, 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) - fixedForEver = fixedForEver + ex - } - case _ => z3.pop(1) + } + case Variable(b) => { + z3.push + z3.assertCnstr(toZ3Formula(z3, 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) + fixedForEver = fixedForEver + ex } + case _ => z3.pop(1) } - case _ => scala.Predef.error("Should not have happened.") - } - if(fixedForEver.size > 0) { - reporter.info("- Pruned out " + fixedForEver.size + " branches.") } + case _ => scala.Predef.error("Should not have happened.") } + if(fixedForEver.size > 0) { + reporter.info("- Pruned out " + fixedForEver.size + " branches.") + } + } - // println("Release set : " + toRelease) + // println("Release set : " + toRelease) - blockingSet = blockingSet -- toRelease + blockingSet = blockingSet -- toRelease - val toReleaseAsPairs : Set[(Identifier,Boolean)] = (toRelease -- fixedForEver).map(b => b match { - case Variable(id) => (id, true) - case Not(Variable(id)) => (id, false) - case _ => scala.Predef.error("Impossible value in release set: " + b) - }) + val toReleaseAsPairs : Set[(Identifier,Boolean)] = (toRelease -- fixedForEver).map(b => b match { + case Variable(id) => (id, true) + case Not(Variable(id)) => (id, false) + case _ => scala.Predef.error("Impossible value in release set: " + b) + }) - reporter.info(" - more unrollings") - for((id,polarity) <- toReleaseAsPairs) { - val (newClauses,newBlockers) = unrollingBank.unlock(id, !polarity) - // for(clause <- newClauses) { - // println("we're getting a new clause " + clause) - // z3.assertCnstr(toZ3Formula(z3, clause).get) - // } + reporter.info(" - more unrollings") + for((id,polarity) <- toReleaseAsPairs) { + val (newClauses,newBlockers) = unrollingBank.unlock(id, !polarity) + // for(clause <- newClauses) { + // println("we're getting a new clause " + clause) + // z3.assertCnstr(toZ3Formula(z3, clause).get) + // } - for(ncl <- newClauses) { - z3.assertCnstr(toZ3Formula(z3, ncl).get) - } - //z3.assertCnstr(toZ3Formula(z3, And(newClauses)).get) - blockingSet = blockingSet ++ Set(newBlockers.map(p => if(p._2) Not(Variable(p._1)) else Variable(p._1)) : _*) + for(ncl <- newClauses) { + z3.assertCnstr(toZ3Formula(z3, ncl).get) } + //z3.assertCnstr(toZ3Formula(z3, And(newClauses)).get) + blockingSet = blockingSet ++ Set(newBlockers.map(p => if(p._2) Not(Variable(p._1)) else Variable(p._1)) : _*) } - // scala.Predef.error("...but not quite now.") } } } + + if(iterationsLeft <= 0) { + foundDefinitiveAnswer = true + definitiveAnswer = None + definitiveModel = Map.empty + reporter.error("Max. number of iterations reached.") + } } (definitiveAnswer, definitiveModel) diff --git a/src/purescala/Settings.scala b/src/purescala/Settings.scala index 5510626eb068e00bf814f3f07cf7e714f7366e96..869cc02809a52422bb8dc637af3e4ba192307a29 100644 --- a/src/purescala/Settings.scala +++ b/src/purescala/Settings.scala @@ -19,6 +19,7 @@ object Settings { var testBounds: (Int, Int) = (0, 3) var useInstantiator: Boolean = false var useFairInstantiator: Boolean = false + var useCores : Boolean = false var pruneBranches : Boolean = false def useAnyInstantiator : Boolean = useInstantiator || useFairInstantiator } diff --git a/src/purescala/Trees.scala b/src/purescala/Trees.scala index 20d2d8f014cfac64424a9f9118ed31946e205afa..8b59d223ff25777940efed271a3544c45f11084b 100644 --- a/src/purescala/Trees.scala +++ b/src/purescala/Trees.scala @@ -834,6 +834,8 @@ object Trees { } object SimplePatternMatching { + def isSimple(me: MatchExpr) : Boolean = unapply(me).isDefined + // (scrutinee, classtype, list((caseclassdef, variable, list(variable), rhs))) def unapply(e: MatchExpr) : Option[(Expr,ClassType,Seq[(CaseClassDef,Identifier,Seq[Identifier],Expr)])] = { val MatchExpr(scrutinee, cases) = e @@ -953,36 +955,20 @@ object Trees { }) } -// def explicitPreconditions(expr: Expr) : Expr = { -// def rewriteFunctionCall(e: Expr) : Option[Expr] = e match { -// case fi @ FunctionInvocation(fd, args) if(fd.hasPrecondition && fd.precondition.get != BooleanLiteral(true)) => { -// val fTpe = fi.getType -// val prec = matchToIfThenElse(fd.precondition.get) -// val newLetIDs = fd.args.map(a => FreshIdentifier("precarg_" + a.id.name, true).setType(a.tpe)) -// val substMap = Map[Expr,Expr]((fd.args.map(_.toVariable) zip newLetIDs.map(Variable(_))) : _*) -// val newPrec = replace(substMap, prec) -// val newThen = FunctionInvocation(fd, newLetIDs.map(_.toVariable)).setType(fTpe).setPosInfo(fi) -// val ifExpr: Expr = IfExpr(newPrec, newThen, Error("precondition violated").setType(fTpe).setPosInfo(fi)).setType(fTpe) -// Some((newLetIDs zip args).foldRight(ifExpr)((iap,e) => Let(iap._1, iap._2, e))) -// } -// case _ => None -// } -// -// searchAndReplaceDFS(rewriteFunctionCall)(expr) -// } - private var matchConverterCache = new scala.collection.mutable.HashMap[Expr,Expr]() /** Rewrites all pattern-matching expressions into if-then-else expressions, * with additional error conditions. Does not introduce additional variables. * We use a cache because we can. */ def matchToIfThenElse(expr: Expr) : Expr = { - if(matchConverterCache.isDefinedAt(expr)) { + val toRet = if(matchConverterCache.isDefinedAt(expr)) { matchConverterCache(expr) } else { val converted = convertMatchToIfThenElse(expr) matchConverterCache(expr) = converted converted } + + toRet } private def convertMatchToIfThenElse(expr: Expr) : Expr = { @@ -1030,14 +1016,23 @@ object Trees { (realCond, newRhs) } - val bigIte = condsAndRhs.foldRight[Expr](Error("non-exhaustive match").setType(bestRealType(m.getType)).setPosInfo(m))((p1, ex) => { + val optCondsAndRhs = if(SimplePatternMatching.isSimple(m)) { + // this is a hackish optimization: because we know all cases are covered, we replace the last condition by true (and that drops the check) + val lastExpr = condsAndRhs.last._2 + + condsAndRhs.dropRight(1) ++ Seq((BooleanLiteral(true),lastExpr)) + } else { + condsAndRhs + } + + val bigIte = optCondsAndRhs.foldRight[Expr](Error("non-exhaustive match").setType(bestRealType(m.getType)).setPosInfo(m))((p1, ex) => { if(p1._1 == BooleanLiteral(true)) { p1._2 } else { IfExpr(p1._1, p1._2, ex).setType(m.getType) } }) - //println(condsAndRhs) + Some(bigIte) } case _ => None