diff --git a/testcases/verification/compilation/ExprCompiler.scala b/testcases/verification/compilation/ExprCompiler.scala new file mode 100644 index 0000000000000000000000000000000000000000..7518c21fb8ba7349e49c37c8acac6f99aaae2cdd --- /dev/null +++ b/testcases/verification/compilation/ExprCompiler.scala @@ -0,0 +1,89 @@ +import leon.lang._ +import leon.lang.Option +import leon.collection._ +import leon.annotation._ +import leon.proof._ +import leon.lang.synthesis._ + +object TinyCertifiedCompiler { + abstract class ByteCode[A] + case class Load[A](c: A) extends ByteCode[A] // loads a constant in to the stack + case class OpInst[A]() extends ByteCode[A] + + abstract class ExprTree[A] + case class Const[A](c: A) extends ExprTree[A] + case class Op[A](e1: ExprTree[A], e2: ExprTree[A]) extends ExprTree[A] + + def compile[A](e: ExprTree[A]): List[ByteCode[A]] = { + e match { + case Const(c) => + Cons(Load(c), Nil[ByteCode[A]]()) + case Op(e1, e2) => + (compile(e1) ++ compile(e2)) ++ Cons(OpInst(), Nil[ByteCode[A]]()) + } + } + + def op[A](x: A, y: A): A = { + ???[A] + } + + def run[A](bytecode: List[ByteCode[A]], S: List[A]): List[A] = { + (bytecode, S) match { + case (Cons(Load(c), tail), _) => + run(tail, Cons[A](c, S)) // adding elements to the head of the stack + case (Cons(OpInst(), tail), Cons(x, Cons(y, rest))) => + run(tail, Cons[A](op(y, x), rest)) + case (Cons(_, tail), _) => + run(tail, S) + case (Nil(), _) => // no bytecode to execute + S + } + } + + def interpret[A](e: ExprTree[A]): A = { + e match { + case Const(c) => c + case Op(e1, e2) => + op(interpret(e1), interpret(e2)) + } + } + + def runAppendLemma[A](l1: List[ByteCode[A]], l2: List[ByteCode[A]], S: List[A]): Boolean = { + // lemma + (run(l1 ++ l2, S) == run(l2, run(l1, S))) because + // induction scheme (induct over l1) + (l1 match { + case Nil() => + true + case Cons(h, tail) => + (h, S) match { + case (Load(c), _) => + runAppendLemma(tail, l2, Cons[A](c, S)) + case (OpInst(), Cons(x, Cons(y, rest))) => + runAppendLemma(tail, l2, Cons[A](op(y, x), rest)) + case (_, _) => + runAppendLemma(tail, l2, S) + case _ => + true + } + }) + }.holds + + def compileInterpretEquivalenceLemma[A](e: ExprTree[A], S: List[A]): Boolean = { + // lemma + (run(compile(e), S) == interpret(e) :: S) because + // induction scheme + (e match { + case Const(c) => + true + case Op(e1, e2) => + // lemma instantiation + val c1 = compile(e1) + val c2 = compile(e2) + runAppendLemma((c1 ++ c2), Cons[ByteCode[A]](OpInst[A](), Nil[ByteCode[A]]()), S) && + runAppendLemma(c1, c2, S) && + compileInterpretEquivalenceLemma(e1, S) && + compileInterpretEquivalenceLemma(e2, Cons[A](interpret(e1), S)) + }) + } holds +} diff --git a/testcases/verification/compilation/IntExprCompiler.scala b/testcases/verification/compilation/IntExprCompiler.scala new file mode 100644 index 0000000000000000000000000000000000000000..b7f46a49a27cf4092518afa08f0cef5ff8f724c7 --- /dev/null +++ b/testcases/verification/compilation/IntExprCompiler.scala @@ -0,0 +1,107 @@ +import leon.lang._ +import leon.lang.Option +import leon.collection._ +import leon.annotation._ +import leon.proof._ + +object TinyCertifiedCompiler { + + abstract class ByteCode + case class Load(c: BigInt) extends ByteCode // loads a constant in to the stack + case class OpInst() extends ByteCode + + abstract class ExprTree + case class Const(c: BigInt) extends ExprTree + case class Op(e1: ExprTree, e2: ExprTree) extends ExprTree + + def compile(e: ExprTree): List[ByteCode] = { + e match { + case Const(c) => + Cons(Load(c), Nil[ByteCode]()) + case Op(e1, e2) => + (compile(e1) ++ compile(e2)) ++ Cons(OpInst(), Nil[ByteCode]()) + } + } + + def op(x: BigInt, y: BigInt): BigInt = { + x - y + } + + def run(bytecode: List[ByteCode], S: List[BigInt]): List[BigInt] = { + (bytecode, S) match { + case (Cons(Load(c), tail), _) => + run(tail, Cons[BigInt](c, S)) // adding elements to the head of the stack + case (Cons(OpInst(), tail), Cons(x, Cons(y, rest))) => + run(tail, Cons[BigInt](op(y, x), rest)) + case (Cons(_, tail), _) => + run(tail, S) + case (Nil(), _) => // no bytecode to execute + S + } + } + + def interpret(e: ExprTree): BigInt = { + e match { + case Const(c) => c + case Op(e1, e2) => + op(interpret(e1), interpret(e2)) + } + } + + def runAppendLemma(l1: List[ByteCode], l2: List[ByteCode], S: List[BigInt]): Boolean = { + // lemma + (run(l1 ++ l2, S) == run(l2, run(l1, S))) because + // induction scheme (induct over l1) + (l1 match { + case Nil() => + true + case Cons(h, tail) => + (h, S) match { + case (Load(c), _) => + runAppendLemma(tail, l2, Cons[BigInt](c, S)) + case (OpInst(), Cons(x, Cons(y, rest))) => + runAppendLemma(tail, l2, Cons[BigInt](op(y, x), rest)) + case (_, _) => + runAppendLemma(tail, l2, S) + case _ => + true + } + }) + }.holds + + def run1(bytecode: List[ByteCode], S: List[BigInt]): List[BigInt] = { + (bytecode, S) match { + case (Cons(Load(c), tail), _) => + run1(tail, Cons[BigInt](c, S)) // adding elements to the head of the stack + case (Cons(OpInst(), tail), Cons(x, Cons(y, rest))) => + run1(tail, Cons[BigInt](op(x, y), rest)) + case (Cons(_, tail), _) => + run1(tail, S) + case (Nil(), _) => // no bytecode to execute + S + } + } + + def compileInterpretEquivalenceLemma1(e: ExprTree, S: List[BigInt]): Boolean = { + // lemma + (run1(compile(e), S) == interpret(e) :: S) + } holds + + def compileInterpretEquivalenceLemma(e: ExprTree, S: List[BigInt]): Boolean = { + // lemma + (run(compile(e), S) == interpret(e) :: S) because + // induction scheme + (e match { + case Const(c) => + true + case Op(e1, e2) => + // lemma instantiation + val c1 = compile(e1) + val c2 = compile(e2) + runAppendLemma((c1 ++ c2), Cons(OpInst(), Nil[ByteCode]()), S) && + runAppendLemma(c1, c2, S) && + compileInterpretEquivalenceLemma(e1, S) && + compileInterpretEquivalenceLemma(e2, Cons[BigInt](interpret(e1), S)) + }) + } holds +}