diff --git a/src/main/java/leon/codegen/runtime/CaseClass.java b/src/main/java/leon/codegen/runtime/CaseClass.java index ea27c7853b4fb0f9a2eae287d186a00f73353e42..bbbdb92f8a41797e455477c5974424f1d2c15718 100644 --- a/src/main/java/leon/codegen/runtime/CaseClass.java +++ b/src/main/java/leon/codegen/runtime/CaseClass.java @@ -1,5 +1,7 @@ package leon.codegen.runtime; public interface CaseClass { - public abstract Object[] productElements(); + public abstract Object[] productElements(); + + public abstract String productName(); } diff --git a/src/main/java/leon/codegen/runtime/Map.java b/src/main/java/leon/codegen/runtime/Map.java index b82ba05c9090677d713d0af74b9b8e937ae66c78..0bdb221c1939dd3ba995e05ec437d24c3f4a2fc6 100644 --- a/src/main/java/leon/codegen/runtime/Map.java +++ b/src/main/java/leon/codegen/runtime/Map.java @@ -1,10 +1,11 @@ package leon.codegen.runtime; import java.util.Arrays; +import java.util.Iterator; import java.util.TreeMap; /** If someone wants to make it an efficient implementation of immutable - * sets, go ahead. */ + * maps, go ahead. */ public final class Map { private final TreeMap<Object,Object> _underlying; @@ -16,22 +17,25 @@ public final class Map { _underlying = new TreeMap<Object,Object>(); } - // For Maps, it's simpler to build them by starting with empty and putting - // the elements in. + private Map(TreeMap<Object,Object> u) { + _underlying = u; + } + + // Uses mutation! Useful at building time. public void add(Object key, Object value) { _underlying.put(key, value); } - private Map(TreeMap<Object,Object> u) { - _underlying = u; + public Iterator<java.util.Map.Entry<Object,Object>> getElements() { + return _underlying.entrySet().iterator(); } public boolean isDefinedAt(Object element) { - return underlying().containsKey(element); + return _underlying.containsKey(element); } public Object get(Object k) throws LeonCodeGenRuntimeException { - Object result = underlying().get(k); + Object result = _underlying.get(k); if(result == null) { throw new LeonCodeGenRuntimeException("Get of undefined key."); } @@ -39,12 +43,52 @@ public final class Map { } public int size() { - return underlying().size(); + return _underlying.size(); } public Map union(Map s) { - TreeMap<Object,Object> n = new TreeMap<Object,Object>(underlying()); + TreeMap<Object,Object> n = new TreeMap<Object,Object>(_underlying); n.putAll(s.underlying()); return new Map(n); } + + @Override + public boolean equals(Object that) { + if(that == this) return true; + if(!(that instanceof Map)) return false; + + Map other = (Map)that; + + if(this.size() != other.size()) return false; + + for(java.util.Map.Entry<Object,Object> entry : _underlying.entrySet()) { + Object there = other.underlying().get(entry.getKey()); + if(there == null || !entry.getValue().equals(there)) return false; + } + + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Map("); + boolean first = true; + for(java.util.Map.Entry<Object,Object> entry : _underlying.entrySet()) { + if(!first) { + sb.append(", "); + first = false; + } + sb.append(entry.getKey().toString()); + sb.append(" -> "); + sb.append(entry.getValue().toString()); + } + sb.append(")"); + return sb.toString(); + } + + @Override + public int hashCode() { + return _underlying.hashCode(); + } } diff --git a/src/main/java/leon/codegen/runtime/Set.java b/src/main/java/leon/codegen/runtime/Set.java index f882fdf3b377b29a8106d00b657a6778eb3590e6..b38ba90706efbebe08d93e3ea9bc20590718a57a 100644 --- a/src/main/java/leon/codegen/runtime/Set.java +++ b/src/main/java/leon/codegen/runtime/Set.java @@ -21,7 +21,7 @@ public final class Set { _underlying = new TreeSet<Object>(Arrays.asList(elements)); } - // Uses mutation! + // Uses mutation! Useful at building time. public void add(Object e) { _underlying.add(e); } @@ -52,7 +52,7 @@ public final class Set { } public Set union(Set s) { - TreeSet<Object> n = new TreeSet<Object>(underlying()); + TreeSet<Object> n = new TreeSet<Object>(_underlying); n.addAll(s.underlying()); return new Set(n); } @@ -79,7 +79,32 @@ public final class Set { public boolean equals(Object that) { if(that == this) return true; if(!(that instanceof Set)) return false; + Set other = (Set)that; - return this.subsetOf(other) && other.subsetOf(this); + + if(this.size() != other.size()) return false; + + return this.subsetOf(other); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Set("); + boolean first = true; + for(Object o : _underlying) { + if(!first) { + sb.append(", "); + first = false; + } + sb.append(o.toString()); + } + sb.append(")"); + return sb.toString(); + } + + @Override + public int hashCode() { + return _underlying.hashCode(); } } diff --git a/src/main/scala/leon/LeonOption.scala b/src/main/scala/leon/LeonOption.scala index 1ad3cb99bd85505b0f85319e8f93af8d0e15527c..40cd6c23fbeb39130cad5a7bf829c8931fa3f30e 100644 --- a/src/main/scala/leon/LeonOption.scala +++ b/src/main/scala/leon/LeonOption.scala @@ -6,7 +6,10 @@ sealed abstract class LeonOption { } /** Boolean (on/off) options. Present means "on". */ -case class LeonFlagOption(name: String) extends LeonOption +case class LeonFlagOption(name: String) extends LeonOption { + override def toString() : String = "--" + name +} + /** Options of the form --option=value. */ case class LeonValueOption(name: String, value: String) extends LeonOption { def splitList : Seq[String] = value.split(':').map(_.trim).filter(!_.isEmpty) @@ -18,6 +21,8 @@ case class LeonValueOption(name: String, value: String) extends LeonOption { ctx.reporter.error("Option --%s takes an integer as value.".format(name)) None } + + override def toString() : String = "--%s=%s".format(name, value) } sealed abstract class LeonOptionDef { diff --git a/src/main/scala/leon/codegen/CodeGeneration.scala b/src/main/scala/leon/codegen/CodeGeneration.scala index 6ccb05f24ec05abb2517a6c4cc824ece3ca97559..03f4222cd0f8cba5937d93d616e171aaf2cb58ac 100644 --- a/src/main/scala/leon/codegen/CodeGeneration.scala +++ b/src/main/scala/leon/codegen/CodeGeneration.scala @@ -14,12 +14,14 @@ import cafebabe.Defaults.constructorName import cafebabe.Flags._ object CodeGeneration { - private val BoxedIntClass = "java/lang/Integer" + private val BoxedIntClass = "java/lang/Integer" private val BoxedBoolClass = "java/lang/Boolean" private val TupleClass = "leon/codegen/runtime/Tuple" private val SetClass = "leon/codegen/runtime/Set" + private val MapClass = "leon/codegen/runtime/Map" private val CaseClassClass = "leon/codegen/runtime/CaseClass" + private val ErrorClass = "leon/codegen/runtime/LeonCodeGenRuntimeException" def defToJVMName(p : Program, d : Definition) : String = "Leon$CodeGen$" + d.id.uniqueName @@ -28,6 +30,8 @@ object CodeGeneration { case BooleanType => "Z" + case UnitType => "Z" + case c : ClassType => env.classDefToClass(c.classDef).map(n => "L" + n + ";").getOrElse("Unsupported class " + c.id) @@ -37,6 +41,9 @@ object CodeGeneration { case _ : SetType => "L" + SetClass + ";" + case _ : MapType => + "L" + MapClass + ";" + case _ => throw CompilationException("Unsupported type : " + tpe) } @@ -50,10 +57,10 @@ object CodeGeneration { mkExpr(exprToCompile, ch)(env.withVars(newMapping)) funDef.returnType match { - case Int32Type | BooleanType => + case Int32Type | BooleanType | UnitType => ch << IRETURN - case _ : ClassType | _ : TupleType | _ : SetType => + case _ : ClassType | _ : TupleType | _ : SetType | _ : MapType => ch << ARETURN case other => @@ -68,7 +75,7 @@ object CodeGeneration { case Variable(id) => val slot = slotFor(id) val instr = id.getType match { - case Int32Type | BooleanType => ILoad(slot) + case Int32Type | BooleanType | UnitType => ILoad(slot) case _ => ALoad(slot) } ch << instr @@ -77,7 +84,7 @@ object CodeGeneration { mkExpr(d, ch) val slot = ch.getFreshVar val instr = i.getType match { - case Int32Type | BooleanType => IStore(slot) + case Int32Type | BooleanType | UnitType => IStore(slot) case _ => AStore(slot) } ch << instr @@ -93,7 +100,7 @@ object CodeGeneration { ch << InvokeVirtual(TupleClass, "get", "(I)Ljava/lang/Object;") mkUnbox(i.getType, ch) val instr = i.getType match { - case Int32Type | BooleanType => IStore(s) + case Int32Type | BooleanType | UnitType => IStore(s) case _ => AStore(s) } ch << instr @@ -107,11 +114,15 @@ object CodeGeneration { case BooleanLiteral(v) => ch << Ldc(if(v) 1 else 0) + case UnitLiteral => + ch << Ldc(1) + + // Case classes case CaseClass(ccd, as) => val ccName = env.classDefToClass(ccd).getOrElse { throw CompilationException("Unknown class : " + ccd.id) } - // It's a little ugly that we do it each time. Could be in env. + // TODO FIXME It's a little ugly that we do it each time. Could be in env. val consSig = "(" + ccd.fields.map(f => typeToJVM(f.tpe)).mkString("") + ")V" ch << New(ccName) << DUP for(a <- as) { @@ -134,6 +145,7 @@ object CodeGeneration { ch << CheckCast(ccName) ch << GetField(ccName, sid.name, typeToJVM(sid.getType)) + // Tuples (note that instanceOf checks are in mkBranch) case Tuple(es) => ch << New(TupleClass) << DUP ch << Ldc(es.size) @@ -153,6 +165,7 @@ object CodeGeneration { ch << InvokeVirtual(TupleClass, "get", "(I)Ljava/lang/Object;") mkUnbox(bs(i - 1), ch) + // Sets case FiniteSet(es) => ch << DefaultNew(SetClass) for(e <- es) { @@ -190,6 +203,34 @@ object CodeGeneration { mkExpr(s2, ch) ch << InvokeVirtual(SetClass, "minus", "(L%s;)L%s;".format(SetClass,SetClass)) + // Maps + case FiniteMap(ss) => + ch << DefaultNew(MapClass) + for((f,t) <- ss) { + ch << DUP + mkBoxedExpr(f, ch) + mkBoxedExpr(t, ch) + ch << InvokeVirtual(MapClass, "add", "(Ljava/lang/Object;Ljava/lang/Object;)V") + } + + case MapGet(m, k) => + val MapType(_, tt) = m.getType + mkExpr(m, ch) + mkBoxedExpr(k, ch) + ch << InvokeVirtual(MapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;") + mkUnbox(tt, ch) + + case MapIsDefinedAt(m, k) => + mkExpr(m, ch) + mkBoxedExpr(k, ch) + ch << InvokeVirtual(MapClass, "isDefinedAt", "(Ljava/lang/Object;)Z") + + case MapUnion(m1, m2) => + mkExpr(m1, ch) + mkExpr(m2, ch) + ch << InvokeVirtual(MapClass, "union", "(L%s;)L%s;".format(MapClass,MapClass)) + + // Branching case IfExpr(c, t, e) => val tl = ch.getFreshLabel("then") val el = ch.getFreshLabel("else") @@ -210,6 +251,7 @@ object CodeGeneration { } ch << InvokeStatic(cn, mn, ms) + // Arithmetic case Plus(l, r) => mkExpr(l, ch) mkExpr(r, ch) @@ -225,13 +267,31 @@ object CodeGeneration { mkExpr(r, ch) ch << IMUL + case Division(l, r) => + mkExpr(l, ch) + mkExpr(r, ch) + ch << IDIV + + case Modulo(l, r) => + mkExpr(l, ch) + mkExpr(r, ch) + ch << IREM + case UMinus(e) => mkExpr(e, ch) ch << INEG + // Misc and boolean tests + case Error(desc) => + ch << New(ErrorClass) << DUP + ch << Ldc(desc) + ch << InvokeSpecial(ErrorClass, constructorName, "(Ljava/lang/String;)V") + ch << ATHROW + // WARNING !!! See remark at the end of mkBranch ! The two functions are // mutually recursive and will loop if none supports some Boolean construct ! case b if b.getType == BooleanType => + // println("Don't know how to mkExpr for " + b) val fl = ch.getFreshLabel("boolfalse") val al = ch.getFreshLabel("boolafter") ch << Ldc(1) @@ -250,7 +310,7 @@ object CodeGeneration { mkExpr(e, ch) ch << InvokeSpecial(BoxedIntClass, constructorName, "(I)V") - case BooleanType => + case BooleanType | UnitType => ch << New(BoxedBoolClass) << DUP mkExpr(e, ch) ch << InvokeSpecial(BoxedBoolClass, constructorName, "(Z)V") @@ -267,7 +327,7 @@ object CodeGeneration { case Int32Type => ch << New(BoxedIntClass) << DUP_X1 << SWAP << InvokeSpecial(BoxedIntClass, constructorName, "(I)V") - case BooleanType => + case BooleanType | UnitType => ch << New(BoxedBoolClass) << DUP_X1 << SWAP << InvokeSpecial(BoxedBoolClass, constructorName, "(Z)V") case _ => @@ -280,7 +340,7 @@ object CodeGeneration { case Int32Type => ch << CheckCast(BoxedIntClass) << InvokeVirtual(BoxedIntClass, "intValue", "()I") - case BooleanType => + case BooleanType | UnitType => ch << CheckCast(BoxedBoolClass) << InvokeVirtual(BoxedBoolClass, "booleanValue", "()Z") case ct : ClassType => @@ -289,6 +349,15 @@ object CodeGeneration { } ch << CheckCast(cn) + case tt : TupleType => + ch << CheckCast(TupleClass) + + case st : SetType => + ch << CheckCast(SetClass) + + case mt : MapType => + ch << CheckCast(MapClass) + case _ => throw new CompilationException("Unsupported type in unboxing : " + tpe) } @@ -314,6 +383,9 @@ object CodeGeneration { ch << Label(fl) mkBranch(Or(es.tail), then, elze, ch) + case Implies(l, r) => + mkBranch(Or(Not(l), r), then, elze, ch) + case Not(c) => mkBranch(c, elze, then, ch) @@ -324,10 +396,17 @@ object CodeGeneration { mkExpr(l, ch) mkExpr(r, ch) l.getType match { - case Int32Type | BooleanType => ch << If_ICmpEq(then) << Goto(elze) - case _ => ch << If_ACmpEq(then) << Goto(elze) + case Int32Type | BooleanType | UnitType => + ch << If_ICmpEq(then) << Goto(elze) + + case _ => + ch << InvokeVirtual("java/lang/Object", "equals", "(Ljava/lang/Object;)Z") + ch << IfEq(elze) << Goto(then) } + case Iff(l,r) => + mkBranch(Equals(l, r), then, elze, ch) + case LessThan(l,r) => mkExpr(l, ch) mkExpr(r, ch) @@ -351,6 +430,7 @@ object CodeGeneration { // WARNING !!! mkBranch delegates to mkExpr, and mkExpr delegates to mkBranch ! // That means, between the two of them, they'd better know what to generate ! case other => + // println("Don't know how to mkBranch for " + other) mkExpr(other, ch) ch << IfEq(elze) << Goto(then) } @@ -424,6 +504,20 @@ object CodeGeneration { cch.freeze } + locally { + val pnm = cf.addMethod("Ljava/lang/String;", "productName") + pnm.setFlags(( + METHOD_ACC_PUBLIC | + METHOD_ACC_FINAL + ).asInstanceOf[U2]) + + val pnch = pnm.codeHandler + + pnch << Ldc(cName) << ARETURN + + pnch.freeze + } + locally { val pem = cf.addMethod("[Ljava/lang/Object;", "productElements") pem.setFlags(( diff --git a/src/main/scala/leon/codegen/CompilationUnit.scala b/src/main/scala/leon/codegen/CompilationUnit.scala index 1a56b8cfa3921576e57c7a2a5667e335937bc783..4cad603f7f6a5b44fd28e3721c89461fa869f6d5 100644 --- a/src/main/scala/leon/codegen/CompilationUnit.scala +++ b/src/main/scala/leon/codegen/CompilationUnit.scala @@ -14,29 +14,47 @@ import cafebabe.Flags._ import scala.collection.JavaConverters._ +import java.lang.reflect.Constructor + import CodeGeneration._ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFile], implicit val env: CompilationEnvironment) { - val jvmClassToDef = classes.map{ case (d, cf) => cf.className -> d }.toMap + private val jvmClassToDef = classes.map { + case (d, cf) => cf.className -> d + }.toMap + + protected[codegen] val loader = { + val l = new CafebabeClassLoader + classes.values.foreach(l.register(_)) + l + } - val loader = new CafebabeClassLoader - classes.values.foreach(loader.register(_)) + private val caseClassConstructors : Map[CaseClassDef,Constructor[_]] = { + (classes collect { + case (ccd : CaseClassDef, cf) => + val klass = loader.loadClass(cf.className) + // This is a hack: we pick the constructor with the most arguments. + val conss = klass.getConstructors().sortBy(_.getParameterTypes().length) + assert(!conss.isEmpty) + (ccd -> conss.last) + }).toMap + } - def writeClassFiles() { + private def writeClassFiles() { for ((d, cl) <- classes) { cl.writeToFile(cl.className + ".class") } } private var _nextExprId = 0 - def nextExprId = { + private def nextExprId = { _nextExprId += 1 _nextExprId } // Currently, this method is only used to prepare arguments to reflective calls. - // This means it is safe to return AnyRef (and never native values), because + // This means it is safe to return AnyRef (as opposed to primitive types), because // reflection needs this anyway. private[codegen] def valueToJVM(e: Expr): AnyRef = e match { case IntLiteral(v) => @@ -45,6 +63,10 @@ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFi case BooleanLiteral(v) => new java.lang.Boolean(v) + case CaseClass(ccd, args) => + val cons = caseClassConstructors(ccd) + cons.newInstance(args.map(valueToJVM).toArray : _*).asInstanceOf[AnyRef] + // just slightly overkill... case _ => compileExpression(e, Seq()).evalToJVM(Seq()) @@ -76,8 +98,16 @@ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFi case set : runtime.Set => FiniteSet(set.getElements().asScala.map(jvmToValue).toSeq) + case map : runtime.Map => + val pairs = map.getElements().asScala.map { entry => + val k = jvmToValue(entry.getKey()) + val v = jvmToValue(entry.getValue()) + (k, v) + } + FiniteMap(pairs.toSeq) + case _ => - throw CompilationException("MEH Unsupported return value : " + e.getClass) + throw CompilationException("Unsupported return value : " + e.getClass) } def compileExpression(e: Expr, args: Seq[Identifier]): CompiledExpression = { @@ -129,8 +159,10 @@ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFi loader.register(cf) - new CompiledExpression(this, cf, args) + new CompiledExpression(this, cf, e, args) } + + // writeClassFiles } object CompilationUnit { diff --git a/src/main/scala/leon/codegen/CompiledExpression.scala b/src/main/scala/leon/codegen/CompiledExpression.scala index 0c10fe02a43b555463beeebc1169ce2c0111d0c1..0ae8f949f253ba305db59543004f29c2d05116a3 100644 --- a/src/main/scala/leon/codegen/CompiledExpression.scala +++ b/src/main/scala/leon/codegen/CompiledExpression.scala @@ -12,23 +12,28 @@ import cafebabe.ByteCodes._ import cafebabe.ClassFileTypes._ import cafebabe.Flags._ -class CompiledExpression(unit: CompilationUnit, cf: ClassFile, argsDecl: Seq[Identifier]) { +import java.lang.reflect.InvocationTargetException - def evalToJVM(args: Seq[Expr]): AnyRef = { - val cl = unit.loader.loadClass(cf.className) - val meth = cl.getMethods()(0) +class CompiledExpression(unit: CompilationUnit, cf: ClassFile, expression : Expr, argsDecl: Seq[Identifier]) { + private lazy val cl = unit.loader.loadClass(cf.className) + private lazy val meth = cl.getMethods()(0) + protected[codegen] def evalToJVM(args: Seq[Expr]): AnyRef = { assert(args.size == argsDecl.size) if (args.isEmpty) { meth.invoke(null) } else { - meth.invoke(null, args.map(unit.valueToJVM).toArray) + meth.invoke(null, args.map(unit.valueToJVM).toArray : _*) } } - def eval(args: Seq[Expr]): Expr = { - unit.jvmToValue(evalToJVM(args)) + // This may throw an exception. We unwrap it if needed. + def eval(args: Seq[Expr]) : Expr = { + try { + unit.jvmToValue(evalToJVM(args)) + } catch { + case ite : InvocationTargetException => throw ite.getCause() + } } - } diff --git a/src/main/scala/leon/evaluators/CodeGenEvaluator.scala b/src/main/scala/leon/evaluators/CodeGenEvaluator.scala new file mode 100644 index 0000000000000000000000000000000000000000..9b9702f31621c41d8f233c68690cc99d1ebe1356 --- /dev/null +++ b/src/main/scala/leon/evaluators/CodeGenEvaluator.scala @@ -0,0 +1,51 @@ +package leon +package evaluators + +import purescala.Common._ +import purescala.Definitions._ +import purescala.TreeOps._ +import purescala.Trees._ +import purescala.TypeTrees._ + +import codegen.CompilationUnit + +class CodeGenEvaluator(ctx : LeonContext, val unit : CompilationUnit) extends Evaluator(ctx, unit.program) { + val name = "codegen-eval" + val description = "Evaluator for PureScala expressions based on compilation to JVM" + + /** Another constructor to make it look more like other `Evaluator`s. */ + def this(ctx : LeonContext, prog : Program) { + this(ctx, CompilationUnit.compileProgram(prog).get) // this .get is dubious... + } + + + def eval(expression : Expr, mapping : Map[Identifier,Expr]) : EvaluationResult = { + // ctx.reporter.warning("Using `eval` in CodeGenEvaluator is discouraged. Use `compile` whenever applicable.") + + val toPairs = mapping.toSeq + compile(expression, toPairs.map(_._1)).map(e => e(toPairs.map(_._2))).getOrElse(EvaluationError("Couldn't compile expression.")) + } + + override def compile(expression : Expr, argorder : Seq[Identifier]) : Option[Seq[Expr]=>EvaluationResult] = { + import leon.codegen.runtime.LeonCodeGenRuntimeException + + val ce = unit.compileExpression(expression, argorder) + + Some((args : Seq[Expr]) => { + try { + EvaluationSuccessful(ce.eval(args)) + } catch { + case e : ArithmeticException => + EvaluationFailure(e.getMessage) + + case e : LeonCodeGenRuntimeException => + EvaluationFailure(e.getMessage) + + // Required, because the class may be loaded from a different classloader, + // and the check above would fail. + case t : Throwable if t.getClass.toString.endsWith("LeonCodeGenRuntimeException") => + EvaluationFailure(t.getMessage) + } + }) + } +} diff --git a/src/main/scala/leon/Evaluator.scala b/src/main/scala/leon/evaluators/DefaultEvaluator.scala similarity index 63% rename from src/main/scala/leon/Evaluator.scala rename to src/main/scala/leon/evaluators/DefaultEvaluator.scala index 24d0fd06e3b63a17ba67c3647bfecb4f443c1954..a6998f10b2ee4807844be3c40b5487b925e1b045 100644 --- a/src/main/scala/leon/Evaluator.scala +++ b/src/main/scala/leon/evaluators/DefaultEvaluator.scala @@ -1,46 +1,29 @@ package leon +package evaluators import purescala.Common._ -import purescala.Trees._ -import xlang.Trees._ +import purescala.Definitions._ import purescala.TreeOps._ +import purescala.Trees._ import purescala.TypeTrees._ -object Evaluator { -// Expr should be some ground term. We have call-by-value semantics. - type EvaluationContext = Map[Identifier,Expr] - - sealed abstract class EvaluationResult { - val finalResult: Option[Expr] - } - case class OK(result: Expr) extends EvaluationResult { - val finalResult = Some(result) - } - case class RuntimeError(msg: String) extends EvaluationResult { - val finalResult = None - } - case class TypeError(expression: Expr, expected: TypeTree) extends EvaluationResult { - lazy val msg = "When typing:\n" + expression + "\nexpected: " + expected + ", found: " + expression.getType - val finalResult = None - } - case class InfiniteComputation() extends EvaluationResult { - val finalResult = None - } - case class ImpossibleComputation() extends EvaluationResult { - val finalResult = None - } - +import xlang.Trees._ - def eval(context: EvaluationContext, expression: Expr, evaluator: Option[(EvaluationContext)=>Boolean], maxSteps: Int=500000) : EvaluationResult = { - case class RuntimeErrorEx(msg: String) extends Exception - case class InfiniteComputationEx() extends Exception - case class TypeErrorEx(typeError: TypeError) extends Exception - case class ImpossibleComputationEx() extends Exception +class DefaultEvaluator(ctx : LeonContext, prog : Program) extends Evaluator(ctx, prog) { + val name = "evaluator" + val description = "Recursive interpreter for PureScala expressions" + private def typeErrorMsg(tree : Expr, expected : TypeTree) : String = "Type error : expected %s, found %s.".format(expected, tree) + private case class EvalError(msg : String) extends Exception + private case class RuntimeError(msg : String) extends Exception + + private val maxSteps = 50000 + + def eval(expression: Expr, mapping : Map[Identifier,Expr]) : EvaluationResult = { var left: Int = maxSteps - def rec(ctx: EvaluationContext, expr: Expr) : Expr = if(left <= 0) { - throw InfiniteComputationEx() + def rec(ctx: Map[Identifier,Expr], expr: Expr) : Expr = if(left <= 0) { + throw RuntimeError("Diverging computation.") } else { // println("Step on : " + expr) // println(ctx) @@ -50,12 +33,12 @@ object Evaluator { if(ctx.isDefinedAt(id)) { val res = ctx(id) if(!isGround(res)) { - throw RuntimeErrorEx("Substitution for identifier " + id.name + " is not ground.") + throw EvalError("Substitution for identifier " + id.name + " is not ground.") } else { res } } else { - throw RuntimeErrorEx("No value for identifier " + id.name + " in context.") + throw EvalError("No value for identifier " + id.name + " in mapping.") } } case Tuple(ts) => { @@ -70,35 +53,35 @@ object Evaluator { val first = rec(ctx, e) rec(ctx + ((i -> first)), b) } - case Error(desc) => throw RuntimeErrorEx("Error reached in evaluation: " + desc) + case Error(desc) => throw RuntimeError("Error reached in evaluation: " + desc) case IfExpr(cond, then, elze) => { val first = rec(ctx, cond) first match { case BooleanLiteral(true) => rec(ctx, then) case BooleanLiteral(false) => rec(ctx, elze) - case _ => throw TypeErrorEx(TypeError(first, BooleanType)) + case _ => throw EvalError(typeErrorMsg(first, BooleanType)) } } case Waypoint(_, arg) => rec(ctx, arg) case FunctionInvocation(fd, args) => { val evArgs = args.map(a => rec(ctx, a)) - // build a context for the function... + // build a mapping for the function... val frame = Map[Identifier,Expr]((fd.args.map(_.id) zip evArgs) : _*) if(fd.hasPrecondition) { rec(frame, matchToIfThenElse(fd.precondition.get)) match { case BooleanLiteral(true) => ; case BooleanLiteral(false) => { - throw RuntimeErrorEx("Precondition violation for " + fd.id.name + " reached in evaluation.: " + fd.precondition.get) + throw RuntimeError("Precondition violation for " + fd.id.name + " reached in evaluation.: " + fd.precondition.get) } - case other => throw TypeErrorEx(TypeError(other, BooleanType)) + case other => throw RuntimeError(typeErrorMsg(other, BooleanType)) } } - if(!fd.hasBody && !context.isDefinedAt(fd.id)) { - throw RuntimeErrorEx("Evaluation of function with unknown implementation.") + if(!fd.hasBody && !mapping.isDefinedAt(fd.id)) { + throw EvalError("Evaluation of function with unknown implementation.") } - val body = fd.body.getOrElse(context(fd.id)) + val body = fd.body.getOrElse(mapping(fd.id)) val callResult = rec(frame, matchToIfThenElse(body)) if(fd.hasPostcondition) { @@ -106,9 +89,8 @@ object Evaluator { val postBody = replace(Map(ResultVariable() -> Variable(freshResID)), matchToIfThenElse(fd.postcondition.get)) rec(frame + ((freshResID -> callResult)), postBody) match { case BooleanLiteral(true) => ; - case BooleanLiteral(false) if !fd.hasImplementation => throw ImpossibleComputationEx() - case BooleanLiteral(false) => throw RuntimeErrorEx("Postcondition violation for " + fd.id.name + " reached in evaluation.") - case other => throw TypeErrorEx(TypeError(other, BooleanType)) + case BooleanLiteral(false) => throw RuntimeError("Postcondition violation for " + fd.id.name + " reached in evaluation.") + case other => throw EvalError(typeErrorMsg(other, BooleanType)) } } @@ -119,7 +101,7 @@ object Evaluator { rec(ctx, args.head) match { case BooleanLiteral(false) => BooleanLiteral(false) case BooleanLiteral(true) => rec(ctx, And(args.tail)) - case other => throw TypeErrorEx(TypeError(other, BooleanType)) + case other => throw EvalError(typeErrorMsg(other, BooleanType)) } } case Or(args) if args.isEmpty => BooleanLiteral(false) @@ -127,20 +109,20 @@ object Evaluator { rec(ctx, args.head) match { case BooleanLiteral(true) => BooleanLiteral(true) case BooleanLiteral(false) => rec(ctx, Or(args.tail)) - case other => throw TypeErrorEx(TypeError(other, BooleanType)) + case other => throw EvalError(typeErrorMsg(other, BooleanType)) } } case Not(arg) => rec(ctx, arg) match { case BooleanLiteral(v) => BooleanLiteral(!v) - case other => throw TypeErrorEx(TypeError(other, BooleanType)) + case other => throw EvalError(typeErrorMsg(other, BooleanType)) } case Implies(l,r) => (rec(ctx,l), rec(ctx,r)) match { case (BooleanLiteral(b1),BooleanLiteral(b2)) => BooleanLiteral(!b1 || b2) - case (le,re) => throw TypeErrorEx(TypeError(le, BooleanType)) + case (le,re) => throw EvalError(typeErrorMsg(le, BooleanType)) } case Iff(le,re) => (rec(ctx,le),rec(ctx,re)) match { case (BooleanLiteral(b1),BooleanLiteral(b2)) => BooleanLiteral(b1 == b2) - case _ => throw TypeErrorEx(TypeError(le, BooleanType)) + case _ => throw EvalError(typeErrorMsg(le, BooleanType)) } case Equals(le,re) => { val lv = rec(ctx,le) @@ -164,53 +146,55 @@ object Evaluator { val le = rec(ctx, expr) le match { case CaseClass(cd2, args) if cd == cd2 => args(cd.selectorID2Index(sel)) - case _ => throw TypeErrorEx(TypeError(le, CaseClassType(cd))) + case _ => throw EvalError(typeErrorMsg(le, CaseClassType(cd))) } } case Plus(l,r) => (rec(ctx,l), rec(ctx,r)) match { case (IntLiteral(i1), IntLiteral(i2)) => IntLiteral(i1 + i2) - case (le,re) => throw TypeErrorEx(TypeError(le, Int32Type)) + case (le,re) => throw EvalError(typeErrorMsg(le, Int32Type)) } case Minus(l,r) => (rec(ctx,l), rec(ctx,r)) match { case (IntLiteral(i1), IntLiteral(i2)) => IntLiteral(i1 - i2) - case (le,re) => throw TypeErrorEx(TypeError(le, Int32Type)) + case (le,re) => throw EvalError(typeErrorMsg(le, Int32Type)) } case UMinus(e) => rec(ctx,e) match { case IntLiteral(i) => IntLiteral(-i) - case re => throw TypeErrorEx(TypeError(re, Int32Type)) + case re => throw EvalError(typeErrorMsg(re, Int32Type)) } case Times(l,r) => (rec(ctx,l), rec(ctx,r)) match { case (IntLiteral(i1), IntLiteral(i2)) => IntLiteral(i1 * i2) - case (le,re) => throw TypeErrorEx(TypeError(le, Int32Type)) + case (le,re) => throw EvalError(typeErrorMsg(le, Int32Type)) } case Division(l,r) => (rec(ctx,l), rec(ctx,r)) match { - case (IntLiteral(i1), IntLiteral(i2)) => IntLiteral(i1 / i2) - case (le,re) => throw TypeErrorEx(TypeError(le, Int32Type)) + case (IntLiteral(i1), IntLiteral(i2)) => + if(i2 != 0) IntLiteral(i1 / i2) else throw RuntimeError("Division by 0.") + case (le,re) => throw EvalError(typeErrorMsg(le, Int32Type)) } case Modulo(l,r) => (rec(ctx,l), rec(ctx,r)) match { - case (IntLiteral(i1), IntLiteral(i2)) => IntLiteral(i1 % i2) - case (le,re) => throw TypeErrorEx(TypeError(le, Int32Type)) + case (IntLiteral(i1), IntLiteral(i2)) => + if(i2 != 0) IntLiteral(i1 % i2) else throw RuntimeError("Modulo by 0.") + case (le,re) => throw EvalError(typeErrorMsg(le, Int32Type)) } case LessThan(l,r) => (rec(ctx,l), rec(ctx,r)) match { case (IntLiteral(i1), IntLiteral(i2)) => BooleanLiteral(i1 < i2) - case (le,re) => throw TypeErrorEx(TypeError(le, Int32Type)) + case (le,re) => throw EvalError(typeErrorMsg(le, Int32Type)) } case GreaterThan(l,r) => (rec(ctx,l), rec(ctx,r)) match { case (IntLiteral(i1), IntLiteral(i2)) => BooleanLiteral(i1 > i2) - case (le,re) => throw TypeErrorEx(TypeError(le, Int32Type)) + case (le,re) => throw EvalError(typeErrorMsg(le, Int32Type)) } case LessEquals(l,r) => (rec(ctx,l), rec(ctx,r)) match { case (IntLiteral(i1), IntLiteral(i2)) => BooleanLiteral(i1 <= i2) - case (le,re) => throw TypeErrorEx(TypeError(le, Int32Type)) + case (le,re) => throw EvalError(typeErrorMsg(le, Int32Type)) } case GreaterEquals(l,r) => (rec(ctx,l), rec(ctx,r)) match { case (IntLiteral(i1), IntLiteral(i2)) => BooleanLiteral(i1 >= i2) - case (le,re) => throw TypeErrorEx(TypeError(le, Int32Type)) + case (le,re) => throw EvalError(typeErrorMsg(le, Int32Type)) } case SetUnion(s1,s2) => (rec(ctx,s1), rec(ctx,s2)) match { case (f@FiniteSet(els1),FiniteSet(els2)) => FiniteSet((els1 ++ els2).distinct).setType(f.getType) - case (le,re) => throw TypeErrorEx(TypeError(le, s1.getType)) + case (le,re) => throw EvalError(typeErrorMsg(le, s1.getType)) } case SetIntersection(s1,s2) => (rec(ctx,s1), rec(ctx,s2)) match { case (f@FiniteSet(els1),FiniteSet(els2)) => { @@ -218,7 +202,7 @@ object Evaluator { val baseType = f.getType.asInstanceOf[SetType].base FiniteSet(newElems).setType(f.getType) } - case (le,re) => throw TypeErrorEx(TypeError(le, s1.getType)) + case (le,re) => throw EvalError(typeErrorMsg(le, s1.getType)) } case SetDifference(s1,s2) => (rec(ctx,s1), rec(ctx,s2)) match { case (f@FiniteSet(els1),FiniteSet(els2)) => { @@ -226,17 +210,17 @@ object Evaluator { val baseType = f.getType.asInstanceOf[SetType].base FiniteSet(newElems).setType(f.getType) } - case (le,re) => throw TypeErrorEx(TypeError(le, s1.getType)) + case (le,re) => throw EvalError(typeErrorMsg(le, s1.getType)) } case ElementOfSet(el,s) => (rec(ctx,el), rec(ctx,s)) match { case (e, f @ FiniteSet(els)) => BooleanLiteral(els.contains(e)) - case (l,r) => throw TypeErrorEx(TypeError(r, SetType(l.getType))) + case (l,r) => throw EvalError(typeErrorMsg(r, SetType(l.getType))) } case SetCardinality(s) => { val sr = rec(ctx, s) sr match { case FiniteSet(els) => IntLiteral(els.size) - case _ => throw TypeErrorEx(TypeError(sr, SetType(AnyType))) + case _ => throw EvalError(typeErrorMsg(sr, SetType(AnyType))) } } @@ -279,9 +263,9 @@ object Evaluator { case g @ MapGet(m,k) => (rec(ctx,m), rec(ctx,k)) match { case (FiniteMap(ss), e) => ss.find(_._1 == e) match { case Some((_, v0)) => v0 - case None => throw RuntimeErrorEx("key not found: " + e) + case None => throw RuntimeError("Key not found: " + e) } - case (l,r) => throw TypeErrorEx(TypeError(l, MapType(r.getType, g.getType))) + case (l,r) => throw EvalError(typeErrorMsg(l, MapType(r.getType, g.getType))) } case u @ MapUnion(m1,m2) => (rec(ctx,m1), rec(ctx,m2)) match { case (f1@FiniteMap(ss1), FiniteMap(ss2)) => { @@ -289,11 +273,11 @@ object Evaluator { val newSs = filtered1 ++ ss2 FiniteMap(newSs).setType(f1.getType) } - case (l, r) => throw TypeErrorEx(TypeError(l, m1.getType)) + case (l, r) => throw EvalError(typeErrorMsg(l, m1.getType)) } case i @ MapIsDefinedAt(m,k) => (rec(ctx,m), rec(ctx,k)) match { case (FiniteMap(ss), e) => BooleanLiteral(ss.exists(_._1 == e)) - case (l, r) => throw TypeErrorEx(TypeError(l, m.getType)) + case (l, r) => throw EvalError(typeErrorMsg(l, m.getType)) } case Distinct(args) => { val newArgs = args.map(rec(ctx, _)) @@ -301,33 +285,22 @@ object Evaluator { } case other => { - Settings.reporter.error("Error: don't know how to handle " + other + " in Evaluator.") - throw RuntimeErrorEx("unhandled case in Evaluator") + context.reporter.error("Error: don't know how to handle " + other + " in Evaluator.") + throw EvalError("Unhandled case in Evaluator : " + other) } } } - evaluator match { - case Some(evalFun) => - try { - OK(BooleanLiteral(evalFun(context))) - } catch { - case e: Exception => RuntimeError(e.getMessage) - } - case None => - try { - OK(rec(context, expression)) - } catch { - case RuntimeErrorEx(msg) => RuntimeError(msg) - case InfiniteComputationEx() => InfiniteComputation() - case TypeErrorEx(te) => te - case ImpossibleComputationEx() => ImpossibleComputation() - } + try { + EvaluationSuccessful(rec(mapping, expression)) + } catch { + case EvalError(msg) => EvaluationError(msg) + case RuntimeError(msg) => EvaluationFailure(msg) } } // quick and dirty.. don't overuse. - def isGround(expr: Expr) : Boolean = { + private def isGround(expr: Expr) : Boolean = { variablesOf(expr) == Set.empty } } diff --git a/src/main/scala/leon/evaluators/Evaluator.scala b/src/main/scala/leon/evaluators/Evaluator.scala new file mode 100644 index 0000000000000000000000000000000000000000..7beda070f28ce84795afb44691e2e124f08906f8 --- /dev/null +++ b/src/main/scala/leon/evaluators/Evaluator.scala @@ -0,0 +1,40 @@ +package leon +package evaluators + +import purescala.Common._ +import purescala.Definitions._ +import purescala.Trees._ + +abstract class Evaluator(val context : LeonContext, val program : Program) extends LeonComponent { + + /** Evaluates an expression, using `mapping` as a valuation function for the free variables. */ + def eval(expr : Expr, mapping : Map[Identifier,Expr]) : EvaluationResult + + /** Evaluates a ground expression. */ + final def eval(expr : Expr) : EvaluationResult = eval(expr, Map.empty) + + /** Compiles an expression into a function, where the arguments are the free variables in the expression. + * `argorder` specifies in which order the arguments should be passed. + * The default implementation uses the evaluation function each time, but evaluators are free + * to (and encouraged to) apply any specialization. */ + def compile(expr : Expr, argorder : Seq[Identifier]) : Option[Seq[Expr]=>EvaluationResult] = Some( + (args : Seq[Expr]) => if(args.size != argorder.size) { + EvaluationError("Wrong number of arguments for evaluation.") + } else { + val mapping = argorder.zip(args).toMap + eval(expr, mapping) + } + ) +} + +/** Possible results of expression evaluation. */ +sealed abstract class EvaluationResult(val result : Option[Expr]) + +/** Represents an evaluation that successfully derived the result `value`. */ +case class EvaluationSuccessful(value : Expr) extends EvaluationResult(Some(value)) + +/** Represents an evaluation that led to an error (in the program). */ +case class EvaluationFailure(message : String) extends EvaluationResult(None) + +/** Represents an evaluation that failed (in the evaluator). */ +case class EvaluationError(message : String) extends EvaluationResult(None) diff --git a/src/main/scala/leon/purescala/Definitions.scala b/src/main/scala/leon/purescala/Definitions.scala index e212c1baae702f0eb6fe4b63ecbe119ecad3f0d2..6eee2247c7792a4653fe5fdbb541bb5fc4f023b2 100644 --- a/src/main/scala/leon/purescala/Definitions.scala +++ b/src/main/scala/leon/purescala/Definitions.scala @@ -68,6 +68,17 @@ object Definitions { } } + object Program { + lazy val empty : Program = Program( + FreshIdentifier("empty"), + ObjectDef( + FreshIdentifier("empty"), + Seq.empty, + Seq.empty + ) + ) + } + /** Objects work as containers for class definitions, functions (def's) and * val's. */ case class ObjectDef(id: Identifier, defs : Seq[Definition], invariants: Seq[Expr]) extends Definition { diff --git a/src/main/scala/leon/solvers/ParallelSolver.scala b/src/main/scala/leon/solvers/ParallelSolver.scala index b6e66ff07f6dcad7618d18847a2fee61cbf31f21..e2a334df1360b121369eec6b7db7f59b0e3fd925 100644 --- a/src/main/scala/leon/solvers/ParallelSolver.scala +++ b/src/main/scala/leon/solvers/ParallelSolver.scala @@ -6,8 +6,6 @@ import purescala.Definitions._ import purescala.Trees._ import purescala.TypeTrees._ -import Evaluator._ - import scala.actors.Actor import scala.actors.DaemonActor import scala.actors.Actor._ diff --git a/src/main/scala/leon/solvers/RandomSolver.scala b/src/main/scala/leon/solvers/RandomSolver.scala index 3c72656350b097c795c97d3d1a95af6d608a354a..c793d37f853829a684b86197955a5bc53f68e0c1 100644 --- a/src/main/scala/leon/solvers/RandomSolver.scala +++ b/src/main/scala/leon/solvers/RandomSolver.scala @@ -7,7 +7,7 @@ import purescala.Trees._ import purescala.TreeOps._ import purescala.TypeTrees._ -import Evaluator._ +import evaluators._ import scala.util.Random @@ -16,6 +16,11 @@ class RandomSolver(context: LeonContext, val nbTrial: Option[Int] = None) extend require(nbTrial.forall(i => i >= 0)) private val reporter = context.reporter + private var evaluator : Evaluator = null + + override def setProgram(program : Program) : Unit = { + evaluator = new DefaultEvaluator(context, program) + } val name = "QC" val description = "Solver applying random testing (QuickCheck-like)" @@ -110,29 +115,26 @@ class RandomSolver(context: LeonContext, val nbTrial: Option[Int] = None) extend val var2val: Map[Identifier, Expr] = Map(vars.map(v => (v, randomValue(v.getType, bound))).toList: _*) //reporter.info("Trying with: " + var2val) - val evalResult = eval(var2val, expression, None) + val evalResult = evaluator.eval(expression, var2val) evalResult match { - case OK(BooleanLiteral(true)) => { + case EvaluationSuccessful(BooleanLiteral(true)) => { //continue trying } - case OK(BooleanLiteral(false)) => { + + case EvaluationSuccessful(BooleanLiteral(false)) => { reporter.info("Found counter example to formula: " + var2val) result = Some(false) stop = true } - /* in any of the following case, simply continue with another assignement */ - case InfiniteComputation() => { - //reporter.info("Model seems to lead to divergent computation.") - } - case RuntimeError(msg) => { - //reporter.info("Model leads to runtime error: " + msg) - } - case t @ TypeError(_,_) => { - //reporter.info("Type error in model evaluation.\n" + t.msg) - } - case _ => { - //reporter.info(" -> candidate model discarded.") + + case EvaluationFailure(_) => { + reporter.info("Input leads to runtime error: " + var2val) + result = Some(false) + stop = true } + + // otherwise, simply continue with another assignement + case EvaluationError(_) => ; } iteration += 1 diff --git a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala index 676f77e6189bbb968194c03c79d68e009af0333a..49a7c539966b49bb8b499ed2218eb9143f96012f 100644 --- a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala +++ b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala @@ -12,6 +12,8 @@ import purescala.Extractors._ import purescala.TreeOps._ import purescala.TypeTrees._ +import evaluators._ + import scala.collection.mutable.{Map => MutableMap} import scala.collection.mutable.{Set => MutableSet} @@ -27,24 +29,42 @@ class FairZ3Solver(context : LeonContext) val description = "Fair Z3 Solver" override val definedOptions : Set[LeonOptionDef] = Set( - LeonFlagOptionDef("checkmodels", "--checkmodels", "Double-check counter-examples"), - LeonFlagOptionDef("feelinglucky", "--feelinglucky", "Use evaluator to find counter-examples early") + LeonFlagOptionDef("checkmodels", "--checkmodels", "Double-check counter-examples with evaluator"), + LeonFlagOptionDef("feelinglucky", "--feelinglucky", "Use evaluator to find counter-examples early"), + LeonFlagOptionDef("codegen", "--codegen", "Use compiled evaluator instead of interpreter") ) - val (feelingLucky, checkModels) = locally { - var lucky = false - var check = false + // What wouldn't we do to avoid defining vars? + val (feelingLucky, checkModels, useCodeGen) = locally { + var lucky = false + var check = false + var codegen = false for(opt <- context.options) opt match { - case LeonFlagOption("checkmodels") => check = true - case LeonFlagOption("feelinglucky") => lucky = true + case LeonFlagOption("checkmodels") => check = true + case LeonFlagOption("feelinglucky") => lucky = true + case LeonFlagOption("codegen") => codegen = true case _ => } - (lucky,check) + (lucky,check,codegen) } - // this is fixed + private var evaluator : Evaluator = null + + override def setProgram(prog : Program) { + super.setProgram(prog) + + evaluator = if(useCodeGen) { + // TODO If somehow we could not recompile each time we create a solver, + // that would be good? + new CodeGenEvaluator(context, prog) + } else { + new DefaultEvaluator(context, prog) + } + } + + // This is fixed. protected[leon] val z3cfg = new Z3Config( "MODEL" -> true, "MBQI" -> false, @@ -67,7 +87,7 @@ class FairZ3Solver(context : LeonContext) private var reverseFunctionMap: Map[Z3FuncDecl, FunDef] = Map.empty private var axiomatizedFunctions : Set[FunDef] = Set.empty - def prepareFunctions: Unit = { + protected[leon] def prepareFunctions: Unit = { functionMap = Map.empty reverseFunctionMap = Map.empty for (funDef <- program.definedFunctions) { @@ -105,9 +125,7 @@ class FairZ3Solver(context : LeonContext) (solver.checkAssumptions(assumptions), solver.getModel, solver.getUnsatCore) } - private def validateAndDeleteModel(model: Z3Model, formula: Expr, variables: Set[Identifier], evaluator: Option[(Map[Identifier,Expr])=>Boolean] = None) : (Boolean, Map[Identifier,Expr]) = { - import Evaluator._ - + private def validateModel(model: Z3Model, formula: Expr, variables: Set[Identifier]) : (Boolean, Map[Identifier,Expr]) = { if(!forceStop) { val functionsModel: Map[Z3FuncDecl, (Seq[(Seq[Z3AST], Z3AST)], Z3AST)] = model.getModelFuncInterpretations.map(i => (i._1, (i._2, i._3))).toMap @@ -137,31 +155,26 @@ class FairZ3Solver(context : LeonContext) val asMap = modelToMap(model, variables) ++ functionsAsMap ++ constantFunctionsAsMap lazy val modelAsString = asMap.toList.map(p => p._1 + " -> " + p._2).mkString("\n") - val evalResult = eval(asMap, formula, evaluator) + val evalResult = evaluator.eval(formula, asMap) evalResult match { - case OK(BooleanLiteral(true)) => { + case EvaluationSuccessful(BooleanLiteral(true)) => reporter.info("- Model validated:") reporter.info(modelAsString) (true, asMap) - } - case RuntimeError(msg) => { - reporter.info("- Invalid model") - //reporter.error(modelAsString) - (false, asMap) - } - case OK(BooleanLiteral(false)) => { + + case EvaluationSuccessful(BooleanLiteral(false)) => reporter.info("- Invalid model.") (false, asMap) - } - case ImpossibleComputation() => { - reporter.info("- Invalid Model: the model could not be verified because of insufficient information.") + + case EvaluationFailure(msg) => + reporter.info("- Model leads to runtime error.") (false, asMap) - } - case other => { - reporter.error("Something went wrong. While evaluating the model, we got this: " + other) + + case EvaluationError(msg) => + reporter.error("Something went wrong. While evaluating the model, we got this : " + msg) (false, asMap) - } + } } else { (false, Map.empty) @@ -233,8 +246,10 @@ class FairZ3Solver(context : LeonContext) } def getNewSolver = new solvers.IncrementalSolver { + private val evaluator = enclosing.evaluator private val feelingLucky = enclosing.feelingLucky private val checkModels = enclosing.checkModels + private val useCodeGen = enclosing.useCodeGen initZ3 @@ -246,15 +261,16 @@ class FairZ3Solver(context : LeonContext) } } + private var varsInVC = Set[Identifier]() + private var frameGuards = List[Z3AST](z3.mkFreshConst("frame", z3.mkBoolSort)) private var frameExpressions = List[List[Expr]](Nil) - private var varsInVC = Set[Identifier]() def entireFormula = And(frameExpressions.flatten) def push() { - frameGuards = z3.mkFreshConst("frame", z3.mkBoolSort) :: frameGuards - frameExpressions = Nil :: frameExpressions + frameGuards = z3.mkFreshConst("frame", z3.mkBoolSort) :: frameGuards + frameExpressions = Nil :: frameExpressions } def halt() { @@ -262,12 +278,15 @@ class FairZ3Solver(context : LeonContext) } def pop(lvl: Int = 1) { + // TODO FIXME : this is wrong if lvl != 1. + // We could fix it, or change the interface to remove the optional arg. + // We make sure we discard the expressions guarded by this frame solver.assertCnstr(z3.mkNot(frameGuards.head)) // Pop the frames - frameGuards = frameGuards.tail - frameExpressions = frameExpressions.tail + frameGuards = frameGuards.tail + frameExpressions = frameExpressions.tail } def check: Option[Boolean] = { @@ -364,7 +383,7 @@ class FairZ3Solver(context : LeonContext) val z3model = solver.getModel if (this.checkModels) { - val (isValid, model) = validateAndDeleteModel(z3model, entireFormula, varsInVC) + val (isValid, model) = validateModel(z3model, entireFormula, varsInVC) if (isValid) { foundAnswer(Some(true), model) @@ -417,7 +436,7 @@ class FairZ3Solver(context : LeonContext) if (this.feelingLucky && !forceStop) { // we might have been lucky :D luckyTime.start - val (wereWeLucky, cleanModel) = validateAndDeleteModel(solver.getModel, entireFormula, varsInVC) + val (wereWeLucky, cleanModel) = validateModel(solver.getModel, entireFormula, varsInVC) luckyTime.stop if(wereWeLucky) { diff --git a/src/main/scala/leon/synthesis/LinearEquations.scala b/src/main/scala/leon/synthesis/LinearEquations.scala index 9debb3a8d340dcc8b9ba594cd242b8c434d122fc..1465ea919f33ec86e31693c37f54de84b99b8cb8 100644 --- a/src/main/scala/leon/synthesis/LinearEquations.scala +++ b/src/main/scala/leon/synthesis/LinearEquations.scala @@ -1,13 +1,18 @@ -package leon.synthesis +package leon +package synthesis -import leon.purescala.Trees._ -import leon.purescala.TreeNormalizations.linearArithmeticForm -import leon.purescala.TypeTrees._ -import leon.purescala.Common._ -import leon.Evaluator -import leon.synthesis.Algebra._ +import purescala.Definitions._ +import purescala.Trees._ +import purescala.TreeNormalizations.linearArithmeticForm +import purescala.TypeTrees._ +import purescala.Common._ + +import synthesis.Algebra._ object LinearEquations { + // This is a hack, but the use of an evaluator in this file is itself beyond that. + import evaluators._ + private lazy val evaluator = new DefaultEvaluator(LeonContext(), Program.empty) //eliminate one variable from normalizedEquation t + a1*x1 + ... + an*xn = 0 //return a mapping for each of the n variables in (pre, map, freshVars) @@ -70,7 +75,8 @@ object LinearEquations { val (_, sols) = particularSolution(as, IntLiteral(coef(j)*K(j)(j)) :: coef.drop(j+1).map(IntLiteral(_)).toList) var i = 0 while(i < sols.size) { - K(i+j+1)(j) = Evaluator.eval(Map(), sols(i), None).asInstanceOf[Evaluator.OK].result.asInstanceOf[IntLiteral].value + // seriously ??? + K(i+j+1)(j) = evaluator.eval(sols(i)).asInstanceOf[EvaluationSuccessful].value.asInstanceOf[IntLiteral].value i += 1 } } diff --git a/src/test/resources/regression/codegen/Prog001.scala b/src/test/resources/regression/codegen/Prog001.scala deleted file mode 100644 index 91f9a253f37cdf7a3119d9b68caa8c12457d3b7a..0000000000000000000000000000000000000000 --- a/src/test/resources/regression/codegen/Prog001.scala +++ /dev/null @@ -1,18 +0,0 @@ -object Prog001 { - def fortyTwo() = 42 - - def plus(x : Int, y : Int) = x + y - - def double(x : Int) : Int = { - val a = x - a + a - } - - def implies(a : Boolean, b : Boolean) : Boolean = !a || b - - def abs(x : Int) : Int = { - if(x < 0) -x else x - } - - def factorial(i : Int) : Int = if(i <= 1) 1 else (i * factorial(i - 1)) -} diff --git a/src/test/resources/regression/codegen/Prog002.scala b/src/test/resources/regression/codegen/Prog002.scala deleted file mode 100644 index 1da0f03c0fa99187cbd693867f04eb72ef8ac9f8..0000000000000000000000000000000000000000 --- a/src/test/resources/regression/codegen/Prog002.scala +++ /dev/null @@ -1,14 +0,0 @@ -object Prog002 { - sealed abstract class List - case class Nil() extends List - case class Cons(head : Int, tail : List) extends List - - def isNil(l : List) : Boolean = { - l == Nil() - } - - def size(l : List) : Int = l match { - case Nil() => 0 - case Cons(_, xs) => 1 + size(xs) - } -} diff --git a/src/test/resources/regression/codegen/Prog003.scala b/src/test/resources/regression/codegen/Prog003.scala deleted file mode 100644 index 82958305781e8e1efad738720f0cf5f30ff0e6b0..0000000000000000000000000000000000000000 --- a/src/test/resources/regression/codegen/Prog003.scala +++ /dev/null @@ -1,14 +0,0 @@ -object Prog003 { - // Some tests for tuples - def wrap(x : Int, b : Boolean) : (Int,Boolean) = (x,b) - - def fst(t : (Int,Boolean)) : Int = t._1 - - def snd(t : (Int,Boolean)) : Boolean = t._2 - - def swap(t : (Int,Boolean)) : (Boolean,Int) = { - val (i,b) = t - - (b,i) - } -} diff --git a/src/test/resources/regression/codegen/Prog004.scala b/src/test/resources/regression/codegen/Prog004.scala deleted file mode 100644 index cf5ece1a1a0e9d85ace9fbefdf4f70c43ffe7a15..0000000000000000000000000000000000000000 --- a/src/test/resources/regression/codegen/Prog004.scala +++ /dev/null @@ -1,17 +0,0 @@ -object Prog004 { - def set1() : Set[Int] = { - Set(6, 7) - } - - def set2() : Set[Int] = { - set1() ++ Set(4) - } - - def set3() : Set[Int] = { - set2() ** set1() - } - - def set4() : Set[Int] = { - set2() -- set1() - } -} diff --git a/src/test/resources/regression/codegen/README b/src/test/resources/regression/codegen/README deleted file mode 100644 index 2edec540798afef79cf99eae4253420d89fbc890..0000000000000000000000000000000000000000 --- a/src/test/resources/regression/codegen/README +++ /dev/null @@ -1,2 +0,0 @@ -This directory contains PureScala programs for which compilation (codegen) -should always succeed entirely. diff --git a/src/test/scala/leon/test/codegen/CodeGenEvaluation.scala b/src/test/scala/leon/test/codegen/CodeGenEvaluation.scala deleted file mode 100644 index 13d4a49484de741320c90acb8539010adf381cad..0000000000000000000000000000000000000000 --- a/src/test/scala/leon/test/codegen/CodeGenEvaluation.scala +++ /dev/null @@ -1,191 +0,0 @@ -package leon.test -package codegen - -import leon._ -import leon.plugin.{TemporaryInputPhase, ExtractionPhase} -import leon.codegen.CompilationUnit -import leon.purescala.Definitions._ -import leon.purescala.TypeTrees.TypeErrorException - -import org.scalatest.FunSuite - -import TestUtils._ - -class CodeGenEvaluation extends FunSuite { - private var counter : Int = 0 - private def nextInt() : Int = { - counter += 1 - counter - } - - object CodeTestPhase extends LeonPhase[Program,Option[CompilationUnit]] { - val name = "CodeGen" - val description = "Compiles a Leon program into Java methods" - - def run(ctx : LeonContext)(p : Program) : Option[CompilationUnit] = { - CompilationUnit.compileProgram(p) - } - } - - private case class Output(result : Option[CompilationUnit], reporter : Reporter) - - private def mkPipeline : Pipeline[List[String], Option[CompilationUnit]] = - ExtractionPhase andThen CodeTestPhase - - private def forProgram(name: String)(content: String)(block: Output => Unit) = { - test("PureScala program %3d: [%s]".format(nextInt(), name)) { - - val ctx = LeonContext( - settings = Settings( - synthesis = false, - xlang = false, - verify = false - ), - files = List(), - reporter = new SilentReporter - ) - - val pipeline = TemporaryInputPhase andThen ExtractionPhase andThen CodeTestPhase - - val result = pipeline.run(ctx)((content, Nil)) - - block(Output(result, ctx.reporter)) - } - } - - import purescala.Trees._ - - def javaEval(unit: CompilationUnit)(ex: Expr): Expr = { - val cp = unit.compileExpression(ex, Seq()) - cp.eval(Seq()) - } - - def getFunction(unit: CompilationUnit, name: String): FunDef = { - unit.program.definedFunctions.find(_.id.toString == name) match { - case Some(fd) => - fd - case _ => - throw new AssertionError("Could not find any function named '"+name+"'") - } - } - - def getCaseClass(unit: CompilationUnit, name: String): CaseClassDef = { - unit.program.mainObject.caseClassDef(name) - } - - forProgram("Simple Evaluation")( - """ -object Prog001 { - def fortyTwo() = 42 - - def plus(x : Int, y : Int) = x + y - - def double(x : Int) : Int = { - val a = x - a + a - } - - def implies(a : Boolean, b : Boolean) : Boolean = !a || b - - def abs(x : Int) : Int = { - if(x < 0) -x else x - } - - def factorial(i : Int) : Int = if(i <= 1) 1 else (i * factorial(i - 1)) -} - """ - ){ out => - assert(out.result.isDefined === true) - val unit = out.result.get - - val fact = getFunction(unit, "factorial") - - val expr1 = Plus(IntLiteral(5), IntLiteral(42)) - assert(javaEval(unit)(expr1) === IntLiteral(47)) - - - val expr2 = Plus(FunctionInvocation(fact, Seq(IntLiteral(5))), IntLiteral(42)) - assert(javaEval(unit)(expr2) === IntLiteral(162)) - - //Type error - intercept[TypeErrorException] { - val expr3 = FunctionInvocation(fact, Seq(BooleanLiteral(false))) - assert(javaEval(unit)(expr3) != IntLiteral(1), "This should be a type error") - } - } - - forProgram("Case Classes Evaluation")( - """ -object Prog002 { - sealed abstract class List - case class Nil() extends List - case class Cons(head : Int, tail : List) extends List - - def isNil(l : List) : Boolean = { - l == Nil() - } - - def size(l : List) : Int = l match { - case Nil() => 0 - case Cons(_, xs) => 1 + size(xs) - } - - def conscons(l: List): List = Cons(0, Cons(1, l)) -} - """ - ){ out => - assert(out.result.isDefined === true) - val unit = out.result.get - - val ccNil = getCaseClass(unit, "Nil") - val ccCons = getCaseClass(unit, "Cons") - val cons = getFunction(unit, "conscons") - - val expr1 = FunctionInvocation(cons, Seq(CaseClass(ccNil, Seq()))) - assert(javaEval(unit)(expr1) === CaseClass(ccCons, Seq(IntLiteral(0), CaseClass(ccCons, Seq(IntLiteral(1), CaseClass(ccNil, Seq())))))) - } - - forProgram("Set Evaluation")( - """ -object Sets { - def s0() : Set[Int] = Set() - def s1() : Set[Int] = Set(1, 2, 3) - def s2() : Set[Int] = Set(2, 4, 6) - def s3() : Set[Int] = s1() ++ s2() - def s4() : Set[Int] = s2() ++ s1() - def s5() : Set[Int] = s1() ** s2() - def s6() : Set[Int] = s2() ** s1() - def s7() : Set[Int] = s1() -- s2() - def s8() : Set[Int] = s2() -- s1() -} - """ - ){ out => - assert(out.result.isDefined === true) - val unit = out.result.get - - def asIntSet(e : Expr) : Option[Set[Int]] = e match { - case FiniteSet(es) => - val ois = es.map(_ match { - case IntLiteral(v) => Some(v) - case _ => None - }) - if(ois.forall(_.isDefined)) - Some(ois.map(_.get).toSet) - else - None - case _ => None - } - - def eval(f : String) : Option[Set[Int]] = asIntSet(javaEval(unit)(FunctionInvocation(getFunction(unit, f), Seq()))) - - assert(eval("s0") === Some(Set.empty[Int])) - assert(eval("s1") === Some(Set(1, 2, 3))) - assert(eval("s2") === Some(Set(2, 4, 6))) - assert(eval("s3") === Some(Set(1, 2, 3, 4, 6))) - assert(eval("s4") === Some(Set(2, 4, 6, 1, 3))) - assert(eval("s5") === Some(Set(2))) - assert(eval("s6") === Some(Set(2))) - assert(eval("s7") === Some(Set(1, 3))) - assert(eval("s8") === Some(Set(4, 6))) - } -} diff --git a/src/test/scala/leon/test/codegen/CodeGenRegression.scala b/src/test/scala/leon/test/codegen/CodeGenRegression.scala deleted file mode 100644 index 75b58e9339a236cd4d1c8d0e3d8731456e2ec511..0000000000000000000000000000000000000000 --- a/src/test/scala/leon/test/codegen/CodeGenRegression.scala +++ /dev/null @@ -1,71 +0,0 @@ -package leon.test -package codegen - -import leon._ -import leon.plugin.ExtractionPhase -import leon.codegen.CodeGenPhase -import leon.codegen.CompilationResult - -import org.scalatest.FunSuite - -import java.io.File - -import TestUtils._ - -class CodeGenRegression extends FunSuite { - private var counter : Int = 0 - private def nextInt() : Int = { - counter += 1 - counter - } - - private case class Output(result : CompilationResult, reporter : Reporter) - - private def mkPipeline : Pipeline[List[String],CompilationResult] = - ExtractionPhase andThen CodeGenPhase - - private def mkTest(file : File)(block: Output=>Unit) = { - val fullName = file.getPath() - val start = fullName.indexOf("regression") - val displayName = if(start != -1) { - fullName.substring(start, fullName.length) - } else { - fullName - } - - test("%3d: %s".format(nextInt(), displayName)) { - assert(file.exists && file.isFile && file.canRead, - "Benchmark [%s] is not a readable file".format(displayName)) - val ctx = LeonContext( - settings = Settings( - synthesis = false, - xlang = false, - verify = false - ), - files = List(file), - reporter = new SilentReporter - ) - - val pipeline = mkPipeline - - val result = pipeline.run(ctx)(file.getPath :: Nil) - - block(Output(result, ctx.reporter)) - } - } - - private def forEachFileIn(cat : String)(block : Output=>Unit) { - val fs = filesInResourceDir(cat, _.endsWith(".scala")) - - for(f <- fs) { - mkTest(f)(block) - } - } - - forEachFileIn("regression/codegen/") { output => - val Output(result, reporter) = output - assert(result.successful, "Compilation should be successful.") - assert(reporter.errorCount === 0) - assert(reporter.warningCount === 0) - } -} diff --git a/src/test/scala/leon/test/evaluators/EvaluatorsTests.scala b/src/test/scala/leon/test/evaluators/EvaluatorsTests.scala new file mode 100644 index 0000000000000000000000000000000000000000..1d2d690bc4b3a423778f503a3aaf9fd9e4c2905b --- /dev/null +++ b/src/test/scala/leon/test/evaluators/EvaluatorsTests.scala @@ -0,0 +1,347 @@ +package leon.test +package evaluators + +import leon._ + +import leon.evaluators._ + +import leon.plugin.{TemporaryInputPhase, ExtractionPhase} + +import leon.purescala.Common._ +import leon.purescala.Definitions._ +import leon.purescala.Trees._ +import leon.purescala.TypeTrees._ + +import org.scalatest.FunSuite + +class EvaluatorsTests extends FunSuite { + private implicit lazy val leonContext = LeonContext( + settings = Settings( + synthesis = false, + xlang = false, + verify = false + ), + files = List(), + reporter = new SilentReporter + ) + + private val evaluatorConstructors : List[(LeonContext,Program)=>Evaluator] = List( + new DefaultEvaluator(_,_), + new CodeGenEvaluator(_,_) + ) + + private def prepareEvaluators(implicit ctx : LeonContext, prog : Program) : List[Evaluator] = evaluatorConstructors.map(c => c(leonContext, prog)) + + private def parseString(str : String) : Program = { + val pipeline = TemporaryInputPhase andThen ExtractionPhase + + val errorsBefore = leonContext.reporter.errorCount + val warningsBefore = leonContext.reporter.warningCount + + val program = pipeline.run(leonContext)((str, Nil)) + + assert(leonContext.reporter.errorCount === errorsBefore) + assert(leonContext.reporter.warningCount === warningsBefore) + + program + } + + private def mkCall(name : String, args : Expr*)(implicit p : Program) = { + val fDef = p.definedFunctions.find(_.id.name == name) getOrElse { + throw new AssertionError("No function named '%s' defined in program.".format(name)) + } + + FunctionInvocation(fDef, args.toSeq) + } + + private def mkCaseClass(name : String, args : Expr*)(implicit p : Program) = { + val ccDef = p.mainObject.caseClassDef(name) + CaseClass(ccDef, args.toSeq) + } + + private def checkCompSuccess(evaluator : Evaluator, in : Expr) : Expr = { + evaluator.eval(in) match { + case EvaluationFailure(msg) => + throw new AssertionError("Evaluation of '%s' with evaluator '%s' should have succeeded, but it failed (%s).".format(in, evaluator.name, msg)) + + case EvaluationError(msg) => + throw new AssertionError("Evaluation of '%s' with evaluator '%s' should have succeeded, but the evaluator had an internal error (%s).".format(in, evaluator.name, msg)) + + case EvaluationSuccessful(result) => + result + } + } + + private def checkComp(evaluator : Evaluator, in : Expr, out : Expr) { + val result = checkCompSuccess(evaluator, in) + if(result != out) + throw new AssertionError("Evaluation of '%s' with evaluator '%s' should have produced '%s' but produced '%s' instead.".format(in, evaluator.name, out, result)) + } + + private def checkSetComp(evaluator : Evaluator, in : Expr, out : Set[Int]) { + val result = checkCompSuccess(evaluator, in) + + def asIntSet(e : Expr) : Option[Set[Int]] = e match { + case FiniteSet(es) => + val ois = es.map(_ match { + case IntLiteral(v) => Some(v) + case _ => None + }) + if(ois.forall(_.isDefined)) + Some(ois.map(_.get).toSet) + else + None + case _ => None + } + + asIntSet(result) match { + case Some(s) if s == out => + ; + + case _ => + throw new AssertionError("Evaluation of '%s' with evaluator '%s' should have produced a set '%s', but it produced '%s' instead.".format(in, evaluator.name, out, result)) + } + } + + private def checkMapComp(evaluator : Evaluator, in : Expr, out : Map[Int,Int]) { + val result = checkCompSuccess(evaluator, in) + + def asIntMap(e : Expr) : Option[Map[Int,Int]] = e match { + case FiniteMap(ss) => + val oips : Seq[Option[(Int,Int)]] = ss.map(_ match { + case (IntLiteral(f),IntLiteral(t)) => Some(f -> t) + case _ => None + }) + if(oips.forall(_.isDefined)) + Some(oips.map(_.get).toMap) + else + None + case _ => None + } + + asIntMap(result) match { + case Some(s) if s == out => + ; + + case _ => + throw new AssertionError("Evaluation of '%s' with evaluator '%s' should produced a map '%s', but it produced '%s' instead.".format(in, evaluator.name, out, result)) + } + } + + private def checkError(evaluator : Evaluator, in : Expr) { + evaluator.eval(in) match { + case EvaluationError(msg) => + throw new AssertionError("Evaluation of '%s' with evaluator '%s' should have failed, but it produced an internal error (%s).".format(in, evaluator.name, msg)) + + case EvaluationSuccessful(result) => + throw new AssertionError("Evaluation of '%s' with evaluator '%s' should have failed, but it produced the result '%s' instead.".format(in, evaluator.name, result)) + + case EvaluationFailure(_) => + // that's the desired outcome + } + } + + private def T = BooleanLiteral(true) + private def F = BooleanLiteral(false) + private def IL(i : Int) = IntLiteral(i) + + test("Arithmetic") { + val p = """|object Program { + | def plus(x : Int, y : Int) : Int = x + y + | def max(x : Int, y : Int) : Int = if(x >= y) x else y + | def square(i : Int) : Int = { val j = i; j * i } + | def abs(i : Int) : Int = if(i < 0) -i else i + | def intSqrt(n : Int) : Int = intSqrt0(abs(n), 0) + | def intSqrt0(n : Int, c : Int) : Int = { + | val s = square(c+1) + | if(s > n) c else intSqrt0(n, c+1) + | } + | def div(x : Int, y : Int) : Int = (x / y) + | def mod(x : Int, y : Int) : Int = (x % y) + |} + |""".stripMargin + + implicit val prog = parseString(p) + + val evaluators = prepareEvaluators + + for(e <- evaluators) { + // Some simple math. + checkComp(e, mkCall("plus", IL(60), UMinus(IL(18))), IL(42)) + checkComp(e, mkCall("max", IL(4), IL(42)), IL(42)) + checkComp(e, mkCall("max", IL(42), UMinus(IL(42))), IL(42)) + checkComp(e, mkCall("intSqrt", UMinus(IL(1800))), IL(42)) + checkComp(e, mkCall("div", IL(7), IL(5)), IL(1)) + checkComp(e, mkCall("div", IL(7), IL(-5)), IL(-1)) + checkComp(e, mkCall("div", IL(-7), IL(5)), IL(-1)) + checkComp(e, mkCall("div", IL(-7), IL(-5)), IL(1)) + checkComp(e, mkCall("mod", IL(7), IL(5)), IL(2)) + checkComp(e, mkCall("mod", IL(7), IL(-5)), IL(2)) + checkComp(e, mkCall("mod", IL(-7), IL(5)), IL(-2)) + checkComp(e, mkCall("mod", IL(-7), IL(-5)), IL(-2)) + + // Things that should crash. + checkError(e, mkCall("div", IL(42), IL(0))) + checkError(e, mkCall("mod", IL(42), IL(0))) + } + } + + test("Booleans") { + val p = """|object Program { + |def and1(x : Boolean, y : Boolean) : Boolean = x && y + |def or1(x : Boolean, y : Boolean) : Boolean = x || y + |def and2(x : Boolean, y : Boolean) : Boolean = !(!x || !y) + |def or2(x : Boolean, y : Boolean) : Boolean = !(!x && !y) + |def safe(n : Int) : Boolean = (n != 0 && (1/n == n)) + |}""".stripMargin + + implicit val prog = parseString(p) + + val evaluators = prepareEvaluators + + for(e <- evaluators) { + checkComp(e, mkCall("and1", F, F), F) + checkComp(e, mkCall("and1", F, T), F) + checkComp(e, mkCall("and1", T, F), F) + checkComp(e, mkCall("and1", T, T), T) + checkComp(e, mkCall("and2", F, F), F) + checkComp(e, mkCall("and2", F, T), F) + checkComp(e, mkCall("and2", T, F), F) + checkComp(e, mkCall("and2", T, T), T) + checkComp(e, mkCall("or1", F, F), F) + checkComp(e, mkCall("or1", F, T), T) + checkComp(e, mkCall("or1", T, F), T) + checkComp(e, mkCall("or1", T, T), T) + checkComp(e, mkCall("or2", F, F), F) + checkComp(e, mkCall("or2", F, T), T) + checkComp(e, mkCall("or2", T, F), T) + checkComp(e, mkCall("or2", T, T), T) + + checkComp(e, mkCall("safe", IL(1)), T) + checkComp(e, mkCall("safe", IL(2)), F) + + // This one needs short-circuit. + checkComp(e, mkCall("safe", IL(0)), F) + } + } + + test("Case classes") { + val p = """|object Program { + | sealed abstract class List + | case class Nil() extends List + | case class Cons(head : Int, tail : List) extends List + | + | def size(l : List) : Int = l match { + | case Nil() => 0 + | case Cons(_, xs) => 1 + size(xs) + | } + | + | def compare(l1 : List, l2 : List) : Boolean = (l1 == l2) + | + | def head(l : List) : Int = l match { + | case Cons(h, _) => h + | } + |}""".stripMargin + + implicit val prog = parseString(p) + + val evaluators = prepareEvaluators + + val nil = mkCaseClass("Nil") + val cons12a = mkCaseClass("Cons", IL(1), mkCaseClass("Cons", IL(2), mkCaseClass("Nil"))) + val cons12b = mkCaseClass("Cons", IL(1), mkCaseClass("Cons", IL(2), mkCaseClass("Nil"))) + + for(e <- evaluators) { + checkComp(e, mkCall("size", nil), IL(0)) + checkComp(e, mkCall("size", cons12a), IL(2)) + checkComp(e, mkCall("compare", nil, cons12a), F) + checkComp(e, mkCall("compare", cons12a, cons12b), T) + checkComp(e, mkCall("head", cons12a), IL(1)) + + // Match error + checkError(e, mkCall("head", nil)) + } + } + + test("Sets") { + val p = """|object Program { + | sealed abstract class List + | case class Nil() extends List + | case class Cons(head : Int, tail : List) extends List + | + | def content(l : List) : Set[Int] = l match { + | case Nil() => Set.empty[Int] + | case Cons(x, xs) => Set(x) ++ content(xs) + | } + | + | def finite() : Set[Int] = Set(1, 2, 3) + | def build(x : Int, y : Int, z : Int) : Set[Int] = Set(x, y, z) + | def union(s1 : Set[Int], s2 : Set[Int]) : Set[Int] = s1 ++ s2 + | def inter(s1 : Set[Int], s2 : Set[Int]) : Set[Int] = s1 ** s2 + | def diff(s1 : Set[Int], s2 : Set[Int]) : Set[Int] = s1 -- s2 + |}""".stripMargin + + implicit val prog = parseString(p) + + val evaluators = prepareEvaluators + + val nil = mkCaseClass("Nil") + val cons12 = mkCaseClass("Cons", IL(1), mkCaseClass("Cons", IL(2), mkCaseClass("Nil"))) + + val semp = FiniteSet(Seq.empty).setType(SetType(Int32Type)) + val s123 = FiniteSet(Seq(IL(1), IL(2), IL(3))).setType(SetType(Int32Type)) + val s246 = FiniteSet(Seq(IL(2), IL(4), IL(6))).setType(SetType(Int32Type)) + + for(e <- evaluators) { + checkSetComp(e, mkCall("finite"), Set(1, 2, 3)) + checkSetComp(e, mkCall("content", nil), Set.empty) + checkSetComp(e, mkCall("content", cons12), Set(1,2)) + checkSetComp(e, mkCall("build", IL(1), IL(2), IL(3)), Set(1,2,3)) + checkSetComp(e, mkCall("build", IL(1), IL(2), IL(2)), Set(1,2)) + checkSetComp(e, mkCall("union", s123, s246), Set(1,2,3,4,6)) + checkSetComp(e, mkCall("union", s246, s123), Set(1,2,3,4,6)) + checkComp(e, Equals(mkCall("union", s123, s246), mkCall("union", s246, s123)), T) + checkSetComp(e, mkCall("inter", s123, s246), Set(2)) + checkSetComp(e, mkCall("inter", s246, s123), Set(2)) + checkComp(e, Equals(mkCall("inter", s123, s246), mkCall("inter", s246, s123)), T) + checkSetComp(e, mkCall("diff", s123, s246), Set(1,3)) + checkSetComp(e, mkCall("diff", s246, s123), Set(4,6)) + checkComp(e, Equals(mkCall("diff", s123, s246), mkCall("diff", s246, s123)), F) + } + } + + test("Maps") { + val p = """|object Program { + |sealed abstract class PList + |case class PNil() extends PList + |case class PCons(headfst : Int, headsnd : Int, tail : PList) extends PList + | + |def toMap(pl : PList) : Map[Int,Int] = pl match { + | case PNil() => Map.empty[Int,Int] + | case PCons(f,s,xs) => toMap(xs).updated(f, s) + |} + | + |def finite0() : Map[Int,Int] = Map() + |def finite1() : Map[Int,Int] = Map(1 -> 2) + |def finite2() : Map[Int,Int] = Map(2 -> 3, 1 -> 2) + |def finite3() : Map[Int,Int] = finite1().updated(2, 3) + |}""".stripMargin + + implicit val prog = parseString(p) + + val evaluators = prepareEvaluators + + val cons1223 = mkCaseClass("PCons", IL(1), IL(2), mkCaseClass("PCons", IL(2), IL(3), mkCaseClass("PNil"))) + + for(e <- evaluators) { + checkMapComp(e, mkCall("finite0"), Map.empty) + checkMapComp(e, mkCall("finite1"), Map(1 -> 2)) + checkMapComp(e, mkCall("finite2"), Map(1 -> 2, 2 -> 3)) + checkComp(e, Equals(mkCall("finite1"), mkCall("finite2")), F) + checkComp(e, Equals(mkCall("finite2"), mkCall("finite3")), T) + checkMapComp(e, mkCall("toMap", cons1223), Map(1 -> 2, 2 -> 3)) + checkComp(e, MapIsDefinedAt(mkCall("finite2"), IL(2)), T) + checkComp(e, MapIsDefinedAt(mkCall("finite2"), IL(3)), F) + } + } +} diff --git a/src/test/scala/leon/test/purescala/LikelyEq.scala b/src/test/scala/leon/test/purescala/LikelyEq.scala index 5de9e10ea9024d270e53b89e71129c9b9c8ea1a7..f1a6e971b5e2958bd742e76f23c5e1420ace3b59 100644 --- a/src/test/scala/leon/test/purescala/LikelyEq.scala +++ b/src/test/scala/leon/test/purescala/LikelyEq.scala @@ -1,17 +1,20 @@ package leon.test.purescala -import leon.Evaluator._ -import leon.purescala.Trees._ -import leon.purescala.TreeOps.replace +import leon._ +import leon.evaluators._ import leon.purescala.Common._ +import leon.purescala.Definitions._ +import leon.purescala.TreeOps.replace +import leon.purescala.Trees._ /* - * Determine if two expressions over arithmetic variables are likely to be the same + * Determine if two expressions over arithmetic variables are likely to be equal. * * This is a probabilistic based approach, it does not rely on any external solver and can * only prove the non equality of two expressions. */ object LikelyEq { + private lazy val evaluator : Evaluator = new DefaultEvaluator(LeonContext(reporter = new SilentReporter), Program.empty) private val min = -5 private val max = 5 @@ -29,9 +32,9 @@ object LikelyEq { compare: (Expr, Expr) => Boolean = (e1, e2) => e1 == e2, defaultMap: Map[Identifier, Expr] = Map()): Boolean = { if(vs.isEmpty) { - val ndm = defaultMap.map{ case (id, expr) => (id, eval(Map(), expr, None).asInstanceOf[OK].result) } //TODO: not quite sure why I need to do this... - (eval(ndm, e1, None), eval(defaultMap, e2, None)) match { - case (OK(v1), OK(v2)) => compare(v1, v2) + val ndm = defaultMap.map { case (id, expr) => (id, evaluator.eval(expr).asInstanceOf[EvaluationSuccessful].value) } //TODO: not quite sure why I need to do this... + (evaluator.eval(e1, ndm), evaluator.eval(e2, defaultMap)) match { + case (EvaluationSuccessful(v1), EvaluationSuccessful(v2)) => compare(v1, v2) case (err1, err2) => sys.error("evaluation could not complete, got: (" + err1 + ", " + err2 + ")") } } else { @@ -46,14 +49,14 @@ object LikelyEq { val ne1 = replace(m, e1) val ne2 = replace(m, e2) val npre = replace(m, pre) - val ndm = defaultMap.map{ case (id, expr) => (id, eval(m.map{case (Variable(id), t) => (id, t)}, expr, None).asInstanceOf[OK].result) } - eval(ndm, npre, None) match { - case OK(BooleanLiteral(false)) => - case OK(BooleanLiteral(true)) => - val ev1 = eval(ndm, ne1, None) - val ev2 = eval(ndm, ne2, None) + val ndm = defaultMap.map{ case (id, expr) => (id, evaluator.eval(expr, m.map{case (Variable(id), t) => (id, t)}).asInstanceOf[EvaluationSuccessful].value) } + evaluator.eval(npre, ndm) match { + case EvaluationSuccessful(BooleanLiteral(false)) => + case EvaluationSuccessful(BooleanLiteral(true)) => + val ev1 = evaluator.eval(ne1, ndm) + val ev2 = evaluator.eval(ne2, ndm) (ev1, ev2) match { - case (OK(v1), OK(v2)) => if(!compare(v1, v2)) isEq = false + case (EvaluationSuccessful(v1), EvaluationSuccessful(v2)) => if(!compare(v1, v2)) isEq = false case (err1, err2) => sys.error("evaluation could not complete, got: (" + err1 + ", " + err2 + ")") } case err => sys.error("evaluation of precondition could not complete, got: " + err) diff --git a/src/test/scala/leon/test/purescala/LikelyEqSuite.scala b/src/test/scala/leon/test/purescala/LikelyEqSuite.scala index dcc0788b0f7125a0125319beff58969774ff03f1..4ba6a5845a67e0fbb602b47970f2feb95d80c405 100644 --- a/src/test/scala/leon/test/purescala/LikelyEqSuite.scala +++ b/src/test/scala/leon/test/purescala/LikelyEqSuite.scala @@ -2,13 +2,11 @@ package leon.test.purescala import org.scalatest.FunSuite -import leon.Evaluator -import leon.purescala.Trees._ import leon.purescala.Common._ +import leon.purescala.Trees._ class LikelyEqSuite extends FunSuite { - def i(x: Int) = IntLiteral(x) val xId = FreshIdentifier("x") diff --git a/src/test/scala/leon/test/purescala/TreeOpsTests.scala b/src/test/scala/leon/test/purescala/TreeOpsTests.scala index a7ca3dac335c46e7b694c3f76f08ffe983f22ce1..b19018e59191c6bb65facd15f51f5d05ae500c9f 100644 --- a/src/test/scala/leon/test/purescala/TreeOpsTests.scala +++ b/src/test/scala/leon/test/purescala/TreeOpsTests.scala @@ -14,15 +14,10 @@ import org.scalatest.FunSuite class TreeOpsTests extends FunSuite { private val silentContext = LeonContext(reporter = new SilentReporter) - private val emptyProgram = Program( - FreshIdentifier("Empty"), - ObjectDef(FreshIdentifier("Empty"), Seq.empty, Seq.empty) - ) - test("Path-aware simplifications") { import leon.solvers.z3.UninterpretedZ3Solver val solver = new UninterpretedZ3Solver(silentContext) - solver.setProgram(emptyProgram) + solver.setProgram(Program.empty) // TODO actually testing something here would be better, sorry // PS diff --git a/src/test/scala/leon/test/solvers/TimeoutSolverTests.scala b/src/test/scala/leon/test/solvers/TimeoutSolverTests.scala index 0372f4706915855f6639e8da7d926f5dcd6a739e..e94e071bc3b95953947d3af5bf86f781e4b90e15 100644 --- a/src/test/scala/leon/test/solvers/TimeoutSolverTests.scala +++ b/src/test/scala/leon/test/solvers/TimeoutSolverTests.scala @@ -26,14 +26,9 @@ class TimeoutSolverTests extends FunSuite { } } - private val emptyProgram = Program( - FreshIdentifier("empty"), - ObjectDef(FreshIdentifier("empty"), Seq.empty, Seq.empty) - ) - private def getTOSolver : Solver = { val s = new TimeoutSolver(new IdioticSolver(LeonContext()), 1) - s.setProgram(emptyProgram) + s.setProgram(Program.empty) s } diff --git a/src/test/scala/leon/test/synthesis/LinearEquationsSuite.scala b/src/test/scala/leon/test/synthesis/LinearEquationsSuite.scala index 1f49ae517ab0961489a803fe98f69d2aabbd83ab..95d8706beed1c963776c102ab1a6e3349cae1fb2 100644 --- a/src/test/scala/leon/test/synthesis/LinearEquationsSuite.scala +++ b/src/test/scala/leon/test/synthesis/LinearEquationsSuite.scala @@ -2,7 +2,6 @@ package leon.test.synthesis import org.scalatest.FunSuite -import leon.Evaluator import leon.purescala.Trees._ import leon.purescala.TypeTrees._ import leon.purescala.TreeOps._ diff --git a/src/test/scala/leon/test/verification/PureScalaVerificationRegression.scala b/src/test/scala/leon/test/verification/PureScalaVerificationRegression.scala index 77a64fef4dcbfe904c7e480bac5f61daf0384130..74ce1ddc96488bd5a401602de0d788546feac4f5 100644 --- a/src/test/scala/leon/test/verification/PureScalaVerificationRegression.scala +++ b/src/test/scala/leon/test/verification/PureScalaVerificationRegression.scala @@ -21,7 +21,7 @@ class PureScalaVerificationRegression extends FunSuite { private def mkPipeline : Pipeline[List[String],VerificationReport] = leon.plugin.ExtractionPhase andThen leon.verification.AnalysisPhase - private def mkTest(file : File, forError: Boolean = false)(block: Output=>Unit) = { + private def mkTest(file : File, leonOptions : Seq[LeonOption], forError: Boolean)(block: Output=>Unit) = { val fullName = file.getPath() val start = fullName.indexOf("regression") @@ -31,7 +31,7 @@ class PureScalaVerificationRegression extends FunSuite { fullName } - test("%3d: %s".format(nextInt(), displayName)) { + test("%3d: %s %s".format(nextInt(), displayName, leonOptions.mkString(" "))) { assert(file.exists && file.isFile && file.canRead, "Benchmark %s is not a readable file".format(displayName)) @@ -41,7 +41,7 @@ class PureScalaVerificationRegression extends FunSuite { xlang = false, verify = true ), - options = List(LeonFlagOption("feelinglucky")), + options = leonOptions.toList, files = List(file), reporter = new SilentReporter ) @@ -67,7 +67,8 @@ class PureScalaVerificationRegression extends FunSuite { _.endsWith(".scala")) for(f <- fs) { - mkTest(f, forError)(block) + mkTest(f, List(LeonFlagOption("feelinglucky")), forError)(block) + mkTest(f, List(LeonFlagOption("codegen"), LeonFlagOption("feelinglucky")), forError)(block) } } diff --git a/unmanaged/common/cafebabe_2.9.2-1.2.jar b/unmanaged/common/cafebabe_2.9.2-1.2.jar index 47ba4d4848e69cb70792570da656c576701949b6..bd36c4eee8b7548a6bb0e4fba9003597fc5bf2f6 100644 Binary files a/unmanaged/common/cafebabe_2.9.2-1.2.jar and b/unmanaged/common/cafebabe_2.9.2-1.2.jar differ