diff --git a/library/annotation/package.scala b/library/annotation/package.scala index 78121f038e15d1844f15f9ff45cdd807b4660708..94c4adb7de4bcd01a344f4ee01dc5f892f53be09 100644 --- a/library/annotation/package.scala +++ b/library/annotation/package.scala @@ -25,4 +25,6 @@ package object annotation { class axiom extends StaticAnnotation @ignore class invstate extends StaticAnnotation + @ignore + class memoize extends StaticAnnotation } \ No newline at end of file diff --git a/src/main/scala/leon/Main.scala b/src/main/scala/leon/Main.scala index 71b49841f1c647c6cb851f529da76a1864037c5e..579e1803c770ba740f3c6d915ed6d9ca1579c558 100644 --- a/src/main/scala/leon/Main.scala +++ b/src/main/scala/leon/Main.scala @@ -57,6 +57,7 @@ object Main { val optInstrument = LeonFlagOptionDef("instrument", "Instrument the code for inferring time/depth/stack bounds", false) val optInferInv = LeonFlagOptionDef("inferInv", "Infer invariants from (instrumented) the code", false) val optLazyEval = LeonFlagOptionDef("lazy", "Handles programs that may use the lazy construct", false) + //val optMemoize = LeonFlagOptionDef("memoize", "Handles programs that uses memoization", false) override val definedOptions: Set[LeonOptionDef[Any]] = Set(optTermination, optRepair, optSynthesis, optIsabelle, optNoop, optHelp, optEval, optVerify, optInstrument, optInferInv, optLazyEval) diff --git a/src/main/scala/leon/laziness/LazinessEliminationPhase.scala b/src/main/scala/leon/laziness/LazinessEliminationPhase.scala index 293aff0eab509d90bbccdcfd805d6aa11b1f0e6d..c8121446f28971e9e6a8ba5f63b52346fe941b53 100644 --- a/src/main/scala/leon/laziness/LazinessEliminationPhase.scala +++ b/src/main/scala/leon/laziness/LazinessEliminationPhase.scala @@ -57,20 +57,16 @@ object LazinessEliminationPhase extends TransformationPhase { // options that control behavior val optRefEquality = LeonFlagOptionDef("refEq", "Uses reference equality for comparing closures", false) - /** - * TODO: enforce that the specs do not create lazy closures - * TODO: we are forced to make an assumption that lazy ops takes as type parameters only those - * type parameters of their return type and not more. (enforce this) - * TODO: Check that lazy types are not nested - * TODO: expose in/out state to the user level, so that they can be used in specs - * For now using lazyinv annotation - */ def apply(ctx: LeonContext, prog: Program): Program = { if (dumpInputProg) println("Input prog: \n" + ScalaPrinter.apply(prog)) + + val (pass, msg) = sanityChecks(prog, ctx) + assert(pass, msg) + val nprog = liftLazyExpressions(prog) - if(dumpLiftProg) { + if (dumpLiftProg) { prettyPrintProgramToFile(nprog, ctx, "-lifted") } @@ -116,6 +112,57 @@ object LazinessEliminationPhase extends TransformationPhase { instProg } + /** + * TODO: we are forced to make an assumption that lazy ops takes as type parameters only those + * type parameters of their return type and not more. (This is checked in the closureFactory,\ + * but may be check this upfront) + */ + def sanityChecks(p: Program, ctx: LeonContext): (Boolean, String) = { + // using a bit of a state here + var failMsg = "" + val checkres = p.definedFunctions.forall { + case fd if !fd.isLibrary => + /** + * Fails when the argument to a suspension creation + * is either a normal or memoized function depending on the flag + * 'argMem' = true implies fail if the argument is a memoized function + */ + def failOnClosures(argMem: Boolean, e: Expr) = e match { + case finv: FunctionInvocation if isLazyInvocation(finv)(p) => + finv match { + case FunctionInvocation(_, Seq(FunctionInvocation(callee, _))) if isMemoized(callee.fd) => argMem + case _ => !argMem + } + case _ => false + } + // specs should not create lazy closures, but can refer to memoized functions + val specCheckFailed = exists(failOnClosures(false, _))(fd.precOrTrue) || exists(failOnClosures(false, _))(fd.postOrTrue) + if (specCheckFailed) { + failMsg = "Lazy closure creation in the specification of function: " + fd.id + false + } else { + // cannot suspend a memoized function + val bodyCheckFailed = exists(failOnClosures(true, _))(fd.body.getOrElse(Util.tru)) + if (bodyCheckFailed) { + failMsg = "Suspending a memoized function is not supported! in body of: " + fd.id + false + } else { + def nestedSusp(e: Expr) = e match { + case finv @ FunctionInvocation(_, Seq(call: FunctionInvocation)) if isLazyInvocation(finv)(p) && isLazyInvocation(call)(p) => true + case _ => false + } + val nestedCheckFailed = exists(nestedSusp)(fd.body.getOrElse(Util.tru)) + if (nestedCheckFailed) { + failMsg = "Nested suspension creation in the body: " + fd.id + false + } else true + } + } + case _ => true + } + (checkres, failMsg) + } + /** * convert the argument of every lazy constructors to a procedure */ @@ -199,7 +246,7 @@ object LazinessEliminationPhase extends TransformationPhase { 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 + 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) @@ -218,7 +265,7 @@ object LazinessEliminationPhase extends TransformationPhase { def contextForChecks(userOptions: LeonContext) = { val solverOptions = Main.processOptions(Seq("--solvers=smt-cvc4,smt-z3", "--assumepre")) LeonContext(userOptions.reporter, userOptions.interruptManager, - solverOptions.options ++ userOptions.options) + solverOptions.options ++ userOptions.options) } def checkSpecifications(prog: Program, checkCtx: LeonContext) { @@ -227,10 +274,10 @@ object LazinessEliminationPhase extends TransformationPhase { 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 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) println(report.summaryString) @@ -244,23 +291,23 @@ object LazinessEliminationPhase extends TransformationPhase { if (fd.annotations.contains("axiom")) fd.addFlag(Annotation("library", Seq())) } -// val solverOptions = if (debugSolvers) Seq("--debug=solver") else Seq() -// val ctx = Main.processOptions(Seq("--solvers=smt-cvc4,smt-z3", "--assumepre") ++ solverOptions) + // val solverOptions = if (debugSolvers) Seq("--debug=solver") else Seq() + // val ctx = Main.processOptions(Seq("--solvers=smt-cvc4,smt-z3", "--assumepre") ++ solverOptions) //(a) create 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 accessesSecondRes(e: Expr, resid: Identifier): Boolean = - exists(_ == TupleSelect(resid.toVariable, 2))(e) + exists(_ == TupleSelect(resid.toVariable, 2))(e) val vcs = p.definedFunctions.collect { // skipping verification of uninterpreted functions case fd if !fd.isLibrary && InstUtil.isInstrumented(fd) && fd.hasBody && - fd.postcondition.exists{post => - val Lambda(Seq(resdef), pbody) = post - accessesSecondRes(pbody, resdef.id) - } => + fd.postcondition.exists { post => + val Lambda(Seq(resdef), pbody) = post + accessesSecondRes(pbody, resdef.id) + } => val Lambda(Seq(resdef), pbody) = fd.postcondition.get val (instPost, assumptions) = pbody match { case And(args) => @@ -272,16 +319,16 @@ object LazinessEliminationPhase extends TransformationPhase { case And(args) => val (instSpecs, rest) = args.partition(accessesSecondRes(_, resdef.id)) (letsCons(createAnd(instSpecs)), - letsCons(createAnd(rest))) + letsCons(createAnd(rest))) case _ => (l, Util.tru) } case e => (e, Util.tru) } val vc = implies(createAnd(Seq(fd.precOrTrue, assumptions, - Equals(resdef.id.toVariable, fd.body.get))), instPost) - if(debugInstVCs) - println(s"VC for function ${fd.id} : "+vc) + Equals(resdef.id.toVariable, fd.body.get))), instPost) + if (debugInstVCs) + println(s"VC for function ${fd.id} : " + vc) VC(vc, fd, VCKinds.Postcondition) } //(b) create a verification context diff --git a/src/main/scala/leon/laziness/LazinessUtil.scala b/src/main/scala/leon/laziness/LazinessUtil.scala index fe56e5878f15b5a903ff4a08b7939155a228f6ba..7c6a3b45f413c346159dc88e779736e4ddfcc46b 100644 --- a/src/main/scala/leon/laziness/LazinessUtil.scala +++ b/src/main/scala/leon/laziness/LazinessUtil.scala @@ -28,6 +28,10 @@ import purescala.PrinterOptions object LazinessUtil { + def isMemoized(fd: FunDef) = { + fd.flags.contains(Annotation("memoize", Seq())) + } + def prettyPrintProgramToFile(p: Program, ctx: LeonContext, suffix: String) { val optOutputDirectory = new LeonOptionDef[String] { val name = "o" @@ -192,7 +196,7 @@ object LazinessUtil { def isEvalFunction(fd: FunDef) = { fd.id.name.startsWith("eval@") } - + def isStateParam(id: Identifier) = { id.name.startsWith("st@") } diff --git a/src/main/scala/leon/laziness/LazyClosureFactory.scala b/src/main/scala/leon/laziness/LazyClosureFactory.scala index 4d98dc89e1f82756ee9616a681206310523b7f86..91a33252161cad2187f75ab24086d039aa9c2aaf 100644 --- a/src/main/scala/leon/laziness/LazyClosureFactory.scala +++ b/src/main/scala/leon/laziness/LazyClosureFactory.scala @@ -34,7 +34,7 @@ class LazyClosureFactory(p: Program) { implicit val prog = p /** * Create a mapping from types to the lazyops that may produce a value of that type - * TODO: relax that requirement that type parameters of return type of a function + * TODO: relax the requirement that type parameters of return type of a function * lazy evaluated should include all of its type parameters */ private val (tpeToADT, opToCaseClass) = { diff --git a/testcases/lazy-datastructures/Unproved/FibonacciMemoized.scala b/testcases/lazy-datastructures/memoization/FibonacciMemoized.scala similarity index 100% rename from testcases/lazy-datastructures/Unproved/FibonacciMemoized.scala rename to testcases/lazy-datastructures/memoization/FibonacciMemoized.scala