diff --git a/src/main/java/leon/codegen/runtime/LeonCodeGenRuntimeMonitor.java b/src/main/java/leon/codegen/runtime/LeonCodeGenRuntimeMonitor.java new file mode 100644 index 0000000000000000000000000000000000000000..12bcc82d81bf0a193dca90348c1db5ecd7f028a7 --- /dev/null +++ b/src/main/java/leon/codegen/runtime/LeonCodeGenRuntimeMonitor.java @@ -0,0 +1,23 @@ +/* Copyright 2009-2013 EPFL, Lausanne */ + +package leon.codegen.runtime; + +/** Such exceptions are thrown when the evaluator encounters a runtime error, + * for instance `.get` with an undefined key on a map. */ +public class LeonCodeGenRuntimeMonitor { + private int invocationsLeft; + + public LeonCodeGenRuntimeMonitor(int maxInvocations) { + this.invocationsLeft = maxInvocations; + } + + public void onInvoke() throws LeonCodeGenEvaluationException { + if(invocationsLeft < 0) { + return; + } else if(invocationsLeft == 0) { + throw new LeonCodeGenEvaluationException("Maximum number of invocations reached."); + } else { + invocationsLeft--; + } + } +} diff --git a/src/main/scala/leon/codegen/CodeGenEvalParams.scala b/src/main/scala/leon/codegen/CodeGenEvalParams.scala deleted file mode 100644 index 31f8c49df55d54acb48ed4d60d2c14f083c16df7..0000000000000000000000000000000000000000 --- a/src/main/scala/leon/codegen/CodeGenEvalParams.scala +++ /dev/null @@ -1,7 +0,0 @@ -package leon -package codegen - -case class CodeGenEvalParams ( - maxFunctionInvocations: Int = -1, - checkContracts: Boolean = false -) diff --git a/src/main/scala/leon/codegen/CodeGenParams.scala b/src/main/scala/leon/codegen/CodeGenParams.scala new file mode 100644 index 0000000000000000000000000000000000000000..b37325747c2b65210d73ff6f9ebbe50eaf1783d1 --- /dev/null +++ b/src/main/scala/leon/codegen/CodeGenParams.scala @@ -0,0 +1,12 @@ +package leon +package codegen + +case class CodeGenParams ( + maxFunctionInvocations: Int = -1, + checkContracts: Boolean = false + +) { + val recordInvocations = maxFunctionInvocations > -1 + + val requireMonitor = recordInvocations +} diff --git a/src/main/scala/leon/codegen/CodeGeneration.scala b/src/main/scala/leon/codegen/CodeGeneration.scala index 727c6ab2b5a311a6b2b9d614c45fb207cc45f587..2f3429ed04e697acd1425df678bf11d0a1896e09 100644 --- a/src/main/scala/leon/codegen/CodeGeneration.scala +++ b/src/main/scala/leon/codegen/CodeGeneration.scala @@ -26,6 +26,7 @@ object CodeGeneration { private val ErrorClass = "leon/codegen/runtime/LeonCodeGenRuntimeException" private val ImpossibleEvaluationClass = "leon/codegen/runtime/LeonCodeGenEvaluationException" private val HashingClass = "leon/codegen/runtime/LeonCodeGenRuntimeHashing" + private[codegen] val MonitorClass = "leon/codegen/runtime/LeonCodeGenRuntimeMonitor" def defToJVMName(d : Definition)(implicit env : CompilationEnvironment) : String = "Leon$CodeGen$" + d.id.uniqueName @@ -57,17 +58,21 @@ object CodeGeneration { // Assumes the CodeHandler has never received any bytecode. // Generates method body, and freezes the handler at the end. def compileFunDef(funDef : FunDef, ch : CodeHandler)(implicit env : CompilationEnvironment) { - val newMapping = funDef.args.map(_.id).zipWithIndex.toMap + val newMapping = if (env.params.requireMonitor) { + funDef.args.map(_.id).zipWithIndex.toMap.mapValues(_ + 1) + } else { + funDef.args.map(_.id).zipWithIndex.toMap + } val body = funDef.body.getOrElse(throw CompilationException("Can't compile a FunDef without body")) - val bodyWithPre = if(funDef.hasPrecondition && env.compileContracts) { + val bodyWithPre = if(funDef.hasPrecondition && env.params.checkContracts) { IfExpr(funDef.precondition.get, body, Error("Precondition failed")) } else { body } - val bodyWithPost = if(funDef.hasPostcondition && env.compileContracts) { + val bodyWithPost = if(funDef.hasPostcondition && env.params.checkContracts) { val Some((id, post)) = funDef.postcondition Let(id, bodyWithPre, IfExpr(post, Variable(id), Error("Postcondition failed")) ) } else { @@ -76,6 +81,10 @@ object CodeGeneration { val exprToCompile = purescala.TreeOps.matchToIfThenElse(bodyWithPost) + if (env.params.recordInvocations) { + ch << ALoad(0) << InvokeVirtual(MonitorClass, "onInvoke", "()V") + } + mkExpr(exprToCompile, ch)(env.withVars(newMapping)) funDef.returnType match { @@ -268,6 +277,9 @@ object CodeGeneration { val (cn, mn, ms) = env.funDefToMethod(fd).getOrElse { throw CompilationException("Unknown method : " + fd.id) } + if (env.params.requireMonitor) { + ch << ALoad(0) + } for(a <- as) { mkExpr(a, ch) } diff --git a/src/main/scala/leon/codegen/CompilationEnvironment.scala b/src/main/scala/leon/codegen/CompilationEnvironment.scala index c364b715311f2f0122db1c2ac6654280f15e98e1..21732db3d294ee4f1e2349de371d7c32620eb8b7 100644 --- a/src/main/scala/leon/codegen/CompilationEnvironment.scala +++ b/src/main/scala/leon/codegen/CompilationEnvironment.scala @@ -15,7 +15,7 @@ abstract class CompilationEnvironment() { val program: Program - val compileContracts: Boolean + val params: CodeGenParams // Returns (JVM) name of class, and signature of constructor def classDefToClass(classDef : ClassTypeDef) : Option[String] @@ -29,7 +29,7 @@ abstract class CompilationEnvironment() { def withVars(pairs : Map[Identifier,Int]) = { new CompilationEnvironment { val program = self.program - val compileContracts = self.compileContracts + val params = self.params def classDefToClass(classDef : ClassTypeDef) = self.classDefToClass(classDef) def funDefToMethod(funDef : FunDef) = self.funDefToMethod(funDef) def varToLocal(v : Identifier) = pairs.get(v).orElse(self.varToLocal(v)) @@ -38,7 +38,7 @@ abstract class CompilationEnvironment() { } object CompilationEnvironment { - def fromProgram(p : Program, _compileContracts: Boolean) : CompilationEnvironment = { + def fromProgram(p : Program, _params: CodeGenParams) : CompilationEnvironment = { import CodeGeneration.typeToJVM // This should change: it should contain the case classes before @@ -46,7 +46,7 @@ object CompilationEnvironment { implicit val initial = new CompilationEnvironment { val program = p - val compileContracts = _compileContracts + val params = _params private val cNames : Map[ClassTypeDef,String] = p.definedClasses.map(c => (c, CodeGeneration.defToJVMName(c)(this))).toMap @@ -60,8 +60,14 @@ object CompilationEnvironment { val fs = p.definedFunctions.filter(_.hasImplementation) + val monitorType = if (_params.requireMonitor) { + "L" + CodeGeneration.MonitorClass + ";" + } else { + "" + } + val fMap : Map[FunDef,(String,String,String)] = (fs.map { fd => - val sig = "(" + fd.args.map(a => typeToJVM(a.tpe)).mkString("") + ")" + typeToJVM(fd.returnType) + val sig = "(" + monitorType + fd.args.map(a => typeToJVM(a.tpe)).mkString("") + ")" + typeToJVM(fd.returnType) fd -> (className, fd.id.uniqueName, sig) }).toMap @@ -69,7 +75,7 @@ object CompilationEnvironment { new CompilationEnvironment { val program = p - val compileContracts = initial.compileContracts + val params = initial.params def classDefToClass(classDef : ClassTypeDef) = initial.classDefToClass(classDef) def funDefToMethod(funDef : FunDef) = fMap.get(funDef) diff --git a/src/main/scala/leon/codegen/CompilationUnit.scala b/src/main/scala/leon/codegen/CompilationUnit.scala index 99976d4c7b4e7aefe990906673dac3811f34e21c..c612803e6eeb77cbd24be8193394313fe0045c71 100644 --- a/src/main/scala/leon/codegen/CompilationUnit.scala +++ b/src/main/scala/leon/codegen/CompilationUnit.scala @@ -145,10 +145,18 @@ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFi cf.addDefaultConstructor + val argsTypes = args.map(a => typeToJVM(a.getType)) + + val realArgs = if (env.params.requireMonitor) { + ("L" + CodeGeneration.MonitorClass + ";") +: argsTypes + } else { + argsTypes + } + val m = cf.addMethod( typeToJVM(e.getType), "eval", - args.map(a => typeToJVM(a.getType)) : _* + realArgs : _* ) m.setFlags(( @@ -159,7 +167,11 @@ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFi val ch = m.codeHandler - val newMapping = args.zipWithIndex.toMap + val newMapping = if (env.params.requireMonitor) { + args.zipWithIndex.toMap.mapValues(_ + 1) + } else { + args.zipWithIndex.toMap + } val exprToCompile = purescala.TreeOps.matchToIfThenElse(e) @@ -187,8 +199,8 @@ class CompilationUnit(val program: Program, val classes: Map[Definition, ClassFi } object CompilationUnit { - def compileProgram(p: Program, compileContracts: Boolean = true): Option[CompilationUnit] = { - implicit val env = CompilationEnvironment.fromProgram(p, compileContracts) + def compileProgram(p: Program, params: CodeGenParams = CodeGenParams()): Option[CompilationUnit] = { + implicit val env = CompilationEnvironment.fromProgram(p, params) var classes = Map[Definition, ClassFile]() @@ -222,10 +234,17 @@ object CompilationUnit { for(funDef <- p.definedFunctions; (_,mn,_) <- env.funDefToMethod(funDef)) { + val argsTypes = funDef.args.map(a => typeToJVM(a.tpe)) + val realArgs = if (env.params.requireMonitor) { + ("L" + CodeGeneration.MonitorClass + ";") +: argsTypes + } else { + argsTypes + } + val m = cf.addMethod( typeToJVM(funDef.returnType), mn, - funDef.args.map(a => typeToJVM(a.tpe)) : _* + realArgs : _* ) m.setFlags(( METHOD_ACC_PUBLIC | diff --git a/src/main/scala/leon/codegen/CompiledExpression.scala b/src/main/scala/leon/codegen/CompiledExpression.scala index d1b4bbb4d763039ecf67fb583db01a094201bd35..0f4c8f855d8230d3ac885198693dc93303258828 100644 --- a/src/main/scala/leon/codegen/CompiledExpression.scala +++ b/src/main/scala/leon/codegen/CompiledExpression.scala @@ -14,6 +14,8 @@ import cafebabe.ByteCodes._ import cafebabe.ClassFileTypes._ import cafebabe.Flags._ +import runtime.LeonCodeGenRuntimeMonitor + import java.lang.reflect.InvocationTargetException class CompiledExpression(unit: CompilationUnit, cf: ClassFile, expression : Expr, argsDecl: Seq[Identifier]) { @@ -22,6 +24,8 @@ class CompiledExpression(unit: CompilationUnit, cf: ClassFile, expression : Expr private val exprType = expression.getType + private val params = unit.env.params + def argsToJVM(args: Seq[Expr]): Seq[AnyRef] = { args.map(unit.valueToJVM) } @@ -29,10 +33,16 @@ class CompiledExpression(unit: CompilationUnit, cf: ClassFile, expression : Expr def evalToJVM(args: Seq[AnyRef]): AnyRef = { assert(args.size == argsDecl.size) - if (args.isEmpty) { + val realArgs = if (params.requireMonitor) { + new LeonCodeGenRuntimeMonitor(params.maxFunctionInvocations) +: args + } else { + args + } + + if (realArgs.isEmpty) { meth.invoke(null) } else { - meth.invoke(null, args.toArray : _*) + meth.invoke(null, realArgs.toArray : _*) } } diff --git a/src/main/scala/leon/datagen/VanuatooDataGen.scala b/src/main/scala/leon/datagen/VanuatooDataGen.scala index 5215670e3fc8f7e1e095038165cea124c9e2aa50..7a579b2373c2cea2a0238e23fe7c6333cb90d6ab 100644 --- a/src/main/scala/leon/datagen/VanuatooDataGen.scala +++ b/src/main/scala/leon/datagen/VanuatooDataGen.scala @@ -16,7 +16,7 @@ import vanuatoo.{Pattern => VPattern, _} import evaluators._ class VanuatooDataGen(ctx: LeonContext, p: Program) extends DataGenerator { - val unit = CompilationUnit.compileProgram(p, compileContracts = false).get + val unit = CompilationUnit.compileProgram(p).get val ints = (for (i <- Set(0, 1, 2, 3)) yield { i -> Constructor[Expr, TypeTree](List(), Int32Type, s => IntLiteral(i), ""+i) diff --git a/src/main/scala/leon/evaluators/CodeGenEvaluator.scala b/src/main/scala/leon/evaluators/CodeGenEvaluator.scala index 87314546f58dcddfb45bb180cad14872d0bba8ff..6d290c56955bf9c238024a70b5bc7975395bbd7d 100644 --- a/src/main/scala/leon/evaluators/CodeGenEvaluator.scala +++ b/src/main/scala/leon/evaluators/CodeGenEvaluator.scala @@ -10,14 +10,15 @@ import purescala.Trees._ import purescala.TypeTrees._ import codegen.CompilationUnit +import codegen.CodeGenParams 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 this(ctx : LeonContext, prog : Program, params: CodeGenParams = CodeGenParams()) { + this(ctx, CompilationUnit.compileProgram(prog, params).get) // this .get is dubious... } def eval(expression : Expr, mapping : Map[Identifier,Expr]) : EvaluationResult = { diff --git a/src/main/scala/leon/synthesis/condabd/evaluation/CodeGenExampleRunner.scala b/src/main/scala/leon/synthesis/condabd/evaluation/CodeGenExampleRunner.scala index d6fd0911c982cf88c3dfe72218208ebdba17a725..1dc6d1d6be4995c1e22d788066e7a10d94631769 100644 --- a/src/main/scala/leon/synthesis/condabd/evaluation/CodeGenExampleRunner.scala +++ b/src/main/scala/leon/synthesis/condabd/evaluation/CodeGenExampleRunner.scala @@ -10,7 +10,7 @@ import leon.purescala.Trees._ import leon.purescala.Definitions.{ FunDef, VarDecl, Program, ObjectDef } import leon.purescala.Common.{ Identifier, FreshIdentifier } import leon.purescala.TreeOps -import leon.codegen.CodeGenEvalParams +import leon.codegen.CodeGenParams import examples._ import ranking._ @@ -19,14 +19,14 @@ import _root_.insynth.util.logging.HasLogger case class CodeGenExampleRunner(program: Program, funDef: FunDef, ctx: LeonContext, candidates: Seq[Candidate], inputExamples: Seq[Example], - params: CodeGenEvalParams = CodeGenEvalParams(maxFunctionInvocations = 200, checkContracts = true)) extends ExampleRunner(inputExamples) with HasLogger { + params: CodeGenParams = CodeGenParams(maxFunctionInvocations = 200, checkContracts = true)) extends ExampleRunner(inputExamples) with HasLogger { private var _examples = ArrayBuffer(transformExamples(inputExamples): _*) val evaluationContext = ctx fine("building codegen evaluator with program:\n" + program) - lazy val _evaluator = new CodeGenEvaluator(evaluationContext, program/*TODO:, params*/) + lazy val _evaluator = new CodeGenEvaluator(evaluationContext, program, params) override def getEvaluator = _evaluator def transformExamples(examples: Seq[Example]) = diff --git a/src/main/scala/leon/synthesis/condabd/evaluation/EvaluationStrategy.scala b/src/main/scala/leon/synthesis/condabd/evaluation/EvaluationStrategy.scala index 1ab73543a6a7c77f972f90856bd6496591286436..ee0e54ff3bfb00f9973c61dc102ae559a3d6c7e3 100644 --- a/src/main/scala/leon/synthesis/condabd/evaluation/EvaluationStrategy.scala +++ b/src/main/scala/leon/synthesis/condabd/evaluation/EvaluationStrategy.scala @@ -8,7 +8,7 @@ import leon.purescala.Trees._ import leon.purescala.Definitions.{ FunDef, VarDecl, Program, ObjectDef } import leon.purescala.Common.{ Identifier, FreshIdentifier } import leon.purescala.TreeOps -import leon.codegen.CodeGenEvalParams +import leon.codegen.CodeGenParams import insynth.reconstruction.Output import _root_.insynth.util.logging._ @@ -94,7 +94,7 @@ case class CodeGenEvaluationStrategy(program: Program, funDef: FunDef, ctx: Leon finest("New program looks like: " + newProgram) finest("Candidates look like: " + candidates.map(_.prepareExpression).mkString("\n")) - val params = CodeGenEvalParams(maxFunctionInvocations = maxSteps, checkContracts = true) + val params = CodeGenParams(maxFunctionInvocations = maxSteps, checkContracts = true) exampleRunner = CodeGenExampleRunner(newProgram, funDef, ctx, candidates, inputExamples, params) @@ -108,4 +108,4 @@ case class CodeGenEvaluationStrategy(program: Program, funDef: FunDef, ctx: Leon override def getExampleRunner = exampleRunner override def getEvaluation = evaluation -} \ No newline at end of file +} diff --git a/src/test/scala/leon/test/condabd/EvaluationTest.scala b/src/test/scala/leon/test/condabd/EvaluationTest.scala index ef7d53911fe434c75ac809968843666aadb7f2dd..2f22361a41dedfdd51774701ed2e22afb847415b 100644 --- a/src/test/scala/leon/test/condabd/EvaluationTest.scala +++ b/src/test/scala/leon/test/condabd/EvaluationTest.scala @@ -10,7 +10,7 @@ import java.io.{ BufferedWriter, FileWriter, File } import leon.evaluators.CodeGenEvaluator import leon.purescala.TreeOps import leon.purescala.Common._ -import leon.codegen.CodeGenEvalParams +import leon.codegen.CodeGenParams import leon.purescala.TypeTrees._ import leon.evaluators.EvaluationResults._ @@ -248,11 +248,11 @@ class EvaluationTest extends FunSuite { val pairs = List(body1, body2) map (formExecutable(_)) val candidates = pairs map (_._1) - val params = CodeGenEvalParams(maxFunctionInvocations = 500, checkContracts = true) + val params = CodeGenParams(maxFunctionInvocations = 500, checkContracts = true) val evaluator = new CodeGenEvaluator(sctx.context, program.copy(mainObject = program.mainObject.copy(defs = program.mainObject.defs ++ pairs.map(_._2))) - /*, params*/) + , params) val eval1 = (for (ind <- 0 until inputExamples.size) yield { val res = evaluator.eval(candidates(0), inputExamples(ind).map) @@ -302,11 +302,11 @@ class EvaluationTest extends FunSuite { val pairs = List(body1, body2) map (formExecutable(_)) val candidates = pairs map (_._1) - val params = CodeGenEvalParams(maxFunctionInvocations = 500, checkContracts = true) + val params = CodeGenParams(maxFunctionInvocations = 500, checkContracts = true) val evaluator = new CodeGenEvaluator(sctx.context, program.copy(mainObject = program.mainObject.copy(defs = program.mainObject.defs ++ pairs.map(_._2))) - /*, params*/) + , params) val eval1 = (for (ind <- 0 until inputExamples.size) yield { val res = evaluator.eval(candidates(0), inputExamples(ind).map) @@ -326,4 +326,4 @@ class EvaluationTest extends FunSuite { } } } -} \ No newline at end of file +}