diff --git a/library/collection/List.scala b/library/collection/List.scala index 530bb5b41eb79539550897d201183851f721b262..448aea577adba630090410f2acc4b410c22d0aa5 100644 --- a/library/collection/List.scala +++ b/library/collection/List.scala @@ -285,46 +285,40 @@ sealed abstract class List[T] { (take(c), drop(c)) } - def insertAt(pos: BigInt, l: List[T]): List[T] = { - if(pos < 0) { - insertAt(size + pos, l) - } else if(pos == BigInt(0)) { - l ++ this - } else { - this match { - case Cons(h, t) => - Cons(h, t.insertAt(pos-1, l)) - case Nil() => - l + def insertAt(pos: BigInt, l: List[T]): List[T] = (this match { + case Nil() => l + case Cons(h, t) => + if (pos < 0) { + insertAt(size + pos, l) + } else if (pos == BigInt(0)) { + l ++ this + } else { + Cons(h, t.insertAt(pos - 1, l)) } - } - } ensuring { res => + }) ensuring { res => res.size == this.size + l.size && res.content == this.content ++ l.content } - def replaceAt(pos: BigInt, l: List[T]): List[T] = { - if(pos < 0) { - replaceAt(size + pos, l) - } else if(pos == BigInt(0)) { - l ++ this.drop(l.size) - } else { - this match { - case Cons(h, t) => - Cons(h, t.replaceAt(pos-1, l)) - case Nil() => - l + def replaceAt(pos: BigInt, l: List[T]): List[T] = (this match { + case Nil() => l + case Cons(h, t) => + if (pos < 0) { + replaceAt(size + pos, l) + } else if (pos == BigInt(0)) { + l ++ this.drop(l.size) + } else { + Cons(h, t.replaceAt(pos - 1, l)) } - } - } ensuring { res => + }) ensuring { res => res.content.subsetOf(l.content ++ this.content) } def rotate(s: BigInt): List[T] = { - if (s < 0) { - rotate(size + s) - } else if (s > size) { - rotate(s - size) + if (isEmpty) { + Nil() + } else if (s < 0) { + rotate(size+s) } else { drop(s) ++ take(s) } diff --git a/src/main/scala/leon/termination/ChainProcessor.scala b/src/main/scala/leon/termination/ChainProcessor.scala index 753663bbe1eae8ad0f6ca0e714b1a2102fe11a8c..5537d41e24e2bf6340a2802e5a8bc2de29621772 100644 --- a/src/main/scala/leon/termination/ChainProcessor.scala +++ b/src/main/scala/leon/termination/ChainProcessor.scala @@ -14,13 +14,13 @@ class ChainProcessor(val checker: TerminationChecker with ChainBuilder with Chai def run(problem: Problem) = { reporter.debug("- Strengthening postconditions") - checker.strengthenPostconditions(problem.funDefs)(this) + checker.strengthenPostconditions(problem.funSet)(this) reporter.debug("- Strengthening applications") - checker.strengthenApplications(problem.funDefs)(this) + checker.strengthenApplications(problem.funSet)(this) reporter.debug("- Running ChainBuilder") - val chainsMap : Map[FunDef, (Set[FunDef], Set[Chain])] = problem.funDefs.map { funDef => + val chainsMap : Map[FunDef, (Set[FunDef], Set[Chain])] = problem.funSet.map { funDef => funDef -> checker.getChains(funDef)(this) }.toMap @@ -28,7 +28,7 @@ class ChainProcessor(val checker: TerminationChecker with ChainBuilder with Chai if (loopPoints.size > 1) { reporter.debug("-+> Multiple looping points, can't build chain proof") - (Nil, List(problem)) + None } else { def exprs(fd: FunDef): (Expr, Seq[(Seq[Expr], Expr)], Set[Chain]) = { @@ -43,7 +43,7 @@ class ChainProcessor(val checker: TerminationChecker with ChainBuilder with Chai (e1, e2s, fdChains) } - val funDefs = if (loopPoints.size == 1) Set(loopPoints.head) else problem.funDefs + val funDefs = if (loopPoints.size == 1) Set(loopPoints.head) else problem.funSet reporter.debug("-+> Searching for structural size decrease") @@ -60,11 +60,10 @@ class ChainProcessor(val checker: TerminationChecker with ChainBuilder with Chai numericFormulas.exists(formula => definitiveALL(formula)) } - if (structuralDecreasing || numericDecreasing) { - (problem.funDefs.map(Cleared), Nil) - } else { - (Nil, List(problem)) - } + if (structuralDecreasing || numericDecreasing) + Some(problem.funDefs map Cleared) + else + None } } } diff --git a/src/main/scala/leon/termination/ComplexTerminationChecker.scala b/src/main/scala/leon/termination/ComplexTerminationChecker.scala index 78a5c93c4f239805407f3bcd0ec8338f04ece65a..e4936762b2003851b91fb83ecb06379b205b4151 100644 --- a/src/main/scala/leon/termination/ComplexTerminationChecker.scala +++ b/src/main/scala/leon/termination/ComplexTerminationChecker.scala @@ -9,62 +9,22 @@ import purescala.Expressions._ import scala.collection.mutable.{Map => MutableMap} class ComplexTerminationChecker(context: LeonContext, program: Program) - extends TerminationChecker(context, program) + extends ProcessingPipeline(context, program) with StructuralSize with RelationComparator with ChainComparator with Strengthener - with ComponentBuilder with RelationBuilder with ChainBuilder { val name = "Complex Termination Checker" val description = "A modular termination checker with a few basic modules™" - private val pipeline = new ProcessingPipeline( - program, context, // required for solvers and reporting - new ComponentProcessor(this), + def processors = List( new RecursionProcessor(this), new RelationProcessor(this), new ChainProcessor(this), new LoopProcessor(this) ) - private val clearedMap : MutableMap[FunDef, String] = MutableMap() - private val brokenMap : MutableMap[FunDef, (String, Seq[Expr])] = MutableMap() - private val maybeBrokenMap : MutableMap[FunDef, (String, Seq[Expr])] = MutableMap() - - def initialize() { - for ((reason, results) <- pipeline.run; result <- results) result match { - case Cleared(fd) => clearedMap(fd) = reason - case Broken(fd, args) => brokenMap(fd) = (reason, args) - case MaybeBroken(fd, args) => maybeBrokenMap(fd) = (reason, args) - } - } - - private val terminationMap : MutableMap[FunDef, TerminationGuarantee] = MutableMap() - def terminates(funDef: FunDef): TerminationGuarantee = terminationMap.get(funDef) match { - case Some(guarantee) => guarantee - case None => { - val guarantee = brokenMap.get(funDef) match { - case Some((reason, args)) => LoopsGivenInputs(reason, args) - case None => maybeBrokenMap.get(funDef) match { - case Some((reason, args)) => MaybeLoopsGivenInputs(reason, args) - case None => program.callGraph.transitiveCallees(funDef) intersect (brokenMap.keys.toSet ++ maybeBrokenMap.keys) match { - case set if set.nonEmpty => CallsNonTerminating(set) - case _ => if (pipeline.clear(funDef)) clearedMap.get(funDef) match { - case Some(reason) => Terminates(reason) - case None => scala.sys.error(funDef.id + " -> not problem, but not cleared or broken ??") - } else NoGuarantee - } - } - } - - if (guarantee != NoGuarantee) { - terminationMap(funDef) = guarantee - } - - guarantee - } - } } diff --git a/src/main/scala/leon/termination/ComponentBuilder.scala b/src/main/scala/leon/termination/ComponentBuilder.scala deleted file mode 100644 index 2826cb3d4725722167e657defe7932936b1ab093..0000000000000000000000000000000000000000 --- a/src/main/scala/leon/termination/ComponentBuilder.scala +++ /dev/null @@ -1,10 +0,0 @@ -/* Copyright 2009-2015 EPFL, Lausanne */ - -package leon -package termination - -import utils._ - -trait ComponentBuilder { - def getComponents[T](graph : Map[T,Set[T]]) : List[Set[T]] = SCC.scc(graph) -} diff --git a/src/main/scala/leon/termination/ComponentProcessor.scala b/src/main/scala/leon/termination/ComponentProcessor.scala deleted file mode 100644 index 4a7935d78985dd992c3dd17b01c5946a92fb29cd..0000000000000000000000000000000000000000 --- a/src/main/scala/leon/termination/ComponentProcessor.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* Copyright 2009-2015 EPFL, Lausanne */ - -package leon -package termination - -import purescala.Definitions._ -import scala.collection.mutable.{Map => MutableMap} - -class ComponentProcessor(val checker: TerminationChecker with ComponentBuilder) extends Processor { - - val name: String = "Component Processor" - - def run(problem: Problem) = { - val pairs : Set[(FunDef, FunDef)] = checker.program.callGraph.allCalls.filter({ - case (fd1, fd2) => problem.funDefs(fd1) && problem.funDefs(fd2) - }) - - val callGraph : Map[FunDef,Set[FunDef]] = pairs.groupBy(_._1).mapValues(_.map(_._2)) - val components : List[Set[FunDef]] = checker.getComponents(callGraph) - val fdToSCC : Map[FunDef, Set[FunDef]] = components.map(set => set.map(fd => fd -> set)).flatten.toMap - - val terminationCache : MutableMap[FunDef, Boolean] = MutableMap() - def terminates(fd: FunDef) : Boolean = terminationCache.getOrElse(fd, { - val scc = fdToSCC.getOrElse(fd, Set()) // functions that aren't called and don't call belong to no SCC - val result = if (scc(fd)) false else scc.forall(terminates) - terminationCache(fd) = result - result - }) - - val terminating = problem.funDefs.filter(terminates) - assert(components.forall(scc => (scc subsetOf terminating) || (scc intersect terminating).isEmpty)) - val newProblems = components.filter(scc => (scc intersect terminating).isEmpty).map(Problem) - (terminating.map(Cleared), newProblems) - } -} diff --git a/src/main/scala/leon/termination/LoopProcessor.scala b/src/main/scala/leon/termination/LoopProcessor.scala index a63928021d3e40964e892fa2f604bf5757c93bf6..9f419e8ab1e4a3a10533ff7c6bd5358ddf9bb6ec 100644 --- a/src/main/scala/leon/termination/LoopProcessor.scala +++ b/src/main/scala/leon/termination/LoopProcessor.scala @@ -16,10 +16,10 @@ class LoopProcessor(val checker: TerminationChecker with ChainBuilder with Stren def run(problem: Problem) = { reporter.debug("- Strengthening applications") - checker.strengthenApplications(problem.funDefs)(this) + checker.strengthenApplications(problem.funSet)(this) reporter.debug("- Running ChainBuilder") - val chains : Set[Chain] = problem.funDefs.flatMap(fd => checker.getChains(fd)(this)._2) + val chains : Set[Chain] = problem.funSet.flatMap(fd => checker.getChains(fd)(this)._2) reporter.debug("- Searching for loops") val nonTerminating: MutableMap[FunDef, Result] = MutableMap.empty @@ -46,10 +46,10 @@ class LoopProcessor(val checker: TerminationChecker with ChainBuilder with Stren cs.flatMap(c1 => chains.flatMap(c2 => c1.compose(c2))) } - val results = nonTerminating.values.toSet - val remaining = problem.funDefs -- nonTerminating.keys - val newProblems = if (remaining.nonEmpty) List(Problem(remaining)) else Nil - (results, newProblems) + if (nonTerminating.nonEmpty) + Some(nonTerminating.values.toSeq) + else + None } } diff --git a/src/main/scala/leon/termination/ProcessingPipeline.scala b/src/main/scala/leon/termination/ProcessingPipeline.scala new file mode 100644 index 0000000000000000000000000000000000000000..3b7a36c0a3127e95a524a95a296bd1fb5295521f --- /dev/null +++ b/src/main/scala/leon/termination/ProcessingPipeline.scala @@ -0,0 +1,217 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package termination + +import purescala.Expressions._ +import purescala.Definitions._ +import utils._ + +trait Problem { + def funSet: Set[FunDef] + def funDefs: Seq[FunDef] + def contains(fd: FunDef): Boolean = funSet(fd) + + override def toString : String = funDefs.map(_.id).mkString("Problem(", ",", ")") +} + +object Problem { + def apply(fds: Set[FunDef]): Problem = new Problem { + val funSet = fds + lazy val funDefs = funSet.toSeq + } + + def apply(fds: Seq[FunDef]): Problem = new Problem { + val funDefs = fds + lazy val funSet = funDefs.toSet + } +} + +sealed abstract class Result(funDef: FunDef) +case class Cleared(funDef: FunDef) extends Result(funDef) +case class Broken(funDef: FunDef, args: Seq[Expr]) extends Result(funDef) +case class MaybeBroken(funDef: FunDef, args: Seq[Expr]) extends Result(funDef) + +abstract class ProcessingPipeline(context: LeonContext, program: Program) extends TerminationChecker(context, program) { + implicit val debugSection = utils.DebugSectionTermination + + import scala.collection.mutable.{PriorityQueue, Map => MutableMap, Set => MutableSet} + + def processors: List[Processor] + + private lazy val processorArray: Array[Processor] = { + assert(processors.nonEmpty) + processors.toArray + } + + private val reporter: Reporter = context.reporter + + implicit object ProblemOrdering extends Ordering[(Problem, Int)] { + def compare(a: (Problem, Int), b: (Problem, Int)): Int = { + val ((aProblem, aIndex), (bProblem, bIndex)) = (a,b) + val (aDefs, bDefs) = (aProblem.funSet, bProblem.funSet) + + val aCallees: Set[FunDef] = aDefs.flatMap(program.callGraph.transitiveCallees) + val bCallees: Set[FunDef] = bDefs.flatMap(program.callGraph.transitiveCallees) + + lazy val aCallers: Set[FunDef] = aDefs.flatMap(program.callGraph.transitiveCallers) + lazy val bCallers: Set[FunDef] = bDefs.flatMap(program.callGraph.transitiveCallers) + + val aCallsB = bDefs.subsetOf(aCallees) + val bCallsA = aDefs.subsetOf(bCallees) + + if (aCallsB && !bCallsA) { + -1 + } else if (bCallsA && !aCallsB) { + 1 + } else { + val smallerPool = bCallees.size compare aCallees.size + if (smallerPool != 0) smallerPool else { + val largerImpact = aCallers.size compare bCallers.size + if (largerImpact != 0) largerImpact else { + bIndex compare aIndex + } + } + } + } + } + + private val problems = new PriorityQueue[(Problem, Int)] + def running = problems.nonEmpty + + private val clearedMap : MutableMap[FunDef, String] = MutableMap.empty + private val brokenMap : MutableMap[FunDef, (String, Seq[Expr])] = MutableMap.empty + private val maybeBrokenMap : MutableMap[FunDef, (String, Seq[Expr])] = MutableMap.empty + + private val unsolved : MutableSet[Problem] = MutableSet.empty + private val dependencies : MutableSet[Problem] = MutableSet.empty + + def isProblem(fd: FunDef): Boolean = + unsolved.exists(_.contains(fd)) || + dependencies.exists(_.contains(fd)) || { + val problemDefs = problems.flatMap(_._1.funDefs).toSet + problemDefs(fd) || { + val callees = program.callGraph.transitiveCallees(fd) + (problemDefs intersect callees).nonEmpty + } + } + + private def printQueue() { + val sb = new StringBuilder() + sb.append("- Problems in Queue:\n") + for(p @ (problem, index) <- problems) { + sb.append(" -> Problem awaiting processor #") + sb.append(index + 1) + sb.append(" (") + sb.append(processorArray(index).name) + sb.append(")") + if (p == problems.head) sb.append(" <- next") + sb.append("\n") + for(funDef <- problem.funDefs) { + sb.append(" " + funDef.id + "\n") + } + } + reporter.debug(sb.toString) + } + + private def printResult(results: List[Result]) { + val sb = new StringBuilder() + sb.append("- Processing Result:\n") + for(result <- results) result match { + case Cleared(fd) => sb.append(f" ${fd.id}%-10s Cleared\n") + case Broken(fd, args) => sb.append(f" ${fd.id}%-10s ${"Broken for arguments: " + args.mkString("(", ",", ")")}\n") + case MaybeBroken(fd, args) => sb.append(f" ${fd.id}%-10s ${"HO construct application breaks for arguments: " + args.mkString("(", ",", ")")}\n") + } + reporter.debug(sb.toString) + } + + private val terminationMap : MutableMap[FunDef, TerminationGuarantee] = MutableMap.empty + def terminates(funDef: FunDef): TerminationGuarantee = terminationMap.get(funDef) match { + case Some(guarantee) => guarantee + case None => + val guarantee = brokenMap.get(funDef) match { + case Some((reason, args)) => LoopsGivenInputs(reason, args) + case None => maybeBrokenMap.get(funDef) match { + case Some((reason, args)) => MaybeLoopsGivenInputs(reason, args) + case None => + val callees = program.callGraph.transitiveCallees(funDef) + val broken = brokenMap.keys.toSet ++ maybeBrokenMap.keys + callees intersect broken match { + case set if set.nonEmpty => CallsNonTerminating(set) + case _ if isProblem(funDef) => NoGuarantee + case _ => clearedMap.get(funDef) match { + case Some(reason) => Terminates(reason) + case None if !running => + verifyTermination(funDef) + terminates(funDef) + case _ => + if (!dependencies.exists(_.contains(funDef))) { + dependencies ++= generateProblems(funDef) + } + NoGuarantee + } + } + } + } + + if (!running) terminationMap(funDef) = guarantee + guarantee + } + + private def generateProblems(funDef: FunDef): Seq[Problem] = { + val funDefs = program.callGraph.transitiveCallees(funDef) + funDef + val pairs = program.callGraph.allCalls.filter { case (fd1, fd2) => funDefs(fd1) && funDefs(fd2) } + val callGraph = pairs.groupBy(_._1).mapValues(_.map(_._2)) + val components = SCC.scc(callGraph) + + for (fd <- funDefs -- components.toSet.flatten) clearedMap(fd) = "Non-recursive" + + components.map(fds => Problem(fds.toSeq)) + } + + def verifyTermination(funDef: FunDef): Unit = { + problems ++= generateProblems(funDef).map(_ -> 0) + + val it = new Iterator[(String, List[Result])] { + def hasNext : Boolean = running + def next() : (String, List[Result]) = { + printQueue() + val (problem, index) = problems.head + val processor : Processor = processorArray(index) + reporter.debug("Running " + processor.name) + val result = processor.run(problem) + reporter.debug(" +-> " + (if (result.isDefined) "Success" else "Failure")) + + // dequeue and enqueue atomically to make sure the queue always + // makes sense (necessary for calls to terminates(fd)) + problems.dequeue() + result match { + case None if index == processorArray.length - 1 => + unsolved += problem + case None => + problems.enqueue(problem -> (index + 1)) + case Some(results) => + val impacted = problem.funDefs.flatMap(fd => program.callGraph.transitiveCallers(fd)) + val reenter = unsolved.filter(p => (p.funDefs intersect impacted).nonEmpty) + problems.enqueue(reenter.map(_ -> 0).toSeq : _*) + unsolved --= reenter + } + + if (dependencies.nonEmpty) { + problems.enqueue(dependencies.map(_ -> 0).toSeq : _*) + dependencies.clear + } + + processor.name -> result.toList.flatten + } + } + + for ((reason, results) <- it; result <- results) result match { + case Cleared(fd) => clearedMap(fd) = reason + case Broken(fd, args) => brokenMap(fd) = (reason, args) + case MaybeBroken(fd, args) => maybeBrokenMap(fd) = (reason, args) + } + } +} + +// vim: set ts=4 sw=4 et: diff --git a/src/main/scala/leon/termination/Processor.scala b/src/main/scala/leon/termination/Processor.scala index b6c6972f9925ad5389b99cdad6689ace103c2fa3..46c125d8e7172408443e8c4eb001bbb2c71dbd76 100644 --- a/src/main/scala/leon/termination/Processor.scala +++ b/src/main/scala/leon/termination/Processor.scala @@ -12,15 +12,6 @@ import scala.concurrent.duration._ import leon.solvers._ import leon.solvers.z3._ -case class Problem(funDefs: Set[FunDef]) { - override def toString : String = funDefs.map(_.id).mkString("Problem(", ",", ")") -} - -sealed abstract class Result(funDef: FunDef) -case class Cleared(funDef: FunDef) extends Result(funDef) -case class Broken(funDef: FunDef, args: Seq[Expr]) extends Result(funDef) -case class MaybeBroken(funDef: FunDef, args: Seq[Expr]) extends Result(funDef) - trait Processor { val name: String @@ -30,7 +21,7 @@ trait Processor { implicit val debugSection = utils.DebugSectionTermination val reporter = checker.context.reporter - def run(problem: Problem): (Traversable[Result], Traversable[Problem]) + def run(problem: Problem): Option[Seq[Result]] } trait Solvable extends Processor { @@ -82,119 +73,3 @@ trait Solvable extends Processor { } } -class ProcessingPipeline(program: Program, context: LeonContext, _processors: Processor*) { - implicit val debugSection = utils.DebugSectionTermination - - import scala.collection.mutable.{Queue => MutableQueue} - - assert(_processors.nonEmpty) - private val processors: Array[Processor] = _processors.toArray - private val reporter: Reporter = context.reporter - - implicit object ProblemOrdering extends Ordering[(Problem, Int)] { - def compare(a: (Problem, Int), b: (Problem, Int)): Int = { - val ((aProblem, aIndex), (bProblem, bIndex)) = (a,b) - val (aDefs, bDefs) = (aProblem.funDefs, bProblem.funDefs) - - val aCallees: Set[FunDef] = aDefs.flatMap(program.callGraph.transitiveCallees) - val bCallees: Set[FunDef] = bDefs.flatMap(program.callGraph.transitiveCallees) - - lazy val aCallers: Set[FunDef] = aDefs.flatMap(program.callGraph.transitiveCallers) - lazy val bCallers: Set[FunDef] = bDefs.flatMap(program.callGraph.transitiveCallers) - - val aCallsB = bDefs.subsetOf(aCallees) - val bCallsA = aDefs.subsetOf(bCallees) - - if (aCallsB && !bCallsA) { - -1 - } else if (bCallsA && !aCallsB) { - 1 - } else { - val smallerPool = bCallees.size compare aCallees.size - if (smallerPool != 0) smallerPool else { - val largerImpact = aCallers.size compare bCallers.size - if (largerImpact != 0) largerImpact else { - bIndex compare aIndex - } - } - } - } - } - - private val initialProblem : Problem = Problem(program.definedFunctions.toSet) - private val problems = new scala.collection.mutable.PriorityQueue[(Problem, Int)] += (initialProblem -> 0) -// private val problems : MutableQueue[(Problem,Int)] = MutableQueue((initialProblem, 0)) - private var unsolved : Set[Problem] = Set() - - private def printQueue() { - val sb = new StringBuilder() - sb.append("- Problems in Queue:\n") - for((problem, index) <- problems) { - sb.append(" -> Problem awaiting processor #") - sb.append(index + 1) - sb.append(" (") - sb.append(processors(index).name) - sb.append(")\n") - for(funDef <- problem.funDefs) { - sb.append(" " + funDef.id + "\n") - } - } - reporter.debug(sb.toString) - } - - private def printResult(results: List[Result]) { - val sb = new StringBuilder() - sb.append("- Processing Result:\n") - for(result <- results) result match { - case Cleared(fd) => sb.append(f" ${fd.id}%-10s Cleared\n") - case Broken(fd, args) => sb.append(f" ${fd.id}%-10s ${"Broken for arguments: " + args.mkString("(", ",", ")")}\n") - case MaybeBroken(fd, args) => sb.append(f" ${fd.id}%-10s ${"HO construct application breaks for arguments: " + args.mkString("(", ",", ")")}\n") - } - reporter.debug(sb.toString) - } - - def clear(fd: FunDef) : Boolean = { - lazy val unsolvedDefs = unsolved.map(_.funDefs).flatten - lazy val problemDefs = problems.map({ case (problem, _) => problem.funDefs }).flatten.toSet - def issue(defs: Set[FunDef]) : Boolean = defs(fd) || (defs intersect program.callGraph.transitiveCallees(fd)).nonEmpty - ! (issue(unsolvedDefs) || issue(problemDefs)) - } - - def run : Iterator[(String, List[Result])] = new Iterator[(String, List[Result])] { - // basic sanity check, funDefs shouldn't call themselves in precondition! - // XXX: it seems like some do... - // assert(initialProblem.funDefs.forall(fd => !fd.precondition.map({ precondition => - // functionCallsOf(precondition).map(fi => program.transitiveCallees(fi.funDef)).flatten - // }).flatten.toSet(fd))) - - def hasNext : Boolean = problems.nonEmpty - def next() : (String, List[Result]) = { - printQueue() - val (problem, index) = problems.head - val processor : Processor = processors(index) - reporter.debug("Running " + processor.name) - val (_results, nextProblems) = processor.run(problem) - val results = _results.toList - printResult(results) - - // dequeue and enqueue atomically to make sure the queue always - // makes sense (necessary for calls to clear(fd)) - problems.dequeue() - nextProblems match { - case x :: xs if x == problem => - assert(xs.isEmpty) - if (index == processors.length - 1) unsolved += x - else problems.enqueue(x -> (index + 1)) - case list @ x :: xs => - problems.enqueue(list.map(p => p -> 0) : _*) - problems.enqueue(unsolved.map(p => p -> 0).toSeq : _*) - unsolved = Set() - case Nil => // no problem => do nothing! - } - - processor.name -> results.toList - } - } -} - -// vim: set ts=4 sw=4 et: diff --git a/src/main/scala/leon/termination/RecursionProcessor.scala b/src/main/scala/leon/termination/RecursionProcessor.scala index eec2e3bca22c3bde632909dd384c8338ecba6b36..d0b29d62f1e1e0e0ee59783dd6a3796f0ec6e84e 100644 --- a/src/main/scala/leon/termination/RecursionProcessor.scala +++ b/src/main/scala/leon/termination/RecursionProcessor.scala @@ -22,12 +22,14 @@ class RecursionProcessor(val checker: TerminationChecker with RelationBuilder) e rec(expr, true) } - def run(problem: Problem) = if (problem.funDefs.size > 1) (Nil, List(problem)) else { + def run(problem: Problem) = if (problem.funDefs.size > 1) None else { val funDef = problem.funDefs.head val relations = checker.getRelations(funDef) val (recursive, others) = relations.partition({ case Relation(_, _, FunctionInvocation(tfd, _), _) => tfd.fd == funDef }) - if (others.exists({ case Relation(_, _, FunctionInvocation(tfd, _), _) => !checker.terminates(tfd.fd).isGuaranteed })) (Nil, List(problem)) else { + if (others.exists({ case Relation(_, _, FunctionInvocation(tfd, _), _) => !checker.terminates(tfd.fd).isGuaranteed })) { + None + } else { val decreases = funDef.params.zipWithIndex.exists({ case (arg, index) => recursive.forall({ case Relation(_, path, FunctionInvocation(_, args), _) => args(index) match { @@ -41,8 +43,10 @@ class RecursionProcessor(val checker: TerminationChecker with RelationBuilder) e }) }) - if (!decreases) (Nil, List(problem)) - else (Cleared(funDef) :: Nil, Nil) + if (decreases) + Some(Cleared(funDef) :: Nil) + else + None } } } diff --git a/src/main/scala/leon/termination/RelationProcessor.scala b/src/main/scala/leon/termination/RelationProcessor.scala index e8303c74ac6827723a5f67185cfa34ba52fedbcb..d4d1aad73f1c17d9f9ff323f38e19707a7356ae2 100644 --- a/src/main/scala/leon/termination/RelationProcessor.scala +++ b/src/main/scala/leon/termination/RelationProcessor.scala @@ -19,14 +19,14 @@ class RelationProcessor( def run(problem: Problem) = { reporter.debug("- Strengthening postconditions") - checker.strengthenPostconditions(problem.funDefs)(this) + checker.strengthenPostconditions(problem.funSet)(this) reporter.debug("- Strengthening applications") - checker.strengthenApplications(problem.funDefs)(this) + checker.strengthenApplications(problem.funSet)(this) val formulas = problem.funDefs.map({ funDef => funDef -> checker.getRelations(funDef).collect({ - case Relation(_, path, FunctionInvocation(tfd, args), _) if problem.funDefs(tfd.fd) => + case Relation(_, path, FunctionInvocation(tfd, args), _) if problem.funSet(tfd.fd) => val (e1, e2) = (tupleWrap(funDef.params.map(_.toVariable)), tupleWrap(args)) def constraint(expr: Expr) = implies(andJoin(path.toSeq), expr) val greaterThan = checker.sizeDecreasing(e1, e2) @@ -69,14 +69,15 @@ class RelationProcessor( val ok = decreasing.collect({ case (fd, Success) => fd }) val nok = decreasing.collect({ case (fd, Dep(fds)) => fd -> fds }).toList - val (allOk, allNok) = fix(currentReducing, ok, nok) + val (allOk, allNok) = fix(currentReducing, ok.toSet, nok) (allOk, allNok.map(_._1).toSet ++ decreasing.collect({ case (fd, Failure) => fd })) } - assert(terminating ++ nonTerminating == problem.funDefs) + assert(terminating ++ nonTerminating == problem.funSet) - val results = terminating.map(Cleared).toList - val newProblems = if ((problem.funDefs intersect nonTerminating).nonEmpty) List(Problem(nonTerminating)) else Nil - (results, newProblems) + if (nonTerminating.isEmpty) + Some(terminating.map(Cleared).toSeq) + else + None } } diff --git a/src/main/scala/leon/termination/SimpleTerminationChecker.scala b/src/main/scala/leon/termination/SimpleTerminationChecker.scala index 7a92744241c9999fac898a24d92e44c18b3fd982..2a6c5bcbc81941bf1d1c8834468f6993100deea8 100644 --- a/src/main/scala/leon/termination/SimpleTerminationChecker.scala +++ b/src/main/scala/leon/termination/SimpleTerminationChecker.scala @@ -7,12 +7,13 @@ import purescala.Common._ import purescala.Definitions._ import purescala.Expressions._ import purescala.ExprOps._ +import utils._ import scala.collection.mutable.{ Map => MutableMap } import scala.annotation.tailrec -class SimpleTerminationChecker(context: LeonContext, program: Program) extends TerminationChecker(context, program) with ComponentBuilder { +class SimpleTerminationChecker(context: LeonContext, program: Program) extends TerminationChecker(context, program) { val name = "T1" val description = "The simplest form of Terminator™" @@ -20,7 +21,7 @@ class SimpleTerminationChecker(context: LeonContext, program: Program) extends T private lazy val callGraph: Map[FunDef, Set[FunDef]] = program.callGraph.allCalls.groupBy(_._1).mapValues(_.map(_._2)) // one liner from hell - private lazy val components = getComponents(callGraph) + private lazy val components = SCC.scc(callGraph) val allVertices = callGraph.keySet ++ callGraph.values.flatten val sccArray = components.toArray @@ -36,7 +37,6 @@ class SimpleTerminationChecker(context: LeonContext, program: Program) extends T private val answerCache = MutableMap.empty[FunDef, TerminationGuarantee] - def initialize() {} def terminates(funDef: FunDef) = answerCache.getOrElse(funDef, { val g = forceCheckTermination(funDef) answerCache(funDef) = g diff --git a/src/main/scala/leon/termination/TerminationChecker.scala b/src/main/scala/leon/termination/TerminationChecker.scala index f881b125ea8a914be7f29edb837f3d38e349271f..9b12460d2c77834b6685c5050dc7d2367e2d5b32 100644 --- a/src/main/scala/leon/termination/TerminationChecker.scala +++ b/src/main/scala/leon/termination/TerminationChecker.scala @@ -11,7 +11,6 @@ abstract class TerminationChecker(val context: LeonContext, val program: Program val functions = visibleFunDefsFromMain(program) - def initialize() : Unit def terminates(funDef : FunDef) : TerminationGuarantee } diff --git a/src/main/scala/leon/termination/TerminationPhase.scala b/src/main/scala/leon/termination/TerminationPhase.scala index d674340d75fe572d118c15a7b866a2c95c3fde00..4ad4aac3f52f147cf19e84d991edbfc35172494c 100644 --- a/src/main/scala/leon/termination/TerminationPhase.scala +++ b/src/main/scala/leon/termination/TerminationPhase.scala @@ -16,9 +16,7 @@ object TerminationPhase extends LeonPhase[Program,TerminationReport] { // val tc = new SimpleTerminationChecker(ctx, program) val tc = new ComplexTerminationChecker(ctx, program) - tc.initialize() - - val results = visibleFunDefsFromMain(program).toList.sortWith(_.getPos < _.getPos).map { funDef => + val results = funDefsFromMain(program).toList.sortWith(_.getPos < _.getPos).map { funDef => funDef -> tc.terminates(funDef) } val endTime = System.currentTimeMillis diff --git a/src/test/resources/regression/termination/looping/Queue.scala b/src/test/resources/regression/termination/looping/Queue.scala new file mode 100644 index 0000000000000000000000000000000000000000..a571e82e853d96dc84b7376a6751b1281dc50787 --- /dev/null +++ b/src/test/resources/regression/termination/looping/Queue.scala @@ -0,0 +1,28 @@ + +import leon._ +import leon.lang._ +import leon.collection._ + +sealed abstract class Queue[T] { + + def looping_1: BigInt = { + this match { + case QEmpty() => 0 + case QCons(f, r) => f.size + r.size + } + } ensuring (res => res == this.looping_2.size && res >= 0) + + def looping_2: List[T] = (this match { + case QEmpty() => Nil() + case QCons(f, r) => f ++ r.reverse + }) ensuring (resOne => this.looping_3 == resOne.content && resOne.size == this.looping_1 && resOne.size >= 0) + + + def looping_3: Set[T] = (this match { + case QEmpty() => Set() + case QCons(f, r) => f.content ++ r.content + }) ensuring (res => res == this.looping_2.content) +} + +case class QCons[T](f : List[T], r: List[T]) extends Queue[T] +case class QEmpty[T]() extends Queue[T] diff --git a/src/test/resources/regression/termination/valid/Queue.scala b/src/test/resources/regression/termination/valid/Queue.scala new file mode 100644 index 0000000000000000000000000000000000000000..2b73919a7c732a2233273d4f284f4f8c3ad93143 --- /dev/null +++ b/src/test/resources/regression/termination/valid/Queue.scala @@ -0,0 +1,27 @@ + +import leon._ +import leon.lang._ +import leon.collection._ + +sealed abstract class Queue[T] { + + def size: BigInt = { + this match { + case QEmpty() => 0 + case QCons(f, r) => f.size + r.size + } + } ensuring (res => res == this.toList.size && res >= 0) + + def toList: List[T] = (this match { + case QEmpty() => Nil() + case QCons(f, r) => f ++ r.reverse + }) ensuring (resOne => this.content == resOne.content && resOne.size >= 0) + + def content: Set[T] = this match { + case QEmpty() => Set() + case QCons(f, r) => f.content ++ r.content + } +} + +case class QCons[T](f : List[T], r: List[T]) extends Queue[T] +case class QEmpty[T]() extends Queue[T]