diff --git a/src/main/scala/leon/laziness/FreeVariableFactory.scala b/src/main/scala/leon/laziness/FreeVariableFactory.scala index fe07ea8b9a2ed30b0c727a84342fc168c83018d9..9e93edb5e92e7d8b49a2dbab7346260867d1bc17 100644 --- a/src/main/scala/leon/laziness/FreeVariableFactory.scala +++ b/src/main/scala/leon/laziness/FreeVariableFactory.scala @@ -33,42 +33,50 @@ import invariant.util.ProgramUtil._ * All free variables are of type `FreeVar` which can be mapped * to a required type by applying uninterpreted functions. */ -class FreeVariableFactory() { +object FreeVariableFactory { - val absClass = AbstractClassDef(FreshIdentifier("FreeVar@"), Seq(), None) - val absType = AbstractClassType(absClass, Seq()) + val fvClass = AbstractClassDef(FreshIdentifier("FreeVar@"), Seq(), None) + val fvType = AbstractClassType(fvClass, Seq()) val varCase = { - val cdef = CaseClassDef(FreshIdentifier("Var@"), Seq(), Some(absType), false) - cdef.setFields(Seq(ValDef(FreshIdentifier("fl", absType)))) - absClass.registerChild(cdef) + val cdef = CaseClassDef(FreshIdentifier("Var@"), Seq(), Some(fvType), false) + cdef.setFields(Seq(ValDef(FreshIdentifier("fl", fvType)))) + fvClass.registerChild(cdef) cdef } val nextCase = { - val cdef = CaseClassDef(FreshIdentifier("NextVar@"), Seq(), Some(absType), false) - cdef.setFields(Seq(ValDef(FreshIdentifier("fl", absType)))) - absClass.registerChild(cdef) + val cdef = CaseClassDef(FreshIdentifier("NextVar@"), Seq(), Some(fvType), false) + cdef.setFields(Seq(ValDef(FreshIdentifier("fl", fvType)))) + fvClass.registerChild(cdef) cdef } val nilCase = { - val cdef = CaseClassDef(FreshIdentifier("NilVar@"), Seq(), Some(absType), false) - absClass.registerChild(cdef) + val cdef = CaseClassDef(FreshIdentifier("NilVar@"), Seq(), Some(fvType), false) + fvClass.registerChild(cdef) cdef } class FreeVarListIterator(initRef: Variable) { - require(initRef.getType == absType) + require(initRef.getType == fvType) var refExpr : Expr = initRef def current = CaseClass(varCase.typed, Seq(refExpr)) // Var(refExpr) def next { refExpr = CaseClass(nextCase.typed, Seq(refExpr)) // Next(refExpr) } + // returns the current expressions and increments state + def nextExpr = { + val e = current + next + e + } } + def getFreeListIterator(initRef: Variable) = new FreeVarListIterator(initRef) + var uifuns = Map[TypeTree, FunDef]() def getOrCreateUF(t: TypeTree) = { uifuns.getOrElse(t, { val funName = "uop@" + TypeUtil.typeNameWOParams(t) - val param = ValDef(FreshIdentifier("a", absType)) + val param = ValDef(FreshIdentifier("a", fvType)) val tparams = TypeUtil.getTypeParameters(t) map TypeParameterDef.apply _ val uop = new FunDef(FreshIdentifier(funName), tparams, Seq(param), t) uifuns += (t -> uop) @@ -80,8 +88,9 @@ class FreeVariableFactory() { val flIter = new FreeVarListIterator(initRef) /** - * Free variables are not guaranteed to be unique. - * They are operations over unique references. + * Free operations are not guaranteed to be unique: They are + * uninterpreted functions of the form: f(ref). + * f(res_1) could be equal to f(res_2). */ def nextFV(t: TypeTree) = { val uop = getOrCreateUF(t) @@ -102,7 +111,7 @@ class FreeVariableFactory() { def getFreeVarGenerator(initRef: Variable) = new FreeVariableGenerator(initRef) - def createdClasses = Seq(absClass, varCase, nextCase, nilCase) + def fvClasses = Seq(fvClass, varCase, nextCase, nilCase) - def createdFunctions = uifuns.keys.toSeq + def fvFunctions = uifuns.keys.toSeq } diff --git a/src/main/scala/leon/laziness/LazinessEliminationPhase.scala b/src/main/scala/leon/laziness/LazinessEliminationPhase.scala index d20a741385d22f4111948b68fd278cbb646ebb75..867ba31ab448e8648ae4cfa69fc1aa97ec84fa8c 100644 --- a/src/main/scala/leon/laziness/LazinessEliminationPhase.scala +++ b/src/main/scala/leon/laziness/LazinessEliminationPhase.scala @@ -36,14 +36,14 @@ import purescala.Constructors._ import leon.verification._ import PredicateUtil._ import leon.invariant.engine._ - +import LazyVerificationPhase._ /** * TODO: Function names are assumed to be small case. Fix this!! */ object LazinessEliminationPhase extends TransformationPhase { val debugLifting = false val dumpInputProg = false - val dumpLiftProg = false + val dumpLiftProg = true val dumpProgramWithClosures = true val dumpTypeCorrectProg = false val dumpProgWithPreAsserts = true @@ -52,7 +52,6 @@ object LazinessEliminationPhase extends TransformationPhase { val debugSolvers = false val skipStateVerification = false val skipResourceVerification = false - val debugInstVCs = false val name = "Laziness Elimination Phase" val description = "Coverts a program that uses lazy construct" + @@ -62,7 +61,7 @@ object LazinessEliminationPhase extends TransformationPhase { val optRefEquality = LeonFlagOptionDef("refEq", "Uses reference equality for comparing closures", false) val optUseOrb = LeonFlagOptionDef("useOrb", "Use Orb to infer constants", false) - override val definedOptions: Set[LeonOptionDef[Any]] = Set(optUseOrb) + override val definedOptions: Set[LeonOptionDef[Any]] = Set(optUseOrb, optRefEquality) /** * TODO: add inlining annotations for optimization. @@ -75,7 +74,8 @@ object LazinessEliminationPhase extends TransformationPhase { val (pass, msg) = sanityChecks(prog, ctx) assert(pass, msg) - val nprog = liftLazyExpressions(prog) + // refEq is by default false + val nprog = LazyExpressionLifter.liftLazyExpressions(prog, ctx.findOption(optRefEquality).getOrElse(false)) if (dumpLiftProg) { prettyPrintProgramToFile(nprog, ctx, "-lifted") } @@ -184,379 +184,4 @@ object LazinessEliminationPhase extends TransformationPhase { } (checkres, failMsg) } - - /** - * convert the argument of every lazy constructors to a procedure - */ - var globalId = 0 - def freshFunctionNameForArg = { - globalId += 1 - "lazyarg" + globalId - } - - /** - * (a) The functions lifts arguments of '$' to functions - * (b) lifts eager computations to lazy computations if necessary - * (c) converts memoization to lazy evaluation - */ - def liftLazyExpressions(prog: Program): Program = { - var newfuns = Map[ExprStructure, (FunDef, ModuleDef)]() - val fdmap = prog.definedFunctions.collect { - case fd if !fd.isLibrary => - val nfd = new FunDef(FreshIdentifier(fd.id.name), fd.tparams, fd.params, fd.returnType) - fd.flags.foreach(nfd.addFlag(_)) - (fd -> nfd) - }.toMap - - lazy val lazyFun = ProgramUtil.functionByFullName("leon.lazyeval.$.apply", prog).get - lazy val valueFun = ProgramUtil.functionByFullName("leon.lazyeval.$.value", prog).get - - prog.modules.foreach { md => - def exprLifter(inmem: Boolean)(expr: Expr) = expr match { - // is the arugment of lazy invocation not a function ? - case finv @ FunctionInvocation(lazytfd, Seq(arg)) if isLazyInvocation(finv)(prog) && !arg.isInstanceOf[FunctionInvocation] => - val freevars = variablesOf(arg).toList - val tparams = freevars.map(_.getType).flatMap(getTypeParameters).distinct - val argstruc = new ExprStructure(arg) - val argfun = - if (newfuns.contains(argstruc)) { - newfuns(argstruc)._1 - } else { - //construct type parameters for the function - // note: we should make the base type of arg as the return type - val nfun = new FunDef(FreshIdentifier(freshFunctionNameForArg, Untyped, true), tparams map TypeParameterDef.apply, - freevars.map(ValDef(_)), bestRealType(arg.getType)) - nfun.body = Some(arg) - newfuns += (argstruc -> (nfun, md)) - nfun - } - FunctionInvocation(lazytfd, Seq(FunctionInvocation(TypedFunDef(argfun, tparams), - freevars.map(_.toVariable)))) - - // is the argument of eager invocation not a variable ? - case finv @ FunctionInvocation(TypedFunDef(fd, _), Seq(arg)) if isEagerInvocation(finv)(prog) && !arg.isInstanceOf[Variable] => - val rootType = bestRealType(arg.getType) - val freshid = FreshIdentifier("t", rootType) - Let(freshid, arg, FunctionInvocation(TypedFunDef(fd, Seq(rootType)), Seq(freshid.toVariable))) - - case FunctionInvocation(TypedFunDef(fd, targs), args) if isMemoized(fd) && !inmem => - // calling a memoized function is modeled as creating a lazy closure and forcing it - val tfd = TypedFunDef(fdmap.getOrElse(fd, fd), targs) - val finv = FunctionInvocation(tfd, args) - // enclose the call within the $ and force it - val susp = FunctionInvocation(TypedFunDef(lazyFun, Seq(tfd.returnType)), Seq(finv)) - FunctionInvocation(TypedFunDef(valueFun, Seq(tfd.returnType)), Seq(susp)) - - case FunctionInvocation(TypedFunDef(fd, targs), args) if fdmap.contains(fd) => - FunctionInvocation(TypedFunDef(fdmap(fd), targs), args) - case e => e - } - md.definedFunctions.foreach { - case fd if fd.hasBody && !fd.isLibrary => - def rec(inmem: Boolean)(e: Expr): Expr = e match { - case Operator(args, op) => - val nargs = args map rec(inmem || isMemCons(e)(prog)) - exprLifter(inmem)(op(nargs)) - } - val nfd = fdmap(fd) - nfd.fullBody = rec(false)(fd.fullBody) - /*val nbody = simplePostTransform(exprLifter(false))(fd.body.get) - val npre = fd.precondition.map(simplePostTransform(exprLifter(true))) - val npost = fd.postcondition.map(simplePostTransform(exprLifter(true)))*/ - //println(s"New body of $fd: $nbody") - /*nfd.body = Some(nbody) - nfd.precondition = npre - nfd.postcondition = npost*/ - case _ => ; - } - } - val repProg = copyProgram(prog, (defs: Seq[Definition]) => defs.map { - case fd: FunDef => fdmap.getOrElse(fd, fd) - case d => d - }) - val nprog = - if (!newfuns.isEmpty) { - val modToNewDefs = newfuns.values.groupBy(_._2).map { case (k, v) => (k, v.map(_._1)) }.toMap - appendDefsToModules(repProg, modToNewDefs) - } else - repProg - if (debugLifting) - println("After lifiting arguments of lazy constructors: \n" + ScalaPrinter.apply(nprog)) - nprog - } - - def removeInstrumentationSpecs(p: Program): Program = { - def hasInstVar(e: Expr) = { - exists { e => InstUtil.InstTypes.exists(i => i.isInstVariable(e)) }(e) - } - val newPosts = p.definedFunctions.collect { - case fd if fd.postcondition.exists { exists(hasInstVar) } => - // remove the conjuncts that use instrumentation vars - val Lambda(resdef, pbody) = fd.postcondition.get - val npost = pbody match { - case And(args) => - createAnd(args.filterNot(hasInstVar)) - case l: Let => // checks if the body of the let can be deconstructed as And - //println(s"Fist let val: ${l.value} body: ${l.body}") - val (letsCons, letsBody) = letStarUnapply(l) - //println("Let* body: "+letsBody) - letsBody match { - case And(args) => - letsCons(createAnd(args.filterNot(hasInstVar))) - case _ => Util.tru - } - case e => Util.tru - } - (fd -> Lambda(resdef, npost)) - }.toMap - ProgramUtil.updatePost(newPosts, p) //note: this will not update libraries - } - - def contextForChecks(userOptions: LeonContext) = { - val solverOptions = Main.processOptions(Seq("--solvers=smt-cvc4,smt-z3", "--assumepre")) - LeonContext(userOptions.reporter, userOptions.interruptManager, - solverOptions.options ++ userOptions.options) - } - - // cumulative stats - var totalTime = 0L - var totalVCs = 0 - var solvedWithZ3 = 0 - var solvedWithCVC4 = 0 - var z3Time = 0L - var cvc4Time = 0L - - def collectCumulativeStats(rep: VerificationReport) { - totalTime += rep.totalTime - totalVCs += rep.totalConditions - val (withz3, withcvc) = rep.vrs.partition{ - case (vc, vr) => - vr.solvedWith.map(s => s.name.contains("smt-z3")).get - } - solvedWithZ3 += withz3.size - solvedWithCVC4 += withcvc.size - z3Time += withz3.map(_._2.timeMs.getOrElse(0L)).sum - cvc4Time += withcvc.map(_._2.timeMs.getOrElse(0L)).sum - } - - def dumpStats() { - println("totalTime: "+f"${totalTime/1000d}%-3.3f") - println("totalVCs: "+totalVCs) - println("solvedWithZ3: "+ solvedWithZ3) - println("solvedWithCVC4: "+ solvedWithCVC4) - println("z3Time: "+f"${z3Time/1000d}%-3.3f") - println("cvc4Time: "+f"${cvc4Time/1000d}%-3.3f") - } - - def checkSpecifications(prog: Program, checkCtx: LeonContext) { - // convert 'axiom annotation to library - prog.definedFunctions.foreach { fd => - if (fd.annotations.contains("axiom")) - fd.addFlag(Annotation("library", Seq())) - } - // val functions = Seq() //Seq("--functions=rotate") - // val solverOptions = if (debugSolvers) Seq("--debug=solver") else Seq() - // val unfoldFactor = 3 , - // "--unfoldFactor="+unfoldFactor) ++ solverOptions ++ functions - //val solverOptions = Main.processOptions(Seq("--solvers=smt-cvc4,smt-z3", "--assumepre") - val report = VerificationPhase.apply(checkCtx, prog) - // collect stats - collectCumulativeStats(report) - println(report.summaryString) - /*ctx.reporter.whenDebug(leon.utils.DebugSectionTimers) { debug => - ctx.timers.outputTable(debug) - }*/ - } - - def checkInstrumentationSpecs(p: Program, checkCtx: LeonContext) = { - p.definedFunctions.foreach { fd => - if (fd.annotations.contains("axiom")) - fd.addFlag(Annotation("library", Seq())) - } - val funsToCheck = p.definedFunctions.filter(shouldGenerateVC) - if (checkCtx.findOption(optUseOrb).getOrElse(false)) { - // create an inference context - val inferOpts = Main.processOptions(Seq("--disableInfer", "--assumepreInf", "--minbounds")) - val ctxForInf = LeonContext(checkCtx.reporter, checkCtx.interruptManager, - inferOpts.options ++ checkCtx.options) - val inferctx = new InferenceContext(p, ctxForInf) - val vcSolver = (funDef: FunDef, prog: Program) => new VCSolver(inferctx, prog, funDef) - prettyPrintProgramToFile(inferctx.inferProgram, checkCtx, "-inferProg", true) - (new InferenceEngine(inferctx)).analyseProgram(inferctx.inferProgram, funsToCheck, vcSolver, None) - } else { - val vcs = funsToCheck.map { fd => - val (ants, post, tmpl) = createVC(fd) - if (tmpl.isDefined) - throw new IllegalStateException("Postcondition has holes! Run with --useOrb option") - val vc = implies(ants, post) - if (debugInstVCs) - println(s"VC for function ${fd.id} : " + vc) - VC(vc, fd, VCKinds.Postcondition) - } - val rep = checkVCs(vcs, checkCtx, p) - // record some stats - collectCumulativeStats(rep) - println("Resource Verification Results: \n" + rep.summaryString) - } - } - - def accessesSecondRes(e: Expr, resid: Identifier): Boolean = - exists(_ == TupleSelect(resid.toVariable, 2))(e) - - /** - * Note: we also skip verification of uninterpreted functions - */ - def shouldGenerateVC(fd: FunDef) = { - !fd.isLibrary && InstUtil.isInstrumented(fd) && fd.hasBody && - fd.postcondition.exists { post => - val Lambda(Seq(resdef), pbody) = post - accessesSecondRes(pbody, resdef.id) - } - } - - /** - * creates vcs - * Note: we only need to check specs involving instvars since others were checked before. - * Moreover, we can add other specs as assumptions since (A => B) ^ ((A ^ B) => C) => A => B ^ C - * checks if the expression uses res._2 which corresponds to instvars after instrumentation - */ - def createVC(fd: FunDef) = { - val Lambda(Seq(resdef), _) = fd.postcondition.get - val (pbody, tmpl) = (fd.getPostWoTemplate, fd.template) - val (instPost, assumptions) = pbody match { - case And(args) => - val (instSpecs, rest) = args.partition(accessesSecondRes(_, resdef.id)) - (createAnd(instSpecs), createAnd(rest)) - case l: Let => - val (letsCons, letsBody) = letStarUnapply(l) - letsBody match { - case And(args) => - val (instSpecs, rest) = args.partition(accessesSecondRes(_, resdef.id)) - (letsCons(createAnd(instSpecs)), - letsCons(createAnd(rest))) - case _ => - (l, Util.tru) - } - case e => (e, Util.tru) - } - val ants = createAnd(Seq(fd.precOrTrue, assumptions, Equals(resdef.id.toVariable, fd.body.get))) - (ants, instPost, tmpl) - } - - def checkVCs(vcs: List[VC], checkCtx: LeonContext, p: Program) = { - val timeout: Option[Long] = None - val reporter = checkCtx.reporter - // Solvers selection and validation - val baseSolverF = SolverFactory.getFromSettings(checkCtx, p) - val solverF = timeout match { - case Some(sec) => - baseSolverF.withTimeout(sec / 1000) - case None => - baseSolverF - } - val vctx = VerificationContext(checkCtx, p, solverF, reporter) - try { - VerificationPhase.checkVCs(vctx, vcs) - //println("Resource Verification Results: \n" + veriRep.summaryString) - } finally { - solverF.shutdown() - } - } - - class VCSolver(ctx: InferenceContext, p: Program, rootFd: FunDef) extends - UnfoldingTemplateSolver(ctx, p, rootFd) { - override def constructVC(fd: FunDef): (Expr, Expr) = { - val (ants, post, tmpl) = createVC(fd) - val conseq = matchToIfThenElse(createAnd(Seq(post, tmpl.getOrElse(Util.tru)))) - (matchToIfThenElse(ants), conseq) - } - - /** - * TODO: fix this!! - */ - override def verifyInvariant(newposts: Map[FunDef, Expr]) = (Some(false), Model.empty) - } - - /** - * NOT USED CURRENTLY - * Lift the specifications on functions to the invariants corresponding - * case classes. - * Ideally we should class invariants here, but it is not currently supported - * so we create a functions that can be assume in the pre and post of functions. - * TODO: can this be optimized - */ - /* def liftSpecsToClosures(opToAdt: Map[FunDef, CaseClassDef]) = { - val invariants = opToAdt.collect { - case (fd, ccd) if fd.hasPrecondition => - val transFun = (args: Seq[Identifier]) => { - val argmap: Map[Expr, Expr] = (fd.params.map(_.id.toVariable) zip args.map(_.toVariable)).toMap - replace(argmap, fd.precondition.get) - } - (ccd -> transFun) - }.toMap - val absTypes = opToAdt.values.collect { - case cd if cd.parent.isDefined => cd.parent.get.classDef - } - val invFuns = absTypes.collect { - case abs if abs.knownCCDescendents.exists(invariants.contains) => - val absType = AbstractClassType(abs, abs.tparams.map(_.tp)) - val param = ValDef(FreshIdentifier("$this", absType)) - val tparams = abs.tparams - val invfun = new FunDef(FreshIdentifier(abs.id.name + "$Inv", Untyped), - tparams, BooleanType, Seq(param)) - (abs -> invfun) - }.toMap - // assign bodies for the 'invfuns' - invFuns.foreach { - case (abs, fd) => - val bodyCases = abs.knownCCDescendents.collect { - case ccd if invariants.contains(ccd) => - val ctype = CaseClassType(ccd, fd.tparams.map(_.tp)) - val cvar = FreshIdentifier("t", ctype) - val fldids = ctype.fields.map { - case ValDef(fid, Some(fldtpe)) => - FreshIdentifier(fid.name, fldtpe) - } - val pattern = CaseClassPattern(Some(cvar), ctype, - fldids.map(fid => WildcardPattern(Some(fid)))) - val rhsInv = invariants(ccd)(fldids) - // assert the validity of substructures - val rhsValids = fldids.flatMap { - case fid if fid.getType.isInstanceOf[ClassType] => - val t = fid.getType.asInstanceOf[ClassType] - val rootDef = t match { - case absT: AbstractClassType => absT.classDef - case _ if t.parent.isDefined => - t.parent.get.classDef - } - if (invFuns.contains(rootDef)) { - List(FunctionInvocation(TypedFunDef(invFuns(rootDef), t.tps), - Seq(fid.toVariable))) - } else - List() - case _ => List() - } - val rhs = Util.createAnd(rhsInv +: rhsValids) - MatchCase(pattern, None, rhs) - } - // create a default case - val defCase = MatchCase(WildcardPattern(None), None, Util.tru) - val matchExpr = MatchExpr(fd.params.head.id.toVariable, bodyCases :+ defCase) - fd.body = Some(matchExpr) - } - invFuns - }*/ - // Expressions for testing solvers - // a test expression - /*val tparam = - val dummyFunDef = new FunDef(FreshIdentifier("i"),Seq(), Seq(), IntegerType) - val eq = Equals(FunctionInvocation(TypedFunDef(dummyFunDef, Seq()), Seq()), InfiniteIntegerLiteral(0)) - import solvers._ - val solver = SimpleSolverAPI(SolverFactory(() => new solvers.smtlib.SMTLIBCVC4Solver(ctx, prog))) - solver.solveSAT(eq) match { - case (Some(true), m) => - println("Model: "+m.toMap) - case _ => println("Formula is unsat") - } - System.exit(0)*/ } diff --git a/src/main/scala/leon/laziness/LazyClosureConverter.scala b/src/main/scala/leon/laziness/LazyClosureConverter.scala index c587a330283e3899edb77937ff729420c6be3803..365babd1b91326356e15d1f591ea5358e4cc999f 100644 --- a/src/main/scala/leon/laziness/LazyClosureConverter.scala +++ b/src/main/scala/leon/laziness/LazyClosureConverter.scala @@ -46,9 +46,7 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, val funsNeedStates = funsManager.funsNeedStates val funsRetStates = funsManager.funsRetStates - val consCallers = funsManager.callersOfLazyCons // transitive constructors of closures val starCallers = funsManager.funsNeedStateTps - val fvFactory = new FreeVariableFactory() val lazyTnames = closureFactory.lazyTypeNames val lazyops = closureFactory.lazyops diff --git a/src/main/scala/leon/laziness/LazyExpressionLifter.scala b/src/main/scala/leon/laziness/LazyExpressionLifter.scala new file mode 100644 index 0000000000000000000000000000000000000000..9cdceceb76a69f473263cc544f36c8b5fff47fff --- /dev/null +++ b/src/main/scala/leon/laziness/LazyExpressionLifter.scala @@ -0,0 +1,263 @@ +package leon +package laziness + +import invariant.factories._ +import invariant.util.Util._ +import invariant.util._ +import invariant.structure.FunctionUtils._ +import purescala.ScalaPrinter +import purescala.Common._ +import purescala.Definitions._ +import purescala.Expressions._ +import purescala.ExprOps._ +import purescala.DefOps._ +import purescala.Extractors._ +import purescala.Types._ +import purescala.TypeOps._ +import leon.invariant.util.TypeUtil._ +import leon.invariant.util.LetTupleSimplification._ +import leon.verification.VerificationPhase +import java.io.File +import java.io.FileWriter +import java.io.BufferedWriter +import scala.util.matching.Regex +import leon.purescala.PrettyPrinter +import leon.solvers._ +import leon.solvers.z3._ +import leon.transformations._ +import leon.LeonContext +import leon.LeonOptionDef +import leon.Main +import leon.TransformationPhase +import LazinessUtil._ +import invariant.datastructure._ +import invariant.util.ProgramUtil._ +import purescala.Constructors._ +import leon.verification._ +import PredicateUtil._ +import leon.invariant.engine._ +import FreeVariableFactory._ + +object LazyExpressionLifter { + + /** + * convert the argument of every lazy constructors to a procedure + */ + var globalId = 0 + def freshFunctionNameForArg = { + globalId += 1 + "lazyarg" + globalId + } + + /** + * (a) The functions lifts arguments of '$' to functions + * (b) lifts eager computations to lazy computations if necessary + * (c) converts memoization to lazy evaluation + * (d) Adds unique references to programs that create lazy closures. + */ + def liftLazyExpressions(prog: Program, createUniqueIds: Boolean = false): Program = { + + lazy val funsMan = new LazyFunctionsManager(prog) + lazy val needsId = funsMan.callersnTargetOfLazyCons + + var newfuns = Map[ExprStructure, (FunDef, ModuleDef)]() + val fdmap = prog.definedFunctions.collect { + case fd if !fd.isLibrary => + val nname = FreshIdentifier(fd.id.name) + val nfd = + if (createUniqueIds && needsId(fd)) { + val idparam = ValDef(FreshIdentifier("id@", fvType)) + new FunDef(nname, fd.tparams, fd.params :+ idparam, fd.returnType) + } else + new FunDef(nname, fd.tparams, fd.params, fd.returnType) + fd.flags.foreach(nfd.addFlag(_)) + (fd -> nfd) + }.toMap + + lazy val lazyFun = ProgramUtil.functionByFullName("leon.lazyeval.$.apply", prog).get + lazy val valueFun = ProgramUtil.functionByFullName("leon.lazyeval.$.value", prog).get + + var anchorDef: Option[FunDef] = None // a hack to find anchors + prog.modules.foreach { md => + def exprLifter(inmem: Boolean)(fl: Option[FreeVarListIterator])(expr: Expr) = expr match { + // is this $(e) where e is not a funtion + case finv @ FunctionInvocation(lazytfd, Seq(arg)) if isLazyInvocation(finv)(prog) => + arg match { + case _: FunctionInvocation => + finv // subexpressions have already been evaluated + case _ => + val freevars = variablesOf(arg).toList + val tparams = freevars.map(_.getType).flatMap(getTypeParameters).distinct + val argstruc = new ExprStructure(arg) + val argfun = + if (newfuns.contains(argstruc)) { + newfuns(argstruc)._1 + } else { + //construct type parameters for the function + // note: we should make the base type of arg as the return type + val nname = FreshIdentifier(freshFunctionNameForArg, Untyped, true) + val tparamDefs = tparams map TypeParameterDef.apply + val params = freevars.map(ValDef(_)) + val retType = bestRealType(arg.getType) + val nfun = + if (createUniqueIds) { + val idparam = ValDef(FreshIdentifier("id@", fvType)) + new FunDef(nname, tparamDefs, params :+ idparam, retType) + } else + new FunDef(nname, tparamDefs, params, retType) + nfun.body = Some(arg) + newfuns += (argstruc -> (nfun, md)) + nfun + } + val fvVars = freevars.map(_.toVariable) + val params = + if (createUniqueIds) + fvVars :+ fl.get.nextExpr + else fvVars + FunctionInvocation(lazytfd, Seq(FunctionInvocation(TypedFunDef(argfun, tparams), params))) + } + + // is the argument of eager invocation not a variable ? + case finv @ FunctionInvocation(TypedFunDef(fd, _), Seq(arg)) if isEagerInvocation(finv)(prog) && !arg.isInstanceOf[Variable] => + val rootType = bestRealType(arg.getType) + val freshid = FreshIdentifier("t", rootType) + Let(freshid, arg, FunctionInvocation(TypedFunDef(fd, Seq(rootType)), Seq(freshid.toVariable))) + + // is this an invocation of a memoized function ? + case FunctionInvocation(TypedFunDef(fd, targs), args) if isMemoized(fd) && !inmem => + // calling a memoized function is modeled as creating a lazy closure and forcing it + val tfd = TypedFunDef(fdmap.getOrElse(fd, fd), targs) + val finv = FunctionInvocation(tfd, args) + // enclose the call within the $ and force it + val susp = FunctionInvocation(TypedFunDef(lazyFun, Seq(tfd.returnType)), Seq(finv)) + FunctionInvocation(TypedFunDef(valueFun, Seq(tfd.returnType)), Seq(susp)) + + // every other function calls ? + case FunctionInvocation(TypedFunDef(fd, targs), args) if fdmap.contains(fd) => + val nargs = + if (createUniqueIds && needsId(fd)) + args :+ fl.get.nextExpr + else args + FunctionInvocation(TypedFunDef(fdmap(fd), targs), nargs) + + case e => e + } + md.definedFunctions.foreach { + case fd if fd.hasBody && !fd.isLibrary => + // create a free list iterator + val nfd = fdmap(fd) + val fliter = + if (createUniqueIds && needsId(fd)) { + if (!anchorDef.isDefined) + anchorDef = Some(nfd) + val initRef = nfd.params.last.id.toVariable + Some(getFreeListIterator(initRef)) + } else + None + + def rec(inmem: Boolean)(e: Expr): Expr = e match { + case Operator(args, op) => + val nargs = args map rec(inmem || isMemCons(e)(prog)) + exprLifter(inmem)(fliter)(op(nargs)) + } + nfd.fullBody = rec(false)(fd.fullBody) + case _ => ; + } + } + val progWithFuns = copyProgram(prog, (defs: Seq[Definition]) => defs.map { + case fd: FunDef => fdmap.getOrElse(fd, fd) + case d => d + }) + val progWithClasses = + if (createUniqueIds) addDefs(progWithFuns, fvClasses, anchorDef.get) + else progWithFuns + if (!newfuns.isEmpty) { + val modToNewDefs = newfuns.values.groupBy(_._2).map { case (k, v) => (k, v.map(_._1)) }.toMap + appendDefsToModules(progWithClasses, modToNewDefs) + } else + progWithClasses + } + + /** + * NOT USED CURRENTLY + * Lift the specifications on functions to the invariants corresponding + * case classes. + * Ideally we should class invariants here, but it is not currently supported + * so we create a functions that can be assume in the pre and post of functions. + * TODO: can this be optimized + */ + /* def liftSpecsToClosures(opToAdt: Map[FunDef, CaseClassDef]) = { + val invariants = opToAdt.collect { + case (fd, ccd) if fd.hasPrecondition => + val transFun = (args: Seq[Identifier]) => { + val argmap: Map[Expr, Expr] = (fd.params.map(_.id.toVariable) zip args.map(_.toVariable)).toMap + replace(argmap, fd.precondition.get) + } + (ccd -> transFun) + }.toMap + val absTypes = opToAdt.values.collect { + case cd if cd.parent.isDefined => cd.parent.get.classDef + } + val invFuns = absTypes.collect { + case abs if abs.knownCCDescendents.exists(invariants.contains) => + val absType = AbstractClassType(abs, abs.tparams.map(_.tp)) + val param = ValDef(FreshIdentifier("$this", absType)) + val tparams = abs.tparams + val invfun = new FunDef(FreshIdentifier(abs.id.name + "$Inv", Untyped), + tparams, BooleanType, Seq(param)) + (abs -> invfun) + }.toMap + // assign bodies for the 'invfuns' + invFuns.foreach { + case (abs, fd) => + val bodyCases = abs.knownCCDescendents.collect { + case ccd if invariants.contains(ccd) => + val ctype = CaseClassType(ccd, fd.tparams.map(_.tp)) + val cvar = FreshIdentifier("t", ctype) + val fldids = ctype.fields.map { + case ValDef(fid, Some(fldtpe)) => + FreshIdentifier(fid.name, fldtpe) + } + val pattern = CaseClassPattern(Some(cvar), ctype, + fldids.map(fid => WildcardPattern(Some(fid)))) + val rhsInv = invariants(ccd)(fldids) + // assert the validity of substructures + val rhsValids = fldids.flatMap { + case fid if fid.getType.isInstanceOf[ClassType] => + val t = fid.getType.asInstanceOf[ClassType] + val rootDef = t match { + case absT: AbstractClassType => absT.classDef + case _ if t.parent.isDefined => + t.parent.get.classDef + } + if (invFuns.contains(rootDef)) { + List(FunctionInvocation(TypedFunDef(invFuns(rootDef), t.tps), + Seq(fid.toVariable))) + } else + List() + case _ => List() + } + val rhs = Util.createAnd(rhsInv +: rhsValids) + MatchCase(pattern, None, rhs) + } + // create a default case + val defCase = MatchCase(WildcardPattern(None), None, Util.tru) + val matchExpr = MatchExpr(fd.params.head.id.toVariable, bodyCases :+ defCase) + fd.body = Some(matchExpr) + } + invFuns + }*/ + // Expressions for testing solvers + // a test expression + /*val tparam = + val dummyFunDef = new FunDef(FreshIdentifier("i"),Seq(), Seq(), IntegerType) + val eq = Equals(FunctionInvocation(TypedFunDef(dummyFunDef, Seq()), Seq()), InfiniteIntegerLiteral(0)) + import solvers._ + val solver = SimpleSolverAPI(SolverFactory(() => new solvers.smtlib.SMTLIBCVC4Solver(ctx, prog))) + solver.solveSAT(eq) match { + case (Some(true), m) => + println("Model: "+m.toMap) + case _ => println("Formula is unsat") + } + System.exit(0)*/ +} diff --git a/src/main/scala/leon/laziness/LazyFunctionsManager.scala b/src/main/scala/leon/laziness/LazyFunctionsManager.scala index f0e9462b33c117c9d340cbae7bc3e9646c0bdf7a..b5197ac4ca2d5ec6dacdd9a320e51a6b5cdd5795 100644 --- a/src/main/scala/leon/laziness/LazyFunctionsManager.scala +++ b/src/main/scala/leon/laziness/LazyFunctionsManager.scala @@ -79,19 +79,21 @@ class LazyFunctionsManager(p: Program) { (readfuns ++ valCallers, valCallers, starCallers ++ readfuns ++ valCallers) } - lazy val callersOfLazyCons = { + lazy val callersnTargetOfLazyCons = { var consRoots = Set[FunDef]() + var targets = Set[FunDef]() funsNeedStates.foreach { case fd if fd.hasBody => postTraversal { case finv: FunctionInvocation if isLazyInvocation(finv)(p) => // this is the lazy invocation constructor consRoots += fd + targets += finv.tfd.fd case _ => ; }(fd.body.get) case _ => ; } - cg.transitiveCallers(consRoots.toSeq) + cg.transitiveCallers(consRoots.toSeq) ++ targets } lazy val cgWithoutSpecs = CallGraphUtil.constructCallGraph(p, true, false) diff --git a/src/main/scala/leon/laziness/LazyVerificationPhase.scala b/src/main/scala/leon/laziness/LazyVerificationPhase.scala new file mode 100644 index 0000000000000000000000000000000000000000..41375996956a49d5f3d61fe74add146d04266440 --- /dev/null +++ b/src/main/scala/leon/laziness/LazyVerificationPhase.scala @@ -0,0 +1,237 @@ +package leon +package laziness + +import invariant.factories._ +import invariant.util.Util._ +import invariant.util._ +import invariant.structure.FunctionUtils._ +import purescala.ScalaPrinter +import purescala.Common._ +import purescala.Definitions._ +import purescala.Expressions._ +import purescala.ExprOps._ +import purescala.DefOps._ +import purescala.Extractors._ +import purescala.Types._ +import purescala.TypeOps._ +import leon.invariant.util.TypeUtil._ +import leon.invariant.util.LetTupleSimplification._ +import leon.verification.VerificationPhase +import java.io.File +import java.io.FileWriter +import java.io.BufferedWriter +import scala.util.matching.Regex +import leon.purescala.PrettyPrinter +import leon.solvers._ +import leon.solvers.z3._ +import leon.transformations._ +import leon.LeonContext +import leon.LeonOptionDef +import leon.Main +import leon.TransformationPhase +import LazinessUtil._ +import invariant.datastructure._ +import invariant.util.ProgramUtil._ +import purescala.Constructors._ +import leon.verification._ +import PredicateUtil._ +import leon.invariant.engine._ + +object LazyVerificationPhase { + + val debugInstVCs = false + + def removeInstrumentationSpecs(p: Program): Program = { + def hasInstVar(e: Expr) = { + exists { e => InstUtil.InstTypes.exists(i => i.isInstVariable(e)) }(e) + } + val newPosts = p.definedFunctions.collect { + case fd if fd.postcondition.exists { exists(hasInstVar) } => + // remove the conjuncts that use instrumentation vars + val Lambda(resdef, pbody) = fd.postcondition.get + val npost = pbody match { + case And(args) => + createAnd(args.filterNot(hasInstVar)) + case l: Let => // checks if the body of the let can be deconstructed as And + //println(s"Fist let val: ${l.value} body: ${l.body}") + val (letsCons, letsBody) = letStarUnapply(l) + //println("Let* body: "+letsBody) + letsBody match { + case And(args) => + letsCons(createAnd(args.filterNot(hasInstVar))) + case _ => Util.tru + } + case e => Util.tru + } + (fd -> Lambda(resdef, npost)) + }.toMap + ProgramUtil.updatePost(newPosts, p) //note: this will not update libraries + } + + def contextForChecks(userOptions: LeonContext) = { + val solverOptions = Main.processOptions(Seq("--solvers=smt-cvc4,smt-z3", "--assumepre")) + LeonContext(userOptions.reporter, userOptions.interruptManager, + solverOptions.options ++ userOptions.options) + } + + // cumulative stats + var totalTime = 0L + var totalVCs = 0 + var solvedWithZ3 = 0 + var solvedWithCVC4 = 0 + var z3Time = 0L + var cvc4Time = 0L + + def collectCumulativeStats(rep: VerificationReport) { + totalTime += rep.totalTime + totalVCs += rep.totalConditions + val (withz3, withcvc) = rep.vrs.partition{ + case (vc, vr) => + vr.solvedWith.map(s => s.name.contains("smt-z3")).get + } + solvedWithZ3 += withz3.size + solvedWithCVC4 += withcvc.size + z3Time += withz3.map(_._2.timeMs.getOrElse(0L)).sum + cvc4Time += withcvc.map(_._2.timeMs.getOrElse(0L)).sum + } + + def dumpStats() { + println("totalTime: "+f"${totalTime/1000d}%-3.3f") + println("totalVCs: "+totalVCs) + println("solvedWithZ3: "+ solvedWithZ3) + println("solvedWithCVC4: "+ solvedWithCVC4) + println("z3Time: "+f"${z3Time/1000d}%-3.3f") + println("cvc4Time: "+f"${cvc4Time/1000d}%-3.3f") + } + + def checkSpecifications(prog: Program, checkCtx: LeonContext) { + // convert 'axiom annotation to library + prog.definedFunctions.foreach { fd => + if (fd.annotations.contains("axiom")) + fd.addFlag(Annotation("library", Seq())) + } + // val functions = Seq() //Seq("--functions=rotate") + // val solverOptions = if (debugSolvers) Seq("--debug=solver") else Seq() + // val unfoldFactor = 3 , + // "--unfoldFactor="+unfoldFactor) ++ solverOptions ++ functions + //val solverOptions = Main.processOptions(Seq("--solvers=smt-cvc4,smt-z3", "--assumepre") + val report = VerificationPhase.apply(checkCtx, prog) + // collect stats + collectCumulativeStats(report) + println(report.summaryString) + /*ctx.reporter.whenDebug(leon.utils.DebugSectionTimers) { debug => + ctx.timers.outputTable(debug) + }*/ + } + + def checkInstrumentationSpecs(p: Program, checkCtx: LeonContext) = { + + val useOrb = checkCtx.findOption(LazinessEliminationPhase.optUseOrb).getOrElse(false) + p.definedFunctions.foreach { fd => + if (fd.annotations.contains("axiom")) + fd.addFlag(Annotation("library", Seq())) + } + val funsToCheck = p.definedFunctions.filter(shouldGenerateVC) + if (useOrb) { + // create an inference context + val inferOpts = Main.processOptions(Seq("--disableInfer", "--assumepreInf", "--minbounds")) + val ctxForInf = LeonContext(checkCtx.reporter, checkCtx.interruptManager, + inferOpts.options ++ checkCtx.options) + val inferctx = new InferenceContext(p, ctxForInf) + val vcSolver = (funDef: FunDef, prog: Program) => new VCSolver(inferctx, prog, funDef) + prettyPrintProgramToFile(inferctx.inferProgram, checkCtx, "-inferProg", true) + (new InferenceEngine(inferctx)).analyseProgram(inferctx.inferProgram, funsToCheck, vcSolver, None) + } else { + val vcs = funsToCheck.map { fd => + val (ants, post, tmpl) = createVC(fd) + if (tmpl.isDefined) + throw new IllegalStateException("Postcondition has holes! Run with --useOrb option") + val vc = implies(ants, post) + if (debugInstVCs) + println(s"VC for function ${fd.id} : " + vc) + VC(vc, fd, VCKinds.Postcondition) + } + val rep = checkVCs(vcs, checkCtx, p) + // record some stats + collectCumulativeStats(rep) + println("Resource Verification Results: \n" + rep.summaryString) + } + } + + def accessesSecondRes(e: Expr, resid: Identifier): Boolean = + exists(_ == TupleSelect(resid.toVariable, 2))(e) + + /** + * Note: we also skip verification of uninterpreted functions + */ + def shouldGenerateVC(fd: FunDef) = { + !fd.isLibrary && InstUtil.isInstrumented(fd) && fd.hasBody && + fd.postcondition.exists { post => + val Lambda(Seq(resdef), pbody) = post + accessesSecondRes(pbody, resdef.id) + } + } + + /** + * creates vcs + * Note: we only need to check specs involving instvars since others were checked before. + * Moreover, we can add other specs as assumptions since (A => B) ^ ((A ^ B) => C) => A => B ^ C + * checks if the expression uses res._2 which corresponds to instvars after instrumentation + */ + def createVC(fd: FunDef) = { + val Lambda(Seq(resdef), _) = fd.postcondition.get + val (pbody, tmpl) = (fd.getPostWoTemplate, fd.template) + val (instPost, assumptions) = pbody match { + case And(args) => + val (instSpecs, rest) = args.partition(accessesSecondRes(_, resdef.id)) + (createAnd(instSpecs), createAnd(rest)) + case l: Let => + val (letsCons, letsBody) = letStarUnapply(l) + letsBody match { + case And(args) => + val (instSpecs, rest) = args.partition(accessesSecondRes(_, resdef.id)) + (letsCons(createAnd(instSpecs)), + letsCons(createAnd(rest))) + case _ => + (l, Util.tru) + } + case e => (e, Util.tru) + } + val ants = createAnd(Seq(fd.precOrTrue, assumptions, Equals(resdef.id.toVariable, fd.body.get))) + (ants, instPost, tmpl) + } + + def checkVCs(vcs: List[VC], checkCtx: LeonContext, p: Program) = { + val timeout: Option[Long] = None + val reporter = checkCtx.reporter + // Solvers selection and validation + val baseSolverF = SolverFactory.getFromSettings(checkCtx, p) + val solverF = timeout match { + case Some(sec) => + baseSolverF.withTimeout(sec / 1000) + case None => + baseSolverF + } + val vctx = VerificationContext(checkCtx, p, solverF, reporter) + try { + VerificationPhase.checkVCs(vctx, vcs) + //println("Resource Verification Results: \n" + veriRep.summaryString) + } finally { + solverF.shutdown() + } + } + + class VCSolver(ctx: InferenceContext, p: Program, rootFd: FunDef) extends + UnfoldingTemplateSolver(ctx, p, rootFd) { + override def constructVC(fd: FunDef): (Expr, Expr) = { + val (ants, post, tmpl) = createVC(fd) + val conseq = matchToIfThenElse(createAnd(Seq(post, tmpl.getOrElse(Util.tru)))) + (matchToIfThenElse(ants), conseq) + } + + /** + * TODO: fix this!! + */ + override def verifyInvariant(newposts: Map[FunDef, Expr]) = (Some(false), Model.empty) + } +} diff --git a/testcases/lazy-datastructures/ManualnOutdated/Concat.scala b/testcases/lazy-datastructures/ManualnOutdated/Concat.scala index 3d3969956b2b27c96646cd9b0a3f9267a28663c0..8a881fc0177518b673ee1ce62a4bdecc338d5fb3 100644 --- a/testcases/lazy-datastructures/ManualnOutdated/Concat.scala +++ b/testcases/lazy-datastructures/ManualnOutdated/Concat.scala @@ -24,7 +24,7 @@ object Concat { case Cons(x, xs) => SCons(x, $(concat(xs, l2))) case Nil() => SNil[T]() } - } ensuring(_ => time <= ?) + } ensuring(_ => time <= 20) def kthElem[T](l: $[LList[T]], k: BigInt): Option[T] = { require(k >= 1) @@ -35,10 +35,10 @@ object Concat { kthElem(xs, k - 1) case SNil() => None[T] } - } ensuring (_ => time <= ? * k) + } //ensuring (_ => time <= ? * k) def concatnSelect[T](l1: List[T], l2: List[T], k: BigInt) : Option[T] = { require(k >= 1) kthElem($(concat(l1, l2)), k) - } ensuring (_ => time <= ? * k) + } //ensuring (_ => time <= ? * k) }